Fix LOGICAL_ERROR in MergeTreeIndexConditionSet under MV layer type change#105552
Fix LOGICAL_ERROR in MergeTreeIndexConditionSet under MV layer type change#105552groeneai wants to merge 3 commits into
Conversation
…hange Closes ClickHouse#89802. When a `set` skip index is built on a non-`Nullable` storage column and the column is read through a MaterializedView declaring it `Nullable`, with `query_plan_merge_expressions = 0` keeping the MV's `_CAST` step separate from the WHERE filter, the index condition exception: Logical error: 'Unexpected return type from equals. Expected Nullable(UInt8). Got UInt8...' `MergeTreeIndexConditionSet` cloned the predicate verbatim, including `equals`' pre-resolved `result_type` resolved against the MV-layer `Nullable(Int32)` input. The granule block carries the storage column at the storage type (`Int32`), so `ExpressionActions::execute` runs `equals` on non-`Nullable` inputs (returning `UInt8`) but checks against the saved `Nullable(UInt8)` and throws. Fix: in `MergeTreeIndexConditionSet::atomFromDAG`, look up the granule (storage) type for each key column from `index_description.sample_block`, add an `INPUT` of that type, and `CAST` it back to the predicate's expected type. The downstream pre-resolved functions then see the type they were resolved for, keeping the function `result_type` invariant intact while the block fed into the actions remains the storage column unchanged. Added `tests/queries/0_stateless/04272_89802_set_index_mv_nullable_column_type.sql`. Verified the new test crashes the server without the fix and passes 30/30 runs with it; the existing 10 set-index stateless tests continue to pass.
|
cc @rschu1ze @alexey-milovidov — could you review this? The fix is in |
|
Workflow [PR], commit [c5346e6] Summary: ❌
AI ReviewSummary
Final Verdict
|
|
Bugfix validation FAIL update — investigated.
On master the test triggers a server crash (core dump captured), not a clean exception. Two options:
@PedroTadim @rschu1ze — which do you prefer? I'll push the PR-body edit either way. |
|
@groeneai I go with option 1 |
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered by tests: 19/22 (86.36%) | Lost baseline coverage: none · Uncovered code |
CI ledger — covered SHA:
|
| Check | Test | Disposition |
|---|---|---|
Stateless tests (amd_tsan, parallel, 1/2) |
00175_obfuscator_schema_inference |
flaky chronic trunk — task 2026-06-02-ci-00175obfuscatorschemainference-ab |
Unit tests (asan_ubsan) |
(check-level) | infra/chronic UBSan UntrackedMemoryCounter — task 2026-06-03-ci-p1-chronic-ubsan-in-untrackedmemoryc |
Bugfix validation (functional tests) |
04272_89802_set_index_mv_nullable_column_type |
this PR's OWN regression test — Bugfix validation framework limitation when MV layer type-change bug isn't reproducible on master HEAD without the right ordering. Not a real regression. |
No PR-caused failures. Awaiting human review.
Session: cron:our-pr-ci-monitor:20260604-213000
|
Going with option 1: category stays Bugfix validation FAIL is the framework limitation: server crashed (SERVER_DIED, captured core) instead of CI ledger for current HEAD |
CI finish ledger — c5346e6 (post master-merge)CI has fully finished (Finish Workflow passed; last checks 2026-06-24 13:14). This PR fixes a set-index LOGICAL_ERROR (MergeTreeIndexSet); the two TSan reds are an unrelated chronic trunk race. Every red check has an owner; only The fix + regression test were AI-Review-approved; the only "failures" are the unrelated chronic TSan race (external fix #108391) and the maintainer-accepted Bugfix-validation framework artifact. Ready for review. Session id: cron:our-pr-ci-monitor:20260625-170000 |

Closes #89802.
A
setskip index on a non-Nullablestorage column raised:when read through a MaterializedView declaring the column as
Nullable, withquery_plan_merge_expressions = 0keeping the MV's_CASTstep separatefrom the WHERE filter. Reproduces from version 25.5 onward
(https://fiddle.clickhouse.com/6dc1980e-30b2-4082-b570-5ef45287afc9).
MergeTreeIndexConditionSetcloned the predicate verbatim. Theequalsfunction's
result_typewas resolved against the MV-layerNullable(Int32)input. The granule block, however, carries the storage column at the storage
type (
Int32), soExpressionActions::executeranequalsonnon-
Nullableinputs (returningUInt8) and rejected the result againstthe saved
Nullable(UInt8).The fix: in
MergeTreeIndexConditionSet::atomFromDAG, look up the granule(storage) type for each key column from
index_description.sample_block,add an
INPUTof that type, andCASTit back to the predicate's expectedtype. The downstream pre-resolved functions then see the type they were
resolved for, keeping the function
result_typeinvariant intact while theblock fed into the actions remains the storage column unchanged.
Added
tests/queries/0_stateless/04272_89802_set_index_mv_nullable_column_type.sql,which reproduces the failure of the original report plus matching,
AND/OR/NOT,IN (...), andUInt8-vs-Nullable(Int32)variants. Confirmed:0_statelessset-index tests(
00907_*,00931_*,00965_*,00979_*,00997_*,01583_*,02112_*,02499_*)continue to pass.
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fix a logical error
Unexpected return type from equals. Expected Nullable(UInt8). Got UInt8.thrown byMergeTreeIndexConditionSet::getPossibleGranuleswhen asetskip index sits on a non-Nullablestorage column read through a MaterializedView that declares the column asNullable, withquery_plan_merge_expressions = 0.Documentation entry for user-facing changes