fix: X4 — advanced-mode SQL INSERT auto-fills omitted non-PK serial (MAX+1)
A Form-A advanced-mode INSERT that omitted a non-PK serial column left it silently NULL (the column is INTEGER UNIQUE, not NOT NULL, so SQLite permits it), while simple-mode do_insert auto-fills it with MAX+1. That violated ADR-0018 §1's "auto-generated on every path" contract and was the unprincipled serial-vs-shortid asymmetry the ADR set out to remove (advanced mode already auto-fills shortid). Fix (decision: advanced mode matches simple mode): the advanced-mode auto-fill reconstruction — renamed plan_shortid_autofill → plan_autogen_autofill — now also fills an omitted non-PK serial with MAX(col)+1 … MAX+n per row (single- and multi-row), reading MAX once under the worker's single-writer serialisation. PK serial stays on the rowid alias; Form B (no column list) still supplies every column. Honours ADR-0018 §1/§5; no ADR amendment needed (the contract already said "every path"). requirements.md X4 marked resolved. Tests: 1949 passing (+1), 0 failed, 0 skipped, 1 ignored; clippy clean.
This commit is contained in:
@@ -707,6 +707,37 @@ fn autofill_does_not_mask_arity_mismatch() {
|
||||
assert!(rows.is_empty(), "no row should land on a rejected insert: {rows:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sql_insert_autofills_omitted_nonpk_serial() {
|
||||
// ADR-0018 §1/§5 + X4: advanced-mode SQL INSERT auto-fills an omitted
|
||||
// non-PK `serial` column with MAX+1 (per row), exactly as simple-mode
|
||||
// `do_insert` does — honouring the "auto-generated on every path"
|
||||
// contract. (Was: silently inserting NULL.) Mirrors the existing
|
||||
// shortid auto-fill, which already runs on this path.
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "t", &[("id", Type::Int), ("seq", Type::Serial)], &["id"]);
|
||||
|
||||
// Single row, omitting the non-PK serial `seq`.
|
||||
run_sqlinsert(&db, &rt, "insert into t (id) values (10)").expect("single-row insert runs");
|
||||
// Multi-row, omitting `seq` — each row gets a distinct, increasing
|
||||
// serial continuing from the current MAX.
|
||||
run_sqlinsert(&db, &rt, "insert into t (id) values (20), (30)")
|
||||
.expect("multi-row insert runs");
|
||||
|
||||
let rows = csv_rows(&project, "t");
|
||||
// No NULL serials, and the sequence is 1, 2, 3 across the three rows.
|
||||
assert_eq!(
|
||||
rows,
|
||||
vec![
|
||||
vec!["10".to_string(), "1".to_string()],
|
||||
vec!["20".to_string(), "2".to_string()],
|
||||
vec!["30".to_string(), "3".to_string()],
|
||||
],
|
||||
"omitted non-PK serial auto-filled MAX+1 per row (no NULLs): {rows:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autofill_insert_select_wider_projection_is_rejected() {
|
||||
// SELECT projects more columns than the list: the guard defers
|
||||
|
||||
Reference in New Issue
Block a user