qleany-common 2.0.1

Shared entities, database, events, and undo/redo infrastructure for Qleany
Documentation
// Generated by Qleany v1.11.0 from transactions.tera

use crate::database::db_context::DbContext;
use crate::database::hashmap_store::HashMapStore;
use crate::snapshot::StoreSnapshot;
use anyhow::{Ok, Result, bail};
use std::sync::Arc;

use crate::types;

pub struct Transaction {
    store: Arc<HashMapStore>,
    is_write: bool,
    savepoint: Option<u64>,
}

impl Transaction {
    pub fn begin_write_transaction(db_context: &DbContext) -> Result<Transaction> {
        let store = Arc::clone(db_context.get_store());
        let savepoint = Some(store.create_savepoint());
        Ok(Transaction {
            store,
            is_write: true,
            savepoint,
        })
    }

    pub fn begin_read_transaction(db_context: &DbContext) -> Result<Transaction> {
        Ok(Transaction {
            store: Arc::clone(db_context.get_store()),
            is_write: false,
            savepoint: None,
        })
    }

    /// Begin a read transaction over an **atomic, isolated snapshot** of the
    /// store rather than the live store. Every read through this transaction
    /// sees one consistent point-in-time view; concurrent writes to the live
    /// store do not affect it. Used by long-operation readers (e.g. `save`)
    /// whose multi-table walk runs on a background thread while the UI thread
    /// keeps editing. O(1) — the freeze clones `im::HashMap` handles
    /// (structural sharing), holding all table locks only for that instant.
    pub fn begin_frozen_read_transaction(db_context: &DbContext) -> Result<Transaction> {
        Ok(Transaction {
            store: Arc::new(db_context.get_store().freeze()),
            is_write: false,
            savepoint: None,
        })
    }

    pub fn commit(&mut self) -> Result<()> {
        if !self.is_write {
            bail!("Cannot commit a read transaction");
        }
        // Discard the auto-savepoint — mutations are now permanent
        if let Some(sp) = self.savepoint.take() {
            self.store.discard_savepoint(sp);
        }
        Ok(())
    }

    pub fn rollback(&mut self) -> Result<()> {
        if !self.is_write {
            bail!("Cannot rollback a read transaction");
        }
        // Restore the auto-savepoint — undo all mutations — then discard it so a
        // rolled-back transaction doesn't leak its whole-store snapshot forever.
        if let Some(sp) = self.savepoint.take() {
            self.store.restore_savepoint(sp);
            self.store.discard_savepoint(sp);
        }
        Ok(())
    }

    pub fn end_read_transaction(&mut self) -> Result<()> {
        if self.is_write {
            bail!("Cannot end a write transaction as read");
        }
        Ok(())
    }

    pub fn get_store(&self) -> &HashMapStore {
        &self.store
    }

    pub fn create_savepoint(&self) -> Result<types::Savepoint> {
        if !self.is_write {
            bail!("Cannot create savepoint on a read transaction");
        }
        Ok(self.store.create_savepoint())
    }

    pub fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
        if !self.is_write {
            bail!("Cannot restore savepoint on a read transaction");
        }
        self.store.restore_savepoint(savepoint);
        Ok(())
    }

    /// Snapshot the entire store for undo. O(1) thanks to im::HashMap.
    pub fn snapshot_store(&self) -> StoreSnapshot {
        self.store.store_snapshot()
    }

    /// Restore the entire store from an undo snapshot (preserves counters).
    pub fn restore_store(&self, snap: &StoreSnapshot) {
        self.store.restore_store_snapshot(snap);
    }
}

impl Drop for Transaction {
    fn drop(&mut self) {
        // Safety net: if the transaction was not committed or rolled back,
        // restore the auto-savepoint to undo any partial mutations, then discard
        // it so the snapshot isn't leaked in HashMapStore::savepoints.
        if let Some(sp) = self.savepoint.take() {
            self.store.restore_savepoint(sp);
            self.store.discard_savepoint(sp);
        }
    }
}