Skip to main content

rushdb_storage/
transaction_view.rs

1//! Buffered transaction reads and dependency tracking over one storage view.
2
3use super::{
4    prefix_range, redb_backend, Arc, BTreeMap, DependencySet, Included, Op, OverlayView,
5    ReadLimits, ReadView, RefCell, ScanBounds, ScanCursor, ScanProjection, ScanRow, StorageError,
6    Unbounded,
7};
8
9/// A transaction's base read source, checked after its staged `buffer`.
10/// Exactly one variant applies per transaction, chosen at construction —
11/// there is no longer a "live data" field that sits unused (defaulted to a
12/// static empty sentinel) whenever a snapshot/overlay/native base applies
13/// instead.
14pub(super) enum TxnBase<'a> {
15    /// Committed-plus-pending overlay supplied by the sequencer: reads
16    /// resolve through it (its own pending buffer over a committed
17    /// snapshot) instead of any other base, since it is already
18    /// authoritative.
19    Overlay(Arc<OverlayView>),
20    /// Snapshot pinned at BEGIN for a cross-request transaction: all base
21    /// reads resolve at that commit index instead of current state, so the
22    /// transaction sees a stable base plus its own overlay. Autocommit and
23    /// recovery use a different variant and read current committed state
24    /// under the writer admission lock.
25    Snapshot(Arc<ReadView>),
26    /// Native-backend merged view over current committed state.
27    Native(redb_backend::MergedViews),
28    /// The live committed-data map itself, borrowed for the transaction's
29    /// duration (no native backend installed).
30    Live(&'a BTreeMap<Vec<u8>, Vec<u8>>),
31}
32
33impl<'a> TxnBase<'a> {
34    /// Choose a transaction's base in priority order: an overlay or pinned
35    /// snapshot (callers pass at most one of the two — a transaction never
36    /// carries both), else a native merged view, else the live
37    /// committed-data map. `live` must be `Some` whenever none of the other
38    /// three apply — callers only acquire the read-lock guard it derefs
39    /// from when a cheaper base doesn't already apply.
40    ///
41    /// # Panics
42    /// Panics if `live` is `None` while `overlay`, `snapshot`, and `native`
43    /// are all also `None` — every call site arranges for one of the four
44    /// to be available.
45    pub(super) fn resolve(
46        overlay: Option<Arc<OverlayView>>,
47        snapshot: Option<Arc<ReadView>>,
48        native: Option<redb_backend::MergedViews>,
49        live: Option<&'a BTreeMap<Vec<u8>, Vec<u8>>>,
50    ) -> Self {
51        if let Some(overlay) = overlay {
52            return TxnBase::Overlay(overlay);
53        }
54        if let Some(snapshot) = snapshot {
55            return TxnBase::Snapshot(snapshot);
56        }
57        if let Some(native) = native {
58            return TxnBase::Native(native);
59        }
60        TxnBase::Live(live.expect(
61            "a transaction without an overlay, snapshot, or native view always has the live guard",
62        ))
63    }
64}
65
66/// Buffered view handed to admission/apply: reads see committed data merged
67/// with staged writes; writes stay buffered until the closure succeeds.
68pub struct Txn<'a> {
69    pub(super) base: TxnBase<'a>,
70    pub(super) buffer: BTreeMap<Vec<u8>, Option<Vec<u8>>>,
71    /// Keys staged via [`Txn::purge`]: their drained op is [`Op::Purge`]
72    /// (history-erasing) instead of [`Op::Del`] (history-retaining).
73    pub(super) purge_keys: std::collections::BTreeSet<Vec<u8>>,
74    /// Keys staged via [`Txn::set`]: their drained op is [`Op::Set`]
75    /// (history-free overwrite) instead of [`Op::Put`] (history-retaining).
76    pub(super) set_keys: std::collections::BTreeSet<Vec<u8>>,
77    pub(super) deps: Option<&'a RefCell<DependencySet>>,
78}
79
80/// Materialize a bounded slice of a pinned snapshot's range. `max_rows == 0`
81/// means unbounded. Used by transaction reads so their base view is the
82/// transaction snapshot rather than current committed state.
83fn snapshot_scan(
84    snapshot: &ReadView,
85    start: &[u8],
86    end: &[u8],
87    projection: ScanProjection,
88    max_rows: usize,
89) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
90    let bounds = ScanBounds {
91        start: Included(start.to_vec()),
92        end: if end.is_empty() {
93            Unbounded
94        } else {
95            std::ops::Bound::Excluded(end.to_vec())
96        },
97    };
98    let mut cursor = snapshot.scan(bounds, projection)?;
99    let mut rows: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
100    loop {
101        let remaining = if max_rows == 0 {
102            1024
103        } else {
104            max_rows.saturating_sub(rows.len()).clamp(1, 1024)
105        };
106        let batch = cursor.next_batch(remaining, 8 * 1024 * 1024)?;
107        for row in batch.rows {
108            rows.push((row.key, row.value.unwrap_or_default()));
109        }
110        if batch.done || (max_rows != 0 && rows.len() >= max_rows) {
111            break;
112        }
113    }
114    Ok(rows)
115}
116
117/// Lazy key-only iterator over a pinned snapshot's rows in `[start, end)`.
118/// Unlike [`snapshot_scan`] it does not materialize, so a caller can stop after
119/// a bounded number of live keys without walking (or buffering) a whole prefix.
120struct SnapshotKeyIter<'view> {
121    cursor: ScanCursor<'view>,
122    pending: std::vec::IntoIter<ScanRow>,
123    done: bool,
124}
125
126impl<'view> SnapshotKeyIter<'view> {
127    fn new(snapshot: &'view ReadView, start: &[u8], end: &[u8]) -> Result<Self, StorageError> {
128        let bounds = ScanBounds {
129            start: Included(start.to_vec()),
130            end: if end.is_empty() {
131                Unbounded
132            } else {
133                std::ops::Bound::Excluded(end.to_vec())
134            },
135        };
136        Ok(Self {
137            cursor: snapshot.scan(bounds, ScanProjection::Keys)?,
138            pending: Vec::new().into_iter(),
139            done: false,
140        })
141    }
142}
143
144impl Iterator for SnapshotKeyIter<'_> {
145    type Item = Result<Vec<u8>, StorageError>;
146
147    fn next(&mut self) -> Option<Self::Item> {
148        loop {
149            if let Some(row) = self.pending.next() {
150                return Some(Ok(row.key));
151            }
152            if self.done {
153                return None;
154            }
155            match self.cursor.next_batch(1024, 8 * 1024 * 1024) {
156                Ok(batch) => {
157                    self.done = batch.done;
158                    self.pending = batch.rows.into_iter();
159                }
160                Err(error) => {
161                    self.done = true;
162                    return Some(Err(error));
163                }
164            }
165        }
166    }
167}
168
169/// Which side of the merged keys-only iteration a row is drawn from.
170enum Take {
171    /// The next key comes from the base (committed) iterator.
172    Base,
173    /// The next key comes from the staged overlay buffer.
174    Overlay { key: Vec<u8>, live: bool },
175}
176
177impl Txn<'_> {
178    fn track(&self, k: &[u8]) {
179        if let Some(deps) = &self.deps {
180            deps.borrow_mut().record(k.to_vec(), k.to_vec());
181        }
182    }
183
184    /// Read the value staged in this transaction, falling back to the
185    /// underlying committed view.
186    ///
187    /// # Errors
188    /// Returns [`StorageError::ResourceExhausted`] when the key plus value
189    /// exceeds the default read item limit.
190    pub fn get(&self, k: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
191        self.track(k);
192        if let Some(v) = self.buffer.get(k) {
193            ReadLimits::default().check_item(k.len(), v.as_ref().map_or(0, Vec::len))?;
194            return Ok(v.clone());
195        }
196        match &self.base {
197            TxnBase::Overlay(overlay) => {
198                let value = overlay.get(k)?;
199                if let Some(v) = &value {
200                    ReadLimits::default().check_item(k.len(), v.len())?;
201                }
202                Ok(value)
203            }
204            TxnBase::Snapshot(snapshot) => snapshot.get(k),
205            TxnBase::Native(native) => {
206                native.get_current(k, ReadLimits::default(), ScanProjection::KeyValue)
207            }
208            TxnBase::Live(data) => data
209                .get(k)
210                .map(|value| {
211                    ReadLimits::default().check_item(k.len(), value.len())?;
212                    Ok(value.clone())
213                })
214                .transpose(),
215        }
216    }
217
218    /// Read-through helper that also records a point dependency.
219    ///
220    /// # Errors
221    /// Returns [`StorageError::ResourceExhausted`] when the key plus value
222    /// exceeds the default read item limit.
223    pub fn get_tracked(&mut self, k: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
224        self.get(k)
225    }
226
227    /// Collect the committed and staged entries in the half-open byte range
228    /// `[start, end)`; an empty `end` means unbounded above.
229    ///
230    /// # Errors
231    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
232    /// would exceed the 64 MiB transaction-range ceiling.
233    pub fn range(&self, start: &[u8], end: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
234        self.range_projected(start, end, ScanProjection::KeyValue)
235    }
236
237    #[expect(
238        clippy::too_many_lines,
239        reason = "one linear merge of base, overlay and staged rows; splitting it obscures the precedence order"
240    )]
241    fn range_projected(
242        &self,
243        start: &[u8],
244        end: &[u8],
245        projection: ScanProjection,
246    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
247        if let Some(deps) = &self.deps {
248            deps.borrow_mut().record(start.to_vec(), end.to_vec());
249        }
250        if !end.is_empty() && start >= end {
251            return Ok(Vec::new());
252        }
253        let bounds = (
254            Included(start),
255            if end.is_empty() {
256                Unbounded
257            } else {
258                std::ops::Bound::Excluded(end)
259            },
260        );
261        let source: Box<dyn Iterator<Item = Result<(Vec<u8>, Vec<u8>), StorageError>> + '_> =
262            match &self.base {
263                TxnBase::Overlay(overlay) => {
264                    if projection == ScanProjection::Keys {
265                        Box::new(
266                            overlay
267                                .range_keys(start, end)?
268                                .into_iter()
269                                .map(|key| Ok((key, Vec::new()))),
270                        )
271                    } else {
272                        Box::new(overlay.range(start, end)?.into_iter().map(Ok))
273                    }
274                }
275                TxnBase::Snapshot(snapshot) => {
276                    let rows = snapshot_scan(snapshot, start, end, projection, 0)?;
277                    Box::new(rows.into_iter().map(Ok::<_, StorageError>))
278                }
279                TxnBase::Native(native) => {
280                    Box::new(native.current_range(start, end, ReadLimits::default(), projection)?)
281                }
282                TxnBase::Live(data) => Box::new(data.range::<[u8], _>(bounds).map(|(k, v)| {
283                    let v = if projection == ScanProjection::Keys {
284                        &[][..]
285                    } else {
286                        v.as_slice()
287                    };
288                    ReadLimits::default().check_item(k.len(), v.len())?;
289                    Ok((k.clone(), v.to_vec()))
290                })),
291            };
292        let mut base = source.peekable();
293        let mut overlay = self.buffer.range::<[u8], _>(bounds).peekable();
294        let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
295        let mut bytes = 0usize;
296        while base.peek().is_some() || overlay.peek().is_some() {
297            if base.peek().is_some_and(Result::is_err) {
298                return Err(base.next().unwrap().unwrap_err());
299            }
300            let row = match (base.peek(), overlay.peek()) {
301                (Some(Ok((bk, _))), Some((ok, _))) if bk < *ok => Some(base.next().unwrap()?),
302                (Some(Ok((bk, _))), Some((ok, _))) if bk == *ok => {
303                    base.next();
304                    let (k, value) = overlay.next().unwrap();
305                    value
306                        .as_ref()
307                        .map(|v| {
308                            let v = if projection == ScanProjection::Keys {
309                                &[][..]
310                            } else {
311                                v.as_slice()
312                            };
313                            ReadLimits::default().check_item(k.len(), v.len())?;
314                            Ok((k.clone(), v.to_vec()))
315                        })
316                        .transpose()?
317                }
318                (_, Some(_)) => {
319                    let (k, value) = overlay.next().unwrap();
320                    value
321                        .as_ref()
322                        .map(|v| {
323                            let v = if projection == ScanProjection::Keys {
324                                &[][..]
325                            } else {
326                                v.as_slice()
327                            };
328                            ReadLimits::default().check_item(k.len(), v.len())?;
329                            Ok((k.clone(), v.to_vec()))
330                        })
331                        .transpose()?
332                }
333                (Some(_), None) => Some(base.next().unwrap()?),
334                (None, None) => break,
335            };
336            if let Some((k, v)) = row {
337                ReadLimits::default().check_item(k.len(), v.len())?;
338                bytes = bytes.checked_add(k.len() + v.len()).ok_or_else(|| {
339                    StorageError::ResourceExhausted("transaction range size overflow".into())
340                })?;
341                if bytes > 64 * 1024 * 1024 {
342                    return Err(StorageError::ResourceExhausted(
343                        "materialized transaction range exceeds 64 MiB; use a bounded operation"
344                            .into(),
345                    ));
346                }
347                out.push((k, v));
348            }
349        }
350        Ok(out)
351    }
352
353    /// Range-scan helper that records the scanned range dependency.
354    ///
355    /// # Errors
356    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
357    /// would exceed the 64 MiB transaction-range ceiling.
358    pub fn range_prefix_tracked(
359        &mut self,
360        prefix: &[u8],
361    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
362        self.range_prefix(prefix)
363    }
364
365    /// Collect the committed and staged entries under `prefix`.
366    ///
367    /// # Errors
368    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
369    /// would exceed the 64 MiB transaction-range ceiling.
370    pub fn range_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
371        let (s, e) = prefix_range(prefix);
372        let out = self.range_projected(&s, &e, ScanProjection::KeyValue)?;
373        if let Some(deps) = &self.deps {
374            deps.borrow_mut().record_prefix(prefix);
375        }
376        Ok(out)
377    }
378
379    /// Key-only ordered scan merged with staged replacements/tombstones.
380    /// Payloads are not copied or charged as result bytes. The transitional
381    /// 64 MiB materialization ceiling still applies to copied key bytes.
382    ///
383    /// # Errors
384    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
385    /// would exceed the 64 MiB transaction-range ceiling.
386    pub fn keys_prefix(&self, prefix: &[u8]) -> Result<Vec<Vec<u8>>, StorageError> {
387        let (start, end) = prefix_range(prefix);
388        let out = self
389            .range_projected(&start, &end, ScanProjection::Keys)?
390            .into_iter()
391            .map(|(key, _)| key)
392            .collect();
393        if let Some(deps) = &self.deps {
394            deps.borrow_mut().record_prefix(prefix);
395        }
396        Ok(out)
397    }
398
399    /// Bounded keys-only enumeration under a prefix, merged with the staged
400    /// buffer (staged replacements are included, tombstones skipped): at most
401    /// `limit` live keys in ascending order. Unlike [`Txn::keys_prefix`] it
402    /// never applies the 64 MiB materialization ceiling — callers use it to
403    /// make bounded progress over an arbitrarily large keyspace (chunked
404    /// tenant purge) instead of materializing the whole range.
405    ///
406    /// # Errors
407    /// Returns [`StorageError::ResourceExhausted`] when a single key exceeds
408    /// the default read item limit.
409    ///
410    /// # Panics
411    /// Panics only if an internal peeked iterator yields `None` immediately
412    /// after `peek` reported an element, which cannot happen for the merged
413    /// in-memory iterators used here.
414    pub fn keys_prefix_limited(
415        &self,
416        prefix: &[u8],
417        limit: usize,
418    ) -> Result<Vec<Vec<u8>>, StorageError> {
419        if limit == 0 {
420            return Ok(Vec::new());
421        }
422        let (start, end) = prefix_range(prefix);
423        if !end.is_empty() && start >= end {
424            return Ok(Vec::new());
425        }
426        let bounds = (
427            Included(start.as_slice()),
428            if end.is_empty() {
429                Unbounded
430            } else {
431                std::ops::Bound::Excluded(end.as_slice())
432            },
433        );
434        // Staged writes are this transaction's buffer plus, when the sequencer
435        // supplies an overlay, the overlay's pending buffer.
436        let merged;
437        let staged: &BTreeMap<Vec<u8>, Option<Vec<u8>>> =
438            if let TxnBase::Overlay(overlay) = &self.base {
439                let mut map = overlay.buffer().clone();
440                for (key, value) in &self.buffer {
441                    map.insert(key.clone(), value.clone());
442                }
443                merged = map;
444                &merged
445            } else {
446                &self.buffer
447            };
448        let source: Box<dyn Iterator<Item = Result<Vec<u8>, StorageError>> + '_> = match &self.base
449        {
450            TxnBase::Overlay(overlay) => {
451                Box::new(SnapshotKeyIter::new(overlay.base(), &start, &end)?)
452            }
453            TxnBase::Snapshot(snapshot) => Box::new(
454                snapshot_scan(snapshot, &start, &end, ScanProjection::Keys, limit)?
455                    .into_iter()
456                    .map(|(key, _)| key)
457                    .map(Ok::<_, StorageError>),
458            ),
459            TxnBase::Native(native) => Box::new(
460                native
461                    .current_range(&start, &end, ReadLimits::default(), ScanProjection::Keys)?
462                    .map(|row| row.map(|(key, _)| key)),
463            ),
464            TxnBase::Live(data) => Box::new(data.range::<[u8], _>(bounds).map(|(key, _)| {
465                ReadLimits::default().check_item(key.len(), 0)?;
466                Ok(key.clone())
467            })),
468        };
469        let mut base = source.peekable();
470        let mut staged_iter = staged.range::<[u8], _>(bounds).peekable();
471        let mut out = Vec::with_capacity(limit.min(4096));
472        while out.len() < limit && (base.peek().is_some() || staged_iter.peek().is_some()) {
473            if base.peek().is_some_and(Result::is_err) {
474                return Err(base.next().unwrap().unwrap_err());
475            }
476            let take = match (base.peek(), staged_iter.peek()) {
477                (Some(Ok(bk)), Some((ok, _))) if bk < *ok => Take::Base,
478                (Some(Ok(bk)), Some((ok, _))) if bk == *ok => {
479                    base.next();
480                    let entry = staged_iter.next().unwrap();
481                    Take::Overlay {
482                        key: entry.0.clone(),
483                        live: entry.1.is_some(),
484                    }
485                }
486                (_, Some(_)) => {
487                    let entry = staged_iter.next().unwrap();
488                    Take::Overlay {
489                        key: entry.0.clone(),
490                        live: entry.1.is_some(),
491                    }
492                }
493                (Some(_), None) => Take::Base,
494                (None, None) => break,
495            };
496            match take {
497                Take::Base => {
498                    let key = base.next().unwrap()?;
499                    ReadLimits::default().check_item(key.len(), 0)?;
500                    out.push(key);
501                }
502                Take::Overlay { key, live } => {
503                    if live {
504                        ReadLimits::default().check_item(key.len(), 0)?;
505                        out.push(key);
506                    }
507                }
508            }
509        }
510        if let Some(deps) = &self.deps {
511            deps.borrow_mut().record_prefix(prefix);
512        }
513        Ok(out)
514    }
515
516    /// Stage an insert/overwrite of `k`; drains as [`Op::Put`].
517    pub fn put(&mut self, k: Vec<u8>, v: Vec<u8>) {
518        self.buffer.insert(k, Some(v));
519    }
520
521    /// Stage a deletion of `k`; drains as [`Op::Del`].
522    pub fn del(&mut self, k: Vec<u8>) {
523        self.buffer.insert(k, None);
524    }
525
526    /// Stage a history-erasing erase of `k` (tenant purge). Unlike [`Txn::del`]
527    /// the drained op is [`Op::Purge`], so no superseded version is retained
528    /// for a key whose value is intentionally destroyed.
529    pub fn purge(&mut self, k: Vec<u8>) {
530        self.purge_keys.insert(k.clone());
531        self.buffer.insert(k, None);
532    }
533
534    /// Stage a history-free overwrite of `k` (internal bookkeeping keys).
535    /// Unlike [`Txn::put`] the drained op is [`Op::Set`], so overwriting a hot
536    /// counter does not append to its retained history.
537    pub fn set(&mut self, k: Vec<u8>, v: Vec<u8>) {
538        self.set_keys.insert(k.clone());
539        self.buffer.insert(k, Some(v));
540    }
541
542    /// Drain the staged base ops.
543    pub(super) fn drain(self) -> Vec<Op> {
544        let Txn {
545            buffer,
546            purge_keys,
547            set_keys,
548            ..
549        } = self;
550        buffer
551            .into_iter()
552            .map(|(k, v)| match v {
553                Some(v) if set_keys.contains(&k) => Op::Set(k, v),
554                Some(v) => Op::Put(k, v),
555                None if purge_keys.contains(&k) => Op::Purge(k),
556                None => Op::Del(k),
557            })
558            .collect()
559    }
560}