Skip to main content

akar_storage/
undo_buffer.rs

1//! Undo Buffer — records old data before writes for transaction rollback.
2//!
3//! Each write transaction accumulates `UndoRecord`s in its `UndoBuffer`.
4//! On rollback, the records are applied in reverse order to restore
5//! the pre-write state. On commit, the buffer is cleared.
6//!
7//! Ported from C++ `src/storage/undo_buffer.cpp`.
8
9use akar_common::error::StorageError;
10use akar_transaction::UndoRecord;
11
12/// Accumulates undo records for a single write transaction.
13#[derive(Debug, Default)]
14pub struct UndoBuffer {
15    records: Vec<UndoRecord>,
16}
17
18impl UndoBuffer {
19    pub fn new() -> Self {
20        Self { records: Vec::new() }
21    }
22
23    /// Record the old value of a cell before it is overwritten.
24    pub fn record(&mut self, table_id: u64, row_id: u64, column: u32, old_data: Vec<u8>) {
25        self.records
26            .push(UndoRecord::update(table_id, row_id, column, old_data));
27    }
28
29    /// Number of undo records accumulated.
30    pub fn len(&self) -> usize {
31        self.records.len()
32    }
33
34    /// Whether the buffer is empty.
35    pub fn is_empty(&self) -> bool {
36        self.records.is_empty()
37    }
38
39    /// Clear all records (called on successful commit).
40    pub fn clear(&mut self) {
41        self.records.clear();
42    }
43
44    /// Drain all records out of the buffer (consuming).
45    /// Used when the caller needs to take ownership of the records
46    /// for rollback application.
47    pub fn drain(&mut self) -> Vec<UndoRecord> {
48        std::mem::take(&mut self.records)
49    }
50
51    /// Apply all undo records in reverse order.
52    ///
53    /// The `apply_fn` callback receives each undo record and should
54    /// write `old_data` back to the appropriate table/row/column.
55    /// Records are applied in **reverse** order (LIFO) so that the
56    /// last write is undone first — preserving intermediate states.
57    pub fn rollback<F>(&mut self, mut apply_fn: F) -> Result<(), StorageError>
58    where
59        F: FnMut(&UndoRecord) -> Result<(), StorageError>,
60    {
61        for record in self.records.iter().rev() {
62            apply_fn(record)?;
63        }
64        self.clear();
65        Ok(())
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_empty_buffer() {
75        let buf = UndoBuffer::new();
76        assert!(buf.is_empty());
77        assert_eq!(buf.len(), 0);
78    }
79
80    #[test]
81    fn test_record_and_drain() {
82        let mut buf = UndoBuffer::new();
83        buf.record(1, 100, 0, vec![1, 2, 3]);
84        buf.record(1, 200, 1, vec![4, 5, 6]);
85        assert_eq!(buf.len(), 2);
86
87        let drained = buf.drain();
88        assert_eq!(drained.len(), 2);
89        assert!(buf.is_empty());
90    }
91
92    #[test]
93    fn test_rollback_applies_reverse_order() {
94        let mut buf = UndoBuffer::new();
95        buf.record(1, 100, 0, vec![1]); // first write
96        buf.record(1, 100, 0, vec![2]); // second write (overwrites first)
97
98        let mut applied = Vec::new();
99        buf.rollback(|rec| {
100            applied.push(rec.old_data[0]);
101            Ok(())
102        })
103        .unwrap();
104
105        // Rollback applies in reverse: last write undone first
106        assert_eq!(applied, vec![2, 1]);
107    }
108
109    #[test]
110    fn test_rollback_clears_buffer() {
111        let mut buf = UndoBuffer::new();
112        buf.record(1, 100, 0, vec![1]);
113
114        buf.rollback(|_| Ok(())).unwrap();
115        assert!(buf.is_empty());
116    }
117}