constraints: CHECK-violation friendly error + typing-surface matrix (ADR-0029 §10)

Completes ADR-0029's implementation: the friendly-error layer
now names the rule a CHECK violation broke, and the
typing-surface matrix covers the whole constraint grammar.

CHECK-violation friendly error (ADR-0029 §10):
- enrich_dsl_failure gains a CHECK branch — it reads the column
  from the engine's `CHECK constraint failed: <column>`
  message, then resolves the table, the offending value, and
  the column's compiled CHECK expression.
- FailureContext / TranslateContext carry the resolved
  check_rule; translate_check renders "the value <v> breaks the
  rule `<rule>`" when it is known, falling back to the plain
  hint otherwise.

Typing-surface matrix: a new `constraints` submodule, 14 cells
covering the create-table / add-column constraint suffix and
the add-constraint / drop-constraint commands (174 → 188).

16 tests added (1 translate unit, 1 enrichment integration, 14
matrix cells).
This commit is contained in:
claude@clouddev1
2026-05-19 18:54:48 +00:00
parent abce1188f2
commit 5e97f6ac6a
22 changed files with 915 additions and 26 deletions
+59
View File
@@ -21,6 +21,7 @@ use rdbms_playground::db::{Database, DbError, SqliteErrorKind};
use rdbms_playground::dsl::{
action::ReferentialAction, ColumnSpec, Command, RowFilter, Type, Value,
};
use rdbms_playground::dsl::parser::parse_command;
use rdbms_playground::runtime::enrich_dsl_failure;
fn rt() -> Runtime {
@@ -475,6 +476,64 @@ fn enrich_fk_delete_resolves_child_table() {
});
}
// ---- CHECK (ADR-0029 §10) ---------------------------------------
#[test]
fn enrich_check_insert_resolves_table_column_value_and_rule() {
let db = db();
rt().block_on(async {
// `Scores(id serial pk)` plus a non-PK `score` column
// carrying `CHECK (score >= 0)`.
db.create_table(
"Scores".to_string(),
vec![ColumnSpec::new("id".to_string(), Type::Serial)],
vec!["id".to_string()],
None,
)
.await
.unwrap();
let score_spec = match parse_command(
"create table __probe with pk score(int) check (score >= 0)",
)
.expect("probe create parses")
{
Command::CreateTable { columns, .. } => {
columns.into_iter().next().expect("one column")
}
other => panic!("expected CreateTable, got {other:?}"),
};
db.add_column("Scores".to_string(), score_spec, None)
.await
.unwrap();
// An insert that violates the CHECK.
let cmd = Command::Insert {
table: "Scores".to_string(),
columns: Some(vec!["score".to_string()]),
values: vec![Value::Number("-5".to_string())],
};
let err = db
.insert(
"Scores".to_string(),
Some(vec!["score".to_string()]),
vec![Value::Number("-5".to_string())],
None,
)
.await
.unwrap_err();
let facts = enrich_dsl_failure(&db, &cmd, &err).await;
assert_eq!(facts.table.as_deref(), Some("Scores"));
assert_eq!(facts.column.as_deref(), Some("score"));
assert_eq!(facts.value.as_deref(), Some("-5"));
let rule = facts.check_rule.expect("the CHECK rule is resolved");
assert!(
rule.contains("score"),
"the resolved rule names the column: {rule}",
);
});
}
// ---- non-engine error → empty enrichment ------------------------
#[test]