grammar+db: 3f — SQL DELETE + cascade summary (ADR-0033 §1/§7)
New src/dsl/grammar/sql_delete.rs (FROM <table> [WHERE] [;]), Command::SqlDelete, Request::RunSqlDelete, do_sql_delete worker. do_sql_delete mirrors the DSL do_delete: detect FK cascade by before/after child row-count diffing, re-persist target + every cascade-affected child, history-on-success inside the tx. Reuses CommandOutcome::Delete -> handle_dsl_delete_success, so the per-relationship cascade summary formatter is shared, not duplicated. ADR-0033 Amendment 2: supersedes §7's WHERE-injected pre-count. Its premise (DSL handler builds pre-counts from the typed Expr) was wrong — do_delete uses count-diff. The pre-count would also have broken the §2 parity promise by reporting SET NULL the DSL path doesn't. Count- diff gives exact parity, no WHERE-byte extraction, and withdraws R2. SET NULL reporting deferred for both paths (user-confirmed). Tests: +6 grammar unit, +12 integration (cascade parity with DSL, both R2 subquery cases, before-execute order, no-WHERE, FK-rejection rollback, childless-parent, two-child cascade). 1542 pass / 0 fail / 1 ignored. Clippy clean. Dev sql_delete entry word removed in 3j.
This commit is contained in:
+51
-1
@@ -20,7 +20,7 @@ use crate::dsl::command::{Command, Expr, RowFilter};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, IdentSource, Node, NumberValidator, ValidationError, Word, expr,
|
||||
shared::{column_value_list, current_column_value},
|
||||
sql_insert, sql_select, sql_update,
|
||||
sql_delete, sql_insert, sql_select, sql_update,
|
||||
};
|
||||
use crate::dsl::walker::context::WalkContext;
|
||||
use crate::dsl::value::Value;
|
||||
@@ -952,6 +952,39 @@ fn build_sql_update(path: &MatchedPath, source: &str) -> Result<Command, Validat
|
||||
Ok(Command::SqlUpdate { sql, target_table })
|
||||
}
|
||||
|
||||
/// Build `Command::SqlDelete` from a validated SQL `DELETE`
|
||||
/// (ADR-0033 §1/§7, sub-phase 3f). Extracts the target table from
|
||||
/// the matched path so the worker re-persists the right CSV and
|
||||
/// snapshots the right inbound children for cascade diffing. No
|
||||
/// WHERE clause is captured — the worker executes the verbatim SQL
|
||||
/// and never inspects the predicate (Amendment 2).
|
||||
///
|
||||
/// Dev-scaffold detail: the entry word is `sql_delete` (not valid
|
||||
/// SQL), so the statement is reconstructed as `delete` + the matched
|
||||
/// tail (which opens at `from`). Sub-phase 3j wires the real
|
||||
/// `delete` entry word, at which point this collapses to
|
||||
/// `source.trim()`.
|
||||
fn build_sql_delete(path: &MatchedPath, source: &str) -> Result<Command, ValidationError> {
|
||||
// The DELETE target is the first `table_name` ident (it precedes
|
||||
// any table referenced inside a WHERE subquery).
|
||||
let target_table = path
|
||||
.items
|
||||
.iter()
|
||||
.find_map(|item| match item.kind {
|
||||
MatchedKind::Ident {
|
||||
role: "table_name", ..
|
||||
} => Some(item.text.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let tail = path
|
||||
.items
|
||||
.first()
|
||||
.map_or(source, |entry| &source[entry.span.1..]);
|
||||
let sql = format!("delete {}", tail.trim());
|
||||
Ok(Command::SqlDelete { sql, target_table })
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
// CommandNodes
|
||||
// =================================================================
|
||||
@@ -1061,6 +1094,23 @@ pub static SQL_UPDATE: CommandNode = CommandNode {
|
||||
usage_ids: &[],
|
||||
};
|
||||
|
||||
/// SQL `DELETE` development scaffold (ADR-0033 sub-phase 3f).
|
||||
///
|
||||
/// Registered under the temporary entry word `sql_delete` so the
|
||||
/// SQL DELETE grammar and execution path (including cascade-summary
|
||||
/// parity) can be exercised in isolation, WITHOUT yet making
|
||||
/// `delete` a shared DSL/SQL entry word. Sharing `delete` is
|
||||
/// sub-phase 3j. This scaffold (entry word + reconstruction in
|
||||
/// `build_sql_delete`) is removed when 3j wires the real `delete`
|
||||
/// entry word.
|
||||
pub static SQL_DELETE: CommandNode = CommandNode {
|
||||
entry: Word::keyword("sql_delete"),
|
||||
shape: Node::Subgrammar(&sql_delete::SQL_DELETE_SHAPE),
|
||||
ast_builder: build_sql_delete,
|
||||
help_id: None,
|
||||
usage_ids: &[],
|
||||
};
|
||||
|
||||
// =================================================================
|
||||
// Tests — `explain` grammar (ADR-0028 §1)
|
||||
// =================================================================
|
||||
|
||||
Reference in New Issue
Block a user