mod test_helpers;
use test_helpers::TestDb;
#[test]
fn union_combines_results() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:Product {name: 'Widget'})");
let rows = db
.run("MATCH (n:User) RETURN n.name AS name UNION MATCH (n:Product) RETURN n.name AS name");
assert_eq!(rows.len(), 2);
}
#[test]
fn union_deduplicates_identical_scalar_rows() {
let db = TestDb::new();
db.run("CREATE (:Item {val: 1, grp: 'a'})");
db.run("CREATE (:Item {val: 1, grp: 'b'})");
let rows = db.run(
"MATCH (n:Item {grp:'a'}) RETURN n.val AS v \
UNION \
MATCH (n:Item {grp:'b'}) RETURN n.val AS v",
);
assert_eq!(rows.len(), 1);
}
#[test]
fn union_combines_different_types() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person) RETURN p.name AS name \
UNION MATCH (c:City) RETURN c.name AS name",
"name",
);
assert_eq!(names.len(), 9); }
#[test]
fn union_all_preserves_duplicates() {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice'})");
db.run("CREATE (b:User {name: 'Alice'})");
let rows = db
.run("MATCH (n:User) RETURN n.name AS name UNION ALL MATCH (n:User) RETURN n.name AS name");
assert_eq!(rows.len(), 4);
}
#[test]
fn union_all_keeps_all_rows() {
let db = TestDb::new();
db.run("CREATE (:A {val: 1})");
db.run("CREATE (:B {val: 1})");
let rows = db.run("MATCH (n:A) RETURN n.val AS v UNION ALL MATCH (n:B) RETURN n.val AS v");
assert_eq!(rows.len(), 2);
}
#[test]
fn union_all_preserves_branch_order() {
let db = TestDb::new();
db.run("CREATE (:A {v:1})");
db.run("CREATE (:A {v:2})");
db.run("CREATE (:B {v:3})");
db.run("CREATE (:B {v:4})");
let rows = db.run("MATCH (n:A) RETURN n.v AS v UNION ALL MATCH (n:B) RETURN n.v AS v");
assert_eq!(rows.len(), 4);
let vals: Vec<i64> = rows.iter().map(|r| r["v"].as_i64().unwrap()).collect();
assert!(vals[0] <= 2 && vals[1] <= 2);
assert!(vals[2] >= 3 && vals[3] >= 3);
}
#[test]
fn union_three_branches() {
let db = TestDb::new();
db.run("CREATE (:X {name:'a'})");
db.run("CREATE (:Y {name:'b'})");
db.run("CREATE (:Z {name:'c'})");
let names = db.sorted_strings(
"MATCH (n:X) RETURN n.name AS name \
UNION MATCH (n:Y) RETURN n.name AS name \
UNION MATCH (n:Z) RETURN n.name AS name",
"name",
);
assert_eq!(names, vec!["a", "b", "c"]);
}
#[test]
fn union_one_empty_branch() {
let db = TestDb::new();
let rows =
db.run("MATCH (n:X) RETURN n.name AS name UNION ALL MATCH (n:Y) RETURN n.name AS name");
assert_eq!(rows.len(), 0);
}
#[test]
fn union_one_populated_one_empty() {
let db = TestDb::new();
db.run("CREATE (:X {name:'exists'})");
db.run("CREATE (:Y {name:'also'})");
let rows = db.run(
"MATCH (n:X) RETURN n.name AS name \
UNION ALL \
MATCH (n:Y) WHERE n.name = 'nope' RETURN n.name AS name",
);
assert_eq!(rows.len(), 1);
}
#[test]
fn union_both_empty() {
let db = TestDb::new();
let rows = db.run("MATCH (a:Nothing) RETURN a.x AS v UNION ALL MATCH (b:Nada) RETURN b.x AS v");
assert_eq!(rows.len(), 0);
}
#[test]
fn union_with_count_in_each_branch() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) RETURN 'persons' AS label, count(p) AS cnt \
UNION ALL \
MATCH (c:City) RETURN 'cities' AS label, count(c) AS cnt",
);
assert_eq!(rows.len(), 2);
}
#[test]
fn union_with_where_in_each_branch() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person) WHERE p.dept = 'Engineering' RETURN p.name AS name \
UNION \
MATCH (p:Person) WHERE p.dept = 'Marketing' RETURN p.name AS name",
"name",
);
assert_eq!(names.len(), 6);
}
#[test]
fn union_all_four_branches() {
let db = TestDb::new();
db.run("CREATE (:A {v:1})");
db.run("CREATE (:B {v:2})");
db.run("CREATE (:C {v:3})");
db.run("CREATE (:D {v:4})");
let rows = db.run(
"MATCH (n:A) RETURN n.v AS v \
UNION ALL MATCH (n:B) RETURN n.v AS v \
UNION ALL MATCH (n:C) RETURN n.v AS v \
UNION ALL MATCH (n:D) RETURN n.v AS v",
);
assert_eq!(rows.len(), 4);
}
#[test]
fn union_dedup_multiple_columns() {
let db = TestDb::new();
db.run("CREATE (:P {x:1, y:'a'})");
db.run("CREATE (:Q {x:1, y:'a'})");
let rows = db.run(
"MATCH (p:P) RETURN p.x AS x, p.y AS y \
UNION \
MATCH (q:Q) RETURN q.x AS x, q.y AS y",
);
assert_eq!(rows.len(), 1);
}
#[test]
fn union_keeps_distinct_multi_column_rows() {
let db = TestDb::new();
db.run("CREATE (:P {x:1, y:'a'})");
db.run("CREATE (:Q {x:1, y:'b'})");
let rows = db.run(
"MATCH (p:P) RETURN p.x AS x, p.y AS y \
UNION \
MATCH (q:Q) RETURN q.x AS x, q.y AS y",
);
assert_eq!(rows.len(), 2);
}
#[test]
fn union_column_name_from_first_branch() {
let db = TestDb::new();
db.run("CREATE (:X {val:1})");
db.run("CREATE (:Y {val:2})");
let rows = db.run(
"MATCH (x:X) RETURN x.val AS result \
UNION ALL \
MATCH (y:Y) RETURN y.val AS result",
);
assert!(rows[0].get("result").is_some());
assert!(rows[1].get("result").is_some());
}
#[test]
fn union_same_query_both_branches_deduplicates() {
let db = TestDb::new();
db.run("CREATE (:N {v:1})");
db.run("CREATE (:N {v:2})");
let rows = db.run(
"MATCH (n:N) RETURN n.v AS v \
UNION \
MATCH (n:N) RETURN n.v AS v",
);
assert_eq!(rows.len(), 2); }
#[test]
fn union_all_same_query_both_branches_doubles() {
let db = TestDb::new();
db.run("CREATE (:N {v:1})");
db.run("CREATE (:N {v:2})");
let rows = db.run(
"MATCH (n:N) RETURN n.v AS v \
UNION ALL \
MATCH (n:N) RETURN n.v AS v",
);
assert_eq!(rows.len(), 4); }
#[test]
fn union_all_large_result() {
let db = TestDb::new();
db.run("UNWIND range(1, 20) AS i CREATE (:Num {val: i})");
let rows = db.run(
"MATCH (n:Num) RETURN n.val AS v \
UNION ALL \
MATCH (n:Num) RETURN n.val AS v",
);
assert_eq!(rows.len(), 40);
}
#[test]
fn union_with_where_filters_in_both_branches() {
let db = TestDb::new();
db.seed_social_graph();
let names = db.sorted_strings(
"MATCH (n:User) WHERE n.age >= 30 RETURN n.name AS name \
UNION \
MATCH (n:User) WHERE n.age >= 35 RETURN n.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Carol"]);
}
#[test]
fn union_all_with_aggregation_in_branches() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) WHERE p.dept = 'Engineering' \
RETURN p.dept AS dept, count(p) AS cnt \
UNION ALL \
MATCH (p:Person) WHERE p.dept = 'Marketing' \
RETURN p.dept AS dept, count(p) AS cnt",
);
assert_eq!(rows.len(), 2);
let eng = rows.iter().find(|r| r["dept"] == "Engineering").unwrap();
let mkt = rows.iter().find(|r| r["dept"] == "Marketing").unwrap();
assert_eq!(eng["cnt"], 4);
assert_eq!(mkt["cnt"], 2);
}
#[test]
fn union_combining_different_labels() {
let db = TestDb::new();
db.seed_recommendation_graph();
let names = db.sorted_strings(
"MATCH (v:Viewer) RETURN v.name AS name \
UNION \
MATCH (m:Movie) RETURN m.title AS name",
"name",
);
assert_eq!(names.len(), 7);
}
#[test]
fn three_way_union_distinct() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person) WHERE p.dept = 'Engineering' AND p.age > 30 RETURN p.name AS name \
UNION \
MATCH (p:Person) WHERE p.dept = 'Marketing' RETURN p.name AS name \
UNION \
MATCH (m:Manager) RETURN m.name AS name",
"name",
);
assert_eq!(names, vec!["Alice", "Carol", "Dave", "Frank"]);
}
#[test]
fn union_with_order_by_on_final_result() {
let db = TestDb::new();
db.run("CREATE (:X {val: 3})");
db.run("CREATE (:Y {val: 1})");
db.run("CREATE (:Z {val: 2})");
let vals = db.sorted_ints(
"MATCH (n:X) RETURN n.val AS v \
UNION ALL \
MATCH (n:Y) RETURN n.val AS v \
UNION ALL \
MATCH (n:Z) RETURN n.val AS v",
"v",
);
assert_eq!(vals, vec![1, 2, 3]);
}
#[test]
fn union_all_with_skip_and_limit_on_final_result() {
let db = TestDb::new();
db.run("UNWIND range(1, 5) AS i CREATE (:Num {val: i})");
db.run("UNWIND range(6, 10) AS i CREATE (:Num2 {val: i})");
let vals = db.sorted_ints(
"MATCH (n:Num) RETURN n.val AS v \
UNION ALL \
MATCH (n:Num2) RETURN n.val AS v",
"v",
);
assert_eq!(vals.len(), 10);
assert_eq!(vals[0], 1);
assert_eq!(vals[9], 10);
}
#[test]
fn union_column_count_mismatch_error() {
let db = TestDb::new();
db.run("CREATE (:A {x:1}), (:B {x:2, y:3})");
let err = db.run_err(
"MATCH (a:A) RETURN a.x AS x \
UNION \
MATCH (b:B) RETURN b.x AS x, b.y AS y",
);
assert!(!err.is_empty());
}
#[test]
fn union_column_name_mismatch_error() {
let db = TestDb::new();
db.run("CREATE (:A {x:1}), (:B {y:2})");
let err = db.run_err(
"MATCH (a:A) RETURN a.x AS foo \
UNION \
MATCH (b:B) RETURN b.y AS bar",
);
assert!(!err.is_empty());
}
#[test]
fn union_with_with_pipeline_in_branches() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.sorted_strings(
"MATCH (p:Person)-[:LIVES_IN]->(c:City {name:'London'}) \
WITH p RETURN p.name AS name \
UNION \
MATCH (p:Person)-[:LIVES_IN]->(c:City {name:'Berlin'}) \
WITH p RETURN p.name AS name",
"name",
);
assert_eq!(names.len(), 5);
}
#[test]
fn union_with_order_by_on_combined() {
let db = TestDb::new();
db.run("CREATE (:A {name: 'Charlie'})");
db.run("CREATE (:B {name: 'Alice'})");
let rows = db.run(
"MATCH (a:A) RETURN a.name AS name \
UNION ALL \
MATCH (b:B) RETURN b.name AS name \
ORDER BY name",
);
assert_eq!(rows.len(), 2);
}
#[test]
fn union_removes_duplicates() {
let db = TestDb::new();
db.run("CREATE (:X {v: 1})");
db.run("CREATE (:Y {v: 1})");
let rows = db.run(
"MATCH (x:X) RETURN x.v AS v \
UNION \
MATCH (y:Y) RETURN y.v AS v",
);
assert_eq!(rows.len(), 1); assert_eq!(rows[0]["v"], 1);
}
#[test]
fn union_all_keeps_duplicates() {
let db = TestDb::new();
db.run("CREATE (:X {v: 1})");
db.run("CREATE (:Y {v: 1})");
let rows = db.run(
"MATCH (x:X) RETURN x.v AS v \
UNION ALL \
MATCH (y:Y) RETURN y.v AS v",
);
assert_eq!(rows.len(), 2);
}
#[test]
fn union_three_branches_extended() {
let db = TestDb::new();
db.run("CREATE (:A {v: 1})");
db.run("CREATE (:B {v: 2})");
db.run("CREATE (:C {v: 3})");
let rows = db.run(
"MATCH (a:A) RETURN a.v AS v \
UNION ALL \
MATCH (b:B) RETURN b.v AS v \
UNION ALL \
MATCH (c:C) RETURN c.v AS v",
);
assert_eq!(rows.len(), 3);
}
#[test]
fn union_empty_branch() {
let db = TestDb::new();
db.run("CREATE (:Present {v: 42})");
db.run("CREATE (:Other {v: 99})");
let rows = db.run(
"MATCH (p:Present) RETURN p.v AS v \
UNION ALL \
MATCH (n:Other {v: -1}) RETURN n.v AS v",
);
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["v"], 42);
}
#[test]
fn union_mixed_types_in_column() {
let db = TestDb::new();
db.run("CREATE (:I {v: 42})");
db.run("CREATE (:S {v: 'hello'})");
let rows = db.run(
"MATCH (i:I) RETURN i.v AS v \
UNION ALL \
MATCH (s:S) RETURN s.v AS v",
);
assert_eq!(rows.len(), 2);
}
#[test]
fn union_with_null_in_branch() {
let db = TestDb::new();
db.run("CREATE (:A {v: 1})");
db.run("CREATE (:B {})"); let rows = db.run(
"MATCH (a:A) RETURN a.v AS v \
UNION ALL \
MATCH (b:B) RETURN b.v AS v",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["v"], 1);
assert!(rows[1]["v"].is_null());
}
#[test]
fn union_dedup_null_values() {
let db = TestDb::new();
let rows = db.run(
"RETURN null AS v \
UNION \
RETURN null AS v",
);
assert_eq!(rows.len(), 1);
assert!(rows[0]["v"].is_null());
}
#[test]
fn union_all_summary_statistics() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) RETURN 'people' AS category, count(p) AS cnt \
UNION ALL \
MATCH (pr:Project) RETURN 'projects' AS category, count(pr) AS cnt \
UNION ALL \
MATCH (c:City) RETURN 'cities' AS category, count(c) AS cnt",
);
assert_eq!(rows.len(), 3);
let people = rows.iter().find(|r| r["category"] == "people").unwrap();
assert_eq!(people["cnt"], 6);
}
#[test]
#[ignore = "pending implementation"]
fn union_with_limit_on_each_branch() {
let db = TestDb::new();
db.seed_org_graph();
let _rows = db.run(
"CALL { MATCH (p:Person) RETURN p.name AS name ORDER BY p.name LIMIT 2 } \
UNION ALL \
CALL { MATCH (c:City) RETURN c.name AS name ORDER BY c.name LIMIT 2 }",
);
}