grammar: WHERE-expression fragment + Expr AST + build_expr (ADR-0026 step 2)

The stratified WHERE-expression grammar — or / and / not /
bool_primary / predicate tiers as named `static` Node
fragments, recursing through `Subgrammar`. Covers the six
comparison operators (`<>` and `!=` both NotEq), AND / OR /
NOT, parentheses, LIKE / IN / BETWEEN with optional infix NOT,
and IS [NOT] NULL. `predicate_tail` factors the shared operand
prefix and the infix NOT so the Choice branches discriminate
on a cleanly-failing first token.

New recursive Expr / Predicate / Operand / CompareOp AST in
dsl::command. `build_expr` folds the flat matched-terminal
slice into an Expr — a deterministic recursive descent
mirroring the grammar tiers, with single-child tiers
collapsing. Per ADR-0026 §3 option 1: the walker stays a pure
structural matcher; Expr is assembled only in this
submit-time fold.

Fragment + builder are unit-tested standalone (walk against
&OR_EXPR, then build_expr); not yet wired into any command.
This commit is contained in:
claude@clouddev1
2026-05-18 22:40:52 +00:00
parent f0b2043a39
commit 59e6a541bf
3 changed files with 816 additions and 1 deletions
+77
View File
@@ -239,6 +239,83 @@ pub enum RowFilter {
AllRows,
}
/// A complex WHERE expression (ADR-0026 §4).
///
/// Built by `grammar::expr::build_expr` from the flat
/// matched-terminal slice the walker produces for a `where`
/// clause. The recursion mirrors the stratified expression
/// grammar — `Or` / `And` are n-ary (a flat `a AND b AND c` is
/// one `And` of three children), and single-child precedence
/// tiers collapse so a bare predicate reached through the
/// `or → and → not` layers is just the `Predicate`, not three
/// wrappers.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
/// `a OR b OR …` — at least two children.
Or(Vec<Self>),
/// `a AND b AND …` — at least two children.
And(Vec<Self>),
/// `NOT <expr>`.
Not(Box<Self>),
/// A leaf comparison / match test.
Predicate(Predicate),
}
/// A single comparison or match test inside an [`Expr`]
/// (ADR-0026 §4). Operands are always a column reference or a
/// literal — never a nested expression.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Predicate {
/// `<operand> <op> <operand>` — one of the six comparisons.
Compare {
left: Operand,
op: CompareOp,
right: Operand,
},
/// `<operand> [NOT] LIKE <operand>` — `%` / `_` wildcards.
Like {
target: Operand,
pattern: Operand,
negated: bool,
},
/// `<operand> [NOT] BETWEEN <operand> AND <operand>`.
Between {
target: Operand,
low: Operand,
high: Operand,
negated: bool,
},
/// `<operand> [NOT] IN (<operand>[, …])`.
In {
target: Operand,
items: Vec<Operand>,
negated: bool,
},
/// `<operand> IS [NOT] NULL`.
IsNull { target: Operand, negated: bool },
}
/// A comparison operand — a column reference or a literal
/// (ADR-0026 §1: operands are never nested expressions).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Operand {
Column(String),
Literal(Value),
}
/// The six comparison operators. `<>` and `!=` both parse to
/// `NotEq` — `<>` is standard SQL, `!=` the common variant
/// (ADR-0026 §1).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CompareOp {
Eq,
NotEq,
Lt,
LtEq,
Gt,
GtEq,
}
/// How a `drop relationship` command identifies the relationship
/// to remove. Both forms are accepted; the executor resolves to
/// a single row in the metadata table.