Commit Graph

339 Commits

Author SHA1 Message Date
claude@clouddev1 631074ff9c feat: ADR-0035 4a — SQL CREATE TABLE command, worker, and exit gate
Command + builder + worker for advanced-mode SQL CREATE TABLE
(sub-phase 4a), executed structurally through do_create_table:

- Command::SqlCreateTable + build_sql_create_table (ddl.rs): aliases via
  from_sql_name (incl. double precision), column- and table-level
  PRIMARY KEY, redundant-flag de-dup off a sole PK, IF NOT EXISTS.
  Advanced REGISTRY entry on the shared `create` word (SQL-first, DSL
  fallback); no-PK tables allowed (user-confirmed).
- Worker (db.rs): Request::SqlCreateTable + CreateOutcome + snapshot_then
  (one undo step); IF NOT EXISTS no-op (no snapshot, but journalled, like
  read-only commands). do_create_table inline-PK rule aligned with the
  rebuild generator schema_to_ddl — no round-trip DDL drift; serial
  autoincrement is independent of inline-PK (verified by round-trip
  tests).
- Runtime/App: dispatch + CommandOutcome::SchemaSkipped +
  AppEvent::DslCreateSkipped (structure + "already exists — skipped"
  note). Friendly catalog keys added (engine-neutral).

DEFAULT/CHECK/table-level UNIQUE are absent from the 4a grammar (parse
error with usage skeleton; friendly message + support land in the 4a.2
constraint slice) — user-confirmed.

Tests: type resolver, grammar shape, builder (incl. the PK
detection bug they caught), and tests/sql_create_table.rs (worker
round-trip, serial autoincrement first/non-first across rebuild, IF NOT
EXISTS no-op + journalling, no-PK table, one undo step) + a replay-as-
write test. 1739 pass / 0 fail / 1 ignored; clippy clean.

Exit gate: ADR-0035 Proposed -> Accepted (validated end-to-end by 4a);
README + requirements.md Q1 updated.
2026-05-25 10:04:28 +00:00
claude@clouddev1 80310929d7 feat: ADR-0035 4a — SQL CREATE TABLE grammar shape
The post-CREATE shape (src/dsl/grammar/sql_create_table.rs):
TABLE [IF NOT EXISTS] <name> ( <col-def | table-PK> , … ) [;]
- col-def: <name> <type> [NOT NULL] [UNIQUE] [PRIMARY KEY]
- type: ten keywords + standard-SQL aliases (via from_sql_name) +
  the two-word `double precision` branch + discarded (len[,len]) arg
- table-level PRIMARY KEY (cols) — single and compound
- __rdbms_* target rejected at walk time

DEFAULT/CHECK/table-level UNIQUE shapes are deliberately absent (the
4a.2 constraint slice); FK is absent (4b). 13 accept/reject tests
mirror sql_insert's walk_node harness. Shape only — the CommandNode +
builder + worker wiring follow.
2026-05-25 08:11:39 +00:00
claude@clouddev1 58386d77e9 feat: ADR-0035 4a — SQL type-alias resolver (Type::from_sql_name)
Advanced-mode SQL type slot accepts the ten playground keywords plus the
standard-SQL aliases (integer/varchar/timestamp/numeric/float/double
precision/binary/..., case-insensitive). Simple-mode FromStr is unchanged
(rejects aliases). Unknown names -> None for the friendly diagnostic.
2026-05-25 07:55:26 +00:00
claude@clouddev1 94ec87b2ff docs: ADR-0035 4a — refine scope (CHECK/DEFAULT to constraint slice; double-precision; serial-inline)
Three design questions settled during 4a implementation (plan + ADR §13
+ README in lockstep):
- CHECK/DEFAULT defer to the 4a.2 constraint slice: sql_expr is
  validate-only (no Expr AST), so they need raw-SQL-text storage on a
  separate path, not do_create_table's Expr->compile reuse. 4a.2 now
  also covers composite UNIQUE / multi-column table CHECK.
- double precision (the lone two-word alias) handled via a keyword-pair
  branch; single-word aliases + discarded (len) cover the rest.
- serial sole-PK in a multi-column table must inline PRIMARY KEY to keep
  autoincrement (worker-step do_create_table extension).
4a core narrows to columns + types + NOT NULL/UNIQUE/PRIMARY KEY +
IF NOT EXISTS; everything else errors "not yet supported".
2026-05-25 07:55:22 +00:00
claude@clouddev1 093496fe6b docs: ADR-0035 4a plan + 4a.2 split for composite UNIQUE / table CHECK
Add the sub-phase 4a implementation plan (docs/plans/), test-first,
mirroring the ADR-0033 DML sub-phase model: SqlCreateTable as its own
command executed structurally through the existing do_create_table
helper; shared-entry-word dispatch (SQL-first, simple fallback); the
type-alias resolver; IF NOT EXISTS no-op-with-note (CreateOutcome
enum); INTEGER PRIMARY KEY -> plain int; one-undo-step wiring.

Records the user-confirmed 4a/4a.2 split: composite UNIQUE(a,b) and
multi-column table CHECK move to a dedicated slice because they are the
first structures TableSchema cannot already represent, so they need a
persistence-model + round-trip extension rather than parse+execute
reuse. ADR-0035 §13 gains 4a.2; README sub-phase line updated in
lockstep.
2026-05-24 22:54:07 +00:00
claude@clouddev1 19d3cd3306 docs: ADR-0035 — record two /runda refinements (IF [NOT] EXISTS, INTEGER PRIMARY KEY)
Pre-implementation /runda round settled two open micro-calls before 4a,
both user-confirmed:

- IF [NOT] EXISTS admitted (no-op-that-succeeds-with-a-note), not
  refused — a near-universal cross-vendor idiom (PostgreSQL, MySQL,
  SQLite, Oracle 23ai), reclassified into scope rather than treated as
  an engine-specific spelling. Touches §3/§4/§12/§13 (4a, 4c).
- INTEGER PRIMARY KEY maps to a plain int PK, not auto-increment;
  serial stays the sole auto-increment type (§3).

README index updated in the same edit per the lockstep rule.
2026-05-24 22:31:44 +00:00
claude@clouddev1 f74226fe8c docs: session handoff 36 — ADR-0006 (undo/snapshot) shipped; ADR-0035 (SQL DDL) drafted, 4a next 2026-05-24 22:16:10 +00:00
claude@clouddev1 a079200b17 docs: ADR-0035 — advanced-mode SQL DDL (Phase 4)
Phase 4 of the ADR-0030 roadmap; clarifies §4. Advanced-mode
CREATE/DROP/ALTER TABLE + CREATE/DROP INDEX get their own
per-statement Sql* commands, executed structurally (not verbatim)
so the playground's types, named relationships, and STRICT stay
intact. Full surface (no pre-emptive cuts): constraints, compound
PK, FK -> named relationships (one statement = one undo step),
ALTER incl. advanced-only table rename (C1), [UNIQUE] indexes.
Unified column-type-conversion: lossy refuses in simple mode but
proceeds-with-a-note in advanced, with undo as the safety net.
Integration (parser/hint/completion/diagnostics/history/replay/undo)
is structural via the unified grammar; replay treats DDL as a write.
Nine sub-phases (4a-4i). Updates the ADR README index.

Status: Proposed (design agreed; implementation pending).
2026-05-24 22:14:30 +00:00
claude@clouddev1 df6aa69155 fix: ADR-0006 — clear redo when new work commits without a snapshot
/runda found silent data loss: with the non-fatal snapshot-failure
policy, a committed mutation whose snapshot couldn't be staged left
the redo stack stale (redo-clear was only a side effect of finalize),
so a later redo silently discarded the new work. Same gap in batches.

- SnapshotStore::clear_redo() drops the redo stack + payloads
- snapshot_then / end_batch call it when committed user work has no
  staged snapshot; for disk-full it succeeds where a full backup
  couldn't (tiny index write + payload deletes)
- unit test + integration regression (forced staging failure)
- ADR-0006 implementation note records the fix + residual edge

1698 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 21:10:44 +00:00
claude@clouddev1 5442cfc0b9 docs: ADR-0006 §8 step 8 — mark undo/snapshot implemented
- requirements.md U1/U2: [ ] -> [x] with implementation notes
- CLAUDE.md Safety bullet: implemented; drop U-series from the
  deliberately-deferred list
- ADR-0006 Amendment 1: "not yet implemented" -> implemented, plus an
  Implementation note (index.yaml, source-gated snapshots, non-fatal
  snapshot-failure policy, batch primitive, testing + accepted gaps)
- ADR README index: undo/snapshot half marked implemented
2026-05-24 20:58:31 +00:00
claude@clouddev1 cf53023a71 test: ADR-0006 §8 step 7 — full-stack undo across DSL + SQL (R21/R22)
Three Tier-3 flows through the real worker:
- undo/redo steps back across interleaved DSL insert, SQL insert,
  and SQL delete — proving SQL DML snapshots like DSL (R22)
- undo restores the database read model AND the on-disk CSV
  together (consistent (db, csv) pair)
- the snapshot ring persists across a close + reopen of the project
  (undo works in a later session)

1696 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 20:56:15 +00:00
claude@clouddev1 d6c5674bf5 feat: ADR-0006 §8 step 6 — .snapshots/ gitignore + export + cleanup
The undo ring is local working state, handled at all three
project-file seams (R13):
- .gitignore template ignores /.snapshots/
- export excludes .snapshots/ (like playground.db / history.log)
- safely_delete_temp_project allowlists .snapshots/ so a temp that
  was modified then undone back to empty stays auto-deletable
- undo::SNAPSHOTS_DIR is now a pub const referenced by all three
- tests: gitignore content, export exclusion, cleanup allowlist

1693 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 20:53:00 +00:00
claude@clouddev1 25800e3eb5 feat: ADR-0006 §8 steps 4-5 — undo/redo commands + confirm-modal flow
Commands & grammar (step 4):
- AppCommand::Undo/Redo, grammar nodes + REGISTRY entries, catalog
  help/usage + keys; parse tests
- replay skips undo/redo (is_app_lifecycle_entry_word) + completion
  entry-keyword lockstep; replay-skip test extended

Wiring (step 5):
- Action::{PrepareUndo,PrepareRedo,Undo,Redo} + AppEvent::{UndoPrepared,
  UndoUnavailable,UndoSucceeded,UndoFailed}
- App: undo_enabled flag, Modal::UndoConfirm, dispatch + event handling
  + confirm-key handler (Y confirms / N/Esc cancels); "turned off" when
  --no-undo; "nothing to undo/redo" when empty
- ui::render_undo_confirm names the command + snapshot time
- runtime: opens with undo enabled (!--no-undo), threads it through the
  project-switch path, spawn_prepare_undo/spawn_undo (peek->modal,
  restore->refresh tables + schema cache)
- 9 Tier-1 app tests + 3 parse tests

1692 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 20:48:30 +00:00
claude@clouddev1 a97069c02e feat: ADR-0006 §8 step 3 — wire the snapshot ring into the db worker
- snapshot_then() brackets all 19 mutating dispatch arms: stage a
  pre-op snapshot, finalise on success / discard on rollback; gated
  on a user command source (internal ops like open-time rebuild are
  not snapshotted) and on undo being enabled
- BatchState + BeginBatch/EndBatch requests: a batch takes one
  boundary snapshot, suppresses per-command snapshots, and finalises
  iff a mutation committed (one undo step per replay/batch)
- Undo/Redo/PeekUndo/PeekRedo requests handled in worker_loop with
  &mut conn for the restore; cleanup() sweeps crash leftovers on open
- Database::{undo,redo,peek_undo,peek_redo,begin_batch,end_batch} +
  open_with_persistence_and_undo(); snapshot failures are non-fatal
  (logged), restore failures surface
- 6 Tier-3 integration tests through the real worker

1680 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 20:31:05 +00:00
claude@clouddev1 64eee3ed6d feat: ADR-0006 §8 steps 1-2 — --no-undo flag + snapshot ring module
Step 1 (Cargo + CLI):
- add the `backup` feature to rusqlite (online backup API)
- `--no-undo` flag (test-first) + help-banner entry

Step 2 (snapshot store, src/undo.rs):
- SnapshotStore: a persisted undo ring + redo stack under
  <project>/.snapshots/ (index.yaml + per-snapshot payload dirs)
- hybrid whole-project snapshot: db via backup API + project.yaml /
  data/*.csv copied as files; restore is text-first, db-last
  (ADR-0015 §6 commit-db-last)
- stage/finalize/discard, undo/redo (each snapshots current to keep
  the inverse possible), N=50 eviction, redo-cleared-on-new-work,
  orphan/staging cleanup, monotonic ids
- 12 Tier-1 tests; adds a crate-visible persistence::utc_iso8601_now

No worker wiring yet (step 3). 1674 passed / 0 failed / 1 ignored; clippy clean.
2026-05-24 20:17:03 +00:00
claude@clouddev1 6cf5705022 docs: ADR-0006 Amendment 1 — undo/snapshot design (every-mutation, hybrid, batch) + plan
Settles the undo/snapshot half (U1/U2) before implementation:
- every-mutation single-step undo (supersedes destructive-only model)
- hybrid whole-project snapshot (db backup API + yaml/csv copy),
  reconciling ADR-0006 with ADR-0015's derived-db model
- persisted N=50 ring; redo discarded on new work
- batch ops (replay + future) record one undo step; import excluded
- --no-undo disable switch
Adds the implementation plan and updates README index, requirements
U1/U2, and CLAUDE.md in lockstep.
2026-05-24 19:57:47 +00:00
claude@clouddev1 2e3131669a docs: session handoff 35 — ADR-0034 shipped; ADR-0006 (undo/snapshot) is next
Records this session's close-out: ADR-0033 Phase 3 marked Accepted; ADR-0034 (history journal + replay filter, incl. Amendment 1 replay app-command skip) implemented and verified. Tees up ADR-0006's undo/snapshot half (U1/U2) as the next job with scope considerations and open design calls.
2026-05-24 19:05:38 +00:00
claude@clouddev1 e4f2f5fa15 feat: ADR-0034 — history journal records err + replay parses/filters the journal
Replay (§3): run_replay parses <ts>|<status>|<source> journal records — runs ok, skips non-ok — while still accepting bare .commands scripts (prefix-detected so a | inside a bare command isn't misread). Fixes replay history.log, which died on line 1.

Journal failures (§1/§2): failed commands are recorded err via a new Action::JournalFailure, emitted by the pure-sync App for both parse failures and worker-execution failures (runtime appends best-effort, never fatal). Hydration reads all records so typo'd/rejected commands are recallable across sessions.

Amendment 1 — replay filters app-lifecycle commands: a working replay history.log exposed that the journal also records save as/load/new/export/import/rebuild/mode (which would panic the worker dispatch or abort replay). Replay now re-applies only schema/data writes and skips every app-lifecycle command + nested replay, classified by entry word so modal/incomplete forms (save as, bare mode) and quit skip uniformly rather than aborting. All skips continue (reversing the nested-replay refusal); import and nested replay warn. replay.error_nested removed; replay.skipped_import/_replay added; ReplayCompleted carries warnings. requirements.md U3/U4 updated; app-command runtime-failure journalling tracked as a follow-up.

1659 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean.
2026-05-24 18:59:06 +00:00
claude@clouddev1 504c24c996 docs: ADR-0034 implementation plan (history journal + replay filter)
Two-sub-task test-first plan mapping 1:1 to ADR-0034's named sub-tasks: (1) journal failures + per-consumer filtering (status-tagged append, best-effort err writes, hydration reads all), (2) replay parses the journal format (ok-only filter, dual-shape input). Opens with a headline failing test that reproduces the live replay history.log bug.
2026-05-24 09:29:52 +00:00
claude@clouddev1 55b784597a docs: ADR-0033 Accepted (Phase 3 complete) + session handoff 34
Phase 3 of the ADR-0030 SQL surface (DML) is implemented and verified through sub-phase 3k; mark ADR-0033 Accepted in the ADR and the README index (index-upkeep rule). Add handoff 34 tracking the close-out and teeing up ADR-0034 (history journal + replay filter) as the next job.

1645 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean.
2026-05-24 09:23:03 +00:00
claude@clouddev1 380c4238ef test+docs: 3k Phase-3 verification sweep — e2e DML + filled cross-cut matrix
Sub-phase 3k of ADR-0033. Adds the Tier-3 end-to-end DML suite (tests/sql_dml_e2e.rs) and the cross-cut gap-fill tests, fills the verification matrix (every row a verified file::function), and produces the phase-exit report.

- tests/sql_dml_e2e.rs: INSERT…SELECT cross-table, all-ten-type multi-row INSERT + RETURNING type recovery, UPDATE-with-subquery-in-SET, cascade DELETE, UPSERT round-trip, RETURNING x3, history.log replay, OOS rejections (full §13 table), validity-indicator-from-SQL-DML.
- walker/mod.rs, highlight.rs, completion.rs, input_render.rs: inherited-diagnostic, DML-keyword highlight, INSERT INTO completion, and advanced-mode DML hint-panel cross-cuts.
- Matrix correction (user-confirmed): predicate warnings fire on row-scoped DML slots; INSERT VALUES has no row scope (ADR-0033 §8.4).
- Auto-snapshot row marked N/A (user-confirmed): ADR-0006 unimplemented for both paths; deferred.

/runda round: added an advanced-mode DML hint-panel test (A6 was attributed to simple-mode prose under the §8 advanced heading); extended OOS coverage to the full ADR-0033 §13 table (OOS-5 INDEXED BY / OOS-6 multi-statement) + a trailing-semicolon guard.

1645 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean.
2026-05-23 22:26:04 +00:00
claude@clouddev1 a5cdb00a86 docs: session handoff 33 — ADR-0033 sub-phase 3j + Amendment 3 + /runda round 2026-05-23 21:18:07 +00:00
claude@clouddev1 d5c7f63513 grammar+walker: 3j — shared insert/update/delete entry words (ADR-0033 §2 / Amendments 1 & 3)
Wire `insert`/`update`/`delete` as shared DSL/SQL entry words through the
category-grouped dispatcher (ADR-0033 Amendment 1): the Advanced SQL nodes
move off the dev words (`sqlinsert`/`sql_update`/`sql_delete`) to the real
keywords, registered alongside the Simple DSL nodes. Remove the dev-word
scaffold; collapse build_sql_{insert,update,delete} to source.trim();
de-duplicate the two REGISTRY entry-word listing sites.

Dispatch model (ADR-0033 Amendment 3, written this round):
- A command is the mode-rooted grammar-path outcome; identity is intrinsic.
  Advanced mode tries SQL first, falling back to the Simple DSL command when
  no SQL branch matches a token (`delete … --all-rows` falls back;
  `update … --all-rows` does not — the SET expression absorbs it, harmless
  since the engine treats `--all-rows` as a comment).
- Simple mode commits the DSL candidate for a shared word, surfacing the real
  DSL error; bare "this is SQL" is reserved for SQL-only entry words
  (`select`/`with`). A content rejection on the SQL candidate (internal
  table) is committed, never masked by the DSL fallback.

Combined DSL-error + advanced-SQL pointer (ADR-0033 Amendment 3): a Simple-mode
definite DSL error that would run as SQL in advanced mode gains the
`advanced_mode.also_valid_sql` suffix — in the live hint (ambient_hint_in_mode)
and on submit (dispatch_dsl), via the shared advanced_alternative_note — so the
actionable DSL fix and the mode pointer coexist (submit covers constructs that
surface only on submit, e.g. `delete … returning`).

Internal-table rejection symmetrised (/runda finding B, ADR-0030 §6): the DSL
data-command target slots (insert/update/delete/show data/show table) gained
reject_internal_table, so `__rdbms_*` tables are refused in Simple mode too —
previously only the advanced SQL grammar rejected them.

Mode-awareness: classify_input_with_schema_in_mode and
invalid_ident_at_cursor_in_mode stop leaking the advanced SQL view into
simple-mode hints for shared words.

Tests: dev-word inputs migrated to the real words (advanced); DSL grammar /
completion / phase-D / db tests parse in Simple mode (the DSL surface); replay
keeps its advanced-mode model (one stale assertion fixed); dispatcher routing,
combined-pointer, and internal-table tests added. Suite 1626 pass / 0 fail /
1 ignored; clippy --all-targets -D warnings clean.

Defer M4 (execution-time mode side-channel; tracked in requirements.md) to its
own ADR.
2026-05-23 21:13:39 +00:00
claude@clouddev1 c16196fc7f docs: session handoff 32 — Phase 3 (3f–3i) + ADR-0034 + /runda round 2026-05-23 07:23:02 +00:00
claude@clouddev1 8d17583fe0 walker: 3i /runda DA round — fix INSERT-target scope confusion (6 cases)
A focused adversarial round (/runda) found a single root cause with
six manifestations, all pre-existing latent false-positives: the
INSERT target is recorded under the `insert_target_table` role, not
as a diagnostic `bindings` entry, so refs that should resolve to the
*target* row were instead checked against the statement's bindings —
which for an `INSERT … SELECT` are the SELECT's *source* tables (the
wrong scope), producing false unknown_column / unknown_qualifier
diagnostics on valid input.

New helper bare_ref_insert_target re-scopes a ref onto the INSERT
target when it sits in a target-referencing region: the UPSERT
DO UPDATE action (byte range) or an INSERT's RETURNING list. Applied
across every ref form:

  1. INSERT column list (insert_column) — validated vs the target,
     skipped in the bare-column branch (was checked vs SELECT source).
  2. ON CONFLICT (col) target (conflict_target_column) — same.
  3. DO UPDATE SET RHS / WHERE bare refs — validated vs the target
     (also closes the #12 residual for VALUES upserts).
  4. RETURNING bare refs — validated vs the target.
  5. target-qualified refs `t.col` in DO UPDATE / RETURNING — the
     unified `excluded` / target-qualifier resolution in the
     qualified-ref None branch.
  6. target-qualified star `t.*` in RETURNING — same re-scoping in
     the qualified-star handler.

Each fix has a positive (resolves cleanly) and negative (genuinely
unknown column / unrelated qualifier still flagged) test; the
`excluded` leak guard and all prior diagnostics remain green.
1613 pass / 0 fail / 1 ignored. Clippy clean.
2026-05-22 22:23:15 +00:00
claude@clouddev1 4fa0aa06e9 db+walker: 3i DA pass — not_null PK false-positive fix + arity hardening
DA pass on 3i. Fix: build_schema_cache set not_null = c.notnull ||
c.primary_key, which would false-flag an omitted `int` PK as a
not_null_missing WARNING — but an int PK is an INTEGER PRIMARY KEY
rowid alias that auto-fills (and SQLite's PK-NULL quirk means a PK
isn't implicitly NOT NULL anyway). Use c.notnull alone (ADR-0033
§8.3 "declared NOT NULL"): faithful and false-positive-free.

Arity-walk hardening (same class as the ON CONFLICT regression the
existing tests caught mid-3i): RETURNING after VALUES is a depth-0
keyword that ends the tuple list (only the real tuple is flagged),
and a comma nested in a function-call value (depth ≥ 2) does not
inflate the tuple's value count.

Tests (+2). 1598 pass / 0 fail / 1 ignored. Clippy clean.
2026-05-22 22:06:04 +00:00
claude@clouddev1 cfd925c24a grammar+db: 3i — DML column-existence + cross-cut verification (ADR-0033 §8)
New dml_target_column_diagnostics pass: an ERROR for an unknown column
in the INSERT column list or the UPSERT DO UPDATE SET (validated
directly against the insert_target_table). The INSERT target isn't a
flat-scope `bindings` entry, so the existing schema-existence pass
didn't cover these; a targeted pass avoids the false INSERT…SELECT
ambiguity a global binding would cause.

Closes the 3i cross-cut "schema-existence fires on INSERT VALUES"
gate item, and closes the DA finding #12 (UPSERT DO UPDATE SET column
now flagged like a top-level UPDATE's SET column). Residual: bare
sql_expr_ident refs in the DO UPDATE SET RHS / WHERE remain
unvalidated for upserts (the documented flat-scope limitation).

Tests (+5): unknown INSERT column flagged + known silent; unknown
DO UPDATE SET column flagged + known/excluded silent; predicate
warning (= NULL) fires on a SQL UPDATE WHERE (cross-cut). 1596 pass /
0 fail / 1 ignored. Clippy clean.
2026-05-22 22:02:33 +00:00
claude@clouddev1 2d1112d0f3 grammar+db: 3i — not_null_missing diagnostic + TableColumn constraints (ADR-0033 §8.3)
Extend SchemaCache TableColumn with not_null + has_default (with a
TableColumn::new constructor for the common no-constraint case),
populated in build_schema_cache from ColumnDescription (a PK column
counts as not-null). New dml_not_null_missing_diagnostics pass: a
WARNING when a SQL INSERT's explicit column list omits a column that
is NOT NULL with no DEFAULT — advisory (the engine enforces it).
serial/shortid (auto-filled) and defaulted columns are excluded.
Anchored on the target-table ident (no token for the omitted column).

Catalog key diagnostic.not_null_missing (engine-neutral). Tests (+4):
fires on omitted required column; silent when included, when
defaulted, and for auto-gen serial/shortid. ~24 TableColumn literal
sites updated for the two new fields (build clean). 1591 pass / 0
fail / 1 ignored. Clippy clean.

All three ADR-0033 §8 DML diagnostics now implemented. Remaining 3i:
cross-cut verification + #12 UPSERT DO UPDATE validation.
2026-05-22 21:58:12 +00:00
claude@clouddev1 6db8253c25 grammar+db: 3i — insert_arity_mismatch diagnostic (ADR-0033 §8.1)
New dml_insert_arity_diagnostics pass (ERROR): when an explicit
(column_name_list) arity disagrees with a row's arity. VALUES tuples
are checked per-row (each offending tuple emits its own diagnostic on
its span; matched rows stay silent). INSERT … SELECT compares the
first SELECT leg's projection arity, anchored on the first projection
item; a WITH-prefixed row source is skipped (engine still reports it —
a false positive would be worse). No-column-list form deferred
(needs schema; outside the 3i gate).

The VALUES walk stops at the first depth-0 keyword so an ON CONFLICT
(col) conflict target / RETURNING tail is not mis-counted as a value
tuple (caught by the existing upsert_excluded tests during dev).

Catalog key diagnostic.insert_arity_mismatch (engine-neutral).
Tests (+7): single-row + matched + per-row multi-row; INSERT…SELECT
mismatch + matched; ON CONFLICT interaction (only the real tuple
flagged, clean case silent). 1587 pass / 0 fail / 1 ignored. Clippy
clean. Remaining 3i: not_null_missing (needs TableColumn
not_null+default), cross-cut verification, #12 UPSERT DO UPDATE
validation.
2026-05-22 21:50:09 +00:00
claude@clouddev1 be63315e61 grammar+db: 3i — auto_column_overridden diagnostic (ADR-0033 §8.2)
New dml_auto_column_diagnostics pass: a WARNING when a SQL INSERT's
explicit column list names a serial/shortid (auto-generated) column —
the explicit value bypasses the auto-counter/generator and may collide
with later auto-generated values. Advisory only (ADR-0027 §1); the
statement still runs. Conflict-target columns (distinct
conflict_target_column role) are not mistaken for inserted columns.

Catalog key diagnostic.auto_column_overridden (engine-neutral).
Tests (+4): serial + shortid fire; omitted is silent; ON CONFLICT
target not falsely flagged. 1580 pass / 0 fail / 1 ignored. Clippy
clean. Remaining 3i: insert_arity_mismatch, not_null_missing (needs
TableColumn not_null+default), cross-cut verification, #12 UPSERT
DO UPDATE validation.
2026-05-22 21:45:02 +00:00
claude@clouddev1 6b8888f105 grammar+db: 3h — UPSERT ON CONFLICT DO NOTHING / DO UPDATE (ADR-0033 §9)
on_conflict_clause on SQL_INSERT_SHAPE: optional (col,…) conflict
target (distinct conflict_target_column role so it never enters
listed_columns), DO NOTHING / DO UPDATE SET … [WHERE …]. `do` is
factored out of the action Choice so nothing/update disambiguate
without tripping the walk_seq/walk_choice shared-prefix trap
(ADR-0033 Amendment 1). Worker runs the UPSERT verbatim (SQLite
native); no new execution path.

build_sql_insert: row_source now stops before the FIRST trailing
clause — ON CONFLICT (3h) or RETURNING (3g) — and do_sql_insert's
shortid auto-fill rewrite re-appends the whole trailing tail, so an
auto-filled INSERT keeps its ON CONFLICT / RETURNING.

excluded pseudo-table (§9): resolves to the target's columns inside
the DO UPDATE action and completes at `excluded.|`, but stays flagged
as unknown_qualifier in VALUES / RETURNING / non-upsert statements.
Diagnostic pass scopes it by the DO UPDATE byte-range (update token →
RETURNING/end); completion resolves it against the INSERT target's
current_table_columns. NOTE: scoping uses byte-range rather than the
plan's prescribed from_scope TableBinding push — same behaviour, no
walker scope-frame change.

Tests (+13): grammar accept/reject; DO NOTHING / DO UPDATE-excluded /
no-target execution + persistence; auto-fill × ON CONFLICT with a
REAL unique conflict (proves the clause survives the rewrite, not a
no-op); excluded resolves in DO UPDATE SET + WHERE, flagged in VALUES
(incl. same statement), unknown column under excluded; excluded.|
completion; conflict-target not in listed_columns. 1576 pass / 0 fail
/ 1 ignored. Clippy clean. Dev sql_insert entry word still removed in
3j.

Known follow-up (tracked for 3i): UPSERT DO UPDATE bare column refs
(SET LHS / WHERE) are not schema-validated, unlike regular UPDATE —
the INSERT target isn't a diagnostic binding. Fits 3i's cross-cut
SET/WHERE validation scope.
2026-05-22 21:28:24 +00:00
claude@clouddev1 fd8b74ba5e grammar+db: 3g — RETURNING on INSERT/UPDATE/DELETE (ADR-0033 §5)
Shared RETURNING_CLAUSE (reuses Phase-2 PROJECTION_LIST, now
pub(crate)) as an optional tail on all three SQL DML shapes.
`returning: bool` on the Command variants, set by the ast-builders
and threaded to the worker. run_returning collects the returned rows
as a DataResult (RETURNING mutates + yields in one pass), reusing
resolve_select_column_types for bare-column type recovery; computed
projections stay typeless. DeleteResult gains a `data` field rendered
alongside the cascade summary.

Follow-set fix: `returning` is added to the table-source and
projection bare-alias follow-sets so an INSERT … SELECT row source
stops before RETURNING instead of reading it as a table alias.

Auto-fill × RETURNING: build_sql_insert stops row_source before the
RETURNING token (keeping it preparable for shortid materialisation),
and plan_shortid_autofill re-appends the RETURNING tail so generated
shortids surface in RETURNING *.

Tests (+17): grammar accept on all three; INSERT/UPDATE/DELETE
RETURNING incl. *, aliases, multi-row, type recovery + computed-
typeless; auto-fill × RETURNING (single + multi-row distinct ids);
INSERT…SELECT…RETURNING execution; UPDATE…RETURNING zero-match;
DELETE…RETURNING cascade+rows; app-level render of both. Dev
sql_insert/sql_update/sql_delete entry words still removed in 3j.
1562 pass / 0 fail / 1 ignored. Clippy clean.
2026-05-22 20:44:55 +00:00
claude@clouddev1 b935090d7b docs: ADR-0034 — history.log as a complete journal; replay reads ok-only
Found while implementing 3f: history.log is success-only, but the
in-memory Up/Down recall ring records every submission — and the ring
is re-seeded from the log on open, so failed commands are recallable
in-session yet lost across sessions. Replay and recall also want
different inputs (state-builders vs everything-typed), which one
success-only file can't serve. And replay never parsed the pipe
format (run_replay parses whole lines), so `replay history.log` fails
on line 1 with no test covering it.

Decision: history.log becomes a complete journal tagged ok/err;
hydration reads all, replay reads ok-only and learns the format.
Amends ADR-0006 + ADR-0015 §5/§12. Code deferred to two tracked
sub-tasks. No migration for existing all-ok logs.
2026-05-22 19:17:52 +00:00
claude@clouddev1 62f09bebc5 db: fix self-referential cascade over-count + SQL-delete render test
A self-referential ON DELETE CASCADE FK (e.g. T.ParentId -> T.id) is
returned by read_relationships_inbound as a child whose table IS the
delete target. The before/after row-count diff then includes the
directly-deleted rows (already in rows_affected), so deleting a chain
root reported 3 cascaded rows when only 2 were removed via the
self-reference.

Fix in both do_delete (DSL) and do_sql_delete (SQL): when the child
table equals the target, subtract rows_affected from the diff and
guard on the corrected count (a leaf delete no longer reports a
phantom 0-row self-cascade); the target's CSV is already queued, so a
self-ref child is not re-added to rewritten_tables. Pre-existing in
do_delete; surfaced by the 3f DA pass, fixed in both paths to keep
DSL/SQL parity. Behaviour: report only the rows removed via the
self-reference (user-confirmed).

Also adds an app-level render test for the SQL DELETE path
(handle_dsl_delete_success via CommandOutcome::Delete) — the shared
renderer's ok-summary + per-relationship cascade line were exercised
only through the DSL path before.

Test-first: self_referential_cascade_counts_only_cascaded_rows added
for both paths (asserted 2, failed at 3 before the fix). 1545 pass /
0 fail / 1 ignored. Clippy clean.
2026-05-22 19:17:43 +00:00
claude@clouddev1 2c86a1313e grammar+db: 3f — SQL DELETE + cascade summary (ADR-0033 §1/§7)
New src/dsl/grammar/sql_delete.rs (FROM <table> [WHERE] [;]),
Command::SqlDelete, Request::RunSqlDelete, do_sql_delete worker.

do_sql_delete mirrors the DSL do_delete: detect FK cascade by
before/after child row-count diffing, re-persist target + every
cascade-affected child, history-on-success inside the tx. Reuses
CommandOutcome::Delete -> handle_dsl_delete_success, so the
per-relationship cascade summary formatter is shared, not duplicated.

ADR-0033 Amendment 2: supersedes §7's WHERE-injected pre-count. Its
premise (DSL handler builds pre-counts from the typed Expr) was wrong
— do_delete uses count-diff. The pre-count would also have broken the
§2 parity promise by reporting SET NULL the DSL path doesn't. Count-
diff gives exact parity, no WHERE-byte extraction, and withdraws R2.
SET NULL reporting deferred for both paths (user-confirmed).

Tests: +6 grammar unit, +12 integration (cascade parity with DSL,
both R2 subquery cases, before-execute order, no-WHERE, FK-rejection
rollback, childless-parent, two-child cascade). 1542 pass / 0 fail /
1 ignored. Clippy clean. Dev sql_delete entry word removed in 3j.
2026-05-22 14:59:01 +00:00
claude@clouddev1 70ecf5535e docs: session handoff 31 — Phase 3 (3c/3d/3e) + interlude close 2026-05-22 14:23:25 +00:00
claude@clouddev1 53808ed9d7 grammar+db: 3e — SQL UPDATE grammar + execution (ADR-0033 §2)
New src/dsl/grammar/sql_update.rs: SQL_UPDATE_SHAPE =
<table> SET col = sql_expr (',' …)* [WHERE sql_expr] [';'], the
__rdbms_* target rejection, and the shared sql_expr on both the
assignment RHS and the predicate. No --all-rows rail — a SQL
UPDATE without WHERE runs as written (ADR-0030 §12). Reuses
sql_select::WHERE_CLAUSE (now pub(crate)) so the predicate
diagnostics are identical. The target uses the shared `table_name`
ident role (not a bespoke one) so the Phase-2 schema-existence and
predicate-warning passes collect it as a scope binding and check
the SET / WHERE columns for free — a bespoke role left them
unchecked (the cross-cut tests caught this).

Command::SqlUpdate { sql, target_table }; Request::RunSqlUpdate +
do_sql_update (execute validated SQL via execute_with_fk_enrichment,
re-persist the target CSV, append history.log). 3e surfaces the
affected-row count only; precise row output is RETURNING (3g), so
the update-success render skips a column-less data set rather than
showing a misleading "(no rows)" band. Behind the dev `sql_update`
entry word until 3j.

Tests: grammar accept/reject; integration (single/multi-col,
no-WHERE all-rows, sql_expr in SET, scalar subquery in SET,
zero-match success, history); walker cross-cut (unknown SET column
→ unknown_column, `= NULL` in WHERE → eq_null warning); app-level
render-guard both ways (column-less → count only; with columns →
table renders). 1524 green, clippy clean.
2026-05-22 13:57:21 +00:00
claude@clouddev1 18d34d0d36 db: 3d fix — don't let shortid auto-fill mask INSERT arity mismatch
plan_shortid_autofill read exactly listed_columns.len() cells from
the materialised row source. When the row source produced a
different column count than the user's list, the extra columns were
silently dropped (wider → wrong data, insert succeeded) or read
out of range (narrower). Guard: if the materialised statement's
column_count differs from the listed-column count, skip auto-fill
and execute the verbatim statement so the engine reports the
mismatch — matching the non-auto-fill path. A friendly pre-flight
diagnostic remains sub-phase 3i.

Tests: VALUES with too many values; INSERT…SELECT with a wider and
a narrower projection — each rejected with nothing persisted.
2026-05-22 12:30:57 +00:00
claude@clouddev1 78ad476d24 db+grammar: 3d — shortid auto-fill for SQL INSERT (ADR-0033 §6)
When an INSERT's column list omits one or more shortid columns,
the worker now fills them. Command::SqlInsert gains listed_columns
and row_source, captured in build_sql_insert from the matched path
(the row source is located by the first values/select/with Word
token, so a string literal like 'select' can't be mistaken for the
keyword). do_sql_insert calls plan_shortid_autofill, which — per
the user-confirmed Option B — materialises the row source by
running it as a query, generates a distinct shortid per row via the
existing generate_shortid_batch (deduped against stored values),
and reconstructs a parameterised multi-row INSERT over the listed
columns plus the omitted shortid columns. Uniform for VALUES and
INSERT…SELECT, and handles multiple omitted shortids in one row
(each gets its own batch). No explicit list, no omitted shortid, or
a zero-row source → execute verbatim (the 3b path). serial stays
engine-filled via rowid. history.log keeps the original line, never
the rewrite (§11).

Tests: VALUES single/multi-row distinct; explicit override
honoured; INSERT…SELECT distinct fills; combined serial(engine) +
shortid(worker); two shortids (PK + non-PK) both fill; one provided
+ one omitted; compound-PK shortid member; mixed-case column name
(ADR-0009 DA gate); original-source-in-history on the rewrite path.
Still behind the dev `sqlinsert` entry word (3j). 1503 green,
clippy clean.
2026-05-22 07:26:54 +00:00
claude@clouddev1 6ff9144c7a grammar: 3c — INSERT … SELECT row source (ADR-0033 §4)
Make the INSERT row source a Choice between the VALUES clause and
Subgrammar(&sql_select::SQL_SELECT_COMPOUND). SQL_SELECT_COMPOUND
is itself a Choice that admits a leading WITH, so a WITH-prefixed
SELECT row source (R4) parses through it for free; the two
branches start on disjoint keywords (values vs select/with) so the
Choice never ambiguously commits. No worker change — do_sql_insert
already executes the validated SQL and re-persists, and the engine
handles insert-from-query.

Tests: grammar accept (plain / column-list+projection / WITH-
prefixed / trailing-semi) and reject (__rdbms_* on the SELECT's
FROM slot, incomplete select); integration parse-path lowering +
worker round-trip (rows land, CSV re-persisted) incl. R4 WITH end-
to-end; walker cross-cut that the Phase-2 unknown_column diagnostic
fires on the INSERT…SELECT projection; DA-gate test that a self-
sourced INSERT…SELECT runs as a plain insert (no cascade summary —
that is DELETE-only). Still behind the dev `sqlinsert` entry word
(shared `insert` is 3j). 1493 tests green, clippy clean.
2026-05-21 22:08:25 +00:00
claude@clouddev1 7f68a53f86 walker+completion: surface list trailing-optionals + identifiers-first ordering (ADR-0022 Amendment 2)
walk_repeated discarded the last matched item's trailing-optional
expectations at a clean item boundary, so a comma-separated list
offered no continuation after a complete item: `order by Name `
gave no asc/desc, `select Name ` no `as`, `create table …
Code(text) ` no not/unique/default/check. Capture the last item's
skipped set and surface it when the list ends at an item boundary
(the separator `,` itself is deliberately not surfaced).

That fix made expression-position candidate lists long, which
exposed a visibility problem: the hint panel's candidate line is
single-row and window-scrolls on overflow, centring on item 0 when
nothing is selected — so with keywords-first, schema identifiers
scrolled off behind the `>` marker. Reverse the ordering: schema
identifiers (table/column/relationship names) now sort before
keywords, since a name the user would have to look up is the
highest-value completion and must stay visible (keywords are
learned over time; the tok_identifier/tok_keyword colour split
marks the boundary). This reverses the handoff-14 keywords-first
call, now recorded in ADR-0022 Amendment 2.

Tests: walker expected-set + completion-layer regressions for the
trailing-optionals and the ordering; candidate_ordering.rs header
invariant inverted; ~20 typing-surface snapshots re-baselined; a
two-line hint box recorded as a deferred follow-up.
2026-05-21 21:52:49 +00:00
claude@clouddev1 43c49f4d1b walker: F5 — drop preceding-clause keywords from committed-child Incomplete sets
walk_seq's Incomplete arm unconditionally merged the accumulated
skipped-Optional expectations (pending_skipped) into the child's
expected set. When a child committed terminals before going
Incomplete (e.g. `order by` consumed, now awaiting a sort item),
this leaked ~13 clause keywords from clauses positioned *before*
the committed child — WHERE/GROUP BY/HAVING, the FROM's JOIN
options, set-ops — into the ORDER BY completion list, shoving the
actual columns off-screen.

Merge pending_skipped only when the Incomplete-producing child
consumed nothing (path length unchanged): the cursor still sits at
the optional boundary, so those optionals are genuine alternatives.
A committed child means the cursor is past them.

Tests: walker expected-set guard (+ over-correction guard) and a
full-stack completion-layer regression test.
2026-05-21 20:52:20 +00:00
claude@clouddev1 5b88ccd2c3 docs: session handoff 30 — Phase 3 (3a/3b) + advanced-mode completion interlude
3a (grouping dispatch / ADR-0033 Amendment 1) and 3b (SQL INSERT) done;
advanced-mode ambient assistance re-enabled (ADR-0022 Amendment 1) plus
F1/F2 completion fixes. F3/F4/case-sensitivity triaged as non-issues;
F5 (walk_seq keyword pollution) remains and needs user OK. Old-project
migration deferred. Resume at F5, then Phase 3 sub-phase 3c.
2026-05-21 20:32:37 +00:00
claude@clouddev1 1c8cbc1983 completion+hint: F1/F2 advanced-mode completion fixes
F1: the hint panel is the completion UI, so a premature "no such table/
column" ERROR on the token the user is still typing must not shadow its
completion. ambient_hint now suppresses an under-cursor error diagnostic
when a completion exists for the (non-empty) partial it overlaps, and
falls through to the candidates. Genuinely-unknown names (no prefix match)
still show the error; WARNINGs are unaffected. Both modes.

F2: projection-before-FROM ("select <cursor> from T" after deleting *)
offered the global column list instead of T's columns, because the §10.6
look-ahead's full-input walk can't reach FROM through an empty projection.
When the look-ahead finds no scope, retry with a neutral placeholder
inserted at the cursor so the trailing FROM/CTE scope is recovered for
narrowing. Only the repaired walk's from_scope/cte_bindings are used.

Test-first: 3 F1 tests (mid-typed completes, unknown still errors, simple-
mode DSL) + 1 F2 multi-table narrowing test. 1469 baseline green.
2026-05-21 20:25:16 +00:00
claude@clouddev1 ed40445828 ui: re-enable advanced-mode ambient assistance (ADR-0022 Amendment 1)
Advanced-mode hinting + completion-preview were dead: render_hint_panel
returned None for advanced mode (stale ADR-0022 §12 gate, predating the
SQL grammar) and the hint resolver/ambient_hint never threaded Mode, so a
SQL statement was gated as "this is SQL". The unified walker (ADR-0030/
0031/0032) speaks SQL, so this lifts the gate.

- ambient_hint_in_mode + hint_resolution_at_input_in_mode +
  expected_for_hint_snapshot(mode); candidate/diagnostic/parse sub-calls
  run in the active mode.
- render_hint_panel calls ambient for all modes; one-shot `:` sigil
  stripped (strip_one_shot_prefix) so `: sel` hints `select`.
- ADR-0022 Amendment 1 + README index.

Found by manual advanced-mode testing; Phase 2 marked SQL hint/completion
green at the engine layer but never exercised the UI. App-level render
test (advanced_mode_hint_panel_surfaces_sql_candidates) + ambient-layer
regression locks. 1466 baseline green.
2026-05-21 19:18:27 +00:00
claude@clouddev1 c87363168f grammar+db: 3b — SQL INSERT grammar + minimal execution (ADR-0033 §1)
SQL_INSERT_SHAPE (INTO <table> [(cols)] VALUES tuple(s)) with __rdbms_*
target rejection; Command::SqlInsert{sql,target_table}; Request::RunSqlInsert
+ do_sql_insert worker (tx-guarded: execute, then finalize_persistence for
CSV + history before commit, so failures roll back and don't re-persist).
Auto-show is best-effort via last_insert_rowid range.

Isolated behind a dev `sqlinsert` entry word (Advanced) so the SQL path is
testable without making `insert` a shared word yet (that's 3j, after 3d
auto-fill parity). Command::SqlInsert carries only sql+target_table; the
plan's listed_columns/returning land in 3d/3g where they're read.

6 grammar accept/reject tests + 8 integration tests (single/multi-row,
column-list, full-arity, history, rollback-on-failure, multi-row atomicity,
parse-path reconstruction, internal-table rejection). 1452 baseline green.
2026-05-21 18:51:21 +00:00
claude@clouddev1 4e16d97fe0 walker: 3a — category-grouped mode-aware dispatch (ADR-0033 Amendment 1)
Replaces ADR-0033 §2's original Node::Guard + Choice(SQL,DSL) mechanism,
which was found during 3a to be unworkable: any guard-in-Choice approach
forces a walk_choice change (walk_choice falls through only on NoMatch, so
simple-mode valid-DSL would wrongly surface "this is SQL"), and walk_seq
treats a NoMatch past idx 0 as a hard Failed, breaking advanced-mode DSL
fall-through.

Mechanism (Amendment 1): each REGISTRY entry is tagged
CommandCategory::{Simple, Advanced}, generalising the whole-command
is_advanced_only gate. walk() becomes a thin dispatcher over decide()
(mode-aware candidate selection: simple commits the DSL node or emits the
"this is SQL" hint; advanced tries SQL first, DSL as a full-line fallback)
and an extracted walk_one_command(); speculative match-testing runs on a
scratch WalkContext so the caller's context is only touched by the
committed walk. No Node::Guard, no walk_choice/walk_seq change.

6 dispatch smoke tests on a shared-entry-word smoke registry; 1446 baseline
green; clippy clean.
2026-05-21 18:18:50 +00:00
claude@clouddev1 a37a0b7d40 docs: ADR-0033 Phase 3 — implementation plan + cross-cut matrix
Build-order plan (sub-phases 3a–3k) with per-sub-phase scope, exit
gates, and written DA gates, modelled on
docs/plans/20260520-adr-0032-phase-2.md. Centrepiece is the cross-cut
verification matrix scaffold (~75 rows) grouped by ADR-0033 section
(statement shapes, dispatch, RETURNING, shortid, cascade, diagnostics,
UPSERT, inherited Phase-2 diagnostics, ambient assistance,
engine-neutrality, persistence, OOS rejections), to be filled in
during 3k.

Carries the handoff-29 §4 process pins into the relevant sections: DA
critiques listed before verdict, no silent out-of-scope classification,
and matrix attribution requiring SQL-input tests for SQL claims. Records
four open questions to escalate before code starts (shortid SELECT
row-source path, R1 mechanism fallback, cascade pre-count construction,
UPSERT catalog wording).
2026-05-21 06:58:15 +00:00
claude@clouddev1 ded1ca8e26 docs: session handoff 29 — Phase 3 planning done; sub-phase 3a is next
Twenty-ninth handover. This session drafted ADR-0033 as the
planning artifact for ADR-0030 Phase 3 (SQL DML in Advanced mode).

Documents the ten settled design decisions (Q1-Q10), the dispatch
architecture (SQL-first / DSL-fallback via the new Node::Guard
mechanism), the eleven phased sub-phases (3a-3k) each with their
exit gates, and the open implementation risks (R1-R4).

Points the next session at sub-phase 3a (Node::Guard scaffolding)
as the concrete entry point, with the plan-doc cross-cut matrix
as the immediate prerequisite task. Pins four process lessons
from the Phase-2 session (DA rubber-stamp risk, defer-trap
reflexes, tests-first on gap closure, matrix attribution
verification).

State: 1446 / 0 / 1 passing (unchanged — planning-only session).
Clippy clean.
2026-05-20 22:37:08 +00:00
claude@clouddev1 555149da3c docs: ADR-0033 — SQL DML grammar (INSERT / UPDATE / DELETE)
Phase 3 of ADR-0030's SQL-surface roadmap. Status: Proposed.

Statement shapes (§1): single- and multi-row INSERT, INSERT…SELECT
(recursing through ADR-0032's SQL_SELECT_COMPOUND), UPDATE with
SET assignment list, DELETE, all three optionally followed by
RETURNING projection_list. Full UPSERT (ON CONFLICT … DO NOTHING /
DO UPDATE with the SQLite/PostgreSQL `excluded` pseudo-table)
on INSERT.

Dispatch (§2): SQL-first / DSL-fallback in Advanced mode via
Choice(SQL_shape, DSL_shape) per shared entry word. Requires a
new walker capability — Node::Guard(fn), a zero-byte-consumption
gating node — landed as the first sub-phase's work (R1 mitigation
budgeted).

Execution (§10): three typed Command variants (SqlInsert /
SqlUpdate / SqlDelete) carrying target_table, listed_columns,
and a returning: bool flag. Worker handlers know per-kind
specialisations: shortid auto-fill (§6, parity with DSL),
cascade summary (§7, WHERE byte-range injection into pre-count
subqueries), DataResult routing on RETURNING (§5).

Diagnostics (§8): three new keys (insert_arity_mismatch ERROR,
auto_column_overridden WARNING, not_null_missing WARNING) with
positive + negative test requirements.

OOS list (§13): DEFAULT VALUES (seed feature), SQLite OR-prefixes,
UPDATE FROM, WITH-prefixed DML, indexed-by hints,
multi-statement batches.

Implementation notes: eleven phased sub-phases (3a–3k) each with
explicit exit gates + written DA gates. Ordering puts Node::Guard
scaffolding (3a) FIRST so the dispatch mechanism is proven before
DML grammar lands on top.

Initial DA review (Initial DA review section) recorded seven
critiques that were resolved before status moved to Proposed; a
second-pass DA surfaced an eighth (Node::Guard wasn't an
existing walker capability) and added it to §2 + sub-phase 3a's
scope.
2026-05-20 22:35:34 +00:00