use laurus::storage::file::{FileStorage, FileStorageConfig};
use laurus::vector::{
DistanceMetric, HnswIndexConfig, HnswIndexReader, HnswIndexWriter, HnswSearcher, Vector,
VectorIndexQuery, VectorIndexSearcher, VectorIndexWriter, VectorIndexWriterConfig,
};
use std::sync::Arc;
use tempfile::tempdir;
#[test]
fn test_hnsw_append_persistence() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let path = dir.path();
let index_name = "test_index";
let index_config = HnswIndexConfig {
dimension: 3,
m: 16,
ef_construction: 100,
normalize_vectors: false,
distance_metric: DistanceMetric::Euclidean,
..Default::default()
};
let writer_config = VectorIndexWriterConfig {
parallel_build: true,
..Default::default()
};
{
let storage_config = FileStorageConfig::new(path);
let storage = Arc::new(FileStorage::new(path, storage_config)?);
let mut writer = HnswIndexWriter::with_storage(
index_config.clone(),
writer_config.clone(),
index_name,
storage,
)?;
let vectors = vec![
(1, "doc1".to_string(), Vector::new(vec![1.0f32, 0.0, 0.0])),
(2, "doc2".to_string(), Vector::new(vec![0.0f32, 1.0, 0.0])),
];
writer.add_vectors(vectors)?;
writer.finalize()?;
writer.write()?;
}
{
let storage_config = FileStorageConfig::new(path);
let storage = Arc::new(FileStorage::new(path, storage_config)?);
let mut writer = HnswIndexWriter::load(
index_config.clone(),
writer_config.clone(),
storage,
index_name,
)?;
let new_vectors = vec![
(3, "doc3".to_string(), Vector::new(vec![0.0f32, 0.0, 1.0])),
(4, "doc4".to_string(), Vector::new(vec![1.0f32, 1.0, 0.0])),
];
writer.add_vectors(new_vectors)?;
writer.finalize()?;
writer.write()?;
}
Ok(())
}
#[test]
fn test_hnsw_append_search_verification() -> Result<(), Box<dyn std::error::Error>> {
let dir = tempdir()?;
let path = dir.path();
let index_name = "search_test";
let index_config = HnswIndexConfig {
dimension: 2,
m: 16,
ef_construction: 100,
normalize_vectors: false,
distance_metric: DistanceMetric::Euclidean,
..Default::default()
};
let writer_config = VectorIndexWriterConfig {
parallel_build: true,
..Default::default()
};
{
let storage_config = FileStorageConfig::new(path);
let storage = Arc::new(FileStorage::new(path, storage_config)?);
let mut writer = HnswIndexWriter::with_storage(
index_config.clone(),
writer_config.clone(),
index_name,
storage,
)?;
writer.add_vectors(vec![(
1,
"doc1".to_string(),
Vector::new(vec![1.0f32, 0.0]),
)])?;
writer.finalize()?;
writer.write()?;
}
{
let storage_config = FileStorageConfig::new(path);
let storage = Arc::new(FileStorage::new(path, storage_config)?);
let mut writer = HnswIndexWriter::load(
index_config.clone(),
writer_config.clone(),
storage,
index_name,
)?;
writer.add_vectors(vec![(
2,
"doc2".to_string(),
Vector::new(vec![0.0f32, 1.0]),
)])?;
writer.finalize()?;
writer.write()?;
}
{
let storage_config = FileStorageConfig::new(path);
let storage = Arc::new(FileStorage::new(path, storage_config)?);
let reader = Arc::new(HnswIndexReader::load(
storage.clone(),
index_name,
DistanceMetric::Euclidean,
)?);
let searcher = HnswSearcher::new(reader)?;
let req1 = VectorIndexQuery::new(Vector::new(vec![1.0f32, 0.0])).top_k(1);
let results1 = searcher.search(&req1)?;
assert_eq!(results1.len(), 1, "Expected 1 result for doc1");
assert_eq!(results1.results[0].doc_id, 1, "Expected doc1");
let req2 = VectorIndexQuery::new(Vector::new(vec![0.0f32, 1.0])).top_k(1);
let results2 = searcher.search(&req2)?;
assert_eq!(results2.len(), 1, "Expected 1 result for doc2");
assert_eq!(results2.results[0].doc_id, 2, "Expected doc2");
}
Ok(())
}