highlandcows_isam/
transaction.rs

1/// `Transaction<'a, K, V>` — a bounded execution context for ISAM operations.
2///
3/// Holds the `MutexGuard` for its entire lifetime (serializable isolation) and
4/// an undo log so that a rollback (explicit or via `Drop`) can restore the
5/// index to the state it had before the transaction began.
6use std::sync::MutexGuard;
7
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11use crate::error::IsamResult;
12use crate::storage::IsamStorage;
13use crate::store::RecordRef;
14
15// ── Undo log entry ───────────────────────────────────────────────────────── //
16
17pub(crate) enum UndoEntry<K, V> {
18    /// Undo an insert by deleting the key from the primary index and secondary indices.
19    Insert { key: K, value: V },
20    /// Undo an update by restoring the old RecordRef and reversing secondary index changes.
21    Update { key: K, old_rec: RecordRef, old_value: V, new_value: V },
22    /// Undo a delete by re-inserting the key with its old RecordRef and secondary indices.
23    Delete { key: K, old_rec: RecordRef, value: V },
24}
25
26// ── Transaction ──────────────────────────────────────────────────────────── //
27
28pub struct Transaction<'a, K, V>
29where
30    K: Serialize + DeserializeOwned + Ord + Clone,
31    V: Serialize + DeserializeOwned,
32{
33    guard: MutexGuard<'a, IsamStorage<K, V>>,
34    undo_log: Vec<UndoEntry<K, V>>,
35    committed: bool,
36}
37
38impl<'a, K, V> Transaction<'a, K, V>
39where
40    K: Serialize + DeserializeOwned + Ord + Clone,
41    V: Serialize + DeserializeOwned,
42{
43    pub(crate) fn new(guard: MutexGuard<'a, IsamStorage<K, V>>) -> Self {
44        Self {
45            guard,
46            undo_log: Vec::new(),
47            committed: false,
48        }
49    }
50
51    // ── pub(crate) interface consumed by Isam ──────────────────────────── //
52
53    pub(crate) fn storage_mut(&mut self) -> &mut IsamStorage<K, V> {
54        &mut self.guard
55    }
56
57    pub(crate) fn log_insert(&mut self, key: K, value: V) {
58        self.undo_log.push(UndoEntry::Insert { key, value });
59    }
60
61    pub(crate) fn log_update(&mut self, key: K, old_rec: RecordRef, old_value: V, new_value: V) {
62        self.undo_log.push(UndoEntry::Update { key, old_rec, old_value, new_value });
63    }
64
65    pub(crate) fn log_delete(&mut self, key: K, old_rec: RecordRef, value: V) {
66        self.undo_log.push(UndoEntry::Delete { key, old_rec, value });
67    }
68
69    // ── Public interface consumed by callers ───────────────────────────── //
70
71    /// Flush store and index to disk (`fsync`) and release the database lock.
72    ///
73    /// After `commit` returns the changes are durable and the lock is released,
74    /// allowing other threads to begin their own transactions.
75    ///
76    /// # Example
77    /// ```
78    /// # use tempfile::TempDir;
79    /// # use highlandcows_isam::Isam;
80    /// # let dir = TempDir::new().unwrap();
81    /// # let path = dir.path().join("db");
82    /// # let db: Isam<u32, String> = Isam::create(&path).unwrap();
83    /// let mut txn = db.begin_transaction().unwrap();
84    /// db.insert(&mut txn, 1u32, &"hello".to_string()).unwrap();
85    /// txn.commit().unwrap();
86    ///
87    /// // Data is now durable — visible after reopen.
88    /// let db2: Isam<u32, String> = Isam::open(&path).unwrap();
89    /// let mut txn2 = db2.begin_transaction().unwrap();
90    /// assert_eq!(db2.get(&mut txn2, &1u32).unwrap(), Some("hello".to_string()));
91    /// txn2.commit().unwrap();
92    /// ```
93    pub fn commit(mut self) -> IsamResult<()> {
94        self.guard.fsync()?;
95        self.committed = true;
96        Ok(())
97        // MutexGuard drops here, releasing the lock
98    }
99
100    /// Roll back all changes made in this transaction and release the lock.
101    ///
102    /// The undo log is applied in reverse order, restoring the index to the
103    /// state it had before the transaction began.  Dropping a `Transaction`
104    /// without calling `commit` has the same effect automatically.
105    ///
106    /// # Example
107    /// ```
108    /// # use tempfile::TempDir;
109    /// # use highlandcows_isam::Isam;
110    /// # let dir = TempDir::new().unwrap();
111    /// # let path = dir.path().join("db");
112    /// # let db: Isam<u32, String> = Isam::create(&path).unwrap();
113    /// let mut txn = db.begin_transaction().unwrap();
114    /// db.insert(&mut txn, 1u32, &"oops".to_string()).unwrap();
115    /// txn.rollback().unwrap();
116    ///
117    /// // Insert was rolled back — key is absent.
118    /// let mut txn2 = db.begin_transaction().unwrap();
119    /// assert_eq!(db.get(&mut txn2, &1u32).unwrap(), None);
120    /// txn2.commit().unwrap();
121    /// ```
122    pub fn rollback(mut self) -> IsamResult<()> {
123        self.do_rollback()?;
124        self.committed = true;
125        Ok(())
126    }
127
128    // ── Private helpers ────────────────────────────────────────────────── //
129
130    fn do_rollback(&mut self) -> IsamResult<()> {
131        // Apply in reverse order
132        while let Some(entry) = self.undo_log.pop() {
133            match entry {
134                UndoEntry::Insert { key, value } => {
135                    for si in &mut self.guard.secondary_indices {
136                        let _ = si.undo_insert(&key, &value);
137                    }
138                    let _ = self.guard.index.delete(&key);
139                }
140                UndoEntry::Update { key, old_rec, old_value, new_value } => {
141                    for si in &mut self.guard.secondary_indices {
142                        let _ = si.undo_update(&key, &old_value, &new_value);
143                    }
144                    let _ = self.guard.index.update(&key, old_rec);
145                }
146                UndoEntry::Delete { key, old_rec, value } => {
147                    for si in &mut self.guard.secondary_indices {
148                        let _ = si.undo_delete(&key, &value);
149                    }
150                    let _ = self.guard.index.insert(&key, old_rec);
151                }
152            }
153        }
154        Ok(())
155    }
156}
157
158impl<'a, K, V> Drop for Transaction<'a, K, V>
159where
160    K: Serialize + DeserializeOwned + Ord + Clone,
161    V: Serialize + DeserializeOwned,
162{
163    fn drop(&mut self) {
164        if !self.committed {
165            let _ = self.do_rollback();
166        }
167    }
168}