Files
rdbms-playground/docs/handoff/20260520-handoff-26.md
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

333 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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).
- **2b** — `Node::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 model** — `ScopeFrame` 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.rs`** — `Node` enum gets the new
`ScopedSubgrammar(&'static Node)` variant alongside the
existing `Subgrammar`.
- **`src/dsl/walker/context.rs`** — `WalkContext` 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.rs`** — `schema_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.rs`** — `do_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.