pub enum BoundExpr {
Show 33 variants
Null,
Integer(i64),
Real(f64),
Text(Vec<u8>),
Blob(Vec<u8>),
Parameter(u32),
Raise {
action: RaiseAction,
message: Option<Vec<u8>>,
computed: Option<Box<BoundExpr>>,
foreign_key: bool,
},
Column {
source: usize,
column: u16,
slot: u16,
affinity: Affinity,
collation: Collation,
},
Rowid {
source: usize,
},
External {
name: Vec<u8>,
arguments: Vec<BoundExpr>,
},
VirtualFunction {
source: usize,
name: Vec<u8>,
arguments: Vec<BoundExpr>,
},
Unary {
op: UnaryOp,
operand: Box<BoundExpr>,
},
Arithmetic {
op: BinaryOp,
left: Box<BoundExpr>,
right: Box<BoundExpr>,
},
Compare {
op: BinaryOp,
left: Box<BoundExpr>,
right: Box<BoundExpr>,
affinity: Option<Affinity>,
collation: Collation,
},
And(Box<BoundExpr>, Box<BoundExpr>),
Or(Box<BoundExpr>, Box<BoundExpr>),
Not(Box<BoundExpr>),
IsNull {
negated: bool,
operand: Box<BoundExpr>,
},
Is {
negated: bool,
left: Box<BoundExpr>,
right: Box<BoundExpr>,
affinity: Option<Affinity>,
collation: Collation,
},
Between {
negated: bool,
operand: Box<BoundExpr>,
low: Box<BoundExpr>,
high: Box<BoundExpr>,
low_affinity: Option<Affinity>,
low_collation: Collation,
high_affinity: Option<Affinity>,
high_collation: Collation,
},
InList {
negated: bool,
operand: Box<BoundExpr>,
list: Vec<BoundExpr>,
affinity: Option<Affinity>,
collation: Collation,
},
Case {
operand: Option<Box<BoundExpr>>,
branches: Vec<(BoundExpr, BoundExpr)>,
otherwise: Option<Box<BoundExpr>>,
comparisons: Vec<(Option<Affinity>, Collation)>,
},
Cast {
operand: Box<BoundExpr>,
affinity: Affinity,
},
Pattern {
negated: bool,
op: PatternOp,
operand: Box<BoundExpr>,
pattern: Box<BoundExpr>,
escape: Option<Box<BoundExpr>>,
},
Time {
func: TimeFunc,
arguments: Vec<BoundExpr>,
},
Math {
func: MathFunc,
arguments: Vec<BoundExpr>,
},
Json {
func: JsonFunc,
arguments: Vec<BoundExpr>,
},
Function {
func: ScalarFunc,
arguments: Vec<BoundExpr>,
collation: Collation,
},
WindowRef {
slot: usize,
collation: Option<Collation>,
},
Aggregate {
slot: usize,
collation: Option<Collation>,
},
SorterColumn {
column: u16,
},
Subquery {
id: usize,
kind: SubqueryKind,
negated: bool,
operand: Option<Box<BoundExpr>>,
block: Box<BoundSelect>,
affinity: Option<Affinity>,
collation: Collation,
},
Collate {
operand: Box<BoundExpr>,
collation: Collation,
},
}Expand description
A bound expression, with every name resolved and every rule decided.
Variants§
Null
A NULL literal.
Integer(i64)
An integer literal.
Real(f64)
A real literal.
Text(Vec<u8>)
A text literal.
Blob(Vec<u8>)
A blob literal.
Parameter(u32)
A bound parameter.
Raise
RAISE(...) inside a trigger body.
It is an expression in the grammar and it never produces a value: every
action either stops the statement or abandons the row. It is bound as one
anyway because that is where it is written - SELECT RAISE(ABORT, 'no') WHERE new.x < 0 puts it in a result column, guarded by a WHERE - and a
statement form would not reach that position.
Fields
action: RaiseActionWhich action.
computed: Option<Box<BoundExpr>>The message, when it is any other expression.
Evaluated when the RAISE fires, and read as text: NULL is an empty
message and a number is its text, which is what SQLite reports. A
literal stays in message, so the bodies the binder synthesises for
foreign keys compile as they always have.
Column
A column of a FROM term.
Fields
slot: u16Which slot of the row’s record holds it.
Not the same number as the declared position once the table has a
VIRTUAL generated column: that column takes no slot, so every
column after it sits one place earlier in the record. Carrying both
is what keeps an index key - which names declared positions - and a
record read - which names slots - from being confused for each
other.
Rowid
The rowid of a FROM term.
External
A call to a function an application registered.
It carries the name and nothing else: the binder resolved that such a function exists and takes this many arguments, and the machine looks up what it does when it runs. A closure in a bound tree would make the tree depend on who was holding it.
VirtualFunction
One of a module’s auxiliary functions, written f(table, ...).
It reads the module’s cursor rather than a column, which is why it
names a FROM term instead of taking the table as an argument: bm25
wants to know which phrase matched where in the row the cursor is on,
and no column carries that.
Fields
Unary
A unary operator.
Arithmetic
An arithmetic, bitwise or concatenation operator.
Fields
Compare
A comparison, with the affinity and collation it applies.
Fields
And(Box<BoundExpr>, Box<BoundExpr>)
AND, with three-valued semantics.
Or(Box<BoundExpr>, Box<BoundExpr>)
OR, with three-valued semantics.
Not(Box<BoundExpr>)
NOT.
IsNull
IS NULL or NOT NULL.
Is
IS / IS NOT, which never yields NULL.
Fields
Between
BETWEEN, kept as one node so its operand is evaluated once.
Each bound has its own affinity and collation (task-2088). SQLite
codes x BETWEEN lo AND hi as x >= lo AND x <= hi, and each of those
comparisons takes its rules from its own two operands. One pair of rules
taken from x and lo ignored hi entirely: measured against 3.53.4,
s BETWEEN 'a' AND 'B' COLLATE NOCASE returned no rows where SQLite
returns a and b, and '5' BETWEEN 1 AND CAST('9' AS INTEGER)
answered 0 where SQLite applies the upper bound’s INTEGER affinity and
answers 1.
Fields
InList
IN over a value list.
Fields
Case
CASE.
Fields
comparisons: Vec<(Option<Affinity>, Collation)>The affinity and collation each WHEN comparison uses in the base
form, one per branch, and empty in the searched form.
SQLite codes CASE x WHEN y as x = y for each branch, so each
comparison takes its rules from x and its own y through
comparison_rules. One collation taken from x for every branch
made CASE 'a' WHEN 'A' COLLATE NOCASE answer 0 where 3.53.4
answers 1, and no affinity made CASE id WHEN '1' answer 0 on an
INTEGER column where 3.53.4 answers 1 (task-2094).
Cast
CAST.
Fields
Pattern
LIKE, GLOB, REGEXP or MATCH.
Fields
Time
A date or time function call.
Math
A math function call.
It is its own variant rather than a Function with a different tag
because a math function has no collation to carry: none of them
compares anything.
Json
A JSON function call.
Its own variant for the reason JsonFunc is its own enum: every one of
these can fail, and every one of them reads the JSON mark its arguments
carry. A Function node promises neither.
Function
A scalar function call.
Fields
func: ScalarFuncWhich function.
WindowRef
A reference to a window value computed for this row.
Fields
collation: Option<Collation>The explicit collation the call’s arguments carry, if one does.
The arguments live in the block’s window list, out of reach of
BoundExpr::explicit_collation, so the binder copies the answer
here (task-2094). The PARTITION BY and the ORDER BY of the
window do not count: 3.53.4 answers max(s) OVER (PARTITION BY s COLLATE NOCASE) = 'C' with 0.
Aggregate
A reference to an aggregate accumulator computed for this row group.
Fields
collation: Option<Collation>The explicit collation the call’s arguments carry, if one does.
SQLite marks the aggregate call EP_Collate from its arguments, so
max(s COLLATE NOCASE) = 'C' compares with NOCASE. The arguments
live in the binder’s aggregate list, out of reach of
BoundExpr::explicit_collation, so the binder copies the answer
here (task-2094). An argument’s ORDER BY and a FILTER do not
count: 3.53.4 answers group_concat(s ORDER BY s COLLATE NOCASE) = 'A,A,B,B,C,C' with 0.
SorterColumn
A column of the current sorter row, used after an ORDER BY sort.
Subquery
A nested query used as a value: EXISTS, a scalar, or the right side
of an IN.
The three are one variant because they differ only in what they do with the block’s rows, and the machinery underneath - a store, filled once or once per outer row depending on correlation - is identical. Splitting them would mean three copies of the correlation rule, which is the part that is easy to get wrong.
Fields
id: usizeThe statement-wide number of this subquery, so the compiler can build it once even when the expression is compiled twice.
kind: SubqueryKindWhat the rows are used for.
block: Box<BoundSelect>The block.
Collate
An explicit COLLATE on an expression that is not a column.
The node exists so the collation survives to the comparison that uses
it. Attaching it only to columns loses x = 'BLUE' COLLATE BINARY,
where the operand carrying the collation is a literal - and losing it
means the column’s own collation wins and the comparison quietly
answers a different question.
Implementations§
Source§impl BoundExpr
impl BoundExpr
Sourcepub fn collation(&self) -> Option<Collation>
pub fn collation(&self) -> Option<Collation>
Returns the collation this expression carries, if it has one.
SQLite’s sqlite3ExprCollSeq, in its order: a column has its declared
collation, a CAST and a unary + have their operand’s, and any other
expression has the explicit collation of an operand, if one has one.
So CAST(n AS TEXT) = 'A' on a NOCASE column n compares with NOCASE,
and n || '' = 'A' compares with BINARY. Measured against 3.53.4
(task-2089): CAST(n AS TEXT) = 'A' and +n = 'A' answered 0 here
where SQLite answers 1.
Sourcepub fn explicit_collation(&self) -> Option<Collation>
pub fn explicit_collation(&self) -> Option<Collation>
Returns the collation an explicit COLLATE forced on this expression.
This is not the same question as BoundExpr::collation. A column
declared COLLATE NOCASE has an implicit collation; x COLLATE BINARY
has an explicit one, and an explicit collation on either side of a
comparison beats an implicit one on the other side.
An explicit collation reaches up through every operator and function
argument (task-2089). SQLite marks a node EP_Collate when any
operand has the mark, and reads the collation from the first operand
that has it, left first. This used to look only at the top node, so
('a' COLLATE NOCASE || 'x') = 'AX' compared with BINARY and answered
0 where 3.53.4 answers 1, and 'a' COLLATE BINARY || 'b' COLLATE NOCASE has to answer BINARY because the left operand is asked first.
BoundExpr::children lists operands in SQLite’s order for every node
whose value is text. A scalar subquery has no children here, and SQLite
does not carry a COLLATE out of one either. An aggregate or window
call has no children here either, because its arguments live in the
block’s lists, so its reference carries the answer for them: see
[explicit_argument_collation].
Source§impl BoundExpr
impl BoundExpr
Sourcepub fn affinity(&self) -> Option<Affinity>
pub fn affinity(&self) -> Option<Affinity>
Returns the affinity this expression has as an operand.
SQLite’s rule: a column has its own affinity, a cast has the cast’s, a parenthesised expression has its operand’s, and everything else has none. “None” is a real answer here, not a missing one.
Sourcepub fn is_constant(&self) -> bool
pub fn is_constant(&self) -> bool
Returns whether the expression reads any column or aggregate.
Sourcepub fn columns_used(&self, into: &mut Vec<u16>)
pub fn columns_used(&self, into: &mut Vec<u16>)
Returns which declared column positions the expression reads.
The declared position rather than the record slot, because the callers that ask - a generated column’s dependency order, and the index-key matcher - both think in declared positions.
Sourcepub fn children(&self) -> Vec<&BoundExpr>
pub fn children(&self) -> Vec<&BoundExpr>
Returns every sub-expression one expression holds, in no order.
The match is exhaustive on purpose: there is no _ arm, so a variant
added later is a compilation error here rather than a silently unvisited
subtree. That matters because the covering-index decision is built on
this walk, and a missed subtree there would be a column read from an
index that does not hold it.
A subquery’s block is deliberately not a child. It is a query of its own with its own FROM terms, and the only thing about it that concerns an enclosing term is which of that term’s columns it correlates to - which the block records separately and which the caller reads.
Sourcepub fn children_mut(&mut self) -> Vec<&mut BoundExpr>
pub fn children_mut(&mut self) -> Vec<&mut BoundExpr>
Returns every sub-expression one expression holds, mutably.
The mirror of BoundExpr::children, and exhaustive for the same
reason: a variant added later is a compilation error here rather than a
subtree some rewrite silently skips. crate::rewrite is the only caller
and the trigger firing point is why it exists - a body’s OLD and NEW
reads are replaced by the values the row actually holds, and one missed
subtree there is a trigger that reads a NULL where a value was.
A subquery’s block is not a child here either, for the reason it is
not one there: it is a query of its own. crate::rewrite descends into
it separately, because a correlated block is exactly where a foreign
key’s NOT EXISTS (SELECT 1 FROM parent WHERE p.k = NEW.c) keeps its
NEW.
Sourcepub fn block_mut(&mut self) -> Option<&mut BoundSelect>
pub fn block_mut(&mut self) -> Option<&mut BoundSelect>
Returns the block a subquery expression holds, when it is one.
Separate from BoundExpr::children_mut because a block is not a
sub-expression: it is a query, with its own FROM terms and its own
scope. A rewrite that treats it as one would run over the wrong tree.
Sourcepub fn columns_read(&self, source: usize, into: &mut ColumnUse)
pub fn columns_read(&self, source: usize, into: &mut ColumnUse)
Records which of one FROM term’s columns this expression reads.
A correlated subquery makes the answer unknowable from here - the block is a query of its own and could read any column of the term it correlates to - so it is recorded as opaque rather than guessed at. @param source - the FROM term to look for @param into - what has been found so far