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