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, IdAllocator, Included, Op,
5    OverlayView, ReadLimits, ReadView, RefCell, ScanBounds, ScanCursor, ScanProjection, ScanRow,
6    StorageError, 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    /// Keys staged via [`Txn::set_watermark`]: monotone counters, which a
78    /// transaction neither depends on nor conflicts on.
79    pub(super) watermark_keys: std::collections::BTreeSet<Vec<u8>>,
80    pub(super) deps: Option<&'a RefCell<DependencySet>>,
81    /// The store's id reservations, for [`Txn::allocate`].
82    pub(super) ids: &'a IdAllocator,
83}
84
85/// Materialize a bounded slice of a pinned snapshot's range. `max_rows == 0`
86/// means unbounded. Used by transaction reads so their base view is the
87/// transaction snapshot rather than current committed state.
88fn snapshot_scan(
89    snapshot: &ReadView,
90    start: &[u8],
91    end: &[u8],
92    projection: ScanProjection,
93    max_rows: usize,
94) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
95    let bounds = ScanBounds {
96        start: Included(start.to_vec()),
97        end: if end.is_empty() {
98            Unbounded
99        } else {
100            std::ops::Bound::Excluded(end.to_vec())
101        },
102    };
103    let mut cursor = snapshot.scan(bounds, projection)?;
104    let mut rows: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
105    loop {
106        let remaining = if max_rows == 0 {
107            1024
108        } else {
109            max_rows.saturating_sub(rows.len()).clamp(1, 1024)
110        };
111        let batch = cursor.next_batch(remaining, 8 * 1024 * 1024)?;
112        for row in batch.rows {
113            rows.push((row.key, row.value.unwrap_or_default()));
114        }
115        if batch.done || (max_rows != 0 && rows.len() >= max_rows) {
116            break;
117        }
118    }
119    Ok(rows)
120}
121
122/// Lazy key-only iterator over a pinned snapshot's rows in `[start, end)`.
123/// Unlike [`snapshot_scan`] it does not materialize, so a caller can stop after
124/// a bounded number of live keys without walking (or buffering) a whole prefix.
125struct SnapshotKeyIter<'view> {
126    cursor: ScanCursor<'view>,
127    pending: std::vec::IntoIter<ScanRow>,
128    done: bool,
129}
130
131impl<'view> SnapshotKeyIter<'view> {
132    fn new(snapshot: &'view ReadView, start: &[u8], end: &[u8]) -> Result<Self, StorageError> {
133        let bounds = ScanBounds {
134            start: Included(start.to_vec()),
135            end: if end.is_empty() {
136                Unbounded
137            } else {
138                std::ops::Bound::Excluded(end.to_vec())
139            },
140        };
141        Ok(Self {
142            cursor: snapshot.scan(bounds, ScanProjection::Keys)?,
143            pending: Vec::new().into_iter(),
144            done: false,
145        })
146    }
147}
148
149impl Iterator for SnapshotKeyIter<'_> {
150    type Item = Result<Vec<u8>, StorageError>;
151
152    fn next(&mut self) -> Option<Self::Item> {
153        loop {
154            if let Some(row) = self.pending.next() {
155                return Some(Ok(row.key));
156            }
157            if self.done {
158                return None;
159            }
160            match self.cursor.next_batch(1024, 8 * 1024 * 1024) {
161                Ok(batch) => {
162                    self.done = batch.done;
163                    self.pending = batch.rows.into_iter();
164                }
165                Err(error) => {
166                    self.done = true;
167                    return Some(Err(error));
168                }
169            }
170        }
171    }
172}
173
174/// Which side of the merged keys-only iteration a row is drawn from.
175enum Take {
176    /// The next key comes from the base (committed) iterator.
177    Base,
178    /// The next key comes from the staged overlay buffer.
179    Overlay { key: Vec<u8>, live: bool },
180}
181
182impl Txn<'_> {
183    fn track(&self, k: &[u8]) {
184        if let Some(deps) = &self.deps {
185            deps.borrow_mut().record(k.to_vec(), k.to_vec());
186        }
187    }
188
189    /// Read the value staged in this transaction, falling back to the
190    /// underlying committed view.
191    ///
192    /// # Errors
193    /// Returns [`StorageError::ResourceExhausted`] when the key plus value
194    /// exceeds the default read item limit.
195    pub fn get(&self, k: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
196        self.track(k);
197        self.get_untracked(k)
198    }
199
200    /// Reserve the next id of the monotone counter at `key` (an ordinal, a
201    /// property family or definition). Ids are unique across concurrent
202    /// transactions; the counter is not a read dependency. See
203    /// [`IdAllocator`].
204    ///
205    /// # Errors
206    /// Returns [`StorageError::Builder`] ("invalid u64 metadata") if the
207    /// stored counter is not a big-endian `u64`, or the reservation error of [`IdAllocator::reserve`].
208    pub fn allocate(&self, key: &[u8]) -> Result<u64, StorageError> {
209        let watermark = self.watermark(key)?;
210        self.ids.reserve(key, watermark)
211    }
212
213    /// Advance the monotone counter at `key` to at least `next` (a
214    /// big-endian `u64`, history-free). It is read untracked and is exempt
215    /// from a transaction's write conflicts: concurrent commits each raise it
216    /// to `max(current, next)`, so their order does not matter. Commit-time
217    /// expansion reads current state, so the counter never moves back.
218    ///
219    /// # Errors
220    /// Returns [`StorageError::Builder`] ("invalid u64 metadata") if the
221    /// stored counter is not a big-endian `u64`.
222    pub fn set_watermark(&mut self, key: Vec<u8>, next: u64) -> Result<(), StorageError> {
223        let value = self.watermark(&key)?.max(next);
224        self.watermark_keys.insert(key.clone());
225        self.set(key, value.to_be_bytes().to_vec());
226        Ok(())
227    }
228
229    fn watermark(&self, key: &[u8]) -> Result<u64, StorageError> {
230        self.get_untracked(key)?
231            .map(|raw| {
232                <[u8; 8]>::try_from(raw.as_slice())
233                    .map(u64::from_be_bytes)
234                    .map_err(|_| StorageError::Builder("invalid u64 metadata".into()))
235            })
236            .transpose()
237            .map(Option::unwrap_or_default)
238    }
239
240    fn get_untracked(&self, k: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
241        if let Some(v) = self.buffer.get(k) {
242            ReadLimits::default().check_item(k.len(), v.as_ref().map_or(0, Vec::len))?;
243            return Ok(v.clone());
244        }
245        match &self.base {
246            TxnBase::Overlay(overlay) => {
247                let value = overlay.get(k)?;
248                if let Some(v) = &value {
249                    ReadLimits::default().check_item(k.len(), v.len())?;
250                }
251                Ok(value)
252            }
253            TxnBase::Snapshot(snapshot) => snapshot.get(k),
254            TxnBase::Native(native) => {
255                native.get_current(k, ReadLimits::default(), ScanProjection::KeyValue)
256            }
257            TxnBase::Live(data) => data
258                .get(k)
259                .map(|value| {
260                    ReadLimits::default().check_item(k.len(), value.len())?;
261                    Ok(value.clone())
262                })
263                .transpose(),
264        }
265    }
266
267    /// Read-through helper that also records a point dependency.
268    ///
269    /// # Errors
270    /// Returns [`StorageError::ResourceExhausted`] when the key plus value
271    /// exceeds the default read item limit.
272    pub fn get_tracked(&mut self, k: &[u8]) -> Result<Option<Vec<u8>>, StorageError> {
273        self.get(k)
274    }
275
276    /// Collect the committed and staged entries in the half-open byte range
277    /// `[start, end)`; an empty `end` means unbounded above.
278    ///
279    /// # Errors
280    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
281    /// would exceed the 64 MiB transaction-range ceiling.
282    pub fn range(&self, start: &[u8], end: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
283        self.range_projected(start, end, ScanProjection::KeyValue)
284    }
285
286    #[expect(
287        clippy::too_many_lines,
288        reason = "one linear merge of base, overlay and staged rows; splitting it obscures the precedence order"
289    )]
290    fn range_projected(
291        &self,
292        start: &[u8],
293        end: &[u8],
294        projection: ScanProjection,
295    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
296        if let Some(deps) = &self.deps {
297            deps.borrow_mut().record(start.to_vec(), end.to_vec());
298        }
299        if !end.is_empty() && start >= end {
300            return Ok(Vec::new());
301        }
302        let bounds = (
303            Included(start),
304            if end.is_empty() {
305                Unbounded
306            } else {
307                std::ops::Bound::Excluded(end)
308            },
309        );
310        let source: Box<dyn Iterator<Item = Result<(Vec<u8>, Vec<u8>), StorageError>> + '_> =
311            match &self.base {
312                TxnBase::Overlay(overlay) => {
313                    if projection == ScanProjection::Keys {
314                        Box::new(
315                            overlay
316                                .range_keys(start, end)?
317                                .into_iter()
318                                .map(|key| Ok((key, Vec::new()))),
319                        )
320                    } else {
321                        Box::new(overlay.range(start, end)?.into_iter().map(Ok))
322                    }
323                }
324                TxnBase::Snapshot(snapshot) => {
325                    let rows = snapshot_scan(snapshot, start, end, projection, 0)?;
326                    Box::new(rows.into_iter().map(Ok::<_, StorageError>))
327                }
328                TxnBase::Native(native) => {
329                    Box::new(native.current_range(start, end, ReadLimits::default(), projection)?)
330                }
331                TxnBase::Live(data) => Box::new(data.range::<[u8], _>(bounds).map(|(k, v)| {
332                    let v = if projection == ScanProjection::Keys {
333                        &[][..]
334                    } else {
335                        v.as_slice()
336                    };
337                    ReadLimits::default().check_item(k.len(), v.len())?;
338                    Ok((k.clone(), v.to_vec()))
339                })),
340            };
341        let mut base = source.peekable();
342        let mut overlay = self.buffer.range::<[u8], _>(bounds).peekable();
343        let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
344        let mut bytes = 0usize;
345        while base.peek().is_some() || overlay.peek().is_some() {
346            if base.peek().is_some_and(Result::is_err) {
347                return Err(base.next().unwrap().unwrap_err());
348            }
349            let row = match (base.peek(), overlay.peek()) {
350                (Some(Ok((bk, _))), Some((ok, _))) if bk < *ok => Some(base.next().unwrap()?),
351                (Some(Ok((bk, _))), Some((ok, _))) if bk == *ok => {
352                    base.next();
353                    let (k, value) = overlay.next().unwrap();
354                    value
355                        .as_ref()
356                        .map(|v| {
357                            let v = if projection == ScanProjection::Keys {
358                                &[][..]
359                            } else {
360                                v.as_slice()
361                            };
362                            ReadLimits::default().check_item(k.len(), v.len())?;
363                            Ok((k.clone(), v.to_vec()))
364                        })
365                        .transpose()?
366                }
367                (_, Some(_)) => {
368                    let (k, value) = overlay.next().unwrap();
369                    value
370                        .as_ref()
371                        .map(|v| {
372                            let v = if projection == ScanProjection::Keys {
373                                &[][..]
374                            } else {
375                                v.as_slice()
376                            };
377                            ReadLimits::default().check_item(k.len(), v.len())?;
378                            Ok((k.clone(), v.to_vec()))
379                        })
380                        .transpose()?
381                }
382                (Some(_), None) => Some(base.next().unwrap()?),
383                (None, None) => break,
384            };
385            if let Some((k, v)) = row {
386                ReadLimits::default().check_item(k.len(), v.len())?;
387                bytes = bytes.checked_add(k.len() + v.len()).ok_or_else(|| {
388                    StorageError::ResourceExhausted("transaction range size overflow".into())
389                })?;
390                if bytes > 64 * 1024 * 1024 {
391                    return Err(StorageError::ResourceExhausted(
392                        "materialized transaction range exceeds 64 MiB; use a bounded operation"
393                            .into(),
394                    ));
395                }
396                out.push((k, v));
397            }
398        }
399        Ok(out)
400    }
401
402    /// Range-scan helper that records the scanned range dependency.
403    ///
404    /// # Errors
405    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
406    /// would exceed the 64 MiB transaction-range ceiling.
407    pub fn range_prefix_tracked(
408        &mut self,
409        prefix: &[u8],
410    ) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
411        self.range_prefix(prefix)
412    }
413
414    /// Collect the committed and staged entries under `prefix`.
415    ///
416    /// # Errors
417    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
418    /// would exceed the 64 MiB transaction-range ceiling.
419    pub fn range_prefix(&self, prefix: &[u8]) -> Result<Vec<(Vec<u8>, Vec<u8>)>, StorageError> {
420        let (s, e) = prefix_range(prefix);
421        let out = self.range_projected(&s, &e, ScanProjection::KeyValue)?;
422        if let Some(deps) = &self.deps {
423            deps.borrow_mut().record_prefix(prefix);
424        }
425        Ok(out)
426    }
427
428    /// Key-only ordered scan merged with staged replacements/tombstones.
429    /// Payloads are not copied or charged as result bytes. The transitional
430    /// 64 MiB materialization ceiling still applies to copied key bytes.
431    ///
432    /// # Errors
433    /// Returns [`StorageError::ResourceExhausted`] if the materialized result
434    /// would exceed the 64 MiB transaction-range ceiling.
435    pub fn keys_prefix(&self, prefix: &[u8]) -> Result<Vec<Vec<u8>>, StorageError> {
436        let (start, end) = prefix_range(prefix);
437        let out = self
438            .range_projected(&start, &end, ScanProjection::Keys)?
439            .into_iter()
440            .map(|(key, _)| key)
441            .collect();
442        if let Some(deps) = &self.deps {
443            deps.borrow_mut().record_prefix(prefix);
444        }
445        Ok(out)
446    }
447
448    /// Bounded keys-only enumeration under a prefix, merged with the staged
449    /// buffer (staged replacements are included, tombstones skipped): at most
450    /// `limit` live keys in ascending order. Unlike [`Txn::keys_prefix`] it
451    /// never applies the 64 MiB materialization ceiling — callers use it to
452    /// make bounded progress over an arbitrarily large keyspace (chunked
453    /// tenant purge) instead of materializing the whole range.
454    ///
455    /// # Errors
456    /// Returns [`StorageError::ResourceExhausted`] when a single key exceeds
457    /// the default read item limit.
458    ///
459    /// # Panics
460    /// Panics only if an internal peeked iterator yields `None` immediately
461    /// after `peek` reported an element, which cannot happen for the merged
462    /// in-memory iterators used here.
463    pub fn keys_prefix_limited(
464        &self,
465        prefix: &[u8],
466        limit: usize,
467    ) -> Result<Vec<Vec<u8>>, StorageError> {
468        if limit == 0 {
469            return Ok(Vec::new());
470        }
471        let (start, end) = prefix_range(prefix);
472        if !end.is_empty() && start >= end {
473            return Ok(Vec::new());
474        }
475        let bounds = (
476            Included(start.as_slice()),
477            if end.is_empty() {
478                Unbounded
479            } else {
480                std::ops::Bound::Excluded(end.as_slice())
481            },
482        );
483        // Staged writes are this transaction's buffer plus, when the sequencer
484        // supplies an overlay, the overlay's pending buffer.
485        let merged;
486        let staged: &BTreeMap<Vec<u8>, Option<Vec<u8>>> =
487            if let TxnBase::Overlay(overlay) = &self.base {
488                let mut map = overlay.buffer().clone();
489                for (key, value) in &self.buffer {
490                    map.insert(key.clone(), value.clone());
491                }
492                merged = map;
493                &merged
494            } else {
495                &self.buffer
496            };
497        let source: Box<dyn Iterator<Item = Result<Vec<u8>, StorageError>> + '_> = match &self.base
498        {
499            TxnBase::Overlay(overlay) => {
500                Box::new(SnapshotKeyIter::new(overlay.base(), &start, &end)?)
501            }
502            TxnBase::Snapshot(snapshot) => Box::new(
503                snapshot_scan(snapshot, &start, &end, ScanProjection::Keys, limit)?
504                    .into_iter()
505                    .map(|(key, _)| key)
506                    .map(Ok::<_, StorageError>),
507            ),
508            TxnBase::Native(native) => Box::new(
509                native
510                    .current_range(&start, &end, ReadLimits::default(), ScanProjection::Keys)?
511                    .map(|row| row.map(|(key, _)| key)),
512            ),
513            TxnBase::Live(data) => Box::new(data.range::<[u8], _>(bounds).map(|(key, _)| {
514                ReadLimits::default().check_item(key.len(), 0)?;
515                Ok(key.clone())
516            })),
517        };
518        let mut base = source.peekable();
519        let mut staged_iter = staged.range::<[u8], _>(bounds).peekable();
520        let mut out = Vec::with_capacity(limit.min(4096));
521        while out.len() < limit && (base.peek().is_some() || staged_iter.peek().is_some()) {
522            if base.peek().is_some_and(Result::is_err) {
523                return Err(base.next().unwrap().unwrap_err());
524            }
525            let take = match (base.peek(), staged_iter.peek()) {
526                (Some(Ok(bk)), Some((ok, _))) if bk < *ok => Take::Base,
527                (Some(Ok(bk)), Some((ok, _))) if bk == *ok => {
528                    base.next();
529                    let entry = staged_iter.next().unwrap();
530                    Take::Overlay {
531                        key: entry.0.clone(),
532                        live: entry.1.is_some(),
533                    }
534                }
535                (_, Some(_)) => {
536                    let entry = staged_iter.next().unwrap();
537                    Take::Overlay {
538                        key: entry.0.clone(),
539                        live: entry.1.is_some(),
540                    }
541                }
542                (Some(_), None) => Take::Base,
543                (None, None) => break,
544            };
545            match take {
546                Take::Base => {
547                    let key = base.next().unwrap()?;
548                    ReadLimits::default().check_item(key.len(), 0)?;
549                    out.push(key);
550                }
551                Take::Overlay { key, live } => {
552                    if live {
553                        ReadLimits::default().check_item(key.len(), 0)?;
554                        out.push(key);
555                    }
556                }
557            }
558        }
559        if let Some(deps) = &self.deps {
560            deps.borrow_mut().record_prefix(prefix);
561        }
562        Ok(out)
563    }
564
565    /// Stage an insert/overwrite of `k`; drains as [`Op::Put`].
566    pub fn put(&mut self, k: Vec<u8>, v: Vec<u8>) {
567        self.buffer.insert(k, Some(v));
568    }
569
570    /// Stage a deletion of `k`; drains as [`Op::Del`].
571    pub fn del(&mut self, k: Vec<u8>) {
572        self.buffer.insert(k, None);
573    }
574
575    /// Stage a history-erasing erase of `k` (tenant purge). Unlike [`Txn::del`]
576    /// the drained op is [`Op::Purge`], so no superseded version is retained
577    /// for a key whose value is intentionally destroyed.
578    pub fn purge(&mut self, k: Vec<u8>) {
579        self.purge_keys.insert(k.clone());
580        self.buffer.insert(k, None);
581    }
582
583    /// Stage a history-free overwrite of `k` (internal bookkeeping keys).
584    /// Unlike [`Txn::put`] the drained op is [`Op::Set`], so overwriting a hot
585    /// counter does not append to its retained history.
586    pub fn set(&mut self, k: Vec<u8>, v: Vec<u8>) {
587        self.set_keys.insert(k.clone());
588        self.buffer.insert(k, Some(v));
589    }
590
591    /// Drain the staged base ops.
592    pub(super) fn drain(self) -> Vec<Op> {
593        let Txn {
594            buffer,
595            purge_keys,
596            set_keys,
597            ..
598        } = self;
599        buffer
600            .into_iter()
601            .map(|(k, v)| match v {
602                Some(v) if set_keys.contains(&k) => Op::Set(k, v),
603                Some(v) => Op::Put(k, v),
604                None if purge_keys.contains(&k) => Op::Purge(k),
605                None => Op::Del(k),
606            })
607            .collect()
608    }
609}