use grafeo_common::types::Value;
use grafeo_engine::GrafeoDB;
fn chain_graph() -> GrafeoDB {
let db = GrafeoDB::new_in_memory();
let session = db.session();
let na = session
.create_node_with_props(&["Node"], [("name", Value::String("A".into()))])
.unwrap();
let nb = session
.create_node_with_props(&["Node"], [("name", Value::String("B".into()))])
.unwrap();
let nc = session
.create_node_with_props(&["Node"], [("name", Value::String("C".into()))])
.unwrap();
let and = session
.create_node_with_props(&["Node"], [("name", Value::String("D".into()))])
.unwrap();
let ne = session
.create_node_with_props(&["Node", "Special"], [("name", Value::String("E".into()))])
.unwrap();
session.create_edge(na, nb, "LINK");
session.create_edge(nb, nc, "LINK");
session.create_edge(nc, and, "LINK");
session.create_edge(and, ne, "LINK");
session.create_edge(na, nc, "SHORTCUT");
assert_eq!(db.node_count(), 5, "chain_graph: expected 5 nodes");
assert_eq!(
db.edge_count(),
5,
"chain_graph: expected 5 edges (4 LINK + 1 SHORTCUT)"
);
db
}
#[test]
fn test_variable_length_1_to_3() {
let db = chain_graph();
let s = db.session();
let r = s
.execute(
"MATCH (a:Node {name: 'A'})-[:LINK *1..3]->(b:Node) \
RETURN b.name AS name ORDER BY name",
)
.unwrap();
assert_eq!(r.rows().len(), 3, "1..3 hops from A via LINK: B, C, D");
let names: Vec<&str> = r
.rows()
.iter()
.filter_map(|row| match &row[0] {
Value::String(s) => Some(s.as_str()),
_ => None,
})
.collect();
assert_eq!(
names,
vec!["B", "C", "D"],
"ORDER BY name should sort alphabetically"
);
}
#[test]
fn test_variable_length_exact_2() {
let db = chain_graph();
let s = db.session();
let r = s
.execute(
"MATCH (a:Node {name: 'A'})-[:LINK *2..2]->(b:Node) \
RETURN b.name AS name",
)
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("C".into()));
}
#[test]
fn test_variable_length_unbounded() {
let db = chain_graph();
let s = db.session();
let r = s
.execute(
"MATCH (a:Node {name: 'A'})-[:LINK*]->(b:Node) \
RETURN b.name AS name ORDER BY name",
)
.unwrap();
assert_eq!(
r.rows().len(),
4,
"Unbounded hops from A via LINK: B, C, D, E"
);
}
#[test]
fn test_multi_label_match_and_semantics() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (n:Node:Special) RETURN n.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("E".into()));
}
#[test]
fn test_multi_label_no_match_when_missing_label() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (n:Special:Nonexistent) RETURN n.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 0);
}
#[test]
fn test_where_label_predicate() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (n) WHERE n:Special RETURN n.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("E".into()));
}
#[test]
fn test_where_label_predicate_multi() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (n) WHERE n:Node:Special RETURN n.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("E".into()));
}
#[test]
fn test_where_label_predicate_with_and() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (n) WHERE n:Node AND n.name = 'A' RETURN n.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("A".into()));
}
#[test]
fn test_undirected_edge() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (a:Node {name: 'B'})-[:LINK]-(b:Node) RETURN b.name AS name ORDER BY name")
.unwrap();
assert_eq!(r.rows().len(), 2, "Undirected LINK from B: A and C");
}
#[test]
fn test_merge_on_create_and_on_match() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.execute(
"MERGE (p:Person {name: 'Django'}) \
ON CREATE SET p.created = true \
ON MATCH SET p.matched = true",
)
.unwrap();
let r = session
.execute("MATCH (p:Person {name: 'Django'}) RETURN p.created AS c, p.matched AS m")
.unwrap();
assert_eq!(r.rows()[0][0], Value::Bool(true));
assert_eq!(r.rows()[0][1], Value::Null);
session
.execute(
"MERGE (p:Person {name: 'Django'}) \
ON CREATE SET p.created2 = true \
ON MATCH SET p.matched = true",
)
.unwrap();
let r = session
.execute("MATCH (p:Person {name: 'Django'}) RETURN p.matched AS m")
.unwrap();
assert_eq!(r.rows()[0][0], Value::Bool(true));
}
#[test]
fn test_merge_edge_pattern() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.create_node_with_props(&["City"], [("name", Value::String("Amsterdam".into()))])
.unwrap();
session
.create_node_with_props(&["City"], [("name", Value::String("Berlin".into()))])
.unwrap();
session
.execute(
"MATCH (a:City {name: 'Amsterdam'}), (b:City {name: 'Berlin'}) \
MERGE (a)-[:ROUTE {distance: 650}]->(b)",
)
.unwrap();
let r = session
.execute("MATCH (:City)-[r:ROUTE]->(:City) RETURN r.distance AS d")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::Int64(650));
}
#[test]
fn test_path_length() {
let db = chain_graph();
let s = db.session();
let r = s
.execute(
"MATCH p = (a:Node {name: 'A'})-[:LINK *1..4]->(b:Node {name: 'E'}) \
RETURN length(p) AS len",
)
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::Int64(4));
}
#[test]
fn test_edge_property_filter() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
let a = session
.create_node_with_props(&["Person"], [("name", Value::String("Alix".into()))])
.unwrap();
let b = session
.create_node_with_props(&["Person"], [("name", Value::String("Gus".into()))])
.unwrap();
let e = session.create_edge(a, b, "RATED");
db.set_edge_property(e, "stars", Value::Int64(5));
let r = session
.execute("MATCH (a:Person)-[r:RATED]->(b:Person) WHERE r.stars >= 4 RETURN b.name AS name")
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("Gus".into()));
}
#[test]
fn test_wildcard_edge_type() {
let db = chain_graph();
let s = db.session();
let r = s
.execute("MATCH (a:Node {name: 'A'})-[]->(b:Node) RETURN b.name AS name ORDER BY name")
.unwrap();
assert_eq!(r.rows().len(), 2);
}
#[test]
fn test_path_with_intermediate_filter() {
let db = chain_graph();
let s = db.session();
let r = s
.execute(
"MATCH (a:Node {name: 'A'})-[:LINK]->(mid:Node)-[:LINK]->(c:Node) \
WHERE mid.name = 'B' \
RETURN c.name AS name",
)
.unwrap();
assert_eq!(r.rows().len(), 1);
assert_eq!(r.rows()[0][0], Value::String("C".into()));
}
#[test]
fn test_delete_node() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.create_node_with_props(&["Temp"], [("x", Value::Int64(1))])
.unwrap();
session.execute("MATCH (t:Temp) DETACH DELETE t").unwrap();
let r = session
.execute("MATCH (t:Temp) RETURN count(t) AS cnt")
.unwrap();
assert_eq!(r.rows()[0][0], Value::Int64(0));
}
#[test]
fn test_set_multiple_properties() {
let db = GrafeoDB::new_in_memory();
let session = db.session();
session
.create_node_with_props(&["Item"], [("name", Value::String("widget".into()))])
.unwrap();
session
.execute("MATCH (i:Item) SET i.price = 9.99, i.stock = 100")
.unwrap();
let r = session
.execute("MATCH (i:Item) RETURN i.price AS p, i.stock AS s")
.unwrap();
assert_eq!(r.rows()[0][0], Value::Float64(9.99));
assert_eq!(r.rows()[0][1], Value::Int64(100));
}