12 KiB
Session handoff — 2026-05-26 (41)
Forty-first handover. This session shipped ADR-0035 Phase-4 sub-phase
4h — ALTER TABLE … RENAME TO (planned Phase 1→3 with a planning
/runda and a finished-slice /runda, both of which found real bugs)
plus a follow-up cross-cutting fix: case-insensitive table-name
resolution across all executors (a drift / silent-data-loss bug the
user asked to make safe). The next job is 4i — the verification
sweep, which completes ADR-0035 Phase 4.
§1. State at handoff
Branch: main. HEAD a95c807. Tests: 1909 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):
f7e77a8 feat: ADR-0035 4h — ALTER TABLE … RENAME TO
a95c807 fix: resolve table names case-insensitively across all executors
Git state. 6112859 (handoff-40 docs), f7e77a8 (4h), a95c807
(case-safety) are unpushed — a normal working state. Push is the
user's step; nothing blocks. Working tree clean.
§2. What shipped this session
-
4h —
ALTER TABLE <old> RENAME TO <new>(f7e77a8). The one genuinely new low-level op in Phase 4: a newdo_rename_table— native engineRENAME TO(structure-preserving, no rebuild) + one-transaction (commit-db-last) reconciliation of everything the engine doesn't track: every metadata row naming the table (__rdbms_playground_columns, both ends of__rdbms_playground_relationships,__rdbms_playground_table_checks), the CSV file (via the existingfinalize_persistencerewrite+delete path —rewritten_tables = [new],deleted_tables = [old]; no new persistence method), and CHECK text that qualifies a column with the old table name (T.age→U.age, both column- and table-level —rewrite_check_table_qualifier, extending the 4e tokenizer). Grammar: a fifthAlterTableAction::RenameTable { new }, added by splitting therenameverb into one branch with an innerChoiceon a distinct second keyword (columnvsto— the §6.1 trap-safe pattern); the new-name slot mirrors theCREATE TABLEname slot (IdentSource::NewName+reject_internal_tablevalidator). Refusals (engine-neutral, case-insensitive): same-name, case-only, existing-target,__rdbms_*, non-existent. Auto-named indexes and relationships keep their stale names (only table-name columns update — §6 scope; user-confirmed). One undo step; advanced-mode only; closes the rename half ofC1. Plan:docs/plans/20260526-adr-0035-sql-ddl-4h.md.The two
/rundapasses both paid off (probe, don't reason):- Planning pass (blocking): the CHECK-text drift — the engine rewrites
a table-qualified column ref in the live CHECK on rename, but the
stored text would stay
T.ageand break a fresh rebuild. Confirmed empirically (SQLite 3.48); resolved by the rewrite (§2.9 of the plan). Headline test:e2e_rename_table_with_table_qualified_check_survives_ fresh_rebuild. - Finished-slice pass (engine-neutrality leak): byte-exact collision guards let a case-only rename / case-insensitive clash surface the raw engine "there is already another table" error; fixed with case-insensitive pre-checks + a dedicated case-only message.
- Planning pass (blocking): the CHECK-text drift — the engine rewrites
a table-qualified column ref in the live CHECK on rename, but the
stored text would stay
-
Case-insensitive table-name resolution (
a95c807). SQL identifiers are case-insensitive (the engine resolves any capitalization), but the metadata tables (table_name/parent_table/child_table) and thedata/<table>.csvfiles used case-sensitive TEXT=— so an op naming a table in a different case drifted: schema ops orphaned metadata, and a wrong-caseinsert/update/deletesilently skipped the CSV write → the change was lost on the next reload/rebuild. This contradicted ADR-0009's stated rule (case-insensitive resolution, case-preserving display) — a bug, not a new decision. Fix: acanonical_table_name(conn, name)helper (resolve to stored case viaCOLLATE NOCASE, excludingsqlite_*and__rdbms_*) applied at the entry of every table-naming executor (drop table, add/drop/rename column, change column type, add/drop constraint, add relationship, add index, rename table, insert/update/delete, and the advanced SQL DML), shadowing the table name with its canonical form so metadata + native SQL + CSV stay in step. The internal-__rdbms_*guard is folded into the same lookup (executors that previously lacked it now refuse internal/sqlite_*as "no such table").do_rename_tablenow accepts a case-variant source too. Columns stay strict (wrong-case column → "no such column" — safe, never drifting; user-confirmed scope). Tests:tests/case_insensitive_names.rs(6, all with fresh-rebuild proofs). ADR-0009 gained a clarifying sentence (the canonicalization mechanism).
ADR-0035 stays Accepted; README + requirements.md Q1/C1 + ADR §13
(4h) + Status updated. ADR-0009 clarified.
§3. The NEXT job — 4i (the verification sweep, completes Phase 4)
Canonical list: ADR-0035 §13 4i. With 4h's own help/usage now in place, the running deferral tail is:
- (a) Refresh the
CREATE TABLEhelp/usage skeleton for the 4a.2DEFAULT/CHECK/composite-UNIQUE, 4a.3 table-CHECK, and 4b FK forms (deferred from each). The 4d/4e/4f/4g/4h forms already carry their own help/usage — so the CREATE TABLE skeleton is the only help/usage debt. - (b)
describedisplay of table-level constraints — compositeUNIQUE+ tableCHECK(incl. named CHECKs from 4g). The unique-index[unique]marker shipped in 4d; only the table-level constraint display remains. - (c) 4b self-ref FK pre-submit indicator — a
CREATE TABLEwith a self-referencing FK (references <self>) parses + executes fine, but the pre-submit schema-existence diagnostic false-flags the not-yet-created self table as unknown. Treat a FK parent equal to theCREATE TABLEtarget as valid. - (d) shared-entry-word completion merge — in advanced mode a shared
entry word (
create/drop) surfaces only the SQL node's continuations, sodropoffers onlytable(not the DSLcolumn/relationship/index/constraint) and a partial likedrop relreturns empty. Merge the expected sets of all candidate nodes. 4d widened this (two advanced nodes each forcreate/drop). - (e) user discussion flag (2026-05-25): before/with (d), discuss visually distinguishing simple- vs advanced-mode completions in the hint UI (likely colour) — a UX design conversation.
- Plus the §4i staples: typing-surface + matrix coverage, engine-neutral error pass, undo-parity (one step per statement).
Sequencing note: (a)/(b)/(c) are mechanical and well-scoped; (d)/(e)
involve a UX design conversation with the user (escalate before building).
A finished-slice /runda covering 4i is worth budgeting (the last two
both found real bugs).
§4. Tracked follow-ups (not lost)
New this session:
- Case-only table rename refusal (4h, autonomous decision).
alter table T rename to tis refused ("differ only in capitalization … nothing to rename"), decided as an extension of the user's same-name refusal. The engine can't do a case-only rename viaRENAME TOdirectly; supporting it would need a two-step temp-name dance. Flagged to the user; left as refuse unless they ask otherwise. - Column-name case strictness (case-safety fix). Table names are now resolved case-insensitively; column names are still matched case-sensitively (wrong case → "no such column"). Safe (never drifts), but not SQL-case-insensitive like tables. A separate UX-consistency change if the user wants it (would need column-name canonicalization across the column ops).
Carried:
- H1 wording —
--create-fkleak (4g).ALTER … ADD FOREIGN KEYon a missing child column reusesdo_add_relationship's error, which mentions the DSL flag--create-fk— off-key in SQL. Fix the wording for the SQL path. H1. - Coverage gap — pre-4g 3-column
CHECK_TABLEmigration. The rebuild-timeALTER … ADD COLUMN namemigration + the named-CHECK-on- old-project refusal are sound + guarded but the genuinely-old 3-column in-place path is untested (can't fabricate a 3-col table through the public API). The common case (old project → fresh rebuild → 4-col) IS tested. A raw-SQL test helper or fixture.dbwould close it. - H1 friendly wording for the 4e CHECK-guard refusals and the 4f type-conversion diagnostics (engine-neutral but terse).
- The §8 items from handoff-39: app-lifecycle-command runtime-failure
journalling; M4 execution-time mode side-channel;
blobvalue literal; CI/TT5; DSL→SQL teaching echo (ADR-0030 Phase 5).
§5. Patterns the implementer must not forget
- Walker
Choicedoes not backtrack between same-leading-keyword branches. Keep one branch per leading verb, fan out on a distinct second token via an innerChoice(4gadd/drop, 4hrename). Bit 4g and shaped 4h. do_rebuild_from_textreconstructs ALL metadata from yaml. A new metadata table/column MUST be added to both the wipe and the repopulation, or a fresh rebuild silently drops it. Probe the fresh-rebuild path, not just in-place (the50a889ebug, and 4h's CHECK-text drift, both lived there).- Canonicalize a user-supplied table name to its stored case at the
entry of any executor that touches metadata or CSV by name
(
canonical_table_name,db.rs). The engine is case-insensitive; our metadata/CSV are case-sensitive. Any NEW table-naming executor must do this or it will drift (the exacta95c807bug class). - Exhaustive matches: a new
AlterTableActionvariant forces arms inruntime.rs,app.rs(build_translate_context), and a newOperationvariant forcesOperation::keyword(). The compiler finds them. - Two DDL generators stay in sync (
do_create_table/schema_to_ddl); catalog lockstep + vocab audit — keep new help/usage engine-neutral (engine_vocabulary_auditenforces it). - Probe, don't reason. Both 4h
/rundapasses found real bugs by testing the engine's actual behaviour (the CHECK rewrite; the case-collision leak) rather than reasoning about it.
§6. Process pins (unchanged, still binding)
- Confirm every commit. Propose the message; wait for the go-ahead. No AI attribution. (Both this session's commits were user-approved.)
- Push is the user's step. Never push. Unpushed commits are normal.
- Escalate ambiguity / scope. 4h's open calls (same-name, index/rel staleness, CHECK-drift handling) and the case-safety scope (all paths; table-only vs +columns) were all surfaced + user-decided before building. Do the same for 4i's (d)/(e).
- DA hat each slice;
/rundaat the planning exit AND on the finished slice. Both paid off this session. Budget at least one finished-slice/rundafor 4i. - Keep docs lockstep — ADR status/§13 + README +
requirements.mdin the same edit.
§7. How to take over
- Read, in order: this file →
docs/adr/0035-advanced-mode-sql-ddl.md(§13 4i is the spec) →CLAUDE.md→docs/requirements.md(Q1,C1). The 4h plan (docs/plans/20260526-adr-0035-sql-ddl-4h.md) is a good model. - Baseline:
cargo test # expect 1909 passing / 0 failing / 0 skipped / 1 ignored cargo clippy --all-targets -- -D warnings # clean - 4i (§3): start with the mechanical, well-scoped items (a)/(b)/(c)
test-first; escalate the (d)/(e) completion-UX conversation to the
user before building; finish with the §4i staples + a finished-slice
/runda. 4i completes ADR-0035 Phase 4.