use std::collections::BTreeMap;
use hirn_core::HirnError;
use hirn_storage::PhysicalStore;
use hirn_storage::store::VersionTag;
#[derive(Debug, Clone)]
pub struct Snapshot {
pub name: String,
pub versions: BTreeMap<String, u64>,
}
#[derive(Debug, Clone)]
pub struct SnapshotReport {
pub tag: String,
pub datasets_tagged: usize,
}
#[derive(Debug, Clone)]
pub struct RollbackReport {
pub tag: String,
pub datasets_rolled_back: usize,
}
pub async fn create_snapshot(
storage: &dyn PhysicalStore,
tag: &str,
) -> Result<SnapshotReport, HirnError> {
let datasets = storage
.list_datasets()
.await
.map_err(|e| HirnError::storage(e))?;
let mut tagged = 0usize;
for ds in &datasets {
storage
.tag(&ds.name, tag)
.await
.map_err(|e| HirnError::storage(e))?;
tagged += 1;
}
Ok(SnapshotReport {
tag: tag.to_string(),
datasets_tagged: tagged,
})
}
pub async fn list_snapshots(storage: &dyn PhysicalStore) -> Result<Vec<Snapshot>, HirnError> {
let datasets = storage
.list_datasets()
.await
.map_err(|e| HirnError::storage(e))?;
if datasets.is_empty() {
return Ok(Vec::new());
}
let mut tag_map: BTreeMap<String, BTreeMap<String, u64>> = BTreeMap::new();
for ds in &datasets {
let tags = storage
.list_tags(&ds.name)
.await
.map_err(|e| HirnError::storage(e))?;
for t in tags {
tag_map
.entry(t.name)
.or_default()
.insert(ds.name.clone(), t.version);
}
}
let num_datasets = datasets.len();
let snapshots = tag_map
.into_iter()
.filter(|(_, versions)| versions.len() == num_datasets)
.map(|(name, versions)| Snapshot { name, versions })
.collect();
Ok(snapshots)
}
pub async fn rollback(storage: &dyn PhysicalStore, tag: &str) -> Result<RollbackReport, HirnError> {
let datasets = storage
.list_datasets()
.await
.map_err(|e| HirnError::storage(e))?;
let mut rolled_back = 0usize;
for ds in &datasets {
let tags: Vec<VersionTag> = storage
.list_tags(&ds.name)
.await
.map_err(|e| HirnError::storage(e))?;
let target = tags.iter().find(|t| t.name == tag).ok_or_else(|| {
HirnError::storage(format!(
"snapshot tag '{}' not found on dataset '{}'",
tag, ds.name
))
})?;
storage
.checkout(&ds.name, target.version)
.await
.map_err(|e| HirnError::storage(e))?;
rolled_back += 1;
}
Ok(RollbackReport {
tag: tag.to_string(),
datasets_rolled_back: rolled_back,
})
}
#[cfg(test)]
mod tests {
use super::*;
use hirn_storage::memory_store::MemoryStore;
#[tokio::test]
async fn snapshot_empty_storage() {
let storage = MemoryStore::new();
let report = create_snapshot(&storage, "test-snap").await.unwrap();
assert_eq!(report.datasets_tagged, 0);
}
#[tokio::test]
async fn list_snapshots_empty_storage() {
let storage = MemoryStore::new();
let snapshots = list_snapshots(&storage).await.unwrap();
assert!(snapshots.is_empty());
}
#[tokio::test]
async fn rollback_empty_storage() {
let storage = MemoryStore::new();
let report = rollback(&storage, "nonexistent").await.unwrap();
assert_eq!(report.datasets_rolled_back, 0);
}
}