use std::path::Path;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use super::error::{IndexPersistenceError, Result};
use super::formats::{
PersistedHnswConfig, VectorIndexData, VectorIndexMeta, VectorMappingsData, VectorSnapshotMeta,
};
use super::{MANIFEST_VERSION, VECTOR_META_MAGIC};
pub fn save_vector_meta(meta: &VectorIndexMeta, path: &Path) -> Result<()> {
super::common::save_encoded_with_crc(meta, path)
}
pub fn load_vector_meta(path: &Path) -> Result<VectorIndexMeta> {
let meta: VectorIndexMeta = super::common::load_encoded_with_crc(
path,
super::MAX_VECTOR_INDEX_FILE_SIZE,
"Vector index",
)?;
if meta.magic != VECTOR_META_MAGIC {
return Err(IndexPersistenceError::InvalidMagic {
path: path.to_path_buf(),
expected: VECTOR_META_MAGIC,
got: meta.magic,
});
}
if meta.version > MANIFEST_VERSION {
return Err(IndexPersistenceError::UnsupportedVersion {
found: meta.version,
supported: MANIFEST_VERSION,
});
}
Ok(meta)
}
pub fn save_vector_mappings(mappings: &VectorMappingsData, path: &Path) -> Result<()> {
super::common::save_encoded_with_crc(mappings, path)
}
pub fn load_vector_mappings(path: &Path) -> Result<VectorMappingsData> {
super::common::load_encoded_with_crc(path, super::MAX_VECTOR_INDEX_FILE_SIZE, "Vector index")
}
#[allow(dead_code)]
pub fn save_snapshot_meta(meta: &VectorSnapshotMeta, path: &Path) -> Result<()> {
super::common::save_encoded_with_crc(meta, path)
}
#[allow(dead_code)]
pub fn load_snapshot_meta(path: &Path) -> Result<VectorSnapshotMeta> {
super::common::load_encoded_with_crc(path, super::MAX_VECTOR_INDEX_FILE_SIZE, "Vector index")
}
pub fn load_vector_index(path: &Path) -> Result<VectorIndexData> {
let meta_path = path.join("meta.idx");
let mappings_path = path.join("mappings.idx");
let index_path = path.join("current.usearch");
let meta = load_vector_meta(&meta_path)?;
let mappings = load_vector_mappings(&mappings_path)?;
Ok(VectorIndexData {
meta,
mappings,
index_path,
})
}
pub fn new_vector_meta(
property_name: &str,
dimensions: u32,
metric: u8,
config: PersistedHnswConfig,
) -> VectorIndexMeta {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_else(|_| Duration::from_secs(0))
.as_secs() as i64;
VectorIndexMeta {
magic: VECTOR_META_MAGIC,
version: MANIFEST_VERSION,
property_name: property_name.to_string(),
dimensions,
metric,
hnsw_config: config,
vector_count: 0,
created_at: now,
last_modified: now,
}
}
pub fn new_vector_mappings() -> VectorMappingsData {
VectorMappingsData {
version: MANIFEST_VERSION,
count: 0,
mappings: Vec::new(),
deleted_ids: Vec::new(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::index_persistence::formats::{PersistedSnapshotType, VectorMapping};
use tempfile::tempdir;
#[test]
fn test_vector_meta_round_trip() {
let dir = tempdir().unwrap();
let path = dir.path().join("meta.idx");
let config = PersistedHnswConfig {
m: 16,
ef_construction: 128,
ef_search: 64,
};
let meta = new_vector_meta("embedding", 384, 0, config);
save_vector_meta(&meta, &path).unwrap();
let loaded = load_vector_meta(&path).unwrap();
assert_eq!(loaded.property_name, "embedding");
assert_eq!(loaded.dimensions, 384);
assert_eq!(loaded.hnsw_config.m, 16);
}
#[test]
fn test_vector_mappings_round_trip() {
let dir = tempdir().unwrap();
let path = dir.path().join("mappings.idx");
let mut mappings = new_vector_mappings();
mappings.count = 3;
mappings.mappings.push(VectorMapping {
node_id: 1,
usearch_key: 100,
});
mappings.mappings.push(VectorMapping {
node_id: 2,
usearch_key: 101,
});
mappings.mappings.push(VectorMapping {
node_id: 3,
usearch_key: 102,
});
mappings.deleted_ids.push(99);
save_vector_mappings(&mappings, &path).unwrap();
let loaded = load_vector_mappings(&path).unwrap();
assert_eq!(loaded.count, 3);
assert_eq!(loaded.mappings.len(), 3);
assert_eq!(loaded.deleted_ids, vec![99]);
}
#[test]
fn test_snapshot_meta_round_trip() {
let dir = tempdir().unwrap();
let path = dir.path().join("snapshot.meta");
let meta = VectorSnapshotMeta {
snapshot_id: 42,
snapshot_type: PersistedSnapshotType::Full,
timestamp: 1234567890,
vector_count: 1000,
config: PersistedHnswConfig {
m: 16,
ef_construction: 128,
ef_search: 64,
},
base_snapshot_id: None,
};
save_snapshot_meta(&meta, &path).unwrap();
let loaded = load_snapshot_meta(&path).unwrap();
assert_eq!(loaded.snapshot_id, 42);
assert_eq!(loaded.vector_count, 1000);
assert!(matches!(loaded.snapshot_type, PersistedSnapshotType::Full));
}
}