Skip to main content

common/database/
transactions.rs

1// Generated by Qleany v1.11.0 from transactions.tera
2
3use crate::database::db_context::DbContext;
4use crate::database::hashmap_store::HashMapStore;
5use crate::snapshot::StoreSnapshot;
6use anyhow::{Ok, Result, bail};
7use std::sync::Arc;
8
9use crate::types;
10
11pub struct Transaction {
12    store: Arc<HashMapStore>,
13    is_write: bool,
14    savepoint: Option<u64>,
15}
16
17impl Transaction {
18    pub fn begin_write_transaction(db_context: &DbContext) -> Result<Transaction> {
19        let store = Arc::clone(db_context.get_store());
20        let savepoint = Some(store.create_savepoint());
21        Ok(Transaction {
22            store,
23            is_write: true,
24            savepoint,
25        })
26    }
27
28    pub fn begin_read_transaction(db_context: &DbContext) -> Result<Transaction> {
29        Ok(Transaction {
30            store: Arc::clone(db_context.get_store()),
31            is_write: false,
32            savepoint: None,
33        })
34    }
35
36    /// Begin a read transaction over an **atomic, isolated snapshot** of the
37    /// store rather than the live store. Every read through this transaction
38    /// sees one consistent point-in-time view; concurrent writes to the live
39    /// store do not affect it. Used by long-operation readers (e.g. `save`)
40    /// whose multi-table walk runs on a background thread while the UI thread
41    /// keeps editing. O(1) — the freeze clones `im::HashMap` handles
42    /// (structural sharing), holding all table locks only for that instant.
43    pub fn begin_frozen_read_transaction(db_context: &DbContext) -> Result<Transaction> {
44        Ok(Transaction {
45            store: Arc::new(db_context.get_store().freeze()),
46            is_write: false,
47            savepoint: None,
48        })
49    }
50
51    pub fn commit(&mut self) -> Result<()> {
52        if !self.is_write {
53            bail!("Cannot commit a read transaction");
54        }
55        // Discard the auto-savepoint — mutations are now permanent
56        if let Some(sp) = self.savepoint.take() {
57            self.store.discard_savepoint(sp);
58        }
59        Ok(())
60    }
61
62    pub fn rollback(&mut self) -> Result<()> {
63        if !self.is_write {
64            bail!("Cannot rollback a read transaction");
65        }
66        // Restore the auto-savepoint — undo all mutations — then discard it so a
67        // rolled-back transaction doesn't leak its whole-store snapshot forever.
68        if let Some(sp) = self.savepoint.take() {
69            self.store.restore_savepoint(sp);
70            self.store.discard_savepoint(sp);
71        }
72        Ok(())
73    }
74
75    pub fn end_read_transaction(&mut self) -> Result<()> {
76        if self.is_write {
77            bail!("Cannot end a write transaction as read");
78        }
79        Ok(())
80    }
81
82    pub fn get_store(&self) -> &HashMapStore {
83        &self.store
84    }
85
86    pub fn create_savepoint(&self) -> Result<types::Savepoint> {
87        if !self.is_write {
88            bail!("Cannot create savepoint on a read transaction");
89        }
90        Ok(self.store.create_savepoint())
91    }
92
93    pub fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
94        if !self.is_write {
95            bail!("Cannot restore savepoint on a read transaction");
96        }
97        self.store.restore_savepoint(savepoint);
98        Ok(())
99    }
100
101    /// Snapshot the entire store for undo. O(1) thanks to im::HashMap.
102    pub fn snapshot_store(&self) -> StoreSnapshot {
103        self.store.store_snapshot()
104    }
105
106    /// Restore the entire store from an undo snapshot (preserves counters).
107    pub fn restore_store(&self, snap: &StoreSnapshot) {
108        self.store.restore_store_snapshot(snap);
109    }
110}
111
112impl Drop for Transaction {
113    fn drop(&mut self) {
114        // Safety net: if the transaction was not committed or rolled back,
115        // restore the auto-savepoint to undo any partial mutations, then discard
116        // it so the snapshot isn't leaked in HashMapStore::savepoints.
117        if let Some(sp) = self.savepoint.take() {
118            self.store.restore_savepoint(sp);
119            self.store.discard_savepoint(sp);
120        }
121    }
122}