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:
- Replay resolution is UNCHANGED.
apply_redo_run_on_tablestill resolves every change by physicalpos/positionsexactly as before. The new metadata is carried but unused by replay in this slice; resolving-by-RowIdand header-preserving replay are later slices. - Backward compatibility. A redo payload written by
pre-Epic-W code carries no metadata;
decode_redo_logfillsrowid/rowidswithRowId::UNASSIGNED(empty forDelete) andwriter_versionwith0. See the codec version gate inencode_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
rowid: RowIdEpic W: stable id the appended row will receive.
RowId::UNASSIGNED when
decoded from a pre-Epic-W redo payload.
Update
Replace the row at physical pos in table with new_row.
Fields
rowid: RowIdEpic W: stable id of the row at pos.
RowId::UNASSIGNED when
decoded from a pre-Epic-W redo payload.
Delete
Remove the rows at the given physical positions from table.
Fields
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).
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.
Implementations§
Source§impl RowChange
impl RowChange
Sourcepub fn table_name(&self) -> &str
pub fn table_name(&self) -> &str
v7.39 (round 736) — which table this change applies to.
Sourcepub fn set_writer_version(&mut self, v: u64)
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).