Skip to main content

Table

Struct Table 

Source
pub struct Table { /* private fields */ }

Implementations§

Source§

impl Table

Source

pub fn new(schema: TableSchema) -> Self

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn write_stats(&self) -> (u64, u64, u64)

(n_tup_ins, n_tup_upd, n_tup_del) for pg_stat_user_tables.

Source

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)).

Source

pub fn stamp_autovacuum(&mut self, unix_us: i64)

Source

pub fn stamp_analyze(&mut self, unix_us: i64)

Source

pub fn scan_stats(&self) -> &ScanStats

v7.39 (pg_stat knife B) — the scan counters (read side of pg_stat_user_tables).

Source

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.

Source

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).

Source

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.

Source

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:

  1. allocate version V via next_version()
  2. call insert_with_xmin(row, V)
  3. update any indexes (as insert does)

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.

Source

pub fn insert_with_xmin_keyed( &mut self, row: Row<'static>, xmin: u64, expr_values: Option<&[Option<Value<'static>>]>, ) -> Result<(), StorageError>

Table::insert_with_xmin with the expression indexes’ keys supplied. See Table::insert_keyed.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

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).

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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_locators remains the right call for the EXACT count (ANALYZE etc.) — its O(N) walk is unsuitable per join stage.
Source

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.

Source

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.

Source

pub const fn schema(&self) -> &TableSchema

Source

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.

Source

pub fn brin_columns(&self) -> Vec<usize>

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. v7.38.11 — the column positions this table has BRIN indexes on.

Source

pub fn brin_candidate_slots( &self, col_pos: usize, lo: Option<i64>, hi: Option<i64>, ) -> Option<Vec<Range<usize>>>

v7.38.11 — the slot ranges a BRIN index cannot rule out for col_pos under lo <= x / x <= hi, or None when there is no BRIN index on that column.

None and “every slot” are deliberately different answers: None means this table has nothing to say, so a caller that does not understand BRIN keeps scanning exactly as before.

A range is skipped only when its summary PROVES no row in it can match. A range with no summary — never written, or written only with values this index cannot order — is always kept. The predicate still runs on every row that survives: the summary decides what to skip, never what to return.

Source

pub const fn rows(&self) -> &PersistentVec<Row<'static>>

Source

pub const fn row_count(&self) -> usize

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

pub fn scan_visible<'a, 'b>( &'a self, snapshot: &'b Snapshot, ) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'b
where 'a: 'b,

Source

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.

Source

pub fn scan_visible_from<'a, 'b>( &'a self, start: usize, snapshot: &'b Snapshot, ) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'b
where '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.

Source

pub fn scan_visible_slots<'a, 'b>( &'a self, slots: Vec<Range<usize>>, snapshot: &'b Snapshot, ) -> impl Iterator<Item = (usize, &'a Row<'static>)> + 'b
where 'a: 'b,

v7.38.11 — like Table::scan_visible_from but visiting only the slots a BRIN summary could not rule out.

The caller passes ranges it got from Table::brin_candidate_slots; passing 0..len gives exactly the same rows in the same order as the unpruned scan, which is how the callers that have no BRIN index keep their behaviour.

Source

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.

Source

pub fn indices(&self) -> &[Index]

Source

pub fn auto_value_from_index(&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. v7.38.19 — the next value for col_pos taken from its index, or None when there is no index to take it from.

A B-tree already holds these values in order, so its largest key is a descent rather than a walk of every row. Self::next_auto_value walked, and one INSERT with a bigserial id cost:

  rows        before     after     PostgreSQL 18
   1,000     1.831 ms      —          1.245
  50,000     2.703         —          1.386
 200,000     3.666       1.106        1.075

Theirs is flat because a sequence is a counter; ours grew with the table, so an ingest workload got slower the longer it ran.

Separate from next_auto_value and public so a test can ask WHICH path a table takes — the two answer identically, measured, so nothing else can tell them apart from the outside. Deleting the highest row and inserting again gives 1,2,3,4,6 either way and on PostgreSQL 18.4, because a deleted row leaves a version behind that both the tree and the scan still see.

Source

pub fn next_auto_value(&self, col_pos: usize) -> Option<i64>

Source

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.)

Source

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. Insert a row, maintaining every index that keys on a column’s own value.

An index that keys on an EXPRESSION is not one of those: its key is not in the row, and this crate has no expression evaluator. Callers that can compute those keys pass them through Table::insert_keyed; this entry point cannot, so it marks each such index incomplete and the engine rebuilds it before it is next consulted. The index is never left holding keys that do not match its expression — that was the v6.8.2 shape, and it cost 1.9x a plain insert to maintain something no lookup could match.

Source

pub fn insert_keyed( &mut self, row: Row<'static>, expr_values: Option<&[Option<Value<'static>>]>, ) -> Result<(), StorageError>

Table::insert with the expression indexes’ VALUES supplied by the caller, one slot per entry of Table::indices (None where the index does not key on an expression, or where the expression evaluated to NULL and so enters no entry, exactly as a NULL column value does).

A value, not a key: each index kind makes its own entries out of one. A B-tree wants an IndexKey, a full-text GIN wants the lexemes of a TsVector, a trigram GIN wants the shingles of a string, a JSONB GIN wants the tokens of a document. All four ask the same question of the row — “what does this expression say here?” — and only the caller can answer it.

Source

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.

Source

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).

Source

pub fn excl_range_index( &self, column_position: usize, ) -> Option<&PersistentBTreeMap<(i128, u8), PostingList>>

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).

Source

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.

Source

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.

Source

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.

Source

pub fn restore_btree_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<IndexKey, PostingList>, ) -> 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.

Source

pub fn restore_btree_multi_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<Box<[IndexKey]>, PostingList>, ) -> Result<(), StorageError>

v7.38.1 (L12) — snapshot-restore counterpart for a tag-7 multi-column B-tree. The extras arrive via the per-index appendix, which Catalog::deserialize applies after this call — exactly as it does for every other restored kind.

Source

pub fn row_values_at(&self, position: usize) -> Option<&[Value<'static>]>

One row’s stored values, by physical position — the same positions a RowLocator::Hot names. Includes rows no snapshot can see: an index entry outlives the version it points at, and visibility is decided when the entry is followed, not when it is made.

Source

pub fn stored_row_count(&self) -> usize

Physical row count, dead versions included.

Source

pub fn index_collation(&self, idx: &Index) -> Option<&str>

v7.38.18 (S0) — the collation an index’s keys are built under, when that is not byte order.

A B-tree here orders IndexKey by a derived Ord, which for text is byte order. A column that collates by a LOCALE cannot key on its raw text, then: the tree would order the entries one way and the scan would answer another, and a range seek would return a subset. Measured before this existed, on a column declared COLLATE "en_US.utf8": WHERE x > 'b' gave four rows scanning and one row seeking.

So such an index takes a SUPPLIED key, exactly as an expression index does — the engine holds the collator, encodes the ICU sort key, and this crate stores the bytes it is handed. None means the index keys on its column’s own value, which is the ordinary case and stays free.

Source

pub fn db_collation(&self) -> &str

v7.38.18 (S2) — the database collation in force for this table, which is what its undeclared text columns are compared under and what its indexes on them key under. "C" when none was set.

Source

pub fn set_db_collation(&mut self, name: &str)

v7.38.18 (S2) — set by the catalog that owns this table.

Source

pub fn index_needs_supplied_key(&self, idx: &Index) -> bool

Does this index’s key come from the caller rather than from the row’s own cell? True for an expression index and for one whose column collates by a locale; the two are the same mechanism.

Source

pub fn expr_index_is_complete(&self, name: &str) -> bool

Is this expression index’s B-tree currently keyed by its expression’s value, and therefore safe to look up in?

false for every index read off disk, for one that a plain Table::insert has touched since it was built, and for an index that keys on a column (which has no expression to be complete about — ask expression.is_none() instead).

Source

pub fn rebuild_expression_index( &mut self, name: &str, values: &[Option<Value<'static>>], ) -> Result<bool, StorageError>

Rebuild an expression index’s B-tree from keys, one per row of this table in row order, and mark it complete.

The caller owns the evaluator, so it owns the keys; this crate only owns the invariant that the map and the flag move together. Returns false — index untouched, still incomplete — when the index does not key on an expression, is not a B-tree, the key count does not match the row count, or any row body lives in a cold segment (whose values the caller could not have evaluated).

Source§

impl Table

Source

pub fn stale_collated_indices(&self) -> Vec<(String, usize, String)>

v7.38.18 (S0) — the LOCALE-COLLATED column indexes that are not currently usable, as (index name, column position, collation).

Sibling of Table::stale_expression_indices and consumed by the same refresh: the engine reads the column, encodes each value as an ICU sort key, and hands the keys back to Table::rebuild_expression_index. The two lists are separate because an expression index names an expression to re-parse and this one names a column and a collation, which is a different question with the same answer shape.

Source

pub fn stale_expression_indices(&self) -> Vec<(String, String)>

The expression indexes that are not currently usable, with the expression each one keys on. The engine evaluates these per row and hands the results back to Table::rebuild_expression_index.

Source

pub fn convert_index_to_multi( &mut self, name: &str, ) -> Result<bool, StorageError>

v7.38.1 (L12) — upgrade a leading-column B-tree that carries extra_column_positions into a real multi-column B-tree, in place, keeping every piece of index metadata. Returns false (untouched) when the index is not a plain BTree, has no extras, or keys on an expression (whose value is not a column’s own).

Cold locators block the conversion too: a composite key cannot be derived for a row whose body lives in a cold segment, and silently dropping the entry would drop the row from every seek.

Source

pub fn add_multi_index( &mut self, name: &str, column_position: usize, extra_column_positions: Vec<usize>, ) -> Result<(), StorageError>

v7.38.1 (L12) — build a real multi-column B-tree over [leading, extras…] from the current rows. The caller supplies resolved column positions; uniqueness and the rest of the index’s metadata are applied by the caller afterwards, exactly as add_index callers do today.

Source

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.

Source

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.

Source

pub fn add_gin_index_on_expression( &mut self, name: String, anchor_column: &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. A GIN index whose entries come from an EXPRESSION, not from a column’s own cell.

anchor_column only gives the index a well-formed catalog position; its type is deliberately not checked, because the expression’s type is what decides the posting-list shape and the anchor is typically the TEXT column the expression reads. The map starts empty and Table::rebuild_expression_index fills it.

Source

pub fn add_gin_index( &mut self, name: String, column_name: &str, ) -> Result<(), StorageError>

Source

pub fn restore_gin_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, PostingList>, ) -> 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.

Source

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).

Source

pub fn restore_gin_trgm_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, PostingList>, ) -> Result<(), StorageError>

v7.15.0 — restore a trigram-GIN from its catalog snapshot payload. Mirrors Self::restore_gin_index.

Source

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).

Source

pub fn restore_gin_fulltext_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, PostingList>, ) -> Result<(), StorageError>

v7.17.0 Phase 2.2 — restore a fulltext-GIN from its catalog snapshot payload. Mirrors Self::restore_gin_trgm_index.

Source

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).

Source

pub fn restore_gin_jsonb_index( &mut self, name: String, column_name: &str, map: PersistentBTreeMap<String, PostingList>, ) -> Result<(), StorageError>

v7.37.8 — restore a JSONB-GIN from its catalog snapshot payload. Mirrors Self::restore_gin_fulltext_index.

Source

pub fn register_cold_locators<I>( &mut self, index_name: &str, locators: I, ) -> Result<usize, StorageError>
where I: IntoIterator<Item = (IndexKey, RowLocator)>,

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).

Source

pub fn register_gin_cold_locators<I>( &mut self, index_name: &str, locators: I, ) -> Result<usize, StorageError>
where I: IntoIterator<Item = (String, RowLocator)>,

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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).

Source

pub fn delete_rows(&mut self, positions: &[usize]) -> usize

Source

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).

Source

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.

Source

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.

Source

pub fn set_rows_and_rebuild_indices( &mut self, new_rows: PersistentVec<Row<'static>>, new_hot_bytes: u64, )

Source

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.

Source

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.

Source

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).

Trait Implementations§

Source§

impl Clone for Table

Source§

fn clone(&self) -> Table

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Table

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl !Freeze for Table

§

impl RefUnwindSafe for Table

§

impl Send for Table

§

impl Sync for Table

§

impl Unpin for Table

§

impl UnsafeUnpin for Table

§

impl UnwindSafe for Table

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.