#![cfg(all(feature = "compact-store", feature = "lpg"))]
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_compacted_vector_db() -> GrafeoDB {
let mut db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "embedding", vec3(1.0, 0.0, 0.0));
db.set_node_property(n1, "title", Value::String("alpha".into()));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "embedding", vec3(0.0, 1.0, 0.0));
db.set_node_property(n2, "title", Value::String("beta".into()));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "embedding", vec3(0.0, 0.0, 1.0));
db.set_node_property(n3, "title", Value::String("gamma".into()));
db.compact().expect("compact");
db
}
#[test]
#[cfg(feature = "vector-index")]
fn vector_index_after_compact_returns_results() {
let db = setup_compacted_vector_db();
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create vector index after compact");
let results = db
.vector_search("Doc", "embedding", &[1.0, 0.0, 0.0], 3, None, None)
.expect("vector search after compact");
assert_eq!(results.len(), 3, "should find all 3 pre-compact nodes");
}
#[test]
#[cfg(feature = "vector-index")]
fn vector_index_after_compact_nearest_neighbor_is_correct() {
let db = setup_compacted_vector_db();
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create vector index");
let results = db
.vector_search("Doc", "embedding", &[1.0, 0.0, 0.0], 1, None, None)
.expect("search");
assert_eq!(results.len(), 1);
let (nearest_id, distance) = results[0];
assert!(
distance < 0.01,
"exact match should have near-zero distance, got {distance}"
);
let title = db
.graph_store()
.get_node_property(nearest_id, &grafeo_common::types::PropertyKey::new("title"));
assert_eq!(title, Some(Value::String("alpha".into())));
}
#[test]
#[cfg(feature = "vector-index")]
fn rebuild_vector_index_after_compact() {
let db = setup_compacted_vector_db();
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create");
db.rebuild_vector_index("Doc", "embedding")
.expect("rebuild after compact");
let results = db
.vector_search("Doc", "embedding", &[0.0, 1.0, 0.0], 3, None, None)
.expect("search after rebuild");
assert_eq!(results.len(), 3);
}
#[test]
#[cfg(feature = "vector-index")]
fn vector_search_with_filter_after_compact() {
let mut db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "embedding", 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, "embedding", vec3(0.0, 1.0, 0.0));
db.set_node_property(n2, "category", Value::String("art".into()));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "embedding", vec3(0.0, 0.0, 1.0));
db.set_node_property(n3, "category", Value::String("science".into()));
db.compact().expect("compact");
db.create_property_index("category");
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create vector index");
let mut filters = std::collections::HashMap::new();
filters.insert("category".to_string(), Value::String("science".into()));
let results = db
.vector_search(
"Doc",
"embedding",
&[1.0, 0.0, 0.0],
3,
None,
Some(&filters),
)
.expect("filtered search");
assert_eq!(results.len(), 2, "should only return science nodes");
}
#[test]
#[cfg(feature = "text-index")]
fn text_index_after_compact() {
let mut db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Article"]);
db.set_node_property(
n1,
"body",
Value::String("the quick brown fox jumps over the lazy dog".into()),
);
let n2 = db.create_node(&["Article"]);
db.set_node_property(
n2,
"body",
Value::String("a fast brown fox leaps over a sleepy hound".into()),
);
let n3 = db.create_node(&["Article"]);
db.set_node_property(n3, "body", Value::String("the cat sat on the mat".into()));
db.compact().expect("compact");
db.create_text_index("Article", "body")
.expect("create text index after compact");
let results = db
.text_search("Article", "body", "fox", 10)
.expect("text search");
assert_eq!(
results.len(),
2,
"should find both fox articles from pre-compact data"
);
}
#[test]
#[cfg(feature = "vector-index")]
fn snapshot_compact_vector_index_round_trip() {
let snapshot_bytes = {
let db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "embedding", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "embedding", vec3(0.0, 1.0, 0.0));
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "embedding", vec3(0.0, 0.0, 1.0));
db.export_snapshot().expect("export")
};
let mut db = GrafeoDB::import_snapshot(&snapshot_bytes).expect("import");
db.compact().expect("compact after import");
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create vector index after snapshot+compact");
let results = db
.vector_search("Doc", "embedding", &[1.0, 0.0, 0.0], 3, None, None)
.expect("vector search after snapshot+compact");
assert_eq!(results.len(), 3, "should find all nodes from snapshot");
}
#[test]
#[cfg(feature = "vector-index")]
fn layered_store_has_vector_index_forwards_to_overlay() {
let mut db = GrafeoDB::new_in_memory();
let n = db.create_node(&["Doc"]);
db.set_node_property(n, "embedding", vec3(1.0, 0.0, 0.0));
db.compact().expect("compact");
let gs = db.graph_store();
assert!(!gs.has_vector_index("Doc", "embedding"));
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create");
let gs = db.graph_store();
assert!(gs.has_vector_index("Doc", "embedding"));
assert!(gs.vector_index_metric("Doc", "embedding").is_some());
}
#[test]
#[cfg(feature = "text-index")]
fn layered_store_has_text_index_forwards_to_overlay() {
let mut db = GrafeoDB::new_in_memory();
let n = db.create_node(&["Article"]);
db.set_node_property(n, "body", Value::String("hello world".into()));
db.compact().expect("compact");
let gs = db.graph_store();
assert!(!gs.has_text_index("Article", "body"));
db.create_text_index("Article", "body").expect("create");
let gs = db.graph_store();
assert!(gs.has_text_index("Article", "body"));
}
#[test]
#[cfg(feature = "vector-index")]
fn vector_index_after_recompact() {
let mut db = GrafeoDB::new_in_memory();
let n1 = db.create_node(&["Doc"]);
db.set_node_property(n1, "embedding", vec3(1.0, 0.0, 0.0));
let n2 = db.create_node(&["Doc"]);
db.set_node_property(n2, "embedding", vec3(0.0, 1.0, 0.0));
db.compact().expect("first compact");
let n3 = db.create_node(&["Doc"]);
db.set_node_property(n3, "embedding", vec3(0.0, 0.0, 1.0));
db.recompact().expect("recompact");
db.create_vector_index(
"Doc",
"embedding",
Some(3),
Some("cosine"),
None,
None,
None,
)
.expect("create index after recompact");
let results = db
.vector_search("Doc", "embedding", &[1.0, 0.0, 0.0], 3, None, None)
.expect("search after recompact");
assert_eq!(
results.len(),
3,
"should find nodes from both compaction rounds"
);
}