Skip to content

Make object_storage_cluster_join_mode=allow keep initiator-local joins - #2609

Open
ianton-ru wants to merge 13 commits into
antalya-26.8from
feature/antalya-26.8/remove-object-storage-cluster-join-mode-local
Open

ianton-ru wants to merge 13 commits into
antalya-26.8from
feature/antalya-26.8/remove-object-storage-cluster-join-mode-local

Conversation

@ianton-ru

Copy link
Copy Markdown

Frontport of #2228 onto antalya-26.8.

object_storage_cluster_join_mode=local is a deprecated alias of allow. The default allow reads the left object-storage table on the cluster and runs JOIN / local IN on the initiator, so the right table does not need to exist on remote nodes. That path works with allow_experimental_analyzer=0. global rewrites the query to GLOBAL JOIN / GLOBAL IN in the old interpreter and sends the right table with the remote query. ORDER BY ALL and GROUP BY ALL are cleared after the join is stripped, so the old analyzer does not re-expand them on the shards.

Related: #2228
Related: #1827

Changelog category (leave one):

  • Improvement

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

object_storage_cluster_join_mode=allow joins initiator-local tables on the initiator, including with the old analyzer. local is a deprecated alias of allow.

Documentation entry for user-facing changes

  • Documentation is written (mandatory for new features)

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Unit 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)
  • CAS (content-addressed storage; Antalya only)
  • 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)

Made with Cursor

…ins.

Frontport of #2228. `local` stays as a deprecated alias of `allow`. The right table is joined on the initiator, including with the old analyzer, and `global` broadcasts that table without requiring the analyzer.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Workflow [PR], commit [07395ad]

@ianton-ru ianton-ru added antalya port-antalya PRs to be ported to all new Antalya releases antalya-26.8 Session label (releasy session config) labels Oct 19, 2026
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T10:51:14.589992Z 82e50da Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 454ce3654d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
auto & tables_element = child->as<ASTTablesInSelectQueryElement &>();
if (tables_element.table_join)
tables_element.table_join->as<ASTTableJoin &>().locality = JoinLocality::Global;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Keep old-analyzer RIGHT/FULL joins on the initiator

With allow_experimental_analyzer=0, object_storage_cluster_join_mode='global', and multiple object-storage workers, this unconditionally converts RIGHT and FULL JOIN to global joins. The complete right side is then broadcast, so each worker can independently emit its preserved unmatched rows, producing duplicate or spurious results. This also contradicts the documented contract and the analyzer path, which deliberately keep these join kinds on the initiator; restrict globalization to the supported INNER, LEFT, and CROSS kinds.

Useful? React with 👍 / 👎.

if (!needsInitiatorLocalJoin(info))
return;

rewriteQueryForInitiatorLocalJoin(query_to_send, query_info, info, context);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the stripped query across remote initiation

When the old analyzer uses allow/local together with object_storage_remote_initiator=1, this rewrites only query_to_send; readFromRemoteInitiator subsequently passes the original full query_info to StorageDistributed::read. That path replaces only the left table expression, leaving the JOIN and its right table in the SQL analyzed on the randomly selected remote initiator. A Memory right table that exists only on the client-facing node therefore causes UNKNOWN_TABLE whenever another host is selected, even though allow promises the right table need not exist remotely.

Useful? React with 👍 / 👎.

Comment on lines +1021 to +1023
TreeRewriterResult rewriter_result = *query_info.syntax_analyzer_result;
if (!removeJoin(*select_query, rewriter_result, context))
throw Exception(ErrorCodes::LOGICAL_ERROR, "Failed to strip JOIN from query sent to cluster nodes");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid pushing filters below preserved-right joins

For an old-analyzer initiator-local RIGHT or FULL JOIN, removeJoin retains left-table-only WHERE predicates in the shard query, even though applying such a predicate before the join can change which right rows are unmatched. For example, with matching nullable keys and WHERE isNull(left.k), filtering the left row during object-storage pushdown makes the initiator null-extend the now-unmatched right row, after which the original predicate passes and returns a spurious row. Clear these predicates for preserved-right joins or apply the same join-side safety check used by the planner.

Useful? React with 👍 / 👎.

Comment on lines +1061 to +1063
select_query->setExpression(ASTSelectQuery::Expression::GROUP_BY, {});
select_query->group_by_all = false;
select_query->setExpression(ASTSelectQuery::Expression::HAVING, {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear GROUP BY modifiers with the stripped clause

In the old-analyzer path for an IN/WHERE subquery without an explicit JOIN, removing GROUP BY leaves group_by_with_totals, group_by_with_rollup, and group_by_with_cube set. A query such as ... WHERE k IN (SELECT ... FROM local_table) GROUP BY k WITH TOTALS is consequently serialized for workers as a SELECT with WITH TOTALS but no GROUP BY, causing parsing or analysis to fail instead of returning raw rows to the initiator. Reset the modifier flags here as removeJoin already does in the sibling branch.

Useful? React with 👍 / 👎.


query_to_send = queryNodeToDistributedSelectQuery(modified_query_tree);
}
auto info = getQueryJoinInfo(query_info, context);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Detect subqueries that reference tables without columns

Under the analyzer, the decision reached here detects foreign sources only through their ColumnNodes, so a table-referencing subquery that projects no table column—such as WHERE value IN (SELECT 1 FROM initiator_only_dim LIMIT 1)—is not classified as initiator-local. The unstripped subquery is then sent to workers, which fail to resolve the initiator-only table, contrary to the newly documented handling of IN/WHERE subqueries. Detection needs to consider referenced table expressions as well as foreign columns.

Useful? React with 👍 / 👎.

@ianton-ru ianton-ru added the forwardport This is a frontport of code that existed in previous Antalya versions label Oct 19, 2026
ianton-ru and others added 6 commits October 19, 2026 17:32
`object_storage_cluster_join_mode = 'global'` was marking every join `GLOBAL`, so each worker emitted the preserved right rows again.

Co-authored-by: Cursor <cursoragent@cursor.com>
…al join.

`StorageDistributed::read` was still analyzing the original `query_info`, so the JOIN and its right table were planned on a host that does not have that table.

Co-authored-by: Cursor <cursoragent@cursor.com>
…FULL` joins.

`removeJoin` kept those predicates on the shard scan, which changes which right rows are unmatched before the initiator applies the original predicate.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ps the clause.

A shard query with `WITH TOTALS`, `ROLLUP`, `CUBE`, or `GROUPING SETS` and no `GROUP BY` is rejected before the initiator can aggregate.

Co-authored-by: Cursor <cursoragent@cursor.com>
…zer.

`WHERE k IN (SELECT 1 FROM dim)` names `dim` without projecting a column, so workers were asked to resolve a table that exists only on the initiator.

Co-authored-by: Cursor <cursoragent@cursor.com>
Fold foreign-table detection into the existing column visitor, use one `RIGHT`/`FULL` check, and drop a left predicate before the existing filter rewrite.

Co-authored-by: Cursor <cursoragent@cursor.com>
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

Failed to set up container
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87c2d9a494

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/Storages/IStorageCluster.cpp Outdated
Comment on lines +1073 to +1074
query_node.getPrewhere() = {};
query_node.getWhere() = {};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve PREWHERE for initiator-local outer joins

When a RIGHT or FULL JOIN has a left-side PREWHERE, clearing it here removes the predicate from the shard query, but the initiator does not add an equivalent filter above ReadFromCluster because PREWHERE is delegated to the storage read. For example, ... PREWHERE left.k = 1 RIGHT JOIN dim ... reads all left rows, changing which right rows match and therefore returning incorrect results. Unlike a post-join WHERE, this predicate is defined to run before the join, so it must be preserved on the left scan or explicitly reapplied before the initiator join.

Useful? React with 👍 / 👎.

Comment on lines +1159 to +1161
select_query->setExpression(ASTSelectQuery::Expression::SELECT, std::move(select_expression_list));
select_query->setExpression(ASTSelectQuery::Expression::PREWHERE, {});
select_query->setExpression(ASTSelectQuery::Expression::WHERE, {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Strip ARRAY JOIN from the raw old-analyzer scan

With the old analyzer and allow/local, a query such as FROM s3Cluster(...) ARRAY JOIN arr AS elem WHERE elem IN (SELECT ... FROM initiator_only_dim) enters this branch, which replaces the projection and filter but leaves the ARRAY JOIN in the tables list. The shards consequently expand each source row before returning it, and the original initiator plan expands it again; an array of length two can produce four rows instead of two. Retain only the source table expression when building this raw scan, as the analyzer path does.

Useful? React with 👍 / 👎.

Comment on lines +1122 to +1123
if (!removeJoin(*select_query, rewriter_result, context))
throw Exception(ErrorCodes::LOGICAL_ERROR, "Failed to strip JOIN from query sent to cluster nodes");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle ordinary joins that follow ARRAY JOIN

With the old analyzer, ASTSelectQuery::hasJoin detects a valid shape such as FROM s3Cluster(...) ARRAY JOIN arr INNER JOIN local_dim ..., but removeJoin only recognizes a join when the second table-list element is a normal join. Here the second element is the ARRAY JOIN, so removeJoin returns false and this newly supported initiator-local path throws LOGICAL_ERROR. The stripping logic needs to find later ordinary joins while reducing the worker query to the raw left scan.

Useful? React with 👍 / 👎.

Comment on lines +1130 to +1131
select_query->group_by_all = false;
select_query->order_by_all = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear DISTINCT when constructing the raw shard query

In the old-analyzer initiator-local path, removeJoin replaces the projection with required source columns but leaves select_query->distinct enabled. The shards can therefore discard duplicate source rows before the initiator performs the original join or aggregation; for example, SELECT DISTINCT sum(v) FROM s3Cluster(...) JOIN local_dim ... computes the sum from deduplicated shard input. Reset DISTINCT together with the aggregation and ordering modifiers when converting the query into a raw scan.

Useful? React with 👍 / 👎.

Comment on lines +394 to +396
auto & tables_element = child->as<ASTTablesInSelectQueryElement &>();
if (tables_element.table_join)
tables_element.table_join->as<ASTTableJoin &>().locality = JoinLocality::Global;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep PASTE JOIN on the initiator

With allow_experimental_analyzer=0, object_storage_cluster_join_mode='global', and multiple workers, this marks PASTE JOIN global even though the documented worker-side set is limited to INNER, LEFT, and CROSS. Each worker then pairs its left partition with the broadcast right table from the beginning; with a one-row right side and two nonempty workers this emits two rows instead of the single row produced by an initiator-side positional join. Treat PASTE like the preserved-side join kinds in both globalization and processing-stage selection.

Useful? React with 👍 / 👎.

ianton-ru and others added 3 commits October 20, 2026 11:16
…LL` join.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@zvonand zvonand mentioned this pull request Oct 20, 2026
56 tasks done
@ianton-ru

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

Reviewed commit: 82e50dab2c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@zvonand

zvonand commented Oct 20, 2026

Copy link
Copy Markdown
Member

@blau-ai

@blau-ai

blau-ai commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

CI triage for 82e50da

Verdict — 7 failing checks: 2 are PR-caused real bugs (3 jobs), 1 is this PR's own new test being flaky under randomized settings, 4 are pre-existing / infra / unrelated.

All the PR-caused failures sit on the old analyzer (allow_experimental_analyzer = 0) path that this PR reworks in InterpreterSelectQuery.cpp + IStorageCluster::rewriteQueryForInitiatorLocalJoin / readFromRemoteInitiator. The new analyzer path is green.


🔴 PR-caused

1. 05391_s3_cluster_right_join_prewhere_stays — SYNTAX_ERROR, 100% reproducible
Fails in both Stateless (amd_debug, parallel) and Stateless (amd_asan_ubsan, distributed plan, selected). The diagnosis harness reproduced it 137/137 and 232/232, and it reproduces without randomized settings too, so it is not a randomization artifact.

Code: 62. DB::Exception: Syntax error: failed at position 211 (RIGHT) (line 4, col 1):
RIGHT JOIN t_dim_05391 AS t2 ON t1.k = t2.k
ORDER BY t2.name, t1.v
SETTINGS allow_experimental_analyzer = 0, object_storage_cluster_join_mode = 'allow';

The query is s3Cluster(...) AS t1 PREWHERE t1.v='L' RIGHT JOIN t_dim AS t2 ... with mode='allow' + old analyzer. The shard-side query that IStorageCluster::rewriteQueryForInitiatorLocalJoin builds (via removeJoin, src/Storages/IStorageCluster.cpp:1137) serializes to text whose left (FROM) table expression is gone but the RIGHT JOIN element survives — hence the dangling RIGHT JOIN at line 4 col 1 when it is re-parsed. The combination that trips it is RIGHT/preserved-side join + a PREWHERE on the stripped left scan on the old-analyzer allow path.
Fix direction: in rewriteQueryForInitiatorLocalJoin, when the join is kept on the initiator (mode != global or RIGHT/FULL/PASTE), make sure the shard query drops the join table element, not just the table_join, and that PREWHERE is re-attached to the surviving left table — right now the serialized result has no left table expression. Worth a unit-level check that removeJoin leaves a parseable single-table FROM for RIGHT/FULL under this path.

2. test_remote_initiator.py::test_remote_initiator_global_join_without_analyzer[s3] — Code 206, Integration (amd_asan_ubsan, targeted)

Code: 206. DB::Exception: No alias for subquery or table function in JOIN
(set joined_subquery_requires_alias=0 to disable restriction).
While processing ' remote('node3:9000', icebergS3(...) AS t1)'.
  at JoinedTables::resolveTables() (JoinedTables.cpp:281)
  <- InterpreterSelectQuery (InterpreterSelectQuery.cpp:715)
  <- StorageDistributed::read
  <- IStorageCluster::readFromRemoteInitiator (IStorageCluster.cpp:642)

The old-analyzer global join via remote initiator path wraps the left side as remote(node3, icebergS3(...) AS t1) but the resulting JOIN table expression carries no alias at the join level, so JoinedTables::resolveTables rejects it. AS t1 lives inside the remote()/icebergS3() args, not on the table expression that the JOIN sees.
Fix direction: when readFromRemoteInitiator / rewriteASTForGlobalJoin constructs the remote(...) wrapper for the old interpreter, propagate the original table alias onto the wrapping table expression (or set joined_subquery_requires_alias = 0 on the reconstructed query's context). Both 05391 and this one are the same class of defect: the AST the old interpreter reconstructs is not a valid standalone query.


🟠 PR's own new test, flaky under randomization (needs author judgment)

3. 05387_s3_cluster_remote_initiator_keeps_stripped_join — Stateless (amd_asan_ubsan, distributed plan, selected)
The harness is explicit here: passes 50/50 without randomization, and the culprit is a single randomized setting:

Result: Passes without randomization. Confirmed: the failure is caused by randomized settings.
Culprit settings: --prefer_localhost_replica 0
Code: 499 ... S3_ERROR ... HTTP response code: 403, Forbidden
  while reading '05387_..._.tsv' in bucket 'test' on disk 'StorageS3'

With prefer_localhost_replica = 0 the s3Cluster(..., object_storage_remote_initiator = 1) read is dispatched over TCP to a "remote" replica and the CI mock-S3 returns 403 (a known mock-S3 flakiness class). This is not a logic regression in the join rewrite — but the test as written is fragile to that randomization.
Suggested fix: pin prefer_localhost_replica in the test's SETTINGS (the test already pins object_storage_remote_initiator*), or add the test to the no-random list. If you want to be thorough, confirm S3 credentials propagate to the non-localhost replica on this path before writing it off as pure mock flakiness.


⚪ Not PR-related (pre-existing / infra)

  • Integration (amd_asan_ubsan, db disk, old analyzer, 3/8) → test_s3_cluster/test.py::test_graceful_shutdown — assert errors == 0 failed with assert 1 == 0, a shutdown-race flaky. This PR's edit to test_s3_cluster/test.py only parametrizes test_joins; it does not touch test_graceful_shutdown. Safe to re-run.
  • Integration (amd_asan_ubsan, db disk, old analyzer, 5/8) → ERROR: session-timeout occurred during test execution — runner/infra timeout (2h42m), no per-test failure recorded. Re-run.
  • Parser memory check → ERROR: No master clickhouse-examples artifact was found — the check could not resolve the base-branch clickhouse-examples binary; infra/artifact issue, independent of this diff.
  • GrypeScanKeeper / GrypeScanServer (-alpine) — container vulnerability scans flagging 4 / 1 high-critical CVEs in the base images. Not introduced by a C++/docs change; these track the base image and fail on the branch generally.

Next steps

  • The two old-analyzer reconstruction bugs (05391, iceberg global-join-without-analyzer) are the only ones that need a code change on this PR; both live in IStorageCluster's old-interpreter query reconstruction.
  • 05387 needs a one-line test-robustness fix (pin prefer_localhost_replica).
  • The remaining 4 (graceful_shutdown, session-timeout, parser-memory, grype) are re-run / infra / base-image and are not blockers attributable to this change.

I did not build or run anything — this is read from the praktika S3 report and the diff; correctness of any fix will be confirmed by CI on the next push.

🤖 Generated with Claude Code

@zvonand

zvonand commented Oct 20, 2026

Copy link
Copy Markdown
Member

@blau-ai fix the PR-caused failing (and flaky) tests

PREWHERE is parsed after the table/JOIN section, so the query
`FROM ... AS t1 PREWHERE ... RIGHT JOIN ...` failed with SYNTAX_ERROR
at the RIGHT keyword (every run of 05391 failed in CI). Reorder to
`FROM ... AS t1 RIGHT JOIN ... ON ... PREWHERE ...`, matching the
PREWHERE-after-JOIN grammar; the intended behaviour and reference
output are unchanged.

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

blau-ai commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

@zvonand I triaged every red check on this PR and split them into PR-caused vs. pre-existing/flaky/infra. One PR-caused failure was a safe test-only fix and I've pushed it; the other two need a build to settle, so I'm reporting rather than guessing.

✅ Fixed and pushed

05391_s3_cluster_right_join_prewhere_stays (Stateless amd_debug, parallel + amd_asan_ubsan, distributed plan)
Pure test-SQL bug — PREWHERE was written before the RIGHT JOIN, but PREWHERE is parsed after the FROM/JOIN section, so every run died with SYNTAX_ERROR at the RIGHT keyword (CI diagnosis: reproducible 137/137 and 232/232, not a randomization issue). Sibling test 05388 already uses the correct RIGHT JOIN … WHERE … order.

Fix: moved PREWHERE after the JOIN. Reference output is unchanged.
→ commit ac820e248b6 on feature/antalya-26.8/remove-object-storage-cluster-join-mode-local. Correctness validated by CI on this PR, not locally.

⚠️ PR-caused, but needs you / a build (I did not guess a C++ change)

test_remote_initiator.py::test_remote_initiator_global_join_without_analyzer[s3] (Integration amd_asan_ubsan, targeted, 1/1)
Combination old analyzer + object_storage_remote_initiator=1 + object_storage_cluster_join_mode='global'. The remote node fails with:

Code: 206. No alias for subquery or table function in JOIN (joined_subquery_requires_alias) — JoinedTables::resolveTables()

The old-analyzer global path (InterpreterSelectQuery.cpp → IStorageCluster::rewriteASTForGlobalJoin → rewriteASTJoinsToGlobal) only flips the join locality to GLOBAL. When the query is shipped through the remote-initiator wrapper and the right side (t2, a Memory table local to the initiator) becomes a GLOBAL JOIN external table on the remote node, its alias isn't preserved, so resolveTables() rejects it. This is a real gap in the new old-analyzer path; the analyzer variant is unaffected. Fixing it (preserve the right-table alias when rewriting to GLOBAL, or ship the query with joined_subquery_requires_alias=0) needs a build to confirm — your call whether to implement it or narrow the test.

05387_s3_cluster_remote_initiator_keeps_stripped_join (Stateless amd_asan_ubsan, distributed plan only)
CI diagnosis: reproducible 31/31 with the randomized settings, passes 50/50 without — the trigger is prefer_localhost_replica=0. With that, the stripped s3Cluster scan is shipped over a real TCP remote hop and the S3 read returns Code: 499 … HTTP response code: 403, Forbidden … No response body against the mock. It passed in the plain amd_debug/db-disk runs (which drew prefer_localhost_replica=1). I can't tell from CI alone whether the 403 is a genuine remote-path credential/serialization bug or a mock artifact, so I didn't touch it. The safe test-level hardening — the test's intent ("the stripped query is what the remote host runs") doesn't depend on localhost preference — would be to pin prefer_localhost_replica = 1 in each query's SETTINGS. Say the word and I'll push that, but only if you agree it isn't masking a product issue.

🟡 Not PR-caused (flaky / infra) — just rerun

  • test_s3_cluster/test.py::test_graceful_shutdown (Integration db disk, old analyzer, 3/8) — the PR only edits test_joins in this file, not this test. It's a kill-during-STOP SWARM concurrency test over a plain no-JOIN s3Cluster query, which never reaches the join-rewrite code this PR changes. assert 1 == 0 = one of 10 concurrent cyclers hit a mismatch/exception during the kill. Timing flake → rerun.
  • Integration amd_asan_ubsan, db disk, old analyzer, 5/8 — ERROR: session-timeout occurred during test execution (suite-level timeout, infra) → rerun.
  • Parser memory check — ERROR: Resolve master binary (couldn't fetch the master binary to diff against), infra → rerun.
  • GrypeScanKeeper / GrypeScanServer (-alpine) — base-image CVE scans (high/critical in the image), unrelated to the code change and pre-existing.

After the push, the amd_debug, parallel suite should go green (05391 was its only failure). The two ⚠️ items are the remaining real work — let me know how you'd like to proceed on each.

🤖 Generated with Claude Code

…iator

With the old analyzer and `object_storage_cluster_join_mode='global'`, an `INNER` join leaves
`query_to_send` pointing at `query_info.query`. `convertToRemote` then wrapped the left table
function in `remote` in place, moving its alias inside, and `StorageDistributed::read` analyzed
that query and threw `ALIAS_REQUIRED`. Pass `convertToRemote` a copy instead.

Fixes `test_remote_initiator.py::test_remote_initiator_global_join_without_analyzer[s3]`.

#2609 (comment)
https://altinity-build-artifacts.s3.amazonaws.com/json.html?PR=2609&sha=ac820e248b649580c6488633d4e3eb2b3f1d9160&name_0=PR&name_1=Integration%20tests%20(amd_asan_ubsan%2C%20targeted)

This branch has not been deployed

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

Labels

antalya antalya-26.8 Session label (releasy session config) forwardport This is a frontport of code that existed in previous Antalya versions port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants