diff --git a/docs/clickhouse-docs/jdbc.mdx b/docs/clickhouse-docs/jdbc.mdx index 0d23aae0f..1545cb3bb 100644 --- a/docs/clickhouse-docs/jdbc.mdx +++ b/docs/clickhouse-docs/jdbc.mdx @@ -86,6 +86,7 @@ as is to the driver. Driver has some own properties that aren't part of the clie | `jdbc_use_max_result_rows` | `false` | Enables using server property `max_result_rows` to limit number of rows returned by query. When enabled, overrides user-set overflow mode. See JavaDoc for details. | | `jdbc_sql_parser` | `JAVACC` | Configures which SQL parser to use. Choices: `ANTLR4`, `ANTLR4_PARAMS_PARSER`, `JAVACC`. | | `remember_last_set_roles` | `true` | Remember last set roles for the connection. | +| `jdbc_multi_dimensional_array_element_type` | `true` | `Array.getBaseTypeName()` and `Array.getBaseType()` of a multi-dimensional array return the element type, which is the next array level. Set to `false` to return the innermost non-array type, as in earlier versions. See **Array Element Type Metadata** below. | **Server Settings** @@ -372,13 +373,14 @@ The general mapping rules for arrays are: | `Array(())` | `()` | `` | | `Array(Nullable())` | `Nullable()` | `` | | `Array(LowCardinality())` | `LowCardinality()` | `` | -| `Array(Array(...()))` | `` (innermost element) | `` | +| `Array(Array(...()))` | `Array(...())` (next array level) | `ARRAY` | | `Array(Tuple(...))` | `Tuple(...)` (full definition) | `OTHER` | | `Array(Enum8(...))` / `Array(Enum16(...))` | `Enum8(...)` / `Enum16(...)` (full definition) | `VARCHAR` | Notes on the rules above: - Wrapper types (`Nullable`, `LowCardinality`) are preserved in `getBaseTypeName()` but `getBaseType()` resolves to the inner type's JDBC code. -- Nested arrays are flattened in the metadata: `getBaseTypeName()` returns the innermost non-array element type, not the immediate child. +- Nested arrays are not flattened in the metadata: `getBaseTypeName()` returns the immediate child type, which is the next array level, and `getBaseType()` returns `ARRAY`. The `VALUE` column of `getResultSet()` has the same type; call `getArray()` on it to read the next level. + Set the driver property `jdbc_multi_dimensional_array_element_type=false` to get the earlier behavior: `getBaseTypeName()` and `getBaseType()` return the innermost non-array type (`Int32` and `INTEGER` for `Array(Array(Int32))`) at each array level. The `VALUE` column metadata of `getResultSet()` always reports the next array level and is not affected by this property. - Parameterized types (`FixedString(N)`, full `Enum`/`Tuple` definitions) keep their parameters in `getBaseTypeName()`. **Examples:** @@ -404,8 +406,8 @@ Notes on the rules above: | Array(Nullable(String)) | Nullable(String) | VARCHAR | | Array(LowCardinality(String)) | LowCardinality(String) | VARCHAR | | Array(LowCardinality(Nullable(String))) | LowCardinality(Nullable(String)) | VARCHAR | -| Array(Array(Int32)) | Int32 | INTEGER | -| Array(Array(Array(String))) | String | VARCHAR | +| Array(Array(Int32)) | Array(Int32) | ARRAY | +| Array(Array(Array(String))) | Array(Array(String)) | ARRAY | | Array(Tuple(name String, val Int32)) | Tuple(name String, val Int32) | OTHER | | Array(Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3)) | Enum8('alpha' = 1, 'beta' = 2, 'gamma' = 3) | VARCHAR | diff --git a/docs/features.md b/docs/features.md index a9c6e405a..41c3df368 100644 --- a/docs/features.md +++ b/docs/features.md @@ -93,7 +93,7 @@ Compatibility-sensitive traits: - Type mapping and conversions: Maps ClickHouse types to JDBC types and Java classes, including date/time handling and `java.time` support. - QBit type mapping: For ClickHouse `25.10+`, JDBC exposes the experimental `QBit(element_type, dimension)` type as `ARRAY`, returning the vector as a `java.sql.Array` of the element type from `getObject()`/`getArray()`. Supported element types are `BFloat16` and `Float32` (both `java.lang.Float`) and `Float64` (`java.lang.Double`). The `allow_experimental_qbit_type` server setting is required to create a `QBit` column. - Custom result-set type map: `ResultSet#getObject(int|String, Map>)` accepts both ClickHouse type names and JDBC `SQLType` names as map keys. Only unwrapped type names are used — `Nullable(...)` and `LowCardinality(...)` wrappers are stripped before lookup, so a key like `"Int32"` matches both `Int32` and `Nullable(Int32)` columns, and keys like `"Nullable(Int32)"` are not recognized. Lookup order is `ClickHouseColumn#getDataType().name()` (e.g. `"Int32"`, `"String"`, `"DateTime"`) then `SQLType.getName()` (e.g. `"INTEGER"`, `"VARCHAR"`, `"TIMESTAMP"`); a missing entry leaves the value uncoerced (read as-is). The feature is supported for primitive ClickHouse types only — `Array`, `Tuple`, `Map`, `Nested`, and geometry types (`Point`, `Ring`, `LineString`, `MultiPoint`, `Polygon`, `MultiPolygon`, `MultiLineString`, `Geometry`) bypass the type map and are returned in their native form. -- Arrays and tuples: Supports JDBC arrays plus ClickHouse tuple values through custom `Array` and `Struct` implementations. +- Arrays and tuples: Supports JDBC arrays plus ClickHouse tuple values through custom `Array` and `Struct` implementations. For a multi-dimensional `Array(Array(...))` value, `java.sql.Array#getBaseType()`/`getBaseTypeName()` report the element type, which is the next array level (`ARRAY`/`Array(T)`), the same as the `VALUE` column of its `getResultSet()`. The driver property `jdbc_multi_dimensional_array_element_type=false` (default `true`) selects the earlier behavior, where they report the innermost non-array type (`T`); the `VALUE` column metadata of `getResultSet()` is not affected. - Nested columns: Un-flattened `Nested(f1 T1, ..., fN TN)` columns (tables created with `flatten_nested = 0`) are exposed as JDBC `ARRAY` whose element type is `Tuple(f1 T1, ..., fN TN)`. They can be inserted through `Connection#createArrayOf`/`setArray` or `setObject` (a Java array of tuples) and read back through `getArray`/`getObject`; `java.sql.Array#getResultSet()` iterates the nested rows as `(INDEX, VALUE)` pairs where each `VALUE` is the tuple. An `Array(Nested(...))` column is read as a JDBC `ARRAY` whose elements are the nested values, each an array of tuples (`Object[]` of `Object[]`); its `getResultSet()` returns each element as a `java.sql.Array` equal to a top-level `Nested(...)` value. - Geometry type mapping: For ClickHouse `25.11+`, where `Geometry` changed from a string alias to `Variant(Point, Ring, LineString, MultiLineString, Polygon, MultiPolygon)`, JDBC exposes `Geometry` as `ARRAY`, returns nested Java arrays from `getObject()`/`getArray()`, and accepts `Struct` or nested `Array` inputs for prepared-statement inserts depending on the geometry shape. For ClickHouse `26.8+` the variant also carries `MultiPoint`. - MultiPoint type mapping: For ClickHouse `26.8+`, JDBC exposes `MultiPoint` as `ARRAY` with type name `MultiPoint`, returns `double[][]` from `getObject()` and a `java.sql.Array` from `getArray()`, and accepts a nested `Array` (`createArrayOf("Array(Point)", ...)`) for prepared-statement inserts, the same as `Ring` and `LineString`. diff --git a/docs/integration-jdbc.md b/docs/integration-jdbc.md index bbdd38d30..b1c1213f3 100644 --- a/docs/integration-jdbc.md +++ b/docs/integration-jdbc.md @@ -834,6 +834,7 @@ Key JDBC-specific properties (see [`DriverProperties`](../jdbc-v2/src/main/java/ | `default_query_settings` | — | Default settings for all queries | | `jdbc_metrics_recorder` | — | Custom `MetricsRecorder` implementation class name | | `jdbc_metadata_use_show_statements` | `true` | `DatabaseMetaData` schemas, tables and columns via `SHOW` / `DESCRIBE` instead of `system.*` tables | +| `jdbc_multi_dimensional_array_element_type` | `true` | `Array#getBaseType()` / `getBaseTypeName()` of a multi-dimensional array return the next array level; `false` returns the innermost non-array type | ## Observability & Monitoring diff --git a/history/latest/3190.md b/history/latest/3190.md new file mode 100644 index 000000000..70f8184d0 --- /dev/null +++ b/history/latest/3190.md @@ -0,0 +1,6 @@ +- **[jdbc-v2]** Breaking change: `java.sql.Array#getBaseType()`/`getBaseTypeName()` of a multi-dimensional array + now report the element type, which is the next array level (`ARRAY`/`Array(UInt8)` for `Array(Array(UInt8))`). + Before, they reported the innermost type (`SMALLINT`/`UInt8`), which did not match the element values. This also + applies to arrays from `Connection#createArrayOf`. The new driver property + `jdbc_multi_dimensional_array_element_type` (default `true`) controls this behavior. Set it to `false` to get the + innermost type, as in earlier versions. (https://github.com/ClickHouse/clickhouse-java/issues/3190) diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java index ff7e1a8fe..1a3963a5a 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ConnectionImpl.java @@ -646,7 +646,8 @@ public Array createArrayOf(String typeName, Object[] elements) throws SQLExcepti ClickHouseColumn column = ClickHouseColumn.of("array", typeName); try { - return new com.clickhouse.jdbc.types.Array(column, elements); + return new com.clickhouse.jdbc.types.Array(column, elements, + config.isFlagSet(DriverProperties.MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE)); } catch (Exception e) { throw new SQLException("Failed to create array", ExceptionUtils.SQL_STATE_CLIENT_ERROR, e); } diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java index 4b27626d0..32fe3864d 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/DriverProperties.java @@ -171,6 +171,17 @@ public enum DriverProperties { */ METADATA_USE_SHOW_STATEMENTS("jdbc_metadata_use_show_statements", String.valueOf(Boolean.TRUE)), + /** + * Controls what {@link java.sql.Array#getBaseType()} and {@link java.sql.Array#getBaseTypeName()} return for a + * multi-dimensional array like {@code Array(Array(Int32))}. When enabled, they return the element type, which is + * the next array level ({@code ARRAY} and {@code Array(Int32)}). When disabled, they return the innermost + * non-array type ({@code INTEGER} and {@code Int32}), as in earlier versions. One-level arrays are not affected. + * The metadata of the {@code VALUE} column of {@link java.sql.Array#getResultSet()} always reports the next array + * level and is not affected. + * Default: true + */ + MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE("jdbc_multi_dimensional_array_element_type", String.valueOf(Boolean.TRUE)), + ; diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java index cd4406cb6..e91dd2e44 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/ResultSetImpl.java @@ -72,6 +72,8 @@ public class ResultSetImpl implements ResultSet, JdbcV2Wrapper { private final Map> connTypeMap; + private final boolean multiDimensionalArrayElementType; + public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, ClickHouseFormatReader reader, Consumer onDataTransferException) throws SQLException { this(parentStatement, response, reader, onDataTransferException, JdbcUtils.DATA_TYPE_CLASS_MAP); @@ -86,6 +88,8 @@ public ResultSetImpl(StatementImpl parentStatement, QueryResponse response, Clic this.featureManager = new FeatureManager(parentStatement.getConnection().getJdbcConfig()); TableSchema tableMetadata = reader.getSchema(); this.connTypeMap = parentStatement.getConnection().getTypeMap(); + this.multiDimensionalArrayElementType = parentStatement.getConnection().getJdbcConfig() + .isFlagSet(DriverProperties.MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE); final Map> resolvedDefaultTypeMap = defaultTypeMap != null ? defaultTypeMap : JdbcUtils.DATA_TYPE_CLASS_MAP; @@ -1552,7 +1556,7 @@ public T getObjectImpl(String columnLabel, Class type, Map type) throws SQLException { } public static Object convert(Object value, Class type, ClickHouseColumn column) throws SQLException { + return convert(value, type, column, true); + } + + /** + * @param multiDimensionalArrayElementType passed to the created {@link Array}, see + * {@link Array#Array(ClickHouseColumn, Object[], boolean)} + */ + public static Object convert(Object value, Class type, ClickHouseColumn column, + boolean multiDimensionalArrayElementType) throws SQLException { if (value == null || type == null) { return value; } @@ -316,16 +325,16 @@ public static Object convert(Object value, Class type, ClickHouseColumn colum ClickHouseDataType baseType = baseColumn.isNested() ? ClickHouseDataType.Tuple : baseColumn.getDataType(); Object[] convertedValues = convertList(listValue, convertToJavaClass(baseType), getArrayDimensions(column)); - return new Array(column, convertedValues); + return new Array(column, convertedValues, multiDimensionalArrayElementType); } // base type is unknown. all objects should be converted - return new Array(column, listValue.toArray()); + return new Array(column, listValue.toArray(), multiDimensionalArrayElementType); } if (value.getClass().isArray()) { if (type == java.sql.Array.class) { - return new Array(column, arrayToObjectArray(value)); + return new Array(column, arrayToObjectArray(value), multiDimensionalArrayElementType); } else if (type == Tuple.class) { return new Tuple(true, value); } @@ -338,10 +347,10 @@ public static Object convert(Object value, Class type, ClickHouseColumn colum ClickHouseDataType baseType = column.getArrayBaseColumn().getDataType(); Object[] convertedValues = convertArray(arrayValue.getArray(), convertToJavaClass(baseType), column.getArrayNestedLevel()); - return new Array(column, convertedValues); + return new Array(column, convertedValues, multiDimensionalArrayElementType); } - return new Array(column, arrayValue.getArrayOfObjects()); + return new Array(column, arrayValue.getArrayOfObjects(), multiDimensionalArrayElementType); } return convertObject(value, type, column); diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java index b7b23cc7f..7a6664356 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/Array.java @@ -22,17 +22,32 @@ public class Array implements java.sql.Array { private final String elementTypeName; private boolean valid; private final ClickHouseDataType baseDataType; + private final boolean multiDimensionalElementType; private ArrayResultSet arrayResultSet; public Array(ClickHouseColumn column, Object[] elements) throws SQLException { + this(column, elements, true); + } + + /** + * @param multiDimensionalElementType if {@code true}, {@link #getBaseType()} and {@link #getBaseTypeName()} of a + * multi-dimensional array return the element type (the next array level); + * if {@code false}, they return the innermost non-array type. See + * {@link com.clickhouse.jdbc.DriverProperties#MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE}. + */ + public Array(ClickHouseColumn column, Object[] elements, boolean multiDimensionalElementType) throws SQLException { this.column = column; this.array = elements; + this.multiDimensionalElementType = multiDimensionalElementType; // QBit is not an Array (isArray() == false) but is still a one-level array of its element // type, so use the array nesting level (set for both Array and QBit) to find the element column. ClickHouseColumn baseColumn = (this.column.getArrayNestedLevel() > 0 ? this.column.getArrayBaseColumn() : this.column); this.baseDataType = baseColumn.getDataType(); - this.elementTypeName = baseColumn.getOriginalTypeName(); - this.type = JdbcUtils.CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(baseDataType, JDBCType.OTHER).getVendorTypeNumber(); + // Elements of a multi-dimensional array are arrays of the next level, not values of the base column. + ClickHouseColumn elementColumn = multiDimensionalElementType && this.column.getArrayNestedLevel() > 1 + ? this.column.getNestedColumns().get(0) : baseColumn; + this.elementTypeName = elementColumn.getOriginalTypeName(); + this.type = JdbcUtils.CLICKHOUSE_TO_SQL_TYPE_MAP.getOrDefault(elementColumn.getDataType(), JDBCType.OTHER).getVendorTypeNumber(); this.valid = true; } @@ -94,7 +109,7 @@ public Object getArray(long index, int count, Map> map) throws public synchronized ResultSet getResultSet() throws SQLException { ensureValid(); if (arrayResultSet == null) { - arrayResultSet = new ArrayResultSet(array, column); + arrayResultSet = new ArrayResultSet(array, column, multiDimensionalElementType); } return arrayResultSet; } @@ -107,7 +122,7 @@ public ResultSet getResultSet(Map> map) throws SQLException { @Override public ResultSet getResultSet(long index, int count) throws SQLException { ensureValid(); - return new ArrayResultSet(getArray(index, count), column); + return new ArrayResultSet(getArray(index, count), column, multiDimensionalElementType); } @Override diff --git a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java index 1f63ad3c8..2c85b9dae 100644 --- a/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java +++ b/jdbc-v2/src/main/java/com/clickhouse/jdbc/types/ArrayResultSet.java @@ -56,9 +56,20 @@ public class ArrayResultSet implements ResultSet { private final Class defaultClass; private final ClickHouseColumn column; private final int columnCount; + private final boolean multiDimensionalElementType; public ArrayResultSet(Object array, ClickHouseColumn column) { + this(array, column, true); + } + + /** + * @param multiDimensionalElementType passed to the {@link com.clickhouse.jdbc.types.Array} values of the + * {@code VALUE} column, see + * {@link com.clickhouse.jdbc.types.Array#Array(ClickHouseColumn, Object[], boolean)} + */ + public ArrayResultSet(Object array, ClickHouseColumn column, boolean multiDimensionalElementType) { this.array = array; + this.multiDimensionalElementType = multiDimensionalElementType; this.length = java.lang.reflect.Array.getLength(array); this.pos = -1; this.column = column; @@ -162,7 +173,8 @@ private Object getValueAsObject(int columnIndex, Class type, Object defaultVa } if (type == Array.class) { - return new com.clickhouse.jdbc.types.Array(elementColumn(column), JdbcUtils.arrayToObjectArray(value)); + return new com.clickhouse.jdbc.types.Array(elementColumn(column), JdbcUtils.arrayToObjectArray(value), + multiDimensionalElementType); } else { Map, Function> valueConverterMap = initValueConverterMapIfNeeded(value); return convertValue(value, type, valueConverterMap); diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java index 782ef8229..8c7fea6b2 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ConnectionTest.java @@ -637,6 +637,29 @@ public void testCreateArrayVariants() throws Exception { } } + @Test(groups = {"integration"}, dataProvider = "multiDimensionalArrayElementTypeFlags") + public void testCreateArrayOfMultiDimensionalElementType(String flag, String baseTypeName, JDBCType baseType) + throws Exception { + Properties props = new Properties(); + if (flag != null) { + props.setProperty(DriverProperties.MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE.getKey(), flag); + } + try (Connection conn = getJdbcConnection(props)) { + Array array = conn.createArrayOf("Array(Array(Int32))", new Object[][] {{1, 2, 3}, {10, 20}}); + assertEquals(array.getBaseTypeName(), baseTypeName); + assertEquals(array.getBaseType(), baseType.getVendorTypeNumber()); + } + } + + @DataProvider + public static Object[][] multiDimensionalArrayElementTypeFlags() { + return new Object[][] { + {null, "Array(Int32)", JDBCType.ARRAY}, + {"true", "Array(Int32)", JDBCType.ARRAY}, + {"false", "Int32", JDBCType.INTEGER}, + }; + } + @Test(groups = { "integration" }) public void testCreateStruct() throws SQLException { try (Connection conn = this.getJdbcConnection()) { diff --git a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java index f6ad7842f..cc75e4920 100644 --- a/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java +++ b/jdbc-v2/src/test/java/com/clickhouse/jdbc/ResultSetImplTest.java @@ -4,6 +4,7 @@ import com.clickhouse.client.api.data_formats.JacksonJsonParserFactory; import com.clickhouse.data.ClickHouseVersion; import org.testng.Assert; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.io.InputStream; @@ -28,6 +29,7 @@ import java.sql.Types; import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.List; @@ -446,6 +448,63 @@ public void testGetResultSetFromArray() throws Exception { } } + @Test(groups = {"integration"}, dataProvider = "multiDimensionalArrays") + public void testMultiDimensionalArrayElementType(String elementTypeFlag, String sql, String[] elementTypeNames, + int leafType, String leafValue) throws Exception { + Properties props = new Properties(); + if (elementTypeFlag != null) { + props.setProperty(DriverProperties.MULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE.getKey(), elementTypeFlag); + } + boolean elementType = !"false".equals(elementTypeFlag); + String leafTypeName = elementTypeNames[elementTypeNames.length - 1]; + try (Connection conn = getJdbcConnection(props); Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery(sql)) { + assertTrue(rs.next()); + Array array = rs.getArray(1); + for (int level = 0; level < elementTypeNames.length; level++) { + boolean leaf = level == elementTypeNames.length - 1; + assertEquals(array.getBaseTypeName(), elementType ? elementTypeNames[level] : leafTypeName); + assertEquals(array.getBaseType(), elementType && !leaf ? Types.ARRAY : leafType); + + ResultSet elements = array.getResultSet(); + assertTrue(elements.next()); + assertEquals(elements.getMetaData().getColumnTypeName(2), elementTypeNames[level]); + assertEquals(elements.getMetaData().getColumnType(2), leaf ? leafType : Types.ARRAY); + if (leaf) { + assertEquals(elements.getString(2), leafValue); + } else { + Array next = elements.getArray(2); + ResultSet slice = array.getResultSet(0, 1); + assertTrue(slice.next()); + assertEquals(slice.getArray(2).getBaseTypeName(), next.getBaseTypeName()); + array = next; + } + } + } + } + + @DataProvider + public static Object[][] multiDimensionalArrays() { + Object[][] arrays = new Object[][] { + {"SELECT [1, 2, 3]", new String[] {"UInt8"}, Types.SMALLINT, "1"}, + {"SELECT [[1, 2, 3]]", new String[] {"Array(UInt8)", "UInt8"}, Types.SMALLINT, "1"}, + {"SELECT [[[7]]]::Array(Array(Array(Int32)))", + new String[] {"Array(Array(Int32))", "Array(Int32)", "Int32"}, Types.INTEGER, "7"}, + {"SELECT [['a', NULL]]::Array(Array(Nullable(String)))", + new String[] {"Array(Nullable(String))", "Nullable(String)"}, Types.VARCHAR, "a"}, + }; + List rows = new ArrayList<>(); + for (String elementTypeFlag : new String[] {null, "false"}) { + for (Object[] array : arrays) { + Object[] row = new Object[array.length + 1]; + row[0] = elementTypeFlag; + System.arraycopy(array, 0, row, 1, array.length); + rows.add(row); + } + } + return rows.toArray(new Object[0][]); + } + @Test(groups = {"integration"}) public void testGetResultSetFromArrayDate() throws Exception { try (Connection conn = getJdbcConnection(); Statement stmt = conn.createStatement()) {