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:
@@ -376,6 +376,7 @@ mod tests {
|
||||
unique: false,
|
||||
not_null: false,
|
||||
default: None,
|
||||
check: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,10 @@ pub struct ColumnSchema {
|
||||
/// form SQLite reports and `schema_to_ddl` echoes verbatim.
|
||||
/// `None` when the column has no default.
|
||||
pub default: Option<String>,
|
||||
/// `CHECK` constraint in compiled-SQL form (ADR-0029 §7),
|
||||
/// echoed verbatim into the rebuilt DDL. `None` when the
|
||||
/// column has no check.
|
||||
pub check: Option<String>,
|
||||
}
|
||||
|
||||
/// One index as recorded in `project.yaml` (ADR-0025).
|
||||
@@ -383,6 +387,7 @@ mod tests {
|
||||
unique: false,
|
||||
not_null: false,
|
||||
default: None,
|
||||
check: None,
|
||||
}],
|
||||
rows: vec![vec![CellValue::Text("Alice".to_string())]],
|
||||
};
|
||||
|
||||
+18
-6
@@ -129,6 +129,10 @@ fn write_column(out: &mut String, col: &ColumnSchema) {
|
||||
line.push_str(", default: ");
|
||||
line.push_str(&yaml_string(default));
|
||||
}
|
||||
if let Some(check) = &col.check {
|
||||
line.push_str(", check: ");
|
||||
line.push_str(&yaml_string(check));
|
||||
}
|
||||
line.push_str(" }");
|
||||
let _ = writeln!(out, "{line}");
|
||||
}
|
||||
@@ -238,6 +242,7 @@ pub(crate) fn parse_schema(body: &str) -> Result<SchemaSnapshot, YamlError> {
|
||||
unique: c.unique,
|
||||
not_null: c.not_null,
|
||||
default: c.default,
|
||||
check: c.check,
|
||||
});
|
||||
}
|
||||
tables.push(TableSchema {
|
||||
@@ -370,6 +375,9 @@ struct RawColumn {
|
||||
/// `DEFAULT` SQL literal (ADR-0029); absent in older files.
|
||||
#[serde(default)]
|
||||
default: Option<String>,
|
||||
/// `CHECK` SQL (ADR-0029); absent in older files.
|
||||
#[serde(default)]
|
||||
check: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -407,16 +415,16 @@ mod tests {
|
||||
name: "Customers".to_string(),
|
||||
primary_key: vec!["id".to_string()],
|
||||
columns: vec![
|
||||
ColumnSchema { name: "id".to_string(), user_type: Type::Serial, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "Name".to_string(), user_type: Type::Text, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "id".to_string(), user_type: Type::Serial, unique: false, not_null: false, default: None, check: None },
|
||||
ColumnSchema { name: "Name".to_string(), user_type: Type::Text, unique: false, not_null: false, default: None, check: None },
|
||||
],
|
||||
},
|
||||
TableSchema {
|
||||
name: "Orders".to_string(),
|
||||
primary_key: vec!["id".to_string()],
|
||||
columns: vec![
|
||||
ColumnSchema { name: "id".to_string(), user_type: Type::Serial, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "CustId".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "id".to_string(), user_type: Type::Serial, unique: false, not_null: false, default: None, check: None },
|
||||
ColumnSchema { name: "CustId".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None, check: None },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -484,6 +492,7 @@ mod tests {
|
||||
unique: false,
|
||||
not_null: false,
|
||||
default: None,
|
||||
check: None,
|
||||
}],
|
||||
}],
|
||||
relationships: vec![],
|
||||
@@ -523,6 +532,7 @@ mod tests {
|
||||
unique: false,
|
||||
not_null: false,
|
||||
default: None,
|
||||
check: None,
|
||||
},
|
||||
ColumnSchema {
|
||||
name: "title".to_string(),
|
||||
@@ -530,6 +540,7 @@ mod tests {
|
||||
unique: true,
|
||||
not_null: true,
|
||||
default: Some("'untitled'".to_string()),
|
||||
check: None,
|
||||
},
|
||||
ColumnSchema {
|
||||
name: "stock".to_string(),
|
||||
@@ -537,6 +548,7 @@ mod tests {
|
||||
unique: false,
|
||||
not_null: false,
|
||||
default: Some("0".to_string()),
|
||||
check: Some("\"stock\" >= 0".to_string()),
|
||||
},
|
||||
],
|
||||
}],
|
||||
@@ -622,8 +634,8 @@ relationships:
|
||||
name: "Items".to_string(),
|
||||
primary_key: vec!["a".to_string(), "b".to_string()],
|
||||
columns: vec![
|
||||
ColumnSchema { name: "a".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "b".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None },
|
||||
ColumnSchema { name: "a".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None, check: None },
|
||||
ColumnSchema { name: "b".to_string(), user_type: Type::Int, unique: false, not_null: false, default: None, check: None },
|
||||
],
|
||||
}],
|
||||
relationships: vec![],
|
||||
|
||||
Reference in New Issue
Block a user