use crate::backend::native::graph_file::GraphFile;
use crate::backend::native::types::{NativeBackendError, NativeResult};
use crate::backend::native::v2::export::ManifestSerializer;
use crate::backend::native::v2::wal::recovery::states::RecoveryState as ExplicitRecoveryState;
use crate::backend::native::v2::wal::{V2WALConfig, V2WALRecord};
use crate::backend::native::v2::{ExportManifest, ImportMode};
use std::path::{Path, PathBuf};
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct V2ImportConfig {
pub target_graph_path: PathBuf,
pub export_dir_path: PathBuf,
pub import_mode: ImportMode,
pub validate_recovery: bool,
pub force_checkpoint_after_import: bool,
}
#[derive(Debug, Clone)]
pub struct ImportValidationReport {
pub manifest_valid: bool,
pub files_exist: bool,
pub format_compatible: bool,
pub target_compatible: bool,
pub warnings: Vec<String>,
pub errors: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ImportResult {
pub records_imported: u64,
pub wal_records_replayed: u64,
pub import_duration: Duration,
pub final_lsn: u64,
pub recovery_state: ExplicitRecoveryState,
pub validation_passed: bool,
}
pub struct V2Importer {
config: V2ImportConfig,
manifest: ExportManifest,
wal_config: V2WALConfig,
}
impl V2Importer {
pub fn from_export_dir(
export_dir: &Path,
target_graph_path: &Path,
import_config: V2ImportConfig,
) -> NativeResult<Self> {
if !export_dir.exists() {
return Err(NativeBackendError::InvalidParameter {
context: format!("Export directory does not exist: {:?}", export_dir),
source: None,
});
}
let manifest_path = export_dir.join("export.manifest");
if !manifest_path.exists() {
return Err(NativeBackendError::InvalidParameter {
context: format!("Export manifest not found: {:?}", manifest_path),
source: None,
});
}
let manifest = ManifestSerializer::read_from_file(&manifest_path).map_err(|_| {
NativeBackendError::InvalidParameter {
context: format!("Failed to read manifest from {:?}", manifest_path),
source: None,
}
})?;
let mut wal_config = V2WALConfig::for_graph_file(target_graph_path);
wal_config.enable_compression = import_config.import_mode == ImportMode::Fresh;
wal_config.validate()?;
Ok(V2Importer {
config: import_config,
manifest,
wal_config,
})
}
pub fn validate_export(&self) -> NativeResult<ImportValidationReport> {
let mut report = ImportValidationReport {
manifest_valid: false,
files_exist: false,
format_compatible: false,
target_compatible: false,
warnings: Vec::new(),
errors: Vec::new(),
};
report.manifest_valid =
self.validate_manifest_integrity(&mut report.warnings, &mut report.errors);
report.files_exist = self.validate_export_files(&mut report.warnings, &mut report.errors);
report.format_compatible =
self.validate_format_compatibility(&mut report.warnings, &mut report.errors);
if self.config.import_mode == ImportMode::Merge {
report.target_compatible =
self.validate_target_compatibility(&mut report.warnings, &mut report.errors);
} else {
report.target_compatible = true; }
Ok(report)
}
pub fn import(&self) -> NativeResult<ImportResult> {
Err(NativeBackendError::CorruptStringTable {
reason: "V2Importer::import not yet implemented".to_string(),
})
}
fn validate_manifest_integrity(
&self,
_warnings: &mut Vec<String>,
errors: &mut Vec<String>,
) -> bool {
if self.manifest.magic != ExportManifest::MAGIC {
errors.push("Invalid manifest magic bytes".to_string());
return false;
}
if self.manifest.version != ExportManifest::VERSION {
errors.push(format!(
"Unsupported manifest version: {}",
self.manifest.version
));
return false;
}
if let (Some(wal_start), Some(wal_end)) =
(self.manifest.wal_start_lsn, self.manifest.wal_end_lsn)
{
if wal_start > wal_end {
errors.push("Invalid WAL LSN range: start > end".to_string());
return false;
}
}
true
}
#[allow(unused_assignments)]
fn validate_export_files(&self, warnings: &mut Vec<String>, errors: &mut Vec<String>) -> bool {
let export_path = &self.config.export_dir_path;
let mut all_files_exist = true;
let expected_graph_files = vec![
"v2_export_checkpoint.graph",
"v2_export_lsn.graph",
"v2_export.graph",
];
let expected_wal_files = vec!["v2_export.wal", "v2_export_lsn.wal"];
let manifest_path = export_path.join("export.manifest");
if !manifest_path.exists() {
errors.push("Export manifest file missing".to_string());
all_files_exist = false;
}
for graph_file in expected_graph_files {
let file_path = export_path.join(graph_file);
if file_path.exists() {
warnings.push(format!("Found graph file: {:?}", file_path));
return true; }
}
errors.push("No graph files found in export directory".to_string());
all_files_exist = false;
for wal_file in expected_wal_files {
let file_path = export_path.join(wal_file);
if file_path.exists() {
warnings.push(format!("Found WAL file: {:?}", file_path));
}
}
all_files_exist
}
fn validate_format_compatibility(
&self,
warnings: &mut Vec<String>,
errors: &mut Vec<String>,
) -> bool {
if self.manifest.graph_format_version != 2 {
errors.push(format!(
"Unsupported graph format version: {}",
self.manifest.graph_format_version
));
return false;
}
if let Some(wal_end_lsn) = self.manifest.wal_end_lsn {
if wal_end_lsn > 0 && self.manifest.wal_format_version != 1 {
errors.push(format!(
"Unsupported WAL format version: {}",
self.manifest.wal_format_version
));
return false;
}
}
if self.manifest.export_mode
!= crate::backend::native::v2::export::ExportMode::CheckpointAligned
&& self.manifest.wal_end_lsn.is_none()
{
warnings.push("Export is not checkpoint aligned and has no WAL tail".to_string());
}
if !self.manifest.v2_clustered_edges {
errors.push("Import requires V2 clustered edge format support".to_string());
return false;
}
true
}
fn validate_target_compatibility(
&self,
warnings: &mut Vec<String>,
errors: &mut Vec<String>,
) -> bool {
let target_path = &self.config.target_graph_path;
if !target_path.exists() {
errors.push("Target graph file does not exist for merge import".to_string());
return false;
}
match GraphFile::open(target_path) {
Ok(_graph_file) => {
warnings.push("Merge import target validation successful".to_string());
true
}
Err(e) => {
errors.push(format!("Target graph file is not readable: {}", e));
false
}
}
}
fn replay_wal_records(&self, _wal_records: &[V2WALRecord]) -> NativeResult<()> {
Err(NativeBackendError::CorruptStringTable {
reason: "V2Importer::replay_wal_records not yet implemented".to_string(),
})
}
}