Repository navigation
Fix jdbc-v2: report the element type of a multi-dimensional array - #3215
polyglotAI-bot wants to merge 3 commits into
Conversation
For an Array(Array(T)) column, java.sql.Array#getBaseType() and getBaseTypeName() reported the innermost type T. The Array constructor took the element type from getArrayBaseColumn(), which is the innermost column below all Array levels. The element of a multi-dimensional array is the next array level, which is also what the VALUE column of the array's own getResultSet() reports. For a column with more than one Array level, the JDBC element type now comes from the first-level element column. getBaseDataType() and getNestedLevel() do not change, so the parameter encoding in PreparedStatementImpl does not change. Fixes: #3190 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Docs drift check: resolved, docs now match this PR's changes. |
The "Array Element Type Metadata" section in docs/clickhouse-docs/jdbc.mdx said that nested arrays are flattened: getBaseTypeName() returned the innermost type. After the fix for #3190, getBaseTypeName()/getBaseType() return the next array level (Array(T)/ARRAY), the same as the VALUE column of getResultSet(). Update the rule row, the note, and the two examples. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Docs drift check: valid. Fixed in 65d7340. In
These values match |
Client V2 CoverageCoverage Report
Class Coverage
|
JDBC V2 CoverageCoverage Report
Class Coverage
|
JDBC V1 CoverageCoverage Report
Class Coverage
|
Client V1 CoverageCoverage Report
Class Coverage
|
TriageCategory: Summary What this impacts
Concerns
Required reviewer action
|
Reporting the element type from java.sql.Array#getBaseType() and getBaseTypeName() of a multi-dimensional array changes behavior that users can rely on. The new driver property controls it: - true (default): the element type, which is the next array level (ARRAY / Array(Int32) for Array(Array(Int32))). - false: the innermost non-array type (INTEGER / Int32), as in earlier versions. The flag is read from the connection configuration and applies to arrays from ResultSet#getArray/getObject, Connection#createArrayOf, and the nested arrays of Array#getResultSet(). The VALUE column metadata of getResultSet() does not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|



Description
Fixes #3190.
For a multi-dimensional array,
java.sql.Array#getBaseType()/getBaseTypeName()reported the innermost type.SELECT [[1, 2, 3]](Array(Array(UInt8))) gaveSMALLINT/UInt8, but the elements of that array areArray(UInt8)values. A caller that trustsgetBaseType()reads an element as a number and getsValue of class [Ljava.lang.Short; cannot be converted to class java.lang.Integer.Root cause. The
Arrayconstructor took the element type fromcolumn.getArrayBaseColumn(). That is the innermost column below allArraylevels, so it is the element type only for a one-level array. JDBC definesgetBaseType()as the type of the elements of the array. TheVALUEcolumn of the array's owngetResultSet()already uses the first-level element (ArrayResultSet.elementColumn), so the two disagreed.Fix. When the column has more than one
Arraylevel,getBaseType()/getBaseTypeName()now come from the first-level element column (column.getNestedColumns().get(0)):ARRAY/Array(UInt8)for the example above. One-level arrays,QBit, geo types, andNestedkeep the current resolution.Feature flag. The new driver property
jdbc_multi_dimensional_array_element_type(defaulttrue) controls this. Set it tofalseto keep the earlier behavior (the innermost type). The flag is read from the connection config and applies toResultSet#getArray/getObject,Connection#createArrayOf, and the nested arrays ofArray#getResultSet().Changes
jdbc-v2types/Array: the JDBC element type (getBaseType(),getBaseTypeName()) of an array with more than one level is the next array level.getBaseDataType()andgetNestedLevel()do not change.PreparedStatementImpluses them to encode an array parameter, so the write path does not change.jdbc-v2DriverProperties: newMULTI_DIMENSIONAL_ARRAY_ELEMENT_TYPE(jdbc_multi_dimensional_array_element_type, defaulttrue).ResultSetImpl,JdbcUtils.convert,ConnectionImpl#createArrayOf,ArrayResultSet: pass the flag to theArraythey create. New overloadsArray(ClickHouseColumn, Object[], boolean),ArrayResultSet(Object, ClickHouseColumn, boolean)andJdbcUtils.convert(..., boolean). The existing signatures stay and use the defaulttrue.history/latest/3190.md: change record, marked as a breaking change, with the property to restore the earlier behavior.docs/features.md,docs/clickhouse-docs/jdbc.mdx,docs/integration-jdbc.md: the element type of a multi-dimensionalArray(Array(...))value and the new property.Test
ResultSetImplTest#testMultiDimensionalArrayElementType(integration,@DataProvider, each query with the default and withjdbc_multi_dimensional_array_element_type=false). For each query it reads the column withgetArray, then goes down one level at a time throughgetResultSet()/getArray(2). At each level it assertsgetBaseTypeName()andgetBaseType()(the next level by default, the innermost type withfalse), and theVALUEcolumn metadata of that level'sgetResultSet()(the next level in both modes). It also checks thatgetResultSet(index, count)gives the same nested array. At the last level it reads the first value.Array(Array(UInt8))— the reported case.Array(Array(Array(Int32)))— three levels.Array(Array(Nullable(String)))— wrapper type at the last level.Array(UInt8)— contrast case: a one-level array keepsSMALLINT/UInt8.ConnectionTest#testCreateArrayOfMultiDimensionalElementTypecovers theConnection#createArrayOfentry point with the property unset,true, andfalse.On
mainthe three multi-level default rows fail (expected [Array(UInt8)] but found [UInt8]), and the contrast row passes. WithArraychanged to ignore the flag, thefalserows fail (expected [UInt8] but found [Array(UInt8)]). With the change all of them pass.jdbc-v2 unit tests pass. Integration tests
ResultSetImplTest,ConnectionTest,DriverTestafter the flag change: 107 of 110 pass, 1 skipped, and the same 3 SSL tests fail (see below). Before the flag change: Integration testsResultSetImplTest,JdbcDataTypeTests,ConnectionTest,PreparedStatementTest: 243 of 247 pass, 1 skipped. The other 3 (ConnectionTest#testSSLModeTrust,#testSSLModeVerifyCa,#testSecureConnection) fail withSSL Problemwhen the connection is opened, because the local test server has no matching TLS setup. They do not use arrays. No existing assertion was changed or removed.Pre-PR validation gate
main, passes on branch)ResultSetImpl#getArray→JdbcUtils.convert→types.Array)history/latest/3190.md);docs/features.mdupdatedAGENTS.md/docs/changes_checklist.mdchanges_checklist.md— Conditional logic or guard changedgetArrayNestedLevel() > 1. A one-level array (Array(T), andQBit, which has level 1) and a column with level 0 (geo types,Nested,createArrayOf("<scalar>", ...)) use the same code as before. The contrast row pins the one-level case.Arraycolumn always has its element ingetNestedColumns().get(0), so the branch cannot get an empty list.changes_checklist.md— New configuration property addedjdbc_multi_dimensional_array_element_typeis unique, follows thejdbc_*driver-property naming, and is handled by the driver (it is not passed to the client).trueparses withBoolean.parseBoolean, the same as the other boolean driver properties. Any other value selects the earlier behavior.DriverPropertiesjavadoc,docs/features.md, and the driver-property tables ofdocs/clickhouse-docs/jdbc.mdxanddocs/integration-jdbc.md.Compatibility
Breaking change for the default, with an opt-out: set
jdbc_multi_dimensional_array_element_type=falseto get the earlier result. Observable change with the default: for an array with two or more levels,getBaseType()now returnsTypes.ARRAYandgetBaseTypeName()returns the next level's type name (Array(...)), not the innermost type. This is the value the JDBC contract defines and the value the array's owngetResultSet()metadata already reported. For a two-level array, code that passesgetBaseTypeName()back toConnection#createArrayOfstill encodes the same SQL literal:createArrayOf("Array(UInt8)", ...)and the oldcreateArrayOf("UInt8", ...)resolve to the same nesting level and base data type.Out of scope
Ring,Polygon,MultiPolygon, ...) and a top-levelNested(...)column have noArraylevel in their column metadata. They keep reporting their own type name (Polygon,Nested(...)), which existing tests pin. TheNestedcase is a separate design question.SimpleAggregateFunction,Variant,Dynamic) is not changed here.🤖 Generated with Claude Code