Files
rdbms-playground/docs/handoff/20260520-handoff-26.md
T
claude@clouddev1 3db292c795 docs: handoff 26 — ADR-0032 + Phase 2 plan accepted
Twenty-sixth handover. Design session, no code touched. Tests
unchanged at 1260 / 0 / 1.

Captures: the Phase 2 grammar decisions (ADR-0032 accepted),
the implementation plan at docs/plans/20260520-adr-0032-phase-2.md
with seven sub-phases and a cross-cut verification matrix that
explicitly names every "X comes for free" claim from ADR-0030/
0031/0032, and the Phase-1 carry-over finding the warning/error
guideline check surfaced — SQL WHERE expressions currently emit
no LIKE-on-numeric / = NULL / type-mismatch warnings because
sql_expr builds no AST. ADR-0032 §11.6 closes the gap; the
plan's cross-cut matrix has a named row to prevent regression.

The next session is Phase 2 sub-phase 2a (grammar fragment) per
the plan; standing authorizations apply.

Status: ready to hand over.
2026-05-20 10:29:04 +00:00

14 KiB
Raw Blame History

Session handoff — 2026-05-20 (26)

Twenty-sixth handover. Design session. ADR-0032 (the full SQL SELECT grammar — ADR-0030 Phase 2) is Accepted and an implementation plan sits next to it under a new docs/plans/ directory. No code changed this session; the next session is implementation territory.

§1. State at handoff

Branch: main. Working tree clean. One unpushed commit (be8a4f5..a7db7dd).

Tests: 1260 passing, 0 failing, 1 ignored — unchanged from handoff-25 (no code touched).

Clippy: clean (last verified at handoff-25; no code changed since).

New commit since handoff-25:

a7db7dd docs: ADR-0032 + Phase 2 plan — full SQL SELECT grammar

A single docs commit; ADR + plan + index update ship as a coherent unit because the plan is Phase-2's mechanics for ADR-0032's decisions.

§2. What got designed — the shape now

Read in order:

  1. docs/adr/0032-sql-select-grammar.md — the grammar decisions for Phase 2.
  2. docs/plans/20260520-adr-0032-phase-2.md — the implementation plan walking the work in seven sub-phases.

2.1. ADR-0032 in one screen

Phase 2's grammar surface (§§19 of the ADR):

  • Five JOIN flavours: INNER, LEFT [OUTER], RIGHT [OUTER], FULL [OUTER], CROSS. NATURAL/USING/comma-FROM are OOS.
  • All four set ops: UNION, UNION ALL, INTERSECT, EXCEPT.
  • CTEs: WITH and WITH RECURSIVE, with optional (col-list).
  • Subquery expressions as additive sql_expr branches: scalar (SELECT …) primary, IN (SELECT …), [NOT] EXISTS (…). Redeems ADR-0031 §7 OOS-1.
  • Qualified column refs t.c / alias.c as a name_or_call tail. Redeems ADR-0031 §7 OOS-2.
  • LIMIT n [OFFSET m]; legacy comma form OOS.
  • DISTINCT / ALL, t.* projection, bare-alias projection (lifts Phase-1 §4.2's autonomous decision).
  • One new grammar file: src/dsl/grammar/sql_select.rs, parallel to sql_expr.rs, exporting SQL_SELECT_STATEMENT and SQL_SELECT_COMPOUND.

Walker-capability honesty (§10) — the part where ADR-0030 §8's "ambient assistance comes for free" got softened:

  • Grammar recursion still needs nothing new — Subgrammar
    • ADR-0026's depth cap unchanged.
  • Completion scope needs a new node variant Node::ScopedSubgrammar(&Node) alongside the existing Node::Subgrammar, plus a from_scope_stack: Vec<ScopeFrame> on WalkContext. Each ScopeFrame holds from_scope, cte_bindings, projection_aliases. Existing DSL Expr and sql_expr recursion stays on the non-scope-pushing variant and is unaffected.
  • CTE column resolution (§10.3): SELECT * and explicit- projection CTE bodies both yield real column completion past cte_alias.| via a body-projection derivation rule that runs at the body's ScopedSubgrammar exit. The earlier "honest limitation" posture is replaced.
  • Qualified-prefix completion (§10.5): at t.|, candidates are scoped to t's binding.
  • Projection-before-FROM problem (§10.6): the debounced re-walk catches up via a post-walk fixup pass that re- resolves projection-list identifiers against the final from_scope, rewriting highlight class and validity diagnostic in the walker's accumulated output. Runs as a final stage of the walk itself — same convention applies to the §11.6 predicate-warnings pass.

Diagnostics (§11) — every Phase-2 validation case classified against ADR-0027's ERROR/WARNING guideline:

  • Five new diagnostic.* keys (parse-time-detectable): unknown_qualifier, ambiguous_column, projection_alias_misplaced, cte_arity_mismatch, compound_arity_mismatch.
  • Eight new engine.* keys for friendly-error layer translations of engine messages.
  • Three diagnostic passes by end of Phase 2 (§11.7): schema-existence (extended for multi-binding scope), arity- check (new), predicate-warnings (extended with a MatchedPath-walking variant — §11.6).

Result-column type resolution (§12):

  • rusqlite 0.39.0 exposes column_table_name / column_origin_name / column_database_name on RawStatement behind a column_metadata feature. Verified during this session by reading the rusqlite source. Cargo.toml change is one line — add "column_metadata" to the feature list.
  • Bare column refs recover their playground type via that metadata; partially lifts Phase-1 §4.5's bool→0/1 deferral.

2.2. The implementation plan

docs/plans/20260520-adr-0032-phase-2.md (881 lines) breaks Phase 2 into seven sub-phases:

  • 2a — Grammar fragment (sql_select.rs, no walker changes yet).
  • 2bNode::ScopedSubgrammar variant + the scope accumulators on WalkContext.
  • 2c — Phase-1 grammar migration: move Phase-1 SELECT nodes from data.rs into sql_select.rs; the 7 Phase-1 integration tests are the safety net.
  • 2d — Diagnostics passes + catalog keys.
  • 2e — Completion + post-walk fixup pass.
  • 2f — Type resolution via rusqlite column_metadata.
  • 2g — Integration sweep + the cross-cut verification matrix (the Phase-1-gap-prevention mechanism).

Each sub-phase has explicit exit gates with required tests and a DA-prompt checklist for the gate review. The plan also encodes the user's standing authorizations:

  • Walk uninterrupted between gates unless there's a real ambiguity, autonomous decision, blocker, or in-the-plan-open- question trigger.
  • Commits pre-authorized at gate boundaries and natural break points within a sub-phase. Standard area: subject message style, detailed body, no AI attribution.
  • Pushes remain user-only.
  • End-of-plan hand-back is the one explicit pause: the verification report + DA PASS verdict get surfaced to the user before Phase 2 is declared complete.

The cross-cut verification matrix (29 rows) names every "X comes for free" claim from ADR-0030/0031/0032 with a test- location placeholder. The Phase-1 SQL-expression predicate- warning gap is row 20 by name — making sure an analogous silent gap cannot ship undetected.

§3. The Phase-1 finding worth carrying forward

While walking ADR-0027 Amendment 1's existing-cases inventory during this session, the warning-vs-error guideline check surfaced a Phase-1 carry-over gap:

ADR-0027 Amendment 1's LIKE-on-numeric warning — together with ADR-0026 §7's = NULL and type-mismatch warnings — is emitted by a pass that walks the DSL Expr AST. Phase 1's sql_expr.rs deliberately builds no AST (ADR-0031 §2). The consequence: SQL WHERE expressions today emit none of these warnings. select * from t where name like 5 parses, the engine runs it, and the learner gets the engine's verdict, not the friendly pre-flight nudge Amendment 1 promised.

Phase 1 shipped structurally green (1260 / 0 / 1) but the validity indicator was effectively dormant on SQL expressions — the test suite never asked "does this warning fire on a SQL WHERE input?" because there was no test for the cross-cut "the free-for-free promise still holds for SQL" claim.

ADR-0032 §11.6 closes the gap (Phase 2's diagnostics work implements a MatchedPath-walking predicate-warnings variant covering every sql_expr slot — WHERE, HAVING, ON, CASE, projection, ORDER BY) and the implementation plan's cross-cut matrix has it as a named row to prevent a regression. The plan is structured so this kind of gap cannot ship undetected again.

Worth carrying forward to future phase planning: every "comes for free" claim in any ADR is also a candidate for an unnoticed silent gap. The cross-cut matrix pattern in the plan generalises.

§4. Autonomous decisions during this session

Per CLAUDE.md, decisions outside ADR-0030's settled scope are flagged here. None are urgent; all were either confirmed by the user via explicit AskUserQuestion gates or are routine detail.

  • Catalog key naming. diagnostic.* for parse-time diagnostics and engine.* for friendly-error translations — chosen by following the existing pattern (diagnostic.unknown_table, select.internal_table); no new naming convention introduced.
  • Subgrammar push-trigger mechanism (§10.2) — Node::Scoped­ Subgrammar(&Node) chosen via explicit user AskUserQuestion gate over two alternatives (flag on existing variant; driver-side identity registry). Recommended option; user picked Recommended.
  • CTE body column derivation (§10.3) — six derivation rules covering *, t.*, bare refs, qualified refs, aliased expressions, and computed-no-alias; first leg / non-recursive leg dictates for compound / recursive bodies. Per standard SQL throughout.
  • Node::ScopedSubgrammar data modelScopeFrame is a struct holding from_scope / cte_bindings / projection_aliases together rather than three parallel stacks. Cleaner data model; no semantic difference. Settled during writing.

Every scope question that affected expressiveness or surface area went through an AskUserQuestion gate. The full list of those gates appears in the conversation log; the answers shape the ADR's §§110 directly.

§5. What's next — Phase 2 implementation

Per ADR-0030's phasing, Phase 2 is the next slice. The user has signed off on the plan and authorized walking it without interruption between gates. The implementer for Phase 2 should:

  1. Read in order: this handoff, ADR-0032, the Phase 2 plan, then CLAUDE.md (working-style rules unchanged) and docs/requirements.md (Q1/Q2 advance further; Q4 already ticked).
  2. Begin sub-phase 2a — the grammar fragment. The plan's "Scope (in)" / "Build steps" / "Exit gate" / "DA gate" sections for 2a are the spec.
  3. Commit at gate boundaries with detailed messages in the area: subject style (recent precedent: grammar: SQL SELECT end-to-end (ADR-0030 Phase 1), etc.).
  4. Escalate when the plan says to — see §2 of the plan's "Standing authorizations" for the four trigger conditions. The plan's "Open questions to escalate before code starts" section pre-identifies three known triggers (subquery type- resolution through scalar subqueries; duplicate CTE-name detection; function name allowlist).
  5. At end of 2g, produce the verification report and surface to the user; do NOT declare Phase 2 complete without the user's confirmation. This is the one explicit pause-point per §5 of the plan's standing authorizations.

After Phase 2 ships and is accepted, ADR-0030 Phase 3 (DML — INSERT / UPDATE / DELETE in SQL) is the next focus area.

§6. Seams for the implementer

These are the file-and-section pointers for Phase 2 work, copied across from the plan for fast access:

  • src/dsl/grammar/sql_select.rs (new) — the §1 grammar. Parallel to sql_expr.rs (ADR-0031). Exports SQL_SELECT_STATEMENT (the full statement with optional WITH) and SQL_SELECT_COMPOUND (the embedded form subqueries recurse into).
  • src/dsl/grammar/sql_expr.rs — three additive Choice branches (scalar subquery primary, IN (subquery), [NOT] EXISTS (subquery)) and one additive name_or_call tail (qualified ref t.c). All recurse through Node::ScopedSubgrammar(&SQL_SELECT_COMPOUND).
  • src/dsl/grammar/mod.rsNode enum gets the new ScopedSubgrammar(&'static Node) variant alongside the existing Subgrammar.
  • src/dsl/walker/context.rsWalkContext gains from_scope_stack: Vec<ScopeFrame>. current_table / current_table_columns become derived helpers over the top frame (DSL paths must stay green — the existing 1240+ test surface verifies this).
  • src/dsl/walker/driver.rs (or wherever the Subgrammar match arm lives) — new arm for Node::ScopedSubgrammar that pushes/pops a ScopeFrame.
  • src/dsl/walker/mod.rsschema_existence_diagnostics extended to walk every from_scope binding; new arity-check pass; existing predicate_warnings gets a MatchedPath-walking variant for sql_expr slots.
  • src/db.rsdo_run_select calls a new resolve_select_column_types helper before constructing the DataResult. The helper uses RawStatement::column_table_name(i) / column_origin_name(i) per result column.
  • Cargo.toml — add "column_metadata" to rusqlite's feature list. One-line change.
  • src/dsl/grammar/data.rs — Phase-1 SQL SELECT static nodes either move into sql_select.rs or get removed outright (the data::SELECT CommandNode is rebuilt against SQL_SELECT_STATEMENT); see plan sub-phase 2c.
  • Catalog file (wherever the i18n keys live; the existing diagnostic.unknown_table neighbours) — five new diagnostic.* keys + eight new engine.* keys per ADR-0032 §11.5.

§7. How to take over

  1. Read this file, then docs/adr/0032-sql-select-grammar.md and docs/plans/20260520-adr-0032-phase-2.md. Then CLAUDE.md and docs/requirements.md.
  2. cargo test — expect 1260 passing, 0 failed, 1 ignored (the baseline).
  3. cargo clippy --all-targets -- -D warnings — clean.
  4. Start sub-phase 2a per the plan. Standing authorizations (plan §§15) apply.
  5. Escalate per CLAUDE.md when the plan's triggers fire; never decide silently on a question the ADR/plan doesn't settle.

§8. What else is open

Unchanged from handoff-25 §8 (prioritisation is a user decision): snapshot/undo U-series (ADR-0006 written, unbuilt); m:n convenience C4; modify-relationship C3a; show family V5; rename-table C1 (may fall out of ADR-0030 Phase 4); friendly-error sweep H1; CI TT5; session-log / Markdown export V4.

ADR-0030's later phases — DML (Phase 3), DDL (Phase 4), the DSL→SQL teaching echo (Phase 5), polish (Phase 6) — remain on the roadmap behind Phase 2.

Phase-1's specific deferrals (§4.1–§4.5 in handoff-25) are addressed as follows by ADR-0032:

  • §4.1 — FROM optional. Stands. ADR-0032 §1 notes SELECT 1 and SELECT upper('x') continue to parse.
  • §4.2 — implicit projection aliasing (select a x). Lifted by ADR-0032 §1 — admitted in Phase 2.
  • §4.3 — help_id: None on SELECT. Stands; Phase 6.
  • §4.4 — walker entries defaulting to internal Mode::Simple. Stands; revisited when advanced mode grows its own ambient assistance (Phase 6).
  • §4.5 — bool SELECT results render as 0/1. Partially lifted by ADR-0032 §12 — bare bool column refs now recover their type via engine column-origin metadata. Computed bool expressions stay typeless until a future enhancement.