Skip to content

Commit fb6ed59

Browse files
Fix client-v2: accept any Number for Float32/Float64 insert values
Float32/Float64 columns threw ClassCastException when a value whose boxed type did not match the column was supplied through the Object-typed insert surface (for example a Double, the natural type of a Java literal like 1.5, for a Float32 column, or a Float for a Float64 column). The RowBinary serializer narrowed with a direct `(float) value` / `(double) value` cast, which is a narrowing reference conversion to Float/Double followed by unboxing and fails for any other boxed numeric type. Route the two branches through the shared NumberConverter.toFloat/toDouble converters, matching the neighboring UInt/Decimal branches, so the float columns accept any Number (and String/Boolean) exactly like the Int* columns already do. Fixes: #2930
1 parent fbe31cd commit fb6ed59

3 files changed

Lines changed: 75 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
### Bug Fixes
66

7+
- **[client-v2]** Fixed `Float32`/`Float64` columns throwing `ClassCastException` when a value of a
8+
non-matching boxed numeric type was supplied through the `Object`-typed insert surface — for example a
9+
`Double` (the natural type of a Java literal like `1.5`) for a `Float32` column, or a `Float` for a
10+
`Float64` column. The `RowBinary` serializer now narrows any `Number` (and, like the `Int*` columns,
11+
a `String`/`Boolean`) through `Number#floatValue()`/`Number#doubleValue()`, so the float columns accept
12+
the same value types the integer columns already did. (https://github.com/ClickHouse/clickhouse-java/issues/2930)
13+
714
- **[client-v2]** Fixed POJO insert error classification so transport write failures such as java.net.SocketException:
815
Broken pipe (Write failed) are now surfaced as transfer/network errors instead of being wrapped as
916
DataSerializationException. This only changes the exception type reported for request-body transport failures during

client-v2/src/main/java/com/clickhouse/client/api/data_formats/internal/SerializerUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,10 +555,10 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl
555555
BinaryStreamUtils.writeUnsignedInt256(stream, NumberConverter.toBigInteger(value));
556556
break;
557557
case Float32:
558-
BinaryStreamUtils.writeFloat32(stream, (float) value);
558+
BinaryStreamUtils.writeFloat32(stream, NumberConverter.toFloat(value));
559559
break;
560560
case Float64:
561-
BinaryStreamUtils.writeFloat64(stream, (double) value);
561+
BinaryStreamUtils.writeFloat64(stream, NumberConverter.toDouble(value));
562562
break;
563563
case Decimal:
564564
case Decimal32:

client-v2/src/test/java/com/clickhouse/client/api/data_formats/internal/SerializerUtilsPrimitiveSerializationTests.java

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import com.clickhouse.data.ClickHouseColumn;
66
import com.clickhouse.data.format.BinaryStreamUtils;
77
import org.testng.Assert;
8+
import org.testng.annotations.DataProvider;
89
import org.testng.annotations.Test;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.math.BigDecimal;
15+
import java.math.BigInteger;
1416
import java.util.TimeZone;
1517

1618
public class SerializerUtilsPrimitiveSerializationTests {
@@ -45,6 +47,70 @@ public void testSerializeDecimalTargetFromGenericNumberValue() throws IOExceptio
4547
Assert.assertEquals(reader.getBigDecimal("value"), new BigDecimal("987654.321"));
4648
}
4749

50+
@Test(dataProvider = "floatColumnValues")
51+
public void testSerializeFloatColumnFromVariousNumberTypes(String type, Object value, double expected)
52+
throws IOException {
53+
RowBinaryWithNamesAndTypesFormatReader reader = serializeSingleValue(type, value);
54+
55+
reader.next();
56+
57+
Assert.assertEquals(reader.getDouble("value"), expected, 0.0);
58+
}
59+
60+
@DataProvider(name = "floatColumnValues")
61+
private Object[][] floatColumnValues() {
62+
return new Object[][] {
63+
// A Double supplied for a Float32 column (and a Float for a Float64 column) used to
64+
// throw ClassCastException; any Number now narrows through Number#floatValue()/
65+
// doubleValue(), matching how the Int* branches accept any Number.
66+
{"Float32", 1.5d, 1.5},
67+
{"Float32", -2.5d, -2.5},
68+
{"Float64", 1.5f, 1.5},
69+
{"Float64", -2.5f, -2.5},
70+
71+
// Same-type value keeps working unchanged (these already passed before the fix).
72+
{"Float32", 1.5f, 1.5},
73+
{"Float64", 2.5d, 2.5},
74+
75+
// Other Number subtypes are accepted for both float widths.
76+
{"Float32", 3, 3.0},
77+
{"Float32", 7L, 7.0},
78+
{"Float32", (short) 5, 5.0},
79+
{"Float32", (byte) 2, 2.0},
80+
{"Float32", BigInteger.valueOf(9), 9.0},
81+
{"Float32", new BigDecimal("1.5"), 1.5},
82+
{"Float32", new CustomNumber("2.5"), 2.5},
83+
{"Float64", 3, 3.0},
84+
{"Float64", 7L, 7.0},
85+
{"Float64", (short) 5, 5.0},
86+
{"Float64", (byte) 2, 2.0},
87+
{"Float64", BigInteger.valueOf(9), 9.0},
88+
{"Float64", new BigDecimal("1.25"), 1.25},
89+
{"Float64", new CustomNumber("2.5"), 2.5},
90+
91+
// String and Boolean are accepted too, matching the other numeric column branches.
92+
{"Float32", "1.5", 1.5},
93+
{"Float32", true, 1.0},
94+
{"Float32", false, 0.0},
95+
{"Float64", "1.5", 1.5},
96+
{"Float64", true, 1.0},
97+
{"Float64", false, 0.0},
98+
99+
// Out-of-Float-range values (now reachable through the fix) narrow like a primitive
100+
// cast: a Double beyond Float range becomes Float32 infinity rather than throwing.
101+
{"Float32", Double.MAX_VALUE, Double.POSITIVE_INFINITY},
102+
{"Float32", -Double.MAX_VALUE, Double.NEGATIVE_INFINITY},
103+
};
104+
}
105+
106+
@Test
107+
public void testSerializeFloatColumnRejectsUnsupportedValue() {
108+
Assert.assertThrows(IllegalArgumentException.class,
109+
() -> serializeSingleValue("Float32", new Object()));
110+
Assert.assertThrows(IllegalArgumentException.class,
111+
() -> serializeSingleValue("Float64", new Object()));
112+
}
113+
48114
private RowBinaryWithNamesAndTypesFormatReader serializeSingleValue(String type, Object value)
49115
throws IOException {
50116
ByteArrayOutputStream out = new ByteArrayOutputStream();

0 commit comments

Comments
 (0)