constraints: CHECK — check (<expr>) at create table & add column (ADR-0029)
The fourth constraint. `check ( <expr> )` reuses the ADR-0026 WHERE-expression grammar via `Subgrammar`, so a check is written in the same language as a `where` filter. - Grammar: a `CHECK_CONSTRAINT` arm joins the shared constraint-suffix Choice; `consume_check_expr` extracts the parenthesised expression (paren-depth aware) into `ColumnSpec.check` / `Command::AddColumn.check`. - Storage: the parsed `Expr` is compiled once to inline SQL (`compile_check_sql` — `compile_expr` + ADR-0028's param-inliner) and stored in that form everywhere — a new `check_expr` column in `__rdbms_playground_columns`, `project.yaml`'s `ColumnSchema.check`, and the column DDL emitted by `do_create_table` / `schema_to_ddl`. - `add column … check` routes through the rebuild primitive (SQLite's `ALTER … ADD COLUMN` cannot carry it); a CHECK on a serial/shortid column is create-table-only and refused at add-column with a friendly message. - `describe` surfaces the CHECK. ADR-0029 §7/§8 updated to the SQL-form decision — double-quoted identifiers, consistent with ADR-0028's `explain` display SQL. 1201 tests pass (+8); clippy clean.
This commit is contained in:
@@ -80,7 +80,7 @@ A column spec gains an optional, repeatable constraint suffix
|
||||
**after** the `(type)` group:
|
||||
|
||||
```
|
||||
create table Books with pk isbn(text) check (length(isbn) = 13)
|
||||
create table Books with pk isbn(text) check (isbn like '978%')
|
||||
|
||||
add column to Books: title (text) not null
|
||||
add column to Books: stock (int) default 0 check (stock >= 0)
|
||||
@@ -283,28 +283,31 @@ the data.
|
||||
constraints must round-trip through `project.yaml` or they
|
||||
vanish on `rebuild` / `export` / `import`.
|
||||
|
||||
The `CHECK` expression is stored — everywhere — as the
|
||||
**compiled SQL** form: the parsed `Expr` run through
|
||||
`compile_expr` with literals inlined (§4). Identifiers are
|
||||
double-quoted, exactly as ADR-0028's `explain` display SQL
|
||||
already renders them, so the form is consistent with what the
|
||||
learner already meets. (The original plan stored canonical
|
||||
*DSL text*; that needs both an `Expr`→text renderer and a
|
||||
text→`Expr` re-parser for the round-trip, while the rebuild
|
||||
path needs the SQL form regardless — storing SQL once removes
|
||||
both.)
|
||||
|
||||
- **`project.yaml`** — the `ColumnSchema` record gains
|
||||
`not_null: bool`, `default: Option<Value>`, and
|
||||
`not_null: bool`, `default: Option<String>`, and
|
||||
`check: Option<String>`. (`unique: bool` already exists,
|
||||
from ADR-0018's `serial` / `shortid` contract.) The `check`
|
||||
expression is stored as its **canonical DSL text** — see the
|
||||
`Expr` text renderer below.
|
||||
from ADR-0018's `serial` / `shortid` contract.) `check`
|
||||
holds the compiled SQL.
|
||||
- **Metadata table** — `NOT NULL`, `UNIQUE`, and `DEFAULT` are
|
||||
all recoverable from SQLite itself (`pragma_table_info`'s
|
||||
`notnull` and `dflt_value`; `pragma_index_list` origin `u`),
|
||||
so they need no metadata row. `CHECK` is *not* exposed by
|
||||
any pragma — only by the raw `sqlite_master` SQL, which is
|
||||
in engine syntax. So `__rdbms_playground_columns` carries a
|
||||
nullable `check_expr TEXT` column holding the canonical DSL
|
||||
text, keeping `describe` independent of engine-syntax
|
||||
parsing. It is part of the internal table's `CREATE TABLE`
|
||||
definition — there are no existing databases to migrate.
|
||||
- **`Expr` → DSL text renderer** — a new `render_expr` (small;
|
||||
the `Expr` tree from ADR-0026 is shallow) produces canonical
|
||||
DSL text for an expression. One renderer, three consumers:
|
||||
`project.yaml` serialization, the `check_expr` metadata
|
||||
column, and the structure view (§8). The check round-trips
|
||||
text → `Expr` (re-parsed on load) → text.
|
||||
any pragma. So `__rdbms_playground_columns` carries a
|
||||
nullable `check_expr TEXT` column holding the compiled SQL,
|
||||
which `schema_to_ddl` and `describe` echo verbatim. It is
|
||||
part of the internal table's `CREATE TABLE` definition —
|
||||
there are no existing databases to migrate.
|
||||
|
||||
### 8. Structure rendering
|
||||
|
||||
@@ -315,17 +318,17 @@ Option<String>`, and `check: Option<String>`.
|
||||
every constraint a column carries:
|
||||
|
||||
```
|
||||
┌───────┬──────┬───────────────────────────────────┐
|
||||
│ Name │ Type │ Constraints │
|
||||
├───────┼──────┼───────────────────────────────────┤
|
||||
┌───────┬────────┬─────────────────────────────────┐
|
||||
│ Name │ Type │ Constraints │
|
||||
├───────┼────────┼─────────────────────────────────┤
|
||||
│ id │ serial │ PK │
|
||||
│ email │ text │ NOT NULL, UNIQUE │
|
||||
│ age │ int │ DEFAULT 18, CHECK age >= 0 │
|
||||
└───────┴──────┴───────────────────────────────────┘
|
||||
│ email │ text │ NOT NULL, UNIQUE │
|
||||
│ age │ int │ DEFAULT 18, CHECK ("age" >= 0) │
|
||||
└───────┴────────┴─────────────────────────────────┘
|
||||
```
|
||||
|
||||
The `CHECK` expression renders in DSL form (`render_expr`),
|
||||
not engine SQL.
|
||||
The `CHECK` renders in its compiled-SQL form (§7) — the same
|
||||
double-quoted-identifier style as ADR-0028's `explain` SQL.
|
||||
|
||||
### 9. PK columns — redundant and impossible constraints
|
||||
|
||||
@@ -409,10 +412,7 @@ additions:
|
||||
tables go through the ADR-0016 pretty-table renderer.
|
||||
- The internal metadata table `__rdbms_playground_columns`
|
||||
carries a new `check_expr` column — its first change since
|
||||
ADR-0012.
|
||||
- A new `Expr` → DSL-text renderer (`render_expr`) is added;
|
||||
it is also reusable by any future feature that needs to
|
||||
show an expression back to the user.
|
||||
ADR-0012 — holding the compiled-SQL form of the `CHECK`.
|
||||
- `project.yaml`'s `ColumnSchema` grows three fields; the
|
||||
format stays backward-compatible (the new keys default to
|
||||
"absent" — `not_null: false`, no `default`, no `check`).
|
||||
@@ -433,8 +433,8 @@ A sensible build order, each step test-guarded:
|
||||
honour it, including the §6 `add column` rules. The
|
||||
`Expr` → SQL compile for `CHECK`.
|
||||
3. **Storage round-trip.** `ColumnSchema` fields, the
|
||||
`check_expr` metadata column, `render_expr`, and the
|
||||
`project.yaml` read/write paths.
|
||||
`check_expr` metadata column, and the `project.yaml`
|
||||
read/write paths.
|
||||
4. **`add constraint …` / `drop constraint …`.** The two
|
||||
commands, the rebuild-table path, and the §5 dry-run with
|
||||
its pretty-table refusals.
|
||||
|
||||
@@ -48,3 +48,8 @@ entry names the ADR that drew the boundary.
|
||||
added afterward with `add column`. Creating a table with a
|
||||
mix of PK and non-PK columns in one statement needs
|
||||
advanced-mode `CREATE TABLE` syntax.
|
||||
- **`check (<expr>)` constraints reuse the WHERE-expression
|
||||
grammar** (ADR-0026), so the same limits apply: no scalar
|
||||
functions (`length(x)`), no arithmetic. A check is a boolean
|
||||
combination of column-vs-literal comparisons, `LIKE`, `IN`,
|
||||
`BETWEEN`, and `IS NULL`.
|
||||
|
||||
Reference in New Issue
Block a user