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