fix: SQL function-call names not flagged as columns
Two layered fixes for the same bug class: `select sum(Age) from
Customers` runs cleanly at the engine but the validator was treating
`sum` as a column reference. The grammar already admits function
calls structurally (ADR-0031 §1: "it does not know which names are
aggregates"); the validator needed to match.
1. Walker (schema_existence_diagnostics): the bare-column check on
`sql_expr_ident` items now skips when the ident is immediately
followed by `(` — it's a function-call name, not a column. New
helper `is_followed_by_call_args` mirrors the existing
`is_followed_by_qualified_ref` guard. Args inside the call are
ordinary expressions and their idents still flow through the
normal bare-column check on subsequent iterations.
Cascades to: the [ERR] validity indicator (verdict derived from
diagnostics), the red highlight overlay (renderer overlays
diagnostic spans), and the ambient hint at complete inputs (the
diagnostic-driven `pick_hint_diagnostic` path).
2. Typing-time (invalid_ident_at_cursor_in_mode): at any
`sql_expr_ident` position the partial could resolve to either a
column reference or a function-call name; without lookahead for a
trailing `(` we can't tell. The check now returns early at
`sql_expr_ident` positions. Submit-time still catches genuine
column typos: the schema-existence diagnostic only skips when the
ident *is* followed by `(`, so a bare unknown ident still trips
and the hint surfaces it via `pick_hint_diagnostic`.
Trade-off worth flagging: typing `select Agx` (no FROM yet) is now
silent until FROM is added; previously the typing-time path flagged
it as "No such column". This makes typing-time consistent with
submit-time — the schema-existence pass already silently skips
no-FROM expressions ("no FROM in scope — engine catches"). For any
expression-with-scope (SELECT with FROM, WHERE, etc.) the
diagnostic-driven hint still fires for column typos; new test pins
this.
Tests added (5): walker positive (every standard aggregate plus
count(*), count(distinct …), nested calls, WHERE-clause functions,
and non-aggregate functions), walker negative (unknown column inside
call args), walker negative for DISTINCT-shielded arg, typing-time
positive (no false flag on partial function name), typing-time
trade-off lockdown (genuine column typo still hints when FROM is in
scope).
No grammar change; no ADR amendment (the fix matches ADR-0031 §1's
existing posture). Full suite 2040 passed / 0 failed / 0 unexpected
skips. Clippy clean.
This commit is contained in:
@@ -1123,6 +1123,21 @@ pub fn invalid_ident_at_cursor_in_mode(
|
||||
if expected.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Issue #6 follow-on: at a SQL expression position the partial
|
||||
// could resolve to either a column reference *or* a function-call
|
||||
// name (the grammar admits the call shape structurally — see
|
||||
// sql_expr.rs's `CALL_ARGS` comment, "it does not know which
|
||||
// names are aggregates"). Without lookahead for a trailing `(`,
|
||||
// we can't tell here, so we don't flag — submit-time validation
|
||||
// still catches a genuine column typo via the `unknown_column`
|
||||
// diagnostic, which only skips when the ident *is* followed by
|
||||
// `(` (i.e. it really is a function call).
|
||||
let has_sql_expr_slot = expected.iter().any(|e| {
|
||||
matches!(e, Expectation::Ident { role: "sql_expr_ident", .. })
|
||||
});
|
||||
if has_sql_expr_slot {
|
||||
return None;
|
||||
}
|
||||
// Find every schema-listable source in the expected list.
|
||||
let sources: Vec<IdentSource> = expected
|
||||
.iter()
|
||||
|
||||
Reference in New Issue
Block a user