Skip to main content

feltdb/
transaction_api.rs

1/// Transaction API Layer
2///
3/// Public-facing transaction management API that integrates:
4/// - TransactionExecutor for execution logic
5/// - OperationLog for operation persistence
6/// - StateTransitionStore for transition durability
7/// - Precondition validation
8/// - Read/write dependency tracking
9use crate::convergence::VectorClock;
10use crate::operation_log::{MemoryOperationLog, OperationLogEntry};
11use crate::state_transition_store::{MemoryStateTransitionStore, StateTransitionRecord};
12use crate::transactions::*;
13use std::sync::{Arc, Mutex};
14
15/// Result type for transaction API operations
16pub type TxResult<T> = Result<T, TxError>;
17
18/// Transaction API errors
19#[derive(Clone, Debug)]
20pub enum TxError {
21    PreconditionFailed(String),
22    ConflictDetected(String),
23    OperationDeduplication(String),
24    InvalidState(String),
25    PersistenceFailure(String),
26}
27
28impl std::fmt::Display for TxError {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            TxError::PreconditionFailed(msg) => write!(f, "Precondition failed: {}", msg),
32            TxError::ConflictDetected(msg) => write!(f, "Conflict detected: {}", msg),
33            TxError::OperationDeduplication(msg) => {
34                write!(f, "Operation already executed: {}", msg)
35            }
36            TxError::InvalidState(msg) => write!(f, "Invalid state: {}", msg),
37            TxError::PersistenceFailure(msg) => write!(f, "Persistence failure: {}", msg),
38        }
39    }
40}
41
42impl std::error::Error for TxError {}
43
44/// Transaction manager combining execution, persistence, and validation
45pub struct TransactionManager {
46    node_id: String,
47    executor: TransactionExecutor,
48    operation_log: MemoryOperationLog,
49    transition_store: MemoryStateTransitionStore,
50    current_version: Arc<Mutex<StateVersion>>,
51}
52
53impl TransactionManager {
54    /// Create a new transaction manager
55    pub fn new(node_id: String, initial_version: StateVersion) -> Self {
56        Self {
57            node_id: node_id.clone(),
58            executor: TransactionExecutor::new(node_id),
59            operation_log: MemoryOperationLog::new(),
60            transition_store: MemoryStateTransitionStore::new(),
61            current_version: Arc::new(Mutex::new(initial_version)),
62        }
63    }
64
65    /// Get the current state version
66    pub fn current_state_version(&self) -> StateVersion {
67        self.current_version.lock().unwrap().clone()
68    }
69
70    /// Execute a transaction with full persistence integration
71    pub fn execute_and_commit(
72        &mut self,
73        transaction_id: String,
74        mut operations: Vec<Operation>,
75        consistency_contract: ConsistencyContract,
76    ) -> TxResult<StateTransitionRecord> {
77        // 1. Validate all operations are new (not duplicated in operation log)
78        for op in &operations {
79            if self.operation_log.contains_operation_id(&op.operation_id) {
80                return Err(TxError::OperationDeduplication(format!(
81                    "Operation {} already executed",
82                    format!("{}:{}", op.operation_id.node_id, op.operation_id.sequence)
83                )));
84            }
85        }
86
87        // 2. Set transaction_id on all operations
88        for op in &mut operations {
89            op.transaction_id = Some(transaction_id.clone());
90        }
91
92        // 3. Execute transaction
93        let current_version = self.current_version.lock().unwrap().clone();
94        let transition = self.executor.execute_transaction(
95            transaction_id.clone(),
96            current_version,
97            operations.clone(),
98            consistency_contract,
99        );
100
101        // 4. Validate transition succeeded
102        if !transition.is_successful() {
103            return Err(TxError::InvalidState(
104                "Transaction execution failed".to_string(),
105            ));
106        }
107
108        // 5. Persist operations to operation log
109        for op in &transition.operations {
110            self.operation_log
111                .append(op.clone())
112                .map_err(|e| TxError::PersistenceFailure(e))?;
113        }
114
115        // 6. Persist transition to state transition store
116        let store_index = self
117            .transition_store
118            .record_transition(transition.clone())
119            .map_err(|e| TxError::PersistenceFailure(e))?;
120
121        // 7. Update current version
122        {
123            let mut current = self.current_version.lock().unwrap();
124            *current = transition.to_version.clone();
125        }
126
127        // 8. Retrieve the recorded transition
128        let record = self
129            .transition_store
130            .get_by_index(store_index)
131            .ok_or_else(|| {
132                TxError::PersistenceFailure("Failed to retrieve recorded transition".to_string())
133            })?;
134
135        Ok(record)
136    }
137
138    /// Get a recorded operation by ID
139    pub fn get_operation(&self, operation_id: &OperationId) -> Option<OperationLogEntry> {
140        self.operation_log.get_by_id(operation_id)
141    }
142
143    /// Get a recorded transition by transaction ID
144    pub fn get_transition(&self, transaction_id: &str) -> Option<StateTransitionRecord> {
145        self.transition_store.get_by_transaction_id(transaction_id)
146    }
147
148    /// Get the operation log size
149    pub fn operation_log_size(&self) -> u64 {
150        self.operation_log.size()
151    }
152
153    /// Get the transition store size
154    pub fn transition_store_size(&self) -> u64 {
155        self.transition_store.size()
156    }
157
158    /// Rebuild state from operation log (for recovery)
159    /// Executes all operations in order to verify state integrity
160    pub fn rebuild_from_operation_log(&mut self) -> TxResult<StateVersion> {
161        let all_ops = self.operation_log.iter();
162
163        if all_ops.is_empty() {
164            return Ok(self.current_state_version());
165        }
166
167        let mut current_version = StateVersion::new(VectorClock::new(), "genesis".to_string());
168
169        for entry in all_ops {
170            let transition = self.executor.execute_transaction(
171                entry.operation.transaction_id.clone().unwrap_or_default(),
172                current_version.clone(),
173                vec![entry.operation.clone()],
174                ConsistencyContract::local(),
175            );
176
177            if !transition.is_successful() {
178                return Err(TxError::InvalidState(
179                    "Failed to rebuild state from operation log".to_string(),
180                ));
181            }
182
183            current_version = transition.to_version;
184        }
185
186        {
187            let mut current = self.current_version.lock().unwrap();
188            *current = current_version.clone();
189        }
190
191        Ok(current_version)
192    }
193
194    /// Get the chain of state transitions from a starting version
195    pub fn get_transition_chain_from(&self, from_state_hash: &str) -> Vec<StateTransitionRecord> {
196        self.transition_store.get_transition_chain(from_state_hash)
197    }
198
199    /// Validate preconditions for an operation
200    /// (Simple string matching - production would have actual condition evaluation)
201    pub fn validate_preconditions(&self, operation: &Operation) -> TxResult<()> {
202        if operation.preconditions.is_empty() {
203            return Ok(());
204        }
205
206        // Placeholder: In production, would evaluate actual state conditions
207        // For now, just verify preconditions are syntactically valid
208        for (condition_name, condition) in &operation.preconditions {
209            if condition.is_empty() {
210                return Err(TxError::PreconditionFailed(format!(
211                    "Empty condition: {}",
212                    condition_name
213                )));
214            }
215        }
216
217        Ok(())
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use super::*;
224    use crate::transactions::OperationCommand;
225    use std::collections::HashMap;
226
227    #[test]
228    fn test_create_transaction_manager() {
229        let mut vc = VectorClock::new();
230        vc.increment("node1");
231        let version = StateVersion::new(vc, "hash0".to_string());
232
233        let manager = TransactionManager::new("node1".to_string(), version);
234
235        assert_eq!(manager.operation_log_size(), 0);
236        assert_eq!(manager.transition_store_size(), 0);
237    }
238
239    #[test]
240    fn test_execute_and_commit_single_operation() {
241        let mut vc = VectorClock::new();
242        vc.increment("node1");
243        let version = StateVersion::new(vc, "hash0".to_string());
244
245        let mut manager = TransactionManager::new("node1".to_string(), version);
246
247        let op = Operation::new(
248            OperationId::new("node1".to_string(), 1),
249            manager.current_state_version(),
250            "Update".to_string(),
251            OperationCommand {
252                op_type: "UpdateField".to_string(),
253                collection: "test".to_string(),
254                record_id: "rec1".to_string(),
255                fields: {
256                    let mut m = HashMap::new();
257                    m.insert("field".to_string(), serde_json::json!("value"));
258                    m
259                },
260            },
261            "user".to_string(),
262        );
263
264        let result =
265            manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
266
267        assert!(result.is_ok());
268        assert_eq!(manager.operation_log_size(), 1);
269        assert_eq!(manager.transition_store_size(), 1);
270    }
271
272    #[test]
273    fn test_duplicate_operation_rejection() {
274        let mut vc = VectorClock::new();
275        vc.increment("node1");
276        let version = StateVersion::new(vc, "hash0".to_string());
277
278        let mut manager = TransactionManager::new("node1".to_string(), version);
279
280        let op = Operation::new(
281            OperationId::new("node1".to_string(), 1),
282            manager.current_state_version(),
283            "Update".to_string(),
284            OperationCommand {
285                op_type: "UpdateField".to_string(),
286                collection: "test".to_string(),
287                record_id: "rec1".to_string(),
288                fields: {
289                    let mut m = HashMap::new();
290                    m.insert("field".to_string(), serde_json::json!("value"));
291                    m
292                },
293            },
294            "user".to_string(),
295        );
296
297        let _ = manager.execute_and_commit(
298            "txn1".to_string(),
299            vec![op.clone()],
300            ConsistencyContract::local(),
301        );
302
303        let result =
304            manager.execute_and_commit("txn2".to_string(), vec![op], ConsistencyContract::local());
305
306        assert!(result.is_err());
307        assert!(result.unwrap_err().to_string().contains("already executed"));
308    }
309
310    #[test]
311    fn test_get_operation_after_commit() {
312        let mut vc = VectorClock::new();
313        vc.increment("node1");
314        let version = StateVersion::new(vc, "hash0".to_string());
315
316        let mut manager = TransactionManager::new("node1".to_string(), version);
317
318        let op_id = OperationId::new("node1".to_string(), 1);
319        let op = Operation::new(
320            op_id.clone(),
321            manager.current_state_version(),
322            "Update".to_string(),
323            OperationCommand {
324                op_type: "UpdateField".to_string(),
325                collection: "test".to_string(),
326                record_id: "rec1".to_string(),
327                fields: {
328                    let mut m = HashMap::new();
329                    m.insert("field".to_string(), serde_json::json!("value"));
330                    m
331                },
332            },
333            "user".to_string(),
334        );
335
336        let _ =
337            manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
338
339        let retrieved = manager.get_operation(&op_id);
340        assert!(retrieved.is_some());
341    }
342
343    #[test]
344    fn test_get_transition_after_commit() {
345        let mut vc = VectorClock::new();
346        vc.increment("node1");
347        let version = StateVersion::new(vc, "hash0".to_string());
348
349        let mut manager = TransactionManager::new("node1".to_string(), version);
350
351        let op = Operation::new(
352            OperationId::new("node1".to_string(), 1),
353            manager.current_state_version(),
354            "Update".to_string(),
355            OperationCommand {
356                op_type: "UpdateField".to_string(),
357                collection: "test".to_string(),
358                record_id: "rec1".to_string(),
359                fields: {
360                    let mut m = HashMap::new();
361                    m.insert("field".to_string(), serde_json::json!("value"));
362                    m
363                },
364            },
365            "user".to_string(),
366        );
367
368        let _ =
369            manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
370
371        let transition = manager.get_transition("txn1");
372        assert!(transition.is_some());
373    }
374
375    #[test]
376    fn test_precondition_validation() {
377        let mut vc = VectorClock::new();
378        vc.increment("node1");
379        let version = StateVersion::new(vc, "hash0".to_string());
380
381        let manager = TransactionManager::new("node1".to_string(), version);
382
383        let mut op = Operation::new(
384            OperationId::new("node1".to_string(), 1),
385            manager.current_state_version(),
386            "Update".to_string(),
387            OperationCommand {
388                op_type: "UpdateField".to_string(),
389                collection: "test".to_string(),
390                record_id: "rec1".to_string(),
391                fields: {
392                    let mut m = HashMap::new();
393                    m.insert("field".to_string(), serde_json::json!("value"));
394                    m
395                },
396            },
397            "user".to_string(),
398        );
399
400        op = op.with_preconditions(vec![("count".to_string(), "count >= 2".to_string())]);
401
402        let result = manager.validate_preconditions(&op);
403        assert!(result.is_ok());
404
405        let mut bad_op = Operation::new(
406            OperationId::new("node1".to_string(), 2),
407            manager.current_state_version(),
408            "Update".to_string(),
409            OperationCommand {
410                op_type: "UpdateField".to_string(),
411                collection: "test".to_string(),
412                record_id: "rec2".to_string(),
413                fields: {
414                    let mut m = HashMap::new();
415                    m.insert("field".to_string(), serde_json::json!("value"));
416                    m
417                },
418            },
419            "user".to_string(),
420        );
421
422        bad_op = bad_op.with_preconditions(vec![("invalid".to_string(), "".to_string())]);
423
424        let bad_result = manager.validate_preconditions(&bad_op);
425        assert!(bad_result.is_err());
426    }
427
428    #[test]
429    fn test_current_state_version_tracking() {
430        let mut vc = VectorClock::new();
431        vc.increment("node1");
432        let initial_version = StateVersion::new(vc, "hash0".to_string());
433        let initial_hash = initial_version.state_hash.clone();
434
435        let mut manager = TransactionManager::new("node1".to_string(), initial_version);
436
437        assert_eq!(manager.current_state_version().state_hash, initial_hash);
438
439        let op = Operation::new(
440            OperationId::new("node1".to_string(), 1),
441            manager.current_state_version(),
442            "Update".to_string(),
443            OperationCommand {
444                op_type: "UpdateField".to_string(),
445                collection: "test".to_string(),
446                record_id: "rec1".to_string(),
447                fields: {
448                    let mut m = HashMap::new();
449                    m.insert("field".to_string(), serde_json::json!("value"));
450                    m
451                },
452            },
453            "user".to_string(),
454        );
455
456        let _ =
457            manager.execute_and_commit("txn1".to_string(), vec![op], ConsistencyContract::local());
458
459        let new_version = manager.current_state_version();
460        assert_ne!(new_version.state_hash, initial_hash);
461    }
462}