Skip to main content

RowChange

Enum RowChange 

Source
pub enum RowChange {
    Insert {
        table: String,
        row: Row<'static>,
        rowid: RowId,
        writer_version: u64,
    },
    Update {
        table: String,
        pos: usize,
        new_row: Vec<Value<'static>>,
        rowid: RowId,
        writer_version: u64,
    },
    Delete {
        table: String,
        positions: Vec<usize>,
        rowids: Vec<RowId>,
        writer_version: u64,
    },
    Tombstone {
        table: String,
        rowids: Vec<RowId>,
        xmax: u64,
    },
}
Expand description

In-memory table: schema + a persistent row vector + secondary indices.

v4.39: rows is a PersistentVec (Bitmapped Vector Trie, 32-way) so Table::clone() is O(1) — the whole reason for v4.39’s existence is to make Catalog::clone() cheap inside the v4.34 auto-commit wrap.

v5.2.1: hot_bytes tracks the encoded byte size of every row currently in [Self::rows], summed over rows. Updated incrementally by insert (+= encoded row size), delete_rows (-= removed rows’ encoded sizes), and update_row (-= old size, += new size). The value is what the v5.2 freezer reads to decide when to demote cold rows — when the catalog-wide sum crosses SPG_HOT_TIER_BYTES (default 4 GiB) the freezer thread wakes. v5.2.1 ships measurement only; the freezer itself lands in v5.2.2. Stored as u64 so a single field clone in Catalog::clone stays at the O(1) invariant v4.39 built. v7.34 (crash-recovery P0 #2) — one row-level physical redo record. Row-level redo replaces statement-based WAL replay (which re-executes each SQL through the full engine — O(records × catalog_rows), the superlinear recovery hang root-caused on the mailrs crash-recovery P0). A RowChange is the exact storage mutation the engine applied (Table::insert / update_row / delete_rows); replaying it on a catalog restored from the matching checkpoint reproduces the state WITHOUT re-validating uniqueness/FK/parse/plan — O(changed rows).

Positions are physical, not key-based: serialize/deserialize preserve row order exactly (rows written + read back in self.rows order) and the mutation ops are deterministic, so the same op sequence replayed from the same checkpoint reproduces the same positions. This matches PostgreSQL’s physical redo and supports tables with no primary key. (Caveat handled at replay integration: a post-checkpoint cold-tier freeze shifts hot positions and must itself be logged or fenced by a checkpoint — see row-level-redo-design.)

§v7.37.15 (Epic W slice 1) — additive MVCC identity metadata

Each variant now also carries, additively, the stable RowId of the affected row(s) and the writer version (xmin for an insert, xmax for a delete/update). This is the codec foundation for making in-place MVCC tombstones durable across crash/upgrade recovery.

Two important properties for the durability path:

  1. Replay resolution is UNCHANGED. apply_redo_run_on_table still resolves every change by physical pos/positions exactly as before. The new metadata is carried but unused by replay in this slice; resolving-by-RowId and header-preserving replay are later slices.
  2. Backward compatibility. A redo payload written by pre-Epic-W code carries no metadata; decode_redo_log fills rowid/rowids with RowId::UNASSIGNED (empty for Delete) and writer_version with 0. See the codec version gate in encode_redo_log/decode_redo_log.

The writer_version is captured as 0 at the storage layer (Table::insert/delete_rows/update_row don’t have the committing TxId), then stamped with the real committing version by the engine after it drains the statement’s changes (Epic W slice 2 — RowChange::set_writer_version, driven from Engine::writer_version_for_current_stmt). All changes from one statement share the one version. Replay still resolves by physical position and does not read writer_version — that is a later slice (header-preserving replay).

Variants§

§

Insert

Append row to table.

Fields

§table: String
§row: Row<'static>
§rowid: RowId

Epic W: stable id the appended row will receive. RowId::UNASSIGNED when decoded from a pre-Epic-W redo payload.

§writer_version: u64

Epic W: writer version (xmin). 0 until the writing TxId is threaded to the storage layer (later slice).

§

Update

Replace the row at physical pos in table with new_row.

Fields

§table: String
§pos: usize
§new_row: Vec<Value<'static>>
§rowid: RowId

Epic W: stable id of the row at pos. RowId::UNASSIGNED when decoded from a pre-Epic-W redo payload.

§writer_version: u64

Epic W: writer version (xmax of the superseded tuple). 0 until the writing TxId is threaded (later slice).

§

Delete

Remove the rows at the given physical positions from table.

Fields

§table: String
§positions: Vec<usize>
§rowids: Vec<RowId>

Epic W: stable ids parallel to positions (same length, RowId::UNASSIGNED for an out-of-bounds input position). Empty when decoded from a pre-Epic-W redo payload (no metadata was recorded).

§writer_version: u64

Epic W: writer version (xmax). 0 until the writing TxId is threaded to the storage layer (later slice).

§

Tombstone

v7.37.15 (Epic W durable-tombstone slice) — an in-place MVCC delete: the row(s) named by rowids are NOT physically removed; their header xmax is stamped so newer snapshots stop seeing them (vacuum reclaims later). This is the redo shape of the gate-on (SPG_MVCC_INPLACE) DELETE / UPDATE-old-version / ON-CONFLICT paths, which call Table::mark_row_deleted instead of delete_rows.

Unlike Delete, the target is named by stable RowId, not physical position: a tombstone keeps the slot, so position would be ambiguous after later compaction, and the header-preserving replay must re-find the exact row the writer tombstoned. On replay the id is matched against the ids the same redo run produced (an Insert’s rowid, or the table’s ids snapshotted at run start); an id that cannot be resolved is skipped and counted (see apply_redo_run_on_table) — this is the documented cross-checkpoint limitation until the V6 envelope persists ids.

Fields

§table: String
§rowids: Vec<RowId>

Stable ids of the tombstoned rows (from self.rowids()[pos] at capture). Never empty for a recorded tombstone.

§xmax: u64

The version stamped into each target row’s header xmax (the deleting statement’s writer version).

Implementations§

Source§

impl RowChange

Source

pub fn table_name(&self) -> &str

v7.39 (round 736) — which table this change applies to.

Source

pub fn set_writer_version(&mut self, v: u64)

v7.37.15 (Epic W slice 2) — stamp the committing writer version onto this change. Every change drained from a single statement shares one version (the statement’s xmin/xmax), so the engine calls this on each drained change with the value from [Engine::writer_version_for_current_stmt]. Additive metadata only: replay still resolves by physical position and does not read writer_version (that is a later slice).

Trait Implementations§

Source§

impl Clone for RowChange

Source§

fn clone(&self) -> RowChange

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 RowChange

Source§

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

Formats the value using the given formatter. Read more
Source§

impl PartialEq for RowChange

Source§

fn eq(&self, other: &RowChange) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for RowChange

Auto Trait Implementations§

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> ErasedDestructor for T
where T: 'static,

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> Same for T

Source§

type Output = T

Should always be Self
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 = Infallible

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.