feat: ADR-0036 Phase 1 — validate advanced-mode INSERT literals + show the value

Capture literal VALUES at parse onto Command::SqlInsert (no grammar change,
no reparse); validate them against column types before the still-verbatim
insert (reusing impl_value_for for DSL-parity wording); read them in the
error enricher so a constraint error names the real value. Execution,
auto-fill, and command identity unchanged. Adds run_sql_insert_with_literals
(runtime path); run_sql_insert stays the no-capture raw entry.

Proven: malformed date 2025/01/15 now refused in advanced-mode SQL; replayed
UNIQUE shows the real value. Tests +3 (expression runs, multi-row, natural
order) + 2 flipped/strengthened. 1930 pass / 0 fail / 0 skip; clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-26 21:58:25 +00:00
parent dc9a4759ce
commit 1d5534b2bd
8 changed files with 312 additions and 22 deletions
+7 -6
View File
@@ -340,17 +340,18 @@ fn replay_constraint_failure_shows_real_names_not_placeholders() {
let AppEvent::ReplayFailed { error, .. } = failed else {
unreachable!()
};
// No unsubstituted placeholders (the safety net + enrichment).
// No unsubstituted placeholders.
assert!(
!error.contains("{table}") && !error.contains("{column}") && !error.contains("{value}"),
"no unsubstituted placeholders; got: {error}"
);
// The real table + column are shown (resolved from the engine
// message). The offending value is NOT shown: replay parses in
// advanced mode → `SqlInsert`, whose values are raw SQL text (ADR-0033
// verbatim execution), not retained typed values — so it degrades to
// the neutral "that value" rather than leaking `{value}`.
// The real table + column are shown (from the engine message), and —
// since ADR-0036 Phase 1 retains the captured literal on the SQL
// INSERT command — the **real offending value** is shown too (it used
// to degrade to the neutral "that value" because `SqlInsert` discarded
// its literals).
assert!(error.contains("T.email"), "names the real table.column; got: {error}");
assert!(error.contains("a@b.com"), "shows the real offending value; got: {error}");
}
#[test]