refactor: ColumnSpec / AddColumn carry constraint fields (ADR-0029 scaffolding)

Expand ColumnSpec and Command::AddColumn with the four
ADR-0029 constraint slots (not_null, unique, default, check),
all defaulting off; `Database::add_column` now takes a
ColumnSpec. No behaviour change — the grammar to set the
fields and the DDL to enforce them land in the following
commits. Isolated here so those commits stay readable.

Adds ColumnSpec::new for the unconstrained case; 110 call
sites updated. 1172 tests pass; clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-19 14:04:36 +00:00
parent 7bfd213ab3
commit eff2ee8d14
15 changed files with 237 additions and 194 deletions
+40 -3
View File
@@ -15,13 +15,42 @@ use crate::dsl::action::ReferentialAction;
use crate::dsl::types::Type;
use crate::dsl::value::Value;
/// A column at table-creation time: a name and a user-facing
/// type. Constraints beyond `PRIMARY KEY` (NOT NULL, UNIQUE,
/// CHECK, DEFAULT) come in later iterations.
/// A column at table-creation time: a name, a user-facing
/// type, and its column-level constraints (ADR-0029).
///
/// `PRIMARY KEY` is not represented here — it is a table-level
/// property carried separately in `Command::CreateTable`'s
/// `primary_key` list, since it may span columns.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ColumnSpec {
pub name: String,
pub ty: Type,
/// `NOT NULL` — the column rejects `NULL` (ADR-0029).
pub not_null: bool,
/// `UNIQUE` — non-`NULL` values must be distinct (ADR-0029).
pub unique: bool,
/// `DEFAULT <literal>` — the value used when an `insert`
/// omits this column (ADR-0029).
pub default: Option<Value>,
/// `CHECK (<expr>)` — every row must satisfy this boolean
/// expression (ADR-0029).
pub check: Option<Expr>,
}
impl ColumnSpec {
/// A column spec carrying no constraints — the common case
/// for callers and tests that do not exercise ADR-0029.
#[must_use]
pub fn new(name: impl Into<String>, ty: Type) -> Self {
Self {
name: name.into(),
ty,
not_null: false,
unique: false,
default: None,
check: None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -39,10 +68,18 @@ pub enum Command {
DropTable {
name: String,
},
/// Add a column to an existing table. The column carries
/// its constraints from the same suffix grammar as
/// `create table` (ADR-0029); `check` is `None` until the
/// CHECK grammar lands.
AddColumn {
table: String,
column: String,
ty: Type,
not_null: bool,
unique: bool,
default: Option<Value>,
check: Option<Expr>,
},
/// Remove a column from a table. Refused if the column is
/// part of the primary key or is involved in a declared
+7 -4
View File
@@ -616,6 +616,12 @@ fn build_add(path: &MatchedPath) -> Result<Command, ValidationError> {
table: require_ident(path, "table_name")?,
column: require_ident(path, "column_name")?,
ty,
// Constraint suffix is wired in once the
// constraint grammar lands (ADR-0029).
not_null: false,
unique: false,
default: None,
check: None,
})
}
Some("1") => build_add_relationship(path),
@@ -907,10 +913,7 @@ fn build_create_table(path: &MatchedPath) -> Result<Command, ValidationError> {
let columns = pk_specs
.iter()
.map(|(n, t)| ColumnSpec {
name: n.clone(),
ty: *t,
})
.map(|(n, t)| ColumnSpec::new(n.clone(), *t))
.collect();
let primary_key = pk_specs.into_iter().map(|(n, _)| n).collect();
+25 -4
View File
@@ -358,10 +358,7 @@ mod tests {
}
fn col(name: &str, ty: Type) -> ColumnSpec {
ColumnSpec {
name: name.to_string(),
ty,
}
ColumnSpec::new(name, ty)
}
#[test]
@@ -657,6 +654,10 @@ mod tests {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -671,6 +672,10 @@ mod tests {
table: "T".to_string(),
column: "C".to_string(),
ty: *ty,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -684,6 +689,10 @@ mod tests {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -696,6 +705,10 @@ mod tests {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -708,6 +721,10 @@ mod tests {
table: "Customers".to_string(),
column: "Name".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -720,6 +737,10 @@ mod tests {
table: "T".to_string(),
column: "Name".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
+5 -4
View File
@@ -1346,6 +1346,10 @@ mod tests {
table: "Customers".to_string(),
column: "Email".to_string(),
ty: Type::Text,
not_null: false,
unique: false,
default: None,
check: None,
}
);
}
@@ -1474,10 +1478,7 @@ mod tests {
use crate::dsl::command::ColumnSpec;
fn col(name: &str, ty: Type) -> ColumnSpec {
ColumnSpec {
name: name.to_string(),
ty,
}
ColumnSpec::new(name, ty)
}
#[test]