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