use crate::backend::native::types::{NativeBackendError, NativeResult};
use crate::backend::native::v2::{
export::snapshot::{SnapshotExportConfig, SnapshotExporter},
wal::{V2WALConfig, V2WALManager},
};
use std::path::{Path, PathBuf};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Clone)]
pub struct BackupConfig {
pub backup_dir: PathBuf,
pub backup_id: Option<String>,
pub checkpoint_before_backup: bool,
pub include_statistics: bool,
pub validate_checksum: bool,
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
backup_dir: PathBuf::from("backup"),
backup_id: None,
checkpoint_before_backup: true,
include_statistics: true,
validate_checksum: true,
}
}
}
impl BackupConfig {
pub fn new(backup_dir: impl AsRef<Path>) -> Self {
Self {
backup_dir: backup_dir.as_ref().to_path_buf(),
..Default::default()
}
}
pub fn with_backup_id(mut self, id: impl Into<String>) -> Self {
self.backup_id = Some(id.into());
self
}
pub fn with_checkpoint(mut self, enabled: bool) -> Self {
self.checkpoint_before_backup = enabled;
self
}
pub fn with_statistics(mut self, enabled: bool) -> Self {
self.include_statistics = enabled;
self
}
pub fn with_checksum_validation(mut self, enabled: bool) -> Self {
self.validate_checksum = enabled;
self
}
}
#[derive(Debug, Clone)]
pub struct BackupResult {
pub snapshot_path: PathBuf,
pub manifest_path: PathBuf,
pub size_bytes: u64,
pub checksum: u64,
pub record_count: u64,
pub duration_secs: f64,
pub timestamp: u64,
pub checkpoint_performed: bool,
}
pub fn create_backup(graph_path: &Path, config: BackupConfig) -> NativeResult<BackupResult> {
let start = SystemTime::now();
let backup_id = config.backup_id.clone().unwrap_or_else(|| {
format!(
"backup_{}",
start
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs()
)
});
std::fs::create_dir_all(&config.backup_dir).map_err(|e| NativeBackendError::Io(e))?;
let checkpoint_performed = if config.checkpoint_before_backup {
match perform_checkpoint(graph_path) {
Ok(()) => true,
Err(e) => {
eprintln!(
"Warning: Checkpoint before backup failed: {:?}. Continuing with backup.",
e
);
false
}
}
} else {
false
};
let export_config = SnapshotExportConfig {
export_path: config.backup_dir.clone(),
snapshot_id: backup_id.clone(),
include_statistics: config.include_statistics,
min_stable_duration: Duration::from_secs(0),
checksum_validation: config.validate_checksum,
};
let mut exporter = SnapshotExporter::new(graph_path, export_config)?;
let export_result = exporter.export_snapshot()?;
let duration = start
.elapsed()
.unwrap_or(Duration::from_secs(0))
.as_secs_f64();
Ok(BackupResult {
snapshot_path: export_result.snapshot_path,
manifest_path: export_result.manifest_path,
size_bytes: export_result.snapshot_size_bytes,
checksum: export_result.checksum,
record_count: export_result.record_count,
duration_secs: duration,
timestamp: export_result.export_timestamp,
checkpoint_performed,
})
}
fn perform_checkpoint(graph_path: &Path) -> NativeResult<()> {
let wal_path = graph_path.with_extension("wal");
if !wal_path.exists() {
return Ok(());
}
let wal_config = V2WALConfig::for_graph_file(graph_path);
let wal_manager = V2WALManager::create(wal_config)?;
wal_manager.force_checkpoint()?;
Ok(())
}
pub fn backup(graph_path: &Path, backup_dir: &Path) -> NativeResult<BackupResult> {
create_backup(graph_path, BackupConfig::new(backup_dir))
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_backup_config_default() {
let config = BackupConfig::default();
assert_eq!(config.backup_dir, PathBuf::from("backup"));
assert!(config.backup_id.is_none());
assert!(config.checkpoint_before_backup);
assert!(config.include_statistics);
assert!(config.validate_checksum);
}
#[test]
fn test_backup_config_builder() {
let config = BackupConfig::new("/tmp/backup")
.with_backup_id("test_backup")
.with_checkpoint(false)
.with_statistics(false)
.with_checksum_validation(false);
assert_eq!(config.backup_dir, PathBuf::from("/tmp/backup"));
assert_eq!(config.backup_id, Some("test_backup".to_string()));
assert!(!config.checkpoint_before_backup);
assert!(!config.include_statistics);
assert!(!config.validate_checksum);
}
#[test]
fn test_backup_config_with_backup_id() {
let config = BackupConfig::default().with_backup_id("my_backup");
assert_eq!(config.backup_id, Some("my_backup".to_string()));
}
#[test]
fn test_backup_result_fields() {
let result = BackupResult {
snapshot_path: PathBuf::from("/backup/snapshot.v2"),
manifest_path: PathBuf::from("/backup/manifest.json"),
size_bytes: 1024,
checksum: 12345,
record_count: 100,
duration_secs: 1.5,
timestamp: 1234567890,
checkpoint_performed: true,
};
assert_eq!(result.snapshot_path, PathBuf::from("/backup/snapshot.v2"));
assert_eq!(result.size_bytes, 1024);
assert!(result.checkpoint_performed);
}
}