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