Skip to main content

rushdb_storage/
overlay_view.rs

1//! Owned, read-buffer-first view over a pinned snapshot.
2//!
3//! An [`OverlayView`] layers uncommitted writes (`None` = tombstone) on top
4//! of a [`ReadView`]. It is the single primitive behind transaction
5//! read-your-writes (a statement sees earlier statements' staged writes) and
6//! the sequencer's expansion layer (a command sees every assigned but not yet
7//! published commit). Reads consult the staged writes first, then the pinned
8//! base snapshot, so the overlay never blocks a reader and never mutates
9//! committed state.
10//!
11//! Staged writes come in two tiers: shared, read-only [`CommitLayer`]s (one
12//! per in-flight commit, oldest first) and the view's own mutable `buffer`
13//! above them. A layer shares its commit's op vector with the publisher queue,
14//! so handing the in-flight state to the next command copies no key or value.
15
16use std::collections::{BTreeMap, BTreeSet};
17use std::ops::Bound;
18use std::sync::Arc;
19
20use crate::{Op, ReadCancellation, ReadView, ScanBounds, ScanProjection, StorageError};
21
22fn op_entry(op: &Op) -> (&[u8], Option<&[u8]>) {
23    match op {
24        Op::Put(key, value) | Op::Set(key, value) => (key, Some(value)),
25        Op::Del(key) | Op::Purge(key) => (key, None),
26    }
27}
28
29/// The writes of one assigned-but-unpublished commit as a read layer: the
30/// commit's ops, shared with the publisher queue, indexed by key (the last
31/// write of each key wins).
32pub(crate) struct CommitLayer {
33    ci: u64,
34    ops: Arc<Vec<Op>>,
35    /// Indices into `ops`, ordered by key, one per distinct key.
36    order: Vec<usize>,
37}
38
39impl CommitLayer {
40    pub(crate) fn new(ci: u64, ops: Arc<Vec<Op>>) -> Self {
41        let mut order: Vec<usize> = (0..ops.len()).collect();
42        // Key ascending, then op index descending: the first index of each
43        // key run is that key's last write, which `dedup_by` keeps.
44        order.sort_unstable_by(|a, b| {
45            op_entry(&ops[*a])
46                .0
47                .cmp(op_entry(&ops[*b]).0)
48                .then(b.cmp(a))
49        });
50        order.dedup_by(|later, kept| op_entry(&ops[*later]).0 == op_entry(&ops[*kept]).0);
51        Self { ci, ops, order }
52    }
53
54    pub(crate) fn ci(&self) -> u64 {
55        self.ci
56    }
57
58    /// The op vector this layer shares.
59    #[cfg(test)]
60    pub(crate) fn ops(&self) -> &Arc<Vec<Op>> {
61        &self.ops
62    }
63
64    /// Distinct keys written.
65    pub(crate) fn len(&self) -> usize {
66        self.order.len()
67    }
68
69    fn entry(&self, at: usize) -> (&[u8], Option<&[u8]>) {
70        op_entry(&self.ops[self.order[at]])
71    }
72
73    /// `Some(None)` for a tombstone, `None` when the layer does not write
74    /// `key`.
75    #[expect(
76        clippy::option_option,
77        reason = "not written by this layer and written as a tombstone are different answers"
78    )]
79    pub(crate) fn get(&self, key: &[u8]) -> Option<Option<&[u8]>> {
80        self.order
81            .binary_search_by(|at| op_entry(&self.ops[*at]).0.cmp(key))
82            .ok()
83            .map(|at| op_entry(&self.ops[self.order[at]]).1)
84    }
85
86    /// Written keys in key order.
87    pub(crate) fn keys(&self) -> impl Iterator<Item = &[u8]> {
88        (0..self.order.len()).map(|at| self.entry(at).0)
89    }
90
91    /// Entries with keys inside `(start, end)`, in key order.
92    fn range<'a>(
93        &'a self,
94        start: Bound<&[u8]>,
95        end: Bound<&[u8]>,
96    ) -> impl Iterator<Item = (&'a [u8], Option<&'a [u8]>)> + 'a {
97        let key = |at: &usize| op_entry(&self.ops[*at]).0;
98        let from = match start {
99            Bound::Included(start) => self.order.partition_point(|at| key(at) < start),
100            Bound::Excluded(start) => self.order.partition_point(|at| key(at) <= start),
101            Bound::Unbounded => 0,
102        };
103        let to = match end {
104            Bound::Included(end) => self.order.partition_point(|at| key(at) <= end),
105            Bound::Excluded(end) => self.order.partition_point(|at| key(at) < end),
106            Bound::Unbounded => self.order.len(),
107        };
108        (from..to.max(from)).map(|at| self.entry(at))
109    }
110}
111
112/// A pinned base snapshot plus uncommitted key overlays.
113///
114/// `Clone` is cheap for the base and the commit layers (`Arc`) and copies
115/// the own buffer; callers that only read should share one instance behind
116/// an `Arc`.
117#[derive(Clone)]
118pub struct OverlayView {
119    base: Arc<ReadView>,
120    /// In-flight commits' writes, oldest first, below `buffer`.
121    layers: Vec<Arc<CommitLayer>>,
122    buffer: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
123    // Derived rows implied by the staged writes, computed lazily on the first
124    // read of a derived key and reset by every write, so a transaction's own
125    // writes are visible to derived reads (T-5b for aggregates, DC12).
126    derived: std::sync::OnceLock<Result<Arc<BTreeMap<Vec<u8>, Option<Vec<u8>>>>, StorageError>>,
127}
128
129impl std::fmt::Debug for OverlayView {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("OverlayView")
132            .field("commit_index", &self.base.commit_index())
133            .field("layers", &self.layers.len())
134            .field("overlay_keys", &self.buffer.len())
135            .finish_non_exhaustive()
136    }
137}
138
139impl OverlayView {
140    /// Wrap a pinned snapshot with an empty overlay.
141    #[must_use]
142    pub fn new(base: Arc<ReadView>) -> Self {
143        Self::with_buffer(base, BTreeMap::new())
144    }
145
146    /// Wrap a pinned snapshot with a pre-existing overlay buffer.
147    #[must_use]
148    pub fn with_buffer(base: Arc<ReadView>, buffer: BTreeMap<Vec<u8>, Option<Vec<u8>>>) -> Self {
149        Self {
150            base,
151            layers: Vec::new(),
152            buffer,
153            derived: std::sync::OnceLock::new(),
154        }
155    }
156
157    /// Wrap a pinned snapshot with in-flight commit layers (oldest first).
158    pub(crate) fn with_layers(base: Arc<ReadView>, layers: Vec<Arc<CommitLayer>>) -> Self {
159        Self {
160            layers,
161            ..Self::new(base)
162        }
163    }
164
165    fn has_staged(&self) -> bool {
166        !self.buffer.is_empty() || self.layers.iter().any(|layer| layer.len() > 0)
167    }
168
169    /// The staged state of `key`: the own buffer, then the layers newest
170    /// first. `Some(None)` is a tombstone.
171    #[expect(
172        clippy::option_option,
173        reason = "not staged and staged as a tombstone are different answers"
174    )]
175    fn staged(&self, key: &[u8]) -> Option<Option<&[u8]>> {
176        if let Some(value) = self.buffer.get(key) {
177            return Some(value.as_deref());
178        }
179        self.layers.iter().rev().find_map(|layer| layer.get(key))
180    }
181
182    /// Every staged write in `(start, end)`, newest winning, borrowed.
183    fn staged_range(
184        &self,
185        start: Bound<&[u8]>,
186        end: Bound<&[u8]>,
187    ) -> BTreeMap<&[u8], Option<&[u8]>> {
188        let mut merged = BTreeMap::new();
189        for layer in &self.layers {
190            merged.extend(layer.range(start, end));
191        }
192        merged.extend(
193            self.buffer
194                .range::<[u8], _>((start, end))
195                .map(|(key, value)| (key.as_slice(), value.as_deref())),
196        );
197        merged
198    }
199
200    /// Every staged write in `(start, end)`, newest winning, as owned rows.
201    pub(crate) fn staged_rows(
202        &self,
203        start: Bound<&[u8]>,
204        end: Bound<&[u8]>,
205    ) -> BTreeMap<Vec<u8>, Option<Vec<u8>>> {
206        self.staged_range(start, end)
207            .into_iter()
208            .map(|(key, value)| (key.to_vec(), value.map(<[u8]>::to_vec)))
209            .collect()
210    }
211
212    /// Derived rows of the staged writes against the base (lazy, cached until
213    /// the next write).
214    fn derived_rows(&self) -> Result<Arc<BTreeMap<Vec<u8>, Option<Vec<u8>>>>, StorageError> {
215        self.derived
216            .get_or_init(|| {
217                let mut rows = BTreeMap::new();
218                if let Some(deriver) = self.base.deriver() {
219                    if self.has_staged() {
220                        let ops: Vec<Op> = self
221                            .staged_range(Bound::Unbounded, Bound::Unbounded)
222                            .into_iter()
223                            .map(|(key, value)| match value {
224                                Some(value) => Op::Put(key.to_vec(), value.to_vec()),
225                                None => Op::Del(key.to_vec()),
226                            })
227                            .collect();
228                        for op in deriver.derive(0, &ops, &*self.base)? {
229                            match op {
230                                Op::Put(key, value) | Op::Set(key, value) => {
231                                    rows.insert(key, Some(value));
232                                }
233                                Op::Del(key) | Op::Purge(key) => {
234                                    rows.insert(key, None);
235                                }
236                            }
237                        }
238                    }
239                }
240                Ok(Arc::new(rows))
241            })
242            .clone()
243    }
244
245    fn reaches_derived(&self, prefix: &[u8]) -> bool {
246        self.has_staged()
247            && self
248                .base
249                .deriver()
250                .is_some_and(|deriver| deriver.prefix_reaches_derived(prefix))
251    }
252
253    /// The pinned base snapshot underneath this overlay.
254    #[must_use]
255    pub fn base(&self) -> &Arc<ReadView> {
256        &self.base
257    }
258
259    /// Commit index this view reads its committed base at.
260    #[must_use]
261    pub fn commit_index(&self) -> u64 {
262        self.base.commit_index()
263    }
264
265    /// Cancellation handle shared with the base snapshot.
266    #[must_use]
267    pub fn cancellation(&self) -> ReadCancellation {
268        self.base.cancellation()
269    }
270
271    /// Stage a write in the overlay.
272    pub fn put(&mut self, key: Vec<u8>, value: Vec<u8>) {
273        self.derived = std::sync::OnceLock::new();
274        self.buffer.insert(key, Some(value));
275    }
276
277    /// Stage a tombstone in the overlay.
278    pub fn delete(&mut self, key: Vec<u8>) {
279        self.derived = std::sync::OnceLock::new();
280        self.buffer.insert(key, None);
281    }
282
283    /// Fold a batch of LMPG ops into the overlay. History is irrelevant to
284    /// reads, so `Put`/`Set` become live values and `Del`/`Purge` tombstones.
285    pub fn apply_ops(&mut self, ops: &[Op]) {
286        for op in ops {
287            match op {
288                Op::Put(k, v) | Op::Set(k, v) => {
289                    self.buffer.insert(k.clone(), Some(v.clone()));
290                }
291                Op::Del(k) | Op::Purge(k) => {
292                    self.buffer.insert(k.clone(), None);
293                }
294            }
295        }
296        self.derived = std::sync::OnceLock::new();
297    }
298
299    /// Read one key: overlay first, then the pinned base.
300    ///
301    /// # Errors
302    /// Returns the underlying storage error from the base read.
303    pub fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
304        self.with_value(key, |value| value.map(<[u8]>::to_vec))
305    }
306
307    /// Read one key without copying it ([`ReadView::with_value`]): `f` sees
308    /// the staged value (or `None` for a staged delete), a derived row of the
309    /// staged writes, or else the pinned base's value in place.
310    ///
311    /// # Errors
312    /// Returns the underlying storage error from the base read.
313    pub fn with_value<R>(
314        &self,
315        key: &[u8],
316        f: impl FnOnce(Option<&[u8]>) -> R,
317    ) -> Result<R, StorageError> {
318        if let Some(value) = self.staged(key) {
319            return Ok(f(value));
320        }
321        if self.reaches_derived(key)
322            && self
323                .base
324                .deriver()
325                .is_some_and(|deriver| deriver.is_derived_key(key))
326        {
327            if let Some(value) = self.derived_rows()?.get(key) {
328                return Ok(f(value.as_deref()));
329            }
330        }
331        self.base.with_value(key, f)
332    }
333
334    /// Materialize every key/value with `key` starting with `prefix`, merging
335    /// the overlay over the base (overlay wins; tombstones hide base rows).
336    ///
337    /// # Errors
338    /// Returns the underlying storage error from the base scan.
339    pub fn range_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
340        let merged = self.base.range_prefix(prefix)?.into_iter().collect();
341        let upper = crate::prefix_range(prefix).1;
342        let end = if upper.is_empty() {
343            Bound::Unbounded
344        } else {
345            Bound::Excluded(upper.as_slice())
346        };
347        let derived = if self.reaches_derived(prefix) {
348            Some(self.derived_rows()?)
349        } else {
350            None
351        };
352        Ok(self.merge_staged(merged, prefix, end, derived.as_deref()))
353    }
354
355    /// `merged` (base rows) overlaid with the staged writes and then the
356    /// derived rows in `[start, end)`; tombstones remove rows.
357    fn merge_staged(
358        &self,
359        mut merged: BTreeMap<Vec<u8>, Vec<u8>>,
360        start: &[u8],
361        end: Bound<&[u8]>,
362        derived: Option<&BTreeMap<Vec<u8>, Option<Vec<u8>>>>,
363    ) -> Vec<(Vec<u8>, Vec<u8>)> {
364        let start = Bound::Included(start);
365        let derived_rows = derived.into_iter().flat_map(|rows| {
366            rows.range::<[u8], _>((start, end))
367                .map(|(key, value)| (key.as_slice(), value.as_deref()))
368        });
369        for (key, value) in self
370            .staged_range(start, end)
371            .into_iter()
372            .chain(derived_rows)
373        {
374            match value {
375                Some(value) => {
376                    merged.insert(key.to_vec(), value.to_vec());
377                }
378                None => {
379                    merged.remove(key);
380                }
381            }
382        }
383        merged.into_iter().collect()
384    }
385
386    /// Materialize the keys with `start <= key < end`, merging the overlay over
387    /// the base (overlay wins; tombstones hidden). Values are never read, so a
388    /// keys-only scan does not pay for large payloads.
389    ///
390    /// # Errors
391    /// Returns the underlying storage error from the base scan.
392    pub fn range_keys(&self, start: &[u8], end: &[u8]) -> Result<Vec<Vec<u8>>, StorageError> {
393        let bounds = ScanBounds {
394            start: Bound::Included(start.to_vec()),
395            end: if end.is_empty() {
396                Bound::Unbounded
397            } else {
398                Bound::Excluded(end.to_vec())
399            },
400        };
401        let mut keys: BTreeSet<Vec<u8>> = BTreeSet::new();
402        let mut cursor = self.base.scan(bounds, ScanProjection::Keys)?;
403        loop {
404            let batch = cursor.next_batch(1024, 8 * 1024 * 1024)?;
405            for row in batch.rows {
406                keys.insert(row.key);
407            }
408            if batch.done {
409                break;
410            }
411        }
412        let end_bound = if end.is_empty() {
413            Bound::Unbounded
414        } else {
415            Bound::Excluded(end)
416        };
417        for (key, value) in self.staged_range(Bound::Included(start), end_bound) {
418            match value {
419                Some(_) => {
420                    keys.insert(key.to_vec());
421                }
422                None => {
423                    keys.remove(key);
424                }
425            }
426        }
427        Ok(keys.into_iter().collect())
428    }
429
430    /// Materialize every key/value with `start <= key < end`, merging the
431    /// overlay over the base. An empty `end` is unbounded above.
432    ///
433    /// # Errors
434    /// Returns the underlying storage error from the base scan.
435    pub fn range(&self, start: &[u8], end: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
436        let merged = self.base.range(start, end)?.into_iter().collect();
437        let end_bound = if end.is_empty() {
438            Bound::Unbounded
439        } else {
440            Bound::Excluded(end)
441        };
442        let common = start
443            .iter()
444            .zip(end.iter())
445            .take_while(|(a, b)| a == b)
446            .count();
447        let derived = if self.reaches_derived(&start[..common]) {
448            Some(self.derived_rows()?)
449        } else {
450            None
451        };
452        Ok(self.merge_staged(merged, start, end_bound, derived.as_deref()))
453    }
454}