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:
@@ -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, 4b–4i**,
|
||||
§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 (**4b–4i**, §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.**
|
||||
|
||||
Reference in New Issue
Block a user