Skip to main content

sqlrite/mvcc/
mod.rs

1//! Multi-version concurrency control primitives (Phase 11).
2//!
3//! This module is the foundation for SQLRite's `BEGIN CONCURRENT`
4//! story — see [`docs/concurrent-writes-plan.md`](../../docs/concurrent-writes-plan.md)
5//! for the full sequenced design.
6//!
7//! Surface as of Phase 11.3:
8//!
9//! - [`MvccClock`] — process-wide monotonic `u64` counter that hands
10//!   out begin- and commit-timestamps. Persisted to the WAL header
11//!   so timestamps don't reuse the same value across reopens.
12//! - [`ActiveTxRegistry`] — tracks the begin-timestamps of in-flight
13//!   transactions; [`ActiveTxRegistry::min_active_begin_ts`] is the
14//!   GC watermark.
15//! - [`TxId`] / [`TxTimestampOrId`] — types the version chains
16//!   carry.
17//! - [`MvStore`] — the in-memory version index. Holds row chains
18//!   keyed by [`RowID`]; `read(row, begin_ts)` implements the
19//!   snapshot-isolation visibility rule (`begin <= T < end`).
20//! - [`JournalMode`] — per-database setting toggled by
21//!   `PRAGMA journal_mode = …`. `Wal` (default) keeps every
22//!   pre-Phase-11 read path in place; `Mvcc` is the opt-in that
23//!   11.4 will wire reads through.
24//!
25//! The executor doesn't consult `MvStore` yet — that wiring lives
26//! in 11.4 alongside `BEGIN CONCURRENT` writes. Decoupling the
27//! data structure (this PR) from the read/write integration (next
28//! PR) keeps the diffs reviewable.
29
30pub mod clock;
31pub mod log;
32pub mod registry;
33pub mod store;
34pub mod transaction;
35
36pub use clock::MvccClock;
37pub use log::{MVCC_BODY_MAGIC, MVCC_FRAME_MARKER, MvccCommitBatch, MvccLogRecord};
38pub use registry::{ActiveTxRegistry, TxHandle, TxId, TxTimestampOrId};
39pub use store::{MvStore, MvStoreError, RowID, RowVersion, RowVersionChain, VersionPayload};
40pub use transaction::ConcurrentTx;
41
42/// Selects the durability + concurrency story a database operates
43/// under. Toggled by `PRAGMA journal_mode = …` (see
44/// [`crate::sql::pragma::execute_pragma`]).
45///
46/// - [`JournalMode::Wal`] (default) — every read goes through the
47///   legacy table → pager path; every write fsyncs a per-page
48///   commit frame. This is the only mode pre-Phase-11 builds knew
49///   about, and it's what file-format-v5 + WAL-format-v2 files
50///   produce by default.
51/// - [`JournalMode::Mvcc`] — opts the database into Phase 11's
52///   multi-version concurrency control. Enables snapshot-isolated
53///   reads (consult `MvStore` first, fall back to the pager) and
54///   `BEGIN CONCURRENT` writes (Phase 11.4). On-disk format is
55///   unchanged; the WAL header's `clock_high_water` byte range
56///   carries the persisted clock value either way.
57///
58/// Phase 11.3 ships the parser surface and the per-database
59/// setting; the read path doesn't change behaviour yet. The
60/// `Mvcc` value is observable via the PRAGMA read form so callers
61/// can confirm the toggle landed.
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
63pub enum JournalMode {
64    /// Default — legacy WAL-backed pager. Every commit fsyncs a
65    /// page-level frame; every read consults `staged → wal_cache
66    /// → on_disk`.
67    #[default]
68    Wal,
69    /// Phase 11 MVCC + `BEGIN CONCURRENT`. Same on-disk format as
70    /// `Wal`; the in-memory `MvStore` sits in front of the pager
71    /// for reads, and writes go through commit-time validation.
72    Mvcc,
73}
74
75impl JournalMode {
76    /// Parses a PRAGMA value (case-insensitive). Returns `None` for
77    /// unrecognized inputs so the caller can surface a typed
78    /// `unknown journal_mode` error with the bad string.
79    pub fn from_str_lossless(s: &str) -> Option<Self> {
80        match s.to_ascii_lowercase().as_str() {
81            "wal" => Some(Self::Wal),
82            "mvcc" => Some(Self::Mvcc),
83            _ => None,
84        }
85    }
86
87    /// The lowercase string form the PRAGMA read renders.
88    pub fn as_str(&self) -> &'static str {
89        match self {
90            Self::Wal => "wal",
91            Self::Mvcc => "mvcc",
92        }
93    }
94}
95
96impl std::fmt::Display for JournalMode {
97    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98        f.write_str(self.as_str())
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn journal_mode_default_is_wal() {
108        assert_eq!(JournalMode::default(), JournalMode::Wal);
109    }
110
111    #[test]
112    fn journal_mode_round_trips_through_str() {
113        assert_eq!(
114            JournalMode::from_str_lossless("wal"),
115            Some(JournalMode::Wal)
116        );
117        assert_eq!(
118            JournalMode::from_str_lossless("WAL"),
119            Some(JournalMode::Wal)
120        );
121        assert_eq!(
122            JournalMode::from_str_lossless("Mvcc"),
123            Some(JournalMode::Mvcc)
124        );
125        assert_eq!(JournalMode::from_str_lossless("delete"), None);
126        assert_eq!(JournalMode::from_str_lossless(""), None);
127    }
128
129    #[test]
130    fn journal_mode_displays_lowercase() {
131        assert_eq!(format!("{}", JournalMode::Wal), "wal");
132        assert_eq!(format!("{}", JournalMode::Mvcc), "mvcc");
133    }
134}