diff --git a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java index 5f4a2b80a..79bce4138 100644 --- a/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java +++ b/clickhouse-jdbc/src/main/java/com/clickhouse/jdbc/JdbcTypeMapping.java @@ -158,11 +158,13 @@ protected int getSqlType(Class javaClass) { // and purpose(e.g. for read or w sqlType = Types.BIGINT; } else if (javaClass == float.class || javaClass == Float.class) { sqlType = Types.FLOAT; - } else if (javaClass == double.class || javaClass == Double.class) { - sqlType = Types.DOUBLE; - } else if (javaClass == BigInteger.class || javaClass == BigDecimal.class) { - sqlType = Types.DECIMAL; - } else if (javaClass == Date.class || javaClass == LocalDate.class) { + } else if (javaClass == double.class || javaClass == Double.class) { + sqlType = Types.DOUBLE; + } else if (javaClass == BigInteger.class) { + sqlType = Types.NUMERIC; + } else if (javaClass == BigDecimal.class) { + sqlType = Types.DECIMAL; + } else if (javaClass == Date.class || javaClass == LocalDate.class) { sqlType = Types.DATE; } else if (javaClass == Time.class || javaClass == LocalTime.class) { sqlType = Types.TIME; @@ -234,6 +236,8 @@ public int toSqlType(ClickHouseColumn column, Map> typeMap) { case UInt128: case Int256: case UInt256: + sqlType = Types.NUMERIC; + break; case Decimal: case Decimal32: case Decimal64: @@ -526,6 +530,13 @@ public Class toJavaClass(ClickHouseColumn column, Map> typeM ClickHouseDataType type = column.getDataType(); switch (type) { + case UInt64: + case Int128: + case UInt128: + case Int256: + case UInt256: + clazz = BigInteger.class; + break; case DateTime: case DateTime32: case DateTime64: diff --git a/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/JdbcTypeMappingTest.java b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/JdbcTypeMappingTest.java new file mode 100644 index 000000000..65a8022d6 --- /dev/null +++ b/clickhouse-jdbc/src/test/java/com/clickhouse/jdbc/JdbcTypeMappingTest.java @@ -0,0 +1,64 @@ +package com.clickhouse.jdbc; + +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.ClickHouseDataType; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Types; + +import static org.testng.Assert.assertEquals; + +public class JdbcTypeMappingTest { + + private final JdbcTypeMapping mapping = JdbcTypeMapping.getDefaultMapping(); + + @Test(groups = {"unit"}) + public void testInt128Mapping() { + ClickHouseColumn col = ClickHouseColumn.of("col", ClickHouseDataType.Int128, false, false); + assertEquals(mapping.toSqlType(col, null), Types.NUMERIC); + assertEquals(mapping.toJavaClass(col, null), BigInteger.class); + } + + @Test(groups = {"unit"}) + public void testInt256Mapping() { + ClickHouseColumn col = ClickHouseColumn.of("col", ClickHouseDataType.Int256, false, false); + assertEquals(mapping.toSqlType(col, null), Types.NUMERIC); + assertEquals(mapping.toJavaClass(col, null), BigInteger.class); + } + + @Test(groups = {"unit"}) + public void testUInt64Mapping() { + ClickHouseColumn col = ClickHouseColumn.of("col", ClickHouseDataType.UInt64, false, false); + assertEquals(mapping.toSqlType(col, null), Types.NUMERIC); + assertEquals(mapping.toJavaClass(col, null), BigInteger.class); + } + + @Test(groups = {"unit"}) + public void testUInt128Mapping() { + ClickHouseColumn col = ClickHouseColumn.of("col", ClickHouseDataType.UInt128, false, false); + assertEquals(mapping.toSqlType(col, null), Types.NUMERIC); + assertEquals(mapping.toJavaClass(col, null), BigInteger.class); + } + + @Test(groups = {"unit"}) + public void testUInt256Mapping() { + ClickHouseColumn col = ClickHouseColumn.of("col", ClickHouseDataType.UInt256, false, false); + assertEquals(mapping.toSqlType(col, null), Types.NUMERIC); + assertEquals(mapping.toJavaClass(col, null), BigInteger.class); + } + + @Test(groups = {"unit"}) + public void testBigIntegerToSqlType() { + // Test that BigInteger class is mapped to Types.NUMERIC (not DECIMAL) + assertEquals(mapping.getSqlType(BigInteger.class), Types.NUMERIC); + } + + @Test(groups = {"unit"}) + public void testBigDecimalToSqlType() { + // Test that BigDecimal class is still mapped to Types.DECIMAL + assertEquals(mapping.getSqlType(BigDecimal.class), Types.DECIMAL); + } +} + \ No newline at end of file diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java index f7dbce675..f23034508 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/internal/JdbcUtils.java @@ -9,7 +9,6 @@ import com.clickhouse.jdbc.types.Array; import com.google.common.collect.ImmutableMap; -import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; @@ -44,9 +43,9 @@ public class JdbcUtils { public static final Map CLICKHOUSE_TYPE_NAME_TO_SQL_TYPE_MAP = Collections.unmodifiableMap(generateTypeMap().entrySet() .stream().collect( - HashMap::new, - (map, entry) -> map.put(entry.getKey().name(), entry.getValue()), - HashMap::putAll + HashMap::new, + (map, entry) -> map.put(entry.getKey().name(), entry.getValue()), + HashMap::putAll )); private static Map generateTypeMap() { @@ -55,14 +54,14 @@ private static Map generateTypeMap() { map.put(ClickHouseDataType.Int16, JDBCType.SMALLINT); map.put(ClickHouseDataType.Int32, JDBCType.INTEGER); map.put(ClickHouseDataType.Int64, JDBCType.BIGINT); - map.put(ClickHouseDataType.Int128, JDBCType.OTHER); - map.put(ClickHouseDataType.Int256, JDBCType.OTHER); + map.put(ClickHouseDataType.Int128, JDBCType.NUMERIC); + map.put(ClickHouseDataType.Int256, JDBCType.NUMERIC); map.put(ClickHouseDataType.UInt8, JDBCType.SMALLINT); map.put(ClickHouseDataType.UInt16, JDBCType.INTEGER); map.put(ClickHouseDataType.UInt32, JDBCType.BIGINT); - map.put(ClickHouseDataType.UInt64, JDBCType.OTHER); - map.put(ClickHouseDataType.UInt128, JDBCType.OTHER); - map.put(ClickHouseDataType.UInt256, JDBCType.OTHER); + map.put(ClickHouseDataType.UInt64, JDBCType.NUMERIC); + map.put(ClickHouseDataType.UInt128, JDBCType.NUMERIC); + map.put(ClickHouseDataType.UInt256, JDBCType.NUMERIC); map.put(ClickHouseDataType.Float32, JDBCType.FLOAT); map.put(ClickHouseDataType.Float64, JDBCType.DOUBLE); map.put(ClickHouseDataType.BFloat16, JDBCType.FLOAT); @@ -130,7 +129,7 @@ private static Map> generateClassMap() { map.put(JDBCType.CHAR, String.class); map.put(JDBCType.VARCHAR, String.class); map.put(JDBCType.LONGVARCHAR, String.class); - map.put(JDBCType.NUMERIC, java.math.BigDecimal.class); + map.put(JDBCType.NUMERIC, java.math.BigInteger.class); map.put(JDBCType.DECIMAL, java.math.BigDecimal.class); map.put(JDBCType.BIT, Boolean.class); map.put(JDBCType.BOOLEAN, Boolean.class); @@ -173,21 +172,6 @@ private static Map> getDataTypeClassMap() { for (Map.Entry e : CLICKHOUSE_TO_SQL_TYPE_MAP.entrySet()) { if (e.getValue().equals(JDBCType.OTHER)) { switch (e.getKey()) { - case UInt64: - map.put(e.getKey(), BigInteger.class); - break; - case UInt128: - map.put(e.getKey(), BigInteger.class); - break; - case UInt256: - map.put(e.getKey(), BigInteger.class); - break; - case Int128: - map.put(e.getKey(), BigInteger.class); - break; - case Int256: - map.put(e.getKey(), BigInteger.class); - break; case Point: map.put(e.getKey(), double[].class); break; @@ -361,6 +345,8 @@ static Object convertObject(Object value, Class type, ClickHouseColumn column return Double.parseDouble(value.toString()); } else if (type == java.math.BigDecimal.class) { return new java.math.BigDecimal(value.toString()); + } else if (type == java.math.BigInteger.class) { + return new java.math.BigInteger(value.toString()); } else if (type == Duration.class && value instanceof LocalDateTime) { return DataTypeUtils.localDateTimeToDuration((LocalDateTime) value); } else if (value instanceof TemporalAccessor) { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java index 9b1df664d..7de1201ea 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -239,6 +239,189 @@ public void testIntegerTypes() throws SQLException { } } + @Test(groups = { "integration" }) + public void testBigIntegerTypesMapping() throws SQLException { + String tableName = "test_biginteger_mapping"; + + // Create table with all large integer types + runQuery("CREATE TABLE " + tableName + " (" + + "id Int32, " + + "int128_col Int128, " + + "int256_col Int256, " + + "uint64_col UInt64, " + + "uint128_col UInt128, " + + "uint256_col UInt256, " + + "int128_null Nullable(Int128), " + + "int256_null Nullable(Int256), " + + "uint64_null Nullable(UInt64), " + + "uint128_null Nullable(UInt128), " + + "uint256_null Nullable(UInt256)" + + ") ENGINE = MergeTree ORDER BY id"); + + // Test values + BigInteger int128Min = new BigInteger("-170141183460469231731687303715884105728"); // -2^127 + BigInteger int128Max = new BigInteger("170141183460469231731687303715884105727"); // 2^127 - 1 + BigInteger int256Min = new BigInteger("-57896044618658097711785492504343953926634992332820282019728792003956564819968"); // -2^255 + BigInteger int256Max = new BigInteger("57896044618658097711785492504343953926634992332820282019728792003956564819967"); // 2^255 - 1 + BigInteger uint64Max = new BigInteger("18446744073709551615"); // 2^64 - 1 + BigInteger uint128Max = new BigInteger("340282366920938463463374607431768211455"); // 2^128 - 1 + BigInteger uint256Max = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935"); // 2^256 - 1 + + // Insert minimum values + insertData("INSERT INTO " + tableName + " VALUES (" + + "1, " + + int128Min + ", " + int256Min + ", 0, 0, 0, " + + "NULL, NULL, NULL, NULL, NULL" + + ")"); + + // Insert maximum values + insertData("INSERT INTO " + tableName + " VALUES (" + + "2, " + + int128Max + ", " + int256Max + ", " + uint64Max + ", " + uint128Max + ", " + uint256Max + ", " + + "NULL, NULL, NULL, NULL, NULL" + + ")"); + + // Insert random values with PreparedStatement + Random rand = new Random(System.currentTimeMillis()); + BigInteger int128Random = new BigInteger(127, rand); + BigInteger int256Random = new BigInteger(255, rand); + BigInteger uint64Random = BigInteger.valueOf(rand.nextLong(Long.MAX_VALUE)); + BigInteger uint128Random = new BigInteger(128, rand); + BigInteger uint256Random = new BigInteger(256, rand); + + try (Connection conn = getJdbcConnection()) { + try (PreparedStatement pstmt = conn.prepareStatement( + "INSERT INTO " + tableName + " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) { + pstmt.setInt(1, 3); + pstmt.setObject(2, int128Random); + pstmt.setObject(3, int256Random); + pstmt.setObject(4, uint64Random); + pstmt.setObject(5, uint128Random); + pstmt.setObject(6, uint256Random); + pstmt.setObject(7, int128Random); + pstmt.setObject(8, int256Random); + pstmt.setObject(9, uint64Random); + pstmt.setObject(10, uint128Random); + pstmt.setObject(11, uint256Random); + pstmt.executeUpdate(); + } + } + + // Verify results and metadata + try (Connection conn = getJdbcConnection()) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY id")) { + ResultSetMetaData meta = rs.getMetaData(); + + // Verify metadata for each large integer column + // Int128 + assertEquals(meta.getColumnType(2), Types.NUMERIC, "Int128 should map to Types.NUMERIC"); + assertEquals(meta.getColumnTypeName(2), "Int128", "Int128 column type name"); + assertEquals(meta.getColumnClassName(2), BigInteger.class.getName(), "Int128 should map to BigInteger class"); + + // Int256 + assertEquals(meta.getColumnType(3), Types.NUMERIC, "Int256 should map to Types.NUMERIC"); + assertEquals(meta.getColumnTypeName(3), "Int256", "Int256 column type name"); + assertEquals(meta.getColumnClassName(3), BigInteger.class.getName(), "Int256 should map to BigInteger class"); + + // UInt64 + assertEquals(meta.getColumnType(4), Types.NUMERIC, "UInt64 should map to Types.NUMERIC"); + assertEquals(meta.getColumnTypeName(4), "UInt64", "UInt64 column type name"); + assertEquals(meta.getColumnClassName(4), BigInteger.class.getName(), "UInt64 should map to BigInteger class"); + + // UInt128 + assertEquals(meta.getColumnType(5), Types.NUMERIC, "UInt128 should map to Types.NUMERIC"); + assertEquals(meta.getColumnTypeName(5), "UInt128", "UInt128 column type name"); + assertEquals(meta.getColumnClassName(5), BigInteger.class.getName(), "UInt128 should map to BigInteger class"); + + // UInt256 + assertEquals(meta.getColumnType(6), Types.NUMERIC, "UInt256 should map to Types.NUMERIC"); + assertEquals(meta.getColumnTypeName(6), "UInt256", "UInt256 column type name"); + assertEquals(meta.getColumnClassName(6), BigInteger.class.getName(), "UInt256 should map to BigInteger class"); + + // Verify first row (minimum values) + assertTrue(rs.next(), "Should have first row"); + assertEquals(rs.getInt("id"), 1); + + // Verify that getObject() returns BigInteger instances + Object int128Obj = rs.getObject("int128_col"); + assertTrue(int128Obj instanceof BigInteger, "Int128 getObject() should return BigInteger, got: " + int128Obj.getClass().getName()); + assertEquals(int128Obj, int128Min, "Int128 min value"); + + Object int256Obj = rs.getObject("int256_col"); + assertTrue(int256Obj instanceof BigInteger, "Int256 getObject() should return BigInteger, got: " + int256Obj.getClass().getName()); + assertEquals(int256Obj, int256Min, "Int256 min value"); + + Object uint64Obj = rs.getObject("uint64_col"); + assertTrue(uint64Obj instanceof BigInteger, "UInt64 getObject() should return BigInteger, got: " + uint64Obj.getClass().getName()); + assertEquals(uint64Obj, BigInteger.ZERO, "UInt64 zero value"); + + Object uint128Obj = rs.getObject("uint128_col"); + assertTrue(uint128Obj instanceof BigInteger, "UInt128 getObject() should return BigInteger, got: " + uint128Obj.getClass().getName()); + assertEquals(uint128Obj, BigInteger.ZERO, "UInt128 zero value"); + + Object uint256Obj = rs.getObject("uint256_col"); + assertTrue(uint256Obj instanceof BigInteger, "UInt256 getObject() should return BigInteger, got: " + uint256Obj.getClass().getName()); + assertEquals(uint256Obj, BigInteger.ZERO, "UInt256 zero value"); + + // Verify nullable columns + assertNull(rs.getObject("int128_null"), "Nullable Int128 should be null"); + assertNull(rs.getObject("int256_null"), "Nullable Int256 should be null"); + assertNull(rs.getObject("uint64_null"), "Nullable UInt64 should be null"); + assertNull(rs.getObject("uint128_null"), "Nullable UInt128 should be null"); + assertNull(rs.getObject("uint256_null"), "Nullable UInt256 should be null"); + + // Verify second row (maximum values) + assertTrue(rs.next(), "Should have second row"); + assertEquals(rs.getInt("id"), 2); + assertEquals(rs.getObject("int128_col"), int128Max, "Int128 max value"); + assertEquals(rs.getObject("int256_col"), int256Max, "Int256 max value"); + assertEquals(rs.getObject("uint64_col"), uint64Max, "UInt64 max value"); + assertEquals(rs.getObject("uint128_col"), uint128Max, "UInt128 max value"); + assertEquals(rs.getObject("uint256_col"), uint256Max, "UInt256 max value"); + + // Verify third row (random values) + assertTrue(rs.next(), "Should have third row"); + assertEquals(rs.getInt("id"), 3); + assertEquals(rs.getObject("int128_col"), int128Random, "Int128 random value"); + assertEquals(rs.getObject("int256_col"), int256Random, "Int256 random value"); + assertEquals(rs.getObject("uint64_col"), uint64Random, "UInt64 random value"); + assertEquals(rs.getObject("uint128_col"), uint128Random, "UInt128 random value"); + assertEquals(rs.getObject("uint256_col"), uint256Random, "UInt256 random value"); + + // Verify that nullable columns contain the inserted values + assertEquals(rs.getObject("int128_null"), int128Random, "Nullable Int128 with value"); + assertEquals(rs.getObject("int256_null"), int256Random, "Nullable Int256 with value"); + assertEquals(rs.getObject("uint64_null"), uint64Random, "Nullable UInt64 with value"); + assertEquals(rs.getObject("uint128_null"), uint128Random, "Nullable UInt128 with value"); + assertEquals(rs.getObject("uint256_null"), uint256Random, "Nullable UInt256 with value"); + + assertFalse(rs.next(), "Should have no more rows"); + } + } + } + + // Additional test: verify getObject(index, BigInteger.class) works correctly + try (Connection conn = getJdbcConnection()) { + try (Statement stmt = conn.createStatement()) { + try (ResultSet rs = stmt.executeQuery("SELECT int128_col, int256_col, uint64_col, uint128_col, uint256_col FROM " + tableName + " WHERE id = 2")) { + assertTrue(rs.next()); + + // Verify that getObject with specific class works + assertEquals(rs.getObject(1, BigInteger.class), int128Max, "getObject(index, BigInteger.class) for Int128"); + assertEquals(rs.getObject(2, BigInteger.class), int256Max, "getObject(index, BigInteger.class) for Int256"); + assertEquals(rs.getObject(3, BigInteger.class), uint64Max, "getObject(index, BigInteger.class) for UInt64"); + assertEquals(rs.getObject(4, BigInteger.class), uint128Max, "getObject(index, BigInteger.class) for UInt128"); + assertEquals(rs.getObject(5, BigInteger.class), uint256Max, "getObject(index, BigInteger.class) for UInt256"); + + assertFalse(rs.next()); + } + } + } + + log.info("BigInteger types mapping test completed successfully"); + } + @Test(groups = { "integration" }) public void testUnsignedIntegerTypes() throws Exception { Random rand = new Random(); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java index cdfeda0d9..315093cff 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/internal/JdbcUtilsTest.java @@ -9,6 +9,8 @@ import java.io.InputStream; import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.JDBCType; import java.sql.SQLException; import java.time.Instant; import java.time.ZoneId; @@ -150,4 +152,22 @@ public void testConvertToUnhexExpression() throws Exception { decodedChecksum.update(decodedBytes); assertEquals(decodedChecksum.getValue(), expectedChecksum, "Checksum of decoded bytes should match original"); } + + @Test(groups = {"unit"}) + public void testClickHouseToSqlType() { + assertEquals(JdbcUtils.convertToSqlType(ClickHouseDataType.Int128), JDBCType.NUMERIC); + assertEquals(JdbcUtils.convertToSqlType(ClickHouseDataType.Int256), JDBCType.NUMERIC); + assertEquals(JdbcUtils.convertToSqlType(ClickHouseDataType.UInt64), JDBCType.NUMERIC); + assertEquals(JdbcUtils.convertToSqlType(ClickHouseDataType.UInt128), JDBCType.NUMERIC); + assertEquals(JdbcUtils.convertToSqlType(ClickHouseDataType.UInt256), JDBCType.NUMERIC); + } + + @Test(groups = {"unit"}) + public void testClickHouseToJavaClass() { + assertEquals(JdbcUtils.convertToJavaClass(ClickHouseDataType.Int128), BigInteger.class); + assertEquals(JdbcUtils.convertToJavaClass(ClickHouseDataType.Int256), BigInteger.class); + assertEquals(JdbcUtils.convertToJavaClass(ClickHouseDataType.UInt64), BigInteger.class); + assertEquals(JdbcUtils.convertToJavaClass(ClickHouseDataType.UInt128), BigInteger.class); + assertEquals(JdbcUtils.convertToJavaClass(ClickHouseDataType.UInt256), BigInteger.class); + } }