Skip to content

Antalya 25.8 Backport of #87303 - Fix condition not being moved to PREWHERE in case there is a row policy (version 2) - #2171

Open
mkmkme wants to merge 7 commits into
antalya-25.8from
backports/antalya-25.8/87303
Open

Antalya 25.8 Backport of #87303 - Fix condition not being moved to PREWHERE in case there is a row policy (version 2)#2171
mkmkme wants to merge 7 commits into
antalya-25.8from
backports/antalya-25.8/87303

Conversation

@mkmkme

@mkmkme mkmkme commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Same as #1345, but for Antalya 25.8, additional fixes included

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

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

Fixed move-to-prewhere optimization, which did not work in the presence of row policy (ClickHouse#87303 by @KochetovNicolai)

Documentation entry for user-facing changes

...

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

zvonand and others added 3 commits August 5, 2026 12:18
25.8.15 Backport of ClickHouse#87303 - Fix condition not being moved to PREWHERE in case there is a row policy (version 2)
), and fix

two-step PREWHERE in the Parquet v3 reader

PR #1345 backported upstream ClickHouse#87303, which lifts row-level security out of
PrewhereInfo into SelectQueryInfo::row_level_filter. Upstream then had to fix
several places that were left reading row-level security off prewhere_info.
None of those fixes are in #1345, so backport them here, and fix a crash that
ClickHouse#87303 makes reachable on this branch.

Parquet::Reader::applyPrewhere could not run two filtering steps. Every block it
assembles holds rows_pass rows - the count surviving all previous steps - but the
function got that wrong in two ways:

* Columns were materialized lazily per step via formOutputColumn, which for a
  primitive column takes the decoded subchunk. The decoders only saw the filter as
  it stood before any step ran, so that subchunk still holds the pre-filter row
  count, while the per-step filtering only shrinks what is already in
  row_subgroup.output - pending subchunks are never touched. A column first needed
  by the second step therefore arrived one filter generation behind and tripped
  chassert(filter.size() == row_subgroup.filter.rows_pass). Materialize every
  step's inputs before running any step so they are filtered in lockstep.
  formOutputColumn moves out of the subchunk, so this only shifts ownership
  earlier and does not change peak memory.

* addDummyColumnWithRowCount was passed rows_total, and it asserts that every
  column already in the block has exactly that many rows. That held only because
  of the bug above, which left the second step's column unfiltered; with the
  columns correctly at rows_pass it fails instead. Pass rows_pass, which is the
  row count the block actually has at every step.

Planner: ClickHouse#87303 also replaced the pre-existing add_filter gate
(canMoveConditionsToPrewhere && optimize_move_to_prewhere &&
supportedPrewhereColumns->contains(...) && !has_table_virtual_column) with a bare
supportsPrewhere() for the row policy, dropping the supportedPrewhereColumns()
check. StorageFile, IStorageURLBase and StorageObjectStorage all restrict prewhere
to physical columns, while a row policy's filter column is usually an expression
name, so before ClickHouse#87303 those storages routed the policy to WHERE. Upstream release
branches do not notice the loss because input_format_parquet_use_native_reader_v3
defaults to false there, making supportsPrewhere() false for Parquet anyway; this
branch enables that reader by default. Restore the check for the row policy only.
MergeTree returns nullopt and is unaffected, so the move-to-prewhere fix that
motivates the backport is preserved, and its prewhere steps are executed by
MergeTreeRangeReader rather than by the code above.

The two fixes cover different shapes. The guard diverts expression-valued
policies, which is the common case. A policy whose condition is a bare column
(USING flag) is named after a physical column, passes the guard, and still reaches
the reader: with an explicit PREWHERE that shape aborted on this branch even
before ClickHouse#87303, and after ClickHouse#87303 a plain WHERE moved into prewhere aborts too, so
the reader fix is needed as well. The applyPrewhere limitation is present on every
upstream release branch carrying ClickHouse#87303 and was only fixed on master, by the
multistage-prewhere redesign (ClickHouse#93542); the fix here is local to this branch and
worth offering upstream separately.

updateFormatPrewhereInfo, two upstream commits that must go together:

* 8ddee54, "Fix exception in updateFormatPrewhereInfo when only
  row_level_filter is set": the assertion still required prewhere_info, but
  every caller now invokes the function when either filter is set, so a row
  policy without PREWHERE on an object storage / File / URL table tripped it.
  row_level_filter was also never stored into the new ReadFromFormatInfo and
  got lost.

* 774b56b, "Fix updateFormatPrewhereInfo called more than once when row
  policy and prewhere are both active": storing row_level_filter (above) makes
  the duplicate-call guard reject a legitimate second call. When a table has a
  row policy and the optimizer later pushes WHERE into PREWHERE, the function
  runs twice - once from read() for the row_level_filter, once from
  updatePrewhereInfo() for both. Guard only against duplicate prewhere_info,
  and skip re-applying a row_level_filter that a previous call already applied.

* 92b0d17, "Consider row level filter for read in order optimization": the
  row-level filter expression was no longer appended to the sorting DAG, so its
  fixed columns were not recognised and read-in-order was skipped; the limit was
  also no longer reset despite filtering being present.

* 25c22b7, 6b35e27, "Fix row policy filter error when using projections" /
  "Fix for NOT_FOUND_COLUMN_IN_BLOCK when selecting from projections":
  projection_query_info kept row_level_filter while projectionsCommon already
  folds it into the projection prewhere, so the filter was applied twice and
  failed on the projection's block layout.

Tests come from the upstream commits verbatim, except:

* 04490_row_policy_parquet_v3_two_prewhere_steps is new and specific to this
  branch: it covers both shapes above on a File(Parquet) table with the v3 reader,
  and pins the routing guard for expression-valued policies.

* 03800_projection_row_policy_filter_column.reference: its EXPLAIN indexes=1
  output has "Ranges: 1" indented two spaces deeper on this branch, because
  ReadFromMergeTree::describeIndexes still prints it with an extra indent level
  here. Regenerated against this branch; no other byte differs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkmkme mkmkme added antalya backport Backport antalya-25.8 25.8 25.8 Altinity Stable labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

Workflow [PR], commit [86ad66a]

@mkmkme

mkmkme commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator Author

This one was quite bumpy. I've had two files with merge conflict. Resolved them with Claude. After that I revealed that some of the tests actually crashed the server. All of that I covered with the second commit. Third commit is aligning the test output.

The tests now pass locally, but something else might fail in CI. If it does, I'll fix it promptly.

mkmkme and others added 4 commits August 6, 2026 08:03
Both tests were imported from the commits that introduced them (25c22b7, 92b0d17),
but upstream hardened them afterwards, in both cases because of the failures we hit:

* 6251342, "Disable parallel replicas for test" (same day 03927 landed): adds
  SET enable_parallel_replicas = 0. clickhouse-test randomizes
  parallel_replicas_local_plan, and with no local plan there is no local
  ReadFromMergeTree, so the ReadType lines the test greps for disappear.

* c70a81c, "Fix flaky 03800 RLS+projection test under ParallelReplicas", plus
  5c5e975, 2954a15 and 8473072: disables parallel replicas on the
  EXPLAIN queries for the same reason, pins index_granularity because the EXPLAIN
  indexes section asserts an exact granule count, adds a baseline query without the
  row policy so the result demonstrably changes once the policy applies, and adds
  two assertions that do not depend on plan indentation - a count() > 0 check that
  the projection was read, and an extract() of the equals(tenant_id, ...) predicate
  showing the policy is applied as a prewhere filter on the projection.

Both files are upstream/master verbatim except for SET explain_query_plan_default,
which selects between the legacy and pretty EXPLAIN plan formats and does not exist
on this branch - it was added upstream on 2026-05-20, and only the legacy format
exists here.

03800's reference is regenerated against this branch: it differs from upstream only
in "Ranges: 1" being indented two spaces deeper, because
ReadFromMergeTree::describeIndexes still prints it with an extra indent level here.
03927's reference is byte-identical to upstream.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
03800_projection_row_policy_filter_column's two data queries fail with
PROJECTION_NOT_USED under the ParallelReplicas variant. Upstream's
enable_parallel_replicas = 0 pins cover only its three EXPLAIN queries.

projectionsCommon.cpp reports projection support for the initiator only when
parallel_replicas_local_plan is set, so with it 0 optimizeUseNormalProjection skips
projection reading on remote replicas and force_optimize_projection = 1 throws.
That logic is identical upstream, and so is the randomization of the setting in
clickhouse-test - but upstream additionally forces it back to 1 (its clickhouse-test
has that override, ours does not). The test itself is therefore not at fault, so
blacklist it rather than diverging the file from upstream, which the previous commit
had just converged.

Note this leaves the underlying gap in place: any other test relying on projections
under parallel replicas will hit the same randomization.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mkmkme

mkmkme commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator Author

@blau-ai

@blau-ai

blau-ai commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

CI triage for #2171

Verdict: 6 failing checks, but only 3 distinct failures (each reported twice — once as the S3-report check-run Regression x86 … and once as the Actions job RegressionTestsRelease / …). None are caused by this PR. All are pre-existing on antalya-25.8 / test-suite-vs-binary version drift / infra flakiness. Every build, Fast/Stateless/Integration/Unit and compatibility check is green or skipped — nothing PR-related is red.

Strongest evidence up front: the sibling PRs #2168 (crash fix in StorageObjectStorage) and #2172 (unblock TTL part) — neither of which touches the code this PR changes — show the identical trio of failures with the same fingerprints (iceberg_1: 1 errored, iceberg_2: 1 failed, swarms: 1 failed). That means the base branch is red for everyone, independent of this change.


1. iceberg_1 / iceberg_2 — position delete reads · position delete smoke · compaction smoke → not PR-caused (test suite ahead of binary)

Decisive log line (identical in both jobs):

Code: 552. DB::Exception: Unrecognized option '--allow_insert_into_iceberg'. (UNRECOGNIZED_ARGUMENTS)

The clickhouse-regression suite passes a client option/setting allow_insert_into_iceberg that does not exist in this 25.8 Antalya binary — I grepped the whole src/ tree and it is absent. These scenarios all require writing to Iceberg (insert → position delete → compaction), so they abort at setup before exercising any read path.

Although this PR does touch Iceberg/ObjectStorage/Parquet files (PositionDeleteTransform.cpp, Compaction.cpp, Mutations.cpp, StorageObjectStorageSource.cpp, …), the failure is a client argument rejection, not a behavior change in those files — and it reproduces on PRs that touch none of them. Not this PR.

2. iceberg_1 — rest catalog / sort key timezone → not PR-caused (test-framework bug)

[ Error ] /iceberg/iceberg engine/rest catalog/sort key timezone (12ms)
AttributeError: feature_supported

This is a Python AttributeError raised inside the regression framework itself (a 12ms errored step, not a query result). It's a bug in the test-harness version, unrelated to any ClickHouse code.

3. swarms — node failure (restart) · swarm joins → not PR-caused (infra / timing flakiness)

Code: 394. DB::Exception: … Query '…' is killed in pending state. (QUERY_WAS_CANCELLED)

Failing scenarios are check restart swarm node, check restart clickhouse on swarm node, and a RIGHT SEMI JOIN swarm-join case — all inside the swarm node-failure/restart injection suite, where a query is cancelled while a node is being restarted. This is timing-dependent cluster-orchestration flakiness and has nothing to do with move-to-PREWHERE or row policies. This PR changes no swarm/cluster code.


Suggested next steps

  • No PR fix is needed for any of the red checks — they are all pre-existing branch/suite/infra issues, not regressions from this backport.
  • The Iceberg failures will clear once the branch's binary gains the allow_insert_into_iceberg setting (or the pinned regression-suite version is realigned) — that's an infra/branch task, not something to fix in this PR. The sort key timezone AttributeError needs a fix in the clickhouse-regression repo. The swarms failures are safe to re-run.
  • The change itself is healthy: it compiles (Build (amd_release) ✅, Compatibility check (release) ✅) and ships its own regression coverage (03591_optimize_prewhere_row_policy, 03927_row_filter_in_read_in_order_optimization, 04490_row_policy_parquet_v3_two_prewhere_steps, 04098_row_policy_disjunction_optimization, plus reference updates). Note the Stateless/Integration jobs show skipping in this batch — if you want the move-to-PREWHERE stateless tests exercised on the merge commit before merging, a re-run of the functional jobs would give that final signal, but nothing indicates a problem.

(Analysis from CI logs only — I can't build or run ClickHouse in this environment; correctness is validated by CI.)

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

Labels

25.8 25.8 Altinity Stable antalya antalya-25.8 backport Backport

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants