use manifoldb::collection::{DistanceMetric, PointStruct};
use manifoldb::{Database, EntityId};
use serde_json::json;
#[test]
fn test_graph_constrained_search_basic() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("symbols")
.expect("builder")
.with_dense_vector("embedding", 4, DistanceMetric::DotProduct)
.build()
.expect("create collection");
let mut tx = db.begin().expect("begin");
let repo_a = tx
.create_entity()
.expect("create")
.with_label("Repository")
.with_property("name", "repo-a");
let repo_b = tx
.create_entity()
.expect("create")
.with_label("Repository")
.with_property("name", "repo-b");
tx.put_entity(&repo_a).expect("put");
tx.put_entity(&repo_b).expect("put");
let file_a1 =
tx.create_entity().expect("create").with_label("Symbol").with_property("name", "class_a1");
let file_a2 =
tx.create_entity().expect("create").with_label("Symbol").with_property("name", "class_a2");
let file_a3 =
tx.create_entity().expect("create").with_label("Symbol").with_property("name", "class_a3");
let file_b1 =
tx.create_entity().expect("create").with_label("Symbol").with_property("name", "class_b1");
let file_b2 =
tx.create_entity().expect("create").with_label("Symbol").with_property("name", "class_b2");
tx.put_entity(&file_a1).expect("put");
tx.put_entity(&file_a2).expect("put");
tx.put_entity(&file_a3).expect("put");
tx.put_entity(&file_b1).expect("put");
tx.put_entity(&file_b2).expect("put");
let e1 = tx.create_edge(repo_a.id, file_a1.id, "CONTAINS").expect("edge");
let e2 = tx.create_edge(repo_a.id, file_a2.id, "CONTAINS").expect("edge");
let e3 = tx.create_edge(repo_a.id, file_a3.id, "CONTAINS").expect("edge");
let e4 = tx.create_edge(repo_b.id, file_b1.id, "CONTAINS").expect("edge");
let e5 = tx.create_edge(repo_b.id, file_b2.id, "CONTAINS").expect("edge");
tx.put_edge(&e1).expect("put");
tx.put_edge(&e2).expect("put");
tx.put_edge(&e3).expect("put");
tx.put_edge(&e4).expect("put");
tx.put_edge(&e5).expect("put");
tx.commit().expect("commit");
let collection = db.collection("symbols").expect("collection");
collection
.upsert_point(
PointStruct::new(file_a1.id.as_u64())
.with_payload(json!({"name": "class_a1"}))
.with_vector("embedding", vec![1.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(file_a2.id.as_u64())
.with_payload(json!({"name": "class_a2"}))
.with_vector("embedding", vec![0.7, 0.7, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(file_a3.id.as_u64())
.with_payload(json!({"name": "class_a3"}))
.with_vector("embedding", vec![0.5, 0.5, 0.5, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(file_b1.id.as_u64())
.with_payload(json!({"name": "class_b1"}))
.with_vector("embedding", vec![0.9, 0.3, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(file_b2.id.as_u64())
.with_payload(json!({"name": "class_b2"}))
.with_vector("embedding", vec![0.0, 1.0, 0.0, 0.0]),
)
.expect("insert");
let unconstrained = db
.search("symbols", "embedding")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.limit(5)
.execute()
.expect("search");
assert_eq!(unconstrained.len(), 5);
assert_eq!(unconstrained[0].entity.id, file_a1.id);
let constrained = db
.search("symbols", "embedding")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.within_traversal(repo_a.id, |p| p.edge_out("CONTAINS"))
.limit(5)
.execute()
.expect("search");
assert_eq!(constrained.len(), 3);
let result_ids: Vec<EntityId> = constrained.iter().map(|r| r.entity.id).collect();
assert!(result_ids.contains(&file_a1.id), "should contain file_a1");
assert!(result_ids.contains(&file_a2.id), "should contain file_a2");
assert!(result_ids.contains(&file_a3.id), "should contain file_a3");
assert!(!result_ids.contains(&file_b1.id), "should NOT contain file_b1");
assert!(!result_ids.contains(&file_b2.id), "should NOT contain file_b2");
assert_eq!(constrained[0].entity.id, file_a1.id, "file_a1 should be first");
}
#[test]
fn test_graph_constrained_search_variable_length() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("projects")
.expect("builder")
.with_dense_vector("features", 4, DistanceMetric::Cosine)
.build()
.expect("create");
let mut tx = db.begin().expect("begin");
let org = tx.create_entity().expect("create").with_label("Org").with_property("name", "acme");
let team_a =
tx.create_entity().expect("create").with_label("Team").with_property("name", "team-a");
let team_b =
tx.create_entity().expect("create").with_label("Team").with_property("name", "team-b");
let proj_a1 =
tx.create_entity().expect("create").with_label("Project").with_property("name", "proj-a1");
let proj_a2 =
tx.create_entity().expect("create").with_label("Project").with_property("name", "proj-a2");
let proj_b1 =
tx.create_entity().expect("create").with_label("Project").with_property("name", "proj-b1");
let external =
tx.create_entity().expect("create").with_label("Project").with_property("name", "external");
tx.put_entity(&org).expect("put");
tx.put_entity(&team_a).expect("put");
tx.put_entity(&team_b).expect("put");
tx.put_entity(&proj_a1).expect("put");
tx.put_entity(&proj_a2).expect("put");
tx.put_entity(&proj_b1).expect("put");
tx.put_entity(&external).expect("put");
let e1 = tx.create_edge(org.id, team_a.id, "CONTAINS").expect("edge");
let e2 = tx.create_edge(org.id, team_b.id, "CONTAINS").expect("edge");
let e3 = tx.create_edge(team_a.id, proj_a1.id, "CONTAINS").expect("edge");
let e4 = tx.create_edge(team_a.id, proj_a2.id, "CONTAINS").expect("edge");
let e5 = tx.create_edge(team_b.id, proj_b1.id, "CONTAINS").expect("edge");
tx.put_edge(&e1).expect("put");
tx.put_edge(&e2).expect("put");
tx.put_edge(&e3).expect("put");
tx.put_edge(&e4).expect("put");
tx.put_edge(&e5).expect("put");
tx.commit().expect("commit");
let collection = db.collection("projects").expect("collection");
collection
.upsert_point(
PointStruct::new(proj_a1.id.as_u64())
.with_payload(json!({"name": "proj-a1", "focus": "ml"}))
.with_vector("features", vec![1.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(proj_a2.id.as_u64())
.with_payload(json!({"name": "proj-a2", "focus": "data"}))
.with_vector("features", vec![0.5, 0.5, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(proj_b1.id.as_u64())
.with_payload(json!({"name": "proj-b1", "focus": "infra"}))
.with_vector("features", vec![0.3, 0.3, 0.3, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(external.id.as_u64())
.with_payload(json!({"name": "external", "focus": "ml"}))
.with_vector("features", vec![0.95, 0.05, 0.0, 0.0]),
)
.expect("insert");
let results = db
.search("projects", "features")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.within_traversal(org.id, |p| p.edge_out("CONTAINS").variable_length(1, 3))
.limit(10)
.execute()
.expect("search");
let result_ids: Vec<EntityId> = results.iter().map(|r| r.entity.id).collect();
assert_eq!(results.len(), 3, "should find exactly 3 projects in org");
assert!(result_ids.contains(&proj_a1.id), "should contain proj_a1");
assert!(result_ids.contains(&proj_a2.id), "should contain proj_a2");
assert!(result_ids.contains(&proj_b1.id), "should contain proj_b1");
assert!(!result_ids.contains(&external.id), "should NOT contain external");
assert_eq!(results[0].entity.id, proj_a1.id);
}
#[test]
fn test_graph_constrained_search_with_filter() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("items")
.expect("builder")
.with_dense_vector("vec", 4, DistanceMetric::Cosine)
.build()
.expect("create");
let mut tx = db.begin().expect("begin");
let root = tx.create_entity().expect("create").with_label("Root");
let item1 =
tx.create_entity().expect("create").with_label("Item").with_property("status", "active");
let item2 =
tx.create_entity().expect("create").with_label("Item").with_property("status", "archived");
let item3 =
tx.create_entity().expect("create").with_label("Item").with_property("status", "active");
let item4 =
tx.create_entity().expect("create").with_label("Item").with_property("status", "active");
tx.put_entity(&root).expect("put");
tx.put_entity(&item1).expect("put");
tx.put_entity(&item2).expect("put");
tx.put_entity(&item3).expect("put");
tx.put_entity(&item4).expect("put");
let e1 = tx.create_edge(root.id, item1.id, "CONTAINS").expect("edge");
let e2 = tx.create_edge(root.id, item2.id, "CONTAINS").expect("edge");
let e3 = tx.create_edge(root.id, item3.id, "CONTAINS").expect("edge");
tx.put_edge(&e1).expect("put");
tx.put_edge(&e2).expect("put");
tx.put_edge(&e3).expect("put");
tx.commit().expect("commit");
let collection = db.collection("items").expect("collection");
collection
.upsert_point(
PointStruct::new(item1.id.as_u64())
.with_payload(json!({"status": "active"}))
.with_vector("vec", vec![1.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(item2.id.as_u64())
.with_payload(json!({"status": "archived"}))
.with_vector("vec", vec![0.9, 0.1, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(item3.id.as_u64())
.with_payload(json!({"status": "active"}))
.with_vector("vec", vec![0.5, 0.5, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(item4.id.as_u64())
.with_payload(json!({"status": "active"}))
.with_vector("vec", vec![0.95, 0.0, 0.0, 0.0]),
)
.expect("insert");
let results = db
.search("items", "vec")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.within_traversal(root.id, |p| p.edge_out("CONTAINS"))
.filter(manifoldb::Filter::eq("status", "active"))
.limit(10)
.execute()
.expect("search");
let result_ids: Vec<EntityId> = results.iter().map(|r| r.entity.id).collect();
assert_eq!(results.len(), 2, "should find 2 active items in root");
assert!(result_ids.contains(&item1.id), "should contain item1");
assert!(result_ids.contains(&item3.id), "should contain item3");
assert!(!result_ids.contains(&item2.id), "should NOT contain item2 (archived)");
assert!(!result_ids.contains(&item4.id), "should NOT contain item4 (not in root)");
}
#[test]
fn test_search_without_traversal_constraint() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("docs")
.expect("builder")
.with_dense_vector("emb", 4, DistanceMetric::DotProduct)
.build()
.expect("create");
let collection = db.collection("docs").expect("collection");
collection
.upsert_point(
PointStruct::new(1)
.with_payload(json!({"title": "doc1"}))
.with_vector("emb", vec![1.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
collection
.upsert_point(
PointStruct::new(2)
.with_payload(json!({"title": "doc2"}))
.with_vector("emb", vec![0.5, 0.5, 0.0, 0.0]),
)
.expect("insert");
let results = db
.search("docs", "emb")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.limit(10)
.execute()
.expect("search");
assert_eq!(results.len(), 2);
assert_eq!(results[0].entity.id, EntityId::new(1));
}
#[test]
fn test_graph_constrained_search_empty_traversal() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("items")
.expect("builder")
.with_dense_vector("vec", 4, DistanceMetric::Cosine)
.build()
.expect("create");
let mut tx = db.begin().expect("begin");
let isolated = tx.create_entity().expect("create").with_label("Root");
let item = tx.create_entity().expect("create").with_label("Item");
tx.put_entity(&isolated).expect("put");
tx.put_entity(&item).expect("put");
tx.commit().expect("commit");
let collection = db.collection("items").expect("collection");
collection
.upsert_point(
PointStruct::new(item.id.as_u64())
.with_payload(json!({"name": "item"}))
.with_vector("vec", vec![1.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
let results = db
.search("items", "vec")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.within_traversal(isolated.id, |p| p.edge_out("CONTAINS"))
.limit(10)
.execute()
.expect("search");
assert!(results.is_empty(), "should return no results when traversal yields nothing");
}
#[test]
fn test_graph_constrained_search_respects_limit() {
let db = Database::in_memory().expect("failed to create db");
db.create_collection("items")
.expect("builder")
.with_dense_vector("vec", 4, DistanceMetric::Cosine)
.build()
.expect("create");
let mut tx = db.begin().expect("begin");
let root = tx.create_entity().expect("create").with_label("Root");
tx.put_entity(&root).expect("put");
let mut items = Vec::new();
for _ in 0..10 {
let item = tx.create_entity().expect("create").with_label("Item");
tx.put_entity(&item).expect("put");
let edge = tx.create_edge(root.id, item.id, "CONTAINS").expect("edge");
tx.put_edge(&edge).expect("put");
items.push(item);
}
tx.commit().expect("commit");
let collection = db.collection("items").expect("collection");
for (i, item) in items.iter().enumerate() {
collection
.upsert_point(
PointStruct::new(item.id.as_u64())
.with_payload(json!({"index": i}))
.with_vector("vec", vec![(10 - i) as f32 / 10.0, 0.0, 0.0, 0.0]),
)
.expect("insert");
}
let results = db
.search("items", "vec")
.expect("search builder")
.query(vec![1.0, 0.0, 0.0, 0.0])
.within_traversal(root.id, |p| p.edge_out("CONTAINS"))
.limit(3)
.execute()
.expect("search");
assert_eq!(results.len(), 3, "should respect limit of 3");
}