db: 3d fix — don't let shortid auto-fill mask INSERT arity mismatch
plan_shortid_autofill read exactly listed_columns.len() cells from the materialised row source. When the row source produced a different column count than the user's list, the extra columns were silently dropped (wider → wrong data, insert succeeded) or read out of range (narrower). Guard: if the materialised statement's column_count differs from the listed-column count, skip auto-fill and execute the verbatim statement so the engine reports the mismatch — matching the non-auto-fill path. A friendly pre-flight diagnostic remains sub-phase 3i. Tests: VALUES with too many values; INSERT…SELECT with a wider and a narrower projection — each rejected with nothing persisted.
This commit is contained in:
@@ -661,3 +661,56 @@ fn compound_pk_with_shortid_member_autofills() {
|
||||
);
|
||||
assert_eq!(rows[0][1], "1", "{rows:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autofill_does_not_mask_arity_mismatch() {
|
||||
// A column-list / value-count mismatch must still be rejected
|
||||
// even when a shortid auto-fill would otherwise kick in — the
|
||||
// auto-fill path must not silently drop the extra value. (3i
|
||||
// adds a friendly pre-flight; until then we defer to the
|
||||
// engine rather than mask the error.)
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
|
||||
let outcome = run_sqlinsert(&db, &rt, "sqlinsert into t (label) values ('a', 'b')");
|
||||
assert!(
|
||||
outcome.is_err(),
|
||||
"arity mismatch must be rejected, not masked: {outcome:?}",
|
||||
);
|
||||
let rows = csv_rows(&project, "t");
|
||||
assert!(rows.is_empty(), "no row should land on a rejected insert: {rows:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autofill_insert_select_wider_projection_is_rejected() {
|
||||
// SELECT projects more columns than the list: the guard defers
|
||||
// to the engine instead of dropping the extra projection.
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "src", &[("a", Type::Text), ("b", Type::Text)], &["a"]);
|
||||
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
|
||||
run_sqlinsert(&db, &rt, "sqlinsert into src (a, b) values ('p', 'q')").expect("seed");
|
||||
let outcome = run_sqlinsert(&db, &rt, "sqlinsert into t (label) select a, b from src");
|
||||
assert!(outcome.is_err(), "wider projection must be rejected: {outcome:?}");
|
||||
assert!(csv_rows(&project, "t").is_empty(), "nothing should land");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn autofill_insert_select_narrower_projection_is_rejected() {
|
||||
// SELECT projects fewer columns than the list: the guard
|
||||
// prevents an out-of-range read and defers to the engine.
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "src", &[("a", Type::Text)], &["a"]);
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"t",
|
||||
&[("id", Type::ShortId), ("x", Type::Text), ("y", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
run_sqlinsert(&db, &rt, "sqlinsert into src (a) values ('p')").expect("seed");
|
||||
let outcome = run_sqlinsert(&db, &rt, "sqlinsert into t (x, y) select a from src");
|
||||
assert!(outcome.is_err(), "narrower projection must be rejected: {outcome:?}");
|
||||
assert!(csv_rows(&project, "t").is_empty(), "nothing should land");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user