mod test_helpers;
use test_helpers::TestDb;
fn db_with_users() -> TestDb {
let db = TestDb::new();
db.run("CREATE (a:User {name: 'Alice', age: 30})");
db.run("CREATE (b:User {name: 'Bob', age: 25})");
db.run("CREATE (c:User {name: 'Carol', age: 35})");
db.run("CREATE (d:User {name: 'Dave', age: 25})");
db
}
#[test]
fn order_by_property_ascending() {
let db = db_with_users();
let names = db.column(
"MATCH (n:User) RETURN n.name AS name ORDER BY n.name ASC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Alice", "Bob", "Carol", "Dave"]);
}
#[test]
fn order_by_property_descending() {
let db = db_with_users();
let names = db.column(
"MATCH (n:User) RETURN n.name AS name ORDER BY n.name DESC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Dave", "Carol", "Bob", "Alice"]);
}
#[test]
fn order_by_numeric_ascending() {
let db = db_with_users();
let ages = db.column(
"MATCH (n:User) RETURN n.age AS age ORDER BY n.age ASC",
"age",
);
let nums: Vec<i64> = ages.iter().map(|v| v.as_i64().unwrap()).collect();
assert_eq!(nums, vec![25, 25, 30, 35]);
}
#[test]
fn order_by_default_is_ascending() {
let db = db_with_users();
let names = db.column(
"MATCH (n:User) RETURN n.name AS name ORDER BY n.name",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Alice", "Bob", "Carol", "Dave"]);
}
#[test]
fn order_by_multiple_keys() {
let db = db_with_users();
let rows = db
.run("MATCH (n:User) RETURN n.name AS name, n.age AS age ORDER BY n.age ASC, n.name DESC");
let names: Vec<&str> = rows.iter().map(|r| r["name"].as_str().unwrap()).collect();
assert_eq!(names, vec!["Dave", "Bob", "Alice", "Carol"]);
}
#[test]
fn order_desc_strings() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name DESC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Frank", "Eve", "Dave", "Carol", "Bob", "Alice"]);
}
#[test]
fn order_by_computed_expression() {
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 ASC",
);
assert_eq!(rows.len(), 6);
assert_eq!(rows[0]["name"], "Frank"); assert_eq!(rows[5]["name"], "Eve"); }
#[test]
fn order_by_null_values_sort_last_ascending() {
let db = TestDb::new();
db.run("CREATE (:Item {name:'A', score: 10})");
db.run("CREATE (:Item {name:'B', score: 20})");
db.run("CREATE (:Item {name:'C'})");
let names = db.column(
"MATCH (i:Item) RETURN i.name AS name ORDER BY i.score ASC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["A", "B", "C"]);
}
#[test]
fn order_by_null_values_sort_first_descending() {
let db = TestDb::new();
db.run("CREATE (:Item {name:'A', score: 10})");
db.run("CREATE (:Item {name:'B', score: 20})");
db.run("CREATE (:Item {name:'C'})");
let names = db.column(
"MATCH (i:Item) RETURN i.name AS name ORDER BY i.score DESC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["C", "B", "A"]);
}
#[test]
fn limit_restricts_rows() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n LIMIT 2", 2);
}
#[test]
fn limit_zero() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n LIMIT 0", 0);
}
#[test]
fn limit_exceeds_result_count() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n LIMIT 100", 4);
}
#[test]
fn skip_rows() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n SKIP 2", 2);
}
#[test]
fn skip_all_rows() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n SKIP 10", 0);
}
#[test]
fn skip_zero() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n SKIP 0", 4);
}
#[test]
fn skip_past_all_results() {
let db = TestDb::new();
db.seed_org_graph();
db.assert_count("MATCH (p:Person) RETURN p.name AS name SKIP 100", 0);
}
#[test]
fn skip_and_limit_combined() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n SKIP 1 LIMIT 2", 2);
}
#[test]
fn order_skip_limit_pipeline() {
let db = db_with_users();
let names = db.column(
"MATCH (n:User) RETURN n.name AS name ORDER BY n.name ASC SKIP 1 LIMIT 2",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Bob", "Carol"]);
}
#[test]
fn paginate_page_1() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC LIMIT 3",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Alice", "Bob", "Carol"]);
}
#[test]
fn paginate_page_2() {
let db = TestDb::new();
db.seed_org_graph();
let names = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 3 LIMIT 3",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Dave", "Eve", "Frank"]);
}
#[test]
fn distinct_removes_duplicates() {
let db = db_with_users();
let rows = db.run("MATCH (n:User) RETURN DISTINCT n.age AS age");
assert_eq!(rows.len(), 3);
}
#[test]
fn distinct_on_all_same() {
let db = TestDb::new();
db.run("CREATE (a:User {age: 30})");
db.run("CREATE (b:User {age: 30})");
db.run("CREATE (c:User {age: 30})");
let rows = db.run("MATCH (n:User) RETURN DISTINCT n.age AS age");
assert_eq!(rows.len(), 1);
}
#[test]
fn distinct_on_all_different() {
let db = db_with_users();
let rows = db.run("MATCH (n:User) RETURN DISTINCT n.name AS name");
assert_eq!(rows.len(), 4);
}
#[test]
fn distinct_on_department() {
let db = TestDb::new();
db.seed_org_graph();
let depts = db.sorted_strings("MATCH (p:Person) RETURN DISTINCT p.dept AS dept", "dept");
assert_eq!(depts, vec!["Engineering", "Marketing"]);
}
#[test]
fn distinct_preserves_unique_combos() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:LIVES_IN]->(c:City) RETURN DISTINCT p.dept AS dept, c.name AS city",
);
assert_eq!(rows.len(), 4);
}
#[test]
fn order_aggregation_results() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:LIVES_IN]->(c:City) \
RETURN c.name AS city, count(p) AS residents ORDER BY c.name ASC",
);
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["city"], "Berlin");
assert_eq!(rows[0]["residents"], 2);
assert_eq!(rows[1]["city"], "London");
assert_eq!(rows[1]["residents"], 3);
}
#[test]
fn limit_on_aggregation_via_with() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:LIVES_IN]->(c:City) \
WITH c.name AS city, count(p) AS n \
RETURN city, n ORDER BY city ASC LIMIT 2",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["city"], "Berlin");
assert_eq!(rows[1]["city"], "London");
}
#[test]
fn order_by_alias_name() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (p:Person) RETURN p.name AS name ORDER BY name ASC LIMIT 3");
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["name"], "Alice");
}
#[test]
fn distinct_with_order_by() {
let db = TestDb::new();
db.seed_org_graph();
let depts = db.column(
"MATCH (p:Person) RETURN DISTINCT p.dept AS dept ORDER BY p.dept ASC",
"dept",
);
let s: Vec<&str> = depts.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["Engineering", "Marketing"]);
}
#[test]
fn distinct_with_limit() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (p:Person) RETURN DISTINCT p.dept AS dept LIMIT 1");
assert_eq!(rows.len(), 1);
}
#[test]
fn order_by_float_values() {
let db = TestDb::new();
db.run("CREATE (:M {name:'a', val: 3.14})");
db.run("CREATE (:M {name:'b', val: 2.71})");
db.run("CREATE (:M {name:'c', val: 1.41})");
let names = db.column(
"MATCH (m:M) RETURN m.name AS name ORDER BY m.val ASC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s, vec!["c", "b", "a"]);
}
#[test]
fn order_by_boolean_values() {
let db = TestDb::new();
db.run("CREATE (:B {name:'a', flag: false})");
db.run("CREATE (:B {name:'b', flag: true})");
db.run("CREATE (:B {name:'c', flag: false})");
let names = db.column(
"MATCH (b:B) RETURN b.name AS name ORDER BY b.flag ASC, b.name ASC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s[0], "a");
assert_eq!(s[1], "c");
assert_eq!(s[2], "b");
}
#[test]
fn order_by_mixed_asc_desc() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) RETURN p.dept AS dept, p.name AS name \
ORDER BY p.dept ASC, p.name DESC",
);
assert_eq!(rows[0]["dept"], "Engineering");
assert_eq!(rows[0]["name"], "Frank");
assert_eq!(rows[3]["name"], "Alice");
assert_eq!(rows[4]["name"], "Dave");
assert_eq!(rows[5]["name"], "Carol");
}
#[test]
fn skip_and_limit_yield_single_row() {
let db = db_with_users();
let rows = db.run("MATCH (n:User) RETURN n.name AS name ORDER BY n.name ASC SKIP 2 LIMIT 1");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["name"], "Carol");
}
#[test]
fn skip_equals_total_returns_zero() {
let db = db_with_users();
db.assert_count("MATCH (n:User) RETURN n SKIP 4", 0);
}
#[test]
fn order_by_single_row() {
let db = TestDb::new();
db.run("CREATE (:Solo {val: 42})");
let rows = db.run("MATCH (n:Solo) RETURN n.val AS val ORDER BY n.val ASC");
assert_eq!(rows.len(), 1);
assert_eq!(rows[0]["val"], 42);
}
#[test]
fn order_by_aggregated_value() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
WITH p.dept AS dept, count(p) AS cnt \
RETURN dept, cnt ORDER BY cnt DESC",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["dept"], "Engineering");
assert_eq!(rows[0]["cnt"], 4);
assert_eq!(rows[1]["dept"], "Marketing");
assert_eq!(rows[1]["cnt"], 2);
}
#[test]
fn distinct_multiple_columns_all_unique() {
let db = TestDb::new();
db.run("CREATE (:P {x:1, y:'a'})");
db.run("CREATE (:P {x:1, y:'b'})");
db.run("CREATE (:P {x:2, y:'a'})");
let rows = db.run("MATCH (p:P) RETURN DISTINCT p.x AS x, p.y AS y");
assert_eq!(rows.len(), 3);
}
#[test]
fn distinct_multiple_columns_with_duplicates() {
let db = TestDb::new();
db.run("CREATE (:P {x:1, y:'a'})");
db.run("CREATE (:P {x:1, y:'a'})");
db.run("CREATE (:P {x:2, y:'b'})");
let rows = db.run("MATCH (p:P) RETURN DISTINCT p.x AS x, p.y AS y");
assert_eq!(rows.len(), 2);
}
#[test]
fn order_by_arithmetic_expression() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (p:Person) RETURN p.name AS name, p.age AS age ORDER BY p.age * -1");
assert_eq!(rows[0]["name"], "Frank");
assert_eq!(rows[0]["age"], 50);
assert_eq!(rows[5]["name"], "Eve");
assert_eq!(rows[5]["age"], 26);
}
#[test]
fn order_by_aggregate_output_after_with() {
let db = TestDb::new();
db.seed_recommendation_graph();
let rows = db.run(
"MATCH (v:Viewer)-[r:RATED]->(m:Movie) \
WITH v.name AS viewer, avg(r.score) AS avg_score \
RETURN viewer, avg_score ORDER BY avg_score DESC",
);
assert_eq!(rows[0]["viewer"], "Carol");
assert_eq!(rows[2]["viewer"], "Bob");
}
#[test]
fn order_by_multi_key_three_keys() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run(
"MATCH (p:Person)-[r:INTERESTED_IN]->(i:Interest) \
RETURN p.city AS city, r.level AS level, p.name AS name \
ORDER BY p.city ASC, r.level ASC, p.name ASC",
);
assert_eq!(rows[0]["city"], "Berlin");
assert!(rows.len() >= 6);
}
#[test]
fn order_by_null_handling_mixed() {
let db = TestDb::new();
db.run("CREATE (:T {name:'A', rank: 3})");
db.run("CREATE (:T {name:'B'})");
db.run("CREATE (:T {name:'C', rank: 1})");
db.run("CREATE (:T {name:'D'})");
db.run("CREATE (:T {name:'E', rank: 2})");
let names = db.column(
"MATCH (t:T) RETURN t.name AS name ORDER BY t.rank ASC",
"name",
);
let s: Vec<&str> = names.iter().map(|v| v.as_str().unwrap()).collect();
assert_eq!(s[0], "C"); assert_eq!(s[1], "E"); assert_eq!(s[2], "A"); assert!(s[3] == "B" || s[3] == "D");
assert!(s[4] == "B" || s[4] == "D");
}
#[test]
fn order_after_distinct() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person)-[:LIVES_IN]->(c:City) \
RETURN DISTINCT c.name AS city ORDER BY c.name DESC",
);
let cities: Vec<&str> = rows.iter().map(|r| r["city"].as_str().unwrap()).collect();
assert_eq!(cities, vec!["Tokyo", "London", "Berlin"]);
}
#[test]
fn skip_greater_than_row_count_returns_empty() {
let db = TestDb::new();
db.seed_org_graph();
db.assert_count("MATCH (p:Person) RETURN p.name AS name SKIP 20", 0);
}
#[test]
fn limit_zero_returns_empty() {
let db = TestDb::new();
db.seed_org_graph();
db.assert_count("MATCH (p:Person) RETURN p.name AS name LIMIT 0", 0);
}
#[test]
fn skip_zero_same_as_no_skip() {
let db = TestDb::new();
db.seed_org_graph();
let with_skip = db.run("MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 0");
let without_skip = db.run("MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC");
assert_eq!(with_skip.len(), without_skip.len());
assert_eq!(with_skip[0]["name"], without_skip[0]["name"]);
}
#[test]
fn skip_limit_pagination_through_results() {
let db = TestDb::new();
db.seed_org_graph();
let page1 = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 0 LIMIT 2",
"name",
);
let page2 = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 2 LIMIT 2",
"name",
);
let page3 = db.column(
"MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 4 LIMIT 2",
"name",
);
assert_eq!(page1.len(), 2);
assert_eq!(page2.len(), 2);
assert_eq!(page3.len(), 2);
assert_eq!(page1[0], "Alice");
assert_eq!(page1[1], "Bob");
assert_eq!(page2[0], "Carol");
assert_eq!(page2[1], "Dave");
assert_eq!(page3[0], "Eve");
assert_eq!(page3[1], "Frank");
}
#[test]
fn skip_limit_past_end_returns_partial() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run("MATCH (p:Person) RETURN p.name AS name ORDER BY p.name ASC SKIP 4 LIMIT 10");
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["name"], "Eve");
assert_eq!(rows[1]["name"], "Frank");
}
#[test]
fn order_by_alias_from_projection() {
let db = TestDb::new();
db.seed_org_graph();
let rows =
db.run("MATCH (p:Person) RETURN p.name AS person_name ORDER BY person_name ASC LIMIT 3");
assert_eq!(rows.len(), 3);
assert_eq!(rows[0]["person_name"], "Alice");
assert_eq!(rows[1]["person_name"], "Bob");
assert_eq!(rows[2]["person_name"], "Carol");
}
#[test]
fn stable_sort_guarantee() {
let db = TestDb::new();
db.run("CREATE (:S {group: 1, name: 'A'})");
db.run("CREATE (:S {group: 1, name: 'B'})");
db.run("CREATE (:S {group: 1, name: 'C'})");
db.run("CREATE (:S {group: 2, name: 'D'})");
let rows = db.run("MATCH (s:S) RETURN s.group AS g, s.name AS name ORDER BY s.group ASC");
assert_eq!(rows.len(), 4);
assert_eq!(rows[0]["name"], "A");
assert_eq!(rows[1]["name"], "B");
assert_eq!(rows[2]["name"], "C");
}
#[test]
fn order_by_aggregate_in_return() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
RETURN p.dept AS dept, count(p) AS cnt ORDER BY count(p) DESC",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["dept"], "Engineering");
assert_eq!(rows[0]["cnt"], 4);
}
#[test]
fn order_by_case_expression() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
RETURN p.name AS name, p.dept AS dept \
ORDER BY CASE p.dept WHEN 'Marketing' THEN 0 ELSE 1 END, p.name ASC",
);
assert_eq!(rows[0]["dept"], "Marketing");
assert_eq!(rows[1]["dept"], "Marketing");
assert_eq!(rows[2]["dept"], "Engineering");
}
#[test]
fn distinct_with_nulls_ordered() {
let db = TestDb::new();
db.run("CREATE (:V {x: 2})");
db.run("CREATE (:V {x: 1})");
db.run("CREATE (:V {x: 3})");
db.run("CREATE (:V {x: 2})"); let rows = db.run("MATCH (v:V) RETURN DISTINCT v.x AS x");
assert_eq!(rows.len(), 3);
let mut vals: Vec<i64> = rows.iter().map(|r| r["x"].as_i64().unwrap()).collect();
vals.sort();
assert_eq!(vals, vec![1, 2, 3]);
}
#[test]
fn order_by_string_function() {
let db = TestDb::new();
db.run("CREATE (:W {name: 'Charlie'})");
db.run("CREATE (:W {name: 'alice'})");
db.run("CREATE (:W {name: 'Bob'})");
let rows = db.run("MATCH (w:W) RETURN w.name AS name ORDER BY toLower(w.name) ASC");
assert_eq!(rows[0]["name"], "alice");
assert_eq!(rows[1]["name"], "Bob");
assert_eq!(rows[2]["name"], "Charlie");
}
#[test]
fn paginate_relationships_with_order() {
let db = TestDb::new();
db.seed_rich_social_graph();
let rows = db.run(
"MATCH (a:Person)-[k:KNOWS]->(b:Person) \
RETURN a.name AS from, b.name AS to, k.strength AS str \
ORDER BY k.strength DESC SKIP 1 LIMIT 2",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["str"], 7);
assert_eq!(rows[1]["str"], 6);
}
#[test]
fn top_n_youngest_per_department() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
RETURN p.dept AS dept, min(p.age) AS youngest_age \
ORDER BY dept",
);
assert_eq!(rows.len(), 2);
assert_eq!(rows[0]["dept"], "Engineering");
assert_eq!(rows[0]["youngest_age"], 26); assert_eq!(rows[1]["dept"], "Marketing");
assert_eq!(rows[1]["youngest_age"], 31); }
#[test]
fn distinct_on_computed_expression() {
let db = TestDb::new();
db.seed_org_graph();
let rows = db.run(
"MATCH (p:Person) \
RETURN DISTINCT CASE \
WHEN p.age < 30 THEN 'under 30' \
WHEN p.age < 40 THEN '30-39' \
ELSE '40+' END AS bracket \
ORDER BY bracket",
);
assert_eq!(rows.len(), 3);
}
#[test]
#[ignore = "pending implementation"]
fn order_by_subquery_count() {
let db = TestDb::new();
db.seed_org_graph();
let _rows = db.run(
"MATCH (p:Person) \
RETURN p.name AS name \
ORDER BY COUNT { MATCH (p)-[:MANAGES]->() } DESC",
);
}