pub struct Table { /* private fields */ }Implementations§
Source§impl Table
impl Table
pub fn new(schema: TableSchema) -> Self
Sourcepub fn rowids(&self) -> &PersistentVec<RowId>
pub fn rowids(&self) -> &PersistentVec<RowId>
v7.37.15 (Phase C.1) — read-only access to the stable row ids
parallel to rows(). rowids().len() == rows().len() is the
load-bearing lock-step invariant (asserted in debug builds at
every mutation boundary alongside headers).
Sourcepub fn rel_id(&self) -> RelId
pub fn rel_id(&self) -> RelId
v7.37.15 (Phase C.1) — this relation’s stable identity.
RelId::UNASSIGNED for
a bare Table::new; a real id once the catalog stamps it.
Sourcepub fn assign_dense_rowids(&mut self)
pub fn assign_dense_rowids(&mut self)
v7.37.15 (Phase C.1) — rebuild the rowids vec so it is dense
1..=rows.len() and reset the allocator above it. Used on the
load / snapshot-restore path where rows arrive without ids
(pre-V6 envelope): every row gets a fresh id, sufficient while
ids are process-local bookkeeping. Keeps the lock-step
invariant against the freshly-loaded rows.
Sourcepub fn dead_rows(&self) -> u64
pub fn dead_rows(&self) -> u64
v7.37.16 (autovacuum) — number of tombstoned-but-present hot rows. Incrementally maintained; drives the engine’s autovacuum threshold.
Sourcepub fn bump_write_stats(&mut self, ins: u64, upd: u64, del: u64)
pub fn bump_write_stats(&mut self, ins: u64, upd: u64, del: u64)
v7.39 (pg_stat knife A) — bump the volatile write counters the engine’s DML dispatcher reports per statement.
Sourcepub fn write_stats(&self) -> (u64, u64, u64)
pub fn write_stats(&self) -> (u64, u64, u64)
(n_tup_ins, n_tup_upd, n_tup_del) for pg_stat_user_tables.
Sourcepub fn maintenance_stamps(&self) -> (Option<i64>, Option<i64>)
pub fn maintenance_stamps(&self) -> (Option<i64>, Option<i64>)
v7.39 (pg_stat knife C) — maintenance stamps for
pg_stat_user_tables ((last_autovacuum_us, last_analyze_us)).
pub fn stamp_autovacuum(&mut self, unix_us: i64)
pub fn stamp_analyze(&mut self, unix_us: i64)
Sourcepub fn scan_stats(&self) -> &ScanStats
pub fn scan_stats(&self) -> &ScanStats
v7.39 (pg_stat knife B) — the scan counters (read side of pg_stat_user_tables).
Sourcepub fn note_seq_scan(&self)
pub fn note_seq_scan(&self)
v7.39 (pg_stat knife B) — one sequential scan over the visible
rows, reported by engine scan loops that walk headers directly
(parallel shards, the aggregate full scan) instead of
scan_visible.
Sourcepub fn note_index_scan(&self, fetched: u64)
pub fn note_index_scan(&self, fetched: u64)
v7.39 (pg_stat knife B) — one index scan returning fetched
rows (the engine’s index-seek paths report here).
Sourcepub fn headers(&self) -> &PersistentVec<RowHeader>
pub fn headers(&self) -> &PersistentVec<RowHeader>
v7.37.15 (Phase A.2) — read-only access to the per-row
MVCC visibility headers. headers().len() == rows().len()
is the load-bearing invariant; Phase B scan paths consult
headers()[idx] to decide visibility.
Sourcepub fn insert_with_xmin(
&mut self,
row: Row<'static>,
xmin: u64,
) -> Result<(), StorageError>
pub fn insert_with_xmin( &mut self, row: Row<'static>, xmin: u64, ) -> Result<(), StorageError>
v7.37.15 (Phase C) — engine writer path. Same as [insert]
but stamps xmin on the new row’s header with the writing
transaction’s id (caller-supplied; obtained from the engine’s
monotonic version counter). The fresh insert is alive
(xmax = XMAX_ALIVE); a later UPDATE / DELETE will set
xmax to a later version, leaving the row physically
present until vacuum reclaims it (Phase D).
Callers in crate::row_header::next_version order:
- allocate version V via
next_version() - call
insert_with_xmin(row, V) - update any indexes (as
insertdoes)
xmin = XMIN_FROZEN short-circuits to plain insert
behaviour so the legacy in-memory / WAL-replay paths keep
returning identical results when they end up here.
Sourcepub fn set_prune_horizon(&mut self, horizon: u64)
pub fn set_prune_horizon(&mut self, horizon: u64)
v7.39 (round 493) — publish the snapshot floor the insert path may
prune dead index entries under. See prune_horizon.
The engine sets this from vacuum_oldest_active() before a
statement’s inserts. 0 disables pruning, which is the default and
is always safe.
Sourcepub fn vacuum(
&mut self,
oldest_active_snapshot: u64,
dry_run: bool,
) -> VacuumReport
pub fn vacuum( &mut self, oldest_active_snapshot: u64, dry_run: bool, ) -> VacuumReport
v7.37.15 (Phase D) — single-table vacuum pass. Walks the
header vec and physically removes any row whose delete
commit is older than oldest_active_snapshot. Returns the
number of reclaimable rows (with dry_run == true) or the
number actually reclaimed.
oldest_active_snapshot is the floor of every live
snapshot’s version — the engine maintains this; hosts
pass it through.
Phase D ships the storage primitive. Hosts (spg-embedded / spg-server) schedule the pass on their own thread.
Sourcepub fn mark_row_deleted(
&mut self,
position: usize,
xmax: u64,
) -> Result<(), StorageError>
pub fn mark_row_deleted( &mut self, position: usize, xmax: u64, ) -> Result<(), StorageError>
v7.37.15 (Phase C) — mark the row at position as deleted
by version xmax. The row stays physically present; later
vacuum (Phase D) reclaims it once no live snapshot can
still see it.
Returns Err(Corrupt) on out-of-bounds and silently no-ops
when the row is already tombstoned (a later DELETE on an
already-deleted row should not change xmax — the original
deletion wins).
Sourcepub fn mark_rows_deleted(&mut self, positions: &[usize], xmax: u64) -> usize
pub fn mark_rows_deleted(&mut self, positions: &[usize], xmax: u64) -> usize
v7.37.16 — batch form of Table::mark_row_deleted: stamp xmax
on every alive, in-bounds position and record ONE
RowChange::Tombstone carrying all affected RowIds (the codec
and replay already handle multi-rowid records). The per-row form
paid one redo record — a Vec alloc plus a log push — PER ROW,
~800 ns/row on a 10k-row gate-on DELETE (heavy_write del_10k).
Semantics match the single-row form: already-tombstoned keeps its
original xmax (first-deleter-wins), out-of-bounds is skipped.
Returns the number of rows NEWLY tombstoned.
Sourcepub fn extract_tx_writeset(&self, v: u64) -> TxWriteSet
pub fn extract_tx_writeset(&self, v: u64) -> TxWriteSet
v7.37.17 (Phase E RC rebase) — extract the write-set one writer
version left on this table, expressed against stable [RowId]s
so it can be replayed onto a FRESHER catalog clone whose
physical slots differ. inserted carries INSERT rows and the
new versions of UPDATEs (xmin == v); tombstoned carries the
ids DELETE / UPDATE-old-version stamped (xmax == v). A row
both inserted and tombstoned by the same version appears in
both lists; replay applies inserts first, tombstones second —
net effect identical.
Sourcepub fn tombstone_conflicts(&self, rids: &[RowId], v: u64) -> Vec<RowId>
pub fn tombstone_conflicts(&self, rids: &[RowId], v: u64) -> Vec<RowId>
v7.37.17 (Phase E4 fix) — read-only conflict probe for a
write-set’s tombstones against THIS (fresher) relation: a target
RowId that is gone, or already tombstoned by a DIFFERENT
version, is a write-write conflict. Callers use this BEFORE
replay_tx_writeset so a conflicting UPDATE can drop its
paired insert too (atomicity of tombstone+insert pairs).
Sourcepub fn replay_tx_writeset(&mut self, ws: &TxWriteSet, v: u64) -> Vec<RowId>
pub fn replay_tx_writeset(&mut self, ws: &TxWriteSet, v: u64) -> Vec<RowId>
v7.37.17 (Phase E RC rebase) — replay a write-set extracted from an OLDER clone of this relation onto this (fresher) one, keeping the original RowIds. Deliberately does NOT capture redo: a replay re-expresses writes the transaction already made, it is not a new mutation (the redo story rides the eventual COMMIT). Returns the ids whose tombstone could not be applied because the row is gone or already tombstoned by a DIFFERENT version — the write-write conflict surface (RC skips them per PG semantics; RR/SER turn them into serialization_failure — Phase E3).
Sourcepub fn enable_redo(&mut self)
pub fn enable_redo(&mut self)
v7.34 (crash-recovery P0 #2) — start capturing row-level redo into this table (engine call before a mutating statement when persistence is on). Idempotent; existing captured changes are kept.
Sourcepub fn take_redo(&mut self) -> Vec<RowChange>
pub fn take_redo(&mut self) -> Vec<RowChange>
v7.34 — drain the captured redo changes and stop capturing.
Returns the physical RowChanges applied since enable_redo,
in apply order (empty when capture was off or nothing changed).
Sourcepub const fn hot_bytes(&self) -> u64
pub const fn hot_bytes(&self) -> u64
Total encoded byte size of every row currently in the hot tier
(self.rows). See struct docs for the maintenance contract.
Returns 0 for an empty table.
Sourcepub const fn cold_row_count(&self) -> u64
pub const fn cold_row_count(&self) -> u64
v6.7.0 — cached count of cold-tier rows. See struct field docs for the staleness contract.
Sourcepub fn set_cold_row_count(&mut self, n: u64)
pub fn set_cold_row_count(&mut self, n: u64)
v6.7.0 — overwrite the cached count. Called by the engine’s
analyze_one_table after walking the indices.
Sourcepub fn mark_cold_row_count_stale(&mut self)
pub fn mark_cold_row_count_stale(&mut self)
v6.7.0 — mark the cached count as potentially out of date.
Called by freezer / promote / DELETE paths so a subsequent
spg_statistic read knows the number may not reflect the
current state.
Sourcepub const fn cold_row_count_stale(&self) -> bool
pub const fn cold_row_count_stale(&self) -> bool
v6.7.0 — report whether the cached count is known to be out of date. Exposed for completeness; the virtual table surface returns the cached value regardless.
Sourcepub const fn has_cold_rows_fast(&self) -> bool
pub const fn has_cold_rows_fast(&self) -> bool
v7.36 — O(1) “could this table possibly have cold rows?”
predicate, intended for perf-critical executor hot paths
that just need to skip the cold-tier branch when there’s
definitely nothing there. Reads the cached cold_row_count:
- cache fresh + cache == 0 → return false (fast path)
- cache stale → return true (conservative; the executor
pays the cold-aware path’s
iter_cold_rows_*cost but stays correct) - cache fresh + cache > 0 → return true
count_cold_locatorsremains the right call for the EXACT count (ANALYZE etc.) — its O(N) walk is unsuitable per join stage.
Sourcepub fn cold_capable_indices(&self) -> impl Iterator<Item = &Index>
pub fn cold_capable_indices(&self) -> impl Iterator<Item = &Index>
r944 — every BTree index a cold row could have been filed under.
The freeze writes a row’s locator into exactly ONE index
(register_cold_locators takes a single index name) and the
freezer picks that index by its own rule, so a reader that guesses
a different one finds nothing. Round 943 is that bug: the freezer
chose the first BTree index over any integer column, the scan
looked at the first index on the primary key’s column, and 15
frozen rows of 40 vanished from a plain SELECT.
Union over all of them rather than guessing one. Because each row’s locator exists in exactly one index, the union yields every row once and needs no visited-set.
Deliberately NOT filtered to declared-unique indices. Freezing
through an index whose keys repeat is a real limitation —
resolve_cold_locator resolves BY KEY and cannot say which of two
rows sharing one was meant — but that limit belongs to the freeze,
which builds the segment keyed that way. Filtering it here only
hides rows that were frozen anyway, which is the bug rather than a
guard against it; the freezer’s own tests freeze tables whose
integer index carries no uniqueness constraint.
Sourcepub fn count_cold_locators(&self) -> u64
pub fn count_cold_locators(&self) -> u64
v6.7.0 — walk every BTree index and count RowLocator::Cold
entries; return the MAX across indices. The freeze path
(freeze_oldest_to_cold) writes cold locators to ONE
designated index — that index ends up with the full per-row
count. MAX-across-indices yields the precise count when a
PK-style index exists; for multi-index tables without a
covering index it’s a lower bound (rare in practice).
Caller responsibility: only invoke under engine.write()
or after taking ownership; the walk is O(N) over every
(key, locator) pair.
pub const fn schema(&self) -> &TableSchema
Sourcepub const fn schema_mut(&mut self) -> &mut TableSchema
pub const fn schema_mut(&mut self) -> &mut TableSchema
v6.7.2 — mutable schema accessor for ALTER TABLE paths.
Used by Engine::exec_alter_table to flip per-table
settings like hot_tier_bytes.
Sourcepub const fn rows(&self) -> &PersistentVec<Row<'static>>
pub const fn rows(&self) -> &PersistentVec<Row<'static>>
v4.39: returns the persistent row vector by reference. Callers that
used to take &[Row] should switch to .iter() (via
IntoIterator for &PersistentVec) or .get(i) for indexing.
pub const fn row_count(&self) -> usize
Sourcepub fn is_row_visible(&self, idx: usize, snapshot: &Snapshot) -> bool
pub fn is_row_visible(&self, idx: usize, snapshot: &Snapshot) -> bool
v7.37.15 (Phase B) — answer “is row at idx visible under
snapshot?” without exposing the header internals to the
engine. Callers in scan paths consult this BEFORE yielding
the row.
Defensive: out-of-bounds idx and the (impossible, asserted)
length mismatch return false, mirroring “row is not there
so it’s not visible.” Production scans never see either.
Phase A always returns true because every header is
RowHeader::frozen() and Snapshot::unbounded() accepts
every header. The full visibility behaviour engages once
Phase C writers start stamping real xmin/xmax.
Sourcepub fn is_all_visible(&self) -> bool
pub fn is_all_visible(&self) -> bool
v7.37.15 (Phase D) — true iff every row in this table is
known-all-visible to every snapshot (frozen xmin + alive
xmax). When true, scan_visible skips the per-row check
entirely — the scan degenerates to a plain rows().iter().
Maintained lazily: any insert/update that stamps a non-
frozen xmin / xmax clears the cached flag; the next call to
this method recomputes by walking the header vec. The walk
is O(n) in the rare case (only when an MVCC writer ran on
this table); steady-state legacy workloads hit the cached
true and scan at pre-v7.37.15 speed.
Phase D wires this into the engine’s hot-tier scan
optimisation; the bit also serves the per-segment all-
visible bitmap (each cold segment is a separately tracked
all_visible bit, but cold segments are frozen wholesale
so they’re trivially true).
Sourcepub fn position_visible(&self, idx: usize, snapshot: &Snapshot) -> bool
pub fn position_visible(&self, idx: usize, snapshot: &Snapshot) -> bool
v7.37.15 (Phase B / D) — iterate over (idx, row) pairs whose
header is visible under snapshot. This is the engine-side
drop-in replacement for for (i, r) in t.rows().iter().enumerate()
at scan sites. The check is a single branch + atomic
register read inside the snapshot path; with Snapshot::unbounded
the optimiser folds the gate away.
'a lifetime on snapshot keeps the helper zero-cost in
the hot loop — no Arc bump, no allocation.
v7.39 (round 560) — is the row at this position visible to the
snapshot? Exposed so an index-only walk can decide without
fetching the row it is deciding about.
Sourcepub fn header_runs(&self) -> HeaderRuns<'_>
pub fn header_runs(&self) -> HeaderRuns<'_>
v7.39 (round 562) — the same question asked many times over ascending positions, without descending the header trie for each one.
A profile of the server serving a 100k-row index-only range put
27% of the connection thread’s CPU on the per-row visibility test.
The headers are a PersistentVec — a 32-way trie — so
position_visible is four dependent pointer loads per row. A
sequential scan never pays that: it walks rows and headers in
lockstep. An index walk cannot, but its positions arrive in
ascending order and a leaf holds 32 of them, so keeping the run
between calls turns 32 descents into one.
A position outside the held run just descends, so an index whose order is uncorrelated with position costs what it costs today.
Sourcepub fn count_visible(&self, snapshot: &Snapshot) -> usize
pub fn count_visible(&self, snapshot: &Snapshot) -> usize
v7.39 (round 559) — how many rows a snapshot sees, without touching a single one of them.
count(*) already short-circuits to rows.len() in the
aggregate layer, so the O(1) part was never the problem: the cost
is UPSTREAM, materialising every visible row so that layer can
take its length. scan_visible zips the row trie with the
headers, and a count needs only the headers.
Measured over pgwire on 500k rows, SELECT count(*):
PG18 (2 parallel workers) 8.2 ms
PG18 (parallelism off) 10.3 ms
SPG 16.5 ms = 33 ns/row— 1.6x slower than a single-threaded PG on the commonest aggregate there is, which no ledger entry recorded.
pub fn scan_visible<'a, 'b>(
&'a self,
snapshot: &'b Snapshot,
) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'bwhere
'a: 'b,
Sourcepub fn resume_slot_after(&self, last: RowId, hint: usize) -> usize
pub fn resume_slot_after(&self, last: RowId, hint: usize) -> usize
The hot-tier slot a scan should resume at, given the last
RowId it consumed and where that row
used to sit.
Slots move. vacuum reclaims tombstones by rebuilding the row
vector, so every position after the first reclaimed one shifts
down — a reader that remembered a bare index would silently skip
or repeat rows. Row ids do not move: they are allocated
monotonically and never reused, which makes them the only stable
way to say “carry on after this row”.
hint is the position that row occupied when it was read. It is
still right whenever nothing was reclaimed under the reader, so
the check costs one lookup; the binary search is the fallback for
when it is not, and it works because appends only ever push
larger ids and reclaiming preserves their order.
Sourcepub fn scan_visible_from<'a, 'b>(
&'a self,
start: usize,
snapshot: &'b Snapshot,
) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'bwhere
'a: 'b,
pub fn scan_visible_from<'a, 'b>(
&'a self,
start: usize,
snapshot: &'b Snapshot,
) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'bwhere
'a: 'b,
The same visibility-gated walk as Table::scan_visible, resuming
at hot-tier index start.
A server-side cursor hands out its result in batches and has to continue where the previous batch stopped. Restarting the walk per batch and discarding a growing prefix would make an N-batch drain quadratic in the row count, so the resume point is a parameter rather than something the caller skips over.
start is a hot-tier position, not a RowId:
callers that resume across a compaction must re-derive it, which is
why the cursor path only resumes tables with no cold segments.
note_seq_scan fires only for start == 0. One cursor drained in
300 batches is one sequential scan of the table, and counting it
300 times would misreport pg_stat_user_tables.seq_scan.
Sourcepub fn indices_mut(&mut self) -> &mut [Index]
pub fn indices_mut(&mut self) -> &mut [Index]
v6.8.0 — exposed for the engine layer to patch
Index::included_columns post-creation. Could fold into
add_index once the engine’s IF-NOT-EXISTS guard moves up,
but the patch shape is the minimal change for v6.8.0.
pub fn indices(&self) -> &[Index]
Sourcepub fn next_auto_value(&self, col_pos: usize) -> Option<i64>
pub fn next_auto_value(&self, col_pos: usize) -> Option<i64>
Compute the next AUTO_INCREMENT value for the column at
col_pos. Defined as max(existing) + 1, falling back to 1
when the column currently holds no integer values. NULL / non-
integer cells are skipped. Returns None when the column isn’t
an integer type.
Sourcepub fn index_on(&self, column_position: usize) -> Option<&Index>
pub fn index_on(&self, column_position: usize) -> Option<&Index>
Return the first index defined over column_position, if any.
(v0.8 supports at most one index per column logically; the search
just picks the first match.)
Sourcepub fn insert(&mut self, row: Row<'static>) -> Result<(), StorageError>
pub fn insert(&mut self, row: Row<'static>) -> Result<(), StorageError>
Insert one row after validating it matches the schema (length + type).
Returns StorageError on mismatch — the table is left unchanged.
Updates every defined index with the new row’s key.
Sourcepub fn add_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
Build a new B-tree index over the named column. Rebuilds from
existing rows. Errors if column_name doesn’t exist or the index
name is taken.
Sourcepub fn ensure_excl_range_index(&mut self, column_position: usize)
pub fn ensure_excl_range_index(&mut self, column_position: usize)
v7.39 (round 215) — ensure a range-exclusion index exists on
column_position, building it from the current rows. Idempotent: a
second call for the same column is a no-op. Called at CREATE TABLE /
ALTER ADD EXCLUDE and on catalog load (rebuild-from-constraints).
Tombstoned rows are indexed too (they are filtered by the consumer via
is_deleted() at query time — the established index pattern).
Sourcepub fn excl_range_index(
&self,
column_position: usize,
) -> Option<&PersistentBTreeMap<(i128, u8), Vec<RowLocator>>>
pub fn excl_range_index( &self, column_position: usize, ) -> Option<&PersistentBTreeMap<(i128, u8), Vec<RowLocator>>>
v7.39 (round 215) — the range-exclusion index on column_position, if
one was built. The EXCLUDE enforcement path probes its
predecessor + successors to
find candidate overlaps in O(log n).
Sourcepub fn add_nsw_index(
&mut self,
name: String,
column_name: &str,
m: usize,
) -> Result<(), StorageError>
pub fn add_nsw_index( &mut self, name: String, column_name: &str, m: usize, ) -> Result<(), StorageError>
Build a new NSW (HNSW-flavoured) index over the named column.
Required for ORDER BY col <-> literal LIMIT k to plan as a
graph traversal instead of a full scan. Column must be a Vector
type. m is the maximum number of neighbours per node.
Sourcepub fn rebuild_nsw_index(
&mut self,
name: &str,
new_encoding: Option<VecEncoding>,
) -> Result<(), StorageError>
pub fn rebuild_nsw_index( &mut self, name: &str, new_encoding: Option<VecEncoding>, ) -> Result<(), StorageError>
v6.0.4 — synchronous rebuild of the named NSW index. If
new_encoding is Some(target) and differs from the column’s
current encoding, every stored cell at the indexed column is
re-coded into the target encoding before the new graph
builds. Returns IndexNotFound if no index by that name exists
and Unsupported for non-NSW indexes (BTree REBUILD is a no-op
the engine layer rejects, not a storage-level concept).
Holds the caller’s &mut self for the duration — no
concurrency / staging / WAL-replay machinery in v6.0.4. The
“live” optimisation lands as v6.0.4.1.
Sourcepub fn restore_nsw_index(
&mut self,
name: String,
column_name: &str,
graph: NswGraph,
) -> Result<(), StorageError>
pub fn restore_nsw_index( &mut self, name: String, column_name: &str, graph: NswGraph, ) -> Result<(), StorageError>
Restore an NSW index from a pre-built graph (used on
deserialize). Skips the bulk-build pass since the topology is
already known. Returns DuplicateIndex or ColumnNotFound on
schema mismatch as usual.
Sourcepub fn restore_btree_index(
&mut self,
name: String,
column_name: &str,
map: PersistentBTreeMap<IndexKey, Vec<RowLocator>>,
) -> Result<(), StorageError>
pub fn restore_btree_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<IndexKey, Vec<RowLocator>>, ) -> Result<(), StorageError>
Restore a BTree index from a pre-built (IndexKey, Vec<RowLocator>)
map. Used by Catalog::deserialize when reading a v9 (or later)
catalog snapshot — the map travels on disk so cold-tier locators
survive a round-trip, instead of being rebuilt from self.rows
(which would lose every Cold entry). Same error contract as
Table::add_index.
Sourcepub fn restore_brin_index(
&mut self,
name: String,
column_name: &str,
column_type: DataType,
) -> Result<(), StorageError>
pub fn restore_brin_index( &mut self, name: String, column_name: &str, column_type: DataType, ) -> Result<(), StorageError>
v6.7.1 — public restore counterpart for BRIN indices. Used
by Catalog::deserialize when a v10 snapshot carries a
BRIN index entry. BRIN carries no in-memory data — only the
column_type snapshot is restored.
Sourcepub fn add_brin_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_brin_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
v6.7.1 — public CREATE INDEX counterpart for BRIN. Creates
the index entry with a snapshot of the indexed column’s
current DataType.
Sourcepub fn add_gin_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_gin_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
v7.12.3 — Build a new GIN inverted index over a tsvector
column. Populates posting lists from existing rows. Errors
if the column doesn’t exist, isn’t TsVector, or the index
name is taken.
Sourcepub fn restore_gin_index(
&mut self,
name: String,
column_name: &str,
map: PersistentBTreeMap<String, Vec<RowLocator>>,
) -> Result<(), StorageError>
pub fn restore_gin_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, Vec<RowLocator>>, ) -> Result<(), StorageError>
v7.12.3 — Restore a GIN index from a deserialised snapshot.
Mirrors Self::restore_btree_index but takes the GIN’s
word → Vec<RowLocator> posting-list map (already populated
from the catalog stream) instead of an IndexKey map.
Sourcepub fn add_gin_trgm_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_gin_trgm_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
v7.15.0 — gin_trgm_ops GIN over a TEXT column. Walks
every row, shingles the cell into PG-compatible trigrams,
and builds the posting-list map. NULL / non-TEXT cells
contribute nothing (no trigrams).
Sourcepub fn restore_gin_trgm_index(
&mut self,
name: String,
column_name: &str,
map: PersistentBTreeMap<String, Vec<RowLocator>>,
) -> Result<(), StorageError>
pub fn restore_gin_trgm_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, Vec<RowLocator>>, ) -> Result<(), StorageError>
v7.15.0 — restore a trigram-GIN from its catalog snapshot
payload. Mirrors Self::restore_gin_index.
Sourcepub fn add_gin_fulltext_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_gin_fulltext_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
v7.17.0 Phase 2.2 — MySQL FULLTEXT KEY GIN over a TEXT
column. Walks every row, tokenises the cell into lower-
cased word lexemes (fts_simple::simple_lex — same rule
as to_tsvector('simple', text)), and builds the
posting-list map. NULL / non-TEXT cells contribute
nothing (no lexemes).
Sourcepub fn restore_gin_fulltext_index(
&mut self,
name: String,
column_name: &str,
map: PersistentBTreeMap<String, Vec<RowLocator>>,
) -> Result<(), StorageError>
pub fn restore_gin_fulltext_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, Vec<RowLocator>>, ) -> Result<(), StorageError>
v7.17.0 Phase 2.2 — restore a fulltext-GIN from its
catalog snapshot payload. Mirrors
Self::restore_gin_trgm_index.
Sourcepub fn add_gin_jsonb_index(
&mut self,
name: String,
column_name: &str,
) -> Result<(), StorageError>
pub fn add_gin_jsonb_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>
v7.37.8(sentori Epic 5 P2)— JSONB-GIN over a Json /
Jsonb column. Walks every row, extracts canonical
(path, leaf) tokens via
crate::jsonb_gin::extract_tokens, and builds the
posting-list map. NULL or non-Json cells contribute no
tokens(<col> @> <jsonb> against a NULL row is always
false so absence here is correct).
Sourcepub fn restore_gin_jsonb_index(
&mut self,
name: String,
column_name: &str,
map: PersistentBTreeMap<String, Vec<RowLocator>>,
) -> Result<(), StorageError>
pub fn restore_gin_jsonb_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, Vec<RowLocator>>, ) -> Result<(), StorageError>
v7.37.8 — restore a JSONB-GIN from its catalog snapshot
payload. Mirrors Self::restore_gin_fulltext_index.
Sourcepub fn register_cold_locators<I>(
&mut self,
index_name: &str,
locators: I,
) -> Result<usize, StorageError>
pub fn register_cold_locators<I>( &mut self, index_name: &str, locators: I, ) -> Result<usize, StorageError>
v5.1: register cold-tier locators on a BTree index. Used
after Catalog::load_segment_bytes to wire every cold-
tier row’s PK back to its segment so
Catalog::lookup_by_pk can resolve it. Each call
appends to the index — keys that already have hot or cold
locators keep them. Returns the number of locators
registered.
Pre-v5.2 (freezer) this is the only path that adds Cold variants to a PB; post-freezer the background freezer thread produces these as a batch under the engine write lock and this API becomes its in-memory primitive.
Errors if index_name doesn’t exist or names an NSW graph
(NSW indices don’t carry per-key row locators — they’re
vector-search structures).
Sourcepub fn register_gin_cold_locators<I>(
&mut self,
index_name: &str,
locators: I,
) -> Result<usize, StorageError>
pub fn register_gin_cold_locators<I>( &mut self, index_name: &str, locators: I, ) -> Result<usize, StorageError>
v7.12.3 — GIN-side parallel to Self::register_cold_locators.
Re-attaches word → cold RowLocator posting-list entries after
the from-rows rebuild loop. Errors when the index doesn’t
exist or isn’t a GIN. Both tsvector-GIN and trigram-GIN
variants share posting-list shape (String → Vec<RowLocator>),
so this helper accepts either.
Sourcepub fn remove_cold_locators_for_key(
&mut self,
index_name: &str,
key: &IndexKey,
) -> Result<usize, StorageError>
pub fn remove_cold_locators_for_key( &mut self, index_name: &str, key: &IndexKey, ) -> Result<usize, StorageError>
v5.2.3: remove every Cold locator currently registered on
index_name under the given key. Hot locators for the
same key are left in place — useful when a row has just been
promoted hot-side and the caller wants the old Cold pointer
retired without losing the new hot entry.
Returns the number of cold locators removed (0 when the key
has only hot entries or the key isn’t present at all).
Errors when the index doesn’t exist or isn’t a BTree.
Sourcepub fn add_column(&mut self, col: ColumnSchema, fill_value: Value<'static>)
pub fn add_column(&mut self, col: ColumnSchema, fill_value: Value<'static>)
v7.13.0 — append a new column to the schema and back-fill
every existing row with fill_value. Used by the engine’s
ALTER TABLE t ADD COLUMN … handler (mailrs round-5 G1).
Indices on existing columns keep working — column positions
don’t shift since the new column lands at the end — so no
index rebuild is needed.
Sourcepub fn set_partial_predicate(&mut self, idx: usize, pred: Option<String>)
pub fn set_partial_predicate(&mut self, idx: usize, pred: Option<String>)
v7.15.0 — replace the partial-index predicate source on
the index at slot idx. Used by ALTER TABLE … RENAME COLUMN after the engine rewrites column-identifier
references in the predicate source text. Pure metadata
edit; index rows are unaffected (they’re keyed by
column position, not predicate text).
Sourcepub fn rename_column(&mut self, col_pos: usize, new_name: &str)
pub fn rename_column(&mut self, col_pos: usize, new_name: &str)
v7.15.0 — rename the column at col_pos to new_name.
The on-disk row encoding is positional, so no row rewrite
is needed; only the schema’s column name changes. Indices,
UCs, FKs all key off column positions and are unaffected.
Source-text references that hold the column name (CHECK
predicates, partial-index predicates, runtime DEFAULT
expressions, trigger UPDATE OF lists) are rewritten by
the engine before this helper is called — the storage
layer doesn’t depend on spg-sql and so can’t re-parse the
predicate sources itself.
Sourcepub fn drop_column(&mut self, col_pos: usize)
pub fn drop_column(&mut self, col_pos: usize)
v7.13.3 — drop the column at col_pos. Removes the entry
from the schema, the value from every row, any index that
references the column (pure drop, not shift), and shifts
every remaining index/UC/FK column position that pointed
past col_pos down by one. Used by ALTER TABLE t DROP COLUMN <c> (mailrs round-7 S8). FK dependents on this
column must already have been removed by the caller (CASCADE
path); the helper assumes only same-column index removal is
needed.
Sourcepub fn truncate(&mut self)
pub fn truncate(&mut self)
v4.4: delete the rows at the given positions in one pass.
positions must be unique; ordering doesn’t matter. Indices
are rebuilt from scratch (cheaper than tracking incremental
shifts across both B-tree and NSW). Returns the number of
rows removed.
v7.17.0 Phase 1.3 — wipe every row. Used by REFRESH
MATERIALIZED VIEW; same effect as delete_rows((0..N).into())
but skips the per-position bookkeeping for the all-removed
fast path. Indices are rebuilt (empty).
pub fn delete_rows(&mut self, positions: &[usize]) -> usize
Sourcepub fn delete_rows_no_index(&mut self, positions: &[usize]) -> usize
pub fn delete_rows_no_index(&mut self, positions: &[usize]) -> usize
v7.37.5 (mailrs crash-recovery Ask 3) — row-only delete for the
WAL-replay batch path: removes the rows + decrements hot_bytes,
does NOT call rebuild_indices() and does NOT capture
redo. The caller is responsible for invoking rebuild_indices_pub
once after a sequence of *_no_index mutations on this table.
Skipping the per-call rebuild closes the
O(records × rows × indices × log rows) replay blow-up
(5000 DELETEs × 100k × 13 × ln 100k ≈ minutes → seconds).
Returns the number of rows actually removed (dedup + bounds-
filtered identically to delete_rows).
Sourcepub fn rebuild_indices_pub(&mut self)
pub fn rebuild_indices_pub(&mut self)
v7.37.5 — public alias for the private rebuild_indices helper.
Used by Catalog::apply_redo to coalesce per-record rebuilds
across a batch of RowChanges into one rebuild per touched table.
Sourcepub fn set_rows_and_rebuild_indices_with_rowids(
&mut self,
new_rows: PersistentVec<Row<'static>>,
new_hot_bytes: u64,
rowids: &[RowId],
headers: &[RowHeader],
)
pub fn set_rows_and_rebuild_indices_with_rowids( &mut self, new_rows: PersistentVec<Row<'static>>, new_hot_bytes: u64, rowids: &[RowId], headers: &[RowHeader], )
v7.37.5 (mailrs crash-recovery Ask 3) — replace the table’s
row vector + hot_bytes in one shot, then rebuild every
index from the new rows. Used by Catalog::apply_redo’s
batched run: a contiguous slice of RowChanges targeting
this table is composed into a final (PersistentVec<Row>, hot_bytes) pair via in-memory bookkeeping, then handed to
this method ONCE for index regeneration. Replaces N per-
record rebuild_indices calls with 1 per run.
v7.39 (flip crash-replay P0) — like
Self::set_rows_and_rebuild_indices but KEEPS the caller’s
per-slot RowIds. Redo replay applies one WAL record per
statement; reassigning ids between records broke every later
record’s tombstone targets (they name the ids the crashed
process allocated), resurrecting deleted rows. The id
allocator advances past every preserved id so post-replay
inserts never collide.
pub fn set_rows_and_rebuild_indices( &mut self, new_rows: PersistentVec<Row<'static>>, new_hot_bytes: u64, )
Sourcepub fn insert_no_index(&mut self, row: Row<'static>) -> Result<(), StorageError>
pub fn insert_no_index(&mut self, row: Row<'static>) -> Result<(), StorageError>
v7.37.5 (mailrs crash-recovery Ask 3) — row-only insert for the
WAL-replay batch path: pushes the row + bumps hot_bytes, and
does NOT update any index (B-tree, GIN, NSW). The caller is
responsible for invoking rebuild_indices_pub once after a
sequence of *_no_index mutations on this table.
Schema validation (arity + per-column type compatibility) is
applied so a malformed redo log surfaces honestly.
Sourcepub fn update_row_no_index(
&mut self,
position: usize,
new_values: Vec<Value<'static>>,
) -> Result<(), StorageError>
pub fn update_row_no_index( &mut self, position: usize, new_values: Vec<Value<'static>>, ) -> Result<(), StorageError>
v7.37.5 (mailrs crash-recovery Ask 3) — row-only update for the
WAL-replay batch path: replaces the row at position + adjusts
hot_bytes, and does NOT touch any index. Skipping the
per-update incremental index work is safe because the trailing
rebuild_indices_pub regenerates indices from self.rows in
their final state.
Sourcepub fn update_row(
&mut self,
position: usize,
new_values: Vec<Value<'static>>,
) -> Result<(), StorageError>
pub fn update_row( &mut self, position: usize, new_values: Vec<Value<'static>>, ) -> Result<(), StorageError>
v4.4: replace the row at position with new_values (must
match the schema arity + types). v7.20: index maintenance is
incremental — only indices whose key value changed are
touched (B-tree entry move in place; NSW / BRIN / GIN fall
back to a full rebuild when their column changed).