grammar+db: 3i — not_null_missing diagnostic + TableColumn constraints (ADR-0033 §8.3)

Extend SchemaCache TableColumn with not_null + has_default (with a
TableColumn::new constructor for the common no-constraint case),
populated in build_schema_cache from ColumnDescription (a PK column
counts as not-null). New dml_not_null_missing_diagnostics pass: a
WARNING when a SQL INSERT's explicit column list omits a column that
is NOT NULL with no DEFAULT — advisory (the engine enforces it).
serial/shortid (auto-filled) and defaulted columns are excluded.
Anchored on the target-table ident (no token for the omitted column).

Catalog key diagnostic.not_null_missing (engine-neutral). Tests (+4):
fires on omitted required column; silent when included, when
defaulted, and for auto-gen serial/shortid. ~24 TableColumn literal
sites updated for the two new fields (build clean). 1591 pass / 0
fail / 1 ignored. Clippy clean.

All three ADR-0033 §8 DML diagnostics now implemented. Remaining 3i:
cross-cut verification + #12 UPSERT DO UPDATE validation.
This commit is contained in:
claude@clouddev1
2026-05-22 21:58:12 +00:00
parent 6db8253c25
commit 2d1112d0f3
9 changed files with 218 additions and 11 deletions
+33 -5
View File
@@ -55,10 +55,36 @@ pub struct SchemaCache {
/// One column's user-facing type info, scoped to a table
/// (ADR-0024 §Phase D, §WalkContext).
///
/// `not_null` / `has_default` (ADR-0033 §8.3, sub-phase 3i) let the
/// walker pre-flight a `not_null_missing` WARNING — an `INSERT`
/// whose column list omits a required column. They default to
/// `false`, so callers/tests that don't care construct via
/// [`TableColumn::new`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TableColumn {
pub name: String,
pub user_type: crate::dsl::types::Type,
/// The column is declared `NOT NULL` (a PK column is also
/// effectively not-null; the cache builder records that).
pub not_null: bool,
/// The column has a `DEFAULT` — so omitting it on `INSERT` is
/// fine even when `not_null`.
pub has_default: bool,
}
impl TableColumn {
/// A column with no NOT-NULL / default info — the common case
/// for callers and tests that don't exercise ADR-0033 §8.3.
#[must_use]
pub fn new(name: impl Into<String>, user_type: crate::dsl::types::Type) -> Self {
Self {
name: name.into(),
user_type,
not_null: false,
has_default: false,
}
}
}
impl SchemaCache {
@@ -1453,6 +1479,8 @@ mod tests {
.map(|(n, t)| TableColumn {
name: (*n).to_string(),
user_type: *t,
not_null: false,
has_default: false,
})
.collect();
for c in &cols {
@@ -1479,7 +1507,7 @@ mod tests {
cache
.table_columns
.insert("Orders".to_string(), vec![
TableColumn { name: "OrderTotal".to_string(), user_type: Type::Real },
TableColumn { name: "OrderTotal".to_string(), user_type: Type::Real, not_null: false, has_default: false },
]);
cache.tables.push("Orders".to_string());
let cs = cands_with("update Customers set ", 21, &cache);
@@ -2102,15 +2130,15 @@ mod tests {
s.table_columns.insert(
"a".to_string(),
vec![
TableColumn { name: "id".to_string(), user_type: Type::Int },
TableColumn { name: "name".to_string(), user_type: Type::Text },
TableColumn { name: "id".to_string(), user_type: Type::Int, not_null: false, has_default: false },
TableColumn { name: "name".to_string(), user_type: Type::Text, not_null: false, has_default: false },
],
);
s.table_columns.insert(
"b".to_string(),
vec![
TableColumn { name: "id".to_string(), user_type: Type::Int },
TableColumn { name: "total".to_string(), user_type: Type::Real },
TableColumn { name: "id".to_string(), user_type: Type::Int, not_null: false, has_default: false },
TableColumn { name: "total".to_string(), user_type: Type::Real, not_null: false, has_default: false },
],
);
s