pub struct Index {
pub name: String,
pub column_position: usize,
pub kind: IndexKind,
pub included_columns: Vec<usize>,
pub partial_predicate: Option<String>,
pub expression: Option<String>,
pub nulls_not_distinct: bool,
pub descending: bool,
pub nulls_first: Option<bool>,
pub collation: Option<String>,
pub is_unique: bool,
pub extra_column_positions: Vec<usize>,
}Expand description
A single-column secondary index. v2.0 carries either a B-tree map (the default — used for equality / range lookups on scalar columns) or a navigable-small-world graph (used for kNN over vector columns).
Fields§
§name: String§column_position: usize§kind: IndexKind§included_columns: Vec<usize>v6.8.0 — column positions of INCLUDE (col1, col2, …)
non-key columns. Carries the planner’s “this query is
covered by the index” signal; lookup paths still resolve
via the RowLocator to fetch the row body, but EXPLAIN
surfaces the covered-scan annotation so operators can
confirm the planner sees the coverage.
Empty Vec = no INCLUDE clause (the legacy shape). v12
catalog snapshots deserialise with an empty vec.
partial_predicate: Option<String>v6.8.1 — partial-index predicate stored as its canonical
Display form (the engine re-parses it on the maintenance
path). None = unconditional index (the legacy shape).
Persisted as [u8 has_pred][u16 LE len][bytes] on the
catalog snapshot (FILE_VERSION 12, appended after
included_columns).
expression: Option<String>v6.8.2 — expression-index key, stored as the expression’s
canonical Display form. None = bare column-reference
index (the legacy shape). Persisted alongside
partial_predicate on the v12 catalog snapshot.
nulls_not_distinct: boolv7.39 (read01 round 52) — CREATE UNIQUE INDEX … NULLS NOT DISTINCT
(PG 15+): a NULL in the key no longer exempts the row, so two
all-NULL keys collide. Default false = SQL-standard NULLS DISTINCT.
Persisted in the index appendix (FILE_VERSION 62+); older catalogs
deserialise with false.
descending: boolv7.39 (round 537) — the key column’s ordering clause, as written.
SPG’s index does not scan in a direction, so this changes no
lookup; pg_indexes.indexdef is a reproduction of the DDL and
dropping the clause made CREATE INDEX i ON t (a DESC NULLS LAST) read back as (a) — a dump lost it and a schema diff saw
drift every run. nulls_first is None when the statement did
not say, in which case PG’s default applies and neither word is
rendered.
nulls_first: Option<bool>§collation: Option<String>v7.39 (round 538) — an explicit COLLATE on the key, as written.
SPG orders text by bytes, so it changes no comparison; PG prints
it because a named collation and an inherited one are different
objects even where they sort identically.
is_unique: boolv7.9.29 — CREATE UNIQUE INDEX …. When true the engine
rejects INSERTs whose key already appears in this index
(combined with partial_predicate when present — only
rows matching the predicate enter the uniqueness check).
Catalog FILE_VERSION 16+; older snapshots deserialise
with false. mailrs K1.
extra_column_positions: Vec<usize>v7.9.29 — extra (non-leading) column positions for
multi-column indexes (CREATE INDEX … (a, b, c)). The
planner today still only uses the leading
column_position for index seeks, but UNIQUE INDEX
enforcement walks the full tuple so partial-unique
invariants like CalDAV (calendar_id, uid, recurrence_id) are enforced correctly. Catalog
FILE_VERSION 16+; older snapshots deserialise empty.
Implementations§
Source§impl Index
impl Index
Sourcepub fn iter_desc(
&self,
) -> Box<dyn Iterator<Item = (&IndexKey, &Vec<RowLocator>)> + '_>
pub fn iter_desc( &self, ) -> Box<dyn Iterator<Item = (&IndexKey, &Vec<RowLocator>)> + '_>
v7.34.4 — descending-order iterator over (IndexKey, locators)
pairs for a BTree index, with O(log N) descent to the rightmost
leaf and lazy emission thereafter. Returns an empty iterator
for non-BTree index kinds — callers handle both uniformly.
Used by the ORDER BY <indexed col> DESC + LIMIT N executor
path: walking only the first N matches off the rightmost leaf
avoids the per-row materialisation + partial-sort cost on
large tables (mailrs content_worker at 250 k rows).
Sourcepub fn iter_asc(
&self,
) -> Box<dyn Iterator<Item = (&IndexKey, &Vec<RowLocator>)> + '_>
pub fn iter_asc( &self, ) -> Box<dyn Iterator<Item = (&IndexKey, &Vec<RowLocator>)> + '_>
v7.34.4 — ascending-order iterator over (IndexKey, locators)
pairs. Mirror of iter_desc for ORDER BY … ASC + LIMIT N.
Sourcepub fn lookup_eq(&self, key: &IndexKey) -> &[RowLocator]
pub fn lookup_eq(&self, key: &IndexKey) -> &[RowLocator]
Look up the locators stored under key (B-tree only). Returns
an empty slice when the key is absent or the index isn’t a
BTree — callers can treat both cases uniformly.
v5.1: return type widened from &[usize] to &[RowLocator].
Pre-v5.2 callers can read the slice and .as_hot().unwrap()
each entry (no Cold variants exist until the freezer lands);
post-v5.2 callers dispatch hot vs. cold per locator.
Sourcepub fn lookup_eq_i64(&self, n: i64) -> &[RowLocator]
pub fn lookup_eq_i64(&self, n: i64) -> &[RowLocator]
v7.37.43 (INSUBQ B-2) — specialised lookup for integer-PK probes.
try_count_star_pk_in_subquery_fast already holds an i64 (the
inner survivor key); skip the IndexKey::from_value enum-dispatch
trip and build the key inline. ~20 ns × N_survivors saved on
the INSUBQ hot loop.
Sourcepub fn lookup_range_capped(
&self,
lo: Bound<&IndexKey>,
hi: Bound<&IndexKey>,
cap: usize,
) -> Option<Vec<RowLocator>>
pub fn lookup_range_capped( &self, lo: Bound<&IndexKey>, hi: Bound<&IndexKey>, cap: usize, ) -> Option<Vec<RowLocator>>
v7.38 (perf, index range scan) — flatten the row locators for every key
in [lo, hi] (bounds per core::ops::Bound) via the BTree’s O(log N + k) range walk. Returns None once more than cap locators accumulate
— a “this range isn’t selective enough, seq-scan instead” signal that
stops a wide range from materialising a near-full table’s worth of rows
through the index. BTree only (other kinds → None).
Sourcepub fn lookup_range_capped_by(
&self,
lo: Bound<&IndexKey>,
hi: Bound<&IndexKey>,
cap: usize,
keep: impl Fn(RowLocator) -> bool,
) -> Option<Vec<RowLocator>>
pub fn lookup_range_capped_by( &self, lo: Bound<&IndexKey>, hi: Bound<&IndexKey>, cap: usize, keep: impl Fn(RowLocator) -> bool, ) -> Option<Vec<RowLocator>>
v7.39 (round 490) — the same range walk, but the caller decides which locators are worth carrying, and the cap counts only those.
A BTree index holds one locator per row VERSION. On a churned table
the dead versions are still in there: round 490 measured a
1000-row range handing back 61 000 locators after 60
delete-and-reinsert cycles with the background vacuum switched off.
Every caller then dropped the dead ones — the mutation paths and the
SELECT range path all test is_row_visible and continue — but only
after they had been collected into a Vec, sorted, and walked.
Handing the predicate down means the walk keeps ~1000, and the cap (which exists so an index walk never costs more than the scan it replaces) is once again measured in rows a caller will actually look at. Round 461 had to add the dead count to the budget to stop the seek being refused outright; with the filter here that compensation is no longer needed.
Sourcepub fn range_keyed(
&self,
lo: Bound<&IndexKey>,
hi: Bound<&IndexKey>,
) -> Option<impl Iterator<Item = (&IndexKey, RowLocator)> + '_>
pub fn range_keyed( &self, lo: Bound<&IndexKey>, hi: Bound<&IndexKey>, ) -> Option<impl Iterator<Item = (&IndexKey, RowLocator)> + '_>
v7.39 (round 560) — the index range as (key, locator) pairs.
lookup_range_capped_by throws the KEY away and returns only
locators, so a query whose projection is exactly the indexed
column still goes to the row store for a value the walk already
had in hand — paying per row for something the index knows.
Uncapped on purpose: an index-only walk touches no row, so the selectivity ceiling that keeps a seek from being worse than the scan it replaces does not apply to it.
v7.39 (round 562) — and it does not collect, either. This
returned a Vec<(IndexKey, RowLocator)>: for a 100k-row range,
100k key clones into a Vec::new() that doubles its way up to
several MB, all to be walked once and dropped. A profile of the
server serving that query put 20% of the connection thread’s CPU
on the collect alone, with another 18% in the allocator beside
it. The caller consumes the pairs in order and needs the key
only by reference, so it can have the walk itself.
Sourcepub fn gin_lookup_word(&self, word: &str) -> &[RowLocator]
pub fn gin_lookup_word(&self, word: &str) -> &[RowLocator]
v7.12.3 — GIN posting-list lookup. Returns the row locators
whose tsvector cell contains word. Empty when the word is
absent from the index or this isn’t a GIN index.
Sourcepub fn gin_trgm_lookup(&self, tri: &str) -> &[RowLocator]
pub fn gin_trgm_lookup(&self, tri: &str) -> &[RowLocator]
v7.15.0 — trigram-GIN posting-list lookup. Returns the row
locators whose indexed TEXT cell contains the trigram
tri. Empty when the trigram is absent or this isn’t a
trigram-GIN index.
Sourcepub fn gin_jsonb_lookup(&self, token: &str) -> &[RowLocator]
pub fn gin_jsonb_lookup(&self, token: &str) -> &[RowLocator]
v7.37.8(sentori Epic 5 P2)— JSONB-GIN posting-list lookup.
Returns the row locators whose indexed JSONB cell carries
the canonical token(see crate::jsonb_gin::extract_tokens).
Empty when the token is absent or this isn’t a JSONB-GIN
index. Planners drive <col> @> <jsonb_literal> through here.
Sourcepub const fn nsw(&self) -> Option<&NswGraph>
pub const fn nsw(&self) -> Option<&NswGraph>
Borrow the NSW graph (if this is an NSW index). Callers that need the graph for a kNN search go through here.
Sourcepub const fn is_brin(&self) -> bool
pub const fn is_brin(&self) -> bool
v6.7.1 — true when this index is a BRIN (block range) index. Used by the segment encoder to opt into BRIN sidecar emission at freeze time, and by the planner to opt into page-skipping on range predicates.
Sourcepub const fn is_gin_trgm(&self) -> bool
pub const fn is_gin_trgm(&self) -> bool
v7.15.0 — true when this index is a trigram GIN
(gin_trgm_ops-flavoured). Used by the LIKE planner to
opt into trigram acceleration.
Sourcepub const fn is_gin(&self) -> bool
pub const fn is_gin(&self) -> bool
v7.12.3 — true when this index is a GIN inverted index.
Used by the planner to opt into posting-list acceleration on
WHERE col @@ tsquery predicates.
Sourcepub const fn is_gin_fulltext(&self) -> bool
pub const fn is_gin_fulltext(&self) -> bool
v7.17.0 Phase 2.2 — true when this index is a fulltext
GIN over a TEXT / VARCHAR column (MySQL FULLTEXT KEY
surface). Used by the planner to opt the FULLTEXT-indexed
column into MATCH AGAINST acceleration.
Sourcepub const fn is_gin_jsonb(&self) -> bool
pub const fn is_gin_jsonb(&self) -> bool
v7.37.8(sentori Epic 5 P2)— true when this index is a
real JSONB-GIN(posting-list backed). Used by the planner
to opt <col> @> <jsonb_literal> into posting-list seek.