style: format the whole tree with cargo fmt (stock defaults, #35)

One-time, mechanical reformat — no functional changes. The tree was not
rustfmt-clean (~1800 hunks across ~100 files); this brings it to stock
`cargo fmt` defaults so a `cargo fmt --check` CI gate can follow.
Behaviour-preserving: 2509 pass / 0 fail / 1 ignored (unchanged baseline),
clippy clean. A .git-blame-ignore-revs entry follows so `git blame`
skips this commit.
This commit is contained in:
claude@clouddev1
2026-06-17 21:39:19 +00:00
parent e9606b5f6d
commit 41b7e9a049
102 changed files with 8017 additions and 4975 deletions
+422 -101
View File
@@ -41,8 +41,7 @@ fn rt() -> tokio::runtime::Runtime {
fn open_project_db() -> (project::Project, Database, tempfile::TempDir) {
let dir = tempfile::tempdir().expect("create tempdir");
let project =
project::open_or_create(None, Some(dir.path())).expect("open or create project");
let project = project::open_or_create(None, Some(dir.path())).expect("open or create project");
let persistence = Persistence::new(project.path().to_path_buf());
let db = Database::open_with_persistence(project.db_path(), persistence)
.expect("open db with persistence");
@@ -86,7 +85,10 @@ fn single_row_insert_persists_and_counts() {
.expect("insert runs");
assert_eq!(result.rows_affected, 1, "one row inserted");
let csv = read_csv(&project, "T").expect("T.csv written after insert");
assert!(csv.contains("Ada"), "CSV reflects the inserted row: {csv:?}");
assert!(
csv.contains("Ada"),
"CSV reflects the inserted row: {csv:?}"
);
}
#[test]
@@ -193,7 +195,10 @@ fn failed_multi_row_insert_is_atomic() {
String::new(),
false,
));
assert!(outcome.is_err(), "multi-row PK conflict must fail: {outcome:?}");
assert!(
outcome.is_err(),
"multi-row PK conflict must fail: {outcome:?}"
);
let csv = read_csv(&project, "T").expect("T.csv still present");
assert!(
csv.contains("existing") && !csv.contains("fresh") && !csv.contains("collides"),
@@ -208,7 +213,9 @@ fn parse_path_lowers_sqlinsert_scaffold_to_command() {
let command = parse_command("insert into Orders (id, total) values (1, 99.5)")
.expect("insert parses in advanced mode");
match command {
Command::SqlInsert { sql, target_table, .. } => {
Command::SqlInsert {
sql, target_table, ..
} => {
assert_eq!(sql, "insert into Orders (id, total) values (1, 99.5)");
assert_eq!(target_table, "Orders");
}
@@ -248,7 +255,9 @@ fn parse_path_lowers_insert_select_to_command() {
let command = parse_command("insert into archive select * from source")
.expect("INSERT … SELECT parses in advanced mode");
match command {
Command::SqlInsert { sql, target_table, .. } => {
Command::SqlInsert {
sql, target_table, ..
} => {
assert_eq!(sql, "insert into archive select * from source");
assert_eq!(target_table, "archive");
}
@@ -259,12 +268,13 @@ fn parse_path_lowers_insert_select_to_command() {
#[test]
fn parse_path_lowers_with_prefixed_insert_select() {
// R4: a WITH-prefixed SELECT row source lowers verbatim.
let command = parse_command(
"insert into archive with t as (select * from orders) select * from t",
)
.expect("WITH-prefixed INSERT … SELECT parses");
let command =
parse_command("insert into archive with t as (select * from orders) select * from t")
.expect("WITH-prefixed INSERT … SELECT parses");
match command {
Command::SqlInsert { sql, target_table, .. } => {
Command::SqlInsert {
sql, target_table, ..
} => {
assert_eq!(
sql,
"insert into archive with t as (select * from orders) select * from t",
@@ -472,7 +482,13 @@ fn csv_rows(project: &project::Project, table: &str) -> Vec<Vec<String>> {
fn values_autofills_omitted_shortid_pk() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let result = run_sqlinsert(&db, &rt, "insert into t (label) values ('x')")
.expect("auto-fill insert runs");
assert_eq!(result.rows_affected, 1);
@@ -486,25 +502,36 @@ fn values_autofills_omitted_shortid_pk() {
fn values_multirow_autofills_distinct_shortids() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
let result = run_sqlinsert(
create_cols(
&db,
&rt,
"insert into t (label) values ('a'), ('b'), ('c')",
)
.expect("multi-row auto-fill runs");
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let result = run_sqlinsert(&db, &rt, "insert into t (label) values ('a'), ('b'), ('c')")
.expect("multi-row auto-fill runs");
assert_eq!(result.rows_affected, 3);
let rows = csv_rows(&project, "t");
let ids: std::collections::HashSet<&String> = rows.iter().map(|r| &r[0]).collect();
assert_eq!(ids.len(), 3, "three DISTINCT non-empty shortids: {rows:?}");
assert!(rows.iter().all(|r| !r[0].is_empty()), "no empty id: {rows:?}");
assert!(
rows.iter().all(|r| !r[0].is_empty()),
"no empty id: {rows:?}"
);
}
#[test]
fn explicit_shortid_value_is_respected() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
// The user provided `id` explicitly — it must be honoured
// verbatim (the override WARNING is sub-phase 3i).
run_sqlinsert(
@@ -521,10 +548,21 @@ fn explicit_shortid_value_is_respected() {
fn insert_select_autofills_distinct_shortids() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "source", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(&db, &rt, "target", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
run_sqlinsert(&db, &rt, "insert into source (label) values ('a'), ('b')")
.expect("seed source");
create_cols(
&db,
&rt,
"source",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
create_cols(
&db,
&rt,
"target",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into source (label) values ('a'), ('b')").expect("seed source");
let result = run_sqlinsert(
&db,
&rt,
@@ -535,7 +573,10 @@ fn insert_select_autofills_distinct_shortids() {
let rows = csv_rows(&project, "target");
let ids: std::collections::HashSet<&String> = rows.iter().map(|r| &r[0]).collect();
assert_eq!(ids.len(), 2, "two DISTINCT fresh shortids: {rows:?}");
assert!(rows.iter().all(|r| !r[0].is_empty()), "no empty id: {rows:?}");
assert!(
rows.iter().all(|r| !r[0].is_empty()),
"no empty id: {rows:?}"
);
}
#[test]
@@ -547,11 +588,14 @@ fn combined_serial_and_shortid_autofill() {
&db,
&rt,
"t",
&[("id", Type::Serial), ("code", Type::ShortId), ("name", Type::Text)],
&[
("id", Type::Serial),
("code", Type::ShortId),
("name", Type::Text),
],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into t (name) values ('x')")
.expect("combined auto-fill runs");
run_sqlinsert(&db, &rt, "insert into t (name) values ('x')").expect("combined auto-fill runs");
let rows = csv_rows(&project, "t");
assert_eq!(rows.len(), 1, "{rows:?}");
assert_eq!(rows[0][0], "1", "serial PK engine-filled: {rows:?}");
@@ -566,7 +610,13 @@ fn shortid_autofill_respects_mixed_case_column_name() {
// schema name `MyId`, not a lowercased form.
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("MyId", Type::ShortId), ("label", Type::Text)], &["MyId"]);
create_cols(
&db,
&rt,
"t",
&[("MyId", Type::ShortId), ("label", Type::Text)],
&["MyId"],
);
run_sqlinsert(&db, &rt, "insert into t (label) values ('x')")
.expect("mixed-case shortid auto-fill runs");
let rows = csv_rows(&project, "t");
@@ -584,7 +634,11 @@ fn two_shortids_pk_and_nonpk_both_autofill_distinctly() {
&db,
&rt,
"t",
&[("id", Type::ShortId), ("code", Type::ShortId), ("label", Type::Text)],
&[
("id", Type::ShortId),
("code", Type::ShortId),
("label", Type::Text),
],
&["id"],
);
let result = run_sqlinsert(&db, &rt, "insert into t (label) values ('x'), ('y')")
@@ -611,7 +665,11 @@ fn two_shortids_one_provided_one_autofilled() {
&db,
&rt,
"t",
&[("id", Type::ShortId), ("code", Type::ShortId), ("label", Type::Text)],
&[
("id", Type::ShortId),
("code", Type::ShortId),
("label", Type::Text),
],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into t (id, label) values ('myid', 'x')")
@@ -631,7 +689,11 @@ fn compound_pk_with_shortid_member_autofills() {
&db,
&rt,
"t",
&[("id", Type::ShortId), ("region", Type::Int), ("label", Type::Text)],
&[
("id", Type::ShortId),
("region", Type::Int),
("label", Type::Text),
],
&["id", "region"],
);
run_sqlinsert(&db, &rt, "insert into t (region, label) values (1, 'x')")
@@ -653,14 +715,23 @@ fn autofill_does_not_mask_arity_mismatch() {
// 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"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let outcome = run_sqlinsert(&db, &rt, "insert 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:?}");
assert!(
rows.is_empty(),
"no row should land on a rejected insert: {rows:?}"
);
}
#[test]
@@ -672,14 +743,19 @@ fn sql_insert_autofills_omitted_nonpk_serial() {
// 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"]);
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");
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.
@@ -700,11 +776,26 @@ fn autofill_insert_select_wider_projection_is_rejected() {
// 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"]);
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, "insert into src (a, b) values ('p', 'q')").expect("seed");
let outcome = run_sqlinsert(&db, &rt, "insert into t (label) select a, b from src");
assert!(outcome.is_err(), "wider projection must be rejected: {outcome:?}");
assert!(
outcome.is_err(),
"wider projection must be rejected: {outcome:?}"
);
assert!(csv_rows(&project, "t").is_empty(), "nothing should land");
}
@@ -724,7 +815,10 @@ fn autofill_insert_select_narrower_projection_is_rejected() {
);
run_sqlinsert(&db, &rt, "insert into src (a) values ('p')").expect("seed");
let outcome = run_sqlinsert(&db, &rt, "insert into t (x, y) select a from src");
assert!(outcome.is_err(), "narrower projection must be rejected: {outcome:?}");
assert!(
outcome.is_err(),
"narrower projection must be rejected: {outcome:?}"
);
assert!(csv_rows(&project, "t").is_empty(), "nothing should land");
}
@@ -736,11 +830,25 @@ fn autofill_insert_select_narrower_projection_is_rejected() {
fn insert_returning_star_returns_inserted_row() {
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("b", Type::Text)], &["id"]);
let result = run_sqlinsert(&db, &rt, "insert into t (id, b) values (1, 'Ada') returning *")
.expect("INSERT … RETURNING * runs");
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("b", Type::Text)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
"insert into t (id, b) values (1, 'Ada') returning *",
)
.expect("INSERT … RETURNING * runs");
assert_eq!(result.rows_affected, 1, "one row inserted");
assert_eq!(result.data.rows.len(), 1, "RETURNING yielded the inserted row");
assert_eq!(
result.data.rows.len(),
1,
"RETURNING yielded the inserted row"
);
assert_eq!(result.data.columns, vec!["id".to_string(), "b".to_string()]);
assert_eq!(result.data.rows[0][1], Some("Ada".to_string()));
}
@@ -749,7 +857,13 @@ fn insert_returning_star_returns_inserted_row() {
fn insert_multirow_returning_id_yields_distinct_rows() {
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("b", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("b", Type::Text)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
@@ -760,7 +874,12 @@ fn insert_multirow_returning_id_yields_distinct_rows() {
assert_eq!(result.data.columns, vec!["id".to_string()]);
let ids: std::collections::BTreeSet<_> =
result.data.rows.iter().map(|r| r[0].clone()).collect();
assert_eq!(ids.len(), 3, "three distinct ids returned: {:?}", result.data.rows);
assert_eq!(
ids.len(),
3,
"three distinct ids returned: {:?}",
result.data.rows
);
}
#[test]
@@ -771,16 +890,33 @@ fn insert_returning_autofills_shortid_and_returns_it() {
// surfaces in the returned row.
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let result = run_sqlinsert(&db, &rt, "insert into t (label) values ('x') returning *")
.expect("auto-fill INSERT … RETURNING * runs");
assert_eq!(result.rows_affected, 1, "one row inserted (RETURNING-counted)");
assert_eq!(
result.rows_affected, 1,
"one row inserted (RETURNING-counted)"
);
assert_eq!(result.data.rows.len(), 1, "RETURNING yielded the row");
// `id` is the auto-filled shortid column; it must be non-empty in
// the returned row (proving the rewrite kept RETURNING).
let id_idx = result.data.columns.iter().position(|c| c == "id").expect("id column");
let id_idx = result
.data
.columns
.iter()
.position(|c| c == "id")
.expect("id column");
let id_val = result.data.rows[0][id_idx].clone();
assert!(id_val.is_some_and(|s| !s.is_empty()), "generated shortid surfaced via RETURNING");
assert!(
id_val.is_some_and(|s| !s.is_empty()),
"generated shortid surfaced via RETURNING"
);
}
#[test]
@@ -790,11 +926,29 @@ fn insert_returning_recovers_bare_column_type() {
// renders as the word, not 0/1).
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("active", Type::Bool)], &["id"]);
let result = run_sqlinsert(&db, &rt, "insert into t (id, active) values (1, true) returning active")
.expect("INSERT … RETURNING active runs");
assert_eq!(result.data.column_types, vec![Some(Type::Bool)], "bool type recovered");
assert_eq!(result.data.rows[0][0], Some("true".to_string()), "rendered as the bool word");
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("active", Type::Bool)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
"insert into t (id, active) values (1, true) returning active",
)
.expect("INSERT … RETURNING active runs");
assert_eq!(
result.data.column_types,
vec![Some(Type::Bool)],
"bool type recovered"
);
assert_eq!(
result.data.rows[0][0],
Some("true".to_string()),
"rendered as the bool word"
);
}
#[test]
@@ -803,11 +957,29 @@ fn insert_returning_computed_expression_is_typeless() {
// so its recovered type is None (renders with neutral alignment).
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("n", Type::Int)], &["id"]);
let result = run_sqlinsert(&db, &rt, "insert into t (id, n) values (1, 5) returning n + 1")
.expect("INSERT … RETURNING <expr> runs");
assert_eq!(result.data.column_types, vec![None], "computed projection is typeless");
assert_eq!(result.data.rows[0][0], Some("6".to_string()), "engine evaluated n + 1");
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("n", Type::Int)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
"insert into t (id, n) values (1, 5) returning n + 1",
)
.expect("INSERT … RETURNING <expr> runs");
assert_eq!(
result.data.column_types,
vec![None],
"computed projection is typeless"
);
assert_eq!(
result.data.rows[0][0],
Some("6".to_string()),
"engine evaluated n + 1"
);
}
#[test]
@@ -858,7 +1030,13 @@ fn multirow_autofill_returning_yields_distinct_generated_ids() {
// rows each carrying its own generated id.
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
@@ -867,11 +1045,25 @@ fn multirow_autofill_returning_yields_distinct_generated_ids() {
.expect("multi-row auto-fill INSERT … RETURNING * runs");
assert_eq!(result.rows_affected, 3, "three rows inserted");
assert_eq!(result.data.rows.len(), 3, "three rows returned");
let id_idx = result.data.columns.iter().position(|c| c == "id").expect("id column");
let id_idx = result
.data
.columns
.iter()
.position(|c| c == "id")
.expect("id column");
let ids: std::collections::BTreeSet<_> =
result.data.rows.iter().map(|r| r[id_idx].clone()).collect();
assert_eq!(ids.len(), 3, "three DISTINCT generated ids via RETURNING: {:?}", result.data.rows);
assert!(ids.iter().all(|v| v.as_ref().is_some_and(|s| !s.is_empty())), "all ids non-empty");
assert_eq!(
ids.len(),
3,
"three DISTINCT generated ids via RETURNING: {:?}",
result.data.rows
);
assert!(
ids.iter()
.all(|v| v.as_ref().is_some_and(|s| !s.is_empty())),
"all ids non-empty"
);
}
#[test]
@@ -881,15 +1073,39 @@ fn insert_select_returning_executes_and_returns_rows() {
// source feeds the insert, and RETURNING yields the inserted rows).
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "src", &[("id", Type::Int), ("b", Type::Text)], &["id"]);
create_cols(&db, &rt, "dst", &[("id", Type::Int), ("b", Type::Text)], &["id"]);
run_sqlinsert(&db, &rt, "insert into src (id, b) values (1, 'x'), (2, 'y')").expect("seed src");
let result = run_sqlinsert(&db, &rt, "insert into dst select * from src returning id, b")
.expect("INSERT … SELECT … RETURNING runs");
create_cols(
&db,
&rt,
"src",
&[("id", Type::Int), ("b", Type::Text)],
&["id"],
);
create_cols(
&db,
&rt,
"dst",
&[("id", Type::Int), ("b", Type::Text)],
&["id"],
);
run_sqlinsert(
&db,
&rt,
"insert into src (id, b) values (1, 'x'), (2, 'y')",
)
.expect("seed src");
let result = run_sqlinsert(
&db,
&rt,
"insert into dst select * from src returning id, b",
)
.expect("INSERT … SELECT … RETURNING runs");
assert_eq!(result.rows_affected, 2, "two rows copied");
assert_eq!(result.data.rows.len(), 2, "RETURNING yielded both inserted rows");
let bs: std::collections::BTreeSet<_> =
result.data.rows.iter().map(|r| r[1].clone()).collect();
assert_eq!(
result.data.rows.len(),
2,
"RETURNING yielded both inserted rows"
);
let bs: std::collections::BTreeSet<_> = result.data.rows.iter().map(|r| r[1].clone()).collect();
assert!(bs.contains(&Some("x".to_string())) && bs.contains(&Some("y".to_string())));
}
@@ -934,32 +1150,59 @@ fn autofill_upsert_real_conflict_preserves_clause_and_excluded() {
"t".to_string(),
vec![
ColumnSpec::new("id", Type::ShortId),
ColumnSpec { unique: true, ..ColumnSpec::new("code", Type::Text) },
ColumnSpec {
unique: true,
..ColumnSpec::new("code", Type::Text)
},
ColumnSpec::new("label", Type::Text),
],
vec!["id".to_string()],
None,
))
.expect("create table with shortid pk + unique code");
run_sqlinsert(&db, &rt, "insert into t (code, label) values ('A', 'first')").expect("seed");
run_sqlinsert(
&db,
&rt,
"insert into t (code, label) values ('A', 'first')",
)
.expect("seed");
let result = run_sqlinsert(
&db,
&rt,
"insert into t (code, label) values ('A', 'second') on conflict (code) do update set label = excluded.label",
)
.expect("auto-filled UPSERT with a real conflict (clause preserved)");
assert_eq!(result.rows_affected, 1, "the conflicting row was updated, not inserted");
assert_eq!(
result.rows_affected, 1,
"the conflicting row was updated, not inserted"
);
let rows = csv_rows(&project, "t");
assert_eq!(rows.len(), 1, "still one row (DO UPDATE, not a second insert)");
assert!(rows[0].iter().any(|c| c == "second"), "label updated via excluded: {rows:?}");
assert!(!rows[0].iter().any(|c| c == "first"), "old label replaced: {rows:?}");
assert_eq!(
rows.len(),
1,
"still one row (DO UPDATE, not a second insert)"
);
assert!(
rows[0].iter().any(|c| c == "second"),
"label updated via excluded: {rows:?}"
);
assert!(
!rows[0].iter().any(|c| c == "first"),
"old label replaced: {rows:?}"
);
}
#[test]
fn on_conflict_do_nothing_keeps_existing_row() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("name", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("name", Type::Text)],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into t (id, name) values (1, 'orig')").expect("seed");
let result = run_sqlinsert(
&db,
@@ -970,14 +1213,23 @@ fn on_conflict_do_nothing_keeps_existing_row() {
assert_eq!(result.rows_affected, 0, "conflicting row left untouched");
let rows = csv_rows(&project, "t");
assert_eq!(rows.len(), 1, "still one row");
assert!(rows[0].iter().any(|c| c == "orig"), "original value kept: {rows:?}");
assert!(
rows[0].iter().any(|c| c == "orig"),
"original value kept: {rows:?}"
);
}
#[test]
fn on_conflict_do_update_applies_excluded() {
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("name", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("name", Type::Text)],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into t (id, name) values (1, 'orig')").expect("seed");
let result = run_sqlinsert(
&db,
@@ -988,14 +1240,23 @@ fn on_conflict_do_update_applies_excluded() {
assert_eq!(result.rows_affected, 1, "the conflicting row was updated");
let rows = csv_rows(&project, "t");
assert_eq!(rows.len(), 1, "still one row (updated, not inserted)");
assert!(rows[0].iter().any(|c| c == "new"), "row updated to excluded.name: {rows:?}");
assert!(
rows[0].iter().any(|c| c == "new"),
"row updated to excluded.name: {rows:?}"
);
}
#[test]
fn on_conflict_do_nothing_without_target() {
let (_project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::Int), ("name", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::Int), ("name", Type::Text)],
&["id"],
);
run_sqlinsert(&db, &rt, "insert into t (id, name) values (1, 'orig')").expect("seed");
let result = run_sqlinsert(
&db,
@@ -1003,7 +1264,10 @@ fn on_conflict_do_nothing_without_target() {
"insert into t (id, name) values (1, 'x') on conflict do nothing",
)
.expect("ON CONFLICT (no target) DO NOTHING runs");
assert_eq!(result.rows_affected, 0, "any-conflict do-nothing absorbed the duplicate");
assert_eq!(
result.rows_affected, 0,
"any-conflict do-nothing absorbed the duplicate"
);
}
#[test]
@@ -1017,7 +1281,13 @@ fn autofill_preserves_on_conflict_clause() {
// the rewrite doesn't prepare-fail and the clause survives.
let (project, db, _dir) = open_project_db();
let rt = rt();
create_cols(&db, &rt, "t", &[("id", Type::ShortId), ("label", Type::Text)], &["id"]);
create_cols(
&db,
&rt,
"t",
&[("id", Type::ShortId), ("label", Type::Text)],
&["id"],
);
let result = run_sqlinsert(
&db,
&rt,
@@ -1054,7 +1324,10 @@ fn sql_dml_validates_literal_values_like_the_dsl() {
let dsl = r.block_on(db.insert(
"T".to_string(),
Some(vec!["id".to_string(), "d".to_string()]),
vec![Value::Number("1".to_string()), Value::Text("2025/01/15".to_string())],
vec![
Value::Number("1".to_string()),
Value::Text("2025/01/15".to_string()),
],
Some("insert".to_string()),
));
assert!(
@@ -1187,20 +1460,34 @@ fn advanced_upsert_do_update_set_offers_typed_slot_hint() {
// column-typed slot hint (boundary-aware lookahead → typed slot).
let mut cache = SchemaCache::default();
let cols = vec![
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 },
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,
},
];
cache.tables.push("Customers".to_string());
cache.columns.push("id".to_string());
cache.columns.push("Name".to_string());
cache.table_columns.insert("Customers".to_string(), cols);
let input = "insert into Customers (id, Name) values (1, 'x') on conflict (id) do update set Name=";
let input =
"insert into Customers (id, Name) values (1, 'x') on conflict (id) do update set Name=";
let hint = ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Advanced);
let Some(AmbientHint::Prose(prose)) = hint else {
panic!("expected a Prose hint at the UPSERT SET value slot, got {hint:?}");
};
assert!(prose.contains("Name"), "hint names the column `Name`: {prose:?}");
assert!(
prose.contains("Name"),
"hint names the column `Name`: {prose:?}"
);
assert!(
prose.contains("quoted string"),
"text-column hint says `quoted string`: {prose:?}"
@@ -1251,8 +1538,14 @@ fn advanced_insert_form_a_value_offers_typed_slot_hint() {
// user-listed column, so the hint is that column's typed prose.
let schema = vschema(&[("Things", &[("k", Type::Int), ("note", Type::Text)])]);
let prose = prose_at("insert into Things (note) values (", &schema);
assert!(prose.contains("note"), "names listed column `note`: {prose:?}");
assert!(prose.contains("quoted string"), "text-column prose: {prose:?}");
assert!(
prose.contains("note"),
"names listed column `note`: {prose:?}"
);
assert!(
prose.contains("quoted string"),
"text-column prose: {prose:?}"
);
}
#[test]
@@ -1271,8 +1564,14 @@ fn advanced_insert_second_position_hints_second_column() {
// hint is the SECOND column's typed prose.
let schema = vschema(&[("Things", &[("k", Type::Int), ("note", Type::Text)])]);
let prose = prose_at("insert into Things (k, note) values (5, ", &schema);
assert!(prose.contains("note"), "second position names `note`: {prose:?}");
assert!(prose.contains("quoted string"), "text-column prose: {prose:?}");
assert!(
prose.contains("note"),
"second position names `note`: {prose:?}"
);
assert!(
prose.contains("quoted string"),
"text-column prose: {prose:?}"
);
}
#[test]
@@ -1283,13 +1582,19 @@ fn advanced_insert_value_int_mismatch_is_caught_live() {
&schema,
Mode::Advanced,
);
assert!(!matches!(bad, InputState::Valid), "decimal into int rejected live: {bad:?}");
assert!(
!matches!(bad, InputState::Valid),
"decimal into int rejected live: {bad:?}"
);
let ok = classify_input_with_schema_in_mode(
"insert into Things (k) values (5)",
&schema,
Mode::Advanced,
);
assert!(matches!(ok, InputState::Valid), "valid int literal parses: {ok:?}");
assert!(
matches!(ok, InputState::Valid),
"valid int literal parses: {ok:?}"
);
}
#[test]
@@ -1303,7 +1608,10 @@ fn advanced_insert_string_into_int_is_caught_live() {
&schema,
Mode::Advanced,
);
assert!(!matches!(bad, InputState::Valid), "string into int rejected live: {bad:?}");
assert!(
!matches!(bad, InputState::Valid),
"string into int rejected live: {bad:?}"
);
}
#[test]
@@ -1314,7 +1622,10 @@ fn advanced_insert_multi_row_typed_and_mismatch_caught() {
&schema,
Mode::Advanced,
);
assert!(matches!(ok, InputState::Valid), "well-formed multi-row parses: {ok:?}");
assert!(
matches!(ok, InputState::Valid),
"well-formed multi-row parses: {ok:?}"
);
let bad = classify_input_with_schema_in_mode(
"insert into Things (k, note) values (1, 'a'), (3.14, 'b')",
&schema,
@@ -1333,14 +1644,21 @@ fn advanced_insert_form_b_maps_all_columns_including_serial() {
// takes an int literal (unlike the DSL, which omits auto-gen cols).
let schema = vschema(&[(
"Customers",
&[("id", Type::Serial), ("Name", Type::Text), ("Email", Type::Text)],
&[
("id", Type::Serial),
("Name", Type::Text),
("Email", Type::Text),
],
)]);
let state = classify_input_with_schema_in_mode(
"insert into Customers values (1, 'Bob', 'b@c')",
&schema,
Mode::Advanced,
);
assert!(matches!(state, InputState::Valid), "Form B maps all 3 columns: {state:?}");
assert!(
matches!(state, InputState::Valid),
"Form B maps all 3 columns: {state:?}"
);
}
#[test]
@@ -1356,6 +1674,9 @@ fn advanced_insert_value_expressions_still_parse_via_sql_expr() {
"insert into Things (k) values ((select 1))",
] {
let state = classify_input_with_schema_in_mode(input, &schema, Mode::Advanced);
assert!(matches!(state, InputState::Valid), "{input:?} must parse: {state:?}");
assert!(
matches!(state, InputState::Valid),
"{input:?} must parse: {state:?}"
);
}
}