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