test+docs: 3k Phase-3 verification sweep — e2e DML + filled cross-cut matrix

Sub-phase 3k of ADR-0033. Adds the Tier-3 end-to-end DML suite (tests/sql_dml_e2e.rs) and the cross-cut gap-fill tests, fills the verification matrix (every row a verified file::function), and produces the phase-exit report.

- tests/sql_dml_e2e.rs: INSERT…SELECT cross-table, all-ten-type multi-row INSERT + RETURNING type recovery, UPDATE-with-subquery-in-SET, cascade DELETE, UPSERT round-trip, RETURNING x3, history.log replay, OOS rejections (full §13 table), validity-indicator-from-SQL-DML.
- walker/mod.rs, highlight.rs, completion.rs, input_render.rs: inherited-diagnostic, DML-keyword highlight, INSERT INTO completion, and advanced-mode DML hint-panel cross-cuts.
- Matrix correction (user-confirmed): predicate warnings fire on row-scoped DML slots; INSERT VALUES has no row scope (ADR-0033 §8.4).
- Auto-snapshot row marked N/A (user-confirmed): ADR-0006 unimplemented for both paths; deferred.

/runda round: added an advanced-mode DML hint-panel test (A6 was attributed to simple-mode prose under the §8 advanced heading); extended OOS coverage to the full ADR-0033 §13 table (OOS-5 INDEXED BY / OOS-6 multi-statement) + a trailing-semicolon guard.

1645 passing / 0 failing / 0 skipped / 1 ignored. Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-23 22:26:04 +00:00
parent a5cdb00a86
commit 380c4238ef
7 changed files with 1150 additions and 84 deletions
+14
View File
@@ -1896,6 +1896,20 @@ mod tests {
assert_eq!(cs, vec!["Customers".to_string(), "Orders".to_string()]);
}
#[test]
fn insert_into_offers_table_names_at_target_slot() {
// 3k cross-cut (matrix A3): after `insert into ` the target
// table slot completes to the schema's table names.
let cache = SchemaCache {
tables: vec!["Customers".to_string(), "Orders".to_string()],
columns: vec![],
relationships: vec![],
..SchemaCache::default()
};
let cs = cands_with("insert into ", 12, &cache);
assert_eq!(cs, vec!["Customers".to_string(), "Orders".to_string()]);
}
#[test]
fn schema_cache_offers_column_names_at_column_slot() {
let cache = SchemaCache {
+34
View File
@@ -397,4 +397,38 @@ mod tests {
);
}
}
#[test]
fn sql_dml_keywords_classified() {
// ADR-0030 §8 / ADR-0033 — the DML entry words and clause
// keywords (INSERT / INTO / VALUES / ON / CONFLICT /
// RETURNING / UPDATE / SET / DELETE / FROM) all get the
// Keyword class in Advanced mode. 3k cross-cut: the
// ambient highlighter covers the DML surface, not just
// SELECT.
let keywords_of = |input: &'static str| -> Vec<&'static str> {
run_advanced(input)
.into_iter()
.filter(|(_, _, c)| *c == HighlightClass::Keyword)
.map(|(s, e, _)| &input[s..e])
.collect()
};
let insert = keywords_of(
"insert into t (a) values (1) on conflict (a) do update set a = excluded.a returning a",
);
for kw in ["insert", "into", "values", "on", "conflict", "do", "update", "set", "returning"] {
assert!(insert.contains(&kw), "INSERT/UPSERT: missing `{kw}`; got {insert:?}");
}
let update = keywords_of("update t set a = 1 where id = 2 returning a");
for kw in ["update", "set", "where", "returning"] {
assert!(update.contains(&kw), "UPDATE: missing `{kw}`; got {update:?}");
}
let delete = keywords_of("delete from t where id = 1 returning *");
for kw in ["delete", "from", "where", "returning"] {
assert!(delete.contains(&kw), "DELETE: missing `{kw}`; got {delete:?}");
}
}
}
+69
View File
@@ -5154,6 +5154,75 @@ mod tests {
);
}
#[test]
fn sql_delete_where_unknown_column_is_error() {
// 3k cross-cut (matrix I3): schema-existence fires on a
// top-level DELETE's WHERE, not only on INSERT/UPDATE slots.
let schema = schema_with("t", &[("id", Type::Int), ("v", Type::Int)]);
let diags = diag_keys("delete from t where nonexistent = 1", &schema);
assert!(
diags.iter().any(|d| d.contains("no such column")),
"expected unknown_column on the DELETE WHERE; got {diags:?}",
);
}
#[test]
fn sql_update_where_unknown_column_is_error() {
// 3k cross-cut (matrix I3): schema-existence fires on a
// top-level UPDATE's WHERE (the SET case is covered by
// `sql_update_unknown_set_column_is_error`).
let schema = schema_with("t", &[("id", Type::Int), ("v", Type::Int)]);
let diags = diag_keys("update t set v = 1 where nonexistent = 1", &schema);
assert!(
diags.iter().any(|d| d.contains("no such column")),
"expected unknown_column on the UPDATE WHERE; got {diags:?}",
);
}
#[test]
fn sql_delete_where_like_numeric_warns() {
// 3k cross-cut (matrix I6): the like_numeric predicate
// warning fires on a DELETE's WHERE (the prior coverage was
// SELECT / HAVING / CASE only).
let schema = schema_with("t", &[("id", Type::Int), ("count", Type::Int)]);
let diags = diag_keys("delete from t where count like 5", &schema);
assert!(
diags.iter().any(|d| d.contains("LIKE")),
"expected like_numeric warning on the DELETE WHERE; got {diags:?}",
);
}
#[test]
fn sql_update_set_case_predicate_warns() {
// 3k cross-cut (matrix I7, corrected): a predicate warning
// fires inside a CASE in an UPDATE SET RHS — a row-scoped
// DML sql_expr slot (ADR-0033 §8.4 — "WHERE and CASE").
let schema = schema_with("t", &[("id", Type::Int), ("v", Type::Int)]);
let diags = diag_keys(
"update t set v = case when v = NULL then 1 else 0 end where id = 1",
&schema,
);
assert!(
diags.iter().any(|d| d.contains("IS NULL")),
"expected eq_null warning inside the UPDATE SET CASE; got {diags:?}",
);
}
#[test]
fn insert_select_where_predicate_warns() {
// 3k cross-cut (matrix I7, corrected): the predicate-warning
// pass fires on the WHERE of an INSERT … SELECT row source
// (`b.total` is real, so `like 5` is a numeric LIKE). Plain
// INSERT VALUES carries no row scope, so the realizable
// claim is the INSERT … SELECT slot, not VALUES.
let schema = two_table_schema(); // a(id,name), b(id,total real)
let diags = diag_keys("insert into a (id) select id from b where total like 5", &schema);
assert!(
diags.iter().any(|d| d.contains("LIKE")),
"expected like_numeric on the INSERT…SELECT WHERE; got {diags:?}",
);
}
#[test]
fn cte_name_is_valid_table_source() {
let schema = schema_with("base", &[("id", Type::Int)]);
+32
View File
@@ -903,6 +903,38 @@ mod tests {
}
}
#[test]
fn advanced_mode_ambient_offers_dml_slot_candidates() {
// 3k cross-cut (matrix A6, advanced surface): the ambient hint
// panel surfaces SQL DML slot assistance in Advanced mode —
// column candidates at an `UPDATE … SET` LHS slot and inside an
// `INSERT … (` column list. (The simple-mode DSL value-slot
// prose is a separate surface; this pins the §8 advanced claim.)
use crate::dsl::types::Type;
let cache = schema_with_columns(
"Customers",
&[("id", Type::Int), ("Name", Type::Text)],
);
let set_slot = "update Customers set ";
match ambient_hint_in_mode(set_slot, set_slot.len(), None, &cache, Mode::Advanced) {
Some(AmbientHint::Candidates { items, .. }) => assert!(
items.iter().any(|c| c.text == "Name" || c.text == "id"),
"UPDATE SET slot should offer column candidates; got {items:?}",
),
other => panic!("expected candidates at the UPDATE SET slot, got {other:?}"),
}
let col_list = "insert into Customers (";
match ambient_hint_in_mode(col_list, col_list.len(), None, &cache, Mode::Advanced) {
Some(AmbientHint::Candidates { items, .. }) => assert!(
items.iter().any(|c| c.text == "Name" || c.text == "id"),
"INSERT column-list slot should offer column candidates; got {items:?}",
),
other => panic!("expected candidates in the INSERT column list, got {other:?}"),
}
}
#[test]
fn simple_mode_ambient_does_not_surface_sql_candidates() {
// The simple-mode entry point keeps gating SQL — advanced