Skip to content

feat(cubesql): Support FULL and RIGHT joins with non-push-to-Cube SQL push down#11008

Merged
MazterQyou merged 2 commits into
masterfrom
cubesql/grouped-join-grouped-right-full
Jun 10, 2026
Merged

feat(cubesql): Support FULL and RIGHT joins with non-push-to-Cube SQL push down#11008
MazterQyou merged 2 commits into
masterfrom
cubesql/grouped-join-grouped-right-full

Conversation

@MazterQyou

@MazterQyou MazterQyou commented Jun 3, 2026

Copy link
Copy Markdown
Member

Check List

  • Tests have been run in packages where changes have been made if available
  • Linter has been run for changed code
  • Tests for the changes have been added if not covered yet
  • Docs have been added / updated if required

Description of Changes Made

This PR fixes a regression introduced by grouped joins with SQL push down (#10234): a FULL OUTER JOIN of two grouped subqueries (e.g. period-over-period cost comparison) now fails with Unsupported join type for join subquery: Full. The rewrite already produced the correct plan; only SQL generation and the join-type templates were limited to Inner/Left.

This adds FULL and RIGHT support for the non-push-to-Cube path (both sides as standalone subqueries, where outer-join semantics map cleanly to SQL). The push-to-Cube path — where the join is folded inside the Cube query with
its own grouping/measures — explicitly keeps refusing FULL/RIGHT, since NULL-extended outer rows there are not validated.

Push down is template-gated: FULL/RIGHT only push down when the target data source declares the join_types/* template, otherwise the join falls back to DataFusion.

The PR also makes Tesseract use separate templates for joins.

@MazterQyou MazterQyou requested review from a team as code owners June 3, 2026 14:56
@github-actions github-actions Bot added rust Pull requests that update Rust code javascript Pull requests that update Javascript code labels Jun 3, 2026
@claude

claude Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 2m 16s —— View job


Code Review

Verdict: Looks good overall — clean, well-scoped fix. 0 high, 0 medium, 3 low (all suggestions / nits posted inline).

Full review (expand)

Summary

The fix is well-scoped: the rewrite already produced a correct plan for FULL/RIGHT joins of two grouped subqueries; the PR just wires SQL generation and join-type templates through. Separation between the push-to-Cube path (refuses FULL/RIGHT) and the non-push-to-Cube path (now supports them) is clearly commented and consistently enforced.

What's good

  • Template-gating via is_subquery_join_type_supported (join.rs:996) is the right abstraction — it composes the join-type rule with can_rewrite_template, so non-supporting drivers fall back to DataFusion rather than emitting unsupported SQL.
  • push_to_cube parameter correctly differentiates the two paths: hardcoded true for transform_ungrouped_join_grouped (always refuses FULL/RIGHT), left_push_to_cube.0 for transform_grouped_join_grouped (refuses FULL/RIGHT only when folding into Cube query).
  • continue vs return false in the grouped-grouped variant (join.rs:1320) is the right choice — lets the non-push variant of the eclass still match.
  • Driver-specific opt-outs (CubeStoreQuery.ts, MysqlQuery.ts) are well-commented with the underlying reason.
  • Comments at wrapper.rs:3462 and :3600 preserve the invariant for future maintainers.

Findings (all low)

  1. BaseQuery.js — making right/full the default exposes all non-overriding drivers; verify each one. Snowflake's explicit templates.join_types.full = 'FULL' (SnowflakeQuery.ts:122) is now redundant. Inline comment posted.
  2. Test coverage — only FULL is exercised; RIGHT is not, and there's no negative test for the template-gating fallback or for the push-to-Cube path still rejecting FULL/RIGHT. Inline comment posted.
  3. Minor redundancy in transform_ungrouped_join_grouped — the data-source lookup runs even though push_to_cube=true makes Right/Full unreachable. Functionally correct but slightly misleading. Inline comment posted.

Security / performance

  • No new user-input surfaces; the templates are static strings. No injection vector introduced.
  • No allocation/hot-path concerns: the rewrite-rule helper does one extra MetaContext clone per rule construction (cheap, one-time at setup) and one template lookup per match attempt (already done for other gates).

Checklist

  • Reviewed Rust changes (join.rs, wrapper.rs, service.rs, tests)
  • Reviewed JS/TS adapter changes (BaseQuery.js, CubeStoreQuery.ts, MysqlQuery.ts)
  • Posted 3 inline findings
  • Verified push-to-Cube path still rejects FULL/RIGHT (both code paths in generate_sql_for_push_to_cube)
• [Branch](https://github.com/cube-js/cube/tree/cubesql/grouped-join-grouped-right-full)

Comment thread rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/join.rs
Comment thread rust/cubesql/cubesql/src/compile/test/test_wrapper.rs
Comment thread packages/cubejs-schema-compiler/src/adapter/BaseQuery.js
@codecov

codecov Bot commented Jun 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.41624% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.10%. Comparing base (c70e7ec) to head (74331ab).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
.../cubesql/src/compile/rewrite/rules/wrapper/join.rs 90.90% 4 Missing ⚠️
...t/cubesql/cubesql/src/compile/test/test_wrapper.rs 97.79% 3 Missing ⚠️
...bejs-schema-compiler/src/adapter/CubeStoreQuery.ts 0.00% 2 Missing ⚠️
...s/cubejs-schema-compiler/src/adapter/MysqlQuery.ts 0.00% 1 Missing ⚠️
...bejs-schema-compiler/src/adapter/SnowflakeQuery.ts 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #11008      +/-   ##
==========================================
+ Coverage   79.06%   79.10%   +0.04%     
==========================================
  Files         472      472              
  Lines       95983    96172     +189     
  Branches     3524     3524              
==========================================
+ Hits        75888    76077     +189     
  Misses      19580    19580              
  Partials      515      515              
Flag Coverage Δ
cube-backend 58.50% <0.00%> (-0.02%) ⬇️
cubesql 83.61% <96.37%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MazterQyou MazterQyou force-pushed the cubesql/grouped-join-grouped-right-full branch from 5fecb83 to 37109d0 Compare June 3, 2026 15:29
@claude

claude Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 1m 6s —— View job


Code Review

Re-checked after PR synchronize: no new commits since the prior review (still 37109d0). All 3 prior inline findings remain unresolved and still applicable to the current diff — re-affirming them rather than re-posting.

Verdict: Looks good overall — clean, well-scoped fix. 0 high, 0 medium, 3 low (suggestions only; all already inline on the PR).

Prior findings (still apply)
  1. rust/cubesql/cubesql/src/compile/rewrite/rules/wrapper/join.rs:1068push_to_cube=true in transform_ungrouped_join_grouped makes the template lookup unreachable for Right/Full; consider a clarifying comment or inline match guard.
  2. rust/cubesql/cubesql/src/compile/test/test_wrapper.rs:1249 — coverage gap: RIGHT JOIN isn't asserted, no negative test for template-gating fallback, no test that push-to-Cube path still rejects FULL/RIGHT.
  3. packages/cubejs-schema-compiler/src/adapter/BaseQuery.js:4608 — making FULL/RIGHT base-default exposes every non-overriding driver; double-check exotic adapters (Druid, Pinot, Elasticsearch SQL, Materialize, MongoDB). Snowflake's templates.join_types.full = 'FULL' is now redundant.

No new high/medium issues introduced. Security and performance unchanged from prior pass.

• [Branch](https://github.com/cube-js/cube/tree/cubesql/grouped-join-grouped-right-full)

… push down

Signed-off-by: Alex Qyoun-ae <4062971+MazterQyou@users.noreply.github.com>
Signed-off-by: Alex Qyoun-ae <4062971+MazterQyou@users.noreply.github.com>
@MazterQyou MazterQyou force-pushed the cubesql/grouped-join-grouped-right-full branch from 37109d0 to 74331ab Compare June 10, 2026 12:12
@claude

claude Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @MazterQyou's task in 1m 26s —— View job


Code Review

Re-reviewed after 74331ab fix(tesseract): Decouple join types from SQL push down.

Verdict: Looks good. The new commit cleanly addresses my prior Snowflake redundancy concern by splitting the namespaces. 0 high, 0 medium, 0 new low. All 3 prior threads are already resolved.

Details

What changed in 74331ab

Tesseract now uses a distinct template namespace (tesseract.join_types_inner/left/full) instead of sharing join_types/* with the SQL API push down path. Concretely:

  • BaseQuery.js:4634-4637 — adds tesseract.join_types_inner = 'INNER' and tesseract.join_types_left = 'LEFT' as base defaults; FULL is intentionally not a base default (opt-in per dialect).
  • SnowflakeQuery.ts:122 — what was templates.join_types.full = 'FULL' is now templates.tesseract.join_types_full = 'FULL', restoring Snowflake's Tesseract FULL support under the new namespace.
  • plan.rs:447-452, 500-502PlanSqlTemplates::render_join and supports_full_join now read from tesseract/join_types_* instead of join_types/*.
  • mock_sql_templates_render.rs:464-470 — test fixture mirrors the new namespace.

Why this is the right call

The two paths had different semantics piled into one set of templates:

  • SQL API push down (join_types/*) — driven by the BaseQuery rewrite rules added in this PR, gated by is_subquery_join_type_supported. Base defaults all 4 (inner/left/right/full); MySQL/CubeStore opt out.
  • Tesseract (tesseract.join_types_*) — used by the native planner via PlanSqlTemplates::render_join. Base defaults inner/left only; FULL is opt-in (currently Snowflake only).

Before this commit, Snowflake's join_types.full = 'FULL' was simultaneously (a) the SQL API push-down opt-in and (b) the Tesseract FULL gate, which is what made my earlier "now redundant" call slightly wrong — it wasn't actually redundant for the Tesseract path. The split makes each path's opt-in surface explicit.

Nits (non-blocking)

  • The fixture comment at mock_sql_templates_render.rs:461 still says "based on BaseQuery.js:4424-4427" but the referenced lines have shifted to around 4665 in current BaseQuery.js. Pre-existing, not introduced here.
  • supports_full_join is now the single source of truth for Tesseract FULL support; worth a quick mental note that a dialect could in principle opt into FULL on one path but not the other (e.g. set tesseract.join_types_full without join_types.full). Not a real concern today, just an architectural consequence of the split.

Prior findings — re-checked

# Path:line Status
1 join.rs:1068 (template lookup unreachable on push-to-Cube path) thread resolved — non-blocking nit
2 test_wrapper.rs:1249 (RIGHT/negative/push-to-Cube test gaps) thread resolved
3 BaseQuery.js:4669 (base-default FULL/RIGHT exposure) thread resolved; partially superseded by namespace split for the Tesseract path
• [Branch](https://github.com/cube-js/cube/tree/cubesql/grouped-join-grouped-right-full)

@MazterQyou MazterQyou merged commit 8ef4b54 into master Jun 10, 2026
180 of 184 checks passed
@MazterQyou MazterQyou deleted the cubesql/grouped-join-grouped-right-full branch June 10, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants