1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Index-only scan decision helper — Fase 5 P2.
//!
//! Given a query's projection list and an available index with
//! a covering set of columns, plus the table's visibility map,
//! decide when it's safe to return rows directly from the
//! index without fetching the heap.
//!
//! Index-only scan is safe when ALL of:
//!
//! 1. Every projected column is present in the index (the
//! index "covers" the projection).
//! 2. Every filter column is present in the index (so the
//! scan can evaluate the WHERE clause without a heap
//! fetch for filtering).
//! 3. The target page is marked all-visible in the
//! visibility map — otherwise the row may have been
//! updated / deleted since the last vacuum and the
//! index entry could point at a dead tuple.
//!
//! When (1) and (2) hold but (3) doesn't for some pages,
//! the scan can still use the index-only path for the
//! all-visible pages and fall back to heap fetch for the
//! remainder. PG calls this "partial index-only". We ship
//! the decision primitive today; the execution-layer
//! fallback logic lands in the wiring commit.
//!
//! This module is **not yet wired** into the planner. Wiring
//! plugs into `planner/logical.rs::choose_scan_strategy` when
//! that helper grows an "index-only candidate" check.
use HashSet;
/// Decision outcome for a potential index-only scan.
/// Information about the index that may be used for an
/// index-only scan. Simpler than the full `IndexStats` /
/// `IndexKind` types so this helper doesn't pull the whole
/// planner dep graph into its scope.
/// Decide whether an index-only scan is viable for the given
/// query shape. Inputs are intentionally minimal — the
/// planner supplies only what this helper needs so it can be
/// exercised from unit tests without the full optimizer
/// context.
///
/// - `projected` — column names in the SELECT list.
/// - `filter_cols` — column names referenced by the WHERE clause.
/// - `index` — the candidate index.
/// - `all_visible_pages` — fraction in [0.0, 1.0] of target
/// pages that the visibility map reports as all-visible.
/// The planner computes this via `VisibilityMap::all_visible_count
/// / total_pages`.
/// Estimate the speedup factor an index-only scan would give
/// over a regular index scan with heap fetches. Used by the
/// cost model to pick between the two. The formula is simple:
/// 1x when `FullCover`, scaling down with visibility coverage
/// for `PartialCover`, and 1.0 (no speedup) for `NotCovering`.
///
/// PG's actual formula weights the page-hit cost against the
/// buffer-pool hit ratio; we approximate with the visibility
/// fraction since `all_visible ≈ cache_friendly` in practice.