mod test_helpers;
use test_helpers::TestDb;
#[test]
fn match_all_nodes_empty_graph() {
let db = TestDb::new();
db.assert_count("MATCH (n) RETURN n", 0);
}
#[test]
fn match_all_nodes_single_node() {
let db = TestDb::new();
db.run("CREATE (n:User {name: 'Alice'})");
db.assert_count("MATCH (n) RETURN n", 1);
}
#[test]
fn match_all_nodes_multiple() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:User {name: 'Bob'})");
db.run("CREATE (c:Product {name: 'Widget'})");
db.assert_count("MATCH (n) RETURN n", 3);
}
#[test]
fn match_by_label() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:Product {name: 'Widget'})");
db.assert_count("MATCH (n:User) RETURN n", 1);
db.assert_count("MATCH (n:Product) RETURN n", 1);
}
#[test]
fn match_by_label_no_results() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
let err = db.run_err("MATCH (n:Product) RETURN n");
assert!(err.contains("Unknown label"));
}
#[test]
fn match_multiple_labels() {
let db = TestDb::new();
db.run("CREATE (a:User:Admin {name: 'Alice'})");
db.run("CREATE (b:User {name: 'Bob'})");
let rows = db.run("MATCH (n:User:Admin) RETURN n");
assert!(rows.len() <= 2);
}
#[test]
fn match_node_with_multiple_labels_via_single() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (m:Manager) RETURN m.name AS name");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Frank");
}
#[test]
fn match_by_property() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice', age: 30})");
db.run("CREATE (b:User {name: 'Bob', age: 25})");
db.assert_count("MATCH (n:User {name: 'Alice'}) RETURN n", 1);
}
#[test]
fn match_by_integer_property() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice', age: 30})");
db.run("CREATE (b:User {name: 'Bob', age: 25})");
db.assert_count("MATCH (n:User {age: 30}) RETURN n", 1);
}
#[test]
fn match_by_multiple_properties() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice', age: 30})");
db.run("CREATE (b:User {name: 'Bob', age: 30})");
db.assert_count("MATCH (n:User {name: 'Alice', age: 30}) RETURN n", 1);
}
#[test]
fn match_property_no_match() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.assert_count("MATCH (n:User {name: 'Bob'}) RETURN n", 0);
}
#[test]
fn match_relationship_by_property_value() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person)-[:ASSIGNED_TO {role:'lead'}]->(proj:Project) RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Carol"]);
}
#[test]
fn match_directed_relationship() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[:FOLLOWS]->(b) RETURN b");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["b"]["properties"]["name"], "Bob");
}
#[test]
fn match_relationship_with_variable() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[r:FOLLOWS]->(b) RETURN r");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["r"]["type"], "FOLLOWS");
assert_eq!(rows[0]["r"]["properties"]["since"], 2020);
}
#[test]
fn match_reverse_direction() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (b:User {name: 'Bob'})<-[:FOLLOWS]-(a) RETURN a");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["a"]["properties"]["name"], "Alice");
}
#[test]
fn match_left_arrow_reverses_traversal() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (c:City {name:'London'})<-[:LIVES_IN]-(p:Person) RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Carol", "Frank"]);
}
#[test]
fn match_undirected_relationship() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[:KNOWS]-(b) RETURN b");
assert!(!rows.is_empty());
}
#[test]
fn match_undirected_finds_both_directions() {
let db = TestDb::new();
db.run("CREATE (a:N {id:1})-[:R]->(b:N {id:2})");
let rows = db.run("MATCH (x:N)-[:R]-(y:N) RETURN x.id AS x, y.id AS y");
assert_eq!(rows.len(), 2);
}
#[test]
fn match_relationship_any_type() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[r]->(b) RETURN r");
assert_eq!(rows.len(), 2); }
#[test]
fn match_chain_two_hops() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[:FOLLOWS]->(b)-[:FOLLOWS]->(c) RETURN c");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["c"]["properties"]["name"], "Carol");
}
#[test]
fn match_two_hop_manager_project() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Manager)-[:MANAGES]->(e:Person)-[:ASSIGNED_TO]->(p:Project) \
RETURN m.name AS mgr, e.name AS emp, p.name AS proj",
);
assert_eq!(rows.len(), 3);
}
#[test]
fn match_manager_to_employee_direct() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (m:Person {name:'Frank'})-[:MANAGES]->(e:Person) RETURN e.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Bob", "Eve"]);
}
#[test]
fn match_three_hop_through_company() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person)-[:WORKS_AT]->(c:Company {name:'Acme'}) RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Bob", "Carol", "Dave", "Eve", "Frank"]);
}
#[test]
fn match_triangle_pattern() {
let db = TestDb::new();
db.run("CREATE (a:T {name:'A'})-[:E]->(b:T {name:'B'})");
db.run("MATCH (b:T {name:'B'}) CREATE (b)-[:E]->(c:T {name:'C'})");
db.run("MATCH (a:T {name:'A'}), (c:T {name:'C'}) CREATE (a)-[:E]->(c)");
let rows = db.run(
"MATCH (a:T)-[:E]->(b:T)-[:E]->(c:T), (a)-[:E]->(c) RETURN a.name AS a, b.name AS b, c.name AS c",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["a"], "A");
assert_eq!(rows[0]["b"], "B");
assert_eq!(rows[0]["c"], "C");
}
#[test]
fn match_disconnected_patterns_cross_product() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:User {name: 'Bob'})");
db.run("CREATE (c:Product {name: 'Widget'})");
db.assert_count("MATCH (u:User), (p:Product) RETURN u, p", 2);
}
#[test]
fn match_cross_product_three_labels() {
let db = TestDb::new();
db.seed_org_graph();
let count = db
.exec_count("MATCH (p:Project), (c:City) RETURN p.name, c.name")
.unwrap();
assert_eq!(count, 6); }
#[test]
fn match_same_variable_constrains_same_node() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'}) RETURN a");
assert_eq!(rows.len(), 1);
}
#[test]
fn match_returns_all_bound_variables() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name: 'Alice'})-[r:FOLLOWS]->(b) RETURN a, r, b");
assert_eq!(rows.len(), 1);
assert!(rows[0].get("a").is_some());
assert!(rows[0].get("r").is_some());
assert!(rows[0].get("b").is_some());
}
#[test]
fn match_same_variable_both_sides_constrains_identity() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (a:Person)-[:MANAGES]->(b:Person)-[:ASSIGNED_TO]->(p:Project) \
RETURN a.name AS mgr, b.name AS emp, p.name AS proj",
);
assert!(rows.len() >= 3);
}
#[test]
fn match_reuse_in_where_constrains_single_node() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (a:Person), (b:Person) WHERE a.name = b.name RETURN a.name AS name");
assert_eq!(rows.len(), 6);
}
#[test]
fn match_no_relationships_returns_empty() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.assert_count("MATCH (a:User {name: 'Alice'})-[:FOLLOWS]->(b) RETURN b", 0);
}
#[test]
fn match_nonexistent_relationship_chain_returns_empty() {
let db = TestDb::new();
db.seed_org_graph();
db.assert_count(
"MATCH (p:Person)-[:MANAGES]->(e:Person) WHERE e.name = 'NONEXISTENT' RETURN p",
0,
);
}
#[test]
fn optional_match_returns_null_when_no_match() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
let rows = db.run("OPTIONAL MATCH (n) RETURN n");
assert!(!rows.is_empty());
}
#[test]
fn optional_match_null_for_missing_relationship() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (d:Person {name:'Dave'}) \
OPTIONAL MATCH (d)-[:MANAGES]->(e:Person) \
RETURN d.name AS name, e.name AS managed",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Dave");
assert!(rows[0]["managed"].is_null());
}
#[test]
fn match_cycle_in_graph() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:User {name: 'Bob'})");
db.run("MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'}) CREATE (a)-[:FOLLOWS]->(b)");
db.run("MATCH (a:User {name: 'Alice'}), (b:User {name: 'Bob'}) CREATE (b)-[:FOLLOWS]->(a)");
db.assert_count("MATCH (a)-[:FOLLOWS]->(b) RETURN a, b", 2);
}
#[test]
fn match_in_cycle_returns_all_edges() {
let db = TestDb::new();
db.seed_cycle(3);
let rows = db.run("MATCH (a:Ring)-[:LOOP]->(b:Ring) RETURN a.idx AS a, b.idx AS b");
assert_eq!(rows.len(), 3);
}
#[test]
fn match_two_hops_in_cycle() {
let db = TestDb::new();
db.seed_cycle(3);
let rows =
db.run("MATCH (a:Ring)-[:LOOP]->(b:Ring)-[:LOOP]->(c:Ring) RETURN a.idx AS a, c.idx AS c");
assert_eq!(rows.len(), 3);
}
#[test]
fn count_relationships_by_type() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (a:Person)-[r:WORKS_AT]->(b:Company) RETURN count(r) AS c");
assert_eq!(rows[0]["c"], 6);
}
#[test]
fn count_manages_relationships() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (a)-[r:MANAGES]->(b) RETURN count(r) AS c");
assert_eq!(rows[0]["c"], 4);
}
#[test]
fn match_all_outgoing_from_person() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (a:Person {name:'Alice'})-[r]->(x) RETURN r");
assert_eq!(rows.len(), 3); }
#[test]
fn match_by_boolean_property_in_pattern() {
let db = TestDb::new();
db.run("CREATE (:Flag {name: 'on', active: true})");
db.run("CREATE (:Flag {name: 'off', active: false})");
db.assert_count("MATCH (f:Flag {active: true}) RETURN f", 1);
}
#[test]
fn match_by_float_property_in_pattern() {
let db = TestDb::new();
db.run("CREATE (:Metric {name: 'temp', value: 36.6})");
db.run("CREATE (:Metric {name: 'pressure', value: 101.3})");
db.assert_count("MATCH (m:Metric {value: 36.6}) RETURN m", 1);
}
#[test]
fn match_property_and_where_combined() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person {dept: 'Engineering'}) WHERE p.age > 30 RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Frank"]);
}
#[test]
fn match_four_hop_chain() {
let db = TestDb::new();
db.seed_chain(6);
let rows = db.run(
"MATCH (a:Chain {idx:0})-[:NEXT]->(b)-[:NEXT]->(c)-[:NEXT]->(d)-[:NEXT]->(e) \
RETURN e.idx AS idx",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["idx"], 4);
}
#[test]
fn match_diamond_pattern() {
let db = TestDb::new();
db.run("CREATE (:D {name:'top'})");
db.run("CREATE (:D {name:'left'})");
db.run("CREATE (:D {name:'right'})");
db.run("CREATE (:D {name:'bottom'})");
db.run("MATCH (t:D {name:'top'}), (l:D {name:'left'}) CREATE (t)-[:E]->(l)");
db.run("MATCH (t:D {name:'top'}), (r:D {name:'right'}) CREATE (t)-[:E]->(r)");
db.run("MATCH (l:D {name:'left'}), (b:D {name:'bottom'}) CREATE (l)-[:E]->(b)");
db.run("MATCH (r:D {name:'right'}), (b:D {name:'bottom'}) CREATE (r)-[:E]->(b)");
let rows = db.run(
"MATCH (t:D {name:'top'})-[:E]->(mid:D)-[:E]->(b:D {name:'bottom'}) \
RETURN mid.name AS via",
);
assert_eq!(rows.len(), 2);
let mut vias: Vec<&str> = rows.iter().map(|r| r["via"].as_str().unwrap()).collect();
vias.sort();
assert_eq!(vias, vec!["left", "right"]);
}
#[test]
fn match_star_pattern_hub_and_spokes() {
let db = TestDb::new();
db.run("CREATE (:Hub {name:'center'})");
for i in 1..=5 {
db.run(&format!("CREATE (:Spoke {{id:{i}}})"));
db.run(&format!(
"MATCH (h:Hub), (s:Spoke {{id:{i}}}) CREATE (h)-[:ARM]->(s)"
));
}
db.assert_count("MATCH (h:Hub)-[:ARM]->(s:Spoke) RETURN s", 5);
}
#[test]
fn match_all_relationships_in_graph() {
let db = TestDb::new();
db.seed_social_graph();
db.assert_count("MATCH (a)-[r]->(b) RETURN r", 3);
}
#[test]
fn match_multiple_independent_match_clauses() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person {name:'Alice'})-[:WORKS_AT]->(c:Company) \
MATCH (p)-[:LIVES_IN]->(city:City) \
RETURN c.name AS company, city.name AS city",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["company"], "Acme");
assert_eq!(rows[0]["city"], "London");
}
#[test]
fn match_two_independent_patterns_constrained() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Person)-[:MANAGES]->(e:Person) \
MATCH (m)-[:LIVES_IN]->(c:City)<-[:LIVES_IN]-(e) \
RETURN m.name AS mgr, e.name AS emp, c.name AS city",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["mgr"], "Frank");
assert_eq!(rows[0]["emp"], "Alice");
assert_eq!(rows[0]["city"], "London");
}
#[test]
fn match_three_way_cross_product() {
let db = TestDb::new();
db.run("CREATE (:Color {name:'red'})");
db.run("CREATE (:Color {name:'blue'})");
db.run("CREATE (:Size {name:'small'})");
db.run("CREATE (:Size {name:'large'})");
db.run("CREATE (:Shape {name:'circle'})");
db.assert_count(
"MATCH (c:Color), (s:Size), (sh:Shape) RETURN c.name, s.name, sh.name",
4,
);
}
#[test]
fn match_direct_dependencies() {
let db = TestDb::new();
db.seed_dependency_graph();
let deps = db.sorted_strings(
"MATCH (src:Package {name:'app'})-[:DEPENDS_ON]->(dep:Package) RETURN dep.name AS name",
"name",
);
assert_eq!(deps, vec!["auth", "log", "web"]);
}
#[test]
fn match_transitive_dependencies_two_hops() {
let db = TestDb::new();
db.seed_dependency_graph();
let indirect = db.sorted_strings(
"MATCH (src:Package {name:'app'})-[:DEPENDS_ON]->(mid:Package)-[:DEPENDS_ON]->(dep:Package) \
RETURN DISTINCT dep.name AS name",
"name",
);
assert!(indirect.contains(&"crypto".to_string()));
assert!(indirect.contains(&"util".to_string()));
assert!(indirect.contains(&"log".to_string()));
}
#[test]
fn match_packages_depended_on_by_most() {
let db = TestDb::new();
db.seed_dependency_graph();
let rows = db.run(
"MATCH (dep:Package)<-[:DEPENDS_ON]-(consumer:Package) \
WHERE dep.name = 'log' \
RETURN count(consumer) AS cnt",
);
assert_eq!(rows[0]["cnt"], 3);
}
#[test]
fn match_direct_routes_from_station() {
let db = TestDb::new();
db.seed_transport_graph();
let destinations = db.sorted_strings(
"MATCH (src:Station {name:'Amsterdam'})-[:ROUTE]->(dest:Station) \
RETURN dest.name AS name",
"name",
);
assert_eq!(destinations, vec!["Rotterdam", "Utrecht"]);
}
#[test]
fn match_routes_with_distance_filter() {
let db = TestDb::new();
db.seed_transport_graph();
let rows = db.run(
"MATCH (a:Station)-[r:ROUTE]->(b:Station) \
WHERE r.distance <= 40 \
RETURN a.name AS from, b.name AS to, r.distance AS dist \
ORDER BY r.distance ASC",
);
assert_eq!(rows.len(), 4);
assert_eq!(rows[0]["dist"], 25);
}
#[test]
fn match_two_hop_routes() {
let db = TestDb::new();
db.seed_transport_graph();
let rows = db.run(
"MATCH (src:Station {name:'Amsterdam'})-[:ROUTE]->(mid:Station)-[:ROUTE]->(dst:Station {name:'Den Haag'}) \
RETURN mid.name AS via",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["via"], "Rotterdam");
}
#[test]
fn match_movies_rated_by_viewer() {
let db = TestDb::new();
db.seed_recommendation_graph();
let movies = db.sorted_strings(
"MATCH (v:Viewer {name:'Alice'})-[:RATED]->(m:Movie) RETURN m.title AS title",
"title",
);
assert_eq!(movies, vec!["Amelie", "Inception", "Matrix"]);
}
#[test]
fn match_viewers_who_rated_same_movie() {
let db = TestDb::new();
db.seed_recommendation_graph();
let others = db.sorted_strings(
"MATCH (v:Viewer)-[:RATED]->(m:Movie {title:'Matrix'}) \
WHERE v.name <> 'Alice' \
RETURN v.name AS name",
"name",
);
assert_eq!(others, vec!["Bob"]);
}
#[test]
fn match_high_rated_movies() {
let db = TestDb::new();
db.seed_recommendation_graph();
let titles = db.sorted_strings(
"MATCH (v:Viewer)-[r:RATED]->(m:Movie) \
WHERE r.score >= 5 \
RETURN DISTINCT m.title AS title",
"title",
);
assert_eq!(titles, vec!["Inception", "Matrix"]);
}
#[test]
fn match_self_loop_typed() {
let db = TestDb::new();
db.run("CREATE (n:Recursive {name:'self'})");
db.run("MATCH (n:Recursive {name:'self'}) CREATE (n)-[:REFS]->(n)");
let rows = db.run(
"MATCH (a:Recursive)-[r:REFS]->(b:Recursive) WHERE a.name = b.name RETURN a.name AS name",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "self");
}
#[test]
fn match_untyped_relationship_returns_all_types() {
let db = TestDb::new();
db.run("CREATE (a:N {id:1}), (b:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:TYPE_A]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:TYPE_B]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:TYPE_C]->(b)");
db.assert_count("MATCH (a:N {id:1})-[r]->(b:N {id:2}) RETURN r", 3);
}
#[test]
fn optional_match_with_data_returns_rows() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("OPTIONAL MATCH (n:User) RETURN n");
assert_eq!(rows.len(), 3);
}
#[test]
fn match_node_requires_all_specified_labels() {
let db = TestDb::new();
db.run("CREATE (:A:B {name:'both'})");
db.run("CREATE (:A {name:'only_a'})");
db.run("CREATE (:B {name:'only_b'})");
db.assert_count("MATCH (n:A:B) RETURN n", 1);
db.assert_count("MATCH (n:A) RETURN n", 2);
db.assert_count("MATCH (n:B) RETURN n", 2);
}
#[test]
fn match_multi_label_no_node_has_all() {
let db = TestDb::new();
db.run("CREATE (:X {id:1})");
db.run("CREATE (:Y {id:2})");
db.assert_count("MATCH (n:X:Y) RETURN n", 0);
}
#[test]
fn match_multi_label_subset_of_node_labels() {
let db = TestDb::new();
db.run("CREATE (:A:B:C {name:'triple'})");
db.run("CREATE (:A:B {name:'double'})");
db.run("CREATE (:A {name:'single'})");
db.assert_count("MATCH (n:A:B) RETURN n", 2);
db.assert_count("MATCH (n:A:B:C) RETURN n", 1);
}
#[test]
fn match_multi_label_anonymous_node() {
let db = TestDb::new();
db.run("CREATE (:Person:Manager {name:'Frank'})");
db.run("CREATE (:Person {name:'Alice'})");
db.assert_count("MATCH (:Person:Manager) RETURN 1 AS x", 1);
}
#[test]
fn match_multi_label_in_relationship_pattern() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Person:Manager)-[:MANAGES]->(e:Person) RETURN e.name AS name ORDER BY e.name",
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["name"], "Alice");
}
#[test]
fn match_single_label_still_works() {
let db = TestDb::new();
db.run("CREATE (:User {name:'Alice'})");
db.run("CREATE (:User:Admin {name:'Bob'})");
db.assert_count("MATCH (n:User) RETURN n", 2);
}
#[test]
fn match_no_label_matches_all_nodes() {
let db = TestDb::new();
db.run("CREATE (:User {name:'Alice'})");
db.run("CREATE (:Product {name:'Widget'})");
db.run("CREATE (n {name:'bare'})");
db.assert_count("MATCH (n) RETURN n", 3);
}
#[test]
fn match_anonymous_node_with_label() {
let db = TestDb::new();
db.run("CREATE (:Person {name:'Alice'})");
db.run("CREATE (:Person {name:'Bob'})");
db.assert_count("MATCH (:Person) RETURN 1 AS x", 2);
}
#[test]
fn match_anonymous_node_in_relationship_pattern() {
let db = TestDb::new();
db.seed_social_graph();
db.assert_count("MATCH (:User)-[:FOLLOWS]->(:User) RETURN 1 AS x", 2);
}
#[test]
fn match_mixed_named_and_anonymous_nodes() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (a:User {name:'Alice'})-[:FOLLOWS]->(:User) RETURN a.name AS name");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Alice");
}
#[test]
fn match_anonymous_source_named_target() {
let db = TestDb::new();
db.seed_social_graph();
let names = db.sorted_strings(
"MATCH (:User)-[:FOLLOWS]->(b:User) RETURN b.name AS name",
"name",
);
assert_eq!(names, vec!["Bob", "Carol"]);
}
#[test]
fn match_multiple_anonymous_nodes_in_chain() {
let db = TestDb::new();
db.seed_social_graph();
db.assert_count(
"MATCH (a:User {name:'Alice'})-[:FOLLOWS]->(:User)-[:FOLLOWS]->(c:User) RETURN c",
1,
);
}
#[test]
fn match_all_anonymous_chain() {
let db = TestDb::new();
db.seed_social_graph();
db.assert_count(
"MATCH (:User)-[:FOLLOWS]->(:User)-[:FOLLOWS]->(:User) RETURN 1 AS x",
1,
);
}
#[test]
fn match_anonymous_node_cross_product() {
let db = TestDb::new();
db.run("CREATE (:X {id:1})");
db.run("CREATE (:X {id:2})");
db.run("CREATE (:Y {id:3})");
db.assert_count("MATCH (:X), (:Y) RETURN 1 AS x", 2);
}
#[test]
fn match_anonymous_with_property_constraint() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (:Person {name:'Frank'})-[:MANAGES]->(e:Person) RETURN e.name AS name ORDER BY e.name",
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["name"], "Alice");
}
#[test]
fn match_anonymous_does_not_leak_into_return() {
let db = TestDb::new();
db.run("CREATE (:A {x:1})-[:R]->(:B {y:2})");
let rows = db.run("MATCH (a:A)-[:R]->(:B) RETURN a");
assert_eq!(rows.len(), 1);
let obj = rows[0].as_object().unwrap();
assert_eq!(obj.len(), 1);
assert!(obj.contains_key("a"));
}
#[test]
fn rich_social_friend_of_friend_via_knows() {
let db = TestDb::new();
db.seed_rich_social_graph();
let fof = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:KNOWS]->(mid:Person)-[:KNOWS]->(fof:Person) \
RETURN DISTINCT fof.name AS name",
"name",
);
assert!(fof.contains(&"Carol".to_string()));
assert!(fof.contains(&"Dave".to_string()));
assert!(fof.contains(&"Eve".to_string()));
}
#[test]
fn rich_social_mutual_friends() {
let db = TestDb::new();
db.seed_rich_social_graph();
let mutual = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:KNOWS]->(friend:Person)<-[:KNOWS]-(b:Person {name:'Bob'}) \
RETURN friend.name AS name",
"name",
);
assert_eq!(mutual, vec!["Carol"]);
}
#[test]
fn rich_social_shared_interests() {
let db = TestDb::new();
db.seed_rich_social_graph();
let shared = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:INTERESTED_IN]->(i:Interest {name:'Music'})<-[:INTERESTED_IN]-(other:Person) \
WHERE other.name <> 'Alice' \
RETURN other.name AS name",
"name",
);
assert_eq!(shared, vec!["Bob", "Dave", "Eve"]);
}
#[test]
fn rich_social_filter_blocked_users() {
let db = TestDb::new();
db.seed_rich_social_graph();
let followed = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:FOLLOWS]->(f:Person) \
RETURN f.name AS name",
"name",
);
assert_eq!(followed, vec!["Carol", "Eve"]);
}
#[test]
fn rich_social_blocked_users_list() {
let db = TestDb::new();
db.seed_rich_social_graph();
let blocked_by_alice = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:BLOCKED]->(blocked:Person) \
RETURN blocked.name AS name",
"name",
);
assert_eq!(blocked_by_alice, vec!["Frank"]);
}
#[test]
fn rich_social_multi_relationship_knows_and_follows() {
let db = TestDb::new();
db.seed_rich_social_graph();
let both = db.sorted_strings(
"MATCH (a:Person {name:'Alice'})-[:KNOWS]->(p:Person) \
MATCH (a)-[:FOLLOWS]->(p) \
RETURN p.name AS name",
"name",
);
assert_eq!(both, vec!["Carol"]);
}
#[test]
fn rich_social_influencer_label_match() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run("MATCH (inf:Influencer) RETURN inf.name AS name");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Eve");
}
#[test]
fn rich_social_influencer_multi_label_in_pattern() {
let db = TestDb::new();
db.seed_rich_social_graph();
let followers_of_influencer = db.sorted_strings(
"MATCH (inf:Influencer) \
MATCH (p:Person)-[:FOLLOWS]->(inf) \
RETURN p.name AS name",
"name",
);
assert_eq!(followers_of_influencer, vec!["Alice"]);
}
#[test]
fn rich_social_strong_friendships() {
let db = TestDb::new();
db.seed_rich_social_graph();
let strong = db.sorted_strings(
"MATCH (a:Person)-[k:KNOWS]->(b:Person) \
WHERE k.strength >= 6 \
RETURN a.name AS name",
"name",
);
assert_eq!(strong, vec!["Alice", "Carol", "Eve"]);
}
#[test]
fn rich_social_travel_enthusiasts() {
let db = TestDb::new();
db.seed_rich_social_graph();
let travelers = db.sorted_strings(
"MATCH (p:Person)-[:INTERESTED_IN]->(i:Interest {name:'Travel'}) \
RETURN p.name AS name",
"name",
);
assert_eq!(travelers, vec!["Alice", "Carol", "Eve"]);
}
#[test]
fn knowledge_entity_to_document_traversal() {
let db = TestDb::new();
db.seed_knowledge_graph();
let docs = db.sorted_strings(
"MATCH (e:Entity {name:'Albert Einstein'})-[:AUTHORED]->(d:Document) \
RETURN d.title AS title",
"title",
);
assert_eq!(docs.len(), 2);
assert!(docs.contains(&"On the Electrodynamics of Moving Bodies".to_string()));
assert!(docs.contains(&"The Foundation of General Relativity".to_string()));
}
#[test]
fn knowledge_multi_hop_person_to_theory_via_document() {
let db = TestDb::new();
db.seed_knowledge_graph();
let theories = db.sorted_strings(
"MATCH (e:Entity {name:'Albert Einstein'})-[:AUTHORED]->(d:Document)-[:ABOUT]->(t:Entity) \
RETURN DISTINCT t.name AS name",
"name",
);
assert_eq!(theories, vec!["General Relativity"]);
}
#[test]
fn knowledge_shared_nobel_prize() {
let db = TestDb::new();
db.seed_knowledge_graph();
let recipients = db.sorted_strings(
"MATCH (p:Entity)-[:RECEIVED]->(np:Entity {name:'Nobel Prize'}) \
RETURN p.name AS name",
"name",
);
assert_eq!(recipients, vec!["Albert Einstein", "Marie Curie"]);
}
#[test]
fn knowledge_nobel_prize_years() {
let db = TestDb::new();
db.seed_knowledge_graph();
let rows = db.run(
"MATCH (p:Entity)-[r:RECEIVED]->(np:Entity {name:'Nobel Prize'}) \
RETURN p.name AS name, r.year AS year ORDER BY r.year",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["name"], "Marie Curie");
assert_eq!(rows[0]["year"], 1903);
assert_eq!(rows[1]["name"], "Albert Einstein");
assert_eq!(rows[1]["year"], 1921);
}
#[test]
fn knowledge_entity_alias_resolution() {
let db = TestDb::new();
db.seed_knowledge_graph();
let aliases = db.sorted_strings(
"MATCH (e:Entity {name:'Albert Einstein'})-[:HAS_ALIAS]->(a:Alias) \
RETURN a.value AS name",
"name",
);
assert_eq!(aliases, vec!["A. Einstein", "Einstein"]);
}
#[test]
fn knowledge_topic_hierarchy_from_field() {
let db = TestDb::new();
db.seed_knowledge_graph();
let rows = db.run(
"MATCH (f:Entity {name:'Physics'})-[:PARENT_OF]->(tp:Topic) \
RETURN tp.name AS name",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Theoretical Physics");
}
#[test]
fn knowledge_theory_to_topic_traversal() {
let db = TestDb::new();
db.seed_knowledge_graph();
let theories = db.sorted_strings(
"MATCH (t:Entity)-[:BELONGS_TO]->(tp:Topic {name:'Theoretical Physics'}) \
RETURN t.name AS name",
"name",
);
assert_eq!(theories, vec!["General Relativity", "Quantum Mechanics"]);
}
#[test]
fn knowledge_dense_subgraph_einstein_outgoing() {
let db = TestDb::new();
db.seed_knowledge_graph();
let rows = db.run("MATCH (e:Entity {name:'Albert Einstein'})-[r]->(x) RETURN count(r) AS cnt");
assert_eq!(rows[0]["cnt"], 9);
}
#[test]
fn advanced_five_hop_chain() {
let db = TestDb::new();
db.seed_chain(7);
let rows = db.run(
"MATCH (a:Chain {idx:0})-[:NEXT]->(b)-[:NEXT]->(c)-[:NEXT]->(d)-[:NEXT]->(e)-[:NEXT]->(f) \
RETURN f.idx AS idx",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["idx"], 5);
}
#[test]
fn advanced_six_hop_chain() {
let db = TestDb::new();
db.seed_chain(8);
let rows = db.run(
"MATCH (a:Chain {idx:0})-[:NEXT]->(b)-[:NEXT]->(c)-[:NEXT]->(d)-[:NEXT]->(e)-[:NEXT]->(f)-[:NEXT]->(g) \
RETURN g.idx AS idx",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["idx"], 6);
}
#[test]
fn advanced_multiple_rel_types_in_sequence() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Manager)-[:MANAGES]->(p:Person)-[:ASSIGNED_TO]->(proj:Project) \
RETURN m.name AS mgr, p.name AS emp, proj.name AS proj ORDER BY p.name",
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["emp"], "Alice");
assert_eq!(rows[0]["proj"], "Alpha");
assert_eq!(rows[1]["emp"], "Bob");
assert_eq!(rows[1]["proj"], "Alpha");
assert_eq!(rows[2]["emp"], "Eve");
assert_eq!(rows[2]["proj"], "Beta");
}
#[test]
fn advanced_fan_out_pattern() {
let db = TestDb::new();
db.seed_dependency_graph();
let deps = db.sorted_strings(
"MATCH (src:Package {name:'app'})-[:DEPENDS_ON]->(dep:Package) \
RETURN dep.name AS name",
"name",
);
assert_eq!(deps, vec!["auth", "log", "web"]);
}
#[test]
fn advanced_fan_in_pattern() {
let db = TestDb::new();
db.seed_dependency_graph();
let consumers = db.sorted_strings(
"MATCH (consumer:Package)-[:DEPENDS_ON]->(dep:Package {name:'log'}) \
RETURN consumer.name AS name",
"name",
);
assert_eq!(consumers, vec!["app", "auth", "web"]);
}
#[test]
fn advanced_all_pairs_within_two_hops() {
let db = TestDb::new();
db.seed_cycle(4);
let rows = db.run(
"MATCH (a:Ring)-[:LOOP]->(b:Ring)-[:LOOP]->(c:Ring) \
RETURN a.idx AS src, c.idx AS dst",
);
assert_eq!(rows.len(), 4);
}
#[test]
fn advanced_three_rel_types_sequence() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:WORKS_AT]->(c:Company {name:'Acme'}) \
MATCH (p)-[:LIVES_IN]->(city:City {name:'London'}) \
MATCH (p)-[:ASSIGNED_TO]->(proj:Project) \
RETURN p.name AS name, proj.name AS proj ORDER BY p.name",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["name"], "Alice");
assert_eq!(rows[0]["proj"], "Alpha");
assert_eq!(rows[1]["name"], "Carol");
assert_eq!(rows[1]["proj"], "Beta");
}
#[test]
fn advanced_transport_three_hop_route() {
let db = TestDb::new();
db.seed_transport_graph();
let rows = db.run(
"MATCH (a:Station {name:'Amsterdam'})-[:ROUTE]->(b:Station)-[:ROUTE]->(c:Station)-[:ROUTE]->(d:Station {name:'Eindhoven'}) \
RETURN b.name AS hop1, c.name AS hop2",
);
assert!(!rows.is_empty());
let has_rot_utr = rows
.iter()
.any(|r| r["hop1"] == "Rotterdam" && r["hop2"] == "Utrecht");
assert!(
has_rot_utr,
"expected path via Rotterdam->Utrecht, got: {rows:?}"
);
}
#[test]
fn multi_edge_different_types_between_same_pair() {
let db = TestDb::new();
db.run("CREATE (:N {id:1}), (:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:LIKES]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:FOLLOWS]->(b)");
db.assert_count("MATCH (a:N {id:1})-[r]->(b:N {id:2}) RETURN r", 2);
db.assert_count("MATCH (a:N {id:1})-[:LIKES]->(b:N {id:2}) RETURN 1 AS x", 1);
db.assert_count(
"MATCH (a:N {id:1})-[:FOLLOWS]->(b:N {id:2}) RETURN 1 AS x",
1,
);
}
#[test]
fn multi_edge_same_type_different_properties() {
let db = TestDb::new();
db.run("CREATE (:N {id:1}), (:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:MSG {text:'hello'}]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:MSG {text:'world'}]->(b)");
db.assert_count("MATCH (a:N {id:1})-[:MSG]->(b:N {id:2}) RETURN 1 AS x", 2);
}
#[test]
fn multi_edge_filter_specific_edge_by_property() {
let db = TestDb::new();
db.run("CREATE (:N {id:1}), (:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:MSG {text:'hello', priority:1}]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:MSG {text:'world', priority:2}]->(b)");
let rows = db
.run("MATCH (a:N {id:1})-[r:MSG]->(b:N {id:2}) WHERE r.priority = 2 RETURN r.text AS text");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["text"], "world");
}
#[test]
fn multi_edge_bidirectional_between_same_pair() {
let db = TestDb::new();
db.run("CREATE (:N {id:1}), (:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:LINK]->(b)");
db.run("MATCH (a:N {id:2}), (b:N {id:1}) CREATE (a)-[:LINK]->(b)");
db.assert_count("MATCH (a:N {id:1})-[:LINK]->(b:N {id:2}) RETURN 1 AS x", 1);
db.assert_count("MATCH (a:N {id:2})-[:LINK]->(b:N {id:1}) RETURN 1 AS x", 1);
db.assert_count("MATCH (a:N {id:1})-[:LINK]-(b:N {id:2}) RETURN 1 AS x", 2);
}
#[test]
fn multi_edge_three_relationships_between_same_pair() {
let db = TestDb::new();
db.run("CREATE (:N {id:1}), (:N {id:2})");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:A {w:1}]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:B {w:2}]->(b)");
db.run("MATCH (a:N {id:1}), (b:N {id:2}) CREATE (a)-[:C {w:3}]->(b)");
db.assert_count("MATCH (a:N {id:1})-[r]->(b:N {id:2}) RETURN r", 3);
db.assert_count("MATCH (a:N {id:1})-[:B]->(b:N {id:2}) RETURN 1 AS x", 1);
}
#[test]
fn future_optional_match_null_for_missing_pattern() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run(
"MATCH (p:Person {name:'Alice'}) \
OPTIONAL MATCH (p)-[:WORKS_AT]->(c:Company) \
RETURN p.name AS name, c.name AS company",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Alice");
assert!(rows[0]["company"].is_null());
}
#[test]
fn future_where_exists_pattern() {
let db = TestDb::new();
db.seed_rich_social_graph();
let _rows = db.run(
"MATCH (p:Person) \
WHERE EXISTS { (p)-[:BLOCKED]->(:Person) } \
RETURN p.name AS name",
);
assert_eq!(_rows.len(), 2);
}
#[test]
fn future_pattern_comprehension_in_return() {
let db = TestDb::new();
db.seed_rich_social_graph();
let _rows = db.run(
"MATCH (p:Person {name:'Alice'}) \
RETURN p.name AS name, [(p)-[:KNOWS]->(f) | f.name] AS friends",
);
assert_eq!(_rows.len(), 1);
}
#[test]
#[ignore = "pending implementation"]
fn future_quantified_path_patterns() {
let db = TestDb::new();
db.seed_chain(5);
let _rows = db.run(
"MATCH (a:Chain {idx:0}) (()-[:NEXT]->())+ (b:Chain) \
RETURN b.idx AS idx",
);
assert_eq!(_rows.len(), 4);
}
#[test]
fn future_relationship_type_disjunction() {
let db = TestDb::new();
db.seed_rich_social_graph();
let _rows = db.run(
"MATCH (a:Person {name:'Alice'})-[:KNOWS|FOLLOWS]->(b:Person) \
RETURN b.name AS name",
);
assert!(_rows.len() >= 3);
}
#[test]
#[ignore = "pending implementation"]
fn future_variable_length_with_inline_where() {
let db = TestDb::new();
db.seed_rich_social_graph();
let _rows = db.run(
"MATCH (a:Person {name:'Alice'})-[:KNOWS*1..3 WHERE ALL(n IN nodes(path) WHERE n.age > 20)]->(b:Person) \
RETURN b.name AS name",
);
assert!(!_rows.is_empty());
}
#[test]
fn future_map_projection() {
let db = TestDb::new();
db.seed_rich_social_graph();
let _rows = db.run(
"MATCH (p:Person {name:'Alice'}) \
RETURN p{.name, .age} AS profile",
);
assert_eq!(_rows.len(), 1);
}
#[test]
fn future_label_disjunction() {
let db = TestDb::new();
db.seed_knowledge_graph();
let _rows = db.run("MATCH (n:Entity|Alias) RETURN n");
assert_eq!(_rows.len(), 10);
}
#[test]
fn future_node_key_constraint_matching() {
let db = TestDb::new();
db.run("CREATE (:Item {sku:'ABC', region:'EU', stock:5})");
db.run("CREATE (:Item {sku:'ABC', region:'US', stock:3})");
db.run("CREATE (:Item {sku:'DEF', region:'EU', stock:7})");
let _rows = db.run("MATCH (i:Item {sku:'ABC', region:'EU'}) RETURN i.stock AS stock");
assert_eq!(_rows.len(), 1);
assert_eq!(_rows[0]["stock"], 5);
}
#[test]
fn match_triangle_pattern_extended() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run(
"MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)-[:KNOWS]->(a) \
WHERE id(a) < id(b) AND id(b) < id(c) \
RETURN a.name AS a, b.name AS b, c.name AS c",
);
for row in &rows {
assert!(row["a"].is_string());
}
}
#[test]
fn match_two_hop_with_intermediate_filter() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Person)-[:MANAGES]->(p:Person)-[:LIVES_IN]->(c:City {name:'Berlin'}) \
WHERE m.dept = 'Engineering' \
RETURN m.name AS manager, p.name AS report",
);
assert!(!rows.is_empty());
}
#[test]
fn match_multiple_relationship_types_in_chain() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:WORKS_AT]->(c:Company), (p)-[:LIVES_IN]->(city:City) \
RETURN p.name AS person, c.name AS company, city.name AS city \
ORDER BY p.name",
);
assert!(rows.len() >= 5);
for row in &rows {
assert!(row["company"].is_string());
assert!(row["city"].is_string());
}
}
#[test]
fn match_optional_match_unmatched_returns_null() {
let db = TestDb::new();
db.run("CREATE (:Orphan {name: 'Alone'})");
let rows = db.run(
"MATCH (n:Orphan) \
OPTIONAL MATCH (n)-[:FRIEND]->(f) \
RETURN n.name AS name, f AS friend",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Alone");
assert!(rows[0]["friend"].is_null());
}
#[test]
fn match_optional_match_mixed_with_regular() {
let db = TestDb::new();
db.run("CREATE (:Person {name: 'Has'})-[:KNOWS]->(:Person {name: 'Friend'})");
db.run("CREATE (:Person {name: 'Lonely'})");
let rows = db.run(
"MATCH (p:Person) \
OPTIONAL MATCH (p)-[:KNOWS]->(f:Person) \
RETURN p.name AS person, f.name AS friend \
ORDER BY p.name",
);
assert_eq!(rows.len(), 3); }
#[test]
fn match_self_relationship() {
let db = TestDb::new();
db.run("CREATE (n:Node {name: 'self'})-[:LOOP]->(n)");
let rows = db.run("MATCH (n:Node)-[:LOOP]->(n) RETURN n.name AS name");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "self");
}
#[test]
fn match_no_results_returns_empty() {
let db = TestDb::new();
db.seed_social_graph();
let rows = db.run("MATCH (n:User {name: 'NoSuchPerson'}) RETURN n");
assert_eq!(rows.len(), 0);
}
#[test]
fn match_multiple_labels_conjunction() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (n:Person:Manager) RETURN n.name AS name");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Frank");
}
#[test]
fn match_relationship_with_property_predicate() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[r:WORKS_AT {since: 2018}]->(c:Company) \
RETURN p.name AS name",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Alice");
}
#[test]
fn match_undirected_relationship_extended() {
let db = TestDb::new();
db.run("CREATE (a:N {name:'A'})-[:E]->(b:N {name:'B'})");
let rows = db.run("MATCH (a:N {name:'A'})-[:E]-(b:N {name:'B'}) RETURN a.name, b.name");
assert!(!rows.is_empty());
}
#[test]
fn match_count_with_group_by_label() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (n) \
WITH labels(n) AS lbls, n \
UNWIND lbls AS lbl \
RETURN lbl, count(n) AS cnt \
ORDER BY lbl",
);
assert!(!rows.is_empty());
}
#[test]
fn match_exists_subquery_basic() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
WHERE EXISTS { MATCH (p)-[:MANAGES]->(:Person) } \
RETURN p.name AS name ORDER BY p.name",
);
assert!(rows.len() >= 2); }
#[test]
fn match_with_where_on_relationship_type() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run(
"MATCH (a:Person {name:'Alice'})-[r]->(b:Person) \
WHERE type(r) = 'KNOWS' \
RETURN b.name AS name ORDER BY name",
);
assert!(!rows.is_empty());
}
#[test]
fn match_collect_relationship_properties() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[r:WORKS_AT]->(c:Company) \
RETURN p.name AS name, r.since AS since \
ORDER BY r.since",
);
assert!(rows[0]["since"].as_i64().unwrap() <= rows[1]["since"].as_i64().unwrap());
}
#[test]
#[ignore = "pending implementation"]
fn match_quantified_path_pattern() {
let db = TestDb::new();
db.seed_chain(10);
let _rows = db.run(
"MATCH (a:Chain {idx: 0})(()-[:NEXT]->())+ (b:Chain {idx: 5}) \
RETURN a, b",
);
}
#[test]
#[ignore = "pending implementation"]
fn match_pattern_with_where_on_path() {
let db = TestDb::new();
db.seed_chain(5);
let _rows = db.run(
"MATCH p = (:Chain {idx:0})-[:NEXT*]->(:Chain {idx:4}) \
WHERE length(p) = 4 \
RETURN p",
);
}
#[test]
fn optional_match_chain_two_optionals() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person {name:'Dave'}) \
OPTIONAL MATCH (p)-[:MANAGES]->(sub:Person) \
OPTIONAL MATCH (p)-[:ASSIGNED_TO]->(proj:Project) \
RETURN p.name AS name, sub.name AS subordinate, proj.name AS project",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Dave");
assert!(rows[0]["subordinate"].is_null());
assert!(rows[0]["project"].is_null());
}
#[test]
fn optional_match_one_matches_one_null() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person {name:'Alice'}) \
OPTIONAL MATCH (p)-[:MANAGES]->(sub:Person) \
OPTIONAL MATCH (p)-[:ASSIGNED_TO]->(proj:Project) \
RETURN p.name AS name, sub.name AS subordinate, proj.name AS project",
);
assert_eq!(rows.len(), 1);
assert!(rows[0]["subordinate"].is_null());
assert_eq!(rows[0]["project"], "Alpha");
}
#[test]
fn optional_match_count_including_nulls() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
OPTIONAL MATCH (p)-[:MANAGES]->(sub:Person) \
RETURN p.name AS name, count(sub) AS subs \
ORDER BY subs DESC, p.name ASC",
);
assert_eq!(rows.len(), 6);
let frank = rows.iter().find(|r| r["name"] == "Frank").unwrap();
assert_eq!(frank["subs"], 3);
let carol = rows.iter().find(|r| r["name"] == "Carol").unwrap();
assert_eq!(carol["subs"], 1);
let zero_count = rows.iter().filter(|r| r["subs"] == 0).count();
assert_eq!(zero_count, 4);
}
#[test]
fn anti_pattern_people_not_managing_anyone() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person) \
OPTIONAL MATCH (p)-[:MANAGES]->(sub:Person) \
WITH p, sub WHERE sub IS NULL \
RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Bob", "Dave", "Eve"]);
}
#[test]
fn anti_pattern_packages_with_no_dependents() {
let db = TestDb::new();
db.seed_dependency_graph();
let names = db.sorted_strings(
"MATCH (p:Package) \
OPTIONAL MATCH (consumer:Package)-[:DEPENDS_ON]->(p) \
WITH p, consumer WHERE consumer IS NULL \
RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["app"]);
}
#[test]
fn anti_pattern_people_not_assigned_to_any_project() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person) \
OPTIONAL MATCH (p)-[:ASSIGNED_TO]->(proj:Project) \
WITH p, proj WHERE proj IS NULL \
RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Dave", "Frank"]);
}
#[test]
fn multi_hop_filter_at_each_step() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Person)-[:MANAGES]->(e:Person)-[:LIVES_IN]->(c:City) \
WHERE e.dept = 'Engineering' AND c.name = 'Berlin' \
RETURN m.name AS mgr, e.name AS emp",
);
assert_eq!(rows.len(), 2);
let emps = db.sorted_strings(
"MATCH (m:Person)-[:MANAGES]->(e:Person)-[:LIVES_IN]->(c:City) \
WHERE e.dept = 'Engineering' AND c.name = 'Berlin' \
RETURN e.name AS name",
"name",
);
assert_eq!(emps, vec!["Bob", "Eve"]);
}
#[test]
fn multi_hop_four_entities_chain() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (m:Manager)-[:MANAGES]->(e:Person)-[:WORKS_AT]->(c:Company {name:'Acme'}) \
RETURN m.name AS mgr, e.name AS emp, c.name AS company ORDER BY e.name",
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["emp"], "Alice");
assert_eq!(rows[0]["company"], "Acme");
}
#[test]
fn large_fan_out_hub_20_spokes() {
let db = TestDb::new();
db.run("CREATE (:Center {name:'hub'})");
db.run("UNWIND range(1, 20) AS i MATCH (h:Center) CREATE (h)-[:SPOKE]->(:Leaf {id: i})");
db.assert_count("MATCH (:Center)-[:SPOKE]->(l:Leaf) RETURN l", 20);
let rows = db.run("MATCH (h:Center)-[r:SPOKE]->(l:Leaf) RETURN count(r) AS cnt");
assert_eq!(rows[0]["cnt"], 20);
}
#[test]
fn large_fan_in_many_to_one() {
let db = TestDb::new();
db.run("CREATE (:Sink {name:'target'})");
db.run("UNWIND range(1, 15) AS i MATCH (s:Sink) CREATE (:Source {id: i})-[:FEEDS]->(s)");
db.assert_count("MATCH (src:Source)-[:FEEDS]->(sink:Sink) RETURN src", 15);
}
#[test]
fn bipartite_graph_pattern() {
let db = TestDb::new();
for i in 1..=3 {
db.run(&format!("CREATE (:Worker {{id:{i}}})"));
db.run(&format!("CREATE (:Task {{id:{i}}})"));
}
db.run("MATCH (w:Worker {id:1}), (t:Task {id:1}) CREATE (w)-[:ASSIGNED]->(t)");
db.run("MATCH (w:Worker {id:1}), (t:Task {id:2}) CREATE (w)-[:ASSIGNED]->(t)");
db.run("MATCH (w:Worker {id:2}), (t:Task {id:2}) CREATE (w)-[:ASSIGNED]->(t)");
db.run("MATCH (w:Worker {id:2}), (t:Task {id:3}) CREATE (w)-[:ASSIGNED]->(t)");
db.run("MATCH (w:Worker {id:3}), (t:Task {id:1}) CREATE (w)-[:ASSIGNED]->(t)");
db.run("MATCH (w:Worker {id:3}), (t:Task {id:3}) CREATE (w)-[:ASSIGNED]->(t)");
let rows = db.run(
"MATCH (w1:Worker)-[:ASSIGNED]->(t:Task)<-[:ASSIGNED]-(w2:Worker) \
WHERE id(w1) < id(w2) \
RETURN DISTINCT w1.id AS w1, w2.id AS w2 ORDER BY w1.id, w2.id",
);
assert_eq!(rows.len(), 3);
}
#[test]
fn match_with_unwind_as_filter() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"UNWIND ['Alice', 'Eve', 'Frank'] AS target \
MATCH (p:Person {name: target}) \
RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Eve", "Frank"]);
}
#[test]
fn match_person_company_city_triangle() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person)-[:WORKS_AT]->(c:Company {name:'Acme'}) \
MATCH (p)-[:LIVES_IN]->(city:City {name:'Berlin'}) \
RETURN p.name AS name",
"name",
);
assert_eq!(names, vec!["Bob", "Eve"]);
}
#[test]
fn knowledge_three_hop_person_to_topic() {
let db = TestDb::new();
db.seed_knowledge_graph();
let rows = db.run(
"MATCH (e:Entity {name:'Albert Einstein'})-[:PROPOSED]->(t:Entity)-[:BELONGS_TO]->(tp:Topic) \
RETURN tp.name AS topic",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["topic"], "Theoretical Physics");
}
#[test]
fn knowledge_common_fields_of_study() {
let db = TestDb::new();
db.seed_knowledge_graph();
let fields = db.sorted_strings(
"MATCH (e:Entity {name:'Albert Einstein'})-[:STUDIED]->(f:Entity)<-[:STUDIED]-(c:Entity {name:'Marie Curie'}) \
RETURN f.name AS name",
"name",
);
assert_eq!(fields, vec!["Physics"]);
}
#[test]
fn transport_all_reachable_in_two_hops() {
let db = TestDb::new();
db.seed_transport_graph();
let stations = db.sorted_strings(
"MATCH (src:Station {name:'Amsterdam'})-[:ROUTE]->(mid:Station)-[:ROUTE]->(dst:Station) \
WHERE dst.name <> 'Amsterdam' AND mid.name <> dst.name \
RETURN DISTINCT dst.name AS name",
"name",
);
assert!(!stations.is_empty());
}
#[test]
#[ignore = "pending implementation"]
fn match_shortest_path() {
let db = TestDb::new();
db.seed_transport_graph();
let _rows = db.run(
"MATCH p = shortestPath((a:Station {name:'Amsterdam'})-[:ROUTE*]-(b:Station {name:'Eindhoven'})) \
RETURN length(p) AS hops",
);
}
#[test]
#[ignore = "pending implementation"]
fn match_all_shortest_paths() {
let db = TestDb::new();
db.seed_transport_graph();
let _rows = db.run(
"MATCH p = allShortestPaths((a:Station {name:'Amsterdam'})-[:ROUTE*]-(b:Station {name:'Den Haag'})) \
RETURN length(p) AS hops, [n IN nodes(p) | n.name] AS route",
);
}
#[test]
#[ignore = "pending implementation"]
fn match_call_subquery() {
let db = TestDb::new();
db.seed_org_graph();
let _rows = db.run(
"MATCH (p:Person) \
CALL { WITH p MATCH (p)-[:MANAGES]->(sub) RETURN count(sub) AS subs } \
RETURN p.name AS name, subs ORDER BY subs DESC",
);
}
#[test]
#[ignore = "pending implementation"]
fn match_collect_subquery() {
let db = TestDb::new();
db.seed_org_graph();
let _rows = db.run(
"MATCH (p:Person) \
RETURN p.name, COLLECT { MATCH (p)-[:MANAGES]->(s) RETURN s.name } AS subordinates",
);
}