grammar+db: 3e — SQL UPDATE grammar + execution (ADR-0033 §2)

New src/dsl/grammar/sql_update.rs: SQL_UPDATE_SHAPE =
<table> SET col = sql_expr (',' …)* [WHERE sql_expr] [';'], the
__rdbms_* target rejection, and the shared sql_expr on both the
assignment RHS and the predicate. No --all-rows rail — a SQL
UPDATE without WHERE runs as written (ADR-0030 §12). Reuses
sql_select::WHERE_CLAUSE (now pub(crate)) so the predicate
diagnostics are identical. The target uses the shared `table_name`
ident role (not a bespoke one) so the Phase-2 schema-existence and
predicate-warning passes collect it as a scope binding and check
the SET / WHERE columns for free — a bespoke role left them
unchecked (the cross-cut tests caught this).

Command::SqlUpdate { sql, target_table }; Request::RunSqlUpdate +
do_sql_update (execute validated SQL via execute_with_fk_enrichment,
re-persist the target CSV, append history.log). 3e surfaces the
affected-row count only; precise row output is RETURNING (3g), so
the update-success render skips a column-less data set rather than
showing a misleading "(no rows)" band. Behind the dev `sql_update`
entry word until 3j.

Tests: grammar accept/reject; integration (single/multi-col,
no-WHERE all-rows, sql_expr in SET, scalar subquery in SET,
zero-match success, history); walker cross-cut (unknown SET column
→ unknown_column, `= NULL` in WHERE → eq_null warning); app-level
render-guard both ways (column-less → count only; with columns →
table renders). 1524 green, clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-22 13:57:21 +00:00
parent 18d34d0d36
commit 53808ed9d7
11 changed files with 646 additions and 5 deletions
+46 -1
View File
@@ -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_insert, sql_select, sql_update,
};
use crate::dsl::walker::context::WalkContext;
use crate::dsl::value::Value;
@@ -923,6 +923,35 @@ fn build_sql_insert(path: &MatchedPath, source: &str) -> Result<Command, Validat
})
}
/// Build `Command::SqlUpdate` from a validated SQL `UPDATE`
/// (ADR-0033 §2, sub-phase 3e). Extracts the target table from the
/// matched path so the worker re-persists the right CSV.
///
/// Dev-scaffold detail: the entry word is `sql_update` (not valid
/// SQL), so the statement is reconstructed as `update` + the
/// matched tail. Sub-phase 3j wires the real `update` entry word,
/// at which point this collapses to `source.trim()`.
fn build_sql_update(path: &MatchedPath, source: &str) -> Result<Command, ValidationError> {
// The UPDATE target is the first `table_name` ident (it
// precedes any table referenced inside a SET / 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!("update {}", tail.trim());
Ok(Command::SqlUpdate { sql, target_table })
}
// =================================================================
// CommandNodes
// =================================================================
@@ -1016,6 +1045,22 @@ pub static SQL_INSERT: CommandNode = CommandNode {
usage_ids: &[],
};
/// SQL `UPDATE` development scaffold (ADR-0033 sub-phase 3e).
///
/// Registered under the temporary entry word `sql_update` so the
/// SQL UPDATE grammar and execution path can be exercised in
/// isolation, WITHOUT yet making `update` a shared DSL/SQL entry
/// word. Sharing `update` is sub-phase 3j. This scaffold (entry
/// word + reconstruction in `build_sql_update`) is removed when 3j
/// wires the real `update` entry word.
pub static SQL_UPDATE: CommandNode = CommandNode {
entry: Word::keyword("sql_update"),
shape: Node::Subgrammar(&sql_update::SQL_UPDATE_SHAPE),
ast_builder: build_sql_update,
help_id: None,
usage_ids: &[],
};
// =================================================================
// Tests — `explain` grammar (ADR-0028 §1)
// =================================================================