Session summary covering nine implementation commits since handoff-26 (e032f01..0c3847a). Records the user-approved deferral of §10.3 stage 2 (CTE column-derivation harvest), flags three further 2d diagnostic-key deferrals that need user confirmation, and points the next implementer at the seams for sub-phases 2e (qualified-prefix completion + post-walk fixup) and 2g (verification sweep + final report). Test totals at handoff: 1385 passing, 0 failed, 1 ignored (+125 from the 1260 baseline at handoff-26).
17 KiB
Session handoff — 2026-05-20 (27)
Twenty-seventh handover. Implementation session for ADR-0032 Phase 2. Sub-phases 2a, 2b (with §10.3 stage 2 deferred), 2c, 2d (with three diagnostic keys deferred), and 2f all shipped. Sub-phases 2e (completion + post-walk fixup) and 2g (verification sweep) remain.
§1. State at handoff
Branch: main. Working tree clean. 11 unpushed commits
(be8a4f5..0c3847a plus the prior handoff-26 commit on top of
3db292c).
Tests: 1385 passing, 0 failing, 1 ignored (up from the 2026-05-19 handoff-26 baseline of 1260 / 0 / 1 — a net +125 tests).
Clippy: clean (last verified at end of 2f, commit
0c3847a).
Commits since handoff-26:
0c3847a db: column-origin type recovery in SELECT results (sub-phase 2f)
c5cf03b walker: SQL diagnostics — multi-binding scope, qualified refs, Phase-1 gap closure (sub-phase 2d)
a491df3 grammar: migrate Phase-1 SELECT to the ADR-0032 fragment (sub-phase 2c)
4ff054c walker: populate cte_bindings placeholders + projection_aliases (§10.3 stage 1 / §10.4)
b522d09 walker: populate from_scope table bindings (§10.1)
98a74b2 grammar: sql_expr additive extensions for §5/§6, CTE body rewires to ScopedSubgrammar
4f89106 walker: Node::ScopedSubgrammar variant + scope-frame stack (§10.2)
8d29335 grammar: SQL SELECT full statement fragment (Phase 2a)
e032f01 docs: ADR-0032 Amendment 1 — empirical scope of column-origin metadata
§2. What's done — the Phase-2 surface
A user typing in advanced mode can now write the full Phase-2 SELECT surface authored by ADR-0032 §§1–9:
- All five JOIN flavours (
INNER/LEFT OUTER/RIGHT OUTER/FULL OUTER/CROSS) and bareJOIN. - All four set ops (
UNION/UNION ALL/INTERSECT/EXCEPT) in compound queries. - CTEs — both
WITHandWITH RECURSIVE, with optional(col-list). Dispatched via a newdata::WITHCommandNode(entry wordwithis now inADVANCED_ONLY_ENTRIES). GROUP BY/HAVINGwith arbitrarysql_exprkeys.- Qualified column refs (
t.c/alias.c) — additive extension tosql_expr.rs'sname_or_call(§5). - Subquery expressions — scalar
(SELECT …),IN (SELECT …),[NOT] EXISTS (SELECT …)— additiveChoicebranches insql_expr.rs(§6), recursing throughNode::ScopedSubgrammar(&SQL_SELECT_COMPOUND). LIMIT n [OFFSET m]with fullsql_expradmission for both args (legacyLIMIT m, nrejected per §13 OOS-4).DISTINCT/ALLprefix.t.*qualified wildcard in projection.- Bare-alias projection (
SELECT a x FROM t— lifts Phase-1 §4.2) via per-positionNode::Lookaheadfollow-set gating. - OOS shapes reject:
NATURAL JOIN,JOIN … USING, comma-FROM, derived tables in FROM, window functions (OVER (…)),VALUESrow source,LATERAL(partial — the single-keyword form is admitted as a bare alias; the comma form rejects via OOS-3).
The Phase-1 data::SELECT shape migrated from data.rs into
sql_select.rs's SQL_SELECT_TAIL (sub-phase 2c). Phase-1's
22 local grammar nodes plus the duplicate
reject_internal_table validator are gone; the single source
of truth for the SELECT shape is now sql_select.rs.
2.1. Walker capabilities
Node::ScopedSubgrammar(&'static Node)(ADR-0032 §10.2) is the new node variant. Same recursion semantics asSubgrammar, plus a push/pop ofScopeFrameon theWalkContext::from_scope_stack. Sharessubgrammar_depthwithSubgrammarso theMAX_SUBGRAMMAR_DEPTH = 64cap fires uniformly.WalkContext::from_scope_stack: Vec<ScopeFrame>— always non-empty (the bottom frame is the implicit top-level scope DSL paths use). EachScopeFramecarriesfrom_scope: Vec<TableBinding>,cte_bindings: Vec<CteBinding>, andprojection_aliases: Vec<String>.- Three new
Node::Identflags:writes_table_alias,writes_cte_name,writes_projection_alias. Together with the existingwrites_table(now repurposed to push a TableBinding alongside its priorcurrent_tablewrite), they populate the top frame's accumulators during the walk. All 48 existing Ident sites carry the new flags asfalse(mechanical sed update; no behavioural change for DSL paths). current_table/current_table_columnsstay as fields onWalkContext(additive approach — they're populated bywrites_tablealongside the from_scope push). Plan §2b's "make them derived helpers" became "keep them populated redundantly" — strictly safer, no behavioural drift for DSL paths, but a future-cleanup opportunity if the duplication proves a maintenance burden.
2.2. Diagnostics
Three diagnostic passes run on every successful parse (ADR-0032 §11.7):
schema_existence_diagnostics— extended for multi-binding scope (a pre-pass collects alltable_name/cte_name/table_aliasidents to sidestep the projection-before-FROM ordering problem). Emitsdiagnostic.unknown_table,diagnostic.unknown_column,diagnostic.unknown_qualifier,diagnostic.ambiguous_column,diagnostic.duplicate_cte(Plan §Open-2, user-approved).sql_predicate_warnings— NEW. MatchedPath-walking variant of the predicate-warning pass, closes the Phase-1 carry-over gap (§11.6). Emitsdiagnostic.eq_null,diagnostic.like_numeric,diagnostic.type_mismatchon SQLWHERE/HAVING/JOIN ON/CASE/ projection /ORDER BYslots. Scoped to bare column refs in<column> <op> <literal>form — qualified-ref and expression-operand cases stay un-flagged (safe false-negative posture).- DSL
predicate_warnings(existing) — unchanged. Still walks the DSLExprAST for DSLWHEREexpressions.
2.3. Type recovery (sub-phase 2f)
Per ADR-0032 §12 + Amendment 1:
Cargo.tomlrusqlite features now includecolumn_metadata.do_run_selectcallsresolve_select_column_types(conn, stmt)afterprepare, queries__rdbms_playground_columnsfor each result column whose origin metadata is populated, and threads the type through toformat_cell.- ADR-0030 Phase-1 §4.5 (bool rendering as
0/1) is lifted for any bare-column reference whose origin the engine carries through — non-recursive CTE bodies, scalar subqueries, derived tables, set ops, JOINs all work for free per Amendment 1.
2.4. Catalog (ADR-0032 §11.5)
Six new diagnostic.* keys + eight new engine.* keys
authored in src/friendly/strings/en-US.yaml and registered
in src/friendly/keys.rs:
diagnostic.ambiguous_column,diagnostic.compound_arity_mismatch,diagnostic.cte_arity_mismatch,diagnostic.duplicate_cte,diagnostic.projection_alias_misplaced,diagnostic.unknown_qualifier.engine.no_such_table,engine.no_such_column,engine.ambiguous_column,engine.aggregate_misuse,engine.group_by_required,engine.compound_arity_mismatch,engine.scalar_subquery_too_many_rows,engine.recursive_cte_malformed.
The engine.* keys are catalog entries only — the
friendly-error layer reads them by key when reached, but no
proactive enhancement of the layer's engine-message
classification was pulled through here. That stays as a
small follow-up.
§3. Deferrals — all user-approved or documented
§3.1. §10.3 stage 2 — CTE column-derivation harvest
Status: user-approved deferral (mid-session, 2026-05-20).
Recorded in: docs/plans/20260520-adr-0032-phase-2.md
§2b "Implementation status (2026-05-20)" block.
The placeholder CTE-binding push (stage 1) IS implemented —
the CTE name is visible to the body (WITH RECURSIVE
self-reference works) and to downstream CTE-name validators
(2d's schema-existence pass accepts CTE-name table-source
refs). What's missing is the six §10.3 derivation rules that
populate the placeholder's columns at body-frame exit.
Knock-on effect: qualified-prefix completion past
cte_alias.| returns an empty candidate list. Bare column
refs into a CTE binding short-circuit to "accept silently" in
2d's schema-existence pass (we don't know the columns yet).
Folds into 2e (where qualified-prefix completion needs CTE
columns) and any future arity-check pass that wants to compare
declared (col-list) vs derived projection arity.
§3.2. Three 2d diagnostic keys deferred
projection_alias_misplaced, compound_arity_mismatch, and
cte_arity_mismatch — catalog keys + strings are authored
but the emitting logic is not implemented:
projection_alias_misplacedneeds clause-context detection (which clause an Ident sits in — the matched path is flat; some role-based recognition would work).compound_arity_mismatchneeds per-leg projection counting as the compound is assembled.cte_arity_mismatchdepends on §10.3 stage 2.
These were not explicitly user-approved as deferrals — they
were noted in the 2d commit message as "deferred per the
2d-scoped DA review (documented as (TBD) in the cross-cut
matrix for 2g)". The user should flag if these need to land
before 2g signs off.
§3.3. Engine.* key wiring
Catalog entries authored; the friendly-error layer doesn't yet recognise the new engine messages and route them through the new keys. This is a small mechanical extension to the engine-error matcher; not blocking but worth folding in.
§4. What's next — sub-phases 2e and 2g
§4.1. Sub-phase 2e — completion + post-walk fixup
Per plan §2e:
- Qualified-prefix completion narrowing (§10.5): at the
cursor in
SELECT t.|, candidates should bet's columns (not the global column set). Requires:- The completion API gains a "prefix qualifier" hint that
the walker passes when the cursor is immediately after
Ident '.'. - The
IdentSource::Columnscompletion path honours the hint when present, resolving against the top frame'sfrom_scopebinding fort.
- The completion API gains a "prefix qualifier" hint that
the walker passes when the cursor is immediately after
- Post-walk projection-list fixup pass (§10.6): the
projection-before-FROM problem. Collect projection
Identterminals during the walk; after walk completion, re-resolve each against the finalfrom_scope; rewrite the highlight class and validity diagnostic.
Note: the §10.6 problem is partially sidestepped already in 2d — the schema-existence pass uses a two-pass approach that collects bindings first and then resolves. So projection-side identifiers don't get false-positive diagnostics from the existing pass. The 2e fixup-pass scope is more about getting the HIGHLIGHT class right at every keystroke (the typing experience) and supporting completion mid-typing. Worth re-reading §10.6 carefully with the 2d implementation in hand.
§4.2. Sub-phase 2g — integration sweep + verification
Per plan §2g:
- Fill in the cross-cut verification matrix (29 rows in
docs/plans/20260520-adr-0032-phase-2.md). Many rows are satisfied by tests already authored in 2a–2f; the matrix needs to be walked and each row pointed at its test. - Produce the final phase-exit verification report.
- DA final review.
§4.3. The §10.3 stage-2 harvest
If 2e wants real CTE-column-prefix completion, the harvest
needs to land first. Six derivation rules per the ADR's
table. Mechanism design: hook a callback into
walk_scoped_subgrammar's on-exit path; when the frame
being popped is a CTE body, walk its projection items and
populate the placeholder binding in the outer frame.
Projection item shape can be reconstructed from a
projection_record accumulator on the frame (record the
shape of each projection_item as the walker matches it —
a new Node::ProjectionRecord wrapper, or a flag on
Lookahead factories, would be the mechanism).
If the user is willing to defer CTE-prefix completion
along with the harvest, 2e can ship without the harvest;
qualified completion past cte.| returns nothing today
and would continue to do so.
§5. Working tree and unpushed commits
e032f01 docs: ADR-0032 Amendment 1 — empirical scope of column-origin metadata
8d29335 grammar: SQL SELECT full statement fragment (Phase 2a)
4f89106 walker: Node::ScopedSubgrammar variant + scope-frame stack (§10.2)
98a74b2 grammar: sql_expr additive extensions for §5/§6, CTE body rewires to ScopedSubgrammar
b522d09 walker: populate from_scope table bindings (§10.1)
4ff054c walker: populate cte_bindings placeholders + projection_aliases (§10.3 stage 1 / §10.4)
a491df3 grammar: migrate Phase-1 SELECT to the ADR-0032 fragment (sub-phase 2c)
c5cf03b walker: SQL diagnostics — multi-binding scope, qualified refs, Phase-1 gap closure (sub-phase 2d)
0c3847a db: column-origin type recovery in SELECT results (sub-phase 2f)
Plus this handoff commit, once it lands.
§6. Seams for the next implementer
Where to look first:
-
For 2e qualified-prefix completion: the existing
IdentSource::Columnscompletion path insrc/completion.rs(driven byWalkContext::current_table_columns). The qualified case needs a new "narrow to this binding's columns" pathway. The walker already records thesql_expr_qualified_refrole on the trailing ident in at.creference; the precedingPunct('.')andsql_expr_identgive the qualifier. WalkerWalkContextcould grow apending_qualifier: Option<String>set when the walker enters a qualified-ref tail, cleared on successful match. -
For 2e post-walk fixup: the schema-existence pass in
src/dsl/walker/mod.rsalready does a pre-pass. The 2e fixup is about the HIGHLIGHT layer — rewritingper_byte_classentries for projection-list idents once the finalfrom_scopeis known. The walker'sper_bytevec is the mutation target. Look atwalker/highlight.rsfor how byte-class runs are built today. -
For 2g verification matrix: walk row by row through
docs/plans/20260520-adr-0032-phase-2.md's "Cross-cut verification matrix" table. Many rows already have tests; fill in the test location column. The handful that don't have explicit tests (the projection-before-FROM re-walk cycle, for example) may need new ones authored. -
For the §10.3 harvest: see §4.3 above. The cleanest mechanism is probably a new walker hook (a function pointer on a per-
ScopedSubgrammarbasis, or ausageflag on theNode::Identvariants that record projection items), but the design isn't fully nailed down. The implementer should think about whether to escalate the harvest design to the user before coding.
Code locations modified this session (in commit order):
docs/adr/0032-sql-select-grammar.md— Amendment 1 added.docs/adr/README.md— index entry extended for Amendment 1.docs/plans/20260520-adr-0032-phase-2.md— §2b "Implementation status (2026-05-20)" block recording the §10.3 stage-2 deferral.src/dsl/grammar/mod.rs—Node::ScopedSubgrammarvariant- three new Ident flags +
data::WITHregistered +ADVANCED_ONLY_ENTRIESextended withwith.
- three new Ident flags +
src/dsl/grammar/data.rs— ~150 lines of dead Phase-1 SELECT grammar removed;data::SELECTreshaped toNode::Subgrammar(&sql_select::SQL_SELECT_TAIL);data::WITHadded.src/dsl/grammar/sql_select.rs— new file (sub-phase 2a), augmented through 2b and 2c.src/dsl/grammar/sql_expr.rs— additive §5 / §6 extensions.src/dsl/walker/context.rs—TableBinding,CteBinding,CteColumn,ScopeFrame;WalkContext::from_scope_stack.src/dsl/walker/driver.rs—Node::ScopedSubgrammarmatch arm;walk_scoped_subgrammar;writes_table_alias/writes_cte_name/writes_projection_aliashandling inwalk_ident.src/dsl/walker/mod.rs— multi-bindingschema_existence_diagnosticsrewrite, newsql_predicate_warningspass, 16 new tests.src/friendly/keys.rs+src/friendly/strings/en-US.yaml— 6 newdiagnostic.*+ 8 newengine.*keys.Cargo.toml—column_metadataadded to rusqlite features.src/db.rs—resolve_select_column_typeshelper;do_run_selectthreads per-column types through toformat_cell.tests/sql_select.rs— 3 new SELECT type-recovery tests.
§7. How to take over
- Read this file in full. Then
docs/adr/0032-sql-select- grammar.md(and Amendment 1 — the empirical findings matter for 2f's design),docs/plans/20260520-adr-0032- phase-2.md, andCLAUDE.md. cargo test— expect 1385 passing, 0 failed, 1 ignored.cargo clippy --all-targets -- -D warnings— clean.- Pick the next sub-phase. 2e is substantial (qualified completion + post-walk fixup); 2g is the closing sweep (cross-cut matrix + verification report). Either is a reasonable next session.
- Escalate before re-litigating deferrals. The three diagnostic deferrals in 2d (§3.2) are not user-approved — check whether they need to land before 2g signs off.
- The §10.3 stage-2 harvest is the biggest open piece; if 2e wants real CTE-prefix completion, escalate the harvest design before coding.
§8. What's open beyond Phase 2
Unchanged from handoff-26 §8: snapshot/undo U-series;
m:n convenience C4; modify-relationship C3a; rename-table
C1; 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 the Phase 2 sub-phases not yet completed.