From 67f3c408fc8dd5add1432647ce10835298b2770d Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 31 Mar 2026 14:50:58 -0700 Subject: [PATCH 1/5] Fixed converting integers thru decimal --- .../internal/NumberConverter.java | 6 +- .../internal/SerializerUtils.java | 6 +- .../internal/ValueConverters.java | 9 ++- .../ClickHouseBinaryFormatReaderTest.java | 60 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java index d80630396..803fa956a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java @@ -142,8 +142,12 @@ public static BigDecimal toBigDecimal(Object value) { return (BigDecimal) value; } else if (value instanceof BigInteger) { return new BigDecimal((BigInteger) value); - } else if (value instanceof Number) { + } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { + return BigDecimal.valueOf(((Number) value).longValue()); + } else if (value instanceof Float || value instanceof Double) { return BigDecimal.valueOf(((Number) value).doubleValue()); + } else if (value instanceof Number) { + return new BigDecimal(value.toString()); } else if (value instanceof String) { return new BigDecimal((String) value); } else if (value instanceof Boolean) { diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java index 54c1e93a8..84f968c5c 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java @@ -782,8 +782,12 @@ public static BigDecimal convertToBigDecimal(Object value) { return (BigDecimal) value; } else if (value instanceof BigInteger) { return new BigDecimal((BigInteger) value); - } else if (value instanceof Number) { + } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { + return BigDecimal.valueOf(((Number) value).longValue()); + } else if (value instanceof Float || value instanceof Double) { return BigDecimal.valueOf(((Number) value).doubleValue()); + } else if (value instanceof Number) { + return new BigDecimal(value.toString()); } else if (value instanceof String) { return new BigDecimal((String) value); } else { diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java index e94283cf9..36511b467 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java @@ -6,7 +6,6 @@ import java.math.BigInteger; import java.net.MalformedURLException; import java.net.URL; -import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; import java.util.Collections; @@ -210,7 +209,13 @@ public BigInteger convertNumberToBigInteger(Object value) { } public BigDecimal convertNumberToBigDecimal(Object value) { - return BigDecimal.valueOf(((Number) value).doubleValue()); + Number number = (Number) value; + if (number instanceof Byte || number instanceof Short || number instanceof Integer || number instanceof Long) { + return BigDecimal.valueOf(number.longValue()); + } else if (number instanceof Float || number instanceof Double) { + return BigDecimal.valueOf(number.doubleValue()); + } + return new BigDecimal(number.toString()); } // Date & Time converters diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java index b72b2ed26..e13673c43 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java @@ -83,6 +83,66 @@ public void testReadingNumbers() throws IOException { } } + @Test + public void testGetBigDecimalForIntegerWidths8To256() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + String[] names = new String[] { + "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "i128", "u128", "i256", "u256" + }; + String[] types = new String[] { + "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", + "Int128", "UInt128", "Int256", "UInt256" + }; + + BinaryStreamUtils.writeVarInt(out, names.length); + for (String name : names) { + BinaryStreamUtils.writeString(out, name); + } + for (String type : types) { + BinaryStreamUtils.writeString(out, type); + } + + BigInteger u64 = new BigInteger("18446744073709551615"); + BigInteger i128 = new BigInteger("-170141183460469231731687303715884105728"); + BigInteger u128 = new BigInteger("340282366920938463463374607431768211455"); + BigInteger i256 = new BigInteger("-57896044618658097711785492504343953926634992332820282019728792003956564819968"); + BigInteger u256 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935"); + + BinaryStreamUtils.writeInt8(out, -128); + BinaryStreamUtils.writeUnsignedInt8(out, 255); + BinaryStreamUtils.writeInt16(out, -32768); + BinaryStreamUtils.writeUnsignedInt16(out, 65535); + BinaryStreamUtils.writeInt32(out, Integer.MIN_VALUE); + BinaryStreamUtils.writeUnsignedInt32(out, 4294967295L); + BinaryStreamUtils.writeInt64(out, Long.MAX_VALUE); + BinaryStreamUtils.writeUnsignedInt64(out, u64); + BinaryStreamUtils.writeInt128(out, i128); + BinaryStreamUtils.writeUnsignedInt128(out, u128); + BinaryStreamUtils.writeInt256(out, i256); + BinaryStreamUtils.writeUnsignedInt256(out, u256); + + InputStream in = new ByteArrayInputStream(out.toByteArray()); + QuerySettings querySettings = new QuerySettings().setUseTimeZone(TimeZone.getTimeZone("UTC").toZoneId().getId()); + RowBinaryWithNamesAndTypesFormatReader reader = + new RowBinaryWithNamesAndTypesFormatReader(in, querySettings, new BinaryStreamReader.CachingByteBufferAllocator()); + + reader.next(); + + Assert.assertEquals(reader.getBigDecimal("i8"), BigDecimal.valueOf(-128)); + Assert.assertEquals(reader.getBigDecimal("u8"), BigDecimal.valueOf(255)); + Assert.assertEquals(reader.getBigDecimal("i16"), BigDecimal.valueOf(-32768)); + Assert.assertEquals(reader.getBigDecimal("u16"), BigDecimal.valueOf(65535)); + Assert.assertEquals(reader.getBigDecimal("i32"), BigDecimal.valueOf(Integer.MIN_VALUE)); + Assert.assertEquals(reader.getBigDecimal("u32"), BigDecimal.valueOf(4294967295L)); + Assert.assertEquals(reader.getBigDecimal("i64"), BigDecimal.valueOf(Long.MAX_VALUE)); + Assert.assertEquals(reader.getBigDecimal("u64"), new BigDecimal(u64)); + Assert.assertEquals(reader.getBigDecimal("i128"), new BigDecimal(i128)); + Assert.assertEquals(reader.getBigDecimal("u128"), new BigDecimal(u128)); + Assert.assertEquals(reader.getBigDecimal("i256"), new BigDecimal(i256)); + Assert.assertEquals(reader.getBigDecimal("u256"), new BigDecimal(u256)); + } + @Test public void testReadingNumbersWithOverflow() throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); From 10a54b780de15a29d1ee29293b882d1df6fb7740 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Wed, 1 Apr 2026 13:22:21 -0700 Subject: [PATCH 2/5] deduplicated some code --- .../internal/SerializerUtils.java | 41 +++---------------- .../ClickHouseBinaryFormatReaderTest.java | 1 + 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java index 84f968c5c..1f0b34d9a 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java @@ -57,6 +57,7 @@ import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; import static org.objectweb.asm.Opcodes.RETURN; +@SuppressWarnings("deprecation") public class SerializerUtils { public static void serializeData(OutputStream stream, Object value, ClickHouseColumn column) throws IOException { @@ -505,10 +506,10 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl BinaryStreamUtils.writeInt64(stream, convertToLong(value)); break; case Int128: - BinaryStreamUtils.writeInt128(stream, convertToBigInteger(value)); + BinaryStreamUtils.writeInt128(stream, NumberConverter.toBigInteger(value)); break; case Int256: - BinaryStreamUtils.writeInt256(stream, convertToBigInteger(value)); + BinaryStreamUtils.writeInt256(stream, NumberConverter.toBigInteger(value)); break; case UInt8: BinaryStreamUtils.writeUnsignedInt8(stream, convertToInteger(value)); @@ -523,10 +524,10 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl BinaryStreamUtils.writeUnsignedInt64(stream, convertToLong(value)); break; case UInt128: - BinaryStreamUtils.writeUnsignedInt128(stream, convertToBigInteger(value)); + BinaryStreamUtils.writeUnsignedInt128(stream, NumberConverter.toBigInteger(value)); break; case UInt256: - BinaryStreamUtils.writeUnsignedInt256(stream, convertToBigInteger(value)); + BinaryStreamUtils.writeUnsignedInt256(stream, NumberConverter.toBigInteger(value)); break; case Float32: BinaryStreamUtils.writeFloat32(stream, (float) value); @@ -539,7 +540,7 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl case Decimal64: case Decimal128: case Decimal256: - BinaryStreamUtils.writeDecimal(stream, convertToBigDecimal(value), column.getPrecision(), column.getScale()); + BinaryStreamUtils.writeDecimal(stream, NumberConverter.toBigDecimal(value), column.getPrecision(), column.getScale()); break; case Bool: BinaryStreamUtils.writeBoolean(stream, (Boolean) value); @@ -765,36 +766,6 @@ public static Long convertToLong(Object value) { } } - public static BigInteger convertToBigInteger(Object value) { - if (value instanceof BigInteger) { - return (BigInteger) value; - } else if (value instanceof Number) { - return BigInteger.valueOf(((Number) value).longValue()); - } else if (value instanceof String) { - return new BigInteger((String) value); - } else { - throw new IllegalArgumentException("Cannot convert " + value + " to BigInteger"); - } - } - - public static BigDecimal convertToBigDecimal(Object value) { - if (value instanceof BigDecimal) { - return (BigDecimal) value; - } else if (value instanceof BigInteger) { - return new BigDecimal((BigInteger) value); - } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { - return BigDecimal.valueOf(((Number) value).longValue()); - } else if (value instanceof Float || value instanceof Double) { - return BigDecimal.valueOf(((Number) value).doubleValue()); - } else if (value instanceof Number) { - return new BigDecimal(value.toString()); - } else if (value instanceof String) { - return new BigDecimal((String) value); - } else { - throw new IllegalArgumentException("Cannot convert " + value + " to BigDecimal"); - } - } - public static String convertToString(Object value) { return java.lang.String.valueOf(value); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java index e13673c43..1cf1477dd 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/ClickHouseBinaryFormatReaderTest.java @@ -19,6 +19,7 @@ import java.util.TimeZone; import java.util.function.Consumer; +@SuppressWarnings("deprecation") public class ClickHouseBinaryFormatReaderTest { @Test From 860ed348004112219d6f656807f4ef240b58db2a Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Thu, 2 Apr 2026 11:29:05 -0700 Subject: [PATCH 3/5] Added more tests and fixed issue --- .../internal/SerializerUtils.java | 2 +- .../client/datatypes/DataTypeTests.java | 18 +++ .../client/internal/SerializerUtilsTests.java | 122 ++++++++++++++++++ .../jdbc/types/ArrayResultSetTest.java | 12 +- 4 files changed, 152 insertions(+), 2 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java index 1f0b34d9a..69c1fb55f 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java @@ -521,7 +521,7 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl BinaryStreamUtils.writeUnsignedInt32(stream, convertToLong(value)); break; case UInt64: - BinaryStreamUtils.writeUnsignedInt64(stream, convertToLong(value)); + BinaryStreamUtils.writeUnsignedInt64(stream, NumberConverter.toBigInteger(value)); break; case UInt128: BinaryStreamUtils.writeUnsignedInt128(stream, NumberConverter.toBigInteger(value)); diff --git a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java index 2045a0d29..93d219771 100644 --- a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java @@ -313,6 +313,24 @@ public void testVariantWithDecimals() throws Exception { }); } + @Test(groups = {"integration"}) + public void testGetBigDecimalDoesNotTruncateLargeIntegers() throws Exception { + BigDecimal expectedInt64 = BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal expectedUInt64 = new BigDecimal("18446744073709551615"); + + String sql = "SELECT toInt64(" + Long.MAX_VALUE + ") AS i64, " + + "toUInt64('" + expectedUInt64.toPlainString() + "') AS u64"; + + try (QueryResponse response = client.query(sql).get(3, TimeUnit.SECONDS)) { + ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); + + Assert.assertNotNull(reader.next()); + Assert.assertEquals(reader.getBigDecimal("i64"), expectedInt64); + Assert.assertEquals(reader.getBigDecimal("u64"), expectedUInt64); + Assert.assertFalse(reader.hasNext()); + } + } + @Test(groups = {"integration"}) public void testVariantWithArrays() throws Exception { testVariantWith("arrays", new String[]{"field Variant(String, Array(String))"}, diff --git a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java index ac962ce93..9dcbe3539 100644 --- a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java @@ -1,10 +1,22 @@ package com.clickhouse.client.internal; +import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; +import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; import com.clickhouse.client.api.data_formats.internal.SerializerUtils; +import com.clickhouse.client.api.query.QuerySettings; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.format.BinaryStreamUtils; import org.testng.annotations.Test; import org.testng.Assert; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.TimeZone; + public class SerializerUtilsTests { @Test @@ -13,4 +25,114 @@ public void testConvertToInteger() { Assert.assertEquals(SerializerUtils.convertToInteger("1640995199").intValue(), expected); Assert.assertEquals(SerializerUtils.convertToInteger(false).intValue(), 0); } + + @Test + public void testSerializeNumbersRoundTrip() throws IOException { + String[] names = new String[]{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r"}; + String[] types = new String[]{"Int8", "Int16", "Int32", "Int64", "UInt8", "UInt16", "UInt32", "UInt64", + "Int128", "Int256", "UInt128", "UInt256", "Float32", "Float64", "Decimal32(3)", "Decimal64(3)", + "Decimal128(4)", "Decimal256(4)"}; + Object[] values = new Object[]{ + (byte) 120, + (short) 120, + 120, + 120L, + 120, + 120, + 120L, + BigInteger.valueOf(120), + BigInteger.valueOf(120), + BigInteger.valueOf(120), + BigInteger.valueOf(120), + BigInteger.valueOf(120), + 120.0f, + 120.0d, + BigDecimal.valueOf(120), + BigDecimal.valueOf(120), + BigDecimal.valueOf(120), + BigDecimal.valueOf(120) + }; + + RowBinaryWithNamesAndTypesFormatReader reader = serializeRow(names, types, values); + reader.next(); + + for (String name : names) { + Assert.assertTrue(reader.getBigDecimal(name).compareTo(BigDecimal.valueOf(120)) == 0); + } + } + + @Test + public void testSerializeIntegerWidths8To256RoundTripAsBigDecimal() throws IOException { + String[] names = new String[]{ + "i8", "u8", "i16", "u16", "i32", "u32", "i64", "u64", "i128", "u128", "i256", "u256" + }; + String[] types = new String[]{ + "Int8", "UInt8", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", + "Int128", "UInt128", "Int256", "UInt256" + }; + + BigInteger u64 = new BigInteger("18446744073709551615"); + BigInteger i128 = new BigInteger("-170141183460469231731687303715884105728"); + BigInteger u128 = new BigInteger("340282366920938463463374607431768211455"); + BigInteger i256 = new BigInteger("-57896044618658097711785492504343953926634992332820282019728792003956564819968"); + BigInteger u256 = new BigInteger("115792089237316195423570985008687907853269984665640564039457584007913129639935"); + + Object[] values = new Object[]{ + -128, + 255, + -32768, + 65535, + Integer.MIN_VALUE, + 4294967295L, + Long.MAX_VALUE, + u64, + i128, + u128, + i256, + u256 + }; + + BigDecimal[] expected = new BigDecimal[]{ + BigDecimal.valueOf(-128), + BigDecimal.valueOf(255), + BigDecimal.valueOf(-32768), + BigDecimal.valueOf(65535), + BigDecimal.valueOf(Integer.MIN_VALUE), + BigDecimal.valueOf(4294967295L), + BigDecimal.valueOf(Long.MAX_VALUE), + new BigDecimal(u64), + new BigDecimal(i128), + new BigDecimal(u128), + new BigDecimal(i256), + new BigDecimal(u256) + }; + + RowBinaryWithNamesAndTypesFormatReader reader = serializeRow(names, types, values); + reader.next(); + + for (int i = 0; i < names.length; i++) { + Assert.assertEquals(reader.getBigDecimal(names[i]), expected[i]); + } + } + + private RowBinaryWithNamesAndTypesFormatReader serializeRow(String[] names, String[] types, Object[] values) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BinaryStreamUtils.writeVarInt(out, names.length); + for (String name : names) { + BinaryStreamUtils.writeString(out, name); + } + for (String type : types) { + BinaryStreamUtils.writeString(out, type); + } + for (int i = 0; i < names.length; i++) { + SerializerUtils.serializeData(out, values[i], ClickHouseColumn.of(names[i], types[i])); + } + + return new RowBinaryWithNamesAndTypesFormatReader( + new ByteArrayInputStream(out.toByteArray()), + new QuerySettings().setUseTimeZone(TimeZone.getTimeZone("UTC").toZoneId().getId()), + new BinaryStreamReader.CachingByteBufferAllocator()); + } } diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java index fc091134b..e4a417201 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/types/ArrayResultSetTest.java @@ -185,7 +185,7 @@ void testPrimitiveValues(Object array, ClickHouseColumn column) throws SQLExcept assertEquals(rs.getLong(valueColumn), number.longValue()); assertEquals(rs.getFloat(valueColumn), number.floatValue()); assertEquals(rs.getDouble(valueColumn), number.doubleValue()); - assertEquals(rs.getBigDecimal(valueColumn), BigDecimal.valueOf(number.doubleValue())); + assertEquals(rs.getBigDecimal(valueColumn), expectedBigDecimal(number)); } else if (itemClass == Boolean.class || itemClass == boolean.class) { Number number = ((Boolean) value) ? 1 : 0; assertEquals(rs.getBoolean(valueColumn), ((Boolean) value)); @@ -207,6 +207,16 @@ void testPrimitiveValues(Object array, ClickHouseColumn column) throws SQLExcept } } + private static BigDecimal expectedBigDecimal(Number number) { + if (number instanceof Byte || number instanceof Short || number instanceof Integer || number instanceof Long) { + return BigDecimal.valueOf(number.longValue()); + } else if (number instanceof Float || number instanceof Double) { + return BigDecimal.valueOf(number.doubleValue()); + } + + return new BigDecimal(number.toString()); + } + @DataProvider static Object[][] testPrimitiveValues() { return new Object[][]{ From c7d0e8ec92a13702b649c6ea29ba13bdb6a77a2b Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 7 Apr 2026 13:08:25 -0700 Subject: [PATCH 4/5] Fixed handling integer values in ValuesConverter --- .../internal/ValueConverters.java | 15 ++- .../internal/NumberConverterTests.java | 53 ++++++++ ...lizerUtilsPrimitiveSerializationTests.java | 94 ++++++++++++++ .../internal/ValueConvertersTests.java | 115 ++++++++++++++++++ .../client/datatypes/DataTypeTests.java | 2 +- .../client/internal/SerializerUtilsTests.java | 3 - .../clickhouse/jdbc/JdbcDataTypeTests.java | 77 ++++++++++++ 7 files changed, 349 insertions(+), 10 deletions(-) create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/SerializerUtilsPrimitiveSerializationTests.java create mode 100644 client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java index 36511b467..3872fb8c3 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java @@ -12,6 +12,10 @@ import java.util.Map; import java.util.function.Function; +/** + * This class should be used to convert non-null value with known type. + * All methods should be kept minimal without null or instanceOf checks. + */ public final class ValueConverters { @@ -209,13 +213,12 @@ public BigInteger convertNumberToBigInteger(Object value) { } public BigDecimal convertNumberToBigDecimal(Object value) { - Number number = (Number) value; - if (number instanceof Byte || number instanceof Short || number instanceof Integer || number instanceof Long) { - return BigDecimal.valueOf(number.longValue()); - } else if (number instanceof Float || number instanceof Double) { - return BigDecimal.valueOf(number.doubleValue()); + if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { + return BigDecimal.valueOf(((Number)value).longValue()); + } else if (value instanceof Float || value instanceof Double) { + return BigDecimal.valueOf(((Number)value).doubleValue()); } - return new BigDecimal(number.toString()); + return new BigDecimal(value.toString()); } // Date & Time converters diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java new file mode 100644 index 000000000..91bb779d6 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java @@ -0,0 +1,53 @@ +package com.clickhouse.client.api.data_formats.internal; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.math.BigDecimal; + +public class NumberConverterTests { + + @Test + public void testToBigDecimalSupportsGenericNumberValues() { + Assert.assertEquals(NumberConverter.toBigDecimal(new CustomNumber("1234567890.123456789")), + new BigDecimal("1234567890.123456789")); + } + + @Test + public void testToBigDecimalSupportsStringValues() { + Assert.assertEquals(NumberConverter.toBigDecimal("98765.4321"), new BigDecimal("98765.4321")); + } + + private static final class CustomNumber extends Number { + private final BigDecimal value; + + private CustomNumber(String value) { + this.value = new BigDecimal(value); + } + + @Override + public int intValue() { + return value.intValue(); + } + + @Override + public long longValue() { + return value.longValue(); + } + + @Override + public float floatValue() { + return value.floatValue(); + } + + @Override + public double doubleValue() { + return value.doubleValue(); + } + + @Override + public String toString() { + return value.toPlainString(); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/SerializerUtilsPrimitiveSerializationTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/SerializerUtilsPrimitiveSerializationTests.java new file mode 100644 index 000000000..71c7988be --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/SerializerUtilsPrimitiveSerializationTests.java @@ -0,0 +1,94 @@ +package com.clickhouse.client.api.data_formats.internal; + +import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; +import com.clickhouse.client.api.query.QuerySettings; +import com.clickhouse.data.ClickHouseColumn; +import com.clickhouse.data.format.BinaryStreamUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.TimeZone; + +public class SerializerUtilsPrimitiveSerializationTests { + + @Test + public void testSerializeIntegerTargetFromStringValue() throws IOException { + RowBinaryWithNamesAndTypesFormatReader reader = + serializeSingleValue("Int32", "123456"); + + reader.next(); + + Assert.assertEquals(reader.getInteger("value"), Integer.valueOf(123456)); + } + + @Test + public void testSerializeDecimalTargetFromStringValue() throws IOException { + RowBinaryWithNamesAndTypesFormatReader reader = + serializeSingleValue("Decimal64(3)", "123456.789"); + + reader.next(); + + Assert.assertEquals(reader.getBigDecimal("value"), new BigDecimal("123456.789")); + } + + @Test + public void testSerializeDecimalTargetFromGenericNumberValue() throws IOException { + RowBinaryWithNamesAndTypesFormatReader reader = + serializeSingleValue("Decimal64(3)", new CustomNumber("987654.321")); + + reader.next(); + + Assert.assertEquals(reader.getBigDecimal("value"), new BigDecimal("987654.321")); + } + + private RowBinaryWithNamesAndTypesFormatReader serializeSingleValue(String type, Object value) + throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + BinaryStreamUtils.writeVarInt(out, 1); + BinaryStreamUtils.writeString(out, "value"); + BinaryStreamUtils.writeString(out, type); + SerializerUtils.serializeData(out, value, ClickHouseColumn.of("value", type)); + + return new RowBinaryWithNamesAndTypesFormatReader( + new ByteArrayInputStream(out.toByteArray()), + new QuerySettings().setUseTimeZone(TimeZone.getTimeZone("UTC").toZoneId().getId()), + new BinaryStreamReader.CachingByteBufferAllocator()); + } + + private static final class CustomNumber extends Number { + private final BigDecimal value; + + private CustomNumber(String value) { + this.value = new BigDecimal(value); + } + + @Override + public int intValue() { + return value.intValue(); + } + + @Override + public long longValue() { + return value.longValue(); + } + + @Override + public float floatValue() { + return value.floatValue(); + } + + @Override + public double doubleValue() { + return value.doubleValue(); + } + + @Override + public String toString() { + return value.toPlainString(); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java new file mode 100644 index 000000000..59f216978 --- /dev/null +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java @@ -0,0 +1,115 @@ +package com.clickhouse.client.api.data_formats.internal; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.math.BigDecimal; +import java.net.URL; +import java.util.Map; +import java.util.function.Function; + +public class ValueConvertersTests { + + private final ValueConverters converters = new ValueConverters(); + + @Test + public void testConvertNumberToBigDecimalSupportsGenericNumberValues() { + Assert.assertEquals(converters.convertNumberToBigDecimal(new CustomNumber("1234567890.123456789")), + new BigDecimal("1234567890.123456789")); + } + + @Test(dataProvider = "integerNumberValues") + public void testConvertNumberToBigDecimalSupportsIntegerNumberValues(Number value, BigDecimal expected) { + Assert.assertEquals(converters.convertNumberToBigDecimal(value), expected); + } + + @Test(dataProvider = "floatingNumberValues") + public void testConvertNumberToBigDecimalSupportsFloatingNumberValues(Number value, BigDecimal expected) { + Assert.assertEquals(converters.convertNumberToBigDecimal(value), expected); + } + + @Test + public void testNumberToBigDecimalConverterIsRegisteredForNumericSourceTypes() { + Map, Function> longConverters = converters.getConvertersForType(Long.class); + Map, Function> doubleConverters = converters.getConvertersForType(Double.class); + + Assert.assertEquals(longConverters.get(BigDecimal.class).apply(123L), BigDecimal.valueOf(123L)); + Assert.assertEquals(doubleConverters.get(BigDecimal.class).apply(12.5d), BigDecimal.valueOf(12.5d)); + } + + @Test + public void testGetConvertersForStringTypeIncludesCommonTargets() throws Exception { + Map, Function> stringConverters = converters.getConvertersForType(String.class); + + Assert.assertEquals(stringConverters.get(Integer.class).apply("42"), 42); + Assert.assertEquals(stringConverters.get(URL.class).apply("https://clickhouse.com"), + new URL("https://clickhouse.com")); + Assert.assertEquals(stringConverters.get(byte[].class).apply("abc"), "abc".getBytes()); + } + + @Test + public void testGetConvertersForBooleanTypeIncludesNumericAndStringTargets() { + Map, Function> booleanConverters = converters.getConvertersForType(Boolean.class); + + Assert.assertEquals(booleanConverters.get(Long.class).apply(Boolean.TRUE), 1L); + Assert.assertEquals(booleanConverters.get(String.class).apply(Boolean.FALSE), "false"); + Assert.assertEquals(((Number) booleanConverters.get(BigDecimal.class).apply(Boolean.TRUE)).longValue(), 1L); + } + + @Test + public void testGetConvertersForUnknownTypeReturnsEmptyMap() { + Assert.assertTrue(converters.getConvertersForType(Void.class).isEmpty()); + } + + @DataProvider + public Object[][] integerNumberValues() { + return new Object[][]{ + {(byte) 7, BigDecimal.valueOf(7)}, + {(short) 12, BigDecimal.valueOf(12)}, + {34, BigDecimal.valueOf(34)}, + {56L, BigDecimal.valueOf(56L)} + }; + } + + @DataProvider + public Object[][] floatingNumberValues() { + return new Object[][]{ + {12.5f, BigDecimal.valueOf(12.5d)}, + {98.765d, BigDecimal.valueOf(98.765d)} + }; + } + + private static final class CustomNumber extends Number { + private final BigDecimal value; + + private CustomNumber(String value) { + this.value = new BigDecimal(value); + } + + @Override + public int intValue() { + return value.intValue(); + } + + @Override + public long longValue() { + return value.longValue(); + } + + @Override + public float floatValue() { + return value.floatValue(); + } + + @Override + public double doubleValue() { + return value.doubleValue(); + } + + @Override + public String toString() { + return value.toPlainString(); + } + } +} diff --git a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java index b7e93c882..2b15130d6 100644 --- a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java @@ -379,7 +379,7 @@ public void testGetBigDecimalDoesNotTruncateLargeIntegers() throws Exception { String sql = "SELECT toInt64(" + Long.MAX_VALUE + ") AS i64, " + "toUInt64('" + expectedUInt64.toPlainString() + "') AS u64"; - try (QueryResponse response = client.query(sql).get(3, TimeUnit.SECONDS)) { + try (QueryResponse response = client.query(sql).get()) { ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response); Assert.assertNotNull(reader.next()); diff --git a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java index 137061659..8c76828ef 100644 --- a/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/internal/SerializerUtilsTests.java @@ -4,14 +4,11 @@ import com.clickhouse.client.api.data_formats.RowBinaryWithNamesAndTypesFormatReader; import com.clickhouse.client.api.data_formats.internal.BinaryStreamReader; import com.clickhouse.client.api.data_formats.internal.SerializerUtils; -import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.client.api.query.QuerySettings; import com.clickhouse.data.ClickHouseColumn; import com.clickhouse.data.format.BinaryStreamUtils; import org.testng.annotations.Test; -import java.io.ByteArrayOutputStream; - import org.testng.Assert; import java.io.ByteArrayInputStream; 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 7de1201ea..a74cea3eb 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -642,6 +642,83 @@ public void testDecimalTypes() throws SQLException { } } + @Test(groups = { "integration" }) + public void testDecimalTypesTruncateOnWriteAndRead() throws SQLException { + final String tableName = "test_decimal_truncate"; + runQuery("DROP TABLE IF EXISTS " + tableName); + runQuery("CREATE TABLE " + tableName + " (order Int8, " + + "dec Decimal(9, 2), dec32 Decimal32(4), dec64 Decimal64(8), dec128 Decimal128(18), dec256 Decimal256(18)" + + ") ENGINE = MergeTree ORDER BY ()"); + + BigDecimal[] positiveWritten = new BigDecimal[] { + new BigDecimal("1234567.899"), + new BigDecimal("12345.67891"), + new BigDecimal("1234567890.123456789"), + new BigDecimal("12345678901234567890.1234567890123456789"), + new BigDecimal("1234567890123456789012345678901234567890.1234567890123456789") + }; + BigDecimal[] positiveExpected = new BigDecimal[] { + new BigDecimal("1234567.89"), + new BigDecimal("12345.6789"), + new BigDecimal("1234567890.12345678"), + new BigDecimal("12345678901234567890.123456789012345678"), + new BigDecimal("1234567890123456789012345678901234567890.123456789012345678") + }; + BigDecimal[] negativeWritten = new BigDecimal[] { + new BigDecimal("-1234567.899"), + new BigDecimal("-12345.67891"), + new BigDecimal("-1234567890.123456789"), + new BigDecimal("-12345678901234567890.1234567890123456789"), + new BigDecimal("-1234567890123456789012345678901234567890.1234567890123456789") + }; + BigDecimal[] negativeExpected = new BigDecimal[] { + new BigDecimal("-1234567.89"), + new BigDecimal("-12345.6789"), + new BigDecimal("-1234567890.12345678"), + new BigDecimal("-12345678901234567890.123456789012345678"), + new BigDecimal("-1234567890123456789012345678901234567890.123456789012345678") + }; + + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement("INSERT INTO " + tableName + " VALUES (?, ?, ?, ?, ?, ?)")) { + stmt.setInt(1, 1); + for (int i = 0; i < positiveWritten.length; i++) { + stmt.setBigDecimal(i + 2, positiveWritten[i]); + } + stmt.executeUpdate(); + + stmt.setInt(1, 2); + for (int i = 0; i < negativeWritten.length; i++) { + stmt.setBigDecimal(i + 2, negativeWritten[i]); + } + stmt.executeUpdate(); + } + + try (Connection conn = getJdbcConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY order")) { + assertDecimalTruncationRow(rs, 1, positiveExpected); + assertDecimalTruncationRow(rs, 2, negativeExpected); + assertFalse(rs.next()); + } + } + + private void assertDecimalTruncationRow(ResultSet rs, int order, BigDecimal[] expectedValues) throws SQLException { + String[] columns = { "dec", "dec32", "dec64", "dec128", "dec256" }; + + assertTrue(rs.next()); + assertEquals(rs.getInt("order"), order); + for (int i = 0; i < columns.length; i++) { + String column = columns[i]; + BigDecimal expected = expectedValues[i]; + + assertEquals(rs.getString(column), expected.toPlainString()); + assertEquals(rs.getBigDecimal(column), expected); + assertEquals(rs.getObject(column), expected); + assertEquals(rs.getObject(column, BigDecimal.class), expected); + } + } + @Test(groups = { "integration" }) public void testDateTimeTypes() throws SQLException { runQuery("CREATE TABLE test_datetimes (order Int8, " + From e1a0be6b82c3a5dbe76ab0bb27d077b64b68e424 Mon Sep 17 00:00:00 2001 From: Sergey Chernov Date: Tue, 7 Apr 2026 13:57:43 -0700 Subject: [PATCH 5/5] Added more tests for different floating point numbers and fixed rounding issue --- .../internal/NumberConverter.java | 2 - .../internal/ValueConverters.java | 2 - .../internal/NumberConverterTests.java | 8 ++ .../internal/ValueConvertersTests.java | 8 ++ .../client/datatypes/DataTypeTests.java | 47 +++++++++++ .../clickhouse/jdbc/JdbcDataTypeTests.java | 78 +++++++++++++++++++ 6 files changed, 141 insertions(+), 4 deletions(-) diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java index 803fa956a..aace6d252 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/NumberConverter.java @@ -144,8 +144,6 @@ public static BigDecimal toBigDecimal(Object value) { return new BigDecimal((BigInteger) value); } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { return BigDecimal.valueOf(((Number) value).longValue()); - } else if (value instanceof Float || value instanceof Double) { - return BigDecimal.valueOf(((Number) value).doubleValue()); } else if (value instanceof Number) { return new BigDecimal(value.toString()); } else if (value instanceof String) { diff --git a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java index 3872fb8c3..adf2246ba 100644 --- a/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java +++ b/client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/ValueConverters.java @@ -215,8 +215,6 @@ public BigInteger convertNumberToBigInteger(Object value) { public BigDecimal convertNumberToBigDecimal(Object value) { if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long) { return BigDecimal.valueOf(((Number)value).longValue()); - } else if (value instanceof Float || value instanceof Double) { - return BigDecimal.valueOf(((Number)value).doubleValue()); } return new BigDecimal(value.toString()); } diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java index 91bb779d6..7a324a45d 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/NumberConverterTests.java @@ -18,6 +18,14 @@ public void testToBigDecimalSupportsStringValues() { Assert.assertEquals(NumberConverter.toBigDecimal("98765.4321"), new BigDecimal("98765.4321")); } + @Test + public void testToBigDecimalPreservesFractionalFloatBoundaries() { + Assert.assertEquals(NumberConverter.toBigDecimal(0.0001f).compareTo(new BigDecimal("0.0001")), 0); + Assert.assertEquals(NumberConverter.toBigDecimal(0.0256f).compareTo(new BigDecimal("0.0256")), 0); + Assert.assertEquals(NumberConverter.toBigDecimal(6.5536f).compareTo(new BigDecimal("6.5536")), 0); + Assert.assertEquals(NumberConverter.toBigDecimal(838.8608f).compareTo(new BigDecimal("838.8608")), 0); + } + private static final class CustomNumber extends Number { private final BigDecimal value; diff --git a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java index 59f216978..42613a5d9 100644 --- a/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/ValueConvertersTests.java @@ -29,6 +29,14 @@ public void testConvertNumberToBigDecimalSupportsFloatingNumberValues(Number val Assert.assertEquals(converters.convertNumberToBigDecimal(value), expected); } + @Test + public void testConvertNumberToBigDecimalPreservesFractionalFloatBoundaries() { + Assert.assertEquals(converters.convertNumberToBigDecimal(0.0001f).compareTo(new BigDecimal("0.0001")), 0); + Assert.assertEquals(converters.convertNumberToBigDecimal(0.0256f).compareTo(new BigDecimal("0.0256")), 0); + Assert.assertEquals(converters.convertNumberToBigDecimal(6.5536f).compareTo(new BigDecimal("6.5536")), 0); + Assert.assertEquals(converters.convertNumberToBigDecimal(838.8608f).compareTo(new BigDecimal("838.8608")), 0); + } + @Test public void testNumberToBigDecimalConverterIsRegisteredForNumericSourceTypes() { Map, Function> longConverters = converters.getConvertersForType(Long.class); diff --git a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java index 2b15130d6..dd9f1e6fc 100644 --- a/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java +++ b/client-v2/src/test/java/com/clickhouse/client/datatypes/DataTypeTests.java @@ -389,6 +389,53 @@ public void testGetBigDecimalDoesNotTruncateLargeIntegers() throws Exception { } } + @Test(groups = {"integration"}) + public void testDecimalColumnWithFractionalFloatValues() throws Exception { + final String table = "test_decimal_fractional_floats"; + float[] values = new float[]{ + 0.0001f, // mantissa 1 + 0.0127f, // mantissa 127 + 0.0128f, // mantissa 128 + 0.0255f, // mantissa 255 + 0.0256f, // mantissa 256 + 6.5535f, // mantissa 65535 + 6.5536f, // mantissa 65536 + 838.8607f, // mantissa 8388607 + 838.8608f // mantissa 8388608 + }; + String[] expected = new String[]{ + "0.0001", + "0.0127", + "0.0128", + "0.0255", + "0.0256", + "6.5535", + "6.5536", + "838.8607", + "838.8608" + }; + + client.execute("DROP TABLE IF EXISTS " + table).get(); + client.execute(tableDefinition(table, "rowId Int32", "field Decimal32(4)")).get(); + + TableSchema tableSchema = client.getTableSchema(table); + client.register(DTOForDynamicPrimitivesTests.class, tableSchema); + + List data = new ArrayList<>(); + for (int i = 0; i < values.length; i++) { + data.add(new DTOForDynamicPrimitivesTests(i, values[i])); + } + client.insert(table, data).get().close(); + + List rows = client.queryAll("SELECT * FROM " + table + " ORDER BY rowId"); + Assert.assertEquals(rows.size(), expected.length); + + for (int i = 0; i < expected.length; i++) { + Assert.assertEquals(rows.get(i).getString("field"), expected[i]); + Assert.assertEquals(rows.get(i).getBigDecimal("field"), new BigDecimal(expected[i])); + } + } + @Test(groups = {"integration"}) public void testVariantWithArrays() throws Exception { testVariantWith("arrays", new String[]{"field Variant(String, Array(String))"}, 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 a74cea3eb..bbafbe576 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/JdbcDataTypeTests.java @@ -719,6 +719,84 @@ private void assertDecimalTruncationRow(ResultSet rs, int order, BigDecimal[] ex } } + @Test(groups = { "integration" }) + public void testDecimalTypesWithFractionalFloatParameters() throws SQLException { + final String tableName = "test_decimal_fractional_floats"; + float[] values = new float[] { + 0.0001f, // mantissa 1 + 0.0127f, // mantissa 127 + 0.0128f, // mantissa 128 + 0.0255f, // mantissa 255 + 0.0256f, // mantissa 256 + 6.5535f, // mantissa 65535 + 6.5536f, // mantissa 65536 + 838.8607f, // mantissa 8388607 + 838.8608f // mantissa 8388608 + }; + String[] expectedScale4 = new String[] { + "0.0001", + "0.0127", + "0.0128", + "0.0255", + "0.0256", + "6.5535", + "6.5536", + "838.8607", + "838.8608" + }; + String[] expectedScale8 = new String[] { + "0.00010000", + "0.01270000", + "0.01280000", + "0.02550000", + "0.02560000", + "6.55350000", + "6.55360000", + "838.86070000", + "838.86080000" + }; + runQuery("DROP TABLE IF EXISTS " + tableName); + runQuery("CREATE TABLE " + tableName + " (order Int8, " + + "dec Decimal(9, 4), dec32 Decimal32(4), dec64 Decimal64(8)" + + ") ENGINE = MergeTree ORDER BY ()"); + + try (Connection conn = getJdbcConnection(); + PreparedStatement stmt = conn.prepareStatement("INSERT INTO " + tableName + " VALUES (?, ?, ?, ?)")) { + for (int i = 0; i < values.length; i++) { + stmt.setInt(1, i + 1); + stmt.setFloat(2, values[i]); + stmt.setFloat(3, values[i]); + stmt.setFloat(4, values[i]); + stmt.executeUpdate(); + } + } + + try (Connection conn = getJdbcConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " ORDER BY order")) { + for (int i = 0; i < expectedScale4.length; i++) { + assertTrue(rs.next()); + assertEquals(rs.getInt("order"), i + 1); + + assertEquals(rs.getString("dec"), expectedScale4[i]); + assertEquals(rs.getBigDecimal("dec"), new BigDecimal(expectedScale4[i])); + assertEquals(rs.getObject("dec"), new BigDecimal(expectedScale4[i])); + assertEquals(rs.getObject("dec", BigDecimal.class), new BigDecimal(expectedScale4[i])); + + assertEquals(rs.getString("dec32"), expectedScale4[i]); + assertEquals(rs.getBigDecimal("dec32"), new BigDecimal(expectedScale4[i])); + assertEquals(rs.getObject("dec32"), new BigDecimal(expectedScale4[i])); + assertEquals(rs.getObject("dec32", BigDecimal.class), new BigDecimal(expectedScale4[i])); + + assertEquals(rs.getString("dec64"), expectedScale8[i]); + assertEquals(rs.getBigDecimal("dec64"), new BigDecimal(expectedScale8[i])); + assertEquals(rs.getObject("dec64"), new BigDecimal(expectedScale8[i])); + assertEquals(rs.getObject("dec64", BigDecimal.class), new BigDecimal(expectedScale8[i])); + } + assertFalse(rs.next()); + } + } + @Test(groups = { "integration" }) public void testDateTimeTypes() throws SQLException { runQuery("CREATE TABLE test_datetimes (order Int8, " +