db: fix self-referential cascade over-count + SQL-delete render test
A self-referential ON DELETE CASCADE FK (e.g. T.ParentId -> T.id) is returned by read_relationships_inbound as a child whose table IS the delete target. The before/after row-count diff then includes the directly-deleted rows (already in rows_affected), so deleting a chain root reported 3 cascaded rows when only 2 were removed via the self-reference. Fix in both do_delete (DSL) and do_sql_delete (SQL): when the child table equals the target, subtract rows_affected from the diff and guard on the corrected count (a leaf delete no longer reports a phantom 0-row self-cascade); the target's CSV is already queued, so a self-ref child is not re-added to rewritten_tables. Pre-existing in do_delete; surfaced by the 3f DA pass, fixed in both paths to keep DSL/SQL parity. Behaviour: report only the rows removed via the self-reference (user-confirmed). Also adds an app-level render test for the SQL DELETE path (handle_dsl_delete_success via CommandOutcome::Delete) — the shared renderer's ok-summary + per-relationship cascade line were exercised only through the DSL path before. Test-first: self_referential_cascade_counts_only_cascaded_rows added for both paths (asserted 2, failed at 3 before the fix). 1545 pass / 0 fail / 1 ignored. Clippy clean.
This commit is contained in:
@@ -380,6 +380,40 @@ fn delete_violating_fk_fails_and_persists_nothing() {
|
||||
assert!(!history.contains(input), "failed delete not logged: {history:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_referential_cascade_counts_only_cascaded_rows() {
|
||||
// A self-referential ON DELETE CASCADE FK: deleting the root of a
|
||||
// chain cascades down within the same table. The directly-deleted
|
||||
// row is reported in rows_affected, so the cascade summary must
|
||||
// report only the *additional* rows removed via the self-
|
||||
// reference — not the raw before/after diff (which includes the
|
||||
// direct delete). Without the self-ref correction this reports 3.
|
||||
let (_project, db, _dir) = open_project_db();
|
||||
let rt = rt();
|
||||
create_cols(&db, &rt, "T", &[("id", Type::Int), ("ParentId", Type::Int)], &["id"]);
|
||||
rt.block_on(db.add_relationship(
|
||||
Some("parent_of".to_string()),
|
||||
"T".to_string(),
|
||||
"id".to_string(),
|
||||
"T".to_string(),
|
||||
"ParentId".to_string(),
|
||||
ReferentialAction::Cascade,
|
||||
ReferentialAction::NoAction,
|
||||
false,
|
||||
None,
|
||||
))
|
||||
.expect("add self-referential relationship");
|
||||
seed(&db, &rt, "insert into T (id, ParentId) values (1, null), (2, 1), (3, 2)", "T");
|
||||
let result =
|
||||
run_delete(&db, &rt, "sql_delete from T where id = 1").expect("self-ref delete runs");
|
||||
assert_eq!(result.rows_affected, 1, "one row matched the WHERE directly");
|
||||
assert_eq!(result.cascade.len(), 1, "self-ref relationship reported once");
|
||||
assert_eq!(
|
||||
result.cascade[0].rows_changed, 2,
|
||||
"only the 2 cascaded rows, not the directly-deleted root too"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_target_table_rejected_at_parse() {
|
||||
// ADR-0030 §6 / ADR-0033 §1: the `__rdbms_*` metadata tables are
|
||||
|
||||
Reference in New Issue
Block a user