db: column-constraint infrastructure — NOT NULL / UNIQUE / DEFAULT (ADR-0029)

The database layer now honours the ColumnSpec constraint
fields end to end, ahead of the grammar that lets users type
them.

- `do_create_table` emits ` NOT NULL` / ` UNIQUE` / ` DEFAULT
  <literal>` per column via the new `column_constraints_sql`
  helper (the default literal bound against the column's type).
- `ReadColumn` gains `default_sql`, read from
  `pragma_table_info.dflt_value`; `schema_to_ddl` emits it, so
  the rebuild-table primitive preserves DEFAULT — it already
  preserved NOT NULL / UNIQUE.
- `ColumnDescription` gains `unique` / `default`;
  `do_describe_table` now sources columns from `read_schema`
  (one source of per-column truth) and `constraints_display`
  lists PK / NOT NULL / UNIQUE / DEFAULT.

No user-facing change yet — no grammar produces constrained
columns. Tests exercise creation, enforcement, describe, and
rebuild-preservation programmatically.

1177 tests pass (+5); clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-19 14:18:45 +00:00
parent eff2ee8d14
commit a60e879f20
5 changed files with 281 additions and 45 deletions
+15 -3
View File
@@ -354,12 +354,20 @@ fn type_display(c: &ColumnDescription) -> String {
}
fn constraints_display(c: &ColumnDescription) -> String {
let mut parts: Vec<&str> = Vec::new();
let mut parts: Vec<String> = Vec::new();
if c.primary_key {
parts.push("PK");
parts.push("PK".to_string());
}
if c.notnull {
parts.push("NOT NULL");
parts.push("NOT NULL".to_string());
}
// ADR-0029: a PK column's implicit uniqueness is already
// conveyed by `PK`; `unique` is only set for non-PK columns.
if c.unique {
parts.push("UNIQUE".to_string());
}
if let Some(default) = &c.default {
parts.push(format!("DEFAULT {default}"));
}
parts.join(", ")
}
@@ -511,6 +519,8 @@ mod tests {
},
notnull,
primary_key: pk,
unique: false,
default: None,
}
}
@@ -978,6 +988,8 @@ mod tests {
sqlite_type: "INTEGER".to_string(),
notnull: false,
primary_key: false,
unique: false,
default: None,
}],
outbound_relationships: Vec::new(),
inbound_relationships: Vec::new(),