Skip to main content

marsdb_storage/
txn.rs

1use std::borrow::Borrow;
2
3use redb::{
4    AccessGuard, Key, MultimapRange, MultimapTable, MultimapTableDefinition, MultimapValue, Range,
5    ReadOnlyMultimapTable, ReadOnlyTable, ReadTransaction, ReadableMultimapTable, ReadableTable,
6    ReadableTableMetadata, Table, TableDefinition, Value, WriteTransaction,
7};
8
9use crate::error::StorageError;
10
11/// Either kind of redb transaction — lets a function that only ever reads
12/// (`.get()`/`.iter()`, never `.insert()`/`.remove()`) run against a real
13/// `WriteTransaction` (the crash-safety boundary for a write statement) or
14/// a `ReadTransaction` (so a read-only statement doesn't have to contend
15/// for redb's single-writer lock at all). `WriteTransaction`/
16/// `ReadTransaction` share no common trait in redb itself — `open_table`/
17/// `open_multimap_table` are inherent methods on two unrelated structs,
18/// returning different concrete types (`Table`/`ReadOnlyTable`,
19/// `MultimapTable`/`ReadOnlyMultimapTable`) — so this (and `TableHandle`/
20/// `MultimapTableHandle` below) is a small local abstraction over that,
21/// not something redb provides.
22#[derive(Clone, Copy)]
23pub enum Txn<'a> {
24    Write(&'a WriteTransaction),
25    Read(&'a ReadTransaction),
26}
27
28impl<'a> Txn<'a> {
29    pub fn open_table<K: Key + 'static, V: Value + 'static>(
30        &self,
31        def: TableDefinition<K, V>,
32    ) -> Result<TableHandle<'a, K, V>, StorageError> {
33        Ok(match self {
34            Txn::Write(w) => TableHandle::Write(w.open_table(def)?),
35            Txn::Read(r) => TableHandle::Read(r.open_table(def)?),
36        })
37    }
38
39    pub fn open_multimap_table<K: Key + 'static, V: Key + 'static>(
40        &self,
41        def: MultimapTableDefinition<K, V>,
42    ) -> Result<MultimapTableHandle<'a, K, V>, StorageError> {
43        Ok(match self {
44            Txn::Write(w) => MultimapTableHandle::Write(w.open_multimap_table(def)?),
45            Txn::Read(r) => MultimapTableHandle::Read(r.open_multimap_table(def)?),
46        })
47    }
48}
49
50/// Exposes `get`/`iter`/`range` as plain inherent methods, not a full
51/// `redb::ReadableTable` trait impl — matching the whole trait (`first`/
52/// `last`/...) would be boilerplate for methods nothing calls. `range`
53/// was deliberately left out until a real call site needed it; v2's
54/// composite-key adjacency (prefix scans over `ADJ_OUT`/`ADJ_IN`) is that
55/// call site.
56pub enum TableHandle<'a, K: Key + 'static, V: Value + 'static> {
57    Write(Table<'a, K, V>),
58    Read(ReadOnlyTable<K, V>),
59}
60
61impl<'a, K: Key + 'static, V: Value + 'static> TableHandle<'a, K, V> {
62    pub fn get<'k>(
63        &self,
64        key: impl Borrow<K::SelfType<'k>>,
65    ) -> Result<Option<AccessGuard<'_, V>>, StorageError> {
66        Ok(match self {
67            TableHandle::Write(t) => t.get(key)?,
68            TableHandle::Read(t) => t.get(key)?,
69        })
70    }
71
72    pub fn iter(&self) -> Result<Range<'_, K, V>, StorageError> {
73        Ok(match self {
74            TableHandle::Write(t) => t.iter()?,
75            TableHandle::Read(t) => t.iter()?,
76        })
77    }
78
79    /// Total entry count — O(1), redb tracks it per table. Added for the
80    /// planner's start-point cardinality comparisons (an `AllNodesScan`
81    /// candidate's cost is exactly this count for `NODES`), same
82    /// "cheap count, never walk the entries" contract as
83    /// `MultimapValue::len()` in `index::match_count`.
84    // Fallible len can't back a conventional is_empty; no caller wants
85    // one (the planner compares counts, never emptiness).
86    #[allow(clippy::len_without_is_empty)]
87    pub fn len(&self) -> Result<u64, StorageError> {
88        Ok(match self {
89            TableHandle::Write(t) => t.len()?,
90            TableHandle::Read(t) => t.len()?,
91        })
92    }
93
94    /// Key-ordered scan over a sub-range — the primitive behind composite-
95    /// key prefix reads (`ADJ_OUT`/`ADJ_IN`'s `node ++ label` expansion)
96    /// and, eventually, indexed range predicates over `PROPERTY_INDEX`
97    /// (whose order-preserving value encoding has been range-ready since
98    /// it was written).
99    pub fn range<'k, KR: Borrow<K::SelfType<'k>> + 'k>(
100        &self,
101        range: impl std::ops::RangeBounds<KR> + 'k,
102    ) -> Result<Range<'_, K, V>, StorageError> {
103        Ok(match self {
104            TableHandle::Write(t) => t.range(range)?,
105            TableHandle::Read(t) => t.range(range)?,
106        })
107    }
108}
109
110pub enum MultimapTableHandle<'a, K: Key + 'static, V: Key + 'static> {
111    Write(MultimapTable<'a, K, V>),
112    Read(ReadOnlyMultimapTable<K, V>),
113}
114
115impl<'a, K: Key + 'static, V: Key + 'static> MultimapTableHandle<'a, K, V> {
116    pub fn get<'k>(
117        &self,
118        key: impl Borrow<K::SelfType<'k>>,
119    ) -> Result<MultimapValue<'_, V>, StorageError> {
120        Ok(match self {
121            MultimapTableHandle::Write(t) => t.get(key)?,
122            MultimapTableHandle::Read(t) => t.get(key)?,
123        })
124    }
125
126    #[allow(dead_code)] // not called by any current read path, kept for parity with TableHandle::iter
127    pub fn iter(&self) -> Result<MultimapRange<'_, K, V>, StorageError> {
128        Ok(match self {
129            MultimapTableHandle::Write(t) => t.iter()?,
130            MultimapTableHandle::Read(t) => t.iter()?,
131        })
132    }
133
134    /// Key-ordered scan over a sub-range of keys — the multimap
135    /// counterpart of `TableHandle::range`, backing `PROPERTY_INDEX`
136    /// range predicates (the order-preserving value encoding has been
137    /// range-ready since it was written).
138    pub fn range<'k, KR: Borrow<K::SelfType<'k>> + 'k>(
139        &self,
140        range: impl std::ops::RangeBounds<KR> + 'k,
141    ) -> Result<MultimapRange<'_, K, V>, StorageError> {
142        Ok(match self {
143            MultimapTableHandle::Write(t) => t.range(range)?,
144            MultimapTableHandle::Read(t) => t.range(range)?,
145        })
146    }
147}