-
Notifications
You must be signed in to change notification settings - Fork 618
fixed issue converting multidimensional lists and arrays #2735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -411,8 +411,7 @@ public static <T> T[] convertList(List<?> values, Class<T> type, int dimensions) | |
| arrayDimensions[0] = values.size(); | ||
| T[] convertedValues = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions); | ||
| Stack<ArrayProcessingCursor> stack = new Stack<>(); | ||
| stack.push(new ArrayProcessingCursor(convertedValues, values, values.size())); | ||
|
|
||
| stack.push(new ArrayProcessingCursor(convertedValues, values, values.size(), dimensions)); | ||
| while (!stack.isEmpty()) { | ||
| ArrayProcessingCursor cursor = stack.pop(); | ||
|
|
||
|
|
@@ -422,10 +421,11 @@ public static <T> T[] convertList(List<?> values, Class<T> type, int dimensions) | |
| continue; // no need to set null value | ||
| } else if (value instanceof List<?>) { | ||
| List<?> srcList = (List<?>) value; | ||
| arrayDimensions = new int[Math.max(dimensions - stack.size() - 1, 1)]; | ||
| int depth = cursor.depth - 1; | ||
| arrayDimensions = new int[depth]; | ||
| arrayDimensions[0] = srcList.size(); | ||
|
Comment on lines
+424
to
426
|
||
| T[] targetArray = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions); | ||
| stack.push(new ArrayProcessingCursor(targetArray, value, srcList.size())); | ||
| stack.push(new ArrayProcessingCursor(targetArray, value, srcList.size(), depth)); | ||
| java.lang.reflect.Array.set(cursor.targetArray, i, targetArray); | ||
| } else { | ||
| java.lang.reflect.Array.set(cursor.targetArray, i, convert(value, type)); | ||
|
|
@@ -454,7 +454,7 @@ public static <T> T[] convertArray(Object values, Class<T> type, int dimensions) | |
| arrayDimensions[0] = java.lang.reflect.Array.getLength(values); | ||
| T[] convertedValues = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions); | ||
| Stack<ArrayProcessingCursor> stack = new Stack<>(); | ||
| stack.push(new ArrayProcessingCursor(convertedValues, values, arrayDimensions[0])); | ||
| stack.push(new ArrayProcessingCursor(convertedValues, values, arrayDimensions[0], dimensions)); | ||
|
|
||
| while (!stack.isEmpty()) { | ||
| ArrayProcessingCursor cursor = stack.pop(); | ||
|
|
@@ -464,10 +464,11 @@ public static <T> T[] convertArray(Object values, Class<T> type, int dimensions) | |
| if (value == null) { | ||
| continue; // no need to set null value | ||
| } else if (value.getClass().isArray()) { | ||
| arrayDimensions = new int[Math.max(dimensions - stack.size() - 1, 1)]; | ||
| int depth = cursor.depth - 1; | ||
| arrayDimensions = new int[depth]; | ||
| arrayDimensions[0] = java.lang.reflect.Array.getLength(value); | ||
|
Comment on lines
+467
to
469
|
||
| T[] targetArray = (T[]) java.lang.reflect.Array.newInstance(type, arrayDimensions); | ||
| stack.push(new ArrayProcessingCursor(targetArray, value, arrayDimensions[0])); | ||
| stack.push(new ArrayProcessingCursor(targetArray, value, arrayDimensions[0], depth)); | ||
| java.lang.reflect.Array.set(cursor.targetArray, i, targetArray); | ||
| } else { | ||
| java.lang.reflect.Array.set(cursor.targetArray, i, convert(value, type)); | ||
|
|
@@ -482,10 +483,12 @@ private static final class ArrayProcessingCursor { | |
| private final Object targetArray; | ||
| private final int size; | ||
| private final Function<Integer, Object> valueGetter; | ||
| private final int depth; | ||
|
|
||
| public ArrayProcessingCursor(Object targetArray, Object srcArray, int size) { | ||
| public ArrayProcessingCursor(Object targetArray, Object srcArray, int size, int depth) { | ||
| this.targetArray = targetArray; | ||
| this.size = size; | ||
| this.depth = depth; | ||
| if (srcArray instanceof List<?>) { | ||
| List<?> list = (List<?>) srcArray; | ||
| this.valueGetter = list::get; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1219,11 +1219,6 @@ public void testNestedArrays() throws Exception { | |||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Test for https://github.com/ClickHouse/clickhouse-java/issues/2723 | ||||||||||||||||||||||||||||||||
| * getString() on nested arrays was failing with NullPointerException due to re-entrancy bug | ||||||||||||||||||||||||||||||||
| * in DataTypeConverter when converting nested arrays to string representation. | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| @Test(groups = { "integration" }) | ||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||
| @Test(groups = { "integration" }) | |
| @Test(groups = { "integration" }) | |
| /** | |
| * Regression test for nested array stringification and {@code getString()} on | |
| * array-typed expressions (see issue #2723). | |
| * <p> | |
| * Originally, calling {@code ResultSet.getString()} on: | |
| * <ul> | |
| * <li>nested array columns such as {@code Array(Array(Int32))}, and</li> | |
| * <li>array-returning expressions used inside {@code CASE/WHEN}</li> | |
| * </ul> | |
| * could throw a {@link NullPointerException} due to re-entrancy issues in | |
| * the driver. This test verifies that {@code getString()} now works | |
| * correctly for those scenarios. | |
| */ |
Copilot
AI
Feb 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two issues here: (1) rs.getArray(1) is index-based and may not refer to deep_nested if the SELECT column order changes; prefer rs.getArray(\"deep_nested\") for stability. (2) assertEquals(arr.getArray(), new String[][][] {...}) is likely to fail because multi-dimensional arrays usually require deep comparison; use a deep equality check (e.g., Arrays.deepEquals) or compare a deep string representation. Also consider freeing the java.sql.Array (e.g., arr.free()) to avoid resource leaks in integration tests.
| Array arr = rs.getArray(1); | |
| assertEquals(arr.getArray(), new String[][][] {{{"a", "b"}, {"c"}}, {{ "d", "e", "f"}}}); | |
| Array arr = rs.getArray("deep_nested"); | |
| String[][][] actual = (String[][][]) arr.getArray(); | |
| String[][][] expected = new String[][][] {{{"a", "b"}, {"c"}}, {{"d", "e", "f"}}}; | |
| assertTrue(Arrays.deepEquals(actual, expected)); | |
| arr.free(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
client.query(...)for an INSERT is unconventional and may add unnecessary overhead versus an execute-style API (if available in this module). If the client providesexecute()/command()for non-SELECT statements, prefer that here to better match intent and reduce reliance on query semantics for DML.