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 makes the answer unknowable from here - the block
325    /// is a query of its own and could read any column of the term it
326    /// correlates to - so it is recorded as opaque rather than guessed at.
327    /// @param source - the FROM term to look for
328    /// @param into - what has been found so far
329    pub fn columns_read(&self, source: usize, into: &mut ColumnUse) {
330        match self {
331            BoundExpr::Column {
332                source: held, slot, ..
333            } if *held == source => into.add(*slot),
334            BoundExpr::Rowid { source: held } if *held == source => into.rowid = true,
335            BoundExpr::Subquery { block, .. } if block.correlations.contains(&source) => {
336                into.opaque = true;
337            }
338            BoundExpr::VirtualFunction {
339                source: held,
340                name,
341                arguments,
342            } if *held == source => into.add_function(name, arguments),
343            _ => {}
344        }
345        for child in self.children() {
346            child.columns_read(source, into);
347        }
348    }
349}
350
351impl BoundExpr {
352    /// Returns which FROM terms the expression reads.
353    pub fn sources_used(&self, into: &mut Vec<usize>) {
354        match self {
355            BoundExpr::Column { source, .. } | BoundExpr::Rowid { source }
356                if !into.contains(source) =>
357            {
358                into.push(*source);
359            }
360            BoundExpr::Unary { operand, .. }
361            | BoundExpr::Not(operand)
362            | BoundExpr::IsNull { operand, .. }
363            | BoundExpr::Collate { operand, .. }
364            | BoundExpr::Cast { operand, .. }
365            | BoundExpr::Raise {
366                computed: Some(operand),
367                ..
368            } => operand.sources_used(into),
369            BoundExpr::Arithmetic { left, right, .. }
370            | BoundExpr::Compare { left, right, .. }
371            | BoundExpr::Is { left, right, .. }
372            | BoundExpr::And(left, right)
373            | BoundExpr::Or(left, right) => {
374                left.sources_used(into);
375                right.sources_used(into);
376            }
377            BoundExpr::Between {
378                operand, low, high, ..
379            } => {
380                operand.sources_used(into);
381                low.sources_used(into);
382                high.sources_used(into);
383            }
384            BoundExpr::InList { operand, list, .. } => {
385                operand.sources_used(into);
386                for item in list {
387                    item.sources_used(into);
388                }
389            }
390            BoundExpr::Case {
391                operand,
392                branches,
393                otherwise,
394                ..
395            } => {
396                if let Some(operand) = operand {
397                    operand.sources_used(into);
398                }
399                for (when, then) in branches {
400                    when.sources_used(into);
401                    then.sources_used(into);
402                }
403                if let Some(otherwise) = otherwise {
404                    otherwise.sources_used(into);
405                }
406            }
407            BoundExpr::Pattern {
408                operand,
409                pattern,
410                escape,
411                ..
412            } => {
413                operand.sources_used(into);
414                pattern.sources_used(into);
415                if let Some(escape) = escape {
416                    escape.sources_used(into);
417                }
418            }
419            // **A JSON call and a registered function's call read their
420            // arguments' terms too.** Both were missing here, so `i.id =
421            // c.value ->> '$.id'` looked like it read no term: the planner put
422            // `i` first and sought it with a key that reads `c`, which had not
423            // been read yet, and the statement failed with "a seek key or range
424            // bound reads a column".
425            BoundExpr::Function { arguments, .. }
426            | BoundExpr::Math { arguments, .. }
427            | BoundExpr::Time { arguments, .. }
428            | BoundExpr::Json { arguments, .. }
429            | BoundExpr::External { arguments, .. } => {
430                for argument in arguments {
431                    argument.sources_used(into);
432                }
433            }
434            BoundExpr::Generated {
435                operand, present, ..
436            } => {
437                operand.sources_used(into);
438                present.sources_used(into);
439            }
440            BoundExpr::VirtualFunction {
441                source, arguments, ..
442            } => {
443                if !into.contains(source) {
444                    into.push(*source);
445                }
446                for argument in arguments {
447                    argument.sources_used(into);
448                }
449            }
450            BoundExpr::Subquery { operand, block, .. } => {
451                if let Some(operand) = operand {
452                    operand.sources_used(into);
453                }
454                // The block's correlations are terms of the *enclosing* query,
455                // so they decide which loop level the subquery can first be
456                // evaluated at. Leaving them out put a correlated `EXISTS`
457                // before the loop whose row it reads.
458                for source in &block.correlations {
459                    if !into.contains(source) {
460                        into.push(*source);
461                    }
462                }
463            }
464            _ => {}
465        }
466    }
467}