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