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