Skip to main content

rushdb_storage/
overlay_view.rs

1//! Owned, read-buffer-first view over a pinned snapshot.
2//!
3//! An [`OverlayView`] layers an uncommitted write buffer (`None` = tombstone)
4//! on top of a [`ReadView`]. It is the single primitive behind transaction
5//! read-your-writes (a statement sees earlier statements' staged writes) and,
6//! later, the sequencer's speculative expansion layer. Reads consult the buffer
7//! first, then the pinned base snapshot, so the overlay never blocks a reader
8//! and never mutates committed state.
9
10use std::collections::{BTreeMap, BTreeSet};
11use std::ops::Bound;
12use std::sync::Arc;
13
14use crate::{Op, ReadCancellation, ReadView, ScanBounds, ScanProjection, StorageError};
15
16/// A pinned base snapshot plus an uncommitted key overlay.
17///
18/// `Clone` is cheap for the base (`Arc`) and copies the overlay map; callers
19/// that only read should share one instance behind an `Arc`.
20#[derive(Clone)]
21pub struct OverlayView {
22    base: Arc<ReadView>,
23    buffer: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
24    // Derived rows implied by the staged writes, computed lazily on the first
25    // read of a derived key and reset by every write, so a transaction's own
26    // writes are visible to derived reads (T-5b for aggregates, DC12).
27    derived: std::sync::OnceLock<Result<Arc<BTreeMap<Vec<u8>, Option<Vec<u8>>>>, StorageError>>,
28}
29
30impl std::fmt::Debug for OverlayView {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        f.debug_struct("OverlayView")
33            .field("commit_index", &self.base.commit_index())
34            .field("overlay_keys", &self.buffer.len())
35            .finish_non_exhaustive()
36    }
37}
38
39impl OverlayView {
40    /// Wrap a pinned snapshot with an empty overlay.
41    #[must_use]
42    pub fn new(base: Arc<ReadView>) -> Self {
43        Self {
44            base,
45            buffer: BTreeMap::new(),
46            derived: std::sync::OnceLock::new(),
47        }
48    }
49
50    /// Wrap a pinned snapshot with a pre-existing overlay buffer.
51    #[must_use]
52    pub fn with_buffer(base: Arc<ReadView>, buffer: BTreeMap<Vec<u8>, Option<Vec<u8>>>) -> Self {
53        Self {
54            base,
55            buffer,
56            derived: std::sync::OnceLock::new(),
57        }
58    }
59
60    /// Derived rows of the staged writes against the base (lazy, cached until
61    /// the next write).
62    fn derived_rows(&self) -> Result<Arc<BTreeMap<Vec<u8>, Option<Vec<u8>>>>, StorageError> {
63        self.derived
64            .get_or_init(|| {
65                let mut rows = BTreeMap::new();
66                if let Some(deriver) = self.base.deriver() {
67                    if !self.buffer.is_empty() {
68                        let ops: Vec<Op> = self
69                            .buffer
70                            .iter()
71                            .map(|(key, value)| match value {
72                                Some(value) => Op::Put(key.clone(), value.clone()),
73                                None => Op::Del(key.clone()),
74                            })
75                            .collect();
76                        crate::derive::fold(&mut rows, &deriver.derive(0, &ops, &*self.base)?);
77                    }
78                }
79                Ok(Arc::new(rows))
80            })
81            .clone()
82    }
83
84    fn reaches_derived(&self, prefix: &[u8]) -> bool {
85        !self.buffer.is_empty()
86            && self
87                .base
88                .deriver()
89                .is_some_and(|deriver| deriver.prefix_reaches_derived(prefix))
90    }
91
92    /// The pinned base snapshot underneath this overlay.
93    #[must_use]
94    pub fn base(&self) -> &Arc<ReadView> {
95        &self.base
96    }
97
98    /// The current overlay buffer (read-buffer-first).
99    #[must_use]
100    pub fn buffer(&self) -> &BTreeMap<Vec<u8>, Option<Vec<u8>>> {
101        &self.buffer
102    }
103
104    /// Commit index this view reads its committed base at.
105    #[must_use]
106    pub fn commit_index(&self) -> u64 {
107        self.base.commit_index()
108    }
109
110    /// Cancellation handle shared with the base snapshot.
111    #[must_use]
112    pub fn cancellation(&self) -> ReadCancellation {
113        self.base.cancellation()
114    }
115
116    /// Stage a write in the overlay.
117    pub fn put(&mut self, key: Vec<u8>, value: Vec<u8>) {
118        self.derived = std::sync::OnceLock::new();
119        self.buffer.insert(key, Some(value));
120    }
121
122    /// Stage a tombstone in the overlay.
123    pub fn delete(&mut self, key: Vec<u8>) {
124        self.derived = std::sync::OnceLock::new();
125        self.buffer.insert(key, None);
126    }
127
128    /// Fold a batch of LMPG ops into the overlay. History is irrelevant to
129    /// reads, so `Put`/`Set` become live values and `Del`/`Purge` tombstones.
130    pub fn apply_ops(&mut self, ops: &[Op]) {
131        for op in ops {
132            match op {
133                Op::Put(k, v) | Op::Set(k, v) => {
134                    self.buffer.insert(k.clone(), Some(v.clone()));
135                }
136                Op::Del(k) | Op::Purge(k) => {
137                    self.buffer.insert(k.clone(), None);
138                }
139            }
140        }
141        self.derived = std::sync::OnceLock::new();
142    }
143
144    /// Read one key: overlay first, then the pinned base.
145    ///
146    /// # Errors
147    /// Returns the underlying storage error from the base read.
148    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
149        if let Some(value) = self.buffer.get(key) {
150            return Ok(value.clone());
151        }
152        if self.reaches_derived(key)
153            && self
154                .base
155                .deriver()
156                .is_some_and(|deriver| deriver.is_derived_key(key))
157        {
158            if let Some(value) = self.derived_rows()?.get(key) {
159                return Ok(value.clone());
160            }
161        }
162        self.base.get(key)
163    }
164
165    /// Materialize every key/value with `key` starting with `prefix`, merging
166    /// the overlay over the base (overlay wins; tombstones hide base rows).
167    ///
168    /// # Errors
169    /// Returns the underlying storage error from the base scan.
170    pub fn range_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
171        let mut merged: BTreeMap<Vec<u8>, Vec<u8>> =
172            self.base.range_prefix(prefix)?.into_iter().collect();
173        let derived = if self.reaches_derived(prefix) {
174            Some(self.derived_rows()?)
175        } else {
176            None
177        };
178        for (key, value) in self
179            .buffer
180            .range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded))
181            .chain(derived.iter().flat_map(|rows| {
182                rows.range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded))
183            }))
184        {
185            if !key.starts_with(prefix) {
186                continue;
187            }
188            match value {
189                Some(value) => {
190                    merged.insert(key.clone(), value.clone());
191                }
192                None => {
193                    merged.remove(key);
194                }
195            }
196        }
197        Ok(merged.into_iter().collect())
198    }
199
200    /// Materialize the keys with `start <= key < end`, merging the overlay over
201    /// the base (overlay wins; tombstones hidden). Values are never read, so a
202    /// keys-only scan does not pay for large payloads.
203    ///
204    /// # Errors
205    /// Returns the underlying storage error from the base scan.
206    pub fn range_keys(&self, start: &[u8], end: &[u8]) -> Result<Vec<Vec<u8>>, StorageError> {
207        let bounds = ScanBounds {
208            start: Bound::Included(start.to_vec()),
209            end: if end.is_empty() {
210                Bound::Unbounded
211            } else {
212                Bound::Excluded(end.to_vec())
213            },
214        };
215        let mut keys: BTreeSet<Vec<u8>> = BTreeSet::new();
216        let mut cursor = self.base.scan(bounds, ScanProjection::Keys)?;
217        loop {
218            let batch = cursor.next_batch(1024, 8 * 1024 * 1024)?;
219            for row in batch.rows {
220                keys.insert(row.key);
221            }
222            if batch.done {
223                break;
224            }
225        }
226        let end_bound = if end.is_empty() {
227            Bound::Unbounded
228        } else {
229            Bound::Excluded(end)
230        };
231        for (key, value) in self
232            .buffer
233            .range::<[u8], _>((Bound::Included(start), end_bound))
234        {
235            match value {
236                Some(_) => {
237                    keys.insert(key.clone());
238                }
239                None => {
240                    keys.remove(key);
241                }
242            }
243        }
244        Ok(keys.into_iter().collect())
245    }
246
247    /// Materialize every key/value with `start <= key < end`, merging the
248    /// overlay over the base. An empty `end` is unbounded above.
249    ///
250    /// # Errors
251    /// Returns the underlying storage error from the base scan.
252    pub fn range(&self, start: &[u8], end: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
253        let mut merged: BTreeMap<Vec<u8>, Vec<u8>> =
254            self.base.range(start, end)?.into_iter().collect();
255        let end_bound = if end.is_empty() {
256            Bound::Unbounded
257        } else {
258            Bound::Excluded(end)
259        };
260        let common = start
261            .iter()
262            .zip(end.iter())
263            .take_while(|(a, b)| a == b)
264            .count();
265        let derived = if self.reaches_derived(&start[..common]) {
266            Some(self.derived_rows()?)
267        } else {
268            None
269        };
270        for (key, value) in self
271            .buffer
272            .range::<[u8], _>((Bound::Included(start), end_bound))
273            .chain(
274                derived
275                    .iter()
276                    .flat_map(|rows| rows.range::<[u8], _>((Bound::Included(start), end_bound))),
277            )
278        {
279            match value {
280                Some(value) => {
281                    merged.insert(key.clone(), value.clone());
282                }
283                None => {
284                    merged.remove(key);
285                }
286            }
287        }
288        Ok(merged.into_iter().collect())
289    }
290}