embeddenator_fs/fs/versioned/
transaction.rs1use super::types::ChunkId;
7use std::sync::atomic::{AtomicU64, Ordering};
8use std::time::Instant;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TransactionStatus {
13 Pending,
15 Committed,
17 Aborted,
19}
20
21#[derive(Debug, Clone)]
23pub enum Operation {
24 AddChunk { chunk_id: ChunkId, data: Vec<u8> },
26 UpdateChunk { chunk_id: ChunkId, data: Vec<u8> },
28 RemoveChunk { chunk_id: ChunkId },
30 AddFile { path: String, chunks: Vec<ChunkId> },
32 UpdateFile { path: String, chunks: Vec<ChunkId> },
34 RemoveFile { path: String },
36 BundleRoot { chunk_ids: Vec<ChunkId> },
38}
39
40#[derive(Debug, Clone)]
42pub struct Transaction {
43 pub id: u64,
45
46 pub engram_version: u64,
48
49 pub operations: Vec<Operation>,
51
52 pub timestamp: Instant,
54
55 pub status: TransactionStatus,
57}
58
59impl Transaction {
60 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 pub fn add_operation(&mut self, op: Operation) {
73 if self.status == TransactionStatus::Pending {
74 self.operations.push(op);
75 }
76 }
77
78 pub fn commit(&mut self) {
80 if self.status == TransactionStatus::Pending {
81 self.status = TransactionStatus::Committed;
82 }
83 }
84
85 pub fn abort(&mut self) {
87 if self.status == TransactionStatus::Pending {
88 self.status = TransactionStatus::Aborted;
89 }
90 }
91
92 pub fn age(&self) -> std::time::Duration {
94 Instant::now().duration_since(self.timestamp)
95 }
96}
97
98pub struct TransactionManager {
100 next_id: AtomicU64,
101}
102
103impl TransactionManager {
104 pub fn new() -> Self {
106 Self {
107 next_id: AtomicU64::new(0),
108 }
109 }
110
111 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 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}