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:
+233
-63
@@ -55,8 +55,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");
|
||||
@@ -116,15 +115,18 @@ fn run_update(
|
||||
input: &str,
|
||||
) -> Result<UpdateResult, DbError> {
|
||||
match parse_command(input).unwrap_or_else(|e| panic!("parse {input:?}: {e:?}")) {
|
||||
Command::SqlUpdate { sql, target_table, returning, set_literals } => rt.block_on(
|
||||
db.run_sql_update_with_literals(
|
||||
sql,
|
||||
Some(input.to_string()),
|
||||
target_table,
|
||||
returning,
|
||||
set_literals,
|
||||
),
|
||||
),
|
||||
Command::SqlUpdate {
|
||||
sql,
|
||||
target_table,
|
||||
returning,
|
||||
set_literals,
|
||||
} => rt.block_on(db.run_sql_update_with_literals(
|
||||
sql,
|
||||
Some(input.to_string()),
|
||||
target_table,
|
||||
returning,
|
||||
set_literals,
|
||||
)),
|
||||
other => panic!("expected Command::SqlUpdate from {input:?}, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -135,9 +137,11 @@ fn run_delete(
|
||||
input: &str,
|
||||
) -> Result<DeleteResult, DbError> {
|
||||
match parse_command(input).unwrap_or_else(|e| panic!("parse {input:?}: {e:?}")) {
|
||||
Command::SqlDelete { sql, target_table, returning } => rt.block_on(
|
||||
db.run_sql_delete(sql, Some(input.to_string()), target_table, returning),
|
||||
),
|
||||
Command::SqlDelete {
|
||||
sql,
|
||||
target_table,
|
||||
returning,
|
||||
} => rt.block_on(db.run_sql_delete(sql, Some(input.to_string()), target_table, returning)),
|
||||
other => panic!("expected Command::SqlDelete from {input:?}, got {other:?}"),
|
||||
}
|
||||
}
|
||||
@@ -162,9 +166,25 @@ fn query(db: &Database, rt: &tokio::runtime::Runtime, table: &str) -> Vec<Vec<Op
|
||||
fn e2e_insert_select_cross_table_copies_rows_and_persists_both() {
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "source", &[("id", Type::Int), ("v", Type::Text)], &["id"]);
|
||||
create_cols(&db, &rt, "archive", &[("id", Type::Int), ("v", Type::Text)], &["id"]);
|
||||
seed(&db, &rt, "insert into source (id, v) values (1, 'x'), (2, 'y')");
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"source",
|
||||
&[("id", Type::Int), ("v", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"archive",
|
||||
&[("id", Type::Int), ("v", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
seed(
|
||||
&db,
|
||||
&rt,
|
||||
"insert into source (id, v) values (1, 'x'), (2, 'y')",
|
||||
);
|
||||
|
||||
let result = run_insert(&db, &rt, "insert into archive select * from source")
|
||||
.expect("INSERT … SELECT runs");
|
||||
@@ -252,15 +272,30 @@ fn e2e_multirow_insert_all_ten_types_roundtrips_and_returning_recovers_each_type
|
||||
let rows = query(&db, &rt, "allten");
|
||||
assert_eq!(rows.len(), 2, "both rows persisted");
|
||||
let csv = read_csv(&project, "allten").expect("allten.csv");
|
||||
assert!(csv.contains("hi") && csv.contains("yo"), "text round-trips: {csv:?}");
|
||||
assert!(csv.contains("2026-05-23") && csv.contains("2025-01-01"), "dates round-trip: {csv:?}");
|
||||
assert!(
|
||||
csv.contains("hi") && csv.contains("yo"),
|
||||
"text round-trips: {csv:?}"
|
||||
);
|
||||
assert!(
|
||||
csv.contains("2026-05-23") && csv.contains("2025-01-01"),
|
||||
"dates round-trip: {csv:?}"
|
||||
);
|
||||
|
||||
let sids: Vec<&str> = rows.iter().filter_map(|r| r[9].as_deref()).collect();
|
||||
assert_eq!(sids.len(), 2, "both shortids present");
|
||||
assert!(sids.iter().all(|s| !s.is_empty()), "shortids non-empty: {sids:?}");
|
||||
assert_ne!(sids[0], sids[1], "auto-filled shortids are distinct: {sids:?}");
|
||||
assert!(
|
||||
sids.iter().all(|s| !s.is_empty()),
|
||||
"shortids non-empty: {sids:?}"
|
||||
);
|
||||
assert_ne!(
|
||||
sids[0], sids[1],
|
||||
"auto-filled shortids are distinct: {sids:?}"
|
||||
);
|
||||
let sers: Vec<&str> = rows.iter().filter_map(|r| r[0].as_deref()).collect();
|
||||
assert!(sers.contains(&"1") && sers.contains(&"2"), "serial auto-incremented: {sers:?}");
|
||||
assert!(
|
||||
sers.contains(&"1") && sers.contains(&"2"),
|
||||
"serial auto-incremented: {sers:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -275,18 +310,34 @@ fn e2e_update_with_subquery_in_set() {
|
||||
&db,
|
||||
&rt,
|
||||
"customers",
|
||||
&[("id", Type::Int), ("name", Type::Text), ("last_order", Type::Int)],
|
||||
&[
|
||||
("id", Type::Int),
|
||||
("name", Type::Text),
|
||||
("last_order", Type::Int),
|
||||
],
|
||||
&["id"],
|
||||
);
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"orders",
|
||||
&[("id", Type::Int), ("cust", Type::Int), ("amount", Type::Int)],
|
||||
&[
|
||||
("id", Type::Int),
|
||||
("cust", Type::Int),
|
||||
("amount", Type::Int),
|
||||
],
|
||||
&["id"],
|
||||
);
|
||||
seed(&db, &rt, "insert into customers (id, name, last_order) values (1, 'A', 0), (2, 'B', 0)");
|
||||
seed(&db, &rt, "insert into orders (id, cust, amount) values (10, 1, 50), (11, 1, 30), (12, 2, 99)");
|
||||
seed(
|
||||
&db,
|
||||
&rt,
|
||||
"insert into customers (id, name, last_order) values (1, 'A', 0), (2, 'B', 0)",
|
||||
);
|
||||
seed(
|
||||
&db,
|
||||
&rt,
|
||||
"insert into orders (id, cust, amount) values (10, 1, 50), (11, 1, 30), (12, 2, 99)",
|
||||
);
|
||||
|
||||
let result = run_update(
|
||||
&db,
|
||||
@@ -298,13 +349,26 @@ fn e2e_update_with_subquery_in_set() {
|
||||
assert_eq!(result.rows_affected, 2, "both customers updated");
|
||||
|
||||
let rows = query(&db, &rt, "customers");
|
||||
let c1 = rows.iter().find(|r| r[0].as_deref() == Some("1")).expect("customer 1");
|
||||
let c2 = rows.iter().find(|r| r[0].as_deref() == Some("2")).expect("customer 2");
|
||||
assert_eq!(c1[2].as_deref(), Some("50"), "customer 1 → max(50, 30) = 50");
|
||||
let c1 = rows
|
||||
.iter()
|
||||
.find(|r| r[0].as_deref() == Some("1"))
|
||||
.expect("customer 1");
|
||||
let c2 = rows
|
||||
.iter()
|
||||
.find(|r| r[0].as_deref() == Some("2"))
|
||||
.expect("customer 2");
|
||||
assert_eq!(
|
||||
c1[2].as_deref(),
|
||||
Some("50"),
|
||||
"customer 1 → max(50, 30) = 50"
|
||||
);
|
||||
assert_eq!(c2[2].as_deref(), Some("99"), "customer 2 → max(99) = 99");
|
||||
|
||||
let csv = read_csv(&project, "customers").expect("customers.csv");
|
||||
assert!(csv.contains("50") && csv.contains("99"), "CSV reflects the update: {csv:?}");
|
||||
assert!(
|
||||
csv.contains("50") && csv.contains("99"),
|
||||
"CSV reflects the update: {csv:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -313,8 +377,20 @@ fn e2e_update_with_subquery_in_set() {
|
||||
// ===============================================================
|
||||
|
||||
fn cascade_fixture(db: &Database, rt: &tokio::runtime::Runtime) {
|
||||
create_cols(db, rt, "Customers", &[("id", Type::Int), ("Name", Type::Text)], &["id"]);
|
||||
create_cols(db, rt, "Orders", &[("id", Type::Int), ("CustId", Type::Int)], &["id"]);
|
||||
create_cols(
|
||||
db,
|
||||
rt,
|
||||
"Customers",
|
||||
&[("id", Type::Int), ("Name", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
create_cols(
|
||||
db,
|
||||
rt,
|
||||
"Orders",
|
||||
&[("id", Type::Int), ("CustId", Type::Int)],
|
||||
&["id"],
|
||||
);
|
||||
rt.block_on(db.add_relationship(
|
||||
Some("places".to_string()),
|
||||
"Customers".to_string(),
|
||||
@@ -327,8 +403,16 @@ fn cascade_fixture(db: &Database, rt: &tokio::runtime::Runtime) {
|
||||
None,
|
||||
))
|
||||
.expect("add cascade relationship");
|
||||
seed(db, rt, "insert into Customers (id, Name) values (1, 'Alice'), (2, 'Bob')");
|
||||
seed(db, rt, "insert into Orders (id, CustId) values (10, 1), (11, 1), (12, 2)");
|
||||
seed(
|
||||
db,
|
||||
rt,
|
||||
"insert into Customers (id, Name) values (1, 'Alice'), (2, 'Bob')",
|
||||
);
|
||||
seed(
|
||||
db,
|
||||
rt,
|
||||
"insert into Orders (id, CustId) values (10, 1), (11, 1), (12, 2)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -337,8 +421,8 @@ fn e2e_delete_with_cascade_reports_summary_and_repersists_children() {
|
||||
let rt = rt();
|
||||
cascade_fixture(&db, &rt);
|
||||
|
||||
let result = run_delete(&db, &rt, "delete from Customers where id = 1")
|
||||
.expect("cascading DELETE runs");
|
||||
let result =
|
||||
run_delete(&db, &rt, "delete from Customers where id = 1").expect("cascading DELETE runs");
|
||||
assert_eq!(result.rows_affected, 1, "one parent row deleted");
|
||||
assert_eq!(result.cascade.len(), 1, "one cascade relationship affected");
|
||||
let effect = &result.cascade[0];
|
||||
@@ -346,9 +430,18 @@ fn e2e_delete_with_cascade_reports_summary_and_repersists_children() {
|
||||
assert_eq!(effect.rows_changed, 2, "Alice's two orders cascaded");
|
||||
|
||||
let orders_csv = read_csv(&project, "Orders").expect("Orders.csv re-persisted");
|
||||
assert!(orders_csv.contains("12"), "Bob's order (12) preserved: {orders_csv:?}");
|
||||
assert!(!orders_csv.contains("10"), "Alice's order 10 cascaded away: {orders_csv:?}");
|
||||
assert!(!orders_csv.contains("11"), "Alice's order 11 cascaded away: {orders_csv:?}");
|
||||
assert!(
|
||||
orders_csv.contains("12"),
|
||||
"Bob's order (12) preserved: {orders_csv:?}"
|
||||
);
|
||||
assert!(
|
||||
!orders_csv.contains("10"),
|
||||
"Alice's order 10 cascaded away: {orders_csv:?}"
|
||||
);
|
||||
assert!(
|
||||
!orders_csv.contains("11"),
|
||||
"Alice's order 11 cascaded away: {orders_csv:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -359,7 +452,13 @@ fn e2e_delete_with_cascade_reports_summary_and_repersists_children() {
|
||||
fn e2e_upsert_round_trip_do_update_then_do_nothing() {
|
||||
let (project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "kv", &[("id", Type::Int), ("name", Type::Text)], &["id"]);
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"kv",
|
||||
&[("id", Type::Int), ("name", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
seed(&db, &rt, "insert into kv (id, name) values (1, 'old')");
|
||||
|
||||
// DO UPDATE on a conflict mutates the existing row.
|
||||
@@ -369,9 +468,15 @@ fn e2e_upsert_round_trip_do_update_then_do_nothing() {
|
||||
"insert into kv (id, name) values (1, 'new') on conflict (id) do update set name = excluded.name",
|
||||
)
|
||||
.expect("UPSERT DO UPDATE runs");
|
||||
assert_eq!(upd.rows_affected, 1, "DO UPDATE touches the conflicting row");
|
||||
assert_eq!(
|
||||
upd.rows_affected, 1,
|
||||
"DO UPDATE touches the conflicting row"
|
||||
);
|
||||
let csv = read_csv(&project, "kv").expect("kv.csv");
|
||||
assert!(csv.contains("new") && !csv.contains("old"), "row updated to 'new': {csv:?}");
|
||||
assert!(
|
||||
csv.contains("new") && !csv.contains("old"),
|
||||
"row updated to 'new': {csv:?}"
|
||||
);
|
||||
|
||||
// DO NOTHING on a conflict is a no-op.
|
||||
let nothing = run_insert(
|
||||
@@ -382,7 +487,10 @@ fn e2e_upsert_round_trip_do_update_then_do_nothing() {
|
||||
.expect("UPSERT DO NOTHING runs");
|
||||
assert_eq!(nothing.rows_affected, 0, "DO NOTHING changes no rows");
|
||||
let csv = read_csv(&project, "kv").expect("kv.csv");
|
||||
assert!(csv.contains("new") && !csv.contains("ignored"), "row unchanged by DO NOTHING: {csv:?}");
|
||||
assert!(
|
||||
csv.contains("new") && !csv.contains("ignored"),
|
||||
"row unchanged by DO NOTHING: {csv:?}"
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -393,23 +501,52 @@ fn e2e_upsert_round_trip_do_update_then_do_nothing() {
|
||||
fn e2e_returning_on_insert_update_delete() {
|
||||
let (_project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "t", &[("id", Type::Int), ("v", Type::Text)], &["id"]);
|
||||
create_cols(
|
||||
&db,
|
||||
&rt,
|
||||
"t",
|
||||
&[("id", Type::Int), ("v", Type::Text)],
|
||||
&["id"],
|
||||
);
|
||||
|
||||
let ins = run_insert(&db, &rt, "insert into t (id, v) values (1, 'a') returning id, v")
|
||||
.expect("INSERT … RETURNING runs");
|
||||
assert_eq!(ins.data.rows.len(), 1, "INSERT RETURNING yields the inserted row");
|
||||
let ins = run_insert(
|
||||
&db,
|
||||
&rt,
|
||||
"insert into t (id, v) values (1, 'a') returning id, v",
|
||||
)
|
||||
.expect("INSERT … RETURNING runs");
|
||||
assert_eq!(
|
||||
ins.data.rows.len(),
|
||||
1,
|
||||
"INSERT RETURNING yields the inserted row"
|
||||
);
|
||||
assert_eq!(ins.data.rows[0][1].as_deref(), Some("a"));
|
||||
|
||||
let upd = run_update(&db, &rt, "update t set v = 'b' where id = 1 returning v")
|
||||
.expect("UPDATE … RETURNING runs");
|
||||
assert_eq!(upd.data.rows.len(), 1, "UPDATE RETURNING yields the modified row");
|
||||
assert_eq!(
|
||||
upd.data.rows.len(),
|
||||
1,
|
||||
"UPDATE RETURNING yields the modified row"
|
||||
);
|
||||
assert_eq!(upd.data.rows[0][0].as_deref(), Some("b"));
|
||||
|
||||
let del = run_delete(&db, &rt, "delete from t where id = 1 returning *")
|
||||
.expect("DELETE … RETURNING runs");
|
||||
assert_eq!(del.data.rows.len(), 1, "DELETE RETURNING yields the pre-delete row");
|
||||
assert_eq!(del.data.rows[0][1].as_deref(), Some("b"), "pre-delete value surfaced");
|
||||
assert!(query(&db, &rt, "t").is_empty(), "row is gone after the DELETE");
|
||||
assert_eq!(
|
||||
del.data.rows.len(),
|
||||
1,
|
||||
"DELETE RETURNING yields the pre-delete row"
|
||||
);
|
||||
assert_eq!(
|
||||
del.data.rows[0][1].as_deref(),
|
||||
Some("b"),
|
||||
"pre-delete value surfaced"
|
||||
);
|
||||
assert!(
|
||||
query(&db, &rt, "t").is_empty(),
|
||||
"row is gone after the DELETE"
|
||||
);
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
@@ -463,7 +600,7 @@ fn e2e_replay_phase3_dml_forms_from_a_script() {
|
||||
("1".to_string(), Some("a".to_string())),
|
||||
("11".to_string(), Some("a".to_string())), // INSERT…SELECT id+10
|
||||
("2".to_string(), Some("z".to_string())), // UPDATE
|
||||
// id 3 was DELETEd
|
||||
// id 3 was DELETEd
|
||||
]
|
||||
.into_iter()
|
||||
.collect::<std::collections::BTreeMap<_, _>>()
|
||||
@@ -481,15 +618,42 @@ fn e2e_replay_phase3_dml_forms_from_a_script() {
|
||||
fn e2e_out_of_scope_dml_forms_parse_reject() {
|
||||
let cases = [
|
||||
("OOS-1 DEFAULT VALUES", "insert into t default values"),
|
||||
("OOS-2 INSERT OR REPLACE", "insert or replace into t values (1)"),
|
||||
("OOS-2 INSERT OR IGNORE", "insert or ignore into t values (1)"),
|
||||
("OOS-3 UPDATE … FROM", "update t set a = b.x from other b where t.id = b.id"),
|
||||
("OOS-4 WITH … UPDATE", "with x as (select 1) update t set a = 1 where id = 1"),
|
||||
("OOS-4 WITH … DELETE", "with x as (select 1) delete from t where id = 1"),
|
||||
("OOS-5 INDEXED BY", "delete from t indexed by idx where id = 1"),
|
||||
("OOS-5 NOT INDEXED", "update t not indexed set a = 1 where id = 1"),
|
||||
("OOS-6 multi-statement (DELETE; DELETE)", "delete from t where id = 1; delete from t where id = 2"),
|
||||
("OOS-6 multi-statement (INSERT; INSERT)", "insert into t values (1); insert into t values (2)"),
|
||||
(
|
||||
"OOS-2 INSERT OR REPLACE",
|
||||
"insert or replace into t values (1)",
|
||||
),
|
||||
(
|
||||
"OOS-2 INSERT OR IGNORE",
|
||||
"insert or ignore into t values (1)",
|
||||
),
|
||||
(
|
||||
"OOS-3 UPDATE … FROM",
|
||||
"update t set a = b.x from other b where t.id = b.id",
|
||||
),
|
||||
(
|
||||
"OOS-4 WITH … UPDATE",
|
||||
"with x as (select 1) update t set a = 1 where id = 1",
|
||||
),
|
||||
(
|
||||
"OOS-4 WITH … DELETE",
|
||||
"with x as (select 1) delete from t where id = 1",
|
||||
),
|
||||
(
|
||||
"OOS-5 INDEXED BY",
|
||||
"delete from t indexed by idx where id = 1",
|
||||
),
|
||||
(
|
||||
"OOS-5 NOT INDEXED",
|
||||
"update t not indexed set a = 1 where id = 1",
|
||||
),
|
||||
(
|
||||
"OOS-6 multi-statement (DELETE; DELETE)",
|
||||
"delete from t where id = 1; delete from t where id = 2",
|
||||
),
|
||||
(
|
||||
"OOS-6 multi-statement (INSERT; INSERT)",
|
||||
"insert into t values (1); insert into t values (2)",
|
||||
),
|
||||
];
|
||||
for (label, src) in cases {
|
||||
assert!(
|
||||
@@ -525,7 +689,10 @@ fn e2e_update_all_rows_in_advanced_falls_back_to_dsl() {
|
||||
assert!(
|
||||
matches!(
|
||||
parse_command_in_mode("update Orders set total = 42 --all-rows", Mode::Advanced),
|
||||
Ok(Command::Update { filter: RowFilter::AllRows, .. })
|
||||
Ok(Command::Update {
|
||||
filter: RowFilter::AllRows,
|
||||
..
|
||||
})
|
||||
),
|
||||
"advanced `update … --all-rows` falls back to the DSL Update",
|
||||
);
|
||||
@@ -599,5 +766,8 @@ fn e2e_validity_indicator_fires_for_sql_dml_diagnostic() {
|
||||
// And the indicator renders the `[WRN]` label.
|
||||
app.input_indicator = app.input_validity_verdict();
|
||||
let text = rendered_text(&mut app, &Theme::dark(), 80, 24);
|
||||
assert!(text.contains("[WRN]"), "the SQL DML warning surfaces as [WRN]:\n{text}");
|
||||
assert!(
|
||||
text.contains("[WRN]"),
|
||||
"the SQL DML warning surfaces as [WRN]:\n{text}"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user