Skip to main content

wasm_dbms/transaction/
session.rs

1// Rust guideline compliant 2026-02-28
2
3//! Transaction session storage.
4//!
5//! Tracks active transactions and their ownership by identity.
6
7use std::collections::HashMap;
8
9use wasm_dbms_api::prelude::{DbmsError, DbmsResult, QueryError, TransactionId};
10
11use super::Transaction;
12
13/// Stores active transactions and their owner identities.
14#[derive(Default, Debug)]
15pub struct TransactionSession {
16    /// Map between transaction IDs and transactions.
17    transactions: HashMap<TransactionId, Transaction>,
18    /// Map between transaction IDs and their owner identity bytes.
19    owners: HashMap<TransactionId, Vec<u8>>,
20    /// Next transaction ID to allocate.
21    next_transaction_id: TransactionId,
22}
23
24impl TransactionSession {
25    /// Begins a new transaction for the given owner identity and returns its ID.
26    pub fn begin_transaction(&mut self, owner: Vec<u8>) -> TransactionId {
27        let transaction_id = self.next_transaction_id;
28        self.next_transaction_id += 1;
29
30        self.transactions
31            .insert(transaction_id, Transaction::default());
32        self.owners.insert(transaction_id, owner);
33
34        transaction_id
35    }
36
37    /// Checks whether a transaction exists and is owned by the given identity.
38    pub fn has_transaction(&self, transaction_id: &TransactionId, caller: &[u8]) -> bool {
39        self.owners
40            .get(transaction_id)
41            .is_some_and(|owner| owner.as_slice() == caller)
42    }
43
44    /// Retrieves a shared reference to the transaction.
45    pub fn get_transaction(&self, transaction_id: &TransactionId) -> DbmsResult<&Transaction> {
46        self.transactions
47            .get(transaction_id)
48            .ok_or(DbmsError::Query(QueryError::TransactionNotFound))
49    }
50
51    /// Removes and returns the transaction (used during commit).
52    pub fn take_transaction(&mut self, transaction_id: &TransactionId) -> DbmsResult<Transaction> {
53        let transaction = self
54            .transactions
55            .remove(transaction_id)
56            .ok_or(DbmsError::Query(QueryError::TransactionNotFound))?;
57        self.owners.remove(transaction_id);
58
59        Ok(transaction)
60    }
61
62    /// Closes (discards) the transaction without returning it.
63    pub fn close_transaction(&mut self, transaction_id: &TransactionId) {
64        self.transactions.remove(transaction_id);
65        self.owners.remove(transaction_id);
66    }
67
68    /// Retrieves a mutable reference to the transaction.
69    pub fn get_transaction_mut(
70        &mut self,
71        transaction_id: &TransactionId,
72    ) -> DbmsResult<&mut Transaction> {
73        self.transactions
74            .get_mut(transaction_id)
75            .ok_or(DbmsError::Query(QueryError::TransactionNotFound))
76    }
77}
78
79#[cfg(test)]
80mod tests {
81
82    use super::*;
83
84    #[test]
85    fn test_should_begin_transaction() {
86        let mut session = TransactionSession::default();
87        let alice = vec![1, 2, 3];
88        let bob = vec![4, 5, 6];
89        let transaction_id = session.begin_transaction(alice.clone());
90
91        assert!(session.has_transaction(&transaction_id, &alice));
92        assert!(!session.has_transaction(&transaction_id, &bob));
93
94        let transaction = session.get_transaction_mut(&transaction_id);
95        assert!(transaction.is_ok());
96    }
97
98    #[test]
99    fn test_should_close_transaction() {
100        let mut session = TransactionSession::default();
101        let alice = vec![1, 2, 3];
102        let transaction_id = session.begin_transaction(alice.clone());
103
104        assert!(session.has_transaction(&transaction_id, &alice));
105
106        session.close_transaction(&transaction_id);
107
108        assert!(!session.has_transaction(&transaction_id, &alice));
109        let transaction = session.get_transaction_mut(&transaction_id);
110        assert!(transaction.is_err());
111        assert!(!session.owners.contains_key(&transaction_id));
112        assert!(!session.transactions.contains_key(&transaction_id));
113    }
114
115    #[test]
116    fn test_should_take_transaction() {
117        let mut session = TransactionSession::default();
118        let alice = vec![1, 2, 3];
119        let transaction_id = session.begin_transaction(alice.clone());
120
121        let _transaction = session
122            .take_transaction(&transaction_id)
123            .expect("failed to take tx");
124
125        assert!(!session.has_transaction(&transaction_id, &alice));
126        let transaction_after_take = session.get_transaction(&transaction_id);
127        assert!(transaction_after_take.is_err());
128        assert!(!session.owners.contains_key(&transaction_id));
129        assert!(!session.transactions.contains_key(&transaction_id));
130    }
131
132    #[test]
133    fn test_should_get_transaction() {
134        let mut session = TransactionSession::default();
135        let alice = vec![1, 2, 3];
136        let transaction_id = session.begin_transaction(alice);
137
138        let _tx = session
139            .get_transaction(&transaction_id)
140            .expect("failed to get tx");
141    }
142}