feat(sql): JOINs — INNER / LEFT / RIGHT / FULL OUTER (SQLR-5)#99
Merged
Conversation
Adds the four-flavor JOIN quartet with explicit ON conditions and
table aliases. SQLite ships only INNER and LEFT OUTER; we implement
RIGHT and FULL OUTER as well because the per-flavor delta on top of
a shared nested-loop driver is just NULL-padding policy. See
docs/design-decisions.md §14a for the reasoning.
Engine changes:
- New RowScope trait abstracts column lookup; SingleTableScope
preserves the legacy fast path verbatim, JoinedScope handles
multi-table resolution with NULL padding.
- eval_expr / eval_predicate / eval_function / json_fn_* / FTS
helpers / vector-arg extractor refactored to take &dyn RowScope.
- execute_select_rows_joined: left-folded nested-loop driver,
O(N×M) per join level. ON only sees tables in scope at that
join level (forward refs error). ON reuses eval_predicate_scope
so non-zero ints are truthy, matching WHERE.
- Parser: SelectQuery gains table_alias + joins; ProjectionKind
Column carries an optional qualifier for t.col disambiguation.
USING / NATURAL / CROSS / comma joins, plus aggregates / GROUP
BY / DISTINCT over JOIN, surface as friendly NotImplemented.
17 new tests covering all four flavors, NULL padding both ways,
qualified/unqualified resolution, ambiguity, self-join rejection,
three-table chaining, chained LEFT OUTER, NULL ordering on outer
joins, ON-references-later-table errors, and truthy-int ON.
Docs: README + supported-sql.md + design-decisions.md +
architecture.md + sql-engine.md updated.
526 tests passing, 0 failures. Build, fmt, clippy clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
joaoh82
added a commit
that referenced
this pull request
Jun 2, 2026
PR #99 shipped INNER/LEFT/RIGHT/FULL OUTER JOIN ... ON; the three related shapes SQLite supports were still rejected as NotImplemented. This wires all three through the existing nested-loop join driver. Parser (src/sql/parser/select.rs): - JoinClause.on: Expr → constraint: JoinConstraintKind (On/Using/Natural). - USING (cols) is narrowed to a list of column names; NATURAL is carried as-is (it needs schemas the parser doesn't have); CROSS JOIN is rewritten to ON true at parse time. Executor (src/sql/executor.rs): - resolve_join_constraint lowers USING/NATURAL into the synthesized `left.col = right.col [AND …]` predicate, schema-aware: the left qualifier is picked per column so join chains resolve correctly, and NATURAL auto-discovers the shared column names (none ⇒ cross product, matching SQLite). - SELECT * de-duplicates USING/NATURAL columns per the SQLite convention — the joined-on column shows once, taking the left copy. Tests: 8 new executor tests (USING≡ON rows, SELECT* dedup for USING and NATURAL, NATURAL multi-column AND, NATURAL-without-common-cols cross product, CROSS cartesian product, LEFT OUTER USING, USING unknown-column error), replacing the old "returns NotImplemented" test. Docs updated (supported-sql, sql-engine, roadmap, README, web docs). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
INNER,LEFT OUTER,RIGHT OUTER,FULL OUTER) with explicitONconditions and table aliases.INNERandLEFT OUTER; we ship all four because the per-flavor delta on top of a shared nested-loop driver is just NULL-padding policy. Seedocs/design-decisions.md§14a for the reasoning.RowScopetrait so the existing single-table fast path is preserved verbatim whileJoinedScopehandles multi-table resolution with proper NULL padding.What changed
Engine (
src/sql/executor.rs)RowScopetrait +SingleTableScope(legacy) andJoinedScope(multi-table) implementations.eval_expr/eval_predicate/eval_function/json_fn_*/ FTS helpers / vector-arg extractor refactored to take&dyn RowScope.execute_select_rows_joined: left-folded nested-loop driver, O(N×M) per join level. ON only sees tables in scope at that join level (forward references error). ON reuseseval_predicate_scopeso non-zero ints are truthy, matching WHERE.Parser (
src/sql/parser/select.rs)SelectQuerygainstable_aliasandjoins: Vec<JoinClause>.ProjectionKind::Columncarries an optionalqualifiersot.colreferences can disambiguate across tables.NotImplemented.GROUP BY/DISTINCTover JOIN rejected at parse time.Tests: 17 new tests covering all four flavors, NULL padding both directions, qualified/unqualified column resolution, ambiguity detection, self-join rejection, three-table chaining, chained LEFT OUTER, NULL ordering on outer joins, ON-references-later-table errors, and truthy-int ON. Total 526 tests passing, 0 failures.
Docs:
README.md,docs/supported-sql.md(new "JOIN semantics" section + per-flavor table),docs/design-decisions.md(§14a rationale),docs/architecture.md,docs/sql-engine.mdall updated.Caveats / follow-ups (filed separately)
JoinedScopeerrors on missing/ambiguous columns whileSingleTableScopereturnsValue::Null— long-standing inconsistency, project-wide.JoinedScopeerrors classify asSQLRiteError::Internalwhen they're really user-input issues.2·N·Mheap allocs; reusable scratch buffer is a future optimization.JOIN ... USINGandNATURAL JOINstillNotImplemented.GROUP BY/DISTINCTover JOIN need wiring through the existing aggregator.Test plan
cargo build --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targetscargo test --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs— 526 pass, 0 failcargo fmt --all -- --checkcargo clippy --workspace --exclude sqlrite-desktop --exclude sqlrite-python --exclude sqlrite-nodejs --all-targets— no new warnings🤖 Generated with Claude Code