Skip to main content

inillucent_sql/bind/
expr_methods.rs

1//! Methods of `BoundExpr` that walk, classify and describe a bound expression.
2//!
3//! Invariant: **every method here reads a `BoundExpr` and changes nothing but
4//! the `children_mut` walk, and each lists operands in the order SQLite reads
5//! them.** They live apart from the type so the binder's own file stays below
6//! its recorded size.
7
8use super::*;
9
10impl BoundExpr {
11    /// Returns the affinity this expression has as an operand.
12    ///
13    /// SQLite's rule: a column has its own affinity, a cast has the cast's, a
14    /// parenthesised expression has its operand's, and everything else has
15    /// none. "None" is a real answer here, not a missing one.
16    pub fn affinity(&self) -> Option<Affinity> {
17        match self {
18            BoundExpr::Column { affinity, .. } => Some(*affinity),
19            BoundExpr::Cast { affinity, .. } => Some(*affinity),
20            BoundExpr::Generated { affinity, .. } => Some(*affinity),
21            BoundExpr::Rowid { .. } => Some(Affinity::Integer),
22            BoundExpr::Collate { operand, .. } => operand.affinity(),
23            // SQLite marks the `coalesce` it builds for a merged `USING` column
24            // so that it reads the first argument's affinity. Without it
25            // `x = '2.5'` over a REAL `x` merged by a `FULL JOIN` compared the
26            // text with no conversion and matched nothing.
27            BoundExpr::Function {
28                func: ScalarFunc::UsingCoalesce,
29                arguments,
30                ..
31            } => arguments.first().and_then(BoundExpr::affinity),
32            // SQLite gives a scalar subquery the affinity of its first result
33            // column, so `(SELECT a FROM t) = 3` over a TEXT column `a` applies
34            // TEXT affinity to the 3.
35            BoundExpr::Subquery {
36                kind: SubqueryKind::Scalar,
37                block,
38                ..
39            } => block.scalar_affinity(),
40            _ => None,
41        }
42    }
43
44    /// Returns whether the expression reads any column or aggregate.
45    pub fn is_constant(&self) -> bool {
46        match self {
47            BoundExpr::Null
48            | BoundExpr::Integer(_)
49            | BoundExpr::Real(_)
50            | BoundExpr::Text(_)
51            | BoundExpr::Blob(_)
52            | BoundExpr::Parameter(_) => true,
53            // RAISE never produces a value, so it is not constant: folding it
54            // away would delete the abort it exists to perform.
55            BoundExpr::Raise { .. }
56            | BoundExpr::Column { .. }
57            | BoundExpr::Generated { .. }
58            | BoundExpr::Rowid { .. }
59            | BoundExpr::External { .. }
60            | BoundExpr::VirtualFunction { .. }
61            | BoundExpr::Aggregate { .. }
62            | BoundExpr::WindowRef { .. }
63            | BoundExpr::SorterColumn { .. } => false,
64            BoundExpr::Unary { operand, .. } => operand.is_constant(),
65            BoundExpr::Collate { operand, .. } => operand.is_constant(),
66            BoundExpr::Json { arguments, .. } => arguments.iter().all(BoundExpr::is_constant),
67            BoundExpr::Not(operand) => operand.is_constant(),
68            BoundExpr::IsNull { operand, .. } => operand.is_constant(),
69            BoundExpr::Cast { operand, .. } => operand.is_constant(),
70            BoundExpr::Arithmetic { left, right, .. }
71            | BoundExpr::Compare { left, right, .. }
72            | BoundExpr::Is { left, right, .. } => left.is_constant() && right.is_constant(),
73            BoundExpr::And(left, right) | BoundExpr::Or(left, right) => {
74                left.is_constant() && right.is_constant()
75            }
76            BoundExpr::Between {
77                operand, low, high, ..
78            } => operand.is_constant() && low.is_constant() && high.is_constant(),
79            BoundExpr::InList { operand, list, .. } => {
80                operand.is_constant() && list.iter().all(BoundExpr::is_constant)
81            }
82            BoundExpr::Case {
83                operand,
84                branches,
85                otherwise,
86                ..
87            } => {
88                operand.as_ref().is_none_or(|e| e.is_constant())
89                    && branches
90                        .iter()
91                        .all(|(when, then)| when.is_constant() && then.is_constant())
92                    && otherwise.as_ref().is_none_or(|e| e.is_constant())
93            }
94            BoundExpr::Pattern {
95                operand,
96                pattern,
97                escape,
98                ..
99            } => {
100                operand.is_constant()
101                    && pattern.is_constant()
102                    && escape.as_ref().is_none_or(|e| e.is_constant())
103            }
104            BoundExpr::Function { arguments, .. }
105            | BoundExpr::Math { arguments, .. }
106            | BoundExpr::Time { arguments, .. } => arguments.iter().all(BoundExpr::is_constant),
107            // A subquery is never constant. It may read no column of the query
108            // that encloses it, but it reads the database, and hoisting it out
109            // of a loop is the compiler's decision to make from its correlation
110            // list rather than one this predicate can make.
111            BoundExpr::Subquery { .. } => false,
112        }
113    }
114
115    /// Returns which declared column positions the expression reads.
116    ///
117    /// The declared position rather than the record slot, because the callers
118    /// that ask - a generated column's dependency order, and the index-key
119    /// matcher - both think in declared positions.
120    pub fn columns_used(&self, into: &mut Vec<u16>) {
121        if let BoundExpr::Column { column, .. } = self {
122            if !into.contains(column) {
123                into.push(*column);
124            }
125        }
126        for child in self.children() {
127            child.columns_used(into);
128        }
129    }
130
131    /// Returns every sub-expression one expression holds, in no order.
132    ///
133    /// The match is exhaustive on purpose: there is no `_` arm, so a variant
134    /// added later is a compilation error here rather than a silently unvisited
135    /// subtree. That matters because the covering-index decision is built on
136    /// this walk, and a missed subtree there would be a column read from an
137    /// index that does not hold it.
138    ///
139    /// A subquery's *block* is deliberately not a child. It is a query of its
140    /// own with its own FROM terms, and the only thing about it that concerns
141    /// an enclosing term is which of that term's columns it correlates to -
142    /// which the block records separately and which the caller reads.
143    pub fn children(&self) -> Vec<&BoundExpr> {
144        match self {
145            BoundExpr::Null
146            | BoundExpr::Integer(_)
147            | BoundExpr::Real(_)
148            | BoundExpr::Text(_)
149            | BoundExpr::Blob(_)
150            | BoundExpr::Parameter(_)
151            | BoundExpr::Raise { computed: None, .. }
152            | BoundExpr::Column { .. }
153            | BoundExpr::Rowid { .. }
154            | BoundExpr::WindowRef { .. }
155            | BoundExpr::Aggregate { .. }
156            | BoundExpr::SorterColumn { .. } => Vec::new(),
157            BoundExpr::Unary { operand, .. }
158            | BoundExpr::Not(operand)
159            | BoundExpr::IsNull { operand, .. }
160            | BoundExpr::Collate { operand, .. }
161            | BoundExpr::Cast { operand, .. }
162            | BoundExpr::Raise {
163                computed: Some(operand),
164                ..
165            } => vec![operand],
166            BoundExpr::Arithmetic { left, right, .. }
167            | BoundExpr::Compare { left, right, .. }
168            | BoundExpr::Is { left, right, .. }
169            | BoundExpr::And(left, right)
170            | BoundExpr::Or(left, right) => vec![left, right],
171            BoundExpr::Generated {
172                operand, present, ..
173            } => vec![operand, present],
174            BoundExpr::Between {
175                operand, low, high, ..
176            } => vec![operand, low, high],
177            BoundExpr::InList { operand, list, .. } => {
178                let mut found: Vec<&BoundExpr> = vec![operand];
179                found.extend(list.iter());
180                found
181            }
182            BoundExpr::Case {
183                operand,
184                branches,
185                otherwise,
186                ..
187            } => {
188                let mut found: Vec<&BoundExpr> = Vec::new();
189                if let Some(operand) = operand {
190                    found.push(operand);
191                }
192                for (when, then) in branches {
193                    found.push(when);
194                    found.push(then);
195                }
196                if let Some(otherwise) = otherwise {
197                    found.push(otherwise);
198                }
199                found
200            }
201            BoundExpr::Pattern {
202                operand,
203                pattern,
204                escape,
205                ..
206            } => {
207                let mut found: Vec<&BoundExpr> = vec![operand, pattern];
208                if let Some(escape) = escape {
209                    found.push(escape);
210                }
211                found
212            }
213            BoundExpr::External { arguments, .. }
214            | BoundExpr::VirtualFunction { arguments, .. }
215            | BoundExpr::Function { arguments, .. }
216            | BoundExpr::Math { arguments, .. }
217            | BoundExpr::Json { arguments, .. }
218            | BoundExpr::Time { arguments, .. } => arguments.iter().collect(),
219            BoundExpr::Subquery { operand, .. } => operand.iter().map(|held| &**held).collect(),
220        }
221    }
222
223    /// Returns every sub-expression one expression holds, mutably.
224    ///
225    /// The mirror of [`BoundExpr::children`], and exhaustive for the same
226    /// reason: a variant added later is a compilation error here rather than a
227    /// subtree some rewrite silently skips. `crate::rewrite` is the only caller
228    /// and the trigger firing point is why it exists - a body's `OLD` and `NEW`
229    /// reads are replaced by the values the row actually holds, and one missed
230    /// subtree there is a trigger that reads a NULL where a value was.
231    ///
232    /// A subquery's *block* is not a child here either, for the reason it is
233    /// not one there: it is a query of its own. `crate::rewrite` descends into
234    /// it separately, because a correlated block is exactly where a foreign
235    /// key's `NOT EXISTS (SELECT 1 FROM parent WHERE p.k = NEW.c)` keeps its
236    /// `NEW`.
237    pub fn children_mut(&mut self) -> Vec<&mut BoundExpr> {
238        match self {
239            BoundExpr::Null
240            | BoundExpr::Integer(_)
241            | BoundExpr::Real(_)
242            | BoundExpr::Text(_)
243            | BoundExpr::Blob(_)
244            | BoundExpr::Parameter(_)
245            | BoundExpr::Raise { computed: None, .. }
246            | BoundExpr::Column { .. }
247            | BoundExpr::Rowid { .. }
248            | BoundExpr::WindowRef { .. }
249            | BoundExpr::Aggregate { .. }
250            | BoundExpr::SorterColumn { .. } => Vec::new(),
251            BoundExpr::Unary { operand, .. }
252            | BoundExpr::Not(operand)
253            | BoundExpr::IsNull { operand, .. }
254            | BoundExpr::Collate { operand, .. }
255            | BoundExpr::Cast { operand, .. }
256            | BoundExpr::Raise {
257                computed: Some(operand),
258                ..
259            } => vec![operand],
260            BoundExpr::Arithmetic { left, right, .. }
261            | BoundExpr::Compare { left, right, .. }
262            | BoundExpr::Is { left, right, .. }
263            | BoundExpr::And(left, right)
264            | BoundExpr::Or(left, right) => vec![left, right],
265            BoundExpr::Generated {
266                operand, present, ..
267            } => vec![operand, present],
268            BoundExpr::Between {
269                operand, low, high, ..
270            } => vec![operand, low, high],
271            BoundExpr::InList { operand, list, .. } => {
272                let mut found: Vec<&mut BoundExpr> = vec![operand];
273                found.extend(list.iter_mut());
274                found
275            }
276            BoundExpr::Case {
277                operand,
278                branches,
279                otherwise,
280                ..
281            } => {
282                let mut found: Vec<&mut BoundExpr> = Vec::new();
283                if let Some(operand) = operand {
284                    found.push(operand);
285                }
286                for (when, then) in branches {
287                    found.push(when);
288                    found.push(then);
289                }
290                if let Some(otherwise) = otherwise {
291                    found.push(otherwise);
292                }
293                found
294            }
295            BoundExpr::Pattern {
296                operand,
297                pattern,
298                escape,
299                ..
300            } => {
301                let mut found: Vec<&mut BoundExpr> = vec![operand, pattern];
302                if let Some(escape) = escape {
303                    found.push(escape);
304                }
305                found
306            }
307            BoundExpr::External { arguments, .. }
308            | BoundExpr::VirtualFunction { arguments, .. }
309            | BoundExpr::Function { arguments, .. }
310            | BoundExpr::Math { arguments, .. }
311            | BoundExpr::Json { arguments, .. }
312            | BoundExpr::Time { arguments, .. } => arguments.iter_mut().collect(),
313            BoundExpr::Subquery { operand, .. } => {
314                operand.iter_mut().map(|held| &mut **held).collect()
315            }
316        }
317    }
318
319    /// Returns the block a subquery expression holds, when it is one.
320    ///
321    /// Separate from [`BoundExpr::children_mut`] because a block is not a
322    /// sub-expression: it is a query, with its own FROM terms and its own
323    /// scope. A rewrite that treats it as one would run over the wrong tree.
324    pub fn block_mut(&mut self) -> Option<&mut BoundSelect> {
325        match self {
326            BoundExpr::Subquery { block, .. } => Some(block),
327            _ => None,
328        }
329    }
330
331    /// Records which of one FROM term's columns this expression reads.
332    ///
333    /// A correlated subquery's reads are the block's own reads of the term,
334    /// gathered by walking the block; a derived table inside it that
335    /// correlates to the term is recorded as opaque.
336    ///
337    /// @param source - the FROM term to look for
338    /// @param into - what has been found so far
339    pub fn columns_read(&self, source: usize, into: &mut ColumnUse) {
340        match self {
341            BoundExpr::Column {
342                source: held, slot, ..
343            } if *held == source => into.add(*slot),
344            BoundExpr::Rowid { source: held } if *held == source => into.rowid = true,
345            // `sqlite_offset(x)` finds the row's page by its rowid, so it reads
346            // the rowid of the term `x` belongs to as well as `x`.
347            BoundExpr::Function {
348                func: crate::function::ScalarFunc::Offset,
349                arguments,
350                ..
351            } if arguments.first().is_some_and(|argument| {
352                matches!(argument, BoundExpr::Column { source: held, .. } if *held == source)
353            }) =>
354            {
355                into.rowid = true;
356            }
357            // **A correlated block's reads are listed, not given up on
358            // (task-2183).** They were recorded as opaque, which made every
359            // scan of the outer term decode every column: `wide.body` for each
360            // row of `SELECT count(*) FROM wide a WHERE EXISTS (SELECT 1 FROM
361            // side_table b WHERE b.owner = a.id)`, which reads only `a.id`.
362            // The block's own walk finds every outer reference it makes, and
363            // stays opaque for the derived tables inside it that it cannot see
364            // into.
365            BoundExpr::Subquery { block, .. } if block.correlations.contains(&source) => {
366                block.gather_columns(source, into, true);
367            }
368            BoundExpr::VirtualFunction {
369                source: held,
370                name,
371                arguments,
372            } if *held == source => into.add_function(name, arguments),
373            _ => {}
374        }
375        for child in self.children() {
376            child.columns_read(source, into);
377        }
378    }
379}
380
381impl BoundExpr {
382    /// Returns which FROM terms the expression reads.
383    pub fn sources_used(&self, into: &mut Vec<usize>) {
384        match self {
385            BoundExpr::Column { source, .. } | BoundExpr::Rowid { source }
386                if !into.contains(source) =>
387            {
388                into.push(*source);
389            }
390            BoundExpr::Unary { operand, .. }
391            | BoundExpr::Not(operand)
392            | BoundExpr::IsNull { operand, .. }
393            | BoundExpr::Collate { operand, .. }
394            | BoundExpr::Cast { operand, .. }
395            | BoundExpr::Raise {
396                computed: Some(operand),
397                ..
398            } => operand.sources_used(into),
399            BoundExpr::Arithmetic { left, right, .. }
400            | BoundExpr::Compare { left, right, .. }
401            | BoundExpr::Is { left, right, .. }
402            | BoundExpr::And(left, right)
403            | BoundExpr::Or(left, right) => {
404                left.sources_used(into);
405                right.sources_used(into);
406            }
407            BoundExpr::Between {
408                operand, low, high, ..
409            } => {
410                operand.sources_used(into);
411                low.sources_used(into);
412                high.sources_used(into);
413            }
414            BoundExpr::InList { operand, list, .. } => {
415                operand.sources_used(into);
416                for item in list {
417                    item.sources_used(into);
418                }
419            }
420            BoundExpr::Case {
421                operand,
422                branches,
423                otherwise,
424                ..
425            } => {
426                if let Some(operand) = operand {
427                    operand.sources_used(into);
428                }
429                for (when, then) in branches {
430                    when.sources_used(into);
431                    then.sources_used(into);
432                }
433                if let Some(otherwise) = otherwise {
434                    otherwise.sources_used(into);
435                }
436            }
437            BoundExpr::Pattern {
438                operand,
439                pattern,
440                escape,
441                ..
442            } => {
443                operand.sources_used(into);
444                pattern.sources_used(into);
445                if let Some(escape) = escape {
446                    escape.sources_used(into);
447                }
448            }
449            // **A JSON call and a registered function's call read their
450            // arguments' terms too.** Both were missing here, so `i.id =
451            // c.value ->> '$.id'` looked like it read no term: the planner put
452            // `i` first and sought it with a key that reads `c`, which had not
453            // been read yet, and the statement failed with "a seek key or range
454            // bound reads a column".
455            BoundExpr::Function { arguments, .. }
456            | BoundExpr::Math { arguments, .. }
457            | BoundExpr::Time { arguments, .. }
458            | BoundExpr::Json { arguments, .. }
459            | BoundExpr::External { arguments, .. } => {
460                for argument in arguments {
461                    argument.sources_used(into);
462                }
463            }
464            BoundExpr::Generated {
465                operand, present, ..
466            } => {
467                operand.sources_used(into);
468                present.sources_used(into);
469            }
470            BoundExpr::VirtualFunction {
471                source, arguments, ..
472            } => {
473                if !into.contains(source) {
474                    into.push(*source);
475                }
476                for argument in arguments {
477                    argument.sources_used(into);
478                }
479            }
480            BoundExpr::Subquery { operand, block, .. } => {
481                if let Some(operand) = operand {
482                    operand.sources_used(into);
483                }
484                // The block's correlations are terms of the *enclosing* query,
485                // so they decide which loop level the subquery can first be
486                // evaluated at. Leaving them out put a correlated `EXISTS`
487                // before the loop whose row it reads.
488                for source in &block.correlations {
489                    if !into.contains(source) {
490                        into.push(*source);
491                    }
492                }
493            }
494            _ => {}
495        }
496    }
497}