Skip to main content

spacedb_store/
engine.rs

1//! The storage **engine seam** — the one trait everything in SpaceDB rests on.
2//!
3//! SpaceDB never talks to a concrete database. It talks to [`KvEngine`]: an
4//! engine-agnostic, transactional key/value interface. This is what lets the
5//! engine be a *per-store decision rather than a rewrite* (redb today; a
6//! versioned engine later if temporal reads become a product need — Open Q #6 in
7//! the mission), and it is the first of the open-core **seams**: `spacedb-store`
8//! ships with the [`crate::RedbEngine`] and [`crate::MemEngine`], and any other
9//! engine (including a MATA-hosted one) drops in behind the same trait.
10//!
11//! ## Transaction model
12//!
13//! - **Reads** see a consistent snapshot for the transaction's lifetime.
14//! - **Writes** are **single-writer** and **atomic**: a [`WriteTx`] buffers its
15//!   mutations and applies them all-or-nothing on [`WriteTx::commit`]. Dropping a
16//!   write transaction **without** committing **rolls back** — this is the
17//!   property the document + index + head-pointer multi-table write depends on,
18//!   and the property the durability test in S4 will kill a process to verify.
19//! - A [`WriteTx`] is also [`Readable`] (read-your-own-writes within the txn).
20//!
21//! All keys and values at this layer are **opaque bytes**. Typing and encoding
22//! live one layer up in [`crate::Table`]; the AEAD value boundary (S2) lives
23//! there too, so the engine only ever sees ciphertext.
24
25use crate::error::StoreResult;
26
27/// Durability for a write transaction. Chosen **per write** because the mission's
28/// consistency tiers want different guarantees: ledger-grade / strong-tier
29/// collections fsync every commit; explicitly-convergent caches may not.
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum Durability {
32    /// fsync on commit — a committed write survives a crash/power loss. The
33    /// default for everything unless a collection opts down.
34    Immediate,
35    /// No fsync barrier on commit — faster, but a crash may lose the most recent
36    /// commits. Permitted **only** for explicitly-convergent caches that can
37    /// recover by re-syncing.
38    Eventual,
39}
40
41/// A read view over the store. Both [`ReadTx`] and [`WriteTx`] implement it, so
42/// [`crate::Table`] read methods accept either (a write txn reads its own
43/// uncommitted writes).
44pub trait Readable {
45    /// Fetch the raw value bytes for `key` in `table`, or `None` if absent.
46    fn get_raw(&self, table: &str, key: &[u8]) -> StoreResult<Option<Vec<u8>>>;
47
48    /// Return `(key, value)` byte pairs in the **half-open** range `[lo, hi)`,
49    /// in ascending key (byte-lexicographic) order. Because keys are written in
50    /// the order-preserving encoding (see [`crate::codec`]), this is logical
51    /// key order.
52    fn range_raw(&self, table: &str, lo: &[u8], hi: &[u8]) -> StoreResult<Vec<(Vec<u8>, Vec<u8>)>>;
53}
54
55/// A read-only transaction: a consistent snapshot for its lifetime.
56pub trait ReadTx: Readable {}
57
58/// A single-writer transaction. Mutations are buffered and applied atomically on
59/// [`commit`](WriteTx::commit); dropping without committing rolls back.
60pub trait WriteTx: Readable {
61    /// Insert or overwrite `key` → `val` in `table`.
62    fn put_raw(&mut self, table: &str, key: &[u8], val: &[u8]) -> StoreResult<()>;
63
64    /// Remove `key` from `table`. Returns `true` if a value was present.
65    fn delete_raw(&mut self, table: &str, key: &[u8]) -> StoreResult<bool>;
66
67    /// Atomically apply every buffered mutation. Consuming `self` makes
68    /// "use after commit" a compile error and "drop without commit" the
69    /// rollback path.
70    fn commit(self) -> StoreResult<()>;
71}
72
73/// The storage engine: opens read and write transactions.
74///
75/// `Send + Sync` so one engine handle can be shared across the components that
76/// need it. The GAT lifetimes let an engine hand a transaction a borrow of
77/// itself (the in-memory engine holds a lock guard for the txn's lifetime; redb
78/// transactions are self-owned, so they simply ignore the lifetime).
79pub trait KvEngine: Send + Sync {
80    type RTx<'a>: ReadTx
81    where
82        Self: 'a;
83    type WTx<'a>: WriteTx
84    where
85        Self: 'a;
86
87    /// Begin a read transaction (a consistent snapshot).
88    fn begin_read(&self) -> StoreResult<Self::RTx<'_>>;
89
90    /// Begin a single-writer transaction with the given durability.
91    fn begin_write(&self, durability: Durability) -> StoreResult<Self::WTx<'_>>;
92}