#[cfg(feature = "vector-index")]
mod vector {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
use std::collections::HashMap;
fn vec3(x: f32, y: f32, z: f32) -> Value {
Value::Vector(vec![x, y, z].into())
}
fn setup_vector_db() -> GrafeoDB {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
db.set_node_property(n1, "category", Value::String("science".into()));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
db.set_node_property(n2, "category", Value::String("science".into()));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "emb", vec3(0.0, 0.0, 1.0));
db.set_node_property(n3, "category", Value::String("art".into()));
let n4 = db.create_node(&["Doc"]);
db.set_node_property(n4, "emb", vec3(0.9, 0.1, 0.0));
db.set_node_property(n4, "category", Value::String("science".into()));
db.create_property_index("category");
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("create vector index");
db
}
#[test]
fn test_vector_search_no_index_error() {
let db = GrafeoDB::new_in_memory();
let n = db.create_node(&["Doc"]);
db.set_node_property(n, "emb", vec3(1.0, 0.0, 0.0));
let result = db.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 5, None, None);
assert!(result.is_err(), "search without index should error");
}
#[test]
fn test_vector_search_with_ef_parameter() {
let db = setup_vector_db();
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, Some(50), None)
.expect("search with ef");
assert_eq!(results.len(), 2);
}
#[test]
fn test_batch_vector_search_multiple_queries() {
let db = setup_vector_db();
let queries = vec![vec![1.0_f32, 0.0, 0.0], vec![0.0, 1.0, 0.0]];
let results = db
.batch_vector_search("Doc", "emb", &queries, 2, None, None)
.expect("batch search");
assert_eq!(results.len(), 2);
for result_set in &results {
assert!(result_set.len() <= 2);
assert!(!result_set.is_empty());
}
}
#[test]
fn test_batch_vector_search_with_filter() {
let db = setup_vector_db();
let mut filters = HashMap::new();
filters.insert("category".to_string(), Value::String("science".into()));
let queries = vec![vec![1.0_f32, 0.0, 0.0]];
let results = db
.batch_vector_search("Doc", "emb", &queries, 10, None, Some(&filters))
.expect("batch search with filter");
assert_eq!(results.len(), 1);
assert_eq!(results[0].len(), 3);
}
#[test]
fn test_mmr_search_with_filter() {
let db = setup_vector_db();
let mut filters = HashMap::new();
filters.insert("category".to_string(), Value::String("science".into()));
let results = db
.mmr_search(
"Doc",
"emb",
&[1.0, 0.0, 0.0],
2,
None,
None,
None,
Some(&filters),
)
.expect("mmr with filter");
assert_eq!(results.len(), 2);
}
#[test]
fn test_vector_search_k_larger_than_dataset() {
let db = setup_vector_db();
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 100, None, None)
.expect("k > dataset");
assert_eq!(results.len(), 4);
}
#[test]
fn test_drop_and_recreate_vector_index() {
let db = setup_vector_db();
let r1 = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search before drop");
assert_eq!(r1.len(), 2);
assert!(db.drop_vector_index("Doc", "emb"));
let err = db.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None);
assert!(err.is_err(), "search after drop should error");
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("recreate index");
let r2 = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search after recreate");
assert_eq!(r2.len(), 2);
}
#[test]
fn test_vector_index_auto_inserts_on_set_property() {
let db = GrafeoDB::new_in_memory();
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("create empty index");
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 5, None, None)
.expect("search without rebuild");
assert_eq!(
results.len(),
2,
"auto-inserted nodes should be searchable without rebuild"
);
assert_eq!(results[0].0, n1, "n1 should be the closest match");
}
#[test]
fn test_rebuild_vector_index_preserves_results() {
let db = setup_vector_db();
let query = &[1.0_f32, 0.0, 0.0];
let before = db
.vector_search("Doc", "emb", query, 4, None, None)
.expect("search before rebuild");
db.rebuild_vector_index("Doc", "emb").expect("rebuild");
let after = db
.vector_search("Doc", "emb", query, 4, None, None)
.expect("search after rebuild");
assert_eq!(before.len(), after.len(), "same result count after rebuild");
let mut before_sorted: Vec<_> = before.iter().collect();
let mut after_sorted: Vec<_> = after.iter().collect();
before_sorted.sort_by_key(|(id, _)| *id);
after_sorted.sort_by_key(|(id, _)| *id);
for (b, a) in before_sorted.iter().zip(after_sorted.iter()) {
assert_eq!(b.0, a.0, "same node IDs after rebuild");
assert!(
(b.1 - a.1).abs() < 1e-5,
"same distances after rebuild: {} vs {}",
b.1,
a.1,
);
}
}
#[test]
fn test_scalar_quantized_vector_index() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "emb", vec3(0.0, 0.0, 1.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("scalar"),
)
.expect("create scalar quantized index");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search scalar quantized");
assert_eq!(results.len(), 2);
assert_eq!(results[0].0, n1);
}
#[test]
fn test_binary_quantized_vector_index() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("euclidean"),
None,
None,
Some("binary"),
)
.expect("create binary quantized index");
let results = db
.vector_search("Doc", "emb", &[0.9, 0.1, 0.0], 2, None, None)
.expect("search binary quantized");
assert_eq!(results.len(), 2);
}
#[test]
fn test_product_quantized_vector_index() {
let db = GrafeoDB::new_in_memory();
for i in 0..10 {
let n = db.create_node(&["Doc"]);
let vec: Vec<f32> = (0..8).map(|j| ((i * 8 + j) as f32) / 80.0).collect();
db.set_node_property(n, "emb", Value::Vector(vec.into()));
}
db.create_vector_index(
"Doc",
"emb",
Some(8),
Some("euclidean"),
None,
None,
Some("product"),
)
.expect("create product quantized index");
let results = db
.vector_search("Doc", "emb", &[0.5; 8], 3, None, None)
.expect("search product quantized");
assert_eq!(results.len(), 3);
}
#[test]
fn test_empty_quantized_index_auto_inserts() {
let db = GrafeoDB::new_in_memory();
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("scalar"),
)
.expect("create empty scalar index");
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search after late insert into quantized index");
assert_eq!(results.len(), 2);
}
#[test]
fn test_rebuild_preserves_quantization_type() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("binary"),
)
.expect("create binary index");
db.rebuild_vector_index("Doc", "emb").expect("rebuild");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search after rebuild of quantized index");
assert_eq!(results.len(), 2);
}
}
#[cfg(feature = "text-index")]
mod text {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
fn setup_text_db() -> GrafeoDB {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Article"]);
db.set_node_property(n1, "title", Value::String("Rust graph database".into()));
let n2 = db.create_node(&["Article"]);
db.set_node_property(n2, "title", Value::String("Python machine learning".into()));
let n3 = db.create_node(&["Article"]);
db.set_node_property(
n3,
"title",
Value::String("Rust systems programming".into()),
);
db.create_text_index("Article", "title")
.expect("create text index");
db
}
#[test]
fn test_text_search_basic() {
let db = setup_text_db();
let results = db.text_search("Article", "title", "Rust", 10).unwrap();
assert!(results.len() >= 2, "expected at least 2 Rust articles");
}
#[test]
fn test_text_search_no_index_error() {
let db = GrafeoDB::new_in_memory();
let n = db.create_node(&["Article"]);
db.set_node_property(n, "title", Value::String("test".into()));
let result = db.text_search("Article", "title", "test", 10);
assert!(result.is_err(), "text search without index should error");
}
#[test]
fn test_text_search_no_matches() {
let db = setup_text_db();
let results = db
.text_search("Article", "title", "nonexistentxyz", 10)
.unwrap();
assert!(results.is_empty(), "no matches expected for nonsense query");
}
#[test]
fn test_text_search_after_mutation() {
let db = setup_text_db();
let n = db.create_node(&["Article"]);
db.set_node_property(n, "title", Value::String("Rust web framework".into()));
let results = db.text_search("Article", "title", "Rust", 10).unwrap();
assert!(
results.len() >= 3,
"expected at least 3 Rust articles after mutation"
);
}
#[test]
fn test_drop_and_rebuild_text_index() {
let db = setup_text_db();
let r1 = db.text_search("Article", "title", "Rust", 10).unwrap();
assert!(!r1.is_empty());
assert!(db.drop_text_index("Article", "title"));
let err = db.text_search("Article", "title", "Rust", 10);
assert!(err.is_err());
db.rebuild_text_index("Article", "title").unwrap();
let r2 = db.text_search("Article", "title", "Rust", 10).unwrap();
assert!(!r2.is_empty());
}
}
#[cfg(feature = "hybrid-search")]
mod hybrid {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
fn vec3(x: f32, y: f32, z: f32) -> Value {
Value::Vector(vec![x, y, z].into())
}
fn setup_hybrid_db() -> GrafeoDB {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(
n1,
"content",
Value::String("Rust graph database engine".into()),
);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(
n2,
"content",
Value::String("Python machine learning framework".into()),
);
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(
n3,
"content",
Value::String("Rust systems programming language".into()),
);
db.set_node_property(n3, "emb", vec3(0.9, 0.1, 0.0));
let n4 = db.create_node(&["Doc"]);
db.set_node_property(
n4,
"content",
Value::String("Graph neural network research".into()),
);
db.set_node_property(n4, "emb", vec3(0.5, 0.5, 0.0));
db.create_text_index("Doc", "content")
.expect("create text index");
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("create vector index");
db
}
#[test]
fn test_hybrid_search_basic() {
let db = setup_hybrid_db();
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust graph",
Some(&[1.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid search");
assert!(!results.is_empty(), "hybrid search should return results");
let top_node = results[0].0;
let top_props = db.get_node(top_node).expect("top node exists");
let content = top_props
.properties
.get(&grafeo_common::types::PropertyKey::new("content"))
.expect("has content");
if let Value::String(s) = content {
assert!(
s.contains("Rust") || s.contains("graph"),
"top result should match query terms, got: {s}"
);
}
}
#[test]
fn test_hybrid_search_text_only() {
let db = setup_hybrid_db();
let results = db
.hybrid_search("Doc", "content", "emb", "Rust", None, 4, None)
.expect("text-only hybrid");
assert!(
!results.is_empty(),
"text-only hybrid should return results"
);
}
#[test]
fn test_hybrid_search_no_matches() {
let db = setup_hybrid_db();
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"nonexistentxyzquery",
Some(&[0.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid no matches");
let _ = results;
}
#[test]
fn test_hybrid_search_scores_descending() {
let db = setup_hybrid_db();
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust graph",
Some(&[1.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid search");
assert!(
results.len() >= 2,
"need at least 2 results to verify order"
);
for window in results.windows(2) {
assert!(
window[0].1 >= window[1].1,
"hybrid_search scores should be descending (higher = better): {} >= {}",
window[0].1,
window[1].1,
);
}
for (_, score) in &results {
assert!(
*score > 0.0,
"hybrid_search fusion scores should be positive"
);
}
}
#[test]
fn test_hybrid_search_without_text_index() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "content", Value::String("Rust graph database".into()));
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "content", Value::String("Python ML".into()));
db.set_node_property(n2, "emb", vec3(0.0, 1.0, 0.0));
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("create vector index");
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust",
Some(&[1.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid without text index should not error");
assert!(
!results.is_empty(),
"should return results from vector source only"
);
}
#[test]
fn test_hybrid_search_without_vector_index() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "content", Value::String("Rust graph database".into()));
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
db.create_text_index("Doc", "content")
.expect("create text index");
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust",
Some(&[1.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid without vector index should not error");
assert!(
!results.is_empty(),
"should return results from text source only"
);
}
#[test]
fn test_hybrid_search_without_any_index() {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "content", Value::String("Rust graph database".into()));
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust",
Some(&[1.0, 0.0, 0.0]),
4,
None,
)
.expect("hybrid without any index should not error");
assert!(
results.is_empty(),
"should return empty when no indexes exist"
);
}
#[test]
fn test_hybrid_weighted_fusion_vector_ranking() {
let db = setup_hybrid_db();
let fusion = grafeo_core::index::text::FusionMethod::Weighted {
weights: vec![0.3, 0.7], };
let results = db
.hybrid_search(
"Doc",
"content",
"emb",
"Rust",
Some(&[1.0, 0.0, 0.0]),
4,
Some(fusion),
)
.expect("weighted hybrid search");
assert!(results.len() >= 2, "need at least 2 results");
let top_node = results[0].0;
let top_props = db.get_node(top_node).expect("top node exists");
let content = top_props
.properties
.get(&grafeo_common::types::PropertyKey::new("content"))
.expect("has content");
if let Value::String(s) = content {
assert!(
s.contains("Rust graph database"),
"with 70% vector weight, closest vector should rank first, got: {s}"
);
}
}
}
#[cfg(feature = "vector-index")]
mod concurrent_vector {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
fn vec3(x: f32, y: f32, z: f32) -> Value {
Value::Vector(vec![x, y, z].into())
}
#[test]
fn test_concurrent_vector_read_during_write() {
let db = std::sync::Arc::new(GrafeoDB::new_in_memory());
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "emb", vec3(1.0, 0.0, 0.0));
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.unwrap();
let db_read = std::sync::Arc::clone(&db);
let db_write = std::sync::Arc::clone(&db);
let writer = std::thread::spawn(move || {
for i in 0..10 {
let n = db_write.create_node(&["Doc"]);
let x = (i as f32) / 10.0;
db_write.set_node_property(n, "emb", vec3(x, 1.0 - x, 0.0));
}
});
let reader = std::thread::spawn(move || {
for _ in 0..10 {
let results = db_read.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 5, None, None);
assert!(results.is_ok(), "concurrent read should not error");
}
});
writer.join().expect("writer thread should not panic");
reader.join().expect("reader thread should not panic");
}
}
#[cfg(feature = "text-index")]
mod concurrent_text {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
#[test]
fn test_concurrent_text_read_during_write() {
let db = std::sync::Arc::new(GrafeoDB::new_in_memory());
let n1 = db.create_node(&["Doc"]);
db.set_node_property(
n1,
"content",
Value::String("initial document about graphs".into()),
);
db.create_text_index("Doc", "content").unwrap();
let db_read = std::sync::Arc::clone(&db);
let db_write = std::sync::Arc::clone(&db);
let writer = std::thread::spawn(move || {
for i in 0..10 {
let n = db_write.create_node(&["Doc"]);
db_write.set_node_property(
n,
"content",
Value::String(format!("document number {i} about databases").into()),
);
}
});
let reader = std::thread::spawn(move || {
for _ in 0..10 {
let results = db_read.text_search("Doc", "content", "database", 5);
assert!(results.is_ok(), "concurrent text read should not error");
}
});
writer.join().expect("writer should not panic");
reader.join().expect("reader should not panic");
}
}
#[cfg(feature = "vector-index")]
mod quantized_vector {
use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
fn vec3(x: f32, y: f32, z: f32) -> Value {
Value::Vector(vec![x, y, z].into())
}
#[test]
fn test_scalar_quantized_index_create_insert_search() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
db.set_node_property(alix, "name", Value::from("Alix"));
let gus = db.create_node(&["Doc"]);
db.set_node_property(gus, "emb", vec3(0.0, 1.0, 0.0));
db.set_node_property(gus, "name", Value::from("Gus"));
let vincent = db.create_node(&["Doc"]);
db.set_node_property(vincent, "emb", vec3(0.9, 0.1, 0.0));
db.set_node_property(vincent, "name", Value::from("Vincent"));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("scalar"),
)
.expect("create scalar-quantized index");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search should succeed");
assert_eq!(results.len(), 2, "should return 2 results");
assert_eq!(results[0].0, alix, "Alix should be the closest match");
}
#[test]
fn test_binary_quantized_index_create_insert_search() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
let gus = db.create_node(&["Doc"]);
db.set_node_property(gus, "emb", vec3(0.0, 1.0, 0.0));
let vincent = db.create_node(&["Doc"]);
db.set_node_property(vincent, "emb", vec3(0.0, 0.0, 1.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("euclidean"),
None,
None,
Some("binary"),
)
.expect("create binary-quantized index");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 2, None, None)
.expect("search should succeed");
assert_eq!(results.len(), 2, "should return 2 results");
assert_eq!(results[0].0, alix, "Alix should be the closest match");
}
#[test]
fn test_no_quantization_still_works() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
let gus = db.create_node(&["Doc"]);
db.set_node_property(gus, "emb", vec3(0.0, 1.0, 0.0));
db.create_vector_index("Doc", "emb", Some(3), Some("cosine"), None, None, None)
.expect("create non-quantized index");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 1, None, None)
.expect("search should succeed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, alix);
}
#[test]
fn test_quantization_none_string_equivalent_to_default() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("none"),
)
.expect("create index with 'none' quantization");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 1, None, None)
.expect("search should succeed");
assert_eq!(results.len(), 1);
}
#[test]
fn test_invalid_quantization_type_errors() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
let result = db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("invalid_type"),
);
assert!(result.is_err(), "invalid quantization type should error");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Unknown quantization type"),
"error should mention unknown type: {err_msg}"
);
}
#[test]
fn test_scalar_quantized_empty_index_then_insert() {
let db = GrafeoDB::new_in_memory();
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("scalar"),
)
.expect("create empty scalar-quantized index");
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
let gus = db.create_node(&["Doc"]);
db.set_node_property(gus, "emb", vec3(0.0, 1.0, 0.0));
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 1, None, None)
.expect("search should succeed");
let _ = results;
}
#[test]
fn test_rebuild_preserves_quantization() {
let db = GrafeoDB::new_in_memory();
let alix = db.create_node(&["Doc"]);
db.set_node_property(alix, "emb", vec3(1.0, 0.0, 0.0));
let gus = db.create_node(&["Doc"]);
db.set_node_property(gus, "emb", vec3(0.0, 1.0, 0.0));
db.create_vector_index(
"Doc",
"emb",
Some(3),
Some("cosine"),
None,
None,
Some("binary"),
)
.expect("create binary-quantized index");
db.rebuild_vector_index("Doc", "emb")
.expect("rebuild should succeed");
let results = db
.vector_search("Doc", "emb", &[1.0, 0.0, 0.0], 1, None, None)
.expect("search after rebuild should succeed");
assert_eq!(results.len(), 1);
assert_eq!(results[0].0, alix);
}
}