constraints: add constraint / drop constraint on existing columns (ADR-0029 §2.2)
Adds the two commands for modifying a column's constraints after creation, completing ADR-0029's §2.2 surface. Grammar (dsl/grammar/ddl.rs): `add constraint <constraint> to <T>.<col>` reuses the §2.1 COLUMN_CONSTRAINT choice; `drop constraint <kind> from <T>.<col>` names only the kind. Both join the `add` / `drop` choices, discriminated by the `constraint` form word. AST (dsl/command.rs): `Command::AddConstraint` / `DropConstraint` plus the `Constraint` / `ConstraintKind` enums. Worker (db.rs): `do_add_constraint` / `do_drop_constraint` apply the change through the rebuild-table primitive. `add` runs the §5 dry-run first — `not null` / `unique` / `check` against a populated column are refused, before any write, with a pretty-printed table of offending rows. §9 redundant-on-PK declarations and §6 `default` on an auto-generated column are friendly refusals; dropping a constraint the column does not carry is likewise refused. Also fixes schema_to_ddl, which suppressed UNIQUE for every PK column — a compound-PK member is not individually unique, so an explicit UNIQUE on it must survive the rebuild. 23 tests added (6 grammar, 17 worker); 3 completion-test and 3 matrix snapshots updated for the new `constraint` subcommand.
This commit is contained in:
@@ -53,6 +53,60 @@ impl ColumnSpec {
|
||||
}
|
||||
}
|
||||
|
||||
/// A column-level constraint with its payload (ADR-0029 §3).
|
||||
///
|
||||
/// Produced by `add constraint <constraint> to <T>.<col>`.
|
||||
/// `Default` / `Check` carry the value / expression; `NotNull`
|
||||
/// and `Unique` are payload-free.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Constraint {
|
||||
NotNull,
|
||||
Unique,
|
||||
Default(Value),
|
||||
Check(Expr),
|
||||
}
|
||||
|
||||
impl Constraint {
|
||||
/// The bare constraint kind, dropping any payload — used for
|
||||
/// the `[ok]` summary line and log output.
|
||||
#[must_use]
|
||||
pub const fn kind(&self) -> ConstraintKind {
|
||||
match self {
|
||||
Self::NotNull => ConstraintKind::NotNull,
|
||||
Self::Unique => ConstraintKind::Unique,
|
||||
Self::Default(_) => ConstraintKind::Default,
|
||||
Self::Check(_) => ConstraintKind::Check,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The kind of a column-level constraint, without a payload.
|
||||
///
|
||||
/// Produced by `drop constraint <kind> from <T>.<col>`
|
||||
/// (ADR-0029 §3) — naming the kind is enough, since at most one
|
||||
/// constraint of each kind exists per column.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ConstraintKind {
|
||||
NotNull,
|
||||
Unique,
|
||||
Default,
|
||||
Check,
|
||||
}
|
||||
|
||||
impl ConstraintKind {
|
||||
/// Upper-case SQL-style label for user-facing messages
|
||||
/// (`NOT NULL`, `UNIQUE`, `DEFAULT`, `CHECK`).
|
||||
#[must_use]
|
||||
pub const fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::NotNull => "NOT NULL",
|
||||
Self::Unique => "UNIQUE",
|
||||
Self::Default => "DEFAULT",
|
||||
Self::Check => "CHECK",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Command {
|
||||
CreateTable {
|
||||
@@ -150,6 +204,22 @@ pub enum Command {
|
||||
DropIndex {
|
||||
selector: IndexSelector,
|
||||
},
|
||||
/// Add a column-level constraint to an existing column
|
||||
/// (ADR-0029 §2.2). Applied through the rebuild-table
|
||||
/// primitive after a §5 dry-run guards populated columns.
|
||||
AddConstraint {
|
||||
table: String,
|
||||
column: String,
|
||||
constraint: Constraint,
|
||||
},
|
||||
/// Remove a column-level constraint from an existing column
|
||||
/// (ADR-0029 §2.2). Naming the `kind` is enough — at most
|
||||
/// one constraint of each kind exists per column.
|
||||
DropConstraint {
|
||||
table: String,
|
||||
column: String,
|
||||
kind: ConstraintKind,
|
||||
},
|
||||
/// Re-display a table's structure in the output. Doesn't
|
||||
/// change schema; useful when the user wants to look at a
|
||||
/// table they aren't currently DDL'ing on.
|
||||
@@ -498,6 +568,8 @@ impl Command {
|
||||
Self::DropRelationship { .. } => "drop relationship",
|
||||
Self::AddIndex { .. } => "add index",
|
||||
Self::DropIndex { .. } => "drop index",
|
||||
Self::AddConstraint { .. } => "add constraint",
|
||||
Self::DropConstraint { .. } => "drop constraint",
|
||||
Self::ShowTable { .. } => "show table",
|
||||
Self::Insert { .. } => "insert into",
|
||||
Self::Update { .. } => "update",
|
||||
@@ -536,6 +608,8 @@ impl Command {
|
||||
| Self::DropColumn { table, .. }
|
||||
| Self::RenameColumn { table, .. }
|
||||
| Self::ChangeColumnType { table, .. }
|
||||
| Self::AddConstraint { table, .. }
|
||||
| Self::DropConstraint { table, .. }
|
||||
| Self::Insert { table, .. }
|
||||
| Self::Update { table, .. }
|
||||
| Self::Delete { table, .. } => table,
|
||||
@@ -598,6 +672,12 @@ impl Command {
|
||||
"from {parent_table}.{parent_column} to {child_table}.{child_column}"
|
||||
),
|
||||
},
|
||||
// A constraint command's subject is the dotted
|
||||
// `<table>.<column>` it acts on (ADR-0029 §2.2).
|
||||
Self::AddConstraint { table, column, .. }
|
||||
| Self::DropConstraint { table, column, .. } => {
|
||||
format!("{table}.{column}")
|
||||
}
|
||||
_ => self.target_table().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user