Skip to content

fix(r13): NULL join keys never pair, hash and nested-loop paths (P52, R13 slice 6c) - #103

Merged
TimelordUK merged 2 commits into
mainfrom
refactor/r13-slice6c-join-null-keys
Oct 3, 2026
Merged

TimelordUK merged 2 commits into
mainfrom
refactor/r13-slice6c-join-null-keys

Conversation

@TimelordUK

Copy link
Copy Markdown
Owner

Summary

R13 slice 6, third part — closes P52.

  • Nested loop (INNER / LEFT / RIGHT multi, expression form): compare_values now asks the predicate-layer compare_trilean (made pub(crate)) and keeps a pair only when TRUE. JOIN ON uses the same NULL rule as WHERE and SELECT.
  • Hash path: a NULL key is never indexed. A NULL probe then finds nothing; INNER drops the row, LEFT's existing no-match branch emits it unmatched. No probe-side change needed.

Found along the way: P63 (filed, pinned, not fixed here)

The hash RIGHT case now returns DuckDB's 12 rows but still differs: every single-condition RIGHT JOIN swaps a.* and b.* (pre-existing on main — P8's fault on the hash path, which no corpus case had ever reached). Outside R13 (result assembly), so it gets its own PR. New case 04_joins.toml :: right_join_single_condition (DIFFER).

Checks

  • Parity: 191 → 197 AGREE / 230 — six P52 cases DIFFER → AGREE, three inequality guards held; contract holds.
  • Rust lib (842) + integration (508), Python (572) green; all FORMAL examples pass (only expander_rewriters smoke fails, as on main).
  • 100k-row hash equi-join: level with main (~3.3 s, mostly load).

Next

P63 (own PR) → 6d: WhereClause connector list → BinaryOp AND, retire RecursiveWhereEvaluator, close R13.

🤖 Generated with Claude Code

TimelordUK and others added 2 commits October 3, 2026 12:39
…s (P52, R13 slice 6)

Nested loop: `compare_values` asks the predicate-layer `compare_trilean`
(now pub(crate)) and keeps a pair only when it is TRUE, so a NULL on either
side is UNKNOWN - the same rule WHERE and SELECT use. Hash path: a NULL key
is never indexed, so a NULL probe finds nothing and LEFT still emits its row
unmatched; the probe side needs no change.

Six of the seven P52 cases DIFFER -> AGREE; the three inequality guards held.
The seventh, the single-condition RIGHT join, now returns DuckDB's 12 rows
but still differs: it uncovered P63 (that path labels the swapped tables
backwards - pre-existing, outside R13), pinned as
04_joins.toml :: right_join_single_condition.

191 -> 197 AGREE / 230. FORMAL examples, Python (572) and Rust suites green;
100k-row hash join level with main.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
P52 closed on every join path, 191 -> 197 AGREE / 230. P63 filed: the
single-condition RIGHT JOIN (hash path) swaps a.* and b.* on every row -
P8's fault on the path P8's scope note called safe; corrected there. P63
goes to the top of the fix-order table beside R13, as its own PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@TimelordUK
TimelordUK merged commit 6991a56 into main Oct 3, 2026
8 checks 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