use std::path::Path;
use std::sync::{Arc, Mutex};
use graphforge_ir::{Binder, OntologyMode, RuntimeCatalog};
use graphforge_ontology::{OntologyCompiler, OntologyHandle, OntologyLoader};
fn hr_fixture() -> std::path::PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent() .unwrap()
.join("graphforge-ontology")
.join("tests")
.join("fixtures")
.join("hr.yaml")
}
fn hr_binder() -> Binder {
let doc = OntologyLoader::load_file(&hr_fixture()).expect("failed to load hr.yaml");
let runtime = OntologyCompiler::compile(&doc).expect("failed to compile HR ontology");
let handle = OntologyHandle::new(runtime);
let catalog = Arc::new(Mutex::new(RuntimeCatalog::new()));
Binder::new(Some(handle), catalog, OntologyMode::Advisory)
}
fn bind_query(query: &str) -> graphforge_ir::GraphPlan {
let ast = graphforge_cypher::parse(query)
.unwrap_or_else(|e| panic!("parse failed for query {query:?}: {e}"));
hr_binder().bind(&ast).unwrap_or_else(|errs| {
let msgs: Vec<_> = errs.iter().map(|e| e.message.as_str()).collect();
panic!("bind failed for query {query:?}: {msgs:?}");
})
}
fn golden_settings() -> insta::Settings {
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("ir_goldens"),
);
settings.set_omit_expression(true);
settings
}
#[test]
fn simple_node_scan() {
let plan = bind_query("MATCH (n:Person) RETURN n.name");
golden_settings().bind(|| {
insta::assert_json_snapshot!("simple_node_scan", plan);
});
}
#[test]
fn filtered_scan() {
let plan = bind_query("MATCH (n:Person) WHERE n.age > 30 RETURN n.name");
golden_settings().bind(|| {
insta::assert_json_snapshot!("filtered_scan", plan);
});
}
#[test]
fn one_hop_expand() {
let plan = bind_query("MATCH (a:Person)-[:MANAGES]->(b:Department) RETURN a.name, b.dept_name");
golden_settings().bind(|| {
insta::assert_json_snapshot!("one_hop_expand", plan);
});
}
#[test]
fn two_hop_expand() {
let plan = bind_query(
"MATCH (a:Employee)-[:REPORTS_TO]->(b:Manager)-[:MANAGES]->(c:Department) \
RETURN c.dept_name",
);
golden_settings().bind(|| {
insta::assert_json_snapshot!("two_hop_expand", plan);
});
}
#[test]
fn variable_length_expand() {
let plan = bind_query("MATCH (a:Person)-[:IS_FRIEND_OF*1..3]->(b:Person) RETURN b.name");
golden_settings().bind(|| {
insta::assert_json_snapshot!("variable_length_expand", plan);
});
}
#[test]
fn named_path_fixed_segment_and_return_p() {
let plan = bind_query(
"MATCH p = (a:Person)-[:IS_FRIEND_OF]->(b:Person) \
RETURN length(p) AS hops, nodes(p) AS ns, relationships(p) AS rels, p",
);
golden_settings().bind(|| {
insta::assert_json_snapshot!("named_path_fixed_segment_and_return_p", plan);
});
}
#[test]
fn named_path_variable_functions() {
let plan = bind_query(
"MATCH p = (a:Person)-[:IS_FRIEND_OF*1..2]->(b:Person) \
RETURN length(p) AS hops, relationships(p) AS rels, nodes(p) AS ns",
);
golden_settings().bind(|| {
insta::assert_json_snapshot!("named_path_variable_functions", plan);
});
}
#[test]
fn optional_match() {
let plan = bind_query(
"MATCH (a:Employee) \
OPTIONAL MATCH (a)-[:REPORTS_TO]->(b:Manager) \
RETURN a.employee_id, b.title",
);
golden_settings().bind(|| {
insta::assert_json_snapshot!("optional_match", plan);
});
}
#[test]
fn aggregation() {
let plan = bind_query("MATCH (n:Person) RETURN count(n) AS total");
golden_settings().bind(|| {
insta::assert_json_snapshot!("aggregation", plan);
});
}
#[test]
fn order_by_limit() {
let plan = bind_query("MATCH (n:Person) RETURN n.name ORDER BY n.name DESC LIMIT 10");
golden_settings().bind(|| {
insta::assert_json_snapshot!("order_by_limit", plan);
});
}
#[test]
fn with_pipeline() {
let plan = bind_query("MATCH (n:Person) WITH n.name AS nm WHERE nm = 'Alice' RETURN nm");
golden_settings().bind(|| {
insta::assert_json_snapshot!("with_pipeline", plan);
});
}
#[test]
fn unwind() {
let plan = bind_query("UNWIND [1,2,3] AS x RETURN x");
golden_settings().bind(|| {
insta::assert_json_snapshot!("unwind", plan);
});
}
#[test]
fn parameter() {
let plan = bind_query("MATCH (n:Employee) WHERE n.employee_id = $eid RETURN n.name");
golden_settings().bind(|| {
insta::assert_json_snapshot!("parameter", plan);
});
}