Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
682746c
Fix client-v2 Native reader misreading multi-row Array columns
polyglotAI-bot Jul 24, 2026
6b9215c
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 28, 2026
a892414
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 29, 2026
4475214
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 29, 2026
640c93d
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 29, 2026
4787c56
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 29, 2026
3200360
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Jul 30, 2026
faa631d
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Aug 18, 2026
cc1bb77
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Aug 18, 2026
76b9411
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Aug 19, 2026
68a1133
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Aug 26, 2026
04f3415
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Sep 3, 2026
d94f8e8
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Sep 9, 2026
17e6ec8
Merge remote-tracking branch 'origin/main' into polyglot/native-multi…
polyglotAI-bot Sep 9, 2026
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 @@ -150,6 +150,12 @@

### Bug Fixes

- **[client-v2]** Fixed the `Native` format reader (`NativeFormatReader`) misreading `Array` columns in multi-row
results whose rows have different lengths. Native encodes an array column as cumulative row offsets followed by the
flattened elements, but the reader used the first row's offset as the element count for every row — truncating later
rows and desyncing the columns that follow the array in the same block. Each row's length is now derived from the
difference between consecutive offsets, and empty array rows (`len == 0`) no longer read a phantom element. Results
with uniform array lengths were unaffected. (https://github.com/ClickHouse/clickhouse-java/issues/2955)
- **[jdbc-v2]** Fixed `SQLException#getSQLState()` returning the generic data-exception state `22000`
when ClickHouse reports an unknown table. The driver now returns `42S02` (base table or view not found) while
preserving the ClickHouse error code and original exception. (https://github.com/ClickHouse/clickhouse-java/issues/3104)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,19 @@ private boolean readBlock() throws IOException {
+ "(e.g. Array/Tuple/Map), is not decoded. Use a RowBinary format "
+ "(e.g. RowBinaryWithNamesAndTypes) to read such QBit values");
} else if (column.isArray()) {
// Native encodes an Array column as nRows cumulative offsets followed by the
// flattened elements; each row's element count is the delta between consecutive
// offsets, not the first offset.
values = new ArrayList<>(nRows);
int[] sizes = new int[nRows];
long[] offsets = new long[nRows];
for (int j = 0; j < nRows; j++) {
sizes[j] = Math.toIntExact(binaryStreamReader.readLongLE());
offsets[j] = binaryStreamReader.readLongLE();
}
long prevOffset = 0;
for (int j = 0; j < nRows; j++) {
values.add(binaryStreamReader.readArrayItem(column.getNestedColumns().get(0), sizes[0]));
int len = Math.toIntExact(offsets[j] - prevOffset);
values.add(binaryStreamReader.readArrayItem(column.getNestedColumns().get(0), len));
prevOffset = offsets[j];
}
} else {
values = new ArrayList<>(nRows);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,11 @@ public ArrayValue readArray(ClickHouseColumn column) throws IOException {
}

public ArrayValue readArrayItem(ClickHouseColumn itemTypeColumn, int len) throws IOException {
if (len == 0) {
// Nothing to read for an empty array; typing it via resolveArrayItemClass avoids the
// primitive branch below reading a phantom element and indexing a zero-length array.
return new ArrayValue(resolveArrayItemClass(itemTypeColumn), 0);
}
ArrayValue array;
if (itemTypeColumn.isNullable()) {
Class<?> itemClass = resolveArrayItemClass(itemTypeColumn);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,52 @@
}
}

@DataProvider(name = "multiRowArrayCases")
Object[][] getMultiRowArrayCases() {
String nonUniform = "SELECT id, arr, tag FROM values("
+ "'id UInt32, arr Array(Int32), tag Int32', "
+ "(1, [10], 100), (2, [20, 21], 200), (3, [], 300), (4, [30, 31, 32], 400), (5, [40], 500)"
+ ") ORDER BY id";
List<Object[]> nonUniformRows = Arrays.asList(
new Object[]{1L, Arrays.asList(10), 100},
new Object[]{2L, Arrays.asList(20, 21), 200},
new Object[]{3L, Collections.emptyList(), 300},
new Object[]{4L, Arrays.asList(30, 31, 32), 400},
new Object[]{5L, Arrays.asList(40), 500});

String uniform = "SELECT id, arr, tag FROM values("
+ "'id UInt32, arr Array(Int32), tag Int32', "
+ "(1, [10, 11], 100), (2, [20, 21], 200), (3, [30, 31], 300)"
+ ") ORDER BY id";
List<Object[]> uniformRows = Arrays.asList(
new Object[]{1L, Arrays.asList(10, 11), 100},
new Object[]{2L, Arrays.asList(20, 21), 200},
new Object[]{3L, Arrays.asList(30, 31), 300});

return new Object[][]{
{ClickHouseFormat.Native, nonUniform, nonUniformRows},
{ClickHouseFormat.Native, uniform, uniformRows},
{ClickHouseFormat.RowBinaryWithNamesAndTypes, nonUniform, nonUniformRows},
};
}

@Test(groups = {"integration"}, dataProvider = "multiRowArrayCases")
public void testReadingMultiRowArrays(ClickHouseFormat format, String sql, List<Object[]> expectedRows)
throws Exception {
QuerySettings settings = new QuerySettings().setFormat(format);
try (QueryResponse response = client.query(sql, settings).get()) {
ClickHouseBinaryFormatReader reader = client.newBinaryFormatReader(response);
for (Object[] expected : expectedRows) {
Map<String, Object> record = reader.next();

Check warning on line 521 in client-v2/src/test/java/com/clickhouse/client/query/QueryTests.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this variable to not match a restricted identifier.

See more on https://sonarcloud.io/project/issues?id=ClickHouse_clickhouse-java&issues=AZ-UYt9PzkWu0uAdNVA-&open=AZ-UYt9PzkWu0uAdNVA-&pullRequest=2956
Assert.assertNotNull(record, "Expected a row for id " + expected[0]);
Assert.assertEquals(record.get("id"), expected[0]);
Assert.assertEquals(((BinaryStreamReader.ArrayValue) record.get("arr")).asList(), expected[1]);
Assert.assertEquals(record.get("tag"), expected[2]);
}
Assert.assertNull(reader.next());
}
}

@Test(groups = {"integration"})
public void testBinaryStreamReader() throws Exception {
final String table = "dynamic_schema_test_table";
Expand Down
Loading