Skip to content

Commit 0c70a36

Browse files
fix(client-v2): accept any Number/String/Boolean when writing BFloat16
BFloat16 serialization cast the value directly to float, so inserting a Double (or any other non-Float Number, String, or Boolean) into a BFloat16 column threw ClassCastException. Route the value through NumberConverter.toFloat, matching the Float32/Float64 branches: any Number narrows via Number#floatValue(), String parses, Boolean maps to 1.0f/0.0f, and a truly unconvertible value now raises a clear IllegalArgumentException instead of ClassCastException.
1 parent f703a75 commit 0c70a36

2 files changed

Lines changed: 46 additions & 1 deletion

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ private static void serializePrimitiveData(OutputStream stream, Object value, Cl
555555
BinaryStreamUtils.writeUnsignedInt256(stream, NumberConverter.toBigInteger(value));
556556
break;
557557
case BFloat16:
558-
BinaryStreamUtils.writeBFloat16(stream, (float) value);
558+
BinaryStreamUtils.writeBFloat16(stream, NumberConverter.toFloat(value));
559559
break;
560560
case Float32:
561561
BinaryStreamUtils.writeFloat32(stream, NumberConverter.toFloat(value));

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,51 @@ public void testSerializeFloatColumnRejectsUnsupportedValue() {
111111
() -> serializeSingleValue("Float64", new Object()));
112112
}
113113

114+
@Test(dataProvider = "bFloat16ColumnValues")
115+
public void testSerializeBFloat16ColumnFromVariousNumberTypes(Object value, float expected)
116+
throws IOException {
117+
RowBinaryWithNamesAndTypesFormatReader reader = serializeSingleValue("BFloat16", value);
118+
119+
reader.next();
120+
121+
Assert.assertEquals(reader.getFloat("value"), expected, 0.0f);
122+
}
123+
124+
@DataProvider(name = "bFloat16ColumnValues")
125+
private Object[][] bFloat16ColumnValues() {
126+
// A non-Float value supplied for a BFloat16 column used to throw ClassCastException from the
127+
// direct (float) cast; any Number now narrows through NumberConverter.toFloat (Number#floatValue()),
128+
// matching the Float32/Float64 branches. Every value below is exactly representable in BFloat16
129+
// (its float32 low 16 bits are zero), so the write-side truncation is lossless and the expected
130+
// read-back is exact.
131+
return new Object[][] {
132+
// Same-type value keeps working unchanged (already passed before the fix).
133+
{1.5f, 1.5f},
134+
135+
// A Double (and other Number subtypes) supplied for a BFloat16 column.
136+
{1.5d, 1.5f},
137+
{-2.5d, -2.5f},
138+
{3, 3.0f},
139+
{7L, 7.0f},
140+
{(short) 5, 5.0f},
141+
{(byte) 2, 2.0f},
142+
{BigInteger.valueOf(9), 9.0f},
143+
{new BigDecimal("1.5"), 1.5f},
144+
{new CustomNumber("2.5"), 2.5f},
145+
146+
// String and Boolean are accepted too, matching the other numeric column branches.
147+
{"1.5", 1.5f},
148+
{true, 1.0f},
149+
{false, 0.0f},
150+
};
151+
}
152+
153+
@Test
154+
public void testSerializeBFloat16ColumnRejectsUnsupportedValue() {
155+
Assert.assertThrows(IllegalArgumentException.class,
156+
() -> serializeSingleValue("BFloat16", new Object()));
157+
}
158+
114159
@Test
115160
public void testSerializeBFloat16RoundTripAllValues() throws IOException {
116161
// Exhaustively round-trip every one of the 2^16 BFloat16 bit patterns. For pattern b the

0 commit comments

Comments
 (0)