use crate::batch::executor::BatchExecutor;
use crate::batch::spec::BatchSpec;
use crate::error::{Result, SpliceError};
use crate::graph::CodeGraph;
use crate::proof::storage::SnapshotStorage;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RollbackMode {
Never,
OnFailure,
Always,
}
#[derive(Debug, Clone)]
pub struct TransactionResult {
pub batch_result: BatchResult,
pub rolled_back: bool,
pub rollback_snapshot: Option<PathBuf>,
pub rollback_duration_ms: Option<u64>,
}
pub use crate::batch::executor::BatchResult;
pub struct BatchTransaction {
db_path: PathBuf,
rollback_mode: RollbackMode,
snapshot_before: bool,
analyzer_mode: crate::validate::AnalyzerMode,
}
impl BatchTransaction {
pub fn new(
db_path: PathBuf,
rollback_mode: RollbackMode,
snapshot_before: bool,
analyzer_mode: crate::validate::AnalyzerMode,
) -> Self {
Self {
db_path,
rollback_mode,
snapshot_before,
analyzer_mode,
}
}
pub fn execute(&self, spec: &BatchSpec, dry_run: bool) -> Result<TransactionResult> {
use std::time::Instant;
let snapshot_path = if self.snapshot_before || self.rollback_mode != RollbackMode::Never {
Some(self.capture_pre_snapshot(spec)?)
} else {
None
};
let start = Instant::now();
let batch_result = self.execute_batch(spec, dry_run)?;
let total_duration_ms = start.elapsed().as_millis() as u64;
let batch_result = BatchResult {
total_duration_ms,
..batch_result
};
let should_rollback = match self.rollback_mode {
RollbackMode::Never => false,
RollbackMode::OnFailure => batch_result.failed > 0,
RollbackMode::Always => true,
};
let (rolled_back, rollback_duration_ms) = if should_rollback {
if let Some(ref snapshot) = snapshot_path {
let start = std::time::Instant::now();
self.perform_rollback(snapshot)?;
let duration = start.elapsed().as_millis() as u64;
(true, Some(duration))
} else {
eprintln!("Warning: Rollback requested but no snapshot available");
(false, None)
}
} else {
(false, None)
};
Ok(TransactionResult {
batch_result,
rolled_back,
rollback_snapshot: snapshot_path,
rollback_duration_ms,
})
}
fn execute_batch(&self, spec: &BatchSpec, dry_run: bool) -> Result<BatchResult> {
let mut executor = BatchExecutor::new(
dry_run,
Some(self.db_path.clone()),
self.analyzer_mode.clone(),
);
executor.execute(spec)
}
fn capture_pre_snapshot(&self, spec: &BatchSpec) -> Result<PathBuf> {
use crate::proof::generation::generate_snapshot;
let storage = SnapshotStorage::new()?;
let snapshot = generate_snapshot(&self.db_path)?;
let operation = spec
.metadata
.description
.as_ref()
.map(|d| format!("batch-{}", d.replace(' ', "-")))
.unwrap_or_else(|| "batch".to_string());
let metadata = storage.save_snapshot(&operation, &self.db_path, snapshot)?;
eprintln!("Pre-batch snapshot: {}", metadata.snapshot_path.display());
Ok(metadata.snapshot_path)
}
fn perform_rollback(&self, snapshot_path: &Path) -> Result<()> {
eprintln!("Rolling back from snapshot: {}", snapshot_path.display());
SnapshotStorage::restore_from_snapshot(&self.db_path, snapshot_path)?;
Ok(())
}
pub fn cleanup_snapshot(&self, snapshot_path: &Path) -> Result<()> {
std::fs::remove_file(snapshot_path)
.map_err(|e| SpliceError::Other(format!("Failed to cleanup snapshot: {}", e)))?;
Ok(())
}
}