reinhardt_db/orm/two_phase_commit/
transaction_log.rs1use super::{TransactionState, TwoPhaseError};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::{Arc, Mutex};
10
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
13pub struct TransactionLogEntry {
14 pub transaction_id: String,
16 pub state: TransactionState,
18 pub participants: Vec<String>,
20 pub timestamp: chrono::DateTime<chrono::Utc>,
22 pub metadata: HashMap<String, String>,
24}
25
26impl TransactionLogEntry {
27 pub fn new(
29 transaction_id: impl Into<String>,
30 state: TransactionState,
31 participants: Vec<String>,
32 ) -> Self {
33 Self {
34 transaction_id: transaction_id.into(),
35 state,
36 participants,
37 timestamp: chrono::Utc::now(),
38 metadata: HashMap::new(),
39 }
40 }
41
42 pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
44 self.metadata.insert(key.into(), value.into());
45 self
46 }
47}
48
49pub trait TransactionLog: Send + Sync {
51 fn write(&self, entry: &TransactionLogEntry) -> Result<(), TwoPhaseError>;
53
54 fn read(&self, transaction_id: &str) -> Result<Option<TransactionLogEntry>, TwoPhaseError>;
56
57 fn read_all(&self) -> Result<Vec<TransactionLogEntry>, TwoPhaseError>;
59
60 fn delete(&self, transaction_id: &str) -> Result<(), TwoPhaseError>;
62
63 fn find_by_state(
65 &self,
66 state: TransactionState,
67 ) -> Result<Vec<TransactionLogEntry>, TwoPhaseError>;
68}
69
70#[derive(Debug, Clone)]
74pub struct InMemoryTransactionLog {
75 entries: Arc<Mutex<HashMap<String, TransactionLogEntry>>>,
76}
77
78impl InMemoryTransactionLog {
79 pub fn new() -> Self {
81 Self {
82 entries: Arc::new(Mutex::new(HashMap::new())),
83 }
84 }
85}
86
87impl Default for InMemoryTransactionLog {
88 fn default() -> Self {
89 Self::new()
90 }
91}
92
93impl TransactionLog for InMemoryTransactionLog {
94 fn write(&self, entry: &TransactionLogEntry) -> Result<(), TwoPhaseError> {
95 let mut entries = self
96 .entries
97 .lock()
98 .map_err(|_| TwoPhaseError::LogError("Failed to acquire lock".to_string()))?;
99 entries.insert(entry.transaction_id.clone(), entry.clone());
100 Ok(())
101 }
102
103 fn read(&self, transaction_id: &str) -> Result<Option<TransactionLogEntry>, TwoPhaseError> {
104 let entries = self
105 .entries
106 .lock()
107 .map_err(|_| TwoPhaseError::LogError("Failed to acquire lock".to_string()))?;
108 Ok(entries.get(transaction_id).cloned())
109 }
110
111 fn read_all(&self) -> Result<Vec<TransactionLogEntry>, TwoPhaseError> {
112 let entries = self
113 .entries
114 .lock()
115 .map_err(|_| TwoPhaseError::LogError("Failed to acquire lock".to_string()))?;
116 Ok(entries.values().cloned().collect())
117 }
118
119 fn delete(&self, transaction_id: &str) -> Result<(), TwoPhaseError> {
120 let mut entries = self
121 .entries
122 .lock()
123 .map_err(|_| TwoPhaseError::LogError("Failed to acquire lock".to_string()))?;
124 entries.remove(transaction_id);
125 Ok(())
126 }
127
128 fn find_by_state(
129 &self,
130 state: TransactionState,
131 ) -> Result<Vec<TransactionLogEntry>, TwoPhaseError> {
132 let entries = self
133 .entries
134 .lock()
135 .map_err(|_| TwoPhaseError::LogError("Failed to acquire lock".to_string()))?;
136 Ok(entries
137 .values()
138 .filter(|e| e.state == state)
139 .cloned()
140 .collect())
141 }
142}
143
144#[derive(Debug)]
148pub struct FileTransactionLog {
149 log_dir: PathBuf,
150}
151
152impl FileTransactionLog {
153 pub fn new(log_dir: PathBuf) -> Result<Self, TwoPhaseError> {
164 if !log_dir.exists() {
166 std::fs::create_dir_all(&log_dir).map_err(|e| {
167 TwoPhaseError::LogError(format!("Failed to create log directory: {}", e))
168 })?;
169 }
170
171 Ok(Self { log_dir })
172 }
173
174 fn entry_path(&self, transaction_id: &str) -> PathBuf {
176 self.log_dir.join(format!("{}.json", transaction_id))
177 }
178}
179
180impl TransactionLog for FileTransactionLog {
181 fn write(&self, entry: &TransactionLogEntry) -> Result<(), TwoPhaseError> {
182 let path = self.entry_path(&entry.transaction_id);
183 let json = serde_json::to_string_pretty(entry)
184 .map_err(|e| TwoPhaseError::LogError(format!("Serialization error: {}", e)))?;
185
186 std::fs::write(&path, json)
187 .map_err(|e| TwoPhaseError::LogError(format!("Failed to write log file: {}", e)))?;
188
189 Ok(())
190 }
191
192 fn read(&self, transaction_id: &str) -> Result<Option<TransactionLogEntry>, TwoPhaseError> {
193 let path = self.entry_path(transaction_id);
194
195 if !path.exists() {
196 return Ok(None);
197 }
198
199 let json = std::fs::read_to_string(&path)
200 .map_err(|e| TwoPhaseError::LogError(format!("Failed to read log file: {}", e)))?;
201
202 let entry: TransactionLogEntry = serde_json::from_str(&json)
203 .map_err(|e| TwoPhaseError::LogError(format!("Deserialization error: {}", e)))?;
204
205 Ok(Some(entry))
206 }
207
208 fn read_all(&self) -> Result<Vec<TransactionLogEntry>, TwoPhaseError> {
209 let mut entries = Vec::new();
210
211 let dir_entries = std::fs::read_dir(&self.log_dir)
212 .map_err(|e| TwoPhaseError::LogError(format!("Failed to read log directory: {}", e)))?;
213
214 for entry_result in dir_entries {
215 let entry = entry_result.map_err(|e| {
216 TwoPhaseError::LogError(format!("Failed to read directory entry: {}", e))
217 })?;
218
219 let path = entry.path();
220 if path.extension().and_then(|s| s.to_str()) == Some("json") {
221 let json = std::fs::read_to_string(&path).map_err(|e| {
222 TwoPhaseError::LogError(format!("Failed to read log file: {}", e))
223 })?;
224
225 let log_entry: TransactionLogEntry = serde_json::from_str(&json).map_err(|e| {
226 TwoPhaseError::LogError(format!("Deserialization error: {}", e))
227 })?;
228
229 entries.push(log_entry);
230 }
231 }
232
233 Ok(entries)
234 }
235
236 fn delete(&self, transaction_id: &str) -> Result<(), TwoPhaseError> {
237 let path = self.entry_path(transaction_id);
238
239 if path.exists() {
240 std::fs::remove_file(&path).map_err(|e| {
241 TwoPhaseError::LogError(format!("Failed to delete log file: {}", e))
242 })?;
243 }
244
245 Ok(())
246 }
247
248 fn find_by_state(
249 &self,
250 state: TransactionState,
251 ) -> Result<Vec<TransactionLogEntry>, TwoPhaseError> {
252 let all_entries = self.read_all()?;
253 Ok(all_entries
254 .into_iter()
255 .filter(|e| e.state == state)
256 .collect())
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn test_log_entry_creation() {
266 let entry = TransactionLogEntry::new(
267 "txn_001",
268 TransactionState::Active,
269 vec!["db1".to_string(), "db2".to_string()],
270 );
271
272 assert_eq!(entry.transaction_id, "txn_001");
273 assert_eq!(entry.state, TransactionState::Active);
274 assert_eq!(entry.participants.len(), 2);
275 assert!(entry.metadata.is_empty());
276 }
277
278 #[test]
279 fn test_log_entry_with_metadata() {
280 let entry = TransactionLogEntry::new("txn_002", TransactionState::Prepared, vec![])
281 .with_metadata("user", "alice")
282 .with_metadata("operation", "transfer");
283
284 assert_eq!(entry.metadata.get("user"), Some(&"alice".to_string()));
285 assert_eq!(
286 entry.metadata.get("operation"),
287 Some(&"transfer".to_string())
288 );
289 }
290
291 #[test]
292 fn test_in_memory_log_write_read() {
293 let log = InMemoryTransactionLog::new();
294 let entry =
295 TransactionLogEntry::new("txn_003", TransactionState::Active, vec!["db1".to_string()]);
296
297 log.write(&entry).unwrap();
298 let read_entry = log.read("txn_003").unwrap();
299
300 assert_eq!(read_entry, Some(entry));
301 }
302
303 #[test]
304 fn test_in_memory_log_delete() {
305 let log = InMemoryTransactionLog::new();
306 let entry = TransactionLogEntry::new("txn_004", TransactionState::Active, vec![]);
307
308 log.write(&entry).unwrap();
309 assert!(log.read("txn_004").unwrap().is_some());
310
311 log.delete("txn_004").unwrap();
312 assert!(log.read("txn_004").unwrap().is_none());
313 }
314
315 #[test]
316 fn test_in_memory_log_find_by_state() {
317 let log = InMemoryTransactionLog::new();
318
319 log.write(&TransactionLogEntry::new(
320 "txn_005",
321 TransactionState::Active,
322 vec![],
323 ))
324 .unwrap();
325 log.write(&TransactionLogEntry::new(
326 "txn_006",
327 TransactionState::Prepared,
328 vec![],
329 ))
330 .unwrap();
331 log.write(&TransactionLogEntry::new(
332 "txn_007",
333 TransactionState::Prepared,
334 vec![],
335 ))
336 .unwrap();
337
338 let prepared = log.find_by_state(TransactionState::Prepared).unwrap();
339 assert_eq!(prepared.len(), 2);
340 }
341
342 #[test]
343 fn test_in_memory_log_read_all() {
344 let log = InMemoryTransactionLog::new();
345
346 log.write(&TransactionLogEntry::new(
347 "txn_008",
348 TransactionState::Active,
349 vec![],
350 ))
351 .unwrap();
352 log.write(&TransactionLogEntry::new(
353 "txn_009",
354 TransactionState::Prepared,
355 vec![],
356 ))
357 .unwrap();
358
359 let all = log.read_all().unwrap();
360 assert_eq!(all.len(), 2);
361 }
362
363 #[test]
364 fn test_file_log_write_read() {
365 let temp_dir = std::env::temp_dir().join("reinhardt_2pc_test");
366 let _ = std::fs::remove_dir_all(&temp_dir); let log = FileTransactionLog::new(temp_dir.clone()).unwrap();
369 let entry = TransactionLogEntry::new(
370 "txn_file_001",
371 TransactionState::Active,
372 vec!["db1".to_string()],
373 );
374
375 log.write(&entry).unwrap();
376 let read_entry = log.read("txn_file_001").unwrap();
377
378 assert_eq!(
379 read_entry.as_ref().map(|e| &e.transaction_id),
380 Some(&"txn_file_001".to_string())
381 );
382
383 std::fs::remove_dir_all(&temp_dir).unwrap();
385 }
386
387 #[test]
388 fn test_file_log_delete() {
389 let temp_dir = std::env::temp_dir().join("reinhardt_2pc_test_delete");
390 let _ = std::fs::remove_dir_all(&temp_dir);
391
392 let log = FileTransactionLog::new(temp_dir.clone()).unwrap();
393 let entry = TransactionLogEntry::new("txn_file_002", TransactionState::Active, vec![]);
394
395 log.write(&entry).unwrap();
396 assert!(log.read("txn_file_002").unwrap().is_some());
397
398 log.delete("txn_file_002").unwrap();
399 assert!(log.read("txn_file_002").unwrap().is_none());
400
401 std::fs::remove_dir_all(&temp_dir).unwrap();
403 }
404
405 #[test]
406 fn test_file_log_find_by_state() {
407 let temp_dir = std::env::temp_dir().join("reinhardt_2pc_test_state");
408 let _ = std::fs::remove_dir_all(&temp_dir);
409
410 let log = FileTransactionLog::new(temp_dir.clone()).unwrap();
411
412 log.write(&TransactionLogEntry::new(
413 "txn_file_003",
414 TransactionState::Active,
415 vec![],
416 ))
417 .unwrap();
418 log.write(&TransactionLogEntry::new(
419 "txn_file_004",
420 TransactionState::Prepared,
421 vec![],
422 ))
423 .unwrap();
424 log.write(&TransactionLogEntry::new(
425 "txn_file_005",
426 TransactionState::Prepared,
427 vec![],
428 ))
429 .unwrap();
430
431 let prepared = log.find_by_state(TransactionState::Prepared).unwrap();
432 assert_eq!(prepared.len(), 2);
433
434 std::fs::remove_dir_all(&temp_dir).unwrap();
436 }
437}