Skip to main content

nexir_mvcc/
backend.rs

1use std::collections::{BTreeMap, BTreeSet, HashMap};
2
3use crate::types::{CommittedVersion, Intent, Key, Timestamp, TxnId, Value};
4
5/// The storage backend contract.
6/// - `get_committed_versions` must return versions for the key sorted by ascending `commit_ts`.
7/// - A durable backend must make commit atomic with respect to committed-version creation and intent removal.
8/// - `put_committed_batch` must be strictly all-or-nothing for durable backends.
9pub trait Backend {
10    /// Returns all committed versions for a key, ordered ascending by `commit_ts`.
11    fn get_committed_versions(&self, key: &[u8]) -> Result<Vec<CommittedVersion>, String>;
12    /// Returns the most recent committed version for a key, if any.
13    fn get_latest_committed(&self, key: &[u8]) -> Result<Option<CommittedVersion>, String>;
14    /// Returns the most recent committed version for a key that is visible at or before `read_ts`.
15    fn get_visible_committed(
16        &self,
17        key: &[u8],
18        read_ts: Timestamp,
19    ) -> Result<Option<CommittedVersion>, String>;
20    /// Returns the timestamp of the most recent committed version for a key, if any.
21    fn get_latest_commit_ts(&self, key: &[u8]) -> Result<Option<Timestamp>, String>;
22    /// Fetches the active intent for a given key, if any exists.
23    fn get_intent(&self, _key: &[u8]) -> Result<Option<Intent>, String> {
24        Ok(None)
25    }
26    /// Writes a single intent to the backend.
27    fn put_intent(&mut self, _intent: Intent) -> Result<(), String> {
28        Err("intent transactions are not supported by this backend".to_string())
29    }
30    /// Removes an intent from the backend if it matches the given `txn_id` and `start_ts`.
31    /// Returns `true` if removed, `false` otherwise.
32    fn remove_intent(
33        &mut self,
34        _key: &[u8],
35        _txn_id: TxnId,
36        _start_ts: Timestamp,
37    ) -> Result<bool, String> {
38        Ok(false)
39    }
40    /// Writes a single committed version to the backend.
41    fn put_committed(&mut self, version: CommittedVersion) -> Result<(), String>;
42    /// Writes multiple committed versions atomically. Must be all-or-nothing.
43    fn put_committed_batch(&mut self, versions: Vec<CommittedVersion>) -> Result<(), String>;
44    /// Removes a specific committed version during garbage collection.
45    fn remove_committed_version(&mut self, key: &[u8], commit_ts: Timestamp) -> Result<(), String>;
46    /// Returns a deduplicated, sorted list of all keys currently managed by the backend.
47    fn all_keys(&self) -> Result<Vec<Key>, String>;
48    /// Writes multiple intents atomically. Must be all-or-nothing.
49    fn put_intents_batch(&mut self, intents: Vec<Intent>) -> Result<(), String> {
50        if intents.is_empty() {
51            Ok(())
52        } else {
53            Err("intent transactions are not supported by this backend".to_string())
54        }
55    }
56    /// Converts multiple intents into committed versions atomically.
57    /// Must create the versions and remove the intents in a single durable transaction.
58    fn commit_intents_batch(
59        &mut self,
60        _commits: Vec<CommittedVersion>,
61        _removed_intents: Vec<(Key, TxnId, Timestamp)>,
62    ) -> Result<(), String> {
63        Err("intent transactions are not supported by this backend".to_string())
64    }
65    /// Removes multiple intents atomically. Must be all-or-nothing.
66    fn remove_intents_batch(
67        &mut self,
68        intents: Vec<(Key, TxnId, Timestamp)>,
69    ) -> Result<(), String> {
70        if intents.is_empty() {
71            Ok(())
72        } else {
73            Err("intent transactions are not supported by this backend".to_string())
74        }
75    }
76    /// Returns up to `limit` keys strictly ordered, starting from `start` (inclusive if provided).
77    fn keys_from(&self, start: Option<&[u8]>, limit: usize) -> Result<Vec<Key>, String>;
78    /// Returns up to `limit` keys starting with `prefix`, strictly ordered, starting from `start` if provided.
79    /// Excludes intents and returns committed keys only.
80    fn keys_from_prefix(
81        &self,
82        prefix: &[u8],
83        start: Option<&[u8]>,
84        limit: usize,
85    ) -> Result<Vec<Key>, String>;
86    /// Returns the `limit` newest commit timestamps strictly before `before_ts` for the given key.
87    /// Ordered descending (newest first).
88    fn get_committed_timestamps_before(
89        &self,
90        key: &[u8],
91        before_ts: Timestamp,
92        limit: usize,
93    ) -> Result<Vec<Timestamp>, String>;
94    /// Atomically removes a tombstone version and every supplied older version.
95    /// Callers must pass the complete older version set for a final tombstone collapse.
96    fn collapse_tombstone(
97        &mut self,
98        key: &[u8],
99        tombstone_ts: Timestamp,
100        older_ts: Vec<Timestamp>,
101    ) -> Result<(), String>;
102}
103
104/// A simple, non-durable in-memory implementation of the `Backend` trait.
105/// Intended for testing, examples, and rapid prototyping.
106#[derive(Debug, Clone, Default)]
107pub struct InMemoryBackend {
108    committed: BTreeMap<(Key, Timestamp), Option<Value>>,
109    intents: HashMap<Key, Intent>,
110    all_keys_set: BTreeSet<Key>,
111}
112
113impl InMemoryBackend {
114    /// Creates a new, empty in-memory backend.
115    pub fn new() -> Self {
116        Self::default()
117    }
118}
119
120impl Backend for InMemoryBackend {
121    fn get_committed_versions(&self, key: &[u8]) -> Result<Vec<CommittedVersion>, String> {
122        let mut result = Vec::new();
123        let start = (key.to_vec(), Timestamp(0));
124        for ((k, ts), value) in self.committed.range(start..) {
125            if k.as_slice() != key {
126                break;
127            }
128            result.push(CommittedVersion {
129                key: k.clone(),
130                commit_ts: *ts,
131                value: value.clone(),
132            });
133        }
134        Ok(result)
135    }
136
137    fn get_latest_committed(&self, key: &[u8]) -> Result<Option<CommittedVersion>, String> {
138        let range = (key.to_vec(), Timestamp(0))..=(key.to_vec(), Timestamp(u128::MAX));
139        if let Some(((k, ts), value)) = self.committed.range(range).next_back()
140            && k.as_slice() == key
141        {
142            return Ok(Some(CommittedVersion {
143                key: k.clone(),
144                commit_ts: *ts,
145                value: value.clone(),
146            }));
147        }
148        Ok(None)
149    }
150
151    fn get_visible_committed(
152        &self,
153        key: &[u8],
154        read_ts: Timestamp,
155    ) -> Result<Option<CommittedVersion>, String> {
156        let range = (key.to_vec(), Timestamp(0))..=(key.to_vec(), read_ts);
157        if let Some(((k, ts), value)) = self.committed.range(range).next_back()
158            && k.as_slice() == key
159        {
160            return Ok(Some(CommittedVersion {
161                key: k.clone(),
162                commit_ts: *ts,
163                value: value.clone(),
164            }));
165        }
166        Ok(None)
167    }
168
169    fn get_latest_commit_ts(&self, key: &[u8]) -> Result<Option<Timestamp>, String> {
170        let range = (key.to_vec(), Timestamp(0))..=(key.to_vec(), Timestamp(u128::MAX));
171        if let Some(((k, ts), _)) = self.committed.range(range).next_back()
172            && k.as_slice() == key
173        {
174            return Ok(Some(*ts));
175        }
176        Ok(None)
177    }
178
179    fn get_intent(&self, key: &[u8]) -> Result<Option<Intent>, String> {
180        Ok(self.intents.get(key).cloned())
181    }
182
183    fn put_intent(&mut self, intent: Intent) -> Result<(), String> {
184        self.all_keys_set.insert(intent.key.clone());
185        self.intents.insert(intent.key.clone(), intent);
186        Ok(())
187    }
188
189    fn remove_intent(
190        &mut self,
191        key: &[u8],
192        txn_id: TxnId,
193        start_ts: Timestamp,
194    ) -> Result<bool, String> {
195        if let Some(intent) = self.intents.get(key)
196            && intent.txn_id == txn_id
197            && intent.start_ts == start_ts
198        {
199            self.intents.remove(key);
200            self.maybe_remove_from_keys(key);
201            return Ok(true);
202        }
203        Ok(false)
204    }
205
206    fn put_committed(&mut self, version: CommittedVersion) -> Result<(), String> {
207        self.all_keys_set.insert(version.key.clone());
208        self.committed.insert(
209            (version.key.clone(), version.commit_ts),
210            version.value.clone(),
211        );
212        Ok(())
213    }
214
215    fn put_committed_batch(&mut self, versions: Vec<CommittedVersion>) -> Result<(), String> {
216        for version in versions {
217            self.all_keys_set.insert(version.key.clone());
218            self.committed.insert(
219                (version.key.clone(), version.commit_ts),
220                version.value.clone(),
221            );
222        }
223        Ok(())
224    }
225
226    fn remove_committed_version(&mut self, key: &[u8], commit_ts: Timestamp) -> Result<(), String> {
227        self.committed.remove(&(key.to_vec(), commit_ts));
228        self.maybe_remove_from_keys(key);
229        Ok(())
230    }
231
232    fn all_keys(&self) -> Result<Vec<Key>, String> {
233        Ok(self.all_keys_set.iter().cloned().collect())
234    }
235
236    fn put_intents_batch(&mut self, intents: Vec<Intent>) -> Result<(), String> {
237        for intent in intents {
238            self.all_keys_set.insert(intent.key.clone());
239            self.intents.insert(intent.key.clone(), intent);
240        }
241        Ok(())
242    }
243
244    fn commit_intents_batch(
245        &mut self,
246        commits: Vec<CommittedVersion>,
247        removed_intents: Vec<(Key, TxnId, Timestamp)>,
248    ) -> Result<(), String> {
249        for version in commits {
250            self.all_keys_set.insert(version.key.clone());
251            self.committed.insert(
252                (version.key.clone(), version.commit_ts),
253                version.value.clone(),
254            );
255        }
256        for (key, txn_id, start_ts) in removed_intents {
257            if let Some(intent) = self.intents.get(&key)
258                && intent.txn_id == txn_id
259                && intent.start_ts == start_ts
260            {
261                self.intents.remove(&key);
262                self.maybe_remove_from_keys(&key);
263            }
264        }
265        Ok(())
266    }
267
268    fn remove_intents_batch(
269        &mut self,
270        intents: Vec<(Key, TxnId, Timestamp)>,
271    ) -> Result<(), String> {
272        for (key, txn_id, start_ts) in intents {
273            if let Some(intent) = self.intents.get(&key)
274                && intent.txn_id == txn_id
275                && intent.start_ts == start_ts
276            {
277                self.intents.remove(&key);
278                self.maybe_remove_from_keys(&key);
279            }
280        }
281        Ok(())
282    }
283
284    fn keys_from(&self, start: Option<&[u8]>, limit: usize) -> Result<Vec<Key>, String> {
285        if let Some(s) = start {
286            Ok(self
287                .all_keys_set
288                .range(s.to_vec()..)
289                .take(limit)
290                .cloned()
291                .collect())
292        } else {
293            Ok(self.all_keys_set.iter().take(limit).cloned().collect())
294        }
295    }
296
297    fn keys_from_prefix(
298        &self,
299        prefix: &[u8],
300        start: Option<&[u8]>,
301        limit: usize,
302    ) -> Result<Vec<Key>, String> {
303        if limit == 0 {
304            return Ok(Vec::new());
305        }
306        if prefix.is_empty() {
307            return Err("Prefix cannot be empty".to_string());
308        }
309        if let Some(s) = start
310            && !s.starts_with(prefix)
311        {
312            return Err("Start cursor must start with the prefix".to_string());
313        }
314        let scan_start = start.unwrap_or(prefix);
315        let mut result = Vec::new();
316        let range_start = (scan_start.to_vec(), Timestamp(0));
317        for ((k, _), _) in self.committed.range(range_start..) {
318            if !k.starts_with(prefix) {
319                break;
320            }
321            if result.last() != Some(k) {
322                result.push(k.clone());
323                if result.len() == limit {
324                    break;
325                }
326            }
327        }
328        Ok(result)
329    }
330
331    fn get_committed_timestamps_before(
332        &self,
333        key: &[u8],
334        before_ts: Timestamp,
335        limit: usize,
336    ) -> Result<Vec<Timestamp>, String> {
337        let range = (key.to_vec(), Timestamp(0))..(key.to_vec(), before_ts);
338        let mut result = Vec::new();
339        for ((k, ts), _) in self.committed.range(range).rev().take(limit) {
340            if k.as_slice() == key {
341                result.push(*ts);
342            }
343        }
344        Ok(result)
345    }
346
347    fn collapse_tombstone(
348        &mut self,
349        key: &[u8],
350        tombstone_ts: Timestamp,
351        older_ts: Vec<Timestamp>,
352    ) -> Result<(), String> {
353        self.committed.remove(&(key.to_vec(), tombstone_ts));
354        for ts in older_ts {
355            self.committed.remove(&(key.to_vec(), ts));
356        }
357        self.maybe_remove_from_keys(key);
358        Ok(())
359    }
360}
361
362impl InMemoryBackend {
363    fn maybe_remove_from_keys(&mut self, key: &[u8]) {
364        if !self.intents.contains_key(key) {
365            let range = (key.to_vec(), Timestamp(0))..=(key.to_vec(), Timestamp(u128::MAX));
366            if self.committed.range(range).next().is_none() {
367                self.all_keys_set.remove(key);
368            }
369        }
370    }
371}