pub struct UnitOfWork { /* private fields */ }Expand description
A batch of writes across several tables that lands whole or not at all (ADR 0012). Operations
are collected with put/patch/delete and nothing touches a table until commit(), which
runs the six steps documented in docs/architecture.md: resolve, barrier, prepare, journal,
write, cleanup. &mut Table borrows held for the call are the barrier — no separate write
queue to drain, since nothing else can hold those tables mutably at the same time.
commit() borrows the unit rather than consuming it, so a refused commit can be retried on the
same unit once its cause is fixed — the same contract as the TS UnitOfWork. Staging is
append-only in both engines: fixing a refusal means fixing what it named, not unstaging an op.
Implementations§
Source§impl UnitOfWork
impl UnitOfWork
pub fn new() -> Self
pub fn operations(&self) -> usize
pub fn tables(&self) -> &[String]
pub fn put(&mut self, table: &str, document: Value) -> &mut Self
pub fn patch( &mut self, table: &str, key: impl Into<String>, changes: Value, ) -> &mut Self
pub fn delete(&mut self, table: &str, key: impl Into<String>) -> &mut Self
Sourcepub fn preview(
&self,
table_name: &str,
key: &str,
table: &Table,
) -> Result<StagedView, TableError>
pub fn preview( &self, table_name: &str, key: &str, table: &Table, ) -> Result<StagedView, TableError>
Reads a key through the unit: a value staged for table since the unit started, falling
back to table’s committed state for a key the unit does not touch. put and patch ops
are applied in staging order, patch merging over whatever the unit has staged so far (or
the committed record, on the first touch) exactly as Table::patch would.
pub fn get( &self, table_name: &str, key: &str, table: &Table, ) -> Result<Option<HaysonRecord>, TableError>
Sourcepub fn query(
&self,
table_name: &str,
table: &Table,
spec: &QuerySpec,
) -> Result<QueryResult, TableError>
pub fn query( &self, table_name: &str, table: &Table, spec: &QuerySpec, ) -> Result<QueryResult, TableError>
Runs spec as the unit will leave table: the ops staged for table_name so far are
resolved into a StagedIndex, the query is planned over the committed candidates the
overlay has not taken over plus the staged records that can still match, and the answer is
tested against staged records and committed ones alike. A search is scored over the
overlaid corpus, so a staged record joins the ranking and not merely the result set.
The plan carries a PlanOverlay reporting what the staged half contributed — how many
staged records joined the candidates, how many committed ones the overlay suppressed, and
whether the scores behind them were recomputed.
Sourcepub fn commit(
&mut self,
tables: &mut HashMap<String, Table>,
journal_path: &Path,
) -> Result<UnitOfWorkResult, TxnError>
pub fn commit( &mut self, tables: &mut HashMap<String, Table>, journal_path: &Path, ) -> Result<UnitOfWorkResult, TxnError>
Runs every staged operation, or none of them, across tables.
- resolve — an op naming a table missing from
tablesfails before anything else runs; - barrier — the
&mut Tableborrows this call holds for its whole body; - prepare —
Table::prepare_txn_opson every table, in staging order; the first rejected op fails the call with nothing appended anywhere; - journal — every prepared line, across every table, written to
journal_pathin oneatomic::write_atomiccall; - write —
Table::apply_txn_entriesper table, in staging order; - cleanup — the journal file removed.
A refusal raised by steps 1–3 leaves the unit live and fully staged, so the caller can fix
whatever the refusal named and call commit again on the same unit — nothing was written,
and only monotonic _seq counters were burned. From step 4 on the unit is spent whatever
happens, because a journal that reached the disk is finished by recover_incomplete, and a
second run of the same operations would apply them twice.
Steps 1–3, 4 and 5–6 are also callable one at a time as UnitOfWork::prepare,
UnitOfWork::journal and apply_prepared; this is those three back to back. A caller
that holds the tables behind a lock uses the split so it can let go of that lock across
step 4, which touches no table — see the concurrency section of docs/architecture.md.
Sourcepub fn prepare(
&mut self,
tables: &mut HashMap<String, Table>,
) -> Result<PreparedUnit, TxnError>
pub fn prepare( &mut self, tables: &mut HashMap<String, Table>, ) -> Result<PreparedUnit, TxnError>
Steps 1–3 of UnitOfWork::commit: resolve, prepare, and the triggers the prepared
entries fire. Mutates only the _seq counters of the tables it touches; nothing is
appended, no registry moves and no file is written, so a refusal here still leaves the unit
staged and retriable.
Sourcepub fn journal(
&mut self,
prepared: &PreparedUnit,
journal_path: &Path,
) -> Result<(), TxnError>
pub fn journal( &mut self, prepared: &PreparedUnit, journal_path: &Path, ) -> Result<(), TxnError>
Step 4 of UnitOfWork::commit: every prepared line, across every table, written to
journal_path and fsynced in one atomic::write_atomic call. Reads no table and mutates
none, which is what lets a caller run it without the lock that guards them — the writes are
still invisible to a reader at this point, because nothing has been applied yet.
Retires the unit: from the moment the journal is on disk the operations land whether this
process finishes them or the next open() does, so a second run would apply them twice.