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
+6
View File
@@ -250,6 +250,8 @@ fn fake_table(name: &str, columns: &[(&str, Type, bool)]) -> TableDescription {
sqlite_type: t.sqlite_strict_type().to_string(),
notnull: false,
primary_key: *pk,
unique: false,
default: None,
})
.collect(),
outbound_relationships: Vec::new(),
@@ -415,6 +417,8 @@ fn add_relationship_flow_shows_parent_side_with_inbound_section() {
sqlite_type: "INTEGER".to_string(),
notnull: false,
primary_key: true,
unique: false,
default: None,
}],
outbound_relationships: Vec::new(),
inbound_relationships: vec![RelationshipEnd {
@@ -464,6 +468,8 @@ fn add_relationship_flow_shows_inbound_section_on_parent() {
sqlite_type: "INTEGER".to_string(),
notnull: false,
primary_key: true,
unique: false,
default: None,
}],
outbound_relationships: Vec::new(),
inbound_relationships: vec![RelationshipEnd {