pub enum Expr {
Show 27 variants
Literal(Literal),
Column(ColumnName),
NamedArg {
name: String,
expr: Box<Expr>,
},
Variadic(Box<Expr>),
Placeholder(u16),
Binary {
lhs: Box<Expr>,
op: BinOp,
rhs: Box<Expr>,
},
Unary {
op: UnOp,
expr: Box<Expr>,
},
Cast {
expr: Box<Expr>,
target: CastTarget,
},
FieldAccess {
base: Box<Expr>,
field: String,
},
IsNull {
expr: Box<Expr>,
negated: bool,
},
BoolTest {
expr: Box<Expr>,
value: Option<bool>,
negated: bool,
},
FunctionCall {
name: String,
args: Vec<Expr>,
},
AggregateOrdered {
call: Box<Expr>,
order_by: Vec<OrderBy>,
distinct: bool,
filter: Option<Box<Expr>>,
},
Like {
expr: Box<Expr>,
pattern: Box<Expr>,
negated: bool,
case_insensitive: bool,
},
WindowFunction {
name: String,
args: Vec<Expr>,
partition_by: Vec<Expr>,
order_by: Vec<(Expr, bool, Option<bool>)>,
frame: Option<WindowFrame>,
null_treatment: NullTreatment,
filter: Option<Box<Expr>>,
},
ScalarSubquery(Box<SelectStatement>),
Exists {
subquery: Box<SelectStatement>,
negated: bool,
},
InSubquery {
expr: Box<Expr>,
subquery: Box<SelectStatement>,
negated: bool,
},
RowInSubquery {
row: Vec<Expr>,
subquery: Box<SelectStatement>,
negated: bool,
},
RowCmpSubquery {
row: Vec<Expr>,
op: BinOp,
subquery: Box<SelectStatement>,
},
InList {
expr: Box<Expr>,
list: Vec<Expr>,
negated: bool,
},
Extract {
field: ExtractField,
source: Box<Expr>,
},
Array(Vec<Expr>),
ArraySubscript {
target: Box<Expr>,
index: Box<Expr>,
},
ArraySlice {
target: Box<Expr>,
lo: Option<Box<Expr>>,
hi: Option<Box<Expr>>,
},
AnyAll {
expr: Box<Expr>,
op: BinOp,
array: Box<Expr>,
is_any: bool,
},
Case {
operand: Option<Box<Expr>>,
branches: Vec<(Expr, Expr)>,
else_branch: Option<Box<Expr>>,
},
}Variants§
Literal(Literal)
Column(ColumnName)
NamedArg
v7.39 (read01 round 77) — a NAMED call argument (f(x := 1), or the
older f(x => 1) spelling). Which slot the name fills depends on the
callee’s declared parameter names, and a user function’s live in the
catalog — which the parser cannot see. So the name rides along in the
tree and the evaluator, which has the catalog, does the reordering.
Appears only inside a FunctionCall’s argument list.
Variadic(Box<Expr>)
v7.39 (read01 round 100) — VARIADIC <array> as the last argument of a
variadic function call (concat_ws(',', VARIADIC ARRAY[…])). The inner
expression evaluates to an array whose elements the evaluator splices
into the call as individual trailing arguments. Appears only inside a
FunctionCall’s argument list.
Placeholder(u16)
v6.1.1 — $N parameter placeholder for the extended query
protocol. The number is 1-based per PostgreSQL convention.
Evaluation looks up params[N-1] from the prepared-statement
bind buffer; out-of-range indices raise a runtime error
(same shape as a column-not-found miss).
Binary
Unary
Cast
PG-style expr::TYPE cast. v1.3 supports VECTOR, INT, BIGINT, FLOAT,
TEXT, BOOL targets; engine coerces at evaluation time.
FieldAccess
v7.38 (read01, T9) — composite field access (expr).field. base
evaluates to a composite/record value (an explicit ROW(...), a
whole-row reference, or a composite-returning function); field names
the member (f1..fN positional for an anonymous ROW, or the base
column names for a whole-row). Only the parenthesised form reaches
here — a bare a.b is parsed as a qualified column reference.
IsNull
Postfix IS NULL / IS NOT NULL. Returns BOOL.
BoolTest
v7.39 (round 328, V45) — x IS [NOT] TRUE | FALSE | UNKNOWN, the
three-valued boolean tests. value is Some(true) for TRUE,
Some(false) for FALSE and None for UNKNOWN.
These used to be lowered to CASE / IS NULL right in the parser.
The semantics were right, but the AST then had no way to say what
the user wrote, so every renderer printed the lowering:
CHECK ((a > 1) IS TRUE) came back as
CHECK ((CASE WHEN (a > 1) THEN TRUE ELSE FALSE END)), and a
dumped view lost the form too.
FunctionCall
Function call name(args...). v1.4 supports a small built-in set
(length, upper, lower, abs, coalesce); unknown names error at eval
time so the parser stays open for v1.5 aggregates.
AggregateOrdered
v7.24 (mailrs round-16 A) — an aggregate call with an
internal ordering: array_agg(x ORDER BY y DESC NULLS LAST).
Wraps the plain Expr::FunctionCall so every existing
FunctionCall consumer stays untouched; only the aggregate
executor (and the expression walkers) know the wrapper.
Non-aggregate evaluation contexts reject it at eval time.
Fields
distinct: boolv7.25 (round-17) — COUNT(DISTINCT x) /
string_agg(DISTINCT s, ','). The wrapper carries every
aggregate modifier so plain FunctionCall stays untouched.
filter: Option<Box<Expr>>v7.32 (mailrs round-29) — agg(args) FILTER (WHERE cond).
Only the rows where cond is true contribute to this
aggregate (SQL:2003 T612 / PG 9.4). Carried as a first-class
modifier — NOT desugared to agg(CASE WHEN cond THEN arg END), which is faithful for NULL-ignoring aggregates but
WRONG for array_agg (it would collect a NULL per excluded
row). The executor instead skips excluded rows before
accumulation, which is correct for every aggregate.
Like
SQL LIKE predicate. pattern evaluates to text at runtime;
wildcards are % (any run) and _ (one char), backslash escapes
the next char (so \% matches a literal %).
Fields
WindowFunction
v4.12 window function call: name(args) OVER (PARTITION BY ... ORDER BY ...). Supports ROW_NUMBER / RANK /
DENSE_RANK and the partition-aware aggregates SUM /
AVG / COUNT / MIN / MAX. The window frame defaults to “entire partition” for
unordered windows and “from start of partition through
current row” for ordered windows — no explicit ROWS /
RANGE clause in v4.12 MVP.
Fields
order_by: Vec<(Expr, bool, Option<bool>)>v7.24.1 — third slot: explicit NULLS FIRST/LAST
(None = PG default, same contract as OrderBy).
frame: Option<WindowFrame>v4.20 explicit frame. None means “use the default”:
whole-partition when unordered, running aggregate from
partition start through current row when ordered.
null_treatment: NullTreatmentv6.4.2 — IGNORE NULLS / RESPECT NULLS modifier on
LAG / LEAD / FIRST_VALUE / LAST_VALUE. Default is
Respect (PG / ANSI default — NULLs participate). Other
window functions ignore this flag.
ScalarSubquery(Box<SelectStatement>)
v4.10 scalar subquery — (SELECT ...) used in expression
position. Must return exactly one row × one column at eval
time; the engine errors out otherwise. Uncorrelated only —
the inner SELECT cannot reference outer columns.
Exists
v4.10 [NOT] EXISTS (SELECT ...). Returns Bool. Inner
projection is ignored; only row-count matters.
InSubquery
v4.10 expr [NOT] IN (SELECT ...). Inner SELECT must
project exactly one column; membership is tested by Eq
against each row’s value (NULL handling follows ANSI:
NULL ∈ list ⇒ NULL ; otherwise present ⇒ true).
RowInSubquery
(a, b, …) [NOT] IN (SELECT x, y, …) — a row constructor tested
against a multi-column subquery. Row comparisons against a list
decompose to OR-of-AND at parse time, but the subquery form can’t
(its rows are only known at runtime), so this survives as its own
node evaluated with PG’s row-comparison three-valued logic.
RowCmpSubquery
(a, b, …) <op> (SELECT x, y, …) — a row constructor compared to a
single-row subquery (=, <>, <, <=, >, >=). Like
RowInSubquery, the literal-RHS form decomposes at parse time but the
subquery form can’t, so it survives as its own node. The subquery
must yield at most one row (zero → NULL, PG scalar-subquery rule).
InList
v7.30.2 (mailrs round-25) — expr [NOT] IN (a, b, …) as a FLAT
list. Both the parser’s literal-list path and the engine’s
IN-subquery materialisation used to desugar into a left-deep
OR-Eq chain, so expression depth scaled with the element count
— a 24k-row subquery result overflowed the 2 MiB worker stack
(recursive eval AND recursive Box drop) and aborted embedding
host processes. The flat node keeps depth constant: eval is an
iterative scan with PG three-valued logic, drop is a Vec drop.
Extract
EXTRACT(<field> FROM <source>) — pull an integer component
out of a DATE or TIMESTAMP. Parsed as its own AST node
because the FROM keyword is what separates the two halves,
not a comma.
Array(Vec<Expr>)
v7.10.10 — ARRAY[expr, expr, …] array constructor. Each
element is evaluated independently; NULLs are allowed.
v7.10 supports only single-dimension TEXT[] semantically;
non-text elements coerce at engine evaluation time when
the surrounding context (column type / cast) makes the
target clear.
ArraySubscript
v7.10.10 — array subscript arr[i]. PG 1-based; the
engine returns NULL for out-of-range indices.
ArraySlice
Array slice arr[lo:hi] — PG 1-based, both ends
inclusive; a missing bound extends to that end of the
array and out-of-range bounds clamp. Returns an array of
the same element type.
AnyAll
v7.10.12 — expr op ANY(arr) and expr op ALL(arr). The
operator is the comparison binary op (Eq / Ne / Lt / …);
the engine desugars: ANY returns true if any element
satisfies; ALL returns true only if every element does.
NULL handling follows PG’s three-valued logic.
Case
v7.13.0 — CASE WHEN <cond> THEN <val> ... ELSE <val> END
(searched form, operand is None) and
CASE <expr> WHEN <val> THEN <val> ... END (simple form,
operand is the lead expression compared against each
branch’s match). Each (when_expr, then_expr) branch
stays as written; engine short-circuits on the first match.
else_branch is None when no ELSE; evaluates to NULL.
mailrs round-5 G9.
Implementations§
Source§impl Expr
impl Expr
Sourcepub fn for_each_subquery_mut<E>(
&mut self,
f: &mut impl FnMut(&mut SelectStatement) -> Result<(), E>,
) -> Result<(), E>
pub fn for_each_subquery_mut<E>( &mut self, f: &mut impl FnMut(&mut SelectStatement) -> Result<(), E>, ) -> Result<(), E>
v7.39 (round 305, V23) — hand every SelectStatement nested
directly inside this expression to f. f receives each nested
statement once; descending further (into that statement’s own
clauses) is the caller’s job, which keeps this walk finite and
lets the caller order the recursion.
The match is deliberately wildcard-free: a new Expr variant
does not compile until it says whether it can carry a subquery.
The row-count resolution pass is built on this, and a shape it
silently failed to visit would leave a LimitExpr::Expr behind —
which every row-count reader would take as “no limit”, i.e. the
whole table. Compile-time exhaustiveness is what rules that out.
Iterative on purpose. Expression trees here get deep (long
boolean chains, big IN lists), and this walk is on the path of
every statement; recursing would add a frame per node to a stack
budget the engine already runs close to — a depth guard that runs
on a deliberately small stack caught exactly that. Depth costs
heap here instead.