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