2g: advanced-mode highlight + engine.* wiring + matrix tests
Cross-cut verification matrix for ADR-0032 Phase 2 is now fully populated with concrete test references — every row green. Filling the matrix surfaced three real gaps that this commit closes. 1. Advanced-mode syntax highlighting (ADR-0030 §8 matrix row). The `ui.rs` Advanced branch routed through `plain_input_spans`, bypassing the highlight walker entirely. In production SQL keywords past the entry word rendered as plain identifiers. Fix: mode-aware variants of `highlight_runs`, `render_input_runs`, `lex_to_runs`, and `input_diagnostics`; the Advanced render path now uses the highlighted form with `Mode::Advanced`. `plain_input_spans` removed (unused). 2. Engine.* key wiring (ADR-0032 §11.4 / §13 matrix rows + handoff §3.3 follow-up). The four Phase-2 engine.* catalog entries were authored in 2d but never reached: `translate_generic` discarded the engine message and returned a vague catalog entry. Fix: pattern-match the engine message text for the four Phase-2 categories (aggregate misuse, group-by required, compound arity mismatch fallback, scalar-subquery cardinality) inside `translate_generic`, routing each to its engine-neutral catalog entry. 3. Matrix-coverage tests. Thirteen new tests covering the rows that had no explicit coverage: - 3 SQL keyword/operator/CASE highlight tests - 4 engine.* engine-message tests - 3 sql_expr column-completion tests (WHERE, HAVING) - 3 predicate-warning slot tests (CASE, ORDER BY, projection) - 1 all-10-playground-types recovery test (tests/sql_select.rs) Plan document (docs/plans/20260520-adr-0032-phase-2.md) updated: every (TBD) row in the cross-cut matrix replaced with a concrete test file::function reference and a green status marker. Test totals: 1428 → 1441 passing (+13 new). Clippy clean.
This commit is contained in:
+104
-1
@@ -612,9 +612,48 @@ fn translate_already_exists(message: &str, ctx: &TranslateContext) -> FriendlyEr
|
||||
|
||||
// ---- Generic catch-all -----------------------------------------
|
||||
|
||||
fn translate_generic(_message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
fn translate_generic(message: &str, ctx: &TranslateContext) -> FriendlyError {
|
||||
// Engine message is intentionally NOT surfaced — ADR-0002
|
||||
// posture. The catalog provides the abstract wording.
|
||||
//
|
||||
// ADR-0032 §11.5 engine-error translations: pattern-match
|
||||
// the engine's message text for the four Phase-2 cases that
|
||||
// arrive as `SqliteErrorKind::Other` and route each to its
|
||||
// engine-neutral catalog entry. The classifier
|
||||
// intentionally doesn't grow new SqliteErrorKind variants
|
||||
// for these — they share a single fallback bucket and are
|
||||
// distinguished by text pattern at translation time.
|
||||
let lower = message.to_ascii_lowercase();
|
||||
if lower.contains("misuse of aggregate") {
|
||||
return headline_only(t!("engine.aggregate_misuse", name = "?"));
|
||||
}
|
||||
if lower.contains("group by")
|
||||
|| lower.contains("must appear in")
|
||||
{
|
||||
return headline_only(t!("engine.group_by_required"));
|
||||
}
|
||||
if (lower.contains("union")
|
||||
|| lower.contains("intersect")
|
||||
|| lower.contains("except"))
|
||||
&& lower.contains("result columns")
|
||||
{
|
||||
// Last-resort safety net — the pre-flight pass in 2d.1
|
||||
// catches this in most cases; if the engine surfaces it
|
||||
// anyway, route it through the engine-neutral key.
|
||||
return headline_only(t!(
|
||||
"engine.compound_arity_mismatch",
|
||||
op = "set operator"
|
||||
));
|
||||
}
|
||||
if lower.contains("scalar subquery") || lower.contains("more than one row") {
|
||||
return headline_only(t!("engine.scalar_subquery_too_many_rows"));
|
||||
}
|
||||
if lower.contains("recursive")
|
||||
&& (lower.contains("cte") || lower.contains("union"))
|
||||
{
|
||||
return headline_only(t!("engine.recursive_cte_malformed"));
|
||||
}
|
||||
|
||||
let operation = ctx
|
||||
.operation
|
||||
.map_or("operation", Operation::keyword);
|
||||
@@ -1044,4 +1083,68 @@ mod tests {
|
||||
assert_eq!(extract_quoted("column `T.x` already exists"), Some("T.x"));
|
||||
assert_eq!(extract_quoted("no backticks here"), None);
|
||||
}
|
||||
|
||||
// ---- ADR-0032 §11.5 engine.* keys ----
|
||||
|
||||
#[test]
|
||||
fn aggregate_misuse_engine_message_routes_through_catalog() {
|
||||
let err = sqlite(
|
||||
"misuse of aggregate function COUNT()",
|
||||
SqliteErrorKind::Other,
|
||||
);
|
||||
let f = translate(&err, &TranslateContext::default());
|
||||
assert!(
|
||||
f.headline.contains("aggregate"),
|
||||
"expected engine.aggregate_misuse wording; got {}",
|
||||
f.headline,
|
||||
);
|
||||
// Engine name (SQLite) must not appear (ADR-0002 posture).
|
||||
assert!(
|
||||
!f.headline.to_lowercase().contains("sqlite"),
|
||||
"headline leaks engine name: {}",
|
||||
f.headline,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_by_required_engine_message_routes_through_catalog() {
|
||||
let err = sqlite(
|
||||
"column must appear in the GROUP BY clause or be used in an aggregate function",
|
||||
SqliteErrorKind::Other,
|
||||
);
|
||||
let f = translate(&err, &TranslateContext::default());
|
||||
assert!(
|
||||
f.headline.contains("GROUP BY"),
|
||||
"expected engine.group_by_required wording; got {}",
|
||||
f.headline,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compound_arity_engine_message_routes_through_catalog() {
|
||||
let err = sqlite(
|
||||
"SELECTs to the left and right of UNION do not have the same number of result columns",
|
||||
SqliteErrorKind::Other,
|
||||
);
|
||||
let f = translate(&err, &TranslateContext::default());
|
||||
assert!(
|
||||
f.headline.contains("number of columns"),
|
||||
"expected engine.compound_arity_mismatch wording; got {}",
|
||||
f.headline,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scalar_subquery_too_many_rows_routes_through_catalog() {
|
||||
let err = sqlite(
|
||||
"scalar subquery returned more than one row",
|
||||
SqliteErrorKind::Other,
|
||||
);
|
||||
let f = translate(&err, &TranslateContext::default());
|
||||
assert!(
|
||||
f.headline.contains("more than one row"),
|
||||
"expected engine.scalar_subquery_too_many_rows wording; got {}",
|
||||
f.headline,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user