Skip to main content

embeddenator_fs/fs/versioned/
transaction.rs

1//! Transaction support for atomic multi-operation updates
2//!
3//! Provides ACID-like properties for complex operations that span multiple
4//! components (codebook, manifest, corrections).
5
6use super::types::ChunkId;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::Instant;
9
10/// Transaction status
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TransactionStatus {
13    /// Transaction is in progress
14    Pending,
15    /// Transaction completed successfully
16    Committed,
17    /// Transaction was rolled back
18    Aborted,
19}
20
21/// Operations that can be performed in a transaction
22#[derive(Debug, Clone)]
23pub enum Operation {
24    /// Add a new chunk to the codebook
25    AddChunk { chunk_id: ChunkId, data: Vec<u8> },
26    /// Update an existing chunk
27    UpdateChunk { chunk_id: ChunkId, data: Vec<u8> },
28    /// Remove a chunk
29    RemoveChunk { chunk_id: ChunkId },
30    /// Add a new file to the manifest
31    AddFile { path: String, chunks: Vec<ChunkId> },
32    /// Update an existing file
33    UpdateFile { path: String, chunks: Vec<ChunkId> },
34    /// Remove a file
35    RemoveFile { path: String },
36    /// Bundle chunks into root
37    BundleRoot { chunk_ids: Vec<ChunkId> },
38}
39
40/// A transaction
41#[derive(Debug, Clone)]
42pub struct Transaction {
43    /// Unique transaction ID
44    pub id: u64,
45
46    /// Engram version at transaction start
47    pub engram_version: u64,
48
49    /// Operations in this transaction
50    pub operations: Vec<Operation>,
51
52    /// When the transaction started
53    pub timestamp: Instant,
54
55    /// Current status
56    pub status: TransactionStatus,
57}
58
59impl Transaction {
60    /// Create a new pending transaction
61    pub fn new(id: u64, engram_version: u64) -> Self {
62        Self {
63            id,
64            engram_version,
65            operations: Vec::new(),
66            timestamp: Instant::now(),
67            status: TransactionStatus::Pending,
68        }
69    }
70
71    /// Add an operation to the transaction
72    pub fn add_operation(&mut self, op: Operation) {
73        if self.status == TransactionStatus::Pending {
74            self.operations.push(op);
75        }
76    }
77
78    /// Mark the transaction as committed
79    pub fn commit(&mut self) {
80        if self.status == TransactionStatus::Pending {
81            self.status = TransactionStatus::Committed;
82        }
83    }
84
85    /// Mark the transaction as aborted
86    pub fn abort(&mut self) {
87        if self.status == TransactionStatus::Pending {
88            self.status = TransactionStatus::Aborted;
89        }
90    }
91
92    /// Get the age of the transaction
93    pub fn age(&self) -> std::time::Duration {
94        Instant::now().duration_since(self.timestamp)
95    }
96}
97
98/// Transaction manager
99pub struct TransactionManager {
100    next_id: AtomicU64,
101}
102
103impl TransactionManager {
104    /// Create a new transaction manager
105    pub fn new() -> Self {
106        Self {
107            next_id: AtomicU64::new(0),
108        }
109    }
110
111    /// Begin a new transaction
112    pub fn begin(&self, engram_version: u64) -> Transaction {
113        let id = self.next_id.fetch_add(1, Ordering::AcqRel);
114        Transaction::new(id, engram_version)
115    }
116}
117
118impl Default for TransactionManager {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn test_transaction_creation() {
130        let tx = Transaction::new(1, 0);
131        assert_eq!(tx.id, 1);
132        assert_eq!(tx.engram_version, 0);
133        assert_eq!(tx.status, TransactionStatus::Pending);
134        assert!(tx.operations.is_empty());
135    }
136
137    #[test]
138    fn test_transaction_operations() {
139        let mut tx = Transaction::new(1, 0);
140
141        tx.add_operation(Operation::AddChunk {
142            chunk_id: 1,
143            data: vec![1, 2, 3],
144        });
145
146        assert_eq!(tx.operations.len(), 1);
147    }
148
149    #[test]
150    fn test_transaction_commit() {
151        let mut tx = Transaction::new(1, 0);
152        tx.commit();
153        assert_eq!(tx.status, TransactionStatus::Committed);
154
155        // Can't add operations after commit
156        tx.add_operation(Operation::AddChunk {
157            chunk_id: 1,
158            data: vec![],
159        });
160        assert_eq!(tx.operations.len(), 0);
161    }
162
163    #[test]
164    fn test_transaction_manager() {
165        let manager = TransactionManager::new();
166
167        let tx1 = manager.begin(0);
168        let tx2 = manager.begin(1);
169
170        assert_eq!(tx1.id, 0);
171        assert_eq!(tx2.id, 1);
172    }
173}