Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

### Bug Fixes

- **[client-v2]** Fixed a `NullPointerException` when serializing a `null` value into a non-nullable
`Enum8`/`Enum16` column. `SerializerUtils.serializeEnumData` had no `null` guard, so a `null` in a
non-nullable enum column reached `value.getClass()` and failed the RowBinary insert path with a confusing
NPE instead of a clear error. It now throws `IllegalArgumentException` naming the column, consistent with
the existing `IllegalArgumentException` for other unsupported enum values. Nullable enum columns are
unaffected. (https://github.com/ClickHouse/clickhouse-java/issues/2931)
- **[client-v2]** Fixed POJO insert error classification so transport write failures such as java.net.SocketException:
Broken pipe (Write failed) are now surfaced as transfer/network errors instead of being wrapped as
DataSerializationException. This only changes the exception type reported for request-body transport failures during
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,10 @@ private static void serializeTime64(OutputStream stream, Object value) throws IO
}

public static void serializeEnumData(OutputStream stream, ClickHouseColumn column, Object value) throws IOException {
if (value == null) {
throw new IllegalArgumentException("Cannot insert null into non-nullable column " + column.getColumnName()
+ " of type " + column.getOriginalTypeName());
}
int enumValue = -1;
if (value instanceof String) {
enumValue = column.getEnumConstants().value((String) value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,44 @@ public void testGeometrySerializationRejectsMalformedList() {
ClickHouseColumn.of("v", "Geometry")));
}

@Test(dataProvider = "nonNullableEnumTypes")
public void testNullIntoNonNullableEnumThrowsIllegalArgument(String typeName) {
ClickHouseColumn column = ClickHouseColumn.of("bs_flag", typeName);

IllegalArgumentException ex = Assert.expectThrows(IllegalArgumentException.class,
() -> SerializerUtils.serializeData(new ByteArrayOutputStream(), null, column));
String message = ex.getMessage();
Assert.assertTrue(message.contains("Cannot insert null into non-nullable column"),
"Unexpected message: " + message);
Assert.assertTrue(message.contains("bs_flag"),
"Message should name the offending column: " + message);
Assert.assertTrue(message.contains(typeName),
"Message should include the enum type: " + message);
}

@DataProvider(name = "nonNullableEnumTypes")
private Object[][] nonNullableEnumTypes() {
return new Object[][] {
{"Enum8('B' = 1, 'S' = 2)"},
{"Enum16('B' = 1, 'S' = 2)"},
};
}

@Test
public void testEnumSerializationUnaffectedByNullGuard() throws Exception {
// A Nullable(Enum) with null still takes the early null-marker path and never reaches
// enum serialization, so a single null-marker byte is written.
ByteArrayOutputStream nullableOut = new ByteArrayOutputStream();
SerializerUtils.serializeData(nullableOut, null,
ClickHouseColumn.of("v", "Nullable(Enum8('B' = 1, 'S' = 2))"));
Assert.assertEquals(nullableOut.toByteArray(), new byte[] {1});

// A present value in a non-nullable Enum column still serializes to its mapped numeric value.
ByteArrayOutputStream valueOut = new ByteArrayOutputStream();
SerializerUtils.serializeData(valueOut, "S", ClickHouseColumn.of("v", "Enum8('B' = 1, 'S' = 2)"));
Assert.assertEquals(valueOut.toByteArray(), new byte[] {2});
}

@Test(dataProvider = "nestedNullableData")
public void testNestedNullableRoundTrip(String typeName, Object value) throws Exception {
ClickHouseColumn column = ClickHouseColumn.of("v", typeName);
Expand Down
Loading