use crate::fs::errors::{FsError, FsResult};
use crate::fs::operations::{FsOperation, OperationSummary};
use sha3::{Digest, Sha3_256};
use std::sync::{Arc, Mutex};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransactionMode {
ReadOnly,
ReadWrite,
Exclusive,
}
impl TransactionMode {
pub fn allows_writes(&self) -> bool {
matches!(
self,
TransactionMode::ReadWrite | TransactionMode::Exclusive
)
}
pub fn is_exclusive(&self) -> bool {
*self == TransactionMode::Exclusive
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TransactionState {
Active,
Committed,
RolledBack,
Error,
}
#[derive(Clone)]
pub struct Transaction {
id: String,
mode: TransactionMode,
state: Arc<Mutex<TransactionState>>,
operations: Arc<Mutex<Vec<FsOperation>>>,
summary: Arc<Mutex<OperationSummary>>,
parent_commit: Option<String>,
branch: Option<String>,
created_at: i64,
max_operations: usize,
}
impl Transaction {
pub fn new(mode: TransactionMode) -> Self {
let id = uuid::Uuid::new_v4().to_string();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
Self {
id,
mode,
state: Arc::new(Mutex::new(TransactionState::Active)),
operations: Arc::new(Mutex::new(Vec::new())),
summary: Arc::new(Mutex::new(OperationSummary::new())),
parent_commit: None,
branch: None,
created_at: now,
max_operations: 0,
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn state(&self) -> TransactionState {
*self.state.lock().unwrap()
}
pub fn is_active(&self) -> bool {
self.state() == TransactionState::Active
}
pub fn allows_writes(&self) -> bool {
self.mode.allows_writes()
}
pub fn mode(&self) -> TransactionMode {
self.mode
}
pub fn operation_count(&self) -> usize {
self.operations.lock().unwrap().len()
}
pub fn operations(&self) -> Vec<FsOperation> {
self.operations.lock().unwrap().clone()
}
pub fn summary(&self) -> OperationSummary {
self.summary.lock().unwrap().clone()
}
pub fn add_operation(&self, op: FsOperation) -> FsResult<()> {
if !self.is_active() {
return Err(FsError::TransactionError(
"Cannot add operation to inactive transaction".to_string(),
));
}
if !self.allows_writes() {
return Err(FsError::TransactionError(
"Cannot add write operation to read-only transaction".to_string(),
));
}
if self.max_operations > 0 && self.operation_count() >= self.max_operations {
return Err(FsError::TransactionError(format!(
"Transaction operation limit ({}) exceeded",
self.max_operations
)));
}
let mut ops = self.operations.lock().unwrap();
ops.push(op);
Ok(())
}
pub fn set_parent(&mut self, commit_hash: String) {
self.parent_commit = Some(commit_hash);
}
pub fn set_branch(&mut self, branch: String) {
self.branch = Some(branch);
}
pub fn parent_commit(&self) -> Option<&str> {
self.parent_commit.as_deref()
}
pub fn branch(&self) -> Option<&str> {
self.branch.as_deref()
}
pub fn commit(&self, message: &str, author: &str) -> FsResult<String> {
if !self.is_active() {
return Err(FsError::TransactionError(format!(
"Cannot commit {} transaction",
match self.state() {
TransactionState::Committed => "already-committed",
TransactionState::RolledBack => "rolled-back",
TransactionState::Error => "error",
TransactionState::Active => "unknown",
}
)));
}
let ops = self.operations.lock().unwrap();
if ops.is_empty() {
return Err(FsError::TransactionError(
"Cannot commit empty transaction".to_string(),
));
}
let mut hasher = Sha3_256::new();
hasher.update(format!("{}{}{}", message, author, self.id).as_bytes());
let hash = hasher.finalize();
let commit_hash = format!("commit_{:x}", hash);
*self.state.lock().unwrap() = TransactionState::Committed;
Ok(commit_hash)
}
pub fn rollback(&self) -> FsResult<()> {
if !self.is_active() {
return Err(FsError::TransactionError(
"Cannot rollback inactive transaction".to_string(),
));
}
self.operations.lock().unwrap().clear();
*self.state.lock().unwrap() = TransactionState::RolledBack;
Ok(())
}
pub fn set_error(&self) {
*self.state.lock().unwrap() = TransactionState::Error;
}
pub fn savepoint(&self) -> FsResult<SavePoint> {
if !self.is_active() {
return Err(FsError::TransactionError(
"Cannot create savepoint in inactive transaction".to_string(),
));
}
let ops = self.operations.lock().unwrap();
Ok(SavePoint {
transaction_id: self.id.clone(),
operation_count: ops.len(),
})
}
pub fn rollback_to_savepoint(&self, savepoint: &SavePoint) -> FsResult<()> {
if self.id != savepoint.transaction_id {
return Err(FsError::TransactionError(
"Savepoint is from a different transaction".to_string(),
));
}
let mut ops = self.operations.lock().unwrap();
if savepoint.operation_count < ops.len() {
ops.truncate(savepoint.operation_count);
Ok(())
} else {
Err(FsError::TransactionError(
"Invalid savepoint state".to_string(),
))
}
}
}
impl std::fmt::Debug for Transaction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Transaction")
.field("id", &self.id)
.field("mode", &self.mode)
.field("state", &self.state())
.field("operations", &self.operation_count())
.field("parent_commit", &self.parent_commit)
.field("branch", &self.branch)
.field("created_at", &self.created_at)
.finish()
}
}
#[derive(Debug, Clone)]
pub struct SavePoint {
transaction_id: String,
operation_count: usize,
}
impl SavePoint {
pub fn transaction_id(&self) -> &str {
&self.transaction_id
}
pub fn operation_count(&self) -> usize {
self.operation_count
}
}
pub struct TransactionHandle {
transaction: Arc<Transaction>,
}
impl TransactionHandle {
pub fn new(mode: TransactionMode) -> Self {
Self {
transaction: Arc::new(Transaction::new(mode)),
}
}
pub fn transaction(&self) -> &Transaction {
&self.transaction
}
pub fn commit(self, message: &str, author: &str) -> FsResult<String> {
self.transaction.commit(message, author)
}
pub fn rollback(self) -> FsResult<()> {
self.transaction.rollback()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_transaction_creation() {
let tx = Transaction::new(TransactionMode::ReadWrite);
assert!(tx.is_active());
assert!(tx.allows_writes());
assert_eq!(tx.operation_count(), 0);
}
#[test]
fn test_transaction_state() {
let tx = Transaction::new(TransactionMode::ReadOnly);
assert_eq!(tx.state(), TransactionState::Active);
assert!(!tx.allows_writes());
}
#[test]
fn test_transaction_mode() {
let rw = TransactionMode::ReadWrite;
let ro = TransactionMode::ReadOnly;
assert!(rw.allows_writes());
assert!(!ro.allows_writes());
assert!(!rw.is_exclusive());
assert!(TransactionMode::Exclusive.is_exclusive());
}
#[test]
fn test_operation_count() {
let tx = Transaction::new(TransactionMode::ReadWrite);
assert_eq!(tx.operation_count(), 0);
}
}