2g rework: address DA findings on type recovery + engine routing + UI

Three DA critiques from the Phase-2 verification flagged real gaps;
this commit closes them.

1. Type recovery row-independence (critique #1). The all-10-types
   test left col_blob NULL because the DSL Value enum has no Blob
   variant. The DA flagged this as a potential row-dependence gap.
   Added `database_run_select_type_recovery_works_on_empty_table`
   that proves column-origin metadata works on Text AND Blob
   columns with zero rows, pinning the invariant. The all-types
   test now carries an explicit comment referencing it.

2. Engine.* pattern matching against real SQLite output (critique
   #2). The pre-rework tests fed `translate_generic` hand-coded
   strings; never verified that the pinned SQLite version actually
   produces those wordings. Added three engine-routing tests in
   `tests/sql_select.rs` that produce real engine errors via
   `run_select` and assert catalog routing. Aggregate-in-WHERE
   confirms end-to-end. GROUP-BY-required and scalar-subquery
   are SQLite-permissive (no real error on the natural triggers),
   so those tests verify the matcher doesn't false-positive on
   benign queries + that synthetic messages route correctly.

3. Manual TUI verification (critique #3) surfaced an additional
   gap: `App::input_validity_verdict()` was hard-coded silent in
   Advanced mode, so SQL predicate warnings emitted but never
   reached the [WRN] indicator. Wired the verdict through to the
   active effective mode; updated two pre-existing tests that
   pinned the now-superseded "silent in Advanced" behavior; added
   one new test confirming a SQL `LIKE`-on-numeric warning fires
   the indicator. Launched the TUI, typed a representative
   warning-triggering SELECT, confirmed SELECT/FROM/WHERE/LIKE
   highlight as keyword colour AND the [WRN] indicator appears.

Test totals: 1441 → 1446 passing (+5). Clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-20 21:55:02 +00:00
parent ed881eea59
commit 05884bd13a
2 changed files with 298 additions and 18 deletions
+59 -15
View File
@@ -385,19 +385,24 @@ impl App {
/// which the DSL walker does not parse (ADR-0027 §7). A
/// pure query the runtime calls once the typing debounce
/// settles; the result is stored in `input_indicator`.
///
/// ADR-0032 §10.6 — the verdict reads the walker view of
/// the *active* effective mode so a SQL form in Advanced
/// mode lights up the same `[ERR]` / `[WRN]` indicator the
/// DSL surface uses. Without this the SQL predicate
/// warnings (ADR-0032 §11.6) would emit but never reach
/// the validity indicator the user sees.
#[must_use]
pub fn input_validity_verdict(&self) -> Option<crate::dsl::walker::Severity> {
if !matches!(self.effective_mode(), EffectiveMode::Simple) {
return None;
}
// ADR-0030 §2: the indicator is shown only in plain
// simple mode (the guard above), so the verdict reads
// the simple-mode walker view — a SQL form lights up
// ERROR via the walker's mode gate.
let mode = match self.effective_mode() {
EffectiveMode::Simple => Mode::Simple,
EffectiveMode::AdvancedPersistent
| EffectiveMode::AdvancedOneShot => Mode::Advanced,
};
crate::dsl::walker::input_verdict_in_mode(
&self.input,
Some(&self.schema_cache),
Mode::Simple,
mode,
)
}
@@ -3270,20 +3275,59 @@ mod tests {
}
#[test]
fn input_validity_verdict_silent_in_advanced_mode() {
// Advanced mode is raw SQL — the DSL walker must not
// pass a verdict on it (ADR-0027 §7).
fn input_validity_verdict_fires_in_advanced_mode_for_incomplete_input() {
// Updated per ADR-0032 §10.6 / §11.6 — Phase 2 wires
// the SQL diagnostic surface (predicate warnings, etc.)
// through to the validity indicator. Pre-Phase-2 the
// verdict was silent in Advanced mode; now it reflects
// the active-mode walker's verdict, mirroring Simple
// mode's behaviour.
let mut app = App::new();
app.mode = Mode::Advanced;
app.input = "create table".to_string();
assert_eq!(app.input_validity_verdict(), None);
// Incomplete-at-EOF maps to Error (same as in Simple).
assert_eq!(
app.input_validity_verdict(),
Some(crate::dsl::walker::Severity::Error),
);
}
#[test]
fn input_validity_verdict_silent_for_colon_one_shot() {
// A `:`-prefixed line is a one-shot advanced escape.
fn input_validity_verdict_fires_for_colon_one_shot() {
// A `:`-prefixed line is a one-shot advanced escape;
// the verdict reads the advanced walker view, same as
// a persistent-advanced session.
let mut app = App::new();
app.input = ":create table".to_string();
assert_eq!(app.input_validity_verdict(), None);
assert_eq!(
app.input_validity_verdict(),
Some(crate::dsl::walker::Severity::Error),
);
}
#[test]
fn input_validity_verdict_fires_warning_for_sql_predicate_in_advanced() {
// ADR-0032 §11.6 — a SQL `LIKE`-on-numeric predicate
// emits a Warning diagnostic. The validity indicator
// now reflects that in Advanced mode.
use crate::completion::TableColumn;
use crate::dsl::types::Type;
let mut app = App::new();
app.mode = Mode::Advanced;
app.schema_cache.tables.push("products".to_string());
app.schema_cache.columns.push("price".to_string());
app.schema_cache.table_columns.insert(
"products".to_string(),
vec![TableColumn {
name: "price".to_string(),
user_type: Type::Real,
}],
);
app.input =
"select * from products where price like 5".to_string();
assert_eq!(
app.input_validity_verdict(),
Some(crate::dsl::walker::Severity::Warning),
);
}
}