Skip to content

Add a regression test for NOT_FOUND_COLUMN_IN_BLOCK with LEFT JOIN and a constant predicate#111608

Open
groeneai wants to merge 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/test-111452-filter-push-down-constant-predicate
Open

Add a regression test for NOT_FOUND_COLUMN_IN_BLOCK with LEFT JOIN and a constant predicate#111608
groeneai wants to merge 2 commits into
ClickHouse:masterfrom
groeneai:groeneai/test-111452-filter-push-down-constant-predicate

Conversation

@groeneai

@groeneai groeneai commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Closes: #111452

Changelog category (leave one):

  • CI Fix or Improvement (changelog entry is not required)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

...

Description

A constant-true predicate (AND 1) combined with a LEFT JOIN, ORDER BY and LIMIT used to throw NOT_FOUND_COLUMN_IN_BLOCK. With filter push down, merge expressions, top-k-through-join and lazy materialization all active, the merged outer filter was left as a dangling input pass-through in the lazy half of the plan, referencing a column no input block provides. Regression from #104268 (shipped in 26.5/26.6), already fixed on master.

Test-only PR adding a stateless regression test that locks the fix in. The bug lives in the lazy-materialization-through-join path, so the test pins every optimization that must be enabled to reach that path (query_plan_filter_push_down, query_plan_merge_expressions, query_plan_top_k_through_join, query_plan_optimize_lazy_materialization, both *_max_limit_* caps set to 0 = unlimited so they never drop below the LIMIT) and includes an EXPLAIN liveness oracle. The oracle is join-specific: it asserts the SQL Join step is present, that top-k-through-join pushed a Sorting onto the preserved (left) input, and that lazy materialization split the read (JoinLazyColumnsStep). JoinLazyColumnsStep alone is the generic lazy-materialization stitch step (it also appears in single-table plans), so asserting only it would let the oracle stay green if the join rewrite regressed; the three-column form goes red when top-k-through-join or lazy materialization is disabled. Tagged no-parallel-replicas (like the other lazy-materialization tests).

Verified: the query throws Code: 10 NOT_FOUND_COLUMN_IN_BLOCK on a pre-fix master build and returns the expected single row on current master; the test passes under CI setting randomization.

…d a constant predicate

A constant-true predicate (AND 1) combined with a LEFT JOIN, ORDER BY and LIMIT used to
throw NOT_FOUND_COLUMN_IN_BLOCK: the optimizer looked for the whole merged filter expression
as a materialized input column instead of evaluating it. Regression from ClickHouse#104268, fixed on
master. This test locks the fix in, pinning the three default-on optimizations that must all
be enabled to trigger the defect.

Closes: ClickHouse#111452

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author
Pre-PR validation gate (click to expand)
# Question Answer
a Deterministic repro? Yes. On a pre-fix build the exact query throws Code: 10 NOT_FOUND_COLUMN_IN_BLOCK every time (all triggering optimizations default-on).
b Root cause explained? Yes. With filter push down + merge expressions + top-k-through-join + lazy materialization all active, the merged outer filter and(and(ts>=0,ts<=10), and(cluster='cluster1',1)) was left as a dangling input pass-through in the lazy half of the plan, referencing a column no input block provides. Fixed in the lazy-materialization pass on master (regression from #104268).
c Fix matches root cause? N/A for source — the engine fix is already on master. This PR's change is a regression test that pins exactly the optimizations forming the triggering path and asserts that path is taken.
d Test intent preserved / new tests added? Yes. New stateless test 04627 asserts the query returns ts=1 AND (via an EXPLAIN oracle) that the lazy-materialization-through-join path is actually present, so the test cannot pass without exercising the fixed path.
e Both directions demonstrated? Yes. Throws NOT_FOUND_COLUMN_IN_BLOCK on a pre-fix master build; returns ts=1 on current master. Test passes 50/50 under CI setting randomization.
f Fix is general across code paths? N/A (test-only, no source change).
g Fix generalizes across inputs? N/A (test-only, no type-gating guard). The related HAVING ... AND 1 variant (#80443) of the same class is already covered by existing test 03519.
h Backward compatible? N/A (test-only; no setting default, format, or validation change).
i Invariants and contracts preserved? N/A (test-only; adds no code, touches no shared state).

Session id: cron:clickhouse-author-slot-3:20260723-111600

@groeneai

Copy link
Copy Markdown
Contributor Author
Internal second-model review — adjudication log (click to expand)

Pre-publication review by an independent model (engine: codex; 3 findings across one full pass + one delta recheck; bounded: 1 full pass + 1 fix round + 1 delta recheck).

# Sev Finding Verdict Evidence / action
1 Test could pass without exercising the fixed Top-K / lazy-materialization path (runner can disable lazy-mat or cap the optimizations below LIMIT 10, result still 1). AGREE — fixed Verified by EXPLAIN sensitivity: the lazy path is present only when lazy-mat is on, both *_max_limit_* caps are >= the LIMIT, and top-k-through-join is on. Pinned all of these (caps to 0 = unlimited) and added an EXPLAIN oracle asserting JoinLazyColumnsStep is in the plan.
2 💡 Literal SQL / exception text in the header comment not formatted as inline code. AGREE — fixed Wrapped AND 1, LEFT JOIN, NOT_FOUND_COLUMN_IN_BLOCK etc. in backticks.
3 ⚠️ Test fails under the old-analyzer and ParallelReplicas CI profiles (pins neither the analyzer nor local execution). AGREE — fixed Confirmed against the sibling lazy-materialization test 02354_vector_search_lazy_materialization.sql: added -- Tags: no-parallel-replicas and a session-level SET enable_analyzer = 1 (verified it overrides the old-analyzer profile → 1,1).

Severity: ❌ blocker / ⚠️ major / 💡 nit. DISAGREE verdicts carry recorded evidence and are terminal per finding.

Session id: cron:clickhouse-author-slot-3:20260723-111600

@groeneai

Copy link
Copy Markdown
Contributor Author

cc @PedroTadim — as requested. Test-only PR adding the regression test for #111452 (the NOT_FOUND_COLUMN_IN_BLOCK is already fixed on master); it pins the optimizations that reach the lazy-materialization-through-join path and asserts via EXPLAIN that the path is exercised.

@PedroTadim PedroTadim added the can be tested Allows running workflows for external contributors label Jul 23, 2026
@PedroTadim
PedroTadim enabled auto-merge July 23, 2026 12:54
@clickhouse-gh

clickhouse-gh Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [1531dcd]


AI Review

Summary

This PR adds a stateless regression test for issue #111452, covering the NOT_FOUND_COLUMN_IN_BLOCK exception triggered by LEFT JOIN plus a constant predicate under filter push down, topKThroughJoin, and lazy materialization. The query/result check is good, but one plan-shaping setting is still left to stateless randomization, so the test does not yet reliably lock the exact LEFT JOIN path that the bug report and PR description say it covers.

Findings

⚠️ Majors

  • [tests/queries/0_stateless/04627_filter_push_down_constant_predicate.sql:9] The test says every optimizer needed for the failing path is pinned, but query_plan_join_swap_table is still randomized by the stateless runner (tests/clickhouse-test chooses auto or false). Existing test 04230_top_k_through_join_join_swap_gate.sql documents why this matters: once swap is allowed, the planner can rewrite the logical LEFT JOIN into RIGHT JOIN, and this oracle still stays green because it only checks for a Join, some Sorting below it, and JoinLazyColumnsStep. That means the regression can pass without proving the original LEFT JOIN lazy-materialization path is still exercised.
    Suggested fix: add SET query_plan_join_swap_table = false; alongside the other pinned settings, and keep the oracle comment aligned with that guarantee.
Final Verdict

Changes requested.

@clickhouse-gh clickhouse-gh Bot added the pr-ci label Jul 23, 2026
Comment thread tests/queries/0_stateless/04627_filter_push_down_constant_predicate.sql Outdated
@PedroTadim PedroTadim self-assigned this Jul 23, 2026
…c marker

The old liveness oracle only asserted that a JoinLazyColumnsStep is present in
the plan. That step is the generic lazy-materialization stitch step: it is built
whenever optimizeLazyMaterialization (or optimizeLazyFinal) splits a read into a
main and a lazy half, including for single-table plans (e.g. it appears in
02354_vector_search_queries.reference). So the oracle could stay green even if
the LEFT JOIN / top-k-through-join rewrite this test targets regressed, and the
test would no longer prove the code path described in its comment.

Replace it with a join-specific oracle that asserts three properties at once:
the SQL Join step is present; top-k-through-join pushed a Sorting onto the
preserved (left) input, i.e. a Sorting appears below the Join in the plan; and
lazy materialization split the read (JoinLazyColumnsStep). Kept as a single
EXPLAIN over one CTE, in the SELECT ... FROM (EXPLAIN ...) form.

Verified on a debug build: the oracle returns 1/1/1 on fixed master, and goes
red (0 in the relevant column) when top-k-through-join or lazy materialization
is disabled. 30/30 runner passes with randomization enabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
auto-merge was automatically disabled July 23, 2026 15:48

Head branch was pushed to by a user without write access

-- in the lazy half of the plan, referencing a column no input block provides. Regression from #104268,
-- fixed on master. Issue #111452.
--
-- All optimizations that must be enabled to reach the fixed code path are pinned so the runner cannot

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/clickhouse-test still randomizes query_plan_join_swap_table between auto and false, so this test is not actually pinning every plan-shaping knob it depends on. The sibling regression test 04230_top_k_through_join_join_swap_gate.sql documents why that matters: once swap is allowed, optimizeJoinLegacy can rewrite the logical LEFT JOIN into RIGHT JOIN. The current oracle only checks for Join, Sorting somewhere below it, and JoinLazyColumnsStep, so it can stay green on that swapped path too.

Please add SET query_plan_join_swap_table = false; here so the regression stays on the LEFT JOIN lazy-materialization path from #111452 instead of whichever join orientation the randomizer leaves behind.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: NOT_FOUND_COLUMN_IN_BLOCK with LEFT JOIN + AND 1

2 participants