Specializer SQL support wave 4: join forms — USING, residual ON, semi joins, comma rewrite — corpus 383 → 395 (TASK-50) - #35
Merged
Conversation
…created In Progress
…-ON laziness, comma=probe equivalence (TASK-50) Three-agent fleet, measured before implementation. Headlines: USING's merged column stays at the LEFT position (not hoisted) and equals the left value under INNER/LEFT; LEFT keeps residual-failing key-matches with a NULL side; DuckDB pushes single-side residuals into scans (eager traps) while both-sides residuals are pair-lazy — v0 requires single-side residuals trap-free; comma-join == INNER probe bit-exactly incl. star order; duplicate star names survive to Arrow (our typed model cannot hold them — documented constraint). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…JoinHit with hit-guarded lazy lowering (TASK-50) The residual evaluates in a branch taken only on a raw key hit (the measured per-candidate-pair laziness), 3VL-collapsed, and the block probe cache is overwritten with the final match so every downstream consumer (INNER skip, LEFT flags, StaticCol, JoinHit) picks it up unchanged. Frontend wiring (bind_on split, USING, comma rewrite, all-key lift, star reconstruction) lands next; all 129 Rust tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er-join key reconstruction (TASK-50) bind_on splits equalities-with-a-static-side into probe keys and returns everything else raw; residuals bind with the join in scope and enforce the measured evaluation-order rule (single-side must be trap-free — DuckDB scan-pushes those; both-sides evaluate per candidate pair, our hit-guarded lowering exactly). USING pairs the LEFT scope's binding with this table's column (dedupes silently, ambiguity after an ON join errors like DuckDB) and suppresses the static-side key from stars and bare-name binds (merged column = left value, measured). Key columns reconstruct everywhere via key_lane: INNER = the key expr, LEFT = CASE JoinHit THEN key ELSE NULL. Map statics may now be one-side-empty (semi joins / cross), incl. text-format round-trip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…reject — corpus 383 -> 395 (TASK-50) Comma relations rewrite to INNER probes (measured bit-identical incl. star order): WHERE equi conjuncts pairing the current scope with a comma table's column are consumed as its keys, everything else stays WHERE. Keyless comma tables are cross joins via the empty-key map — the duplicate-key check enforces single-entry-ness at compile, and a 0-row static annihilates, both measured. The residual guard now threads the probe's value lanes through the block params / live stack (strict block-param SSA — caught by the corpus, fixed, verifier green). Static values outside BIGINT range (UBIGINT/HUGEINT payloads) reject cleanly. Join-blocked corpus pool: 71 -> 13 (10 dynamic self-joins, 2 NATURAL deeper-blocked by COLUMNS(), 1 FULL OUTER). 15 new join oracle tests. Corpus 395/678, zero FAILs, deterministic; gate green (753 + 13 xfail). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ahrzb
approved these changes
Jul 26, 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.
Wave 4: join forms — USING, residual ON, semi joins, comma rewrite — corpus 383 → 395 (TASK-50)
Pins-first: a three-agent fleet measured DuckDB 1.5.5's join semantics before implementation (
2026-07-26-wave4-join-pins.md+pins-wave4/*.json).What ships (stage A — no new execution model)
JOIN ... USING— measured merged-column semantics mirrored exactly: the merged column sits at the left table's position with the left value (not PostgreSQL's front-hoisting);t1.aandt2.aboth stay addressable (t2.aNULL on a LEFT miss);USING (a, a)dedupes silently; ambiguity after a prior ON join errors like DuckDB.ON l.id = r.id AND l.val > 1on INNER and LEFT:match = key_hit AND residual, evaluated hit-guarded per candidate pair (DuckDB's measured laziness for both-sides residuals, bit-exact). LEFT keeps residual-failing key-matches with an all-NULL right side — the classic ON-vs-WHERE trap, pinned and reproduced. Single-side residuals are restricted to trap-free shapes: DuckDB scan-pushes those, so a trapping one has different error timing — rejected by name rather than silently diverging.r.id≡ CASE match THEN probe-key ELSE NULL). Duplicate-output-name stars stay cleanly unsupported (DuckDB emits duplicate Arrow columns verbatim; a typed output model cannot).FROM t, u WHERE t.k = u.krewrites to the INNER probe (measured bit-identical, star order included); WHERE equi conjuncts are consumed as keys, everything else stays WHERE.Found by the gate
The corpus replay caught a strict-SSA violation in the first residual lowering (probe value lanes crossing blocks without riding params) — fixed by threading them through block params and the live stack. UBIGINT/HUGEINT static payloads now reject cleanly instead of raising a raw OverflowError.
Results
COLUMNS(), 1 FULL OUTER — all named).Stage B (design only, per the ticket's gate)
Output multiplicity for pure non-equi joins:
Term::EmitTocontinuation + per-entry static scan loop; batch-level API only (breaks 1:1 row mapping). Written up in TASK-50 — awaiting a go before any build.🤖 Generated with Claude Code