Skip to content

Specializer SQL support wave 4: join forms — USING, residual ON, semi joins, comma rewrite — corpus 383 → 395 (TASK-50) - #35

Merged
claude-agent-ahrzb[bot] merged 6 commits into
masterfrom
claude/specializer-join-forms
Jul 26, 2026
Merged

Specializer SQL support wave 4: join forms — USING, residual ON, semi joins, comma rewrite — corpus 383 → 395 (TASK-50)#35
claude-agent-ahrzb[bot] merged 6 commits into
masterfrom
claude/specializer-join-forms

Conversation

@claude-agent-ahrzb

Copy link
Copy Markdown

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.a and t2.a both stay addressable (t2.a NULL on a LEFT miss); USING (a, a) dedupes silently; ambiguity after a prior ON join errors like DuckDB.
  • Residual ON predicatesON l.id = r.id AND l.val > 1 on 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.
  • All-key/semi joins — the no-value-columns restriction lifted (one-side-empty map statics, IR text format extended with round-trip fuzz coverage).
  • Star over joined tables — expands with key columns reconstructed from the dynamic side (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).
  • Comma joinsFROM t, u WHERE t.k = u.k rewrites to the INNER probe (measured bit-identical, star order included); WHERE equi conjuncts are consumed as keys, everything else stays WHERE.
  • Cross join to a 1-row static — empty-key maps; a 0-row static annihilates (measured), multi-row rejects via the duplicate-key check.

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

  • Corpus: 395 match / 283 clean-unsupported / 0 FAIL of 678, deterministic. Join-blocked pool: 71 → 13 (10 dynamic self-joins, 2 NATURAL deeper-blocked by COLUMNS(), 1 FULL OUTER — all named).
  • Gate green: cargo 129, pytest 753 + 13 xfail; 15 new join oracle tests.

Stage B (design only, per the ticket's gate)

Output multiplicity for pure non-equi joins: Term::EmitTo continuation + 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

ahrzb and others added 6 commits July 26, 2026 20:15
…-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>
@claude-agent-ahrzb
claude-agent-ahrzb Bot merged commit 1856658 into master Jul 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant