Skip to main content

inillucent_sql/
plan.rs

1//! The logical and physical plans.
2//!
3//! Invariant: a physical plan is *legal* before it is fast. Every access path
4//! this planner produces returns exactly the rows a full scan of the same term
5//! would return, and every predicate a path consumes is either fully enforced
6//! by the path or left in the residual filter. A predicate that is neither is a
7//! wrong answer, so the two lists are built together and the compiler emits
8//! whatever is left over.
9//!
10//! The phase-6 planner is deliberately minimal: FROM terms stay in written
11//! order, joins are nested loops, and the only paths are a full scan, a rowid
12//! lookup or range, and an index seek over an equality prefix with an optional
13//! range on the column after it. Cost is not modelled yet; a path is chosen
14//! because it is more selective by construction, not because a number said so.
15
16use inillucent_value::Collation;
17
18use crate::ast::{BinaryOp, CompoundOp, JoinKind, NullOrder, SortOrder};
19use crate::bind::{BoundExpr, BoundSelect, BoundSource, ColumnUse, SourceRows};
20use crate::catalog_view::{IndexInfo, TableInfo};
21use crate::cost;
22
23mod hint;
24mod partial;
25mod pattern;
26mod range;
27mod terms;
28pub use hint::unanswerable_index_hint;
29use hint::{forced_path, index_usable, outer_terms, statement_terms};
30use partial::implies;
31use terms::{
32    collation_of, compares_unconverted, comparison_against_column, comparison_against_rowid,
33    comparison_collation, indexable_comparison,
34};
35mod seek_union;
36
37/// A comparison an access path can enforce.
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub enum BoundKind {
40    /// `>=`
41    GreaterEqual,
42    /// `>`
43    Greater,
44    /// `<=`
45    LessEqual,
46    /// `<`
47    Less,
48}
49
50/// One end of a scan range.
51#[derive(Clone, Debug, PartialEq)]
52pub struct RangeBound {
53    /// Which comparison the bound enforces.
54    pub kind: BoundKind,
55    /// The value to compare against.
56    pub value: BoundExpr,
57    /// Whether the seek compares `value` without converting it to the
58    /// column's affinity. See [`AccessPath::IndexSeek`]'s `unconverted`.
59    pub unconverted: bool,
60}
61
62/// One seek over an index, as a branch of an [`AccessPath::IndexSeekUnion`].
63#[derive(Clone, Debug, PartialEq)]
64pub struct IndexSeekBranch {
65    /// The equality prefix this branch pins, one value per leading index
66    /// column.
67    pub equalities: Vec<BoundExpr>,
68    /// The positions in `equalities` whose value is compared unconverted.
69    /// See [`AccessPath::IndexSeek`]'s `unconverted`.
70    pub unconverted: Vec<usize>,
71    /// The lower bound on the column after the prefix, when there is one.
72    pub low: Option<RangeBound>,
73    /// The upper bound on that same column.
74    pub high: Option<RangeBound>,
75}
76
77/// How one FROM term's rows are produced.
78#[derive(Clone, Debug, PartialEq)]
79pub enum AccessPath {
80    /// Every row of the table, in rowid order.
81    TableScan {
82        /// The table B-tree's root page.
83        root: u32,
84    },
85    /// One row, found by rowid.
86    RowidSeek {
87        /// The table B-tree's root page.
88        root: u32,
89        /// The rowid to look up.
90        key: BoundExpr,
91    },
92    /// A contiguous run of rows, by rowid.
93    RowidRange {
94        /// The table B-tree's root page.
95        root: u32,
96        /// The lower bound, when there is one.
97        low: Option<RangeBound>,
98        /// The upper bound, when there is one.
99        high: Option<RangeBound>,
100    },
101    /// Rows found through an index, then fetched from the table.
102    IndexSeek {
103        /// The table B-tree's root page.
104        table_root: u32,
105        /// The index B-tree's root page.
106        index_root: u32,
107        /// The index's name, for the plan description.
108        index_name: Vec<u8>,
109        /// The equality prefix, one value per leading index column.
110        equalities: Vec<BoundExpr>,
111        /// The positions in `equalities` whose value the seek compares without
112        /// converting it to the column's affinity.
113        ///
114        /// **The comparison decides this, and only the planner sees the
115        /// comparison (task-2083).** A seek converts the probe value to the
116        /// indexed column's affinity, except when both sides of the `=` have
117        /// an affinity and neither is numeric: then `WHERE` converts nothing
118        /// and neither may the seek. That is SQLite's `codeAllEqualityTerms`.
119        /// The executor used to decide it from the probe expression alone,
120        /// and a correlated subquery replaces the outer column with a
121        /// parameter before planning. The parameter has no affinity, so
122        /// `(SELECT id FROM h WHERE h.a = s.k)` with `h.a TEXT` and `s.k`
123        /// untyped converted the number 3 to `'3'` and found a row SQLite
124        /// does not.
125        ///
126        /// A list of positions rather than a flag per equality because it is
127        /// almost always empty, and an empty `Vec` does not allocate. A flag per
128        /// equality cost two allocations to compile `WHERE email = ?1`, which
129        /// `inillucent::budget` counts.
130        unconverted: Vec<usize>,
131        /// A range on the column after the equality prefix.
132        low: Option<RangeBound>,
133        /// The upper end of that range.
134        high: Option<RangeBound>,
135        /// The collation of each index column used, in order.
136        collations: Vec<Collation>,
137        /// Whether the index columns used are stored descending.
138        descending: Vec<bool>,
139        /// Which table column each index column holds.
140        ///
141        /// `None` for a key the index *computes*: an index on `lower(a)` holds
142        /// a value no column of the table carries, and the probe value takes no
143        /// column affinity because there is no column to take it from - which
144        /// is SQLite's rule and the reason this is an `Option` rather than a
145        /// position that would have to be invented.
146        columns: Vec<Option<u16>>,
147        /// Whether the table has no rowid, so the index key holds the key.
148        without_rowid: bool,
149        /// Where in each entry the row's primary key sits, for a `WITHOUT
150        /// ROWID` table read through a *secondary* index.
151        ///
152        /// Such an entry ends with the primary key where a rowid table's would
153        /// end with a rowid, and that is how the row is then found. Empty for a
154        /// rowid table, and empty when the index is the table's own key - then
155        /// the entry the seek landed on already is the row.
156        key_entry_slots: Vec<usize>,
157        /// Where in the index entry every column the query reads sits, when the
158        /// index holds all of them.
159        ///
160        /// An index entry is the indexed columns followed by the row's key, so
161        /// a query that reads only those columns never has to go to the table
162        /// at all - which halves the descents and, on a range, is the whole
163        /// difference between a search and a scan. `None` means the query needs
164        /// something the entry does not carry, and the row is fetched.
165        ///
166        /// The pairs are `(record slot in the table, slot in the index entry)`.
167        /// The rowid is not in the list: it is always the entry's last field
168        /// for a rowid table, and the compiler reads it with `IdxRowid`.
169        covering: Option<Vec<(u16, usize)>>,
170    },
171    /// One row per key, found by rowid - several of
172    /// [`RowidSeek`](Self::RowidSeek), concatenated.
173    ///
174    /// What `WHERE rowid IN (a, b, c)` plans to on a rowid table: every branch
175    /// is the same one-row lookup `RowidSeek` uses alone, so the union is
176    /// nothing more than that lookup run once per key. A rowid is unique by
177    /// construction, so the only way two branches can name the same row is a
178    /// repeated key - a literal list is de-duplicated once, here, at plan
179    /// time; a key that is not a literal (a parameter, a correlated column)
180    /// cannot be compared this way, so the executor still checks each key
181    /// against the ones already probed before it seeks.
182    RowidSeekUnion {
183        /// The table B-tree's root page.
184        root: u32,
185        /// The keys to look up, in the order they are probed.
186        keys: Vec<BoundExpr>,
187    },
188    /// Rows found through one index - several seeks over the same tree,
189    /// concatenated.
190    ///
191    /// The branches are what a disjunction's terms become once each is
192    /// individually seekable: `x IN (a, b, c)` is every branch a bare
193    /// equality on the same column, and a keyset page's
194    /// `(a=? AND b>?) OR a>?` is two branches over the same composite index,
195    /// one an equality followed by a range and the other a range alone. The
196    /// fields outside `branches` describe the one index and table every
197    /// branch reads, because those never vary between branches - only the
198    /// equality prefix and the range do, which is exactly what a term of a
199    /// disjunction can differ in.
200    IndexSeekUnion {
201        /// The table B-tree's root page.
202        table_root: u32,
203        /// The index B-tree's root page.
204        index_root: u32,
205        /// The index's name, for the plan description.
206        index_name: Vec<u8>,
207        /// One seek per branch, in the order they run.
208        branches: Vec<IndexSeekBranch>,
209        /// The collation of each index column a branch can reach, in order.
210        ///
211        /// Sized to the deepest branch - the one whose equality prefix and
212        /// range together reach furthest into the index - because a
213        /// shallower branch simply does not read the columns past its own
214        /// depth.
215        collations: Vec<Collation>,
216        /// Whether each of those columns is stored descending.
217        descending: Vec<bool>,
218        /// Which table column each of those index columns holds.
219        columns: Vec<Option<u16>>,
220        /// Whether the table has no rowid, so the index key holds the key.
221        without_rowid: bool,
222        /// Where in each entry the row's primary key sits, for a `WITHOUT
223        /// ROWID` table read through a *secondary* index. Empty for a rowid
224        /// table, and empty when the index is the table's own key.
225        key_entry_slots: Vec<usize>,
226        /// Where in the index entry every column the query reads sits, when
227        /// the index holds all of them.
228        covering: Option<Vec<(u16, usize)>>,
229        /// Whether a row this union finds can also be found by a different
230        /// branch, and so has to be checked against the rows already
231        /// emitted before it is.
232        ///
233        /// `false` only when the branches are proven disjoint by
234        /// construction - the keyset-range shape, where each branch's
235        /// equality prefix pins a value no other branch's range can reach -
236        /// which is what lets that shape stream straight through a `LIMIT`
237        /// with nothing held back to be deduplicated. An `IN` list is always
238        /// `true`: a non-literal value (a parameter, a correlated column)
239        /// cannot be proven distinct from another at plan time, so the
240        /// executor has to check.
241        dedup: bool,
242    },
243    /// Rows produced by a nested query, materialised and then scanned.
244    Subquery {
245        /// The plan that fills the store.
246        plan: Box<PhysicalPlan>,
247        /// How many columns a materialised row holds.
248        width: usize,
249        /// Whether the nested block reads a FROM term outside itself, and so
250        /// has to be rebuilt for every row of the query that encloses it.
251        correlated: bool,
252    },
253    /// Rows produced by a recursive CTE, filled by walking its own queue.
254    Recursive {
255        /// The arms that do not reference the CTE, in order.
256        seeds: Vec<(CompoundOp, PhysicalPlan)>,
257        /// The arms that do.
258        steps: Vec<(CompoundOp, PhysicalPlan)>,
259        /// How many columns a row holds.
260        width: usize,
261    },
262    /// The one row of a recursive CTE's queue the fill loop is on.
263    RecursiveSelf {
264        /// The FROM term whose store holds the queue.
265        cte: usize,
266    },
267    /// The k nearest vectors, from an index a module owns.
268    ///
269    /// **A `TopN` over a distance is a different question from a scan.** The
270    /// rows are chosen by the index rather than filtered out of a walk, so the
271    /// path carries the probe and the depth rather than a range: the module is
272    /// asked for `k` candidates and the plan's own `ORDER BY` then rescores
273    /// them exactly, over an `ORDER BY` function matching the index's metric.
274    VectorProbe {
275        /// The table's root page, whose rows the candidates name.
276        root: u32,
277        /// The store holding the vectors, by the name the index was created
278        /// with.
279        index: Vec<u8>,
280        /// The vector to measure against, which reads no column of this query.
281        probe: Box<BoundExpr>,
282        /// How many candidates to ask the index for.
283        depth: usize,
284    },
285    /// Rows produced by a virtual table's module.
286    VirtualScan {
287        /// The module and the arguments its `CREATE` gave it.
288        module: crate::vtab::ModuleRef,
289        /// The constraints offered to `best_index`, in the order the module
290        /// will see them.
291        offer: Vec<VirtualConstraint>,
292        /// The ordering offered to `best_index`.
293        order_by: Vec<crate::vtab::OrderSpec>,
294        /// What the module answered, once it has been asked.
295        ///
296        /// It is `None` while the plan is still the planner's, and filled in by
297        /// a pass that runs before compilation. Keeping the two apart is what
298        /// lets the planner stay a pure function of the SQL and one catalog
299        /// generation while the program still carries a real plan.
300        chosen: Option<VirtualChoice>,
301    },
302}
303
304/// What a module answered when it was shown the offer.
305#[derive(Clone, Debug, PartialEq)]
306pub struct VirtualChoice {
307    /// The plan number, passed back to the module's `filter`.
308    pub index_number: i32,
309    /// The plan string, passed back to the module's `filter`.
310    pub index_string: String,
311    /// The offer positions whose values feed `filter`, in argument order.
312    pub arguments: Vec<usize>,
313    /// The offer positions the engine must still test for itself.
314    ///
315    /// Everything the module did not take, and everything it took without
316    /// promising to apply. A module that says `omit` is promising; anything
317    /// else and the predicate is tested twice, which is the safe direction.
318    pub recheck: Vec<usize>,
319    /// Whether the module will produce the requested order by itself.
320    pub ordered: bool,
321}
322
323/// One predicate offered to a module, with what it was made of.
324///
325/// The predicate is kept whole beside the constraint because the compiler may
326/// have to test it after all: a module that used the constraint without
327/// promising to apply it leaves the engine responsible for the answer.
328#[derive(Clone, Debug, PartialEq)]
329pub struct VirtualConstraint {
330    /// The constraint as the module is shown it.
331    pub spec: crate::vtab::ConstraintSpec,
332    /// The value on the other side, which becomes an argument to `filter`.
333    pub value: BoundExpr,
334    /// The whole predicate, for the compiler to re-test when it must.
335    pub predicate: BoundExpr,
336}
337
338impl AccessPath {
339    /// Returns a one-line description, which is what `EXPLAIN QUERY PLAN`
340    /// renders and what a performance test asserts on.
341    pub fn describe(&self, table: &str) -> String {
342        self.describe_over(table, None)
343    }
344
345    /// Returns the same line, naming the columns an index seek compares.
346    ///
347    /// **`(a=?)` rather than `(?=?)`.** The reference names the key column, and
348    /// it is the one part of the line a reader uses to tell "this index" from
349    /// "the other index on the same table". The declaration is passed in
350    /// because an access path carries the index's *name* and not its columns -
351    /// which is the right thing for a plan to carry, and the wrong thing to
352    /// render a description from.
353    ///
354    /// @param table - the name the query calls the term
355    /// @param info - the table's declaration, when the caller has it
356    pub fn describe_over(&self, table: &str, info: Option<&TableInfo>) -> String {
357        match self {
358            AccessPath::TableScan { .. } => format!("SCAN {table}"),
359            AccessPath::RowidSeek { .. } => {
360                format!("SEARCH {table} USING INTEGER PRIMARY KEY (rowid=?)")
361            }
362            AccessPath::RowidRange { .. } => {
363                format!("SEARCH {table} USING INTEGER PRIMARY KEY (rowid>?)")
364            }
365            // Every branch is the same one-row lookup, so one line describes
366            // all of them - which is also how a plain equality reads, and an
367            // `IN` list is nothing else once it has been turned into this.
368            AccessPath::RowidSeekUnion { .. } => {
369                format!("SEARCH {table} USING INTEGER PRIMARY KEY (rowid=?)")
370            }
371            AccessPath::Recursive { .. } => format!("SCAN {table} USING RECURSIVE QUEUE"),
372            AccessPath::RecursiveSelf { .. } => format!("SCAN {table}"),
373            AccessPath::VectorProbe { index, depth, .. } => format!(
374                "SEARCH {table} USING VECTOR INDEX {} (k={depth})",
375                String::from_utf8_lossy(index)
376            ),
377            AccessPath::VirtualScan { .. } => format!("SCAN {table} VIRTUAL TABLE INDEX"),
378            AccessPath::Subquery { correlated, .. } => {
379                if *correlated {
380                    format!("CORRELATED SCALAR SUBQUERY {table}")
381                } else {
382                    format!("SCAN {table}")
383                }
384            }
385            AccessPath::IndexSeek {
386                index_name,
387                equalities,
388                low,
389                high,
390                covering,
391                ..
392            } => {
393                let kind = if covering.is_some() {
394                    "COVERING INDEX"
395                } else {
396                    "INDEX"
397                };
398                // A walk with nothing to seek used to be covering by
399                // construction, so this line said so unconditionally. A
400                // partial index and an `INDEXED BY` are walked whole while a
401                // lookup per entry fetches the row, and SQLite says `USING
402                // INDEX` for that: `SELECT * FROM h INDEXED BY h_a` is
403                // `SCAN h USING INDEX h_a` in the pinned 3.53.4 shell.
404                if equalities.is_empty() && low.is_none() && high.is_none() {
405                    return format!(
406                        "SCAN {table} USING {kind} {}",
407                        String::from_utf8_lossy(index_name)
408                    );
409                }
410                let detail = index_seek_detail(
411                    index_name,
412                    info,
413                    equalities.len(),
414                    low.is_some() || high.is_some(),
415                );
416                format!(
417                    "SEARCH {table} USING {kind} {} ({detail})",
418                    String::from_utf8_lossy(index_name)
419                )
420            }
421            AccessPath::IndexSeekUnion {
422                index_name,
423                branches,
424                covering,
425                ..
426            } => {
427                let kind = if covering.is_some() {
428                    "COVERING INDEX"
429                } else {
430                    "INDEX"
431                };
432                // A branch with the same shape as one already rendered - the
433                // same equality-prefix depth and the same presence of a range
434                // - reads identically, so an `IN` list (every branch the same
435                // bare equality) collapses to the one line a plain equality
436                // would render. A genuine disjunction of differently shaped
437                // branches - the keyset-range case - gets one line per shape,
438                // in the order the branches run.
439                let mut lines: Vec<String> = Vec::new();
440                for branch in branches {
441                    let detail = index_seek_detail(
442                        index_name,
443                        info,
444                        branch.equalities.len(),
445                        branch.low.is_some() || branch.high.is_some(),
446                    );
447                    let line = format!(
448                        "SEARCH {table} USING {kind} {} ({detail})",
449                        String::from_utf8_lossy(index_name)
450                    );
451                    if !lines.contains(&line) {
452                        lines.push(line);
453                    }
454                }
455                lines.join(" OR ")
456            }
457        }
458    }
459}
460
461/// Returns the `(col=? AND col>?)` detail an index seek's description ends
462/// with, given how many leading columns of its equality prefix it pins and
463/// whether it also carries a range on the column after it.
464///
465/// Shared between [`AccessPath::IndexSeek`] and each branch of an
466/// [`AccessPath::IndexSeekUnion`], which differ only in how many branches
467/// there are - the naming of one branch's columns is exactly what a plain
468/// seek already does.
469fn index_seek_detail(
470    index_name: &[u8],
471    info: Option<&TableInfo>,
472    equalities: usize,
473    ranged: bool,
474) -> String {
475    let keyed = info.and_then(|held| {
476        held.indexes
477            .iter()
478            .find(|candidate| candidate.name == index_name)
479    });
480    let named = |position: usize| -> String {
481        keyed
482            .and_then(|index| index.columns.get(position))
483            .and_then(|key| key.column)
484            .and_then(|at| info.and_then(|held| held.column(at)))
485            .map(|column| String::from_utf8_lossy(&column.name).into_owned())
486            .unwrap_or_else(|| "?".to_string())
487    };
488    let mut detail = String::new();
489    for index in 0..equalities {
490        if index > 0 {
491            detail.push_str(" AND ");
492        }
493        detail.push_str(&format!("{}=?", named(index)));
494    }
495    if ranged {
496        if !detail.is_empty() {
497            detail.push_str(" AND ");
498        }
499        detail.push_str(&format!("{}>?", named(equalities)));
500    }
501    detail
502}
503
504/// One FROM term with the path chosen for it.
505#[derive(Clone, Debug, PartialEq)]
506pub struct PlannedSource {
507    /// What the planner estimated this term's path would cost.
508    ///
509    /// It is kept so that a test can assert on the *reason* a plan was chosen
510    /// rather than only on the plan, which is the difference between catching a
511    /// cost-model regression and catching it two releases later.
512    pub cost: f64,
513    /// How many rows the path is estimated to produce.
514    pub rows: f64,
515    /// The statement-wide number every bound expression refers to it by.
516    pub id: usize,
517    /// The table.
518    pub table: TableInfo,
519    /// The name the query calls it.
520    pub alias: Vec<u8>,
521    /// How its rows are produced.
522    pub path: AccessPath,
523    /// The join that attached it to the term before it.
524    pub join: JoinKind,
525    /// The `ON` condition, when the join is an outer one.
526    ///
527    /// An inner join's condition is an ordinary predicate and is distributed
528    /// with the rest; an outer join's is not, because a row that fails it is
529    /// still emitted, null-extended. Keeping it here rather than in the
530    /// residual list is what stops the two being confused.
531    pub on: Option<BoundExpr>,
532    /// Whether the path this term is read by enforces the whole `ON` condition.
533    ///
534    /// **What decides whether an outer join can be an index nested loop.**
535    /// That operator probes the inner tree by a key and
536    /// null-extends when the probe finds nothing; it has nowhere to test a
537    /// condition the key did not capture, so it may only be used when there is
538    /// nothing left to test. When the key is the whole condition - which
539    /// `ON b.k = a.k` over an index on `b(k)` is - the probe's answer and the
540    /// condition's answer are the same answer.
541    ///
542    /// False for every inner join, where the condition is distributed into the
543    /// statement's terms and re-tested as a residual, and false for an outer
544    /// join whose condition says more than its key does.
545    pub on_enforced: bool,
546}
547
548/// How the rows are grouped and aggregated.
549#[derive(Clone, Copy, Debug, PartialEq, Eq)]
550pub enum AggregationMode {
551    /// No aggregation at all.
552    None,
553    /// One group for the whole input, which produces exactly one row.
554    Whole,
555    /// One group per distinct `GROUP BY` key, produced by sorting first.
556    Grouped,
557}
558
559/// A physical plan for a read-only statement.
560#[derive(Clone, Debug, PartialEq)]
561pub struct PhysicalPlan {
562    /// The FROM terms, in the order the nested loops visit them.
563    pub sources: Vec<PlannedSource>,
564    /// The predicates the loops must still evaluate, one per nesting level.
565    ///
566    /// A predicate is attached to the innermost term it reads, so it is tested
567    /// as soon as it can be rather than after every loop has been entered.
568    pub residuals: Vec<Option<BoundExpr>>,
569    /// A predicate over no columns at all, tested once before the loops.
570    pub constant_filter: Option<BoundExpr>,
571    /// The bound statement the plan came from.
572    pub select: BoundSelect,
573    /// How the rows are aggregated.
574    pub aggregation: AggregationMode,
575    /// Whether the results have to pass through a sorter.
576    pub needs_sort: bool,
577    /// Whether the outermost term is walked backwards.
578    ///
579    /// A B-tree read from its last entry to its first produces exactly the
580    /// reverse of what it produces read forwards, so a descending `ORDER BY`
581    /// over an ascending structure is a direction rather than a sort. Only ever
582    /// set when [`needs_sort`](Self::needs_sort) is false: a plan that sorts
583    /// does not care which way its input arrived.
584    pub reverse: bool,
585    /// Whether the walk already brings the rows of each group together.
586    ///
587    /// Grouping needs adjacency, not order: if every row of a group arrives
588    /// before the next group starts, the aggregate can be finished and emitted
589    /// as the key changes and nothing has to be collected first. A walk whose
590    /// leading keys are exactly the `GROUP BY` columns delivers that, whichever
591    /// direction it runs in.
592    pub grouped_walk: bool,
593    /// Whether the walk already brings duplicate result rows together.
594    ///
595    /// The same property for `DISTINCT`: adjacent duplicates can be dropped by
596    /// comparing each row with the one before it, where a set has to remember
597    /// every row it has seen.
598    pub distinct_walk: bool,
599    /// The later arms of a compound, each with the operator that joined it.
600    pub compounds: Vec<(CompoundOp, PhysicalPlan)>,
601    /// Which optimizations were on when this plan was chosen.
602    ///
603    /// Carried on the plan rather than looked up by the executor, because a
604    /// plan is *cached* and a lever that changed after it was built must not
605    /// change what it does - a plan that consulted the connection at execution
606    /// time would answer one way today and another tomorrow with no
607    /// recompilation in between. The connection throws its compiled statements
608    /// away when a lever moves, which is what makes this field the truth.
609    pub levers: Levers,
610    /// Whether any expression in this plan holds a subquery used as a value.
611    ///
612    /// Decided here because it is a property of the *statement* and not of the
613    /// data, and because the alternative was deciding it per execution: the
614    /// executor folds uncorrelated subqueries on the way into each run, and it
615    /// has to ask this question first every time. Walking the expression tree
616    /// to ask it cost about 0.07 us per execution - measurable against a
617    /// `point.rowid` that takes 0.78 - because `BoundExpr::children` allocates
618    /// a vector per node. Asked once per compiled statement instead, it costs
619    /// nothing a statement runs.
620    pub subqueries: bool,
621}
622
623impl PhysicalPlan {
624    /// Returns the highest statement-wide source id anywhere in the plan.
625    ///
626    /// The compiler sizes its cursor map from this, so a nested block's cursor
627    /// has a slot before the block that encloses it is compiled.
628    pub fn max_source_id(&self) -> usize {
629        let mut highest = 0usize;
630        for source in &self.sources {
631            highest = highest.max(source.id);
632            match &source.path {
633                AccessPath::Subquery { plan, .. } => {
634                    highest = highest.max(plan.max_source_id());
635                }
636                AccessPath::Recursive { seeds, steps, .. } => {
637                    for (_, arm) in seeds.iter().chain(steps.iter()) {
638                        highest = highest.max(arm.max_source_id());
639                    }
640                }
641                _ => {}
642            }
643        }
644        for (_, arm) in &self.compounds {
645            highest = highest.max(arm.max_source_id());
646        }
647        highest
648    }
649
650    /// Returns the `EXPLAIN QUERY PLAN` lines this plan renders as.
651    pub fn describe(&self) -> Vec<String> {
652        let mut lines = Vec::new();
653        for source in &self.sources {
654            lines.push(
655                source
656                    .path
657                    .describe_over(&String::from_utf8_lossy(&source.alias), Some(&source.table)),
658            );
659        }
660        for (op, arm) in &self.compounds {
661            lines.push(format!("COMPOUND QUERY {}", compound_name(*op)));
662            lines.extend(arm.describe());
663        }
664        // A temp b-tree is only named when there is one. Grouping and
665        // de-duplicating that the walk already delivers build nothing, and a
666        // plan that said otherwise would be describing a different program.
667        if self.aggregation == AggregationMode::Grouped && !self.grouped_walk {
668            lines.push("USE TEMP B-TREE FOR GROUP BY".to_string());
669        }
670        if self.needs_sort {
671            lines.push("USE TEMP B-TREE FOR ORDER BY".to_string());
672        }
673        if self.select.distinct && !self.distinct_walk {
674            lines.push("USE TEMP B-TREE FOR DISTINCT".to_string());
675        }
676        lines
677    }
678}
679
680/// Returns the word `EXPLAIN QUERY PLAN` names a compound operator by.
681fn compound_name(op: CompoundOp) -> &'static str {
682    match op {
683        CompoundOp::Union => "UNION",
684        CompoundOp::UnionAll => "UNION ALL",
685        CompoundOp::Intersect => "INTERSECT",
686        CompoundOp::Except => "EXCEPT",
687    }
688}
689
690/// Which planner optimizations are switched on.
691///
692/// An optimization that cannot be switched off cannot be measured. The claim
693/// "the covering-index path made range reads thirty times faster" is a
694/// comparison, and without an arm to compare against it is a comparison with a
695/// build that no longer exists - which is an argument, not evidence.
696///
697/// The shape is SQLite's. `sqlite3_test_control(SQLITE_TESTCTRL_OPTIMIZATIONS)`
698/// takes a bitmask of optimizations to *disable*, reached through a control
699/// channel rather than through SQL, for exactly this reason: a knob on the SQL
700/// surface is a knob applications start depending on, and then it is not a
701/// measurement device any more, it is a feature with a compatibility story.
702///
703/// Disabling is what the mask names, so zero is the shipped engine and the
704/// default everywhere. A lever added later defaults to on without anybody
705/// having to remember to turn it on.
706#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
707pub struct Levers {
708    /// The optimizations that are turned *off*.
709    disabled: u32,
710}
711
712impl Levers {
713    /// Read a term's columns from the index entry, without fetching the row.
714    pub const COVERING_INDEX: u32 = 1;
715    /// Find the rows an UPDATE or DELETE touches through an index or a rowid,
716    /// rather than by scanning the table.
717    pub const INDEXED_WRITE: u32 = 2;
718    /// Answer an `ORDER BY` by walking a B-tree in its own key order, forwards
719    /// or backwards, instead of sorting every row and throwing most away.
720    pub const ORDERED_WALK: u32 = 4;
721    /// Group and de-duplicate as the rows arrive, when the walk already brings
722    /// equal keys together, instead of collecting every row into a sorter or a
723    /// set first.
724    pub const STREAMING_GROUP: u32 = 8;
725    /// Fold a value written into a scratch register and immediately copied
726    /// into the one instruction that writes it where it was going.
727    pub const FUSED_BYTECODE: u32 = 16;
728
729    /// Reusing a compiled program for SQL text already prepared.
730    ///
731    /// The rearchitecture's design puts a plan cache in the new engine's
732    /// prepare path, and measures it here, on the existing one, first - so the
733    /// mechanism is proved independently of the new storage. It is a lever
734    /// rather than a constant because a speedup that cannot be switched off
735    /// cannot be measured, and because "the cache made prepare six times
736    /// faster" needs an arm to be a claim rather than an assertion.
737    pub const PLAN_CACHE: u32 = 32;
738    /// Build a throwaway structure over an unindexed inner side of a join,
739    /// rather than walking it once per outer row.
740    ///
741    /// What `PRAGMA automatic_index` switches. It is a lever rather than a
742    /// constant for the same reason the others are - an optimisation that
743    /// cannot be switched off cannot be measured - and because SQLite exposes
744    /// exactly this switch under exactly this name, so an application that
745    /// turns it off there has somewhere to turn it off here.
746    pub const AUTOMATIC_INDEX: u32 = 64;
747    /// Every lever this build has.
748    pub const EVERY: u32 = Levers::PLAN_CACHE
749        | Levers::COVERING_INDEX
750        | Levers::INDEXED_WRITE
751        | Levers::ORDERED_WALK
752        | Levers::STREAMING_GROUP
753        | Levers::FUSED_BYTECODE
754        | Levers::AUTOMATIC_INDEX;
755
756    /// Returns the shipped configuration: everything on.
757    pub fn all() -> Levers {
758        Levers { disabled: 0 }
759    }
760
761    /// Returns a configuration with the named levers turned off.
762    /// @param mask - the levers to disable
763    pub fn without(mask: u32) -> Levers {
764        Levers {
765            disabled: mask & Levers::EVERY,
766        }
767    }
768
769    /// Returns whether one lever is on.
770    /// @param lever - the lever to ask about
771    pub fn has(self, lever: u32) -> bool {
772        self.disabled & lever == 0
773    }
774
775    /// Returns the mask of what is off, which is what a report prints.
776    pub fn disabled(self) -> u32 {
777        self.disabled
778    }
779
780    /// Returns the names of the levers that are off, for a report.
781    pub fn names_disabled(self) -> Vec<&'static str> {
782        let mut names = Vec::new();
783        if !self.has(Levers::COVERING_INDEX) {
784            names.push("covering-index");
785        }
786        if !self.has(Levers::INDEXED_WRITE) {
787            names.push("indexed-write");
788        }
789        if !self.has(Levers::ORDERED_WALK) {
790            names.push("ordered-walk");
791        }
792        if !self.has(Levers::STREAMING_GROUP) {
793            names.push("streaming-group");
794        }
795        if !self.has(Levers::FUSED_BYTECODE) {
796            names.push("fused-bytecode");
797        }
798        names
799    }
800}
801
802/// Plans a bound SELECT with some optimizations switched off.
803///
804/// The levers travel with the recursion rather than being read from anywhere
805/// global, so a subquery is planned under the same arm as the statement that
806/// contains it. An arm that applied to the outer block and not the inner one
807/// would measure a mixture and report it as one number.
808/// @param select - the bound statement
809/// @param levers - which optimizations are on
810pub fn plan_select_with(select: BoundSelect, levers: Levers) -> PhysicalPlan {
811    let mut select = select;
812    let compound_arms = core::mem::take(&mut select.compounds);
813    let terms = statement_terms(&select);
814    // The order the terms are visited in is chosen before their paths are, and
815    // then the paths are chosen in that order - because a path may use a value
816    // from a term visited earlier, and which terms those are is exactly what the
817    // order decides.
818    let order = choose_order(&select, &terms, levers);
819    let ordered: Vec<usize> = order.clone();
820    let ids: Vec<usize> = ordered
821        .iter()
822        .filter_map(|position| select.sources.get(*position))
823        .map(|source| source.id)
824        .collect();
825    let mut consumed = vec![false; terms.len()];
826    let mut sources = Vec::with_capacity(select.sources.len());
827    for (level, position) in ordered.iter().enumerate() {
828        let Some(source) = select.sources.get(*position) else {
829            continue;
830        };
831        // **An outer term's rows are not filtered on the way in.** A `WHERE`
832        // predicate over the null-extendable side is applied *after* the join,
833        // because a row that fails it must still produce a null-extended pair
834        // rather than vanish. Letting `choose_path` turn such a predicate into
835        // a seek would do both wrong things at once: filter the rows before the
836        // null extension, and mark the term consumed so it is never re-tested.
837        //
838        // **Its own `ON` condition is a different question.**
839        // An outer join's `ON` decides which inner rows *match*, and a row with
840        // no match is null-extended by the join itself - so seeking the inner
841        // side by an equality the `ON` states returns exactly the matches and
842        // nothing the join needed is lost. Refusing that made
843        // `LEFT JOIN chunk c ON c.document_id = d.id` scan a 60,000-row table
844        // where the same join written `JOIN` seeks it: 138.2 ms against 0.5 ms
845        // for the same ten rows, and the gap grows with the table.
846        //
847        // Two things keep it honest. The `ON` terms are collected into a list
848        // of their own, so nothing in the statement's `WHERE` can be turned
849        // into a seek here and nothing in the statement's `consumed` is marked.
850        // And `on` below still carries the whole condition, so the join re-tests
851        // it - a seek narrows the rows the test runs over and never stands in
852        // for it.
853        //
854        // A subquery, a recursive CTE and a virtual table each still resolve to
855        // what they are, because those are not access-path choices - they are
856        // what the term *is*.
857        let mut on_enforced = false;
858        let path = if is_outer(source.join) && matches!(source.rows, SourceRows::Table) {
859            match source.table.module.clone() {
860                Some(_) => choose_path(level, &ids, source, &select, &terms, &mut consumed, levers),
861                None => {
862                    // **Only a `LEFT` term may seek on its `ON`.** A `RIGHT`
863                    // or `FULL` term keeps the rows of its own that matched
864                    // nothing, and only a side read whole can know which
865                    // those are: `list l RIGHT JOIN todo t ON t.list_id =
866                    // l.id` with an index on `list_id` probed `todo` per list
867                    // and never produced the todo whose list does not exist.
868                    let on_terms = if source.join == JoinKind::Left {
869                        outer_terms(source)
870                    } else {
871                        Vec::new()
872                    };
873                    let mut on_consumed = vec![false; on_terms.len()];
874                    let chosen = choose_path(
875                        level,
876                        &ids,
877                        source,
878                        &select,
879                        &on_terms,
880                        &mut on_consumed,
881                        levers,
882                    );
883                    // Every conjunct of the condition turned into part of the
884                    // key, so the probe answers the condition and an index
885                    // nested loop can null-extend on an empty probe.
886                    on_enforced = !on_terms.is_empty() && on_consumed.iter().all(|held| *held);
887                    chosen
888                }
889            }
890        } else {
891            choose_path(level, &ids, source, &select, &terms, &mut consumed, levers)
892        };
893        let (cost, rows) = path_cost(source, &path);
894        sources.push(PlannedSource {
895            cost,
896            rows,
897            id: source.id,
898            table: (*source.table).clone(),
899            alias: source.alias.clone(),
900            path,
901            join: source.join,
902            on: is_outer(source.join)
903                .then(|| source.constraint.clone())
904                .flatten(),
905            on_enforced,
906        });
907    }
908    let (residuals, constant_filter) = distribute_residuals(&terms, &consumed, &ids);
909    let aggregation = if !select.group_by.is_empty() {
910        AggregationMode::Grouped
911    } else if !select.aggregates.is_empty() {
912        AggregationMode::Whole
913    } else {
914        AggregationMode::None
915    };
916    // The sort is only needed when the outer term's path does not already
917    // produce the order that was asked for. Walking a B-tree *is* walking it in
918    // key order, and a statement asking for that order has been answered by the
919    // walk - which is the difference between reading fifty rows and reading,
920    // sorting and throwing away six hundred thousand.
921    // A window function sorts the rows into its own order to compute over them,
922    // so whatever order the walk delivered is not the order the result comes
923    // out in - which is why `windows` disqualifies a statement here even though
924    // it has nothing to do with the access path.
925    // Adjacency is a weaker property than order, so it is asked first and for a
926    // wider set of statements: a grouped aggregate can be streamed whether or
927    // not it also answers an ORDER BY.
928    let adjacent = levers.has(Levers::STREAMING_GROUP)
929        && sources.len() == 1
930        && select.windows.is_empty()
931        && select.compounds.is_empty();
932    let outer = sources.first();
933    let grouped_walk = adjacent
934        && aggregation == AggregationMode::Grouped
935        && outer.is_some_and(|outer| grouped_by_walk(&select, outer));
936    let distinct_walk = adjacent && outer.is_some_and(|outer| distinct_by_walk(&select, outer));
937    // A statement that streams its grouping or its de-duplication still comes
938    // out in the order the walk delivered: the rows of a key arrive together,
939    // one output row is emitted per key, and the keys arrive in key order. So
940    // the walk answers the ORDER BY for these too.
941    //
942    // It did not used to. `SELECT DISTINCT category FROM main_table ORDER BY
943    // category` walked the covering index on `(category, key)` - which is
944    // already in `category` order - de-duplicated as the rows arrived, and then
945    // sorted the thirty-two answers through a temporary B-tree anyway. SQLite
946    // reads the same index and does not sort, which is the whole of a 26x
947    // difference on that workload. The same applied to every
948    // `GROUP BY x ORDER BY x`.
949    //
950    // The two are kept apart rather than merged: a statement that is both
951    // grouped and DISTINCT is left to sort, because the de-duplication then
952    // runs on the aggregate output rather than on the walk and the walk's order
953    // is no longer the result's.
954    let streamed_in_order = (grouped_walk && !select.distinct)
955        || (distinct_walk && aggregation == AggregationMode::None);
956    let single = levers.has(Levers::ORDERED_WALK)
957        && sources.len() == 1
958        && select.windows.is_empty()
959        && select.compounds.is_empty()
960        && ((aggregation == AggregationMode::None && !select.distinct) || streamed_in_order);
961    let provided = if single {
962        sources
963            .first()
964            .and_then(|outer| ordering_provided(&select, outer.id, &outer.table, &outer.path))
965    } else {
966        None
967    };
968    let needs_sort = !select.order_by.is_empty() && provided.is_none();
969    let reverse = provided.unwrap_or(false);
970    let compounds: Vec<(CompoundOp, PhysicalPlan)> = compound_arms
971        .into_iter()
972        .map(|(op, arm)| (op, plan_select_with(arm, levers)))
973        .collect();
974    let subqueries = holds_subquery(&select)
975        || residuals.iter().flatten().any(expression_holds_subquery)
976        || constant_filter
977            .as_ref()
978            .is_some_and(expression_holds_subquery)
979        || compounds.iter().any(|(_op, arm)| arm.subqueries);
980    PhysicalPlan {
981        sources,
982        residuals,
983        constant_filter,
984        select,
985        aggregation,
986        needs_sort,
987        reverse,
988        grouped_walk,
989        distinct_walk,
990        compounds,
991        subqueries,
992        levers,
993    }
994}
995
996/// Returns whether a select holds a subquery used as a value.
997///
998/// Compound arms are not walked here: `plan_select_with` has already taken them
999/// out of `select.compounds` and planned them, and each arm carries its own
1000/// answer. A subquery's *block* is not walked either - finding one is enough to
1001/// say the plan has one.
1002///
1003/// @param select - the query to look through
1004fn holds_subquery(select: &BoundSelect) -> bool {
1005    select.filter.iter().any(expression_holds_subquery)
1006        || select.group_by.iter().any(expression_holds_subquery)
1007        || select.having.iter().any(expression_holds_subquery)
1008        || select
1009            .columns
1010            .iter()
1011            .any(|column| expression_holds_subquery(&column.expr))
1012        || select
1013            .order_by
1014            .iter()
1015            .any(|term| expression_holds_subquery(&term.expr))
1016        || select.limit.iter().any(expression_holds_subquery)
1017        || select.offset.iter().any(expression_holds_subquery)
1018        || select
1019            .values
1020            .iter()
1021            .flatten()
1022            .any(expression_holds_subquery)
1023        // **The aggregate's `FILTER` and its inner `ORDER BY` are walked here
1024        // too as of task-1932 (M6).** This flag is the cheap question
1025        // `subquery::fold` asks before it walks anything, so a statement it
1026        // answers `false` for never folds - and a subquery in an aggregate's
1027        // `FILTER` was therefore left in an unfilled slot, which `translate`
1028        // reports as `unsupported("a correlated subquery used as a value")`.
1029        // The windows below already had all four of theirs.
1030        || select.aggregates.iter().any(|aggregate| {
1031            aggregate.arguments.iter().any(expression_holds_subquery)
1032                || aggregate.filter.iter().any(expression_holds_subquery)
1033                || aggregate
1034                    .order_by
1035                    .iter()
1036                    .any(|term| expression_holds_subquery(&term.expr))
1037        })
1038        || select.windows.iter().any(|window| {
1039            window.arguments.iter().any(expression_holds_subquery)
1040                || window.filter.iter().any(expression_holds_subquery)
1041                || window.partition_by.iter().any(expression_holds_subquery)
1042                || window
1043                    .order_by
1044                    .iter()
1045                    .any(|term| expression_holds_subquery(&term.expr))
1046        })
1047        || select.sources.iter().any(|source| {
1048            source.constraint.iter().any(expression_holds_subquery)
1049                || matches!(&source.rows, SourceRows::Subquery(block) if holds_subquery(block))
1050        })
1051}
1052
1053/// Returns whether an expression holds a subquery, anywhere beneath it.
1054///
1055/// Public because the *write* paths need the same answer and cannot get it from
1056/// a plan: a `VALUES` list and an `UPDATE`'s assignments are evaluated without
1057/// one. They ask this once when the statement is compiled, for the same reason
1058/// `PhysicalPlan::subqueries` is decided once - the question is about the
1059/// statement, and asking it per execution walks a tree and allocates.
1060///
1061/// @param expr - the expression to look through
1062pub fn expression_holds_subquery(expr: &BoundExpr) -> bool {
1063    matches!(expr, BoundExpr::Subquery { .. })
1064        || expr
1065            .children()
1066            .iter()
1067            .any(|child| expression_holds_subquery(child))
1068}
1069
1070/// Returns whether the walk brings the rows of each `GROUP BY` key together.
1071///
1072/// Grouping needs adjacency rather than order, so the direction does not
1073/// matter: what matters is that the walk's leading keys are exactly the group
1074/// columns. Exactly, not merely a superset - a walk ordered by `(a, b)` groups
1075/// `a` and groups `(a, b)`, and does not group `b`.
1076///
1077/// The collation does matter. Grouping compares keys with the result collation
1078/// and the walk compares them with the structure's, so a `NOCASE` index does
1079/// not group a `BINARY` key: it would put `Ada` and `ADA` next to each other
1080/// and the grouping would then treat them as one.
1081/// @param select - the bound statement
1082/// @param outer - the planned outer term
1083fn grouped_by_walk(select: &BoundSelect, outer: &PlannedSource) -> bool {
1084    if select.group_by.is_empty() {
1085        return false;
1086    }
1087    let Some(key) = path_ordering(&outer.table, &outer.path) else {
1088        return false;
1089    };
1090    let mut wanted: Vec<(OrderedBy, Collation)> = Vec::new();
1091    for expr in &select.group_by {
1092        let Some(named) = walk_key_of(expr, outer.id, &outer.table) else {
1093            return false;
1094        };
1095        let collation = crate::bind::result_collation(expr);
1096        if !wanted.iter().any(|(held, _)| *held == named) {
1097            wanted.push((named, collation));
1098        }
1099    }
1100    covers_prefix(&key, &wanted)
1101}
1102
1103/// Returns whether the walk brings duplicate result rows together.
1104///
1105/// The same rule as [`grouped_by_walk`], over the result columns rather than
1106/// the group ones - and it is only asked when there is no grouping, because a
1107/// `DISTINCT` over aggregates is distinct over values the walk never saw.
1108/// @param select - the bound statement
1109/// @param outer - the planned outer term
1110fn distinct_by_walk(select: &BoundSelect, outer: &PlannedSource) -> bool {
1111    if !select.distinct || !select.group_by.is_empty() || !select.aggregates.is_empty() {
1112        return false;
1113    }
1114    let Some(key) = path_ordering(&outer.table, &outer.path) else {
1115        return false;
1116    };
1117    let mut wanted: Vec<(OrderedBy, Collation)> = Vec::new();
1118    for column in &select.columns {
1119        let Some(named) = walk_key_of(&column.expr, outer.id, &outer.table) else {
1120            return false;
1121        };
1122        let collation = crate::bind::result_collation(&column.expr);
1123        if !wanted.iter().any(|(held, _)| *held == named) {
1124            wanted.push((named, collation));
1125        }
1126    }
1127    covers_prefix(&key, &wanted)
1128}
1129
1130/// Returns whether a set of keys is exactly the walk's leading keys.
1131///
1132/// A key an equality pinned counts as held: it has one value for every row the
1133/// walk returns, so it is constant across the whole scan and cannot separate
1134/// two rows that are otherwise equal.
1135/// @param key - what the walk is ordered by
1136/// @param wanted - the keys that have to arrive together, with their collations
1137fn covers_prefix(key: &PathOrdering, wanted: &[(OrderedBy, Collation)]) -> bool {
1138    let free: Vec<&(OrderedBy, Collation)> = wanted
1139        .iter()
1140        .filter(|(named, _)| !key.pinned.contains(named))
1141        .collect();
1142    if free.len() > key.columns.len() {
1143        return false;
1144    }
1145    let prefix = match key.columns.get(..free.len()) {
1146        Some(prefix) => prefix,
1147        None => return false,
1148    };
1149    free.iter().all(|(named, collation)| {
1150        prefix
1151            .iter()
1152            .any(|(held, _, held_collation)| held == named && held_collation == collation)
1153    })
1154}
1155
1156/// Returns which of the walk's keys an expression names, if it names one.
1157/// @param expr - the expression to resolve
1158/// @param id - the outer term's source id
1159/// @param table - the table being walked
1160fn walk_key_of(expr: &BoundExpr, id: usize, table: &TableInfo) -> Option<OrderedBy> {
1161    let mut expr = expr;
1162    while let BoundExpr::Collate { operand, .. } = expr {
1163        expr = operand;
1164    }
1165    match expr {
1166        BoundExpr::Column { source, column, .. } if *source == id => {
1167            Some(named_key(table, OrderedBy::Column(*column)))
1168        }
1169        BoundExpr::Rowid { source } if *source == id => Some(OrderedBy::Rowid),
1170        _ => None,
1171    }
1172}
1173
1174/// What a term of an `ORDER BY` names.
1175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1176enum OrderedBy {
1177    /// A column of the table, by its declared position.
1178    Column(u16),
1179    /// The row's key.
1180    Rowid,
1181}
1182
1183/// The order one access path's own walk produces.
1184struct PathOrdering {
1185    /// What the walk is ordered by, in order, with each key's direction and the
1186    /// collation the structure compares it with.
1187    columns: Vec<(OrderedBy, bool, Collation)>,
1188    /// What an equality has pinned to a single value, which therefore holds
1189    /// still across the whole walk and cannot affect its order.
1190    pinned: Vec<OrderedBy>,
1191}
1192
1193/// Returns whether the outer term's path already produces the `ORDER BY`, and
1194/// whether it has to be walked backwards to do it.
1195///
1196/// `None` means it does not and the rows have to go through a sorter.
1197/// `Some(false)` means a forward walk answers it, `Some(true)` a backward one.
1198///
1199/// The rules are narrow on purpose, because getting this wrong returns rows in
1200/// the wrong order and nothing about the result looks wrong:
1201///
1202/// - every `ORDER BY` term is a plain column of the outer term, or its rowid;
1203/// - the path is one whose order is a key's - every rowid path, and an index
1204///   seek over whatever columns the equalities did not pin;
1205/// - the directions agree, all with the structure or all against it, because a
1206///   B-tree can be read either way but not both at once;
1207/// - the collation is the one the structure holds the column in;
1208/// - the NULLs land where the structure puts them, which for the SQL defaults
1209///   they already do: first ascending, last descending, exactly as an index
1210///   holds them.
1211///
1212/// A column an equality pinned is skipped rather than matched: it holds one
1213/// value for every row the path returns, so ordering by it changes nothing.
1214/// @param select - the bound statement, for its ORDER BY
1215/// @param outer - the planned outer term
1216fn ordering_provided(
1217    select: &BoundSelect,
1218    id: usize,
1219    table: &TableInfo,
1220    path: &AccessPath,
1221) -> Option<bool> {
1222    if select.order_by.is_empty() {
1223        return Some(false);
1224    }
1225    let key = path_ordering(table, path)?;
1226    let mut reverse: Option<bool> = None;
1227    let mut at = 0usize;
1228    for term in &select.order_by {
1229        // `ORDER BY name COLLATE NOCASE` binds to a `Collate` around the
1230        // column, and the collation it names is already on the term - so the
1231        // wrapper is unwrapped rather than refused, or the one case an index
1232        // exists precisely to answer would be the one case that sorted.
1233        let mut expr = &term.expr;
1234        while let BoundExpr::Collate { operand, .. } = expr {
1235            expr = operand;
1236        }
1237        let named = match expr {
1238            BoundExpr::Column { source, column, .. } if *source == id => {
1239                named_key(table, OrderedBy::Column(*column))
1240            }
1241            BoundExpr::Rowid { source } if *source == id => OrderedBy::Rowid,
1242            _ => return None,
1243        };
1244        let descending = matches!(term.order, SortOrder::Descending);
1245        // The binder has already defaulted this, so what is left is a written
1246        // placement - and only the one the structure already produces can be
1247        // answered by a walk: an index holds NULLs first, so a forward walk is
1248        // NULLS FIRST and a backward one is NULLS LAST.
1249        let natural = match term.nulls {
1250            NullOrder::First => !descending,
1251            NullOrder::Last => descending,
1252        };
1253        if !natural {
1254            return None;
1255        }
1256        if key.pinned.contains(&named) {
1257            continue;
1258        }
1259        let (held, held_descending, held_collation) = key.columns.get(at).copied()?;
1260        if held != named || held_collation != term.collation {
1261            return None;
1262        }
1263        let walk = descending != held_descending;
1264        match reverse {
1265            None => reverse = Some(walk),
1266            Some(existing) if existing == walk => {}
1267            Some(_) => return None,
1268        }
1269        at = at.saturating_add(1);
1270    }
1271    Some(reverse.unwrap_or(false))
1272}
1273
1274/// Returns the order one access path's walk produces, if it produces one.
1275/// @param table - the table being read
1276/// @param path - the chosen path
1277fn path_ordering(table: &TableInfo, path: &AccessPath) -> Option<PathOrdering> {
1278    match path {
1279        // A table B-tree is keyed by rowid, and a range over it is a slice of
1280        // that same walk.
1281        AccessPath::TableScan { .. } | AccessPath::RowidRange { .. } => Some(PathOrdering {
1282            columns: rowid_key(table),
1283            pinned: Vec::new(),
1284        }),
1285        // One row is in every order at once.
1286        AccessPath::RowidSeek { .. } => Some(PathOrdering {
1287            columns: Vec::new(),
1288            pinned: Vec::new(),
1289        }),
1290        AccessPath::IndexSeek {
1291            index_name,
1292            equalities,
1293            ..
1294        } => {
1295            let index = table
1296                .indexes
1297                .iter()
1298                .find(|candidate| candidate.name == *index_name)?;
1299            let mut columns: Vec<(OrderedBy, bool, Collation)> = Vec::new();
1300            let mut pinned: Vec<OrderedBy> = Vec::new();
1301            for (at, key_column) in index.columns.iter().enumerate() {
1302                // An expression key orders by something no ORDER BY term here
1303                // can name, so the walk stops describing itself at that point.
1304                let Some(column) = key_column.column else {
1305                    break;
1306                };
1307                let named = named_key(table, OrderedBy::Column(column));
1308                let collation = collation_of(&key_column.collation);
1309                if at < equalities.len() {
1310                    pinned.push(named);
1311                    continue;
1312                }
1313                columns.push((named, key_column.descending, collation));
1314            }
1315            // Every index entry ends with the row's key, so the walk is a total
1316            // order even where the indexed columns tie.
1317            columns.push((OrderedBy::Rowid, false, Collation::Binary));
1318            Some(PathOrdering { columns, pinned })
1319        }
1320        // A branch that needs de-duplicating (an `IN` list) has no order:
1321        // list values are probed in whatever order they were written, not
1322        // index order. A branch that does not - the keyset-range shape,
1323        // proven disjoint at plan time - runs each branch in the index's own
1324        // order and the branches themselves in that same order, so the whole
1325        // union reads exactly as an unconstrained walk of the index would: no
1326        // column is pinned, because no column has one value across every
1327        // branch.
1328        AccessPath::IndexSeekUnion {
1329            index_name,
1330            dedup: false,
1331            ..
1332        } => {
1333            let index = table
1334                .indexes
1335                .iter()
1336                .find(|candidate| candidate.name == *index_name)?;
1337            let mut columns: Vec<(OrderedBy, bool, Collation)> = Vec::new();
1338            for key_column in &index.columns {
1339                let Some(column) = key_column.column else {
1340                    break;
1341                };
1342                let named = named_key(table, OrderedBy::Column(column));
1343                let collation = collation_of(&key_column.collation);
1344                columns.push((named, key_column.descending, collation));
1345            }
1346            columns.push((OrderedBy::Rowid, false, Collation::Binary));
1347            Some(PathOrdering {
1348                columns,
1349                pinned: Vec::new(),
1350            })
1351        }
1352        _ => None,
1353    }
1354}
1355
1356/// Returns the ordering a rowid walk produces.
1357/// @param table - the table being walked
1358fn rowid_key(table: &TableInfo) -> Vec<(OrderedBy, bool, Collation)> {
1359    let _ = table;
1360    vec![(OrderedBy::Rowid, false, Collation::Binary)]
1361}
1362
1363/// Returns the one name a key goes by.
1364///
1365/// `INTEGER PRIMARY KEY` is the rowid under another name, so a statement that
1366/// ordered by the declared column and one that ordered by `rowid` asked for the
1367/// same walk. Folding the two spellings into one here is what lets the rest of
1368/// the comparison be an equality.
1369/// @param table - the table the column belongs to
1370/// @param named - the key as the statement or the index spelled it
1371fn named_key(table: &TableInfo, named: OrderedBy) -> OrderedBy {
1372    match named {
1373        OrderedBy::Column(column) if table.rowid_alias == Some(column) => OrderedBy::Rowid,
1374        other => other,
1375    }
1376}
1377
1378/// Returns the order the FROM terms are visited in.
1379///
1380/// The legality rule is the whole of the difficulty. An outer join's rows
1381/// depend on the terms it was written against: a `LEFT JOIN` cannot be visited
1382/// before the term it null-extends, and neither side of one can cross it. A
1383/// `CROSS JOIN` is SQLite's documented instruction not to reorder at all. So a
1384/// term may only move within the run of ordinary joins it belongs to, and the
1385/// enumeration is over those runs rather than over the whole list.
1386///
1387/// Inside a run the search is exhaustive while that is affordable - the runs
1388/// that occur in practice are two to five terms - and falls back to the written
1389/// order beyond, because a greedy answer that is worse than the written order
1390/// is worse than not reordering at all.
1391fn choose_order(select: &BoundSelect, terms: &[BoundExpr], levers: Levers) -> Vec<usize> {
1392    let count = select.sources.len();
1393    if count < 2 {
1394        return (0..count).collect();
1395    }
1396    let mut order = Vec::with_capacity(count);
1397    let mut run: Vec<usize> = Vec::new();
1398    for position in 0..count {
1399        let pins = select
1400            .sources
1401            .get(position)
1402            .is_some_and(|source| matches!(source.join, JoinKind::Cross) || is_outer(source.join));
1403        if pins {
1404            order.extend(best_order(select, terms, &run, levers));
1405            run.clear();
1406            order.push(position);
1407            continue;
1408        }
1409        run.push(position);
1410    }
1411    order.extend(best_order(select, terms, &run, levers));
1412    order
1413}
1414
1415/// Returns the cheapest visiting order for one run of reorderable terms.
1416fn best_order(
1417    select: &BoundSelect,
1418    terms: &[BoundExpr],
1419    run: &[usize],
1420    levers: Levers,
1421) -> Vec<usize> {
1422    // Eight terms is 40,320 orders, which is milliseconds; beyond that the
1423    // written order stands rather than a guess being substituted for it.
1424    if run.len() < 2 || run.len() > 8 {
1425        return run.to_vec();
1426    }
1427    let mut best: Option<(f64, Vec<usize>)> = None;
1428    let mut candidate = run.to_vec();
1429    permute(&mut candidate, 0, &mut |order| {
1430        let cost = order_cost(select, terms, order, levers);
1431        let better = best
1432            .as_ref()
1433            .is_none_or(|(existing, _)| cost < *existing - 1e-9);
1434        if better {
1435            best = Some((cost, order.to_vec()));
1436        }
1437    });
1438    best.map(|(_, order)| order).unwrap_or_else(|| run.to_vec())
1439}
1440
1441/// Calls a closure with every permutation of a slice.
1442fn permute(order: &mut Vec<usize>, at: usize, visit: &mut impl FnMut(&[usize])) {
1443    if at >= order.len() {
1444        visit(order);
1445        return;
1446    }
1447    for index in at..order.len() {
1448        order.swap(at, index);
1449        permute(order, at.saturating_add(1), visit);
1450        order.swap(at, index);
1451    }
1452}
1453
1454/// Returns what one visiting order is estimated to cost.
1455///
1456/// The loops are nested, so each term's cost is multiplied by the rows every
1457/// term before it produced - which is the whole reason the order matters, and
1458/// why putting the most selective term first is usually right and sometimes
1459/// spectacularly wrong.
1460fn order_cost(select: &BoundSelect, terms: &[BoundExpr], order: &[usize], levers: Levers) -> f64 {
1461    let ids: Vec<usize> = order
1462        .iter()
1463        .filter_map(|position| select.sources.get(*position))
1464        .map(|source| source.id)
1465        .collect();
1466    let mut consumed = vec![false; terms.len()];
1467    let mut total = 0.0f64;
1468    let mut outer_rows = 1.0f64;
1469    for (level, position) in order.iter().enumerate() {
1470        let Some(source) = select.sources.get(*position) else {
1471            continue;
1472        };
1473        let path = choose_path(level, &ids, source, select, terms, &mut consumed, levers);
1474        let (cost, rows) = path_cost(source, &path);
1475        total += outer_rows * cost;
1476        outer_rows *= rows.max(1.0);
1477    }
1478    total
1479}
1480
1481/// Returns the vector probe a `TopN` over a distance can use, when it can.
1482///
1483/// **Every condition here is a way the rewrite would change the answer.** The
1484/// index returns `k` candidates and nothing else, so the query has to be asking
1485/// for the nearest `k` of *this* table by *this* measure and by nothing else:
1486///
1487/// - one FROM term, because a join's other side may multiply or drop rows and
1488///   the k the index was asked for would then be the wrong k;
1489/// - the only `ORDER BY` term, ascending, so the index's order and the query's
1490///   are the same order;
1491/// - a `LIMIT` that is a literal, because the index has to be told how deep to
1492///   go before the statement runs;
1493/// - no `OFFSET`, no `GROUP BY`, no aggregate and no `DISTINCT`, each of which
1494///   reads rows the top k does not contain;
1495/// - and a probe that reads no column, because a per-row probe is a different
1496///   query - the index answers one question, not one per row;
1497/// - and the `ORDER BY` function names the distance the index minimises
1498///   (`IndexInfo::metric`), or it falls back to scan-and-sort instead.
1499///
1500/// A `WHERE` clause is *allowed*: the residual is tested over the candidates,
1501/// which is what SQLite does with a partial index and what pgvector's own
1502/// documentation warns about - a narrow filter over an approximate index can
1503/// return fewer than `k` rows. It is the caller's query and this does not
1504/// second-guess it.
1505///
1506/// @param id - the FROM term's statement-wide number
1507/// @param position - where it sits in the join order
1508/// @param source - the term
1509/// @param select - the whole statement, for its `ORDER BY` and `LIMIT`
1510fn vector_path(
1511    id: usize,
1512    position: usize,
1513    source: &BoundSource,
1514    select: &BoundSelect,
1515) -> Option<AccessPath> {
1516    if position != 0 || select.sources.len() != 1 {
1517        return None;
1518    }
1519    if select.distinct
1520        || !select.group_by.is_empty()
1521        || !select.aggregates.is_empty()
1522        || select.offset.is_some()
1523        || !select.compounds.is_empty()
1524    {
1525        return None;
1526    }
1527    let [term] = select.order_by.as_slice() else {
1528        return None;
1529    };
1530    if term.order != crate::ast::SortOrder::Ascending {
1531        return None;
1532    }
1533    let Some(BoundExpr::Integer(depth)) = select.limit.as_ref() else {
1534        return None;
1535    };
1536    let depth = usize::try_from(*depth).ok().filter(|held| *held > 0)?;
1537    let BoundExpr::Function {
1538        func, arguments, ..
1539    } = &term.expr
1540    else {
1541        return None;
1542    };
1543    let wanted = match func {
1544        crate::function::ScalarFunc::VectorDistanceCos => crate::catalog_view::IndexMetric::Cosine,
1545        crate::function::ScalarFunc::VectorDistanceL2 => crate::catalog_view::IndexMetric::L2,
1546        _ => return None,
1547    };
1548    let [BoundExpr::Column {
1549        source: held,
1550        column,
1551        ..
1552    }, probe] = arguments.as_slice()
1553    else {
1554        return None;
1555    };
1556    if *held != id || reads_a_column(probe) {
1557        return None;
1558    }
1559    let index = source.table.indexes.iter().find(|held| {
1560        held.origin == crate::catalog_view::IndexOrigin::Module
1561            && held.metric == Some(wanted)
1562            && held
1563                .columns
1564                .first()
1565                .is_some_and(|first| first.column == Some(*column))
1566    })?;
1567    Some(AccessPath::VectorProbe {
1568        root: source.table.root,
1569        index: index.name.clone(),
1570        probe: Box::new(probe.clone()),
1571        depth,
1572    })
1573}
1574
1575/// Reports whether an expression reads any column or rowid.
1576///
1577/// A probe that did would be a different question per row, and the index
1578/// answers one.
1579///
1580/// @param expr - the expression to look through
1581fn reads_a_column(expr: &BoundExpr) -> bool {
1582    if matches!(
1583        expr,
1584        BoundExpr::Column { .. } | BoundExpr::Rowid { .. } | BoundExpr::VirtualFunction { .. }
1585    ) {
1586        return true;
1587    }
1588    expr.children().into_iter().any(reads_a_column)
1589}
1590
1591/// Returns what one term's path costs, and how many rows it produces.
1592fn path_cost(source: &BoundSource, path: &AccessPath) -> (f64, f64) {
1593    let rows = estimated_rows(&source.table);
1594    match path {
1595        AccessPath::TableScan { .. } => (cost::scan_cost(rows), rows),
1596        // A module prices its own scan, and the planner cannot ask it here
1597        // without making the plan depend on run-time state. What it can do is
1598        // reward an offer: a virtual table that was given a constraint will be
1599        // cheaper than one that was not, whatever the module then says.
1600        AccessPath::VirtualScan { offer, .. } => {
1601            let usable = offer.iter().filter(|item| item.spec.usable).count();
1602            let rows = if usable == 0 {
1603                rows
1604            } else {
1605                rows / (usable as f64 * 8.0)
1606            };
1607            (cost::scan_cost(rows.max(1.0)), rows.max(1.0))
1608        }
1609        // The index returns `depth` rows and the walk visits exactly those, so
1610        // the cost is a descent per candidate and the row count is the depth -
1611        // which is what makes it beat a scan on a table of any size and lose to
1612        // one on a table smaller than `k`.
1613        AccessPath::VectorProbe { depth, .. } => {
1614            let matches = (*depth as f64).min(rows).max(1.0);
1615            (cost::search_cost(rows, matches, true), matches)
1616        }
1617        AccessPath::RowidSeek { .. } => (cost::search_cost(rows, 1.0, true), 1.0),
1618        // A search per key, each a descent of the same tree - which is
1619        // exactly what running them one after another actually costs, and is
1620        // why a list long enough to be worth a scan instead gets priced that
1621        // way on its own, with no separate penalty needed for how many
1622        // branches there are.
1623        AccessPath::RowidSeekUnion { keys, .. } => {
1624            let branches = keys.len().max(1) as f64;
1625            (cost::search_cost(rows, 1.0, true) * branches, branches)
1626        }
1627        AccessPath::RowidRange { low, high, .. } => {
1628            let bounds = usize::from(low.is_some()) + usize::from(high.is_some());
1629            let mut matches = rows;
1630            for _ in 0..bounds {
1631                matches /= cost::RANGE_SHARE;
1632            }
1633            let matches = matches.max(1.0);
1634            (cost::search_cost(rows, matches, true), matches)
1635        }
1636        AccessPath::IndexSeek {
1637            index_name,
1638            equalities,
1639            low,
1640            high,
1641            covering,
1642            ..
1643        } => {
1644            let bounds = usize::from(low.is_some()) + usize::from(high.is_some());
1645            index_seek_cost(
1646                source,
1647                rows,
1648                index_name,
1649                equalities.len(),
1650                bounds,
1651                covering.is_some(),
1652            )
1653        }
1654        // A union is priced by summing one branch's cost over every branch -
1655        // each is a full descent of the same tree, so the total is exactly
1656        // what running them one after another costs, and a branch count large
1657        // enough to make that expensive is a branch count large enough for a
1658        // scan to win the comparison on its own.
1659        AccessPath::IndexSeekUnion {
1660            index_name,
1661            branches,
1662            covering,
1663            ..
1664        } => {
1665            let mut total_cost = 0.0f64;
1666            let mut total_matches = 0.0f64;
1667            for branch in branches {
1668                let bounds = usize::from(branch.low.is_some()) + usize::from(branch.high.is_some());
1669                let (branch_cost, branch_matches) = index_seek_cost(
1670                    source,
1671                    rows,
1672                    index_name,
1673                    branch.equalities.len(),
1674                    bounds,
1675                    covering.is_some(),
1676                );
1677                total_cost += branch_cost;
1678                total_matches += branch_matches;
1679            }
1680            (total_cost, total_matches.max(1.0))
1681        }
1682        // A materialised term is built once and then scanned; the build is
1683        // charged where it happens, which is the block that fills it.
1684        AccessPath::Subquery { .. } | AccessPath::Recursive { .. } => (cost::scan_cost(rows), rows),
1685        AccessPath::RecursiveSelf { .. } => (1.0, 1.0),
1686    }
1687}
1688
1689/// Returns what one seek over an index costs, and how many rows it produces.
1690///
1691/// Shared by [`AccessPath::IndexSeek`] and each branch of an
1692/// [`AccessPath::IndexSeekUnion`], which price identically - a union is
1693/// priced by summing this over its branches.
1694/// @param source - the FROM term the index belongs to
1695/// @param rows - the table's estimated row count
1696/// @param index_name - the index being priced
1697/// @param equalities - how many leading columns the seek's equality prefix pins
1698/// @param bounds - how many range bounds the seek carries after the prefix
1699/// @param covering - whether the seek reads entries rather than fetching rows
1700fn index_seek_cost(
1701    source: &BoundSource,
1702    rows: f64,
1703    index_name: &[u8],
1704    equalities: usize,
1705    bounds: usize,
1706    covering: bool,
1707) -> (f64, f64) {
1708    let index = source
1709        .table
1710        .indexes
1711        .iter()
1712        .find(|candidate| candidate.name == index_name);
1713    let matches = index_matches(index, rows, equalities, bounds);
1714    let Some(index) = index else {
1715        return (cost::search_cost(rows, matches, false), matches);
1716    };
1717    if !covering {
1718        return (cost::search_cost(rows, matches, false), matches);
1719    }
1720    // A covering path reads entries rather than rows, and an entry is the
1721    // indexed columns plus the key rather than the whole row. Cost is bytes
1722    // touched, so the narrower shape is the saving - and it is the whole
1723    // reason a covering scan of a two-column index beats a table scan of a
1724    // five-column table when there is no predicate at all to narrow either of
1725    // them.
1726    let width = cost::entry_share(index.columns.len(), source.table.columns.len());
1727    (cost::search_cost(rows, matches * width, true), matches)
1728}
1729
1730/// Returns how many rows a table is estimated to hold.
1731fn estimated_rows(table: &TableInfo) -> f64 {
1732    match table.analysed_rows {
1733        Some(rows) if rows > 0 => rows as f64,
1734        // A measured zero is a real answer, and so is an unmeasured table: the
1735        // first is empty and the second is assumed large. Collapsing them would
1736        // make an `ANALYZE` on an empty table look like no `ANALYZE` at all.
1737        Some(_) => 1.0,
1738        None => cost::DEFAULT_ROWS,
1739    }
1740}
1741
1742/// Returns how many rows an index search is estimated to return.
1743fn index_matches(index: Option<&IndexInfo>, rows: f64, equalities: usize, bounds: usize) -> f64 {
1744    // **A partial index walked whole returns what it holds.**
1745    // With nothing to seek to, every other arm below prices this as a walk of
1746    // the table - which is what it would be for an ordinary index, and is not
1747    // what it is for one holding only the rows a predicate accepted.
1748    if equalities == 0 && bounds == 0 {
1749        if let Some(index) = index {
1750            if index.partial_sql.is_some() {
1751                if let Some(held) = index.analysed_rows {
1752                    return (held as f64).max(1.0);
1753                }
1754            }
1755        }
1756    }
1757    let mut matches = match index {
1758        // Measured: the average number of rows sharing the prefix the search
1759        // pinned down. This is the number `ANALYZE` exists to supply.
1760        Some(index) if !index.prefix_rows.is_empty() && equalities > 0 => index
1761            .prefix_rows
1762            .get(equalities.saturating_sub(1))
1763            .copied()
1764            .map(|value| value as f64)
1765            .unwrap_or(rows),
1766        // Unmeasured: a unique index pins one row.
1767        Some(index) if index.unique && equalities >= index.columns.len() => 1.0,
1768        // Unmeasured, not unique: SQLite's own default, which is an absolute
1769        // count rather than a share of the table. A column somebody indexed and
1770        // then compared for equality has many distinct values - that is why it
1771        // was indexed - so the number of rows behind one value does not grow
1772        // with the table the way a fraction does.
1773        Some(_) if equalities > 0 => cost::default_equality_rows(equalities, rows),
1774        _ => {
1775            let mut estimate = rows;
1776            for _ in 0..equalities {
1777                estimate /= cost::EQUALITY_SHARE;
1778            }
1779            estimate
1780        }
1781    };
1782    // Once per bound, not once per range. SQLite reduces the estimate by a
1783    // factor for the lower bound and again for the upper, which is why
1784    // `BETWEEN` is treated as sixteen times more selective than a bare `>` -
1785    // and treating them alike made a two-sided range look like a quarter of the
1786    // table, which is a quarter no join order can beat a scan with.
1787    for _ in 0..bounds {
1788        matches /= cost::RANGE_SHARE;
1789    }
1790    matches.max(1.0)
1791}
1792
1793/// Returns whether a join keeps rows that match nothing on the other side.
1794pub fn is_outer(join: JoinKind) -> bool {
1795    matches!(join, JoinKind::Left | JoinKind::Right | JoinKind::Full)
1796}
1797
1798/// Splits `a AND b AND c` into its terms.
1799///
1800/// Only `AND` is split. Splitting an `OR` would produce terms that are not
1801/// individually true of every row the expression accepts, which is the classic
1802/// way to lose rows.
1803pub fn split_conjunction(expr: &BoundExpr, into: &mut Vec<BoundExpr>) {
1804    match expr {
1805        BoundExpr::And(left, right) => {
1806            split_conjunction(left, into);
1807            split_conjunction(right, into);
1808        }
1809        // `x BETWEEN a AND b` *is* `x >= a AND x <= b`, so splitting it lets an
1810        // index range be found where otherwise the whole thing sat in the
1811        // residual and the table was scanned. It is split only when `x` is a
1812        // column, which is both the case that can drive an index and the case
1813        // where evaluating the operand twice cannot change an answer: a
1814        // volatile expression tested twice is a different question.
1815        BoundExpr::Between {
1816            negated: false,
1817            operand,
1818            low,
1819            high,
1820            low_affinity,
1821            low_collation,
1822            high_affinity,
1823            high_collation,
1824        } if matches!(
1825            **operand,
1826            BoundExpr::Column { .. } | BoundExpr::Rowid { .. }
1827        ) =>
1828        {
1829            // Each half keeps the affinity and collation of its own bound,
1830            // which is what SQLite's two comparisons use (task-2088). The
1831            // `between-index*` cases in `differential-part8/task2088.cases`
1832            // grade this path with and without `INDEXED BY`.
1833            into.push(BoundExpr::Compare {
1834                op: BinaryOp::GreaterEqual,
1835                left: operand.clone(),
1836                right: low.clone(),
1837                affinity: *low_affinity,
1838                collation: *low_collation,
1839            });
1840            into.push(BoundExpr::Compare {
1841                op: BinaryOp::LessEqual,
1842                left: operand.clone(),
1843                right: high.clone(),
1844                affinity: *high_affinity,
1845                collation: *high_collation,
1846            });
1847        }
1848        other => into.push(other.clone()),
1849    }
1850}
1851
1852/// Attaches each unconsumed predicate to the innermost term it reads.
1853///
1854/// A predicate that reads only FROM terms belonging to an *enclosing* block is
1855/// constant for the whole of this block: the outer cursors are positioned
1856/// before it starts and do not move while it runs, so it is tested once before
1857/// the loops rather than once per row.
1858fn distribute_residuals(
1859    terms: &[BoundExpr],
1860    consumed: &[bool],
1861    ids: &[usize],
1862) -> (Vec<Option<BoundExpr>>, Option<BoundExpr>) {
1863    let levels = ids.len();
1864    let mut residuals: Vec<Option<BoundExpr>> = vec![None; levels];
1865    let mut constant: Option<BoundExpr> = None;
1866    for (index, term) in terms.iter().enumerate() {
1867        if consumed.get(index).copied().unwrap_or(false) {
1868            continue;
1869        }
1870        let mut used = Vec::new();
1871        term.sources_used(&mut used);
1872        let level = used
1873            .iter()
1874            .filter_map(|source| ids.iter().position(|id| id == source))
1875            .max();
1876        match level {
1877            Some(level) if level < levels => {
1878                if let Some(slot) = residuals.get_mut(level) {
1879                    *slot = Some(match slot.take() {
1880                        Some(existing) => {
1881                            BoundExpr::And(Box::new(existing), Box::new(term.clone()))
1882                        }
1883                        None => term.clone(),
1884                    });
1885                }
1886            }
1887            _ => {
1888                constant = Some(match constant.take() {
1889                    Some(existing) => BoundExpr::And(Box::new(existing), Box::new(term.clone())),
1890                    None => term.clone(),
1891                });
1892            }
1893        }
1894    }
1895    (residuals, constant)
1896}
1897
1898/// Chooses the access path for one FROM term.
1899fn choose_path(
1900    position: usize,
1901    ids: &[usize],
1902    source: &BoundSource,
1903    select: &BoundSelect,
1904    terms: &[BoundExpr],
1905    consumed: &mut [bool],
1906    levers: Levers,
1907) -> AccessPath {
1908    match &source.rows {
1909        SourceRows::Subquery(block) => {
1910            let width = block.columns.len();
1911            let correlated = !block.correlations.is_empty();
1912            return AccessPath::Subquery {
1913                plan: Box::new(plan_select_with((**block).clone(), levers)),
1914                width,
1915                correlated,
1916            };
1917        }
1918        SourceRows::Recursive(body) => {
1919            let width = body.seeds.first().map_or(0, |(_, arm)| arm.columns.len());
1920            return AccessPath::Recursive {
1921                seeds: body
1922                    .seeds
1923                    .iter()
1924                    .map(|(op, arm)| (*op, plan_select_with(arm.clone(), levers)))
1925                    .collect(),
1926                steps: body
1927                    .steps
1928                    .iter()
1929                    .map(|(op, arm)| (*op, plan_select_with(arm.clone(), levers)))
1930                    .collect(),
1931                width,
1932            };
1933        }
1934        SourceRows::RecursiveSelf { cte } => {
1935            return AccessPath::RecursiveSelf { cte: *cte };
1936        }
1937        SourceRows::Table => {}
1938    }
1939    let id = ids.get(position).copied().unwrap_or(position);
1940    let table = &source.table;
1941    // **The k nearest, when the query asked exactly that.** Tried before the
1942    // b-tree paths because none of them apply: an index a module owns has no
1943    // key to seek and no range to walk, and the shape it answers - a distance
1944    // ordered ascending with a `LIMIT` - is one no other path can improve on.
1945    let forced = match &source.index_hint {
1946        crate::bind::IndexChoice::Only(wanted) => Some(wanted.as_slice()),
1947        _ => None,
1948    };
1949    if let Some(path) = vector_path(id, position, source, select) {
1950        // `INDEXED BY` a b-tree index rules the probe out like every other
1951        // path; `INDEXED BY` the probe's own index is the one way to keep it.
1952        let named = match &path {
1953            AccessPath::VectorProbe { index, .. } => table
1954                .indexes
1955                .iter()
1956                .find(|held| &held.name == index)
1957                .map(|held| held.folded.as_slice()),
1958            _ => None,
1959        };
1960        if forced.is_none() || forced == named {
1961            return path;
1962        }
1963    }
1964    if let Some(module) = table.module.clone() {
1965        return virtual_path(id, position, ids, source, select, module, terms, consumed);
1966    }
1967    if forced.is_some() {
1968        return forced_path(id, position, ids, source, select, terms, consumed, levers);
1969    }
1970    // Every candidate is built against a *copy* of the consumed list, because a
1971    // path that is not chosen must not leave its predicates marked as handled.
1972    // It did: when a scan beat an index range, the range's own comparison had
1973    // already been struck off the residual list and the scan then returned
1974    // every row of the table, silently.
1975    let mut candidates: Vec<(AccessPath, Vec<bool>)> = Vec::new();
1976    let mut trial = consumed.to_vec();
1977    if let Some(path) = rowid_path(id, position, ids, table, terms, &mut trial) {
1978        candidates.push((path, trial));
1979    }
1980    // **`NOT INDEXED` removes the index candidates and nothing else.** SQLite's
1981    // rule is that the clause prohibits every index on the table while leaving
1982    // the INTEGER PRIMARY KEY usable, which is why `rowid_path` above is
1983    // unconditional and this is the one candidate the hint takes away.
1984    //
1985    // `crates/inillucent-cli/src/diagnose.rs` is what this is for. Its integrity
1986    // digest reads every table `SELECT * FROM "t" NOT INDEXED`, and its comment
1987    // says that is what makes the digest a fact about the rows - which was not
1988    // true while the hint was dropped, because a corrupt index would then be
1989    // read in place of the table it was meant to be checked against.
1990    if source.index_hint != crate::bind::IndexChoice::NotIndexed {
1991        let mut trial = consumed.to_vec();
1992        let needed = select.columns_read(id);
1993        if let Some(path) = index_path(
1994            id, position, ids, source, terms, &mut trial, &needed, levers,
1995        ) {
1996            candidates.push((path, trial));
1997        }
1998    }
1999    candidates.push((
2000        AccessPath::TableScan { root: table.root },
2001        consumed.to_vec(),
2002    ));
2003
2004    // A scan beats a search that returns most of the table: an index that has
2005    // to fetch every row costs a second descent per row on top of the scan it
2006    // was meant to avoid. And a path that already produces the ORDER BY beats
2007    // one that does not by the whole cost of the sort it saves, which is how a
2008    // `LIMIT 50` over six hundred thousand rows becomes fifty rows read rather
2009    // than six hundred thousand read, sorted and thrown away.
2010    let sort = sort_penalty(select, position, source, levers);
2011    let mut best: Option<(f64, AccessPath, Vec<bool>)> = None;
2012    for (path, trial) in candidates {
2013        let (mut cost, _) = path_cost(source, &path);
2014        if !levers.has(Levers::ORDERED_WALK)
2015            || ordering_provided(select, id, table, &path).is_none()
2016        {
2017            cost += sort;
2018        }
2019        if best
2020            .as_ref()
2021            .is_none_or(|(existing, _, _)| cost < *existing - 1e-9)
2022        {
2023            best = Some((cost, path, trial));
2024        }
2025    }
2026    match best {
2027        Some((_, path, trial)) => {
2028            consumed.copy_from_slice(&trial);
2029            path
2030        }
2031        None => AccessPath::TableScan { root: table.root },
2032    }
2033}
2034
2035/// Returns what a sort would cost this term, or nothing when no path could
2036/// avoid one anyway.
2037///
2038/// Only the outermost term of a single-term statement can answer an `ORDER BY`
2039/// by walking: an inner loop restarts for every outer row, and the order it
2040/// produces inside one of those runs is not the order of the result. Charging
2041/// the sort anywhere else would tilt a plan towards an index for a saving it
2042/// would not make.
2043/// @param select - the bound statement
2044/// @param position - which visiting position this term is at
2045/// @param source - the term being priced
2046fn sort_penalty(
2047    select: &BoundSelect,
2048    position: usize,
2049    source: &BoundSource,
2050    levers: Levers,
2051) -> f64 {
2052    // A grouped or DISTINCT statement that streams over the walk answers its
2053    // ORDER BY the same way an ungrouped one does, so it is priced the same
2054    // way. Charging it the sort regardless would hide the saving that makes the
2055    // index path worth taking.
2056    let streams = levers.has(Levers::STREAMING_GROUP)
2057        && ((!select.group_by.is_empty() && !select.distinct)
2058            || (select.distinct && select.group_by.is_empty() && select.aggregates.is_empty()));
2059    let answerable = levers.has(Levers::ORDERED_WALK)
2060        && position == 0
2061        && select.sources.len() == 1
2062        && select.windows.is_empty()
2063        && select.compounds.is_empty()
2064        && !select.order_by.is_empty()
2065        && ((select.group_by.is_empty() && select.aggregates.is_empty() && !select.distinct)
2066            || streams);
2067    if !answerable {
2068        return 0.0;
2069    }
2070    cost::sort_cost(estimated_rows(&source.table))
2071}
2072
2073/// Builds the offer a virtual table's module will be shown.
2074///
2075/// Every predicate that compares one of this term's columns - or its rowid - to
2076/// something is offered, whether or not the value is available yet: a
2077/// constraint the loop order has put out of reach is offered as *not usable*,
2078/// which is what lets one answer serve every position the term could take.
2079fn virtual_path(
2080    id: usize,
2081    position: usize,
2082    ids: &[usize],
2083    source: &BoundSource,
2084    select: &BoundSelect,
2085    module: crate::vtab::ModuleRef,
2086    terms: &[BoundExpr],
2087    consumed: &mut [bool],
2088) -> AccessPath {
2089    let table = &source.table;
2090    let mut offer = Vec::new();
2091    for (index, term) in terms.iter().enumerate() {
2092        if consumed.get(index).copied().unwrap_or(false) {
2093            continue;
2094        }
2095        let Some((column, op, value)) = virtual_constraint(id, table, term) else {
2096            continue;
2097        };
2098        offer.push(VirtualConstraint {
2099            spec: crate::vtab::ConstraintSpec {
2100                column,
2101                op,
2102                usable: is_available(position, ids, &value),
2103            },
2104            value,
2105            predicate: term.clone(),
2106        });
2107        if let Some(slot) = consumed.get_mut(index) {
2108            *slot = true;
2109        }
2110    }
2111    let order_by = order_offer(id, position, select);
2112    AccessPath::VirtualScan {
2113        module,
2114        offer,
2115        order_by,
2116        chosen: None,
2117    }
2118}
2119
2120/// Returns the `ORDER BY` a module may be able to satisfy for itself.
2121///
2122/// Only the outermost loop is offered one. An inner loop restarts for every row
2123/// of the loops around it, so an ordering it produced would be an ordering
2124/// within each of those restarts - which is not the statement's ordering and
2125/// would let the sorter be skipped wrongly.
2126fn order_offer(id: usize, position: usize, select: &BoundSelect) -> Vec<crate::vtab::OrderSpec> {
2127    if position != 0 {
2128        return Vec::new();
2129    }
2130    let mut offer = Vec::new();
2131    for term in &select.order_by {
2132        let column = match &term.expr {
2133            BoundExpr::Column { source, column, .. } if *source == id => i32::from(*column),
2134            BoundExpr::Rowid { source } if *source == id => crate::vtab::ROWID_COLUMN,
2135            _ => return Vec::new(),
2136        };
2137        offer.push(crate::vtab::OrderSpec {
2138            column,
2139            descending: term.order == crate::ast::SortOrder::Descending,
2140        });
2141    }
2142    offer
2143}
2144
2145/// Splits a predicate into the conjunction the offer is built from.
2146pub fn conjunction(filter: &BoundExpr) -> Vec<BoundExpr> {
2147    let mut terms = Vec::new();
2148    split_conjunction(filter, &mut terms);
2149    terms
2150}
2151
2152/// Returns the column, operator and value when a term constrains this term.
2153fn virtual_constraint(
2154    id: usize,
2155    table: &TableInfo,
2156    term: &BoundExpr,
2157) -> Option<(i32, crate::vtab::ConstraintOp, BoundExpr)> {
2158    use crate::vtab::{ConstraintOp, ROWID_COLUMN};
2159    // `x MATCH 'y'`, `x LIKE 'y'`, `x GLOB 'y'` and `x REGEXP 'y'` are the
2160    // operators a module exists to give meaning to, so they are offered first.
2161    if let BoundExpr::Pattern {
2162        negated: false,
2163        op,
2164        operand,
2165        pattern,
2166        escape: None,
2167    } = term
2168    {
2169        if let BoundExpr::Column { source, column, .. } = operand.as_ref() {
2170            if *source == id {
2171                let op = match op {
2172                    crate::ast::PatternOp::Match => ConstraintOp::Match,
2173                    crate::ast::PatternOp::Like => ConstraintOp::Like,
2174                    crate::ast::PatternOp::Glob => ConstraintOp::Glob,
2175                    crate::ast::PatternOp::Regexp => ConstraintOp::Regexp,
2176                };
2177                return Some((i32::from(*column), op, pattern.as_ref().clone()));
2178            }
2179        }
2180    }
2181    if let Some((op, value)) = comparison_against_rowid(id, term) {
2182        return binary_constraint(op).map(|op| (ROWID_COLUMN, op, value));
2183    }
2184    for column in 0..table.columns.len() {
2185        let column = column as u16;
2186        if let Some((op, value)) = comparison_against_column(id, column, term) {
2187            return binary_constraint(op).map(|op| (i32::from(column), op, value));
2188        }
2189    }
2190    None
2191}
2192
2193/// Returns the constraint operator one comparison offers, if any.
2194fn binary_constraint(op: BinaryOp) -> Option<crate::vtab::ConstraintOp> {
2195    use crate::vtab::ConstraintOp;
2196    Some(match op {
2197        BinaryOp::Equal => ConstraintOp::Eq,
2198        BinaryOp::NotEqual => ConstraintOp::Ne,
2199        BinaryOp::Less => ConstraintOp::Lt,
2200        BinaryOp::LessEqual => ConstraintOp::Le,
2201        BinaryOp::Greater => ConstraintOp::Gt,
2202        BinaryOp::GreaterEqual => ConstraintOp::Ge,
2203        _ => return None,
2204    })
2205}
2206
2207/// Returns how an UPDATE or a DELETE should find the rows it touches, with some
2208/// optimizations switched off.
2209/// @param table - the table being written
2210/// @param source_id - the source the filter's columns are bound to
2211/// @param filter - the WHERE clause, when there is one
2212/// @param levers - which optimizations are on
2213pub fn write_path_with(
2214    table: &TableInfo,
2215    source_id: usize,
2216    filter: Option<&BoundExpr>,
2217    levers: Levers,
2218) -> AccessPath {
2219    if !levers.has(Levers::INDEXED_WRITE) {
2220        return AccessPath::TableScan { root: table.root };
2221    }
2222    let scan = AccessPath::TableScan { root: table.root };
2223    if table.module.is_some() || table.without_rowid {
2224        return scan;
2225    }
2226    let Some(filter) = filter else {
2227        return scan;
2228    };
2229    let mut terms = Vec::new();
2230    split_conjunction(filter, &mut terms);
2231    let ids = [source_id];
2232    let mut consumed = vec![false; terms.len()];
2233    if let Some(path) = rowid_path(source_id, 0, &ids, table, &terms, &mut consumed) {
2234        return path;
2235    }
2236    let source = BoundSource {
2237        index_hint: crate::bind::IndexChoice::Any,
2238        id: source_id,
2239        rows: SourceRows::Table,
2240        table: std::rc::Rc::new(table.clone()),
2241        alias: table.name.clone(),
2242        join: JoinKind::Inner,
2243        constraint: None,
2244        suppressed: Vec::new(),
2245        index_exprs: Vec::new(),
2246    };
2247    let mut consumed = vec![false; terms.len()];
2248    // A write reads the whole row it is about to change, so no index covers it.
2249    let needed = ColumnUse {
2250        opaque: true,
2251        ..ColumnUse::default()
2252    };
2253    let Some(path) = index_path(
2254        source_id,
2255        0,
2256        &ids,
2257        &source,
2258        &terms,
2259        &mut consumed,
2260        &needed,
2261        levers,
2262    ) else {
2263        return scan;
2264    };
2265    // The same crossover the read planner uses: an index that has to fetch most
2266    // of the table costs a second descent per row on top of the scan it was
2267    // meant to replace.
2268    let (index_cost, _) = path_cost(&source, &path);
2269    let (scan_cost, _) = path_cost(&source, &scan);
2270    if index_cost <= scan_cost {
2271        return path;
2272    }
2273    scan
2274}
2275
2276/// Returns a rowid equality or range path, when the predicates allow one.
2277fn rowid_path(
2278    id: usize,
2279    position: usize,
2280    ids: &[usize],
2281    table: &TableInfo,
2282    terms: &[BoundExpr],
2283    consumed: &mut [bool],
2284) -> Option<AccessPath> {
2285    if !table.has_rowid() {
2286        return None;
2287    }
2288    for (index, term) in terms.iter().enumerate() {
2289        if consumed.get(index).copied().unwrap_or(false) {
2290            continue;
2291        }
2292        let Some((op, value)) = comparison_against_rowid(id, term) else {
2293            continue;
2294        };
2295        if op != BinaryOp::Equal || !is_available(position, ids, &value) {
2296            continue;
2297        }
2298        if let Some(slot) = consumed.get_mut(index) {
2299            *slot = true;
2300        }
2301        return Some(AccessPath::RowidSeek {
2302            root: table.root,
2303            key: value,
2304        });
2305    }
2306    if let Some(path) = seek_union::rowid_in_list_path(id, position, ids, table, terms, consumed) {
2307        return Some(path);
2308    }
2309    // **A range is an outermost-term path only.** The physical pass drives an
2310    // inner term either by probing it per outer row or by reading it once into
2311    // a buffer, and neither of those is a walk between two bounds - so a range
2312    // chosen here for an inner term was refused downstream with "the physical
2313    // pass does not handle a rowid range as an inner join term yet", which is
2314    // what `SELECT x.id, y.id FROM t x JOIN t y ON y.a = x.a AND y.id > x.id`
2315    // hit. Not choosing it is better than refusing it: the bound stays
2316    // unconsumed, so it is tested as a residual over the pair and the self join
2317    // answers. The equality half above is unaffected, because a seek per outer
2318    // row *is* what an index nested loop does.
2319    if position != 0 {
2320        return None;
2321    }
2322    let mut low = None;
2323    let mut high = None;
2324    let mut used = Vec::new();
2325    for (index, term) in terms.iter().enumerate() {
2326        if consumed.get(index).copied().unwrap_or(false) {
2327            continue;
2328        }
2329        let Some((op, value)) = comparison_against_rowid(id, term) else {
2330            continue;
2331        };
2332        if !is_available(position, ids, &value) {
2333            continue;
2334        }
2335        match op {
2336            BinaryOp::Greater if low.is_none() => {
2337                low = Some(RangeBound {
2338                    kind: BoundKind::Greater,
2339                    value,
2340                    unconverted: false,
2341                });
2342                used.push(index);
2343            }
2344            BinaryOp::GreaterEqual if low.is_none() => {
2345                low = Some(RangeBound {
2346                    kind: BoundKind::GreaterEqual,
2347                    value,
2348                    unconverted: false,
2349                });
2350                used.push(index);
2351            }
2352            BinaryOp::Less if high.is_none() => {
2353                high = Some(RangeBound {
2354                    kind: BoundKind::Less,
2355                    value,
2356                    unconverted: false,
2357                });
2358                used.push(index);
2359            }
2360            BinaryOp::LessEqual if high.is_none() => {
2361                high = Some(RangeBound {
2362                    kind: BoundKind::LessEqual,
2363                    value,
2364                    unconverted: false,
2365                });
2366                used.push(index);
2367            }
2368            _ => {}
2369        }
2370    }
2371    if low.is_none() && high.is_none() {
2372        return None;
2373    }
2374    for index in used {
2375        if let Some(slot) = consumed.get_mut(index) {
2376            *slot = true;
2377        }
2378    }
2379    Some(AccessPath::RowidRange {
2380        root: table.root,
2381        low,
2382        high,
2383    })
2384}
2385
2386/// Returns an index path over an equality prefix, when one is usable.
2387fn index_path(
2388    id: usize,
2389    position: usize,
2390    ids: &[usize],
2391    source: &BoundSource,
2392    terms: &[BoundExpr],
2393    consumed: &mut [bool],
2394    needed: &ColumnUse,
2395    levers: Levers,
2396) -> Option<AccessPath> {
2397    let table = &source.table;
2398    let forced = match &source.index_hint {
2399        crate::bind::IndexChoice::Only(wanted) => Some(wanted.as_slice()),
2400        _ => None,
2401    };
2402    let context = CandidateContext {
2403        id,
2404        position,
2405        ids,
2406        table,
2407        terms,
2408        consumed,
2409        needed,
2410        levers,
2411        forced: forced.is_some(),
2412    };
2413    let mut best: Option<(f64, AccessPath, Vec<usize>)> = None;
2414    for (at, index) in table.indexes.iter().enumerate() {
2415        // An index a module owns is not a b-tree: it has no root to seek into
2416        // and no key order to walk. `vector_path` is the only path that can use
2417        // one, and it was tried before this.
2418        if index.origin == crate::catalog_view::IndexOrigin::Module {
2419            continue;
2420        }
2421        if forced.is_some_and(|wanted| wanted != index.folded.as_slice()) {
2422            continue;
2423        }
2424        // The expressions this index needs, when the binder could bind them.
2425        // `None` for every ordinary index, and for one whose schema text did
2426        // not bind - which leaves a partial index unusable and an expression
2427        // key unmatched, both the conservative answer.
2428        let computed = source.index_exprs.iter().find(|held| held.position == at);
2429        let usable = index_usable(source, at, index, terms);
2430        if !usable && index.partial_sql.is_some() {
2431            // **A partial index only holds the rows its predicate accepts.**
2432            // Using one over a query that does not imply the predicate would
2433            // lose rows - silently, and only the rows the predicate excludes -
2434            // so the index is skipped unless the implication is *proved*.
2435            //
2436            // The proof is SQLite's own, and it is deliberately the crudest one
2437            // that is sound: the predicate appears, unchanged, as a conjunct of
2438            // the statement's `WHERE`. `WHERE b > 5 AND a = 1` therefore uses an
2439            // index declared `WHERE b > 5`, and `WHERE b > 6` does not, even
2440            // though it implies it. A cleverer test would answer more queries
2441            // and would be a place for a wrong answer to live.
2442            continue;
2443        }
2444        // Three different candidates can come from the same index: the
2445        // ordinary equality-prefix-and-range seek, a union of equality seeks
2446        // when a disjunction is an `IN` list on the leading column, and a
2447        // union of range seeks when a disjunction is a keyset page's tuple
2448        // comparison. None of them rules another out - a statement can only
2449        // ever use one of them here, but which one is cheapest is a cost
2450        // question, so every one that matches is tried and the best kept.
2451        if let Some((path, used)) = index_candidate(&context, index, computed, usable) {
2452            consider_index_candidate(source, &mut best, path, used);
2453        }
2454        if let Some((path, used)) = seek_union::in_list_union_path(&context, index, usable) {
2455            consider_index_candidate(source, &mut best, path, used);
2456        }
2457        if let Some((path, used)) = seek_union::keyset_range_union_path(&context, index, usable) {
2458            consider_index_candidate(source, &mut best, path, used);
2459        }
2460    }
2461    let (_, path, used) = best?;
2462    for index in used {
2463        if let Some(slot) = consumed.get_mut(index) {
2464            *slot = true;
2465        }
2466    }
2467    Some(path)
2468}
2469
2470/// What every index candidate for one FROM term is chosen from.
2471///
2472/// **A type rather than ten arguments (task-1962, A9).** `index_candidate`,
2473/// [`seek_union::in_list_union_path`] and [`seek_union::keyset_range_union_path`]
2474/// each took the same ten, in the same order, and two of them carried
2475/// `#[allow(clippy::too_many_arguments)]` to say so. Ten positional arguments of
2476/// which three are slices of different things is a call nobody can read and a
2477/// call site nobody can check.
2478pub(crate) struct CandidateContext<'a> {
2479    /// The FROM term being planned.
2480    pub(crate) id: usize,
2481    /// Its position in the FROM list; zero drives the pipeline.
2482    pub(crate) position: usize,
2483    /// Every FROM term's id, so a correlated reference can be recognised.
2484    pub(crate) ids: &'a [usize],
2485    /// The table the term reads.
2486    pub(crate) table: &'a TableInfo,
2487    /// The statement's `WHERE` terms, bound.
2488    pub(crate) terms: &'a [BoundExpr],
2489    /// Which of those an earlier path has already consumed.
2490    pub(crate) consumed: &'a [bool],
2491    /// What the statement reads of this term, which decides covering.
2492    pub(crate) needed: &'a ColumnUse,
2493    /// The planner's tuning knobs.
2494    pub(crate) levers: Levers,
2495    /// The term was written `INDEXED BY`, so the one index left must produce a
2496    /// path even when nothing seeks it: a walk of every entry.
2497    pub(crate) forced: bool,
2498}
2499
2500/// Folds one more index candidate into whichever is cheapest so far.
2501///
2502/// The choice between candidates is a cost, not a count of consumed terms:
2503/// two candidates that each satisfy one equality consume the same number of
2504/// terms and can differ by orders of magnitude in how many rows they return -
2505/// and taking the first one found made a query constrained on both a
2506/// two-valued column and a four-hundred-valued one search the two-valued one.
2507/// A tie goes to the later candidate, which is what the reference does - it
2508/// keeps a candidate that is no worse than the one it holds, so the last
2509/// equal one wins, which matters because a query with no `ORDER BY` returns
2510/// rows in whatever order its path produces.
2511fn consider_index_candidate(
2512    source: &BoundSource,
2513    best: &mut Option<(f64, AccessPath, Vec<usize>)>,
2514    path: AccessPath,
2515    used: Vec<usize>,
2516) {
2517    let (cost, _) = path_cost(source, &path);
2518    let better = best
2519        .as_ref()
2520        .is_none_or(|(existing, _, _)| cost <= *existing + 1e-9);
2521    if better {
2522        *best = Some((cost, path, used));
2523    }
2524}
2525
2526/// Builds the best path over one index, or `None` if it cannot be used.
2527fn index_candidate(
2528    context: &CandidateContext<'_>,
2529    index: &IndexInfo,
2530    computed: Option<&crate::dml::BoundIndexExprs>,
2531    usable: bool,
2532) -> Option<(AccessPath, Vec<usize>)> {
2533    let CandidateContext {
2534        id,
2535        position,
2536        ids,
2537        table,
2538        terms,
2539        consumed,
2540        needed,
2541        levers,
2542        forced,
2543    } = *context;
2544    let mut equalities = Vec::new();
2545    let mut unconverted = Vec::new();
2546    let mut used = Vec::new();
2547    let mut collations = Vec::new();
2548    let mut descending = Vec::new();
2549    let mut columns: Vec<Option<u16>> = Vec::new();
2550    let mut key = 0usize;
2551    while let Some(key_column) = index.columns.get(key) {
2552        let collation = collation_of(&key_column.collation);
2553        let found = match key_column.column {
2554            Some(column) => {
2555                find_equality(id, position, ids, column, collation, terms, consumed, &used)
2556                    .map(|(term_index, value)| (term_index, value, Some(column)))
2557            }
2558            // **A key the index computes.** `CREATE INDEX ix ON t(lower(a))`
2559            // answers `WHERE lower(a) = 'ab'` and nothing else: the entry holds
2560            // the expression's value, so the only predicate it can seek on is
2561            // one whose own side is that same expression. The comparison is
2562            // between *bound* expressions, which is why the binder puts them on
2563            // the FROM term - see `BoundSource::index_exprs`.
2564            None => computed
2565                .and_then(|held| held.keys.get(key).cloned().flatten())
2566                .and_then(|wanted| {
2567                    find_expr_equality(position, ids, &wanted, collation, terms, consumed, &used)
2568                })
2569                .map(|(term_index, value)| (term_index, value, None)),
2570        };
2571        let Some((term_index, value, column)) = found else {
2572            break;
2573        };
2574        if terms.get(term_index).is_some_and(compares_unconverted) {
2575            unconverted.push(equalities.len());
2576        }
2577        equalities.push(value);
2578        used.push(term_index);
2579        collations.push(collation);
2580        descending.push(key_column.descending);
2581        columns.push(column);
2582        key = key.saturating_add(1);
2583    }
2584    // **A range is an outermost-term path only**, the rule `rowid_path` and
2585    // `seek_union` already follow and this candidate did not. The physical
2586    // pass refuses an inner index seek with a bound, so
2587    // `SELECT count(*) FROM s CROSS JOIN h WHERE h.b > 595` was refused with
2588    // exit code 3 on the release build, with no hint anywhere (task-2078).
2589    // Left unconsumed, the bound is a residual over the pair, which answers.
2590    let range = match index.columns.get(key) {
2591        Some(key_column) if position == 0 => range::key_range(context, key_column, &mut used),
2592        _ => None,
2593    };
2594    let (low, high) = match range {
2595        Some(found) => {
2596            collations.push(found.collation);
2597            descending.push(found.descending);
2598            columns.push(Some(found.column));
2599            (found.low, found.high)
2600        }
2601        None => (None, None),
2602    };
2603    let covering = levers
2604        .has(Levers::COVERING_INDEX)
2605        .then(|| covering_slots(table, index, needed, usable))
2606        .flatten();
2607    // **A partial index whose predicate the query implies is worth walking whole.**
2608    // It holds only the rows its predicate accepted, so reading
2609    // every entry of it reads exactly the rows the query asked for - even with
2610    // nothing to seek to and even when a lookup per entry is needed, which is
2611    // the case a covering test cannot see.
2612    //
2613    // `CREATE INDEX document_pending_idx ON document (indexed_at) WHERE
2614    // indexed_at IS NULL` over `SELECT id FROM document WHERE indexed_at IS
2615    // NULL` is the shape: 120 of 6,000 documents, and `id` is not in the index,
2616    // so the covering test said no and the whole candidate was dropped. The
2617    // plan was `SCAN document`, over a table whose rows carry nine kilobytes of
2618    // body each, and the equivalent query on the real corpus was thousands of
2619    // times slower than the same question asked of PostgreSQL.
2620    //
2621    // It is offered rather than taken: `path_cost` compares it against the scan
2622    // with the index's own entry count, which `ANALYZE` now writes for a partial
2623    // index instead of the table's.
2624    let partial_walk = usable && index.partial_sql.is_some();
2625    if equalities.is_empty()
2626        && low.is_none()
2627        && high.is_none()
2628        && covering.is_none()
2629        && !partial_walk
2630        && !(forced && usable)
2631    {
2632        // Nothing to seek to and nothing to save by reading the entries: this
2633        // index has no part in answering the query.
2634        return None;
2635    }
2636    Some((
2637        AccessPath::IndexSeek {
2638            table_root: table.root,
2639            index_root: index.root,
2640            index_name: index.name.clone(),
2641            equalities,
2642            unconverted,
2643            low,
2644            high,
2645            collations,
2646            descending,
2647            columns,
2648            without_rowid: table.without_rowid,
2649            key_entry_slots: if table.without_rowid && index.root != table.root {
2650                let leading = index.columns.len();
2651                (0..table.primary_key().len())
2652                    .map(|offset| leading.saturating_add(offset))
2653                    .collect()
2654            } else {
2655                Vec::new()
2656            },
2657            covering,
2658        },
2659        used,
2660    ))
2661}
2662
2663/// The entry slot that stands for the row's own key rather than a field.
2664///
2665/// An index entry over a rowid table ends with the rowid, and the machine reads
2666/// it with `IdxRowid` rather than out of the entry's record - so a column that
2667/// *is* the rowid needs a marker rather than a slot number. It is the largest
2668/// `usize` because no entry can have that many fields, and because a number
2669/// that could also be a real slot would be a silent misread.
2670pub const ROWID_ENTRY_SLOT: usize = usize::MAX;
2671
2672/// Returns where each column the query reads sits in one index's entries.
2673///
2674/// `None` when the index does not hold them all, which is the ordinary case and
2675/// is why a covering path is worth naming when it happens. A `WITHOUT ROWID`
2676/// table is excluded: its rows *are* index entries, so the question is already
2677/// answered by whether the seek is on the table's own key, and mixing the two
2678/// would be two answers to one question.
2679/// @param table - the table being read
2680/// @param index - the index being considered
2681/// @param needed - what the query reads from this term
2682fn covering_slots(
2683    table: &TableInfo,
2684    index: &IndexInfo,
2685    needed: &ColumnUse,
2686    usable: bool,
2687) -> Option<Vec<(u16, usize)>> {
2688    if needed.opaque || table.without_rowid || !usable {
2689        return None;
2690    }
2691    let mut slots = Vec::with_capacity(needed.columns.len());
2692    for slot in &needed.columns {
2693        // The rowid alias is a column of the table and the *rowid* of the
2694        // entry, so it is covered whatever the index holds - but it is read
2695        // with `IdxRowid` rather than out of the entry's record, so it is not
2696        // in the list.
2697        if table.rowid_alias == Some(*slot) {
2698            slots.push((*slot, ROWID_ENTRY_SLOT));
2699            continue;
2700        }
2701        let position = index
2702            .columns
2703            .iter()
2704            .position(|key| key.column == Some(*slot))?;
2705        slots.push((*slot, position));
2706    }
2707    Some(slots)
2708}
2709
2710/// Finds an equality predicate on one column with a matching collation.
2711fn find_equality(
2712    id: usize,
2713    position: usize,
2714    ids: &[usize],
2715    column: u16,
2716    collation: Collation,
2717    terms: &[BoundExpr],
2718    consumed: &[bool],
2719    used: &[usize],
2720) -> Option<(usize, BoundExpr)> {
2721    for (index, term) in terms.iter().enumerate() {
2722        if consumed.get(index).copied().unwrap_or(false) || used.contains(&index) {
2723            continue;
2724        }
2725        let Some((op, value)) = indexable_comparison(id, column, term) else {
2726            continue;
2727        };
2728        if op != BinaryOp::Equal || !is_available(position, ids, &value) {
2729            continue;
2730        }
2731        if comparison_collation(term) != collation {
2732            continue;
2733        }
2734        return Some((index, value));
2735    }
2736    None
2737}
2738
2739/// Finds an equality against an expression the index computes.
2740///
2741/// The mirror of [`find_equality`] for a key that is not a column: the term has
2742/// to compare the index's own key expression against something the join has
2743/// already produced, under the collation the key is ordered by.
2744///
2745/// @param position - the FROM term's position among the ones already joined
2746/// @param ids - the FROM terms joined so far
2747/// @param wanted - the index's bound key expression
2748/// @param collation - the collation the key is ordered under
2749/// @param terms - the statement's `WHERE` conjuncts
2750/// @param consumed - which terms an earlier stage already used
2751/// @param used - which terms this candidate has already used
2752fn find_expr_equality(
2753    position: usize,
2754    ids: &[usize],
2755    wanted: &BoundExpr,
2756    collation: Collation,
2757    terms: &[BoundExpr],
2758    consumed: &[bool],
2759    used: &[usize],
2760) -> Option<(usize, BoundExpr)> {
2761    for (index, term) in terms.iter().enumerate() {
2762        if consumed.get(index).copied().unwrap_or(false) || used.contains(&index) {
2763            continue;
2764        }
2765        let BoundExpr::Compare {
2766            op, left, right, ..
2767        } = term
2768        else {
2769            continue;
2770        };
2771        if *op != BinaryOp::Equal || comparison_collation(term) != collation {
2772            continue;
2773        }
2774        let value = if left.as_ref() == wanted {
2775            right.as_ref().clone()
2776        } else if right.as_ref() == wanted {
2777            left.as_ref().clone()
2778        } else {
2779            continue;
2780        };
2781        if !is_available(position, ids, &value) {
2782            continue;
2783        }
2784        return Some((index, value));
2785    }
2786    None
2787}
2788
2789/// Returns whether a value can be computed before entering a loop level.
2790///
2791/// A seek key may only read terms *outside* the loop it drives. Reading the
2792/// term's own columns would be circular, and reading an inner term's columns
2793/// would read a cursor that has not been positioned yet.
2794fn is_available(position: usize, ids: &[usize], value: &BoundExpr) -> bool {
2795    let mut used = Vec::new();
2796    value.sources_used(&mut used);
2797    used.iter().all(|source| {
2798        // A term this block does not own belongs to an enclosing one, whose
2799        // cursor is positioned before this block runs at all - so it is
2800        // available at every level, including the first.
2801        ids.iter()
2802            .position(|id| id == source)
2803            .is_none_or(|level| level < position)
2804    })
2805}
2806
2807#[cfg(test)]
2808mod tests {
2809    use super::*;
2810    use crate::bind::BoundExpr;
2811
2812    /// A conjunction splits into its terms; a disjunction does not, because a
2813    /// term of an OR is not true of every row the OR accepts.
2814    #[test]
2815    fn only_conjunctions_split() {
2816        let expr = BoundExpr::And(
2817            Box::new(BoundExpr::Integer(1)),
2818            Box::new(BoundExpr::Or(
2819                Box::new(BoundExpr::Integer(2)),
2820                Box::new(BoundExpr::Integer(3)),
2821            )),
2822        );
2823        let mut terms = Vec::new();
2824        split_conjunction(&expr, &mut terms);
2825        assert_eq!(terms.len(), 2);
2826        assert!(matches!(terms.get(1), Some(BoundExpr::Or(_, _))));
2827    }
2828
2829    /// A seek key may read only terms outside its own loop.
2830    #[test]
2831    fn a_seek_key_may_only_read_outer_terms() {
2832        let outer = BoundExpr::Column {
2833            source: 0,
2834            column: 0,
2835            slot: 0,
2836            affinity: inillucent_value::Affinity::Integer,
2837            collation: Collation::Binary,
2838        };
2839        let ids = [0usize, 1usize];
2840        assert!(is_available(1, &ids, &outer));
2841        assert!(!is_available(0, &ids, &outer));
2842        assert!(is_available(0, &ids, &BoundExpr::Integer(5)));
2843        // A term the block does not own belongs to an enclosing block, whose
2844        // cursor is already positioned, so it is available at every level.
2845        assert!(is_available(0, &[7usize], &outer));
2846    }
2847}