Files
rdbms-playground/docs/handoff/20260525-handoff-40.md

240 lines
13 KiB
Markdown

# Session handoff — 2026-05-25 (40)
Fortieth handover. This session **shipped ADR-0035 Phase-4 sub-phases
4f and 4g** (each test-first, with planning escalation and a *combined*
`/runda` finished-slice critique at the end) **plus a `/runda`-found
rebuild fix**. The next session **implements 4h —
`ALTER TABLE … RENAME TO`** (the §6 new low-level op), then **4i — the
verification sweep**. After 4h+4i, Phase 4 (ADR-0035) is complete.
## §1. State at handoff
**Branch:** `main`. **HEAD `50a889e`.** **Tests: 1885 passing, 0 failing,
0 skipped, 1 ignored** (the unchanged `friendly/mod.rs` ` ```ignore `
doctest). **Clippy:** clean (`cargo clippy --all-targets -- -D warnings`).
**This session's commits** (oldest → newest, local):
```
5b76315 feat: ADR-0035 4f — ALTER TABLE … ALTER COLUMN TYPE
6ff97f6 feat: ADR-0035 4g — ALTER TABLE add/drop constraint + add FK
50a889e fix: ADR-0035 4g — reconstruct table-CHECK metadata on rebuild
```
**Git state — no divergence.** `origin/main` is at **`5b76315`** (4f),
so **`6ff97f6` (4g) + `50a889e` (the fix) are unpushed** — a normal
working state. (The earlier `a2fc3c9` docs commit + `5b76315` 4f were
pushed at some point this session.) Push is the user's step; nothing
blocks. Working tree clean.
## §2. What shipped this session
- **4f — `ALTER TABLE <T> ALTER COLUMN <col> TYPE <type>`** (`5b76315`).
A fourth `AlterTableAction::AlterColumnType`, **runtime-decomposed to
`change_column_type(…, ChangeColumnMode::ForceConversion)`** — which
**is** the ADR-0035 §7 advanced policy (lossy converts *with a note*,
no force flag; incompatible + the ADR-0017 static refusals still
refuse; **`int → serial` is *allowed*** per ADR-0018 §8 — the §7
"→serial refused" summary is looser than the code). Builder
discriminates the branch by the **`type` keyword** (unique — ADD
COLUMN's type is an ident; checked **first** because `alter column …`
also contains `column`). Internal-`__rdbms_*` guard folded into
`do_change_column_type`.
- **4g — `ALTER TABLE` add/drop constraint + add FK** (`6ff97f6`).
`ADD [CONSTRAINT <name>] (CHECK | composite UNIQUE | FOREIGN KEY)` +
`DROP CONSTRAINT <name>`. **User-confirmed scope:** ADD = CHECK + FK +
composite UNIQUE; `ADD PRIMARY KEY` and a *named* UNIQUE are refused
(composite UNIQUE is anonymous in our model). Each ADD reuses a
low-level path with a dry-run guard (table-CHECK/UNIQUE rebuild; FK →
`do_add_relationship`, bare `REFERENCES <P>` → parent single PK,
`create_fk = false`). `DROP CONSTRAINT` resolves the name to a named
table-CHECK then a child-side named FK, else refuses. **Named-CHECK
round-trip:** a nullable `name` column on
`__rdbms_playground_table_checks` (**rebuild-only** arrival) **and** a
`project.yaml` `check_constraints` extension to a `{expr, name}`
mapping (the bare-string form still reads — back-compatible). The
internal-`__rdbms_*` guard was folded into `do_add_constraint` /
`do_add_relationship`, **completing the 4d/4e/4f guard class**. ADD
CHECK also refuses a name colliding with an existing CHECK *or*
relationship (keeps DROP unambiguous). **Grammar restructured:** the
walker's `Choice` does **not** backtrack between two same-leading-
keyword branches, so the action `Choice` keeps **one branch per verb**
(`add`/`drop`/`rename`/`alter`) and `add`/`drop` fan out to an **inner
`Choice`** whose branches each lead on a *distinct* second keyword
(column / constraint / check / unique / foreign / primary).
- **Rebuild fix** (`50a889e`, `/runda`-found). `do_rebuild_from_text`
re-emitted table-CHECKs into the recreated DDL (so enforced) but
**never repopulated `CHECK_TABLE`** — so a *fresh* rebuild (missing
`.db`, reconstructed from `project.yaml`) lost the CHECK metadata
(incl. the name): `DROP CONSTRAINT` / `describe` / a later save would
drop it. In-place rebuilds only worked because the wipe never touched
the table. (Latent since 4a.3 for unnamed checks.) Rebuild now wipes +
repopulates `CHECK_TABLE` from yaml (name + seq + expr), like
META/REL, and adds the `name` column if a pre-4g table predates it
(the rebuild-only migration). Regression test:
`e2e_named_check_metadata_survives_a_fresh_rebuild`.
ADR-0035 stays **Accepted**; README + `requirements.md` Q1 + ADR §13
(4f, 4g) updated each slice.
## §3. The NEXT job — 4h (`ALTER TABLE … RENAME TO`)
**No plan written yet** — 4h needs its own plan (Phase 1→3 + a planning
`/runda`/written-DA gate) before building, because it is the **one
genuinely new low-level op** in Phase 4 (ADR-0035 §6), not a reuse.
**Spec (ADR-0035 §6):** `ALTER TABLE <old> RENAME TO <new>` is
**advanced-mode only** (no simple-mode rename-table verb). Within **one
transaction**:
1. Rename the table in the database.
2. Rename its `data/<old>.csv``data/<new>.csv` (persistence layer).
3. Update **every** metadata row that names it:
- `__rdbms_playground_columns` rows (`table_name`),
- **both ends** of any `__rdbms_playground_relationships` row
(`parent_table` *and* `child_table`),
- **`__rdbms_playground_table_checks` rows** (`table_name`, added in
4a.3, keyed by table name).
4. Name validation + `__rdbms_*` rejection apply to the **target**.
Closes the rename half of `C1` for the advanced surface.
**Grammar:** a new `AlterTableAction::RenameTo { new: String }` (or
similar) — a **fifth** action branch in `AT_ACTION_CHOICES`. It leads on
`rename`**but `rename column` already leads on `rename`** (the walker
`Choice` does not backtrack same-led branches; see §7.4). So `rename`
must follow the §2 pattern: one `rename` branch with an inner `Choice`
on the distinct second token (`column` → rename-column; `to` → rename-
table). Mirror the `add`/`drop` restructure 4g did. The builder keys on
`contains_word("column")` (already the §4f/§4g discriminator order) vs a
bare `rename … to`.
**Executor:** a genuinely new `do_rename_table` (no existing reuse) — the
CSV rename is a persistence-layer concern (cf. how `do_drop_table` cleans
CSV + metadata; ADR-0015 §6 ordering, commit-db-last). One undo step
(snapshot is whole-project). Internal-`__rdbms_*` guard on the target
(and refuse renaming an internal table as source — `reject_internal_
table_name` on both).
**Tests:** Tier-1 parse (rename-table vs rename-column dispatch); Tier-3
e2e via `run_replay` — rename a table that has rows (CSV follows), a
relationship (both ends update), and a table-level CHECK (the
`table_checks` rows follow); **survives rebuild** (the renamed table +
its metadata round-trip through yaml); one undo step; refuse rename to an
existing name / to an `__rdbms_*` name.
## §4. Then 4i — verification sweep (the running deferral tail)
Canonical list: ADR-0035 §13 4i. Unchanged from handoff-39 except 4f/4g
now carry their own help/usage (so the *CREATE TABLE* skeleton refresh is
the only help/usage debt left). The tail:
- **(a)** Refresh the **`CREATE TABLE` help/usage skeleton** for the
4a.2 `DEFAULT`/`CHECK`/composite-`UNIQUE`, 4a.3 table-`CHECK`, 4b FK
forms (4d/4e/4f/4g forms already carry their own help/usage).
- **(b)** `describe` display of **table-level constraints** (composite
`UNIQUE` + table `CHECK`, now incl. **named** CHECKs from 4g) — the
unique-*index* `[unique]` marker shipped in 4d, so only the table-
level *constraint* display remains.
- **(c)** 4b **self-ref FK pre-submit indicator** (the diagnostic
false-flags a `references <self>` parent as unknown).
- **(d)** **shared-entry-word completion merge**`create`/`drop` (and
now constraint forms) should offer every valid continuation across the
SQL + DSL nodes; 4d/4g widened this.
- **(e)** **user discussion flag (2026-05-25):** visually distinguish
simple- vs advanced-mode completions in the hint UI (likely colour) —
a UX design conversation, before/with (d).
- Plus the §4i staples: typing-surface + matrix coverage, engine-neutral
error pass, undo-parity (one step per statement).
## §5. Tracked follow-ups (not lost)
- **H1 wording — `--create-fk` leak (4g).** `ALTER … ADD FOREIGN KEY` on
a *missing* child column reuses `do_add_relationship`'s error, which
mentions the DSL flag `--create-fk` — off-key in an SQL context. Fix
the wording for the SQL path (or branch the message). H1.
- **Coverage gap — pre-4g 3-column `CHECK_TABLE` migration (4g/fix).**
The rebuild-time `ALTER … ADD COLUMN name` migration and the named-
CHECK-on-old-project refusal (`check_table_has_name_column`) are
logically sound + guarded, but the **genuinely-old 3-column table**
in-place path is **untested** — a 3-col `CHECK_TABLE` can't be
fabricated through the public API (no raw-SQL exec). The common case
(old project whose `.db` is deleted → fresh rebuild → 4-col table) IS
tested. A test helper (raw-SQL exec, or a fixture `.db`) would close
it; flagged to the user, not yet decided.
- **H1 friendly wording** for the 4e CHECK-guard refusals and the 4f
type-conversion diagnostics (engine-neutral but terse) — carried.
- The §8 items from handoff-39 (app-lifecycle-command runtime-failure
journalling; M4 execution-time mode side-channel; `blob` value
literal; CI/TT5; DSL→SQL teaching echo) remain open.
## §6. Patterns the implementer must not forget
1. **Walker `Choice` does not backtrack between same-leading-keyword
branches.** This bit 4g (two `add` branches "expected column"). Keep
**one branch per leading verb**, fan out on a **distinct** second
token via an inner `Choice`. 4h's `rename` must join `rename column`
under one `rename` branch the same way.
2. **Reuse low-level executors; the runtime decomposes `SqlAlterTable`.**
4f wrapped `change_column_type`; 4g wrapped `do_add_relationship` +
added `do_alter_add_table_check`/`do_alter_add_unique`/
`do_drop_constraint_by_name`. 4h is the exception — a genuinely **new**
`do_rename_table` (§6).
3. **Two DDL generators stay in sync** (`do_create_table` /
`schema_to_ddl`). 4g added the `CONSTRAINT <name> CHECK` emission to
`schema_to_ddl`; `do_create_table`'s table-CHECK insert writes
`name = NULL` (CREATE-TABLE CHECKs stay unnamed — an ALTER-only
naming, a noted consistency item).
4. **`do_rebuild_from_text` reconstructs ALL metadata from yaml.** It now
wipes + repopulates `META` / `REL` / **`CHECK_TABLE`** and recreates
indexes. **A new metadata table or column MUST be added to both the
wipe and the repopulation** or a fresh rebuild silently drops it (the
exact bug `50a889e` fixed). 4h touches metadata *values* (renames), so
verify a renamed table round-trips through a fresh rebuild.
5. **Exhaustive matches:** a new `AlterTableAction` variant forces arms
in `runtime.rs` (inner `match action`) and `app.rs`
(`build_translate_context`'s inner match). The compiler finds them.
6. **Catalog lockstep + vocab audit** — refresh `sql_alter_table`
help/usage for the rename form; keep wording engine-neutral
(`engine_vocabulary_audit` enforces it).
7. **Probe, don't reason** — the combined `/runda` caught the rebuild bug
precisely because a test was written for the *fresh*-rebuild path the
original e2e skipped. For 4h, test the CSV-rename + both-relationship-
ends + table_checks-follow paths explicitly, and a fresh rebuild.
## §7. Process pins (unchanged, still binding)
- **Confirm every commit.** Propose the message; wait for the go-ahead.
No AI attribution.
- **Push is the user's step.** Never push/force-push. Unpushed commits
are normal (see §1).
- **Escalate ambiguity / new cost.** 4g's three scope forks (ADD set,
CHECK migration, DROP scope) and the discovered `project.yaml`-format
implication were all surfaced + user-decided before building. Do the
same for 4h's open calls (e.g. rename-to-existing-name behaviour, any
describe/echo wording).
- **DA hat each slice; `/runda` at the planning exit AND on the finished
slice.** The user opted to **combine** the finished-slice `/runda`
across 4f+4g this session — it found a real bug (the rebuild metadata
loss). Budget for at least one finished-slice `/runda` covering 4h
(+4i); it pays off.
- **Keep docs lockstep** — ADR status/§13 + README + `requirements.md`
in the same edit.
## §8. How to take over
1. **Read, in order:** this file →
`docs/adr/0035-advanced-mode-sql-ddl.md` (§6 rename, §13 4h/4i) →
`CLAUDE.md``docs/requirements.md` (`Q1`, `C1`). The 4f/4g plans
(`docs/plans/20260525-adr-0035-sql-ddl-4f.md`, `…-4g.md`) are good
models for the **4h plan you must write**.
2. **Baseline:**
```
cargo test # expect 1885 passing / 0 failing / 0 skipped / 1 ignored
cargo clippy --all-targets -- -D warnings # clean
```
3. **Plan 4h** (Phase 1→3 + planning `/runda`), surfacing any open calls
to the user; then implement test-first per §3 (grammar restructure of
the `rename` branch → `do_rename_table` executor → runtime/app arms →
Tier-1/Tier-3 tests incl. a fresh rebuild → catalog + docs lockstep →
finished-slice `/runda` → commit proposal).
4. **Then 4i** (§4) — the verification sweep closes Phase 4.