Fix abort casting Array(Dynamic)/Array(Variant) to QBit with accurateCastOrNull#108288
Conversation
…CastOrNull
accurateCastOrNull(arr, 'QBit(...)') aborted (assertTypeEquality in
IColumn::insertFrom) when the source array element type was Dynamic or
Variant, e.g.
SELECT accurateCastOrNull(CAST(range(114), 'Array(Dynamic)'), 'QBit(Float32, 114)')
Under accurateOrNull, the per-variant element conversion from Dynamic/Variant
returns a Nullable column (overflow is reported as NULL), but
createArrayToQBitWrapper drove that conversion with a non-nullable QBit element
target. ConvertImplFromDynamicToColumn::execute then built a non-nullable result
column and called res->insertFrom() on the Nullable per-variant column, failing
the structural type-equality assertion.
QBit is not rejected by validateNestedTypesForAccurateCastOrNull (it reports
canBeInsideNullable() == true), unlike Array/Map, so the cast reaches the
wrapper instead of erroring early.
Fix: when the source nested type is Dynamic/Variant and cast_type is
accurateOrNull, request a Nullable nested target so the conversion assembles a
consistent column; the existing removeNullable() then strips it before bit
transposition (QBit elements are non-nullable). Covers both Dynamic and Variant
sources.
Found by the AST fuzzer (issue ClickHouse#107951, site 13).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
cc @rschu1ze @Avogar for review. Small fix in the CAST machinery ( |
|
Workflow [PR], commit [82126ec] Summary: ✅
AI ReviewSummaryThis PR fixes Missing context / blind spots
Final VerdictStatus: ✅ Approve |
The previous fix added a Nullable nested target so the Dynamic/Variant to QBit element conversion assembles a consistent column, then stripped it with removeNullable() before bit transposition (QBit elements are non-nullable). That dropped the per-element null map: a nested element that fails accurate conversion was marked NULL inside the nested column, but removeNullable() exposed its default value and the QBit row came back non-NULL. That violates accurateCastOrNull's contract (an inaccurate element must make the whole outer value NULL, as createTupleWrapper does for tuple elements). Aggregate the nested element null map per array row (any NULL element makes the row NULL) before stripping it, and wrap the returned QBit column with that row-level null map. wrapInNullable in prepareRemoveNullable then merges it with the source null map. QBit elements cannot hold NULL, so this mirrors createTupleWrapper's non-Nullable-target branch. Covers both Dynamic and Variant sources. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-PR validation gate
Session id: cron:clickhouse-worker-slot-4:20260623-124200 |
|
📊 Cloud Performance Report ✅ AI verdict: no significant changes detected. K_source=6 K_base=30 flagged=0/65 clickbench🟢 No significant changes tpch_adapted_1_official🟢 No significant changes Debug info
|
LLVM Coverage ReportChanged lines: Changed C/C++ lines covered by tests: 37/37 (100.00%) | Lost baseline coverage: none · Uncovered code |
419eea3

Related: #107951 (AST fuzzer, site 13)
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fix a server abort (
Logical errorinIColumn::insertFrom) when casting anArray(Dynamic)orArray(Variant)toQBitwithaccurateCastOrNull, e.g.accurateCastOrNull(CAST(range(114), 'Array(Dynamic)'), 'QBit(Float32, 114)').Description
accurateCastOrNull(arr, 'QBit(...)')aborted inIColumn::insertFrom(assertTypeEquality) when the source array element type wasDynamicorVariant.Root cause: under
accurateOrNull, the per-variant element conversion fromDynamic/Variantreturns aNullablecolumn (overflow is reported asNULL), butcreateArrayToQBitWrapperdrove that conversion with a non-nullable QBit element target.ConvertImplFromDynamicToColumn::executethen built a non-nullable result column and calledres->insertFrom()on theNullableper-variant column, failing the structural type-equality assertion.QBitis not rejected byvalidateNestedTypesForAccurateCastOrNull(it reportscanBeInsideNullable() == true, unlikeArray/Map), so the cast reaches the wrapper instead of erroring out early.Fix: when the source nested type is
Dynamic/Variantandcast_typeisaccurateOrNull, request aNullablenested target so the conversion assembles a consistent column; the existingremoveNullable()then strips it before bit transposition (QBit elements are non-nullable). Covers bothDynamicandVariantsources.Found by the AST fuzzer (issue #107951, site 13). The crash reproduces via plain SQL on master HEAD; the analyzer constant-folds the cast and aborts during
executeDryRunImpl.CI report (STID 2508-50d4): https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=107454&sha=223bf5ceb23779637a985737f414a20dbbae7a7a&name_0=PR&name_1=Stress%20test%20%28arm_asan_ubsan%29
Version info
26.6.1.1188