This is an automated email from the ASF dual-hosted git repository.

mbudiu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/calcite.git


The following commit(s) were added to refs/heads/main by this push:
     new a1caa65297 [CALCITE-5094] Calcite JDBC Adapter and Avatica should 
support MySQL UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT
a1caa65297 is described below

commit a1caa65297b0ba8aa150ef96265afbb8da87e207
Author: Zhengqiang Duan <[email protected]>
AuthorDate: Tue Sep 30 22:00:11 2025 +0800

    [CALCITE-5094] Calcite JDBC Adapter and Avatica should support MySQL 
UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT
---
 .../apache/calcite/adapter/jdbc/JdbcSchema.java    |  4 +-
 .../apache/calcite/prepare/CalcitePrepareImpl.java |  2 +-
 .../org/apache/calcite/sql/type/SqlTypeName.java   | 22 ++++++++++
 .../apache/calcite/sql/test/SqlTypeNameTest.java   | 28 +++++++++++++
 .../java/org/apache/calcite/test/JdbcTest.java     | 33 +++++++++++++++
 .../apache/calcite/linq4j/tree/UnsignedType.java   |  2 +-
 .../org/apache/calcite/test/CalciteAssert.java     | 49 +++++++++++++++++++++-
 7 files changed, 136 insertions(+), 4 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java 
b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
index dad1294075..eb1f11b74d 100644
--- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
+++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcSchema.java
@@ -423,7 +423,9 @@ private static RelDataType sqlType(RelDataTypeFactory 
typeFactory, int dataType,
       int precision, int scale, @Nullable String typeString) {
     // Fall back to ANY if type is unknown
     final SqlTypeName sqlTypeName =
-        Util.first(SqlTypeName.getNameForJdbcType(dataType), SqlTypeName.ANY);
+        Util.first(typeString != null && 
typeString.toUpperCase(Locale.ROOT).contains("UNSIGNED")
+            ? SqlTypeName.getNameForUnsignedJdbcType(dataType)
+            : SqlTypeName.getNameForJdbcType(dataType), SqlTypeName.ANY);
     switch (sqlTypeName) {
     case ARRAY:
       RelDataType component = null;
diff --git 
a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java 
b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
index 1f036986f4..6bb21e1ae2 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalcitePrepareImpl.java
@@ -793,7 +793,7 @@ private static ColumnMetaData metaData(JavaTypeFactory 
typeFactory, int ordinal,
         type.isNullable()
             ? DatabaseMetaData.columnNullable
             : DatabaseMetaData.columnNoNulls,
-        true,
+        !SqlTypeName.UNSIGNED_TYPES.contains(type.getSqlTypeName()),
         type.getPrecision(),
         fieldName,
         origin(origins, 0),
diff --git a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java 
b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
index 79eeaa95bd..73557737ca 100644
--- a/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
+++ b/core/src/main/java/org/apache/calcite/sql/type/SqlTypeName.java
@@ -289,6 +289,18 @@ public enum SqlTypeName {
           .put(Types.ARRAY, ARRAY)
           .build();
 
+  /**
+   * Mapping between JDBC type codes and their corresponding
+   * {@link org.apache.calcite.sql.type.SqlTypeName} for unsigned types.
+   */
+  private static final Map<Integer, SqlTypeName> UNSIGNED_JDBC_TYPE_TO_NAME =
+      ImmutableMap.<Integer, SqlTypeName>builder()
+          .put(Types.TINYINT, UTINYINT)
+          .put(Types.SMALLINT, USMALLINT)
+          .put(Types.INTEGER, UINTEGER)
+          .put(Types.BIGINT, UBIGINT)
+          .build();
+
   /**
    * Bitwise-or of flags indicating allowable precision/scale combinations.
    */
@@ -447,6 +459,16 @@ public int getDefaultScale() {
     return JDBC_TYPE_TO_NAME.get(jdbcType);
   }
 
+  /**
+   * Gets the SqlTypeName corresponding to an unsigned JDBC type.
+   *
+   * @param jdbcType the unsigned JDBC type of interest
+   * @return corresponding SqlTypeName, or null if the type is not known
+   */
+  public static @Nullable SqlTypeName getNameForUnsignedJdbcType(int jdbcType) 
{
+    return UNSIGNED_JDBC_TYPE_TO_NAME.get(jdbcType);
+  }
+
   /**
    * Returns the limit of this datatype. For example,
    *
diff --git 
a/core/src/test/java/org/apache/calcite/sql/test/SqlTypeNameTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlTypeNameTest.java
index 4281179b0d..457c627278 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlTypeNameTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlTypeNameTest.java
@@ -40,6 +40,10 @@
 import static org.apache.calcite.sql.type.SqlTypeName.TIME;
 import static org.apache.calcite.sql.type.SqlTypeName.TIMESTAMP;
 import static org.apache.calcite.sql.type.SqlTypeName.TINYINT;
+import static org.apache.calcite.sql.type.SqlTypeName.UBIGINT;
+import static org.apache.calcite.sql.type.SqlTypeName.UINTEGER;
+import static org.apache.calcite.sql.type.SqlTypeName.USMALLINT;
+import static org.apache.calcite.sql.type.SqlTypeName.UTINYINT;
 import static org.apache.calcite.sql.type.SqlTypeName.VARBINARY;
 import static org.apache.calcite.sql.type.SqlTypeName.VARCHAR;
 
@@ -81,6 +85,30 @@ class SqlTypeNameTest {
     assertThat("BIGINT did not map to BIGINT", tn, is(BIGINT));
   }
 
+  @Test void testUnsignedTinyint() {
+    SqlTypeName tn =
+        SqlTypeName.getNameForUnsignedJdbcType(Types.TINYINT);
+    assertThat("TINYINT did not map to UTINYINT", tn, is(UTINYINT));
+  }
+
+  @Test void testUnsignedSmallint() {
+    SqlTypeName tn =
+        SqlTypeName.getNameForUnsignedJdbcType(Types.SMALLINT);
+    assertThat("SMALLINT did not map to USMALLINT", tn, is(USMALLINT));
+  }
+
+  @Test void testUnsignedInteger() {
+    SqlTypeName tn =
+        SqlTypeName.getNameForUnsignedJdbcType(Types.INTEGER);
+    assertThat("INTEGER did not map to UINTEGER", tn, is(UINTEGER));
+  }
+
+  @Test void testUnsignedBigint() {
+    SqlTypeName tn =
+        SqlTypeName.getNameForUnsignedJdbcType(Types.BIGINT);
+    assertThat("BIGINT did not map to UBIGINT", tn, is(UBIGINT));
+  }
+
   @Test void testFloat() {
     SqlTypeName tn =
         SqlTypeName.getNameForJdbcType(Types.FLOAT);
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java 
b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 64fb5b0caf..de66b636f5 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -9077,6 +9077,39 @@ void checkCalciteSchemaGetSubSchemaMap(boolean cache) {
         });
   }
 
+  /** Test case for
+   * <a 
href="https://issues.apache.org/jira/browse/CALCITE-5094";>[CALCITE-5094]
+   * Calcite JDBC Adapter and Avatica should support
+   * MySQL UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT</a>. */
+  @Test void testMySQLUnsignedType() {
+    CalciteAssert.that()
+        .with(CalciteAssert.SchemaSpec.UNSIGNED_TYPE)
+        .with(Lex.MYSQL)
+        .query("SELECT * FROM test_unsigned WHERE utiny_value = ? "
+            + "AND usmall_value = ? AND uint_value = ? AND ubig_value = ?")
+        .consumesPreparedStatement(p -> {
+          p.setInt(1, 255);
+          p.setInt(2, 65535);
+          p.setLong(3, 4294967295L);
+          p.setBigDecimal(4, new BigDecimal("18446744073709551615"));
+        })
+        .returns(resultSet -> {
+          try {
+            assertTrue(resultSet.next());
+            final Integer uTinyInt = resultSet.getInt(1);
+            final Integer uSmallInt = resultSet.getInt(2);
+            final Long uInteger = resultSet.getLong(3);
+            final BigDecimal uBigInt = resultSet.getBigDecimal(4);
+            assertThat(uTinyInt, is(255));
+            assertThat(uSmallInt, is(65535));
+            assertThat(uInteger, is(4294967295L));
+            assertThat(uBigInt, is(new BigDecimal("18446744073709551615")));
+          } catch (SQLException e) {
+            throw TestUtil.rethrow(e);
+          }
+        });
+  }
+
   @Test void bindByteParameter() {
     for (SqlTypeName tpe : SqlTypeName.INT_TYPES) {
       final String sql =
diff --git 
a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java 
b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
index b1cbbcc2e6..32622315f7 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/tree/UnsignedType.java
@@ -278,7 +278,7 @@ public static ULong toULong(double n) {
   private static ULong toULongNonNull(Number n, RoundingMode mode) {
     if (n instanceof BigDecimal) {
       BigDecimal d = ((BigDecimal) n).setScale(0, mode);
-      return Unsigned.ulong(d.longValueExact());
+      return Unsigned.ulong(d.toBigIntegerExact());
     } else if (n instanceof Byte || n instanceof Short || n instanceof Integer
         || n instanceof Long || n instanceof Float || n instanceof Double
         || n instanceof UByte || n instanceof UShort || n instanceof UInteger) 
{
diff --git a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java 
b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
index a01501ce23..8a846da87e 100644
--- a/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
+++ b/testkit/src/main/java/org/apache/calcite/test/CalciteAssert.java
@@ -30,6 +30,8 @@
 import org.apache.calcite.jdbc.CalciteMetaImpl;
 import org.apache.calcite.jdbc.CalcitePrepare;
 import org.apache.calcite.jdbc.CalciteSchema;
+import org.apache.calcite.linq4j.Enumerable;
+import org.apache.calcite.linq4j.Linq4j;
 import org.apache.calcite.linq4j.tree.Expression;
 import org.apache.calcite.materialize.Lattice;
 import org.apache.calcite.model.ModelHandler;
@@ -47,6 +49,7 @@
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.runtime.SpatialTypeFunctions;
 import org.apache.calcite.runtime.UnionOperation;
+import org.apache.calcite.schema.ScannableTable;
 import org.apache.calcite.schema.Schema;
 import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.SchemaVersion;
@@ -90,6 +93,7 @@
 import org.apache.calcite.util.Util;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultiset;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
@@ -105,6 +109,7 @@
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
 import java.net.URL;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
@@ -865,7 +870,8 @@ static SchemaPlus addSchema_(SchemaPlus rootSchema, 
SchemaSpec schema) {
 
     case MY_DB:
       return rootSchema.add(schema.schemaName, MY_DB_SCHEMA);
-
+    case UNSIGNED_TYPE:
+      return rootSchema.add(schema.schemaName, UNSIGNED_TYPE_SCHEMA);
     case SCOTT:
       jdbcScott = addSchemaIfNotExists(rootSchema, SchemaSpec.JDBC_SCOTT);
       return rootSchema.add(schema.schemaName, new CloneSchema(jdbcScott));
@@ -2077,6 +2083,7 @@ public enum SchemaSpec {
     GEO("GEO"),
     HR("hr"),
     MY_DB("myDb"),
+    UNSIGNED_TYPE("UNSIGNED_TYPE"),
     JDBC_SCOTT("JDBC_SCOTT"),
     SCOTT("scott"),
     SCOTT_WITH_TEMPORAL("scott_temporal"),
@@ -2355,4 +2362,44 @@ static List<String> unwrap(String java) {
       throw new UnsupportedOperationException("snapshot");
     }
   };
+
+  /** Schema instance for {@link SchemaSpec#UNSIGNED_TYPE}. */
+  private static final Schema UNSIGNED_TYPE_SCHEMA = new AbstractSchema() {
+
+    @Override protected Map<String, Table> getTableMap() {
+      return ImmutableMap.of("test_unsigned", new UnsingedScannableTable());
+    }
+  };
+
+  /** Scannable table for unsigned types test. */
+  private static class UnsingedScannableTable extends AbstractTable implements 
ScannableTable {
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Table schema is as follows:
+     *
+     * <pre>{@code
+     * test_unsigned(
+     *      utiny_value: TINYINT UNSIGNED,
+     *      usmall_value: SMALLINT UNSIGNED,
+     *      uint_value: INT UNSIGNED,
+     *      ubig_value: BIGINT UNSIGNED)
+     * }</pre>
+     */
+    @Override public RelDataType getRowType(RelDataTypeFactory typeFactory) {
+      return typeFactory.builder()
+          .add("utiny_value", typeFactory.createSqlType(SqlTypeName.UTINYINT))
+          .add("usmall_value", 
typeFactory.createSqlType(SqlTypeName.USMALLINT))
+          .add("uint_value", typeFactory.createSqlType(SqlTypeName.UINTEGER))
+          .add("ubig_value", typeFactory.createSqlType(SqlTypeName.UBIGINT))
+          .build();
+    }
+
+    @Override public Enumerable<@Nullable Object[]> scan(DataContext root) {
+      return Linq4j.asEnumerable(new Object[][] {
+          {255, 65535, 4294967295L, new BigDecimal("18446744073709551615")}
+      });
+    }
+  }
 }

Reply via email to