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}