feat: ADR-0035 4a.3 — table-level / multi-column CHECK

Add table-level CHECK (e.g. `CREATE TABLE t (a int, b int, CHECK (a < b))`)
to advanced-mode SQL CREATE TABLE. Since SQLite exposes no PRAGMA for CHECK
constraints, a table-level CHECK cannot be read back from the engine and
becomes the source of truth in a new internal metadata table
`__rdbms_playground_table_checks (table_name, seq, check_expr)`.

- Grammar: new TABLE_CHECK element in ELEMENT_CHOICES.
- Builder: distinguishes a table-level CHECK from a column-level one by
  element position (no column-def open in the element), using depth-aware
  boundary tracking so a length-arg comma (`numeric(10,2)`) or a
  table-PRIMARY KEY's inner comma is not mistaken for an element separator.
- Worker: do_create_table emits the CHECK clauses and writes the metadata
  rows in its transaction; schema_to_ddl emits them identically on rebuild;
  read_schema / read_schema_snapshot read them from the metadata table;
  do_drop_table clears them.
- Persistence: TableSchema.check_constraints round-trips through project.yaml
  (#[serde(default)], optional on read), mirroring unique_constraints.
- Composite UNIQUE deliberately stays PRAGMA-detected (engine-reportable,
  unlike CHECK) — user-confirmed.

DA/runda round added cross-cutting tests and a forward-looking doc fix:
- table CHECK survives a rebuild triggered by `add column`, and a later
  rebuild_from_text (the ADR-0013 rebuild primitive uses a raw DROP, so the
  metadata rows keyed on the final name are preserved);
- dropping a column a table CHECK references fails cleanly (rollback, table
  intact); detection is 4e, friendly wording is H1;
- dropping a table clears its CHECK metadata (no orphan rows on re-create);
- amended ADR §6 so 4h's RENAME also updates the new metadata table.

20 Tier-3 + 9 grammar/builder + 2 YAML tests. Docs: ADR-0035 Status/§13/§6,
README index, requirements.md Q1. Help/usage skeleton + describe display of
table-level constraints deferred to 4i (symmetric with 4a.2).

Tests: 1769 passing, 0 failing, 1 ignored. Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-25 14:06:52 +00:00
parent 1991fb4fc7
commit 60111f69d5
12 changed files with 899 additions and 39 deletions
+34 -19
View File
@@ -3,10 +3,12 @@
## Status
Accepted. Design agreed with the user (2026-05-24); the approach is
**validated end-to-end by sub-phase 4a** (`CREATE TABLE`, implemented
2026-05-25 — plan `docs/plans/20260524-adr-0035-sql-ddl-4a.md`), so the
decision is accepted while the remaining sub-phases (**4a.2, 4b4i**,
§13) continue. This is **Phase 4** of the ADR-0030 roadmap (the
**validated end-to-end by sub-phases 4a / 4a.2 / 4a.3** (`CREATE TABLE`
with column- and table-level constraints, implemented 2026-05-25 —
plans `docs/plans/20260524-adr-0035-sql-ddl-4a.md`,
`…-4a2.md`, `…-4a3.md`), so the decision is accepted while the remaining
sub-phases (**4b4i**, §13) continue. This is **Phase 4** of the
ADR-0030 roadmap (the
advanced-mode SQL surface), the peer of ADR-0031 (expression grammar),
ADR-0032 (`SELECT`), and ADR-0033 (DML). It **clarifies ADR-0030 §4**
on how DDL is represented and executed.
@@ -201,10 +203,12 @@ is no simple-mode rename-table verb. It needs a genuinely new
low-level operation (none exists today): within one transaction,
rename the table in the database, rename its `data/<table>.csv` file,
and update every metadata row that names it — the column-metadata
rows, and **both ends of any relationship** in
`__rdbms_playground_relationships` that references the old name. Name
validation and `__rdbms_*` rejection apply to the target. This closes
the rename half of `C1` for the advanced surface.
rows, **both ends of any relationship** in
`__rdbms_playground_relationships` that references the old name, and
**the table-level CHECK rows** in `__rdbms_playground_table_checks`
(added in 4a.3; keyed by `table_name`). Name validation and
`__rdbms_*` rejection apply to the target. This closes the rename half
of `C1` for the advanced surface.
### 7. Column type conversion — one engine, mode-appropriate policy
@@ -332,23 +336,34 @@ ADR-0033's structure:
field, detected on read via the UNIQUE-constraint index
(`PRAGMA index_list` origin `u`), round-tripped through YAML, with
save/load/rebuild tests.
- **4a.3 — Table-level / multi-column `CHECK(…)`.** Split from 4a.2
(2026-05-25, user-confirmed) because SQLite exposes **no PRAGMA for
CHECK constraints**, so a table-level CHECK cannot be read back from
the engine and needs a **new `__rdbms_*` metadata table** as its
source of truth (the ADR-0012/0013 pattern) — a distinct
architectural step. Until 4a.2/4a.3 land, 4a rejects these forms
"not yet supported". (The general rule: a DDL feature needs new
model/execution work only when it introduces a structure simple mode
could never produce, or an expression the structural helper cannot
consume — cf. the `UNIQUE`-index flag in 4d and the rename op in 4h.)
- **4a.3 — Table-level / multi-column `CHECK(…)`.** *(Implemented
2026-05-25 — plan `docs/plans/20260525-adr-0035-sql-ddl-4a3.md`.)*
Split from 4a.2 (2026-05-25, user-confirmed) because SQLite exposes
**no PRAGMA for CHECK constraints**, so a table-level CHECK cannot be
read back from the engine and needs a **new `__rdbms_*` metadata
table** as its source of truth (the ADR-0012/0013 pattern) — a
distinct architectural step. Landed as
`__rdbms_playground_table_checks (table_name, seq, check_expr)`; the
builder distinguishes a table-level CHECK from a column-level one by
element position (no column-def open). Composite `UNIQUE` deliberately
stays PRAGMA-detected (engine-reportable, unlike CHECK). (The general
rule: a DDL feature needs new model/execution work only when it
introduces a structure simple mode could never produce, or an
expression the structural helper cannot consume — cf. the
`UNIQUE`-index flag in 4d and the rename op in 4h.)
- **4b — Foreign keys in `CREATE TABLE`.** Inline `REFERENCES` +
table-level `FOREIGN KEY` → relationship metadata, one undo step.
- **4c — `DROP TABLE [IF EXISTS]`** → `SqlDropTable` (cascade parity;
`IF EXISTS` no-op-with-note, §4).
- **4d — `CREATE [UNIQUE] INDEX` / `DROP INDEX`** → `SqlCreateIndex`
/ `SqlDropIndex` (ADR-0025; the `UNIQUE` flag extension if needed).
- **4e — `ALTER TABLE` add/drop/rename column.**
- **4e — `ALTER TABLE` add/drop/rename column.** Drop/rename column
must guard against a **table-level CHECK that references the column**
(4a.3): today the rebuild rejects it cleanly via the engine (the
rebuilt table's `CHECK` names a missing column), leaving the table
intact — 4e adds the up-front detection (parsing column references
out of the raw CHECK text in `__rdbms_playground_table_checks`) so the
refusal is deliberate; the friendly wording itself is **H1** work.
- **4f — `ALTER TABLE … ALTER COLUMN TYPE`** (the §7 conversion
model + the lossy-with-note path).
- **4g — `ALTER TABLE` add/drop constraint, add foreign key.**
+1 -1
View File
File diff suppressed because one or more lines are too long