§12 was written conservatively, classifying projection items
structurally and listing "subquery expressions" alongside
arithmetic / CASE as cases that stay None. The Phase-2 plan's
Open Question 1 captured the matching uncertainty about CTEs
and scalar subqueries.
A throwaway probe against the pinned bundled SQLite +
rusqlite 0.39.0 (with the `column_metadata` feature) settles
the question across 20 representative query shapes. The
engine's column_table_name / column_origin_name metadata
follows through non-recursive CTEs (SELECT *, bare-ref,
qualified-ref, and (col-list)-renamed bodies; CTE chains),
scalar subqueries (aliased and unaliased), derived tables
(out of scope per §13 OOS-1 but useful to note), all four
set ops, multi-table JOIN projections, and IN-subquery
WHERE clauses (the inner subquery does not affect the
outer projection's origin).
The structural-None classes reduce to computed projections
(function calls, arithmetic, CASE, literals, wildcards —
expected and pedagogically obvious) and recursive CTE result
columns (the one structural surprise — the recursive
temporary table has no base-column origin to point at).
Amendment 1 supersedes §12's "Resolution rule" with a simpler
engine-driven rule: trust column_table_name(i) /
column_origin_name(i) verbatim, with no grammar-side
structural classification. The speculative MatchedPath-walk
fallback is moot. The Phase-2 plan's sub-phase 2f exit gate
gains explicit positive assertions for CTE pass-through and
scalar-subquery type recovery, and a new explicit negative
assertion for the recursive-CTE limitation.
README.md index entry extended in the same style as ADR-0027's
Amendment-1 line. Closes Plan §Open-1.
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.
ADR-0030 §3 commissioned a focused ADR for the full SELECT
grammar (the "SELECT — full" phase). ADR-0032 records the
decisions; docs/plans/20260520-adr-0032-phase-2.md is the
implementation plan walking the work.
Phase 2's grammar surface:
- Five JOIN flavours (INNER, LEFT, RIGHT, FULL OUTER, CROSS).
NATURAL/USING/comma-FROM explicitly OOS.
- All four set ops (UNION, UNION ALL, INTERSECT, EXCEPT).
- WITH and WITH RECURSIVE CTEs, with optional (col-list) renaming.
- Scalar subqueries, IN (SELECT …), [NOT] EXISTS as additive
primary branches in sql_expr (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 `LIMIT m, n` OOS.
- DISTINCT/ALL, t.* projection, bare-alias projection (lifts
Phase-1 §4.2's autonomous decision).
Walker-capability honesty (§10): ADR-0030 §8's "ambient
assistance comes for free" holds for grammar recursion (reuses
ADR-0026's Subgrammar + depth cap unchanged) but not for
completion scope. Phase 2 adds a new Node::ScopedSubgrammar
variant alongside the existing Node::Subgrammar (DSL Expr and
sql_expr recursion untouched), a from_scope_stack of
ScopeFrames holding from_scope / cte_bindings /
projection_aliases, qualified-prefix completion narrowing, and
a post-walk fixup pass that re-resolves projection-list
identifier highlighting/validity once FROM is parsed (the
projection-before-FROM problem).
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 and writes derived columns back into the
binding.
Diagnostics (§11): every Phase-2 validation case classified
against ADR-0027's ERROR/WARNING guideline. Five new diagnostic.*
catalog keys for parse-time-detectable cases (unknown_qualifier,
ambiguous_column, projection_alias_misplaced, cte_arity_mismatch,
compound_arity_mismatch) plus eight engine.* translation keys.
A MatchedPath-walking predicate-warnings variant closes the
Phase-1 carry-over gap where SQL WHERE expressions emitted no
LIKE-on-numeric / = NULL / type-mismatch warnings — ADR-0027
Amendment 1 finally extends to the SQL surface.
Result-column type resolution (§12): rusqlite 0.39.0 exposes
column_table_name / column_origin_name / column_database_name
behind a `column_metadata` feature; verified. Bare column refs
recover their playground type — partially lifts Phase-1 §4.5's
bool→0/1 deferral.
The implementation plan breaks Phase 2 into seven sub-phases
(2a–2g) with explicit exit gates per sub-phase and a cross-cut
verification matrix that names every "X comes for free" claim
from ADR-0030/0031/0032. The Phase-1 SQL-expression
predicate-warning gap is a named row, preventing an analogous
silent gap from shipping. The plan encodes the user's standing
authorization for the implementer to walk uninterrupted between
gates and commit with standard messages — escalation
discipline preserved for design ambiguities and real blockers.
Pushes remain user-only.
New docs/plans/ directory sets a pattern for future phase plans.
Status: Accepted.
Implementation handoff: a SQL `select` typed in advanced mode
parses, runs, and renders end to end; the same line in simple
mode lights up the precise "this is SQL" hint instead. ADR-0031
(the SQL expression grammar) and ADR-0030 Phase 1 ("Foundations
+ first SELECT") landed across five commits. Tests 1240 → 1260,
clippy clean.
The handoff records: the walker mode gate + `is_advanced_only`
set, the `ast_builder` source-param sweep, `Command::Select`
carrying the validated SQL text, the `data::SELECT` shape, the
worker `Request::RunSelect` round-trip, the ambient mode
threading through completion / overlay / validity indicator,
the autonomous calls made during execution (FROM optional,
implicit alias unsupported, etc.), and the seams the next
session uses to take up ADR-0030 Phase 2 (full SELECT) — which
gets its own focused ADR before code, per ADR-0030 §3.
ADR-0030 §3 commissioned a focused ADR for the stratified SQL
expression grammar fragment. ADR-0031 records the decisions:
- One unified precedence ladder (OR/AND/NOT, comparison/LIKE/IN/
BETWEEN/IS NULL predicates, arithmetic incl. `||`, function
calls, CASE) — SQL treats booleans as values, so unlike
ADR-0026's bool/scalar split this is a single ladder.
- No AST — every Phase-1 consumer (SELECT projection, WHERE)
runs validated SQL as text per ADR-0030 §4/§6; CHECK/DEFAULT
in Phase 4 store text too. The fragment's job is accept /
reject + the matched-terminal path + a source span.
- Recursion via Subgrammar with ADR-0026's depth cap reused.
- A parallel `grammar/sql_expr.rs` — separate from `expr.rs` so
simple mode's 1240-test surface is untouched by construction.
- Subquery expressions and qualified `t.c` column refs deferred
to ADR-0030 Phase 2 (they need the recursive SELECT grammar).
`%` modulo is included alongside `+ - * /` and `||` — it isn't
ISO SQL but is near-universal across mainstream engines and
matches learner expectations (pedagogy wins ties, ADR-0030).
Status: Accepted. The implementation lands in subsequent
commits.
Decides the architecture for SQL in advanced mode (Q1/Q2/Q4):
SQL is authored as grammar within the unified grammar tree
(ADR-0024) and parsed by the existing walker — not a separate
batch parser — so SQL gets the same completion, highlighting,
hints, and parse-error reporting as the DSL. Mode gates the
SQL forms. DDL routes through the typed Command executor
(metadata and the playground type vocabulary preserved); DML
and SELECT execute as validated SQL. Engine-neutral posture;
DSL→SQL teaching echo; phased plan.
Supersedes ADR-0001's sqlparser-rs reservation. Ticks Q4;
updates the ADR index and the Q1/Q2 notes. handoff-24 orients
the implementation session at Phase 1.
ADR-0029 (column constraints — NOT NULL / UNIQUE / CHECK /
DEFAULT) is fully implemented across the handoff-22 and
handoff-23 sessions. Ticks requirement C3, and corrects
ADR §10's CHECK-error wording to the compiled-SQL form per
the §7 storage deviation.
ADR-0028 complete (per handoff-21); ADR-0029 (column
constraints) written, accepted, and implemented through
commit 4 of 6 — NOT NULL / UNIQUE / DEFAULT / CHECK at
`create table` and `add column`. Commits 5 (`add constraint`
/ `drop constraint` + the §5 dry-run) and 6 (friendly errors
+ typing-surface matrix) are planned in full in §4.
The fourth constraint. `check ( <expr> )` reuses the ADR-0026
WHERE-expression grammar via `Subgrammar`, so a check is
written in the same language as a `where` filter.
- Grammar: a `CHECK_CONSTRAINT` arm joins the shared
constraint-suffix Choice; `consume_check_expr` extracts the
parenthesised expression (paren-depth aware) into
`ColumnSpec.check` / `Command::AddColumn.check`.
- Storage: the parsed `Expr` is compiled once to inline SQL
(`compile_check_sql` — `compile_expr` + ADR-0028's
param-inliner) and stored in that form everywhere — a new
`check_expr` column in `__rdbms_playground_columns`,
`project.yaml`'s `ColumnSchema.check`, and the column DDL
emitted by `do_create_table` / `schema_to_ddl`.
- `add column … check` routes through the rebuild primitive
(SQLite's `ALTER … ADD COLUMN` cannot carry it); a CHECK on
a serial/shortid column is create-table-only and refused at
add-column with a friendly message.
- `describe` surfaces the CHECK. ADR-0029 §7/§8 updated to the
SQL-form decision — double-quoted identifiers, consistent
with ADR-0028's `explain` display SQL.
1201 tests pass (+8); clippy clean.
`create table … with pk` now parses the column-constraint
suffix; combined with the commit-1 db layer, a constrained
table works end to end.
- A shared constraint-suffix grammar fragment — `not null`,
`unique`, `default <literal>` — sits after each column's
`(type)` group; `build_create_table` walks the matched path
per column and folds the constraints into `ColumnSpec`.
- §9 redundancy check: every `with pk` column is a primary-key
column, so `not null` (any) and `unique` (single-column PK)
are rejected with a friendly error
(`parse.custom.constraint_redundant_on_pk`).
- `project.yaml` round-trip: `ColumnSchema` gains `not_null` /
`default`; the YAML reader/writer and `build_read_schema`
carry them, so `rebuild` / `export` / `import` preserve
constraints.
- ADR-0029 §2.1's example corrected — `create table` columns
are all PK columns, so its suffix is for `default` / `check`;
`docs/simple-mode-limitations.md` records that non-PK
columns at create time need advanced mode.
CHECK is deferred to the next commit. 1184 tests pass (+7);
clippy clean.
Designs the remaining C3 surface: the four column-level
constraints declared in the column-spec suffix at `create
table` / `add column`, and modified on existing columns via
`add constraint … to` / `drop constraint … from`.
- A pre-flight dry-run (the ADR-0017 ethos) scans a populated
column before applying NOT NULL / UNIQUE / CHECK and refuses
with a pretty-table of offending rows; no `--force`.
- CHECK reuses the ADR-0026 expression grammar via Subgrammar.
- `__rdbms_playground_columns` carries a new `check_expr`
column; the other three are recoverable from SQLite pragmas.
- README index updated.
ADR-0028 (query plans / `explain`) is fully implemented; the
handoff-16 design trio (ADR-0026 / 0027 / 0028) is now closed.
- handoff-21: session summary, the two deliberate deviations
from handoff-20's plan, test coverage, open clusters.
- requirements.md: QA1 / QA2 ticked.
- CLAUDE.md: the `EXPLAIN QUERY PLAN` deferred-items line
updated to "implemented per ADR-0028".
Interim handoff. ADR-0028 (query plans / explain) is started:
step 1 (the styled-output-line mechanism, 03d8a09) is done and
committed. handoff-20 carries the full validated build plan
for steps 2-5 with file:line anchors and three implementation
gotchas (the const/static Subgrammar wrinkle, build_show's
positional dispatch, and why steps 2+3 must land as one
commit) so a fresh session implements them without
re-exploring.
handoff-19 §2 now records the two bugs as fixed (was "queued
next"): the optional trailing-flag completion fix (f239ca5)
and the --resume temp-project pointer fix (3a40ae2). §5 drops
them from "what's next" — ADR-0028 is now the natural next
pick. State/§6 updated to 10 commits and 1131 tests;
requirements.md test baseline → 1131.
ADR-0027 gains a "Follow-up" section recording the completed
§2 highlight + hint wiring and precise per-literal WARNING
spans; the three stale As-built bullets point at it.
requirements.md test baseline → 1125 and the S6 entry notes
the completion + Amendment 1. handoff-19 records the run and
queues the two deferred manual-testing bugs (add 1:n
relationship completion/usage hint; --resume / last_project)
as the next session's first work.
LIKE is a text-pattern match; against a numeric column (int,
real, decimal, serial) it runs but is almost never intended.
predicate_warnings now emits a WARNING for it, spanned at the
target column. New Type::is_numeric; catalog key
diagnostic.like_numeric; ADR-0027 gains "Amendment 1" and the
adr/README index line is updated per the index-upkeep rule.
bool and the text-/blob-backed types are deliberately not
flagged — see the amendment for the rationale.
3 walker tests (int, decimal NOT LIKE, text-column clean).
1108 passing, clippy clean.
The multi-form usage-template fix (151ed08) and the reviewed
`add index` syntax decision (kept as-is), so the next agent
does not re-flag a settled question.
Sweep: input_verdict tests confirm the schema-existence check
fires across the identifier-taking commands — unknown table
on drop / show / add column, unknown column on drop column /
update — and that known references stay clean. The Step B
check is grammar-generic, so this is verification + coverage
rather than new code.
Docs: requirements.md S6 -> [x], baseline 1096; CLAUDE.md
deferred list reconciled (C5a and S6 are done — removed);
ADR-0026's as-built note updated (step 5 shipped via
ADR-0027); ADR-0027 gains an As-built notes section
recording the post-walk diagnostics realization, the
pre-rendered message, the timeout-based debounce, coarse
WARNING spans, and the deferred highlight/hint wiring.
Adds tests/typing_surface/where_expression.rs — 9 matrix
cells for the complex WHERE / show-data limit typing surface:
operator candidates after an operand, AND / OR after a
predicate, NOT, BETWEEN / IN bounds, and `show data`
where / limit.
Writing the cells surfaced a grammar bug. `predicate_tail`'s
`[NOT] negatable` branch started with `Optional(not)`, and an
Optional-first `Seq` always "commits" — so on an incomplete
input the walker's `Choice` returned that branch's
`Incomplete` early and discarded every sibling branch's
expected set, dropping `is` and the comparison operators from
completion after a column. Fixed by splitting it into
explicit `NOT negatable` and bare `negatable` branches — no
`predicate_tail` branch starts with an `Optional` now. The
matched terminal sequence is unchanged, so `build_expr` is
untouched.
Docs: ADR-0026 gains an "As-built notes" section recording
the option-1 builder realization, its two deviations from the
§3 sketch, and the deferral of §7 diagnostic flagging to
ADR-0027. requirements.md C5a -> [x] (steps 1-4) with the
test baseline refreshed to 1079; CLAUDE.md's deferred list
reconciled (C5a implemented; the QA1/QA2 note now points at
ADR-0028).
`create table … with pk` parsed column types as `name:type`,
while `add column` uses `name(type)`. Unify on the parens
form so column-type syntax is consistent across the DSL:
create table T with pk id(serial), name(text)
Only `COL_SPEC` changes (`:` → `( … )`); `build_create_table`
reads columns by role, so it is unaffected. The `:` that
separates table from column in `add column` / `drop column`
is unchanged. Sweeps the test suite, the typing-surface
matrix (two `after_colon` cells renamed to `after_paren`,
4 snapshots regenerated), the friendly catalog's usage
templates, ADR-0009's example, and requirements.md.
1039 passing / 0 failing / 1 ignored; clippy clean.
The QA1/QA2 design: an `explain` prefix command over
`show data` / `update` / `delete` that runs
EXPLAIN QUERY PLAN (without executing the statement) and
renders the result as an annotated tree. Plan steps keep
the engine's own wording; an annotation taxonomy marks
full scans, index use, and the automatic-index "you
should add an index here" case. Introduces a general
styled-output-line mechanism — an OutputLine may carry
per-span styling — realising the per-span theming
ADR-0016 deferred; the plan renderer is its first
consumer. The explained SQL is shown above the tree as
standard, copy-pasteable SQL.
- docs/adr/0028-query-plans.md — the ADR.
- docs/adr/README.md — index entry.
- docs/requirements.md — QA2 [~] -> [ ]; QA1 note
reconciled (designed in ADR-0028).
A debounced `[ERR]` / `[WRN]` marker at the right edge of
the input row, summarising — before submit — whether the
current command would run. Backed by a small
diagnostics-severity model: the walker emits severity-
tagged diagnostics (parse outcome, schema-existence of
table / column names) that the indicator summarises and
the existing highlighting / hint layers detail. Advisory
only — submission is never blocked.
- docs/adr/0027-input-validity-indicator.md — the ADR.
- docs/adr/README.md — index entry.
- docs/requirements.md — new S6 (TUI shell).
The C5a design: a stratified, recursive WHERE-expression
grammar (AND/OR/NOT, comparisons, LIKE, IS NULL, IN,
BETWEEN) for update / delete / show-data filters; show
data gains optional `where` and `limit`. Adds the
`Subgrammar` reference-following grammar node and a
recursive `Expr` AST, built selectively for the
expression fragment.
- docs/adr/0026-complex-where-expressions.md — the ADR.
- docs/adr/README.md — index entry.
- docs/simple-mode-limitations.md — new running list of
simple-mode query boundaries vs. advanced SQL, seeded
from ADR-0026.
- docs/requirements.md — C5a [~] -> [ ] (designed, not
yet implemented); new Documentation section with DOC1.
Implement ADR-0025 — indexes as a DSL DDL feature.
- Grammar: `add index [as <name>] on <T> (<cols>)`, `drop index
<name>` / `drop index on <T> (<cols>)`, plus a `--cascade`
flag on `drop column`.
- db.rs: index operations over the engine's native index
catalog (no metadata table). The rebuild-table primitive now
captures and recreates indexes, so `change column` and the
relationship operations no longer silently drop them.
- `drop column` refuses an indexed column unless `--cascade`,
which drops the covering indexes and reports each.
- Persistence: additive `indexes:` list in `project.yaml`
(version unchanged); round-trips through rebuild/export/import.
- Display: an `Indexes:` section in the structure view and a
nested tables/indexes items panel (S2).
Reconciles requirements.md (C3 index portion, S2 satisfied)
and CLAUDE.md. 1038 tests passing (+31), clippy clean.
ADR-0024 audited as fully implemented. Amend the ADR with a "Phase F
minimal" implementation note (parser.rs retained as the router +
ParseError home) and update the README index line to match.
Reconcile docs/requirements.md against handoffs 10-14: refresh the
test baseline (449 -> 1006), mark U4 (replay) satisfied, correct the
A1 / H1a / H3 progress notes.
Amend handoff-14: §3 flagged items both resolved (ranker kept,
CommandNode.hint_mode removed); §4 rewritten as a concrete next-work
pointer at the reconciled requirements.md.
Concrete specification for the direction in ADR-0023, landed
during the round-6 design pass. Resolves all four rounds of
open design questions: walker as single source of truth,
scannerless terminal vocabulary (~8 building blocks), typed
value slots with content validators, WalkContext for schema-
aware narrowing from day one, WalkOutcome multi-purpose
return, HintMode per-node, ranker as separate layer, static
+ dynamic sub-grammars, aliases as Word annotations,
IdentSource taxonomy, six-phase per-command migration with
chumsky and walker side-by-side during the transition.
Key shifts from ADR-0023's sketch:
- Lexer dissolves entirely. Walker operates on bytes directly.
dsl/lexer.rs, dsl/keyword.rs go away in Phase F.
- Schema-aware parse from day one (not phased). Typed value
slots reject mis-shaped input at parse time with localised
wording. Completion narrows per column type.
- Sub-grammars: static (fn() -> Node) for composition;
dynamic (fn(&WalkContext) -> Node) for schema-dependent
expansion. No global named registry.
- Path-bearing commands: BarePath becomes a routine
non-whitespace terminal. Paths with spaces require quoting
via StringLit (UX simplification, aligns with standard CLI
convention).
- 13-node taxonomy: Word, Punct, Ident, NumberLit, StringLit,
BlobLit, Flag, BarePath, Choice, Seq, Optional, Repeated,
DynamicSubgrammar.
Migration plan: Phase A (walker scaffolding + app-lifecycle
commands), Phase B (DDL without value literals), Phase C
(create table), Phase D (data commands with full schema
awareness -- the design's central claim landing), Phase E
(replay), Phase F (delete chumsky + lexer + legacy parser
modules, simplify catalog). Estimated ~4 sessions total.
Also: rename ADR-0023 from 0023-proposed-unified-grammar-tree.md
to 0023-unified-grammar-tree.md (git mv preserves history)
and update its status to reflect the direction-accepted-but-
superseded-for-execution-detail relationship with ADR-0024.
Index updated.
Captures the architectural critique surfaced during round-5
manual testing — that adding a keyword or command currently
requires edits in 7-10 files across parser, completion, usage
registry, catalog, and tests — and the proposed direction: a
single declarative trie registry that drives parse, completion,
highlight, and usage rendering from one source.
Status: Proposed. Not yet accepted. Filename carries the
`-proposed-` segment so status is visible at directory-listing
time; rename to `0023-unified-grammar-tree.md` on acceptance.
Estimated cost: ~4 sessions, per-command migration. Why not
now: feature backlog and bearable scatter cost. Right moment
to execute when backlog quiets or scatter cost becomes
visibly painful.
Replaces the originally-planned separate ADRs for syntax
highlighting (I4) and tab completion (I3) with a single
unified design. The framing: colour, hint panel, and Tab
are three answers to the same question — what does the user
need to know mid-typing? — and planning them separately
produces three loose pieces that drift apart.
Three mid-typing states (valid-so-far / definite-error /
incomplete-but-plausible) drive four layered channels:
token-class colour and parse-error overlay (silent, always
on), hint panel ambient and Tab-triggered completion mode
(verbose, in the existing hint panel — no floating popups).
Schema-aware from day one via an IdentSlot taxonomy in the
parser (NewName / TableName / ColumnIn(TableRef::Earlier(N))
/ RelationshipName); every existing ident() call gets
audited and tagged. Completion candidates come from chumsky's
expected-token-set for keyword slots and from a new worker
request (ListNamesFor) for identifier slots.
Implementation lands in 8 green-after-each commits: theme
colours; input panel highlighting; echo line highlighting;
render-time parse + error overlay; hint panel ambient;
identifier-slot taxonomy + parser audit; schema query
plumbing; completion mode + key bindings. Estimated
1500-2500 lines across the eight stages.
Out of scope (deliberately): inline ghost text (could return
as a "most-likely" affordance later — fish-shell style),
fuzzy matching, punctuation completion, user-customisable
keybindings, SQL highlighting in advanced mode (waits on Q4).
ADR-0020 amends ADR-0001 with a two-phase parse: a lexer
producing a span-tagged token stream, then chumsky over
&[Token]. Single source of truth for keywords and punct via
a define_keywords!/define_punct! macro pattern. Parser
contract committed for I3 (queryable expected-token-set)
and I4 (lexer always succeeds, Error tokens for invalid
input). Includes an honest history note: the no-lexer shape
in dsl/parser.rs arose incrementally without ADR-level
deliberation against the known H1a/I3/I4 requirements; this
ADR corrects that.
ADR-0021 builds on ADR-0020 to close the H1a gap: a
per-command UsageEntry registry keyed off entry-keyword,
with parse errors rendered as caret + structural error +
matching usage template(s). Multi-entry families (add,
drop, show) render together. New catalog sections under
parse.usage.* (per-command grammar) and parse.token.*
(single-token vocabulary). Zero-prefix case ("frobulate
Customers") falls back to an "available commands:" framing.
Anchor-phrase compliance preserved.
Documents this session's work and the recommended next move:
## Session totals
- 11 commits since handoff-5
- 534 → 610 tests passing (+76)
- Release binary 7.2 → 7.8 MB
## What landed
- All four non-CI items from handoff-5's Independent Work
list: B2 (int→bool tests), B1 (help update), A2 (engine-
vocabulary audit), A3 (replay command)
- ADR-0019 fully implemented end-to-end:
- Friendly-error layer + i18n catalog (~170 entries
across 16 categories)
- §6 runtime row-pinpoint enrichment with
schema-resolved facts
- §9 migration sweep — every user-visible literal in
src/ now flows through the catalog (caught a ui.rs
gap during the post-sweep manual sanity check, folded
it in as sweep 3/3)
## Recommended next move
Parser-as-source-of-truth ADR + H1a implementation. The
friendly-error layer made engine errors much better;
parse-error wording is now the visibly-weakest user
surface. User explicitly surfaced the gap during manual
testing this session ("typing `create` should illustrate
the expectation"). Bounded scope, high pedagogical value,
unblocks I3/I4 in passing.
A1 (CI workflow) noted as the easy alternative for a
quick win first.
## Sharp edges captured
- New i18n workflow: catalog + keys.rs + t!() at every
use site, validator catches drift
- TranslateContext is owned (no lifetime); App combines
runtime FailureContext with verbosity
- Anchor phrases load-bearing per ADR-0019 §10
- `running: ` prefix coupled to caret-padding math
- main.rs initialises catalog before args parsing
- Several alignment-coupled strings deliberately left out
of the catalog (echo prefix tags, mode labels)
Per follow-up review: §8.5's framing read as "we'll do this
properly later". Reword to make it explicit that real
plural-form rules per locale (Fluent / ICU) are NOT a goal of
this project. Translators handle pluralisation in their
wording (`(s)` shorthand or rephrased templates) — sufficient
for a teaching tool's output surface, and we're not planning
to revisit it.
Matching Out-of-Scope entry tightened the same way.
Settles the design we discussed across this session's
follow-up to the engine-vocabulary audit:
- A central `friendly` module owns translation; the existing
ad-hoc helpers (`friendly_change_column_engine_error`,
`enrich_fk_message`) absorb into it.
- Initial catalog covers UNIQUE / FK / NOT NULL / CHECK /
type-mismatch errors with operation-tailored,
pedagogically-voiced wording in verbose and short variants.
- New `messages (short|verbose)` app-level command lets
advanced learners shrink the output. In-session state for
now; persisted later when settings persistence lands.
- Row pinpointing via post-failure re-query, rendered through
ADR-0017 §7's bordered diagnostic-table renderer.
`FriendlyError` is a structured payload (headline + hint +
optional table); `output_render` composes it.
- i18n foundation: hierarchical YAML catalog, embedded via
`include_str!`, fixed locale (en-US) for now, no external
files. `{name}` plain substitution; format specifiers
explicitly rejected so a translator cannot reformat values.
Value formats stay invariant across all locales (ISO 8601
dates, `.` decimals, `true`/`false`, `NULL`) — explicitly
not a translatable concern.
- Migration sweep is required follow-on but separable: a
`t!()` macro marks call sites and lets per-category PRs
land incrementally. Anchor-phrase list (§10) limits test
churn for the most common substring assertions.
Out of scope and explicitly deferred: advanced-mode SQL
error sanitisation (waits on Q1), settings persistence for
the messages command, plural-form rules per locale, runtime
locale selection, locale-aware value formatting (rejected,
not deferred), constraint-management surface (C3 territory).
README index updated.
Generalises serial and shortid beyond their previous restricted
forms:
- `serial` is no longer restricted to single-column PK. Non-PK
serial columns get an emitted UNIQUE constraint and use
application-side MAX(col)+1 at INSERT time (rowid alias still
drives the PK case for free; per ADR-0010 worker-thread
serialisation, the read-then-insert sequence is safe).
- `shortid` columns auto-fill existing null cells when the
column is materialised — `add column T: x (shortid)` on a
non-empty table no longer leaves rows in a not-really-valid
NULL state.
- `int -> serial` joins the type-change matrix as always-clean
identity (closes the asymmetry vs `text -> shortid`); other
sources are refused with a route-via-int hint.
- `change column T: x (serial|shortid)` fills null source
cells with sequence / generated values in the same rebuild
transaction.
Internal infrastructure:
- ReadColumn gains `unique: bool`; read_schema detects single-
column UNIQUE indexes via pragma_index_list /
pragma_index_info; schema_to_ddl emits inline UNIQUE for
non-PK columns.
- ColumnSchema (persistence) gains `unique: bool` so the flag
survives YAML round-trip and rebuild-from-text reconstructs
it faithfully — preserves the "serial -> int leaves UNIQUE
in place" promise across save/load cycles.
- ChangeColumnTypeResult.client_side now carries `auto_filled`
+ `auto_fill_kind` alongside `transformed` + `lossy`; the
app handler renders separate note lines when both apply.
- AddColumnResult is a new return type carrying pre-rendered
[client-side] note lines for the auto-fill paths.
Tests: 519 -> 534 (+15). Clippy clean.
Replaces the placeholder "trust STRICT" body of do_change_column_type
with the per-cell transformer matrix from ADR-0017. Adds:
- src/type_change.rs: CellOutcome { Clean / Lossy / Incompatible }
+ transform_cell + static_refusal covering every matrix pair
from §3 (54 unit tests).
- --force-conversion and --dont-convert flags on `change column`
(mutually exclusive at parse time per §5).
- Refined PK rule (§4.1): refused only when the column has an
inbound FK and fk_target_type would change. Outbound-FK columns
still refused outright (§4.2). PK / shortid uniqueness checked
post-transformation (§4.3).
- Bordered diagnostic tables (lossy / incompatible / collision)
via the pretty-table renderer (§7) — uses ADR-0016's primitives.
- [client-side] success note (§6) when any cell was rewritten.
- Friendly wrapper for engine-level errors under --dont-convert
so no engine vocabulary leaks (ADR-0002 user-facing posture).
ADR-0017 §3 + §7 amended in place (with user sign-off): serial->int
added explicitly to the always-clean matrix, and diagnostic rows
identify themselves by PK value(s) rather than positional indices
(SQLite returns rows unordered without ORDER BY, so positional
"row 5" is unaddressable).
Tests: 449 -> 517 (+68). Clippy clean with nursery lints.
Specifies the curated per-cell classification (clean /
lossy / incompatible) for column type changes, the static
transformer matrix (numeric chains, text↔structured types,
always-clean stringifications), and the PK / shortid /
uniqueness-bearing handling. Replaces the B2/C2
placeholder of "rely on engine STRICT and surface its
errors" with a learner-friendly model that:
* refuses incompatibles up-front,
* refuses lossy conversions by default with a re-run-with-
--force-conversion hint,
* refines the PK refusal: an inbound-FK PK is only refused
when the new type would change the FK target type
(so `serial → int` and `shortid → text` on FK-referenced
PKs are allowed; `int → text` etc. still refuse),
* adds a post-transformation uniqueness check for PK and
shortid columns,
* uses the pretty-table renderer (ADR-0016) for all
diagnostic row lists,
* emits a `[client-side] …` note in the success summary
whenever the transformer rewrote any cell.
`--force-conversion` accepts loss; `--dont-convert` skips
the client-side layer entirely; mutually exclusive.
Forward-look: a future iteration may add resolution flags
(`--default 0`, `--on-incompatible '<value>'`).
Also amends ADR-0002 with a new "User-facing posture"
section cementing that the database engine choice is an
implementation detail and is never named in user-visible
strings. Adds a corresponding bullet to CLAUDE.md's
working-style rules so every session picks it up.
Implementation lands as a follow-up.
Closes B2 (rebuild-table reused outside relationships) and
C2 (full add/drop/rename/change-type column operations).
* drop column [from] [table] <T>: <col>
- ALTER TABLE DROP COLUMN (SQLite 3.35+) + metadata
cleanup in __rdbms_playground_columns.
- Refuses PK columns and columns involved in a declared
relationship (drop the relationship first).
* rename column [in] [table] <T>: <old> to <new>
- ALTER TABLE RENAME COLUMN (SQLite 3.25+); SQLite
cascades the rename through FK declarations on other
tables.
- Mirrors the new name into both metadata tables
(__rdbms_playground_columns, __rdbms_playground_relationships)
so describes stay accurate after a rename.
- Refuses identity rename and name collisions.
* change column [in] [table] <T>: <col> (<newtype>)
- Routes through the rebuild_table primitive (ADR-0013)
since SQLite ALTER doesn't support type changes.
INSERT INTO new SELECT FROM old; STRICT typing enforces
cell compatibility, transaction rolls back on mismatch.
- Refuses PK columns, relationship-involved columns,
`serial` target, and no-op same-type changes.
Adds 20 tests (parser + db layer); updates the in-app help
listing. Both prepositions independently optional in each
new command, matching `add column`'s grammar shape.
Total: 449 passing, 0 failing, 0 skipped (up from 429).
Clippy clean.
Known spec gap: column-type-change conversion compatibility
is not yet documented (currently relies on SQLite STRICT
errors); follow-up will close this.