WHERE expressions: wire into update/delete/show data + SQL gen (ADR-0026 steps 3-4)
Wires the stratified WHERE-expression fragment into the three
filter commands and compiles the resulting Expr to SQL.
Grammar (data.rs): the `update` / `delete` `where` clause is
now the expression fragment (`Subgrammar(&expr::OR_EXPR)`) in
place of the single `col = val` slot; `show data` gains an
optional `where <expr>` and an optional `limit <n>` (a
non-negative integer, validated at parse time). The
expression's right-hand operands are a schema-aware
`DynamicSubgrammar` so the hint panel still narrows to the
left column's type (ADR-0026 §8) — but the inner grammar is
permissive: a type-mismatched literal still parses (§7).
AST: `RowFilter::Where{column,value}` -> `RowFilter::Where(Expr)`;
`ShowData` gains `filter: Option<Expr>` and `limit: Option<u64>`.
A `RowFilter::eq` convenience constructor keeps simple-equality
call sites and tests readable.
SQL (db.rs): `compile_expr` lowers an `Expr` to a
parameterised WHERE — every literal a `?` placeholder,
identifiers `quote_ident`-quoted, `<>` for inequality. A
literal compared against a column binds through that column's
type where compatible and falls back to its syntactic shape on
a mismatch (§7 — permissive). `show data ... limit n` emits
`LIMIT ?` with an implicit primary-key `ORDER BY`, so it is a
stable "first n by primary key".
completion.rs: `invalid_ident_at_cursor` no longer mis-flags a
digit-led literal (`1`) as an unknown column now that the
WHERE operand slot also accepts a column reference; a
`ProseOnly` slot suppresses keyword candidates even when the
expected set also carries a column ident.
11 db integration tests cover AND / OR / NOT, BETWEEN, IN,
LIKE, filtered `show data`, and limit ordering; walker and
expr unit tests cover the parse surface. Type-mismatch /
`= NULL` diagnostic flagging (§7 highlight + hint) is the
remaining ADR-0026 piece.
This commit is contained in:
+37
-2
@@ -267,9 +267,20 @@ pub fn candidates_at_cursor_with(
|
||||
Expectation::Ident { source, .. } if source.completes_from_schema()
|
||||
)
|
||||
});
|
||||
// A slot the grammar explicitly marked `ProseOnly`
|
||||
// (`Node::Hinted`) suppresses its keyword candidates
|
||||
// regardless of `has_schema_ident`. The WHERE-expression
|
||||
// operand is exactly this case: it accepts a column
|
||||
// reference *and* a value literal, so the signature
|
||||
// heuristic alone would surface the misleading
|
||||
// `null`/`true`/`false` trio (ADR-0026 §8).
|
||||
let prose_only_slot = matches!(
|
||||
probe.pending_hint_mode,
|
||||
Some(crate::dsl::grammar::HintMode::ProseOnly(_))
|
||||
);
|
||||
if partial_prefix.is_empty()
|
||||
&& is_value_literal_signature(&expected)
|
||||
&& !has_schema_ident
|
||||
&& (prose_only_slot
|
||||
|| (is_value_literal_signature(&expected) && !has_schema_ident))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
@@ -676,6 +687,15 @@ pub fn invalid_ident_at_cursor(
|
||||
return None;
|
||||
}
|
||||
let partial = &input[start..cursor];
|
||||
// A token that starts with a digit cannot be an identifier
|
||||
// (the identifier shape requires a letter or `_` first) —
|
||||
// it is a numeric literal, never an "invalid column".
|
||||
// Without this guard a literal like `1` at a slot that
|
||||
// *also* accepts a column reference — the WHERE-expression
|
||||
// operand (ADR-0026) — is mis-flagged as an unknown column.
|
||||
if partial.starts_with(|c: char| c.is_ascii_digit()) {
|
||||
return None;
|
||||
}
|
||||
let leading = &input[..start];
|
||||
let expected = expected_at(leading);
|
||||
if expected.is_empty() {
|
||||
@@ -1163,6 +1183,21 @@ mod tests {
|
||||
assert!(!cs.contains(&"OrderTotal".to_string()), "got {cs:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn numeric_literal_in_where_is_not_flagged_as_invalid_column() {
|
||||
use crate::dsl::types::Type;
|
||||
// ADR-0026: the WHERE-expression operand accepts a
|
||||
// column reference *or* a literal. A number literal
|
||||
// (`1`) sits at a slot that also expects a column —
|
||||
// it must not be mis-flagged as an unknown column.
|
||||
let cache = schema_with_table("Customers", &[("id", Type::Int)]);
|
||||
let input = "delete from Customers where id=1";
|
||||
assert!(
|
||||
invalid_ident_at_cursor(input, input.len(), &cache).is_none(),
|
||||
"a numeric literal must not be reported as an invalid column",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_into_open_paren_offers_current_table_columns() {
|
||||
use crate::dsl::types::Type;
|
||||
|
||||
Reference in New Issue
Block a user