#![allow(unused_variables)]
use interstellar::prelude::*;
use interstellar::storage::Graph;
use std::collections::HashMap;
use std::sync::Arc;
fn create_aggregation_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let people = vec![
("Alice", 30i64, "New York"),
("Bob", 25i64, "Boston"),
("Carol", 35i64, "New York"),
("Dave", 28i64, "Boston"),
("Eve", 22i64, "Chicago"),
];
for (name, age, city) in people {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::from(name));
props.insert("age".to_string(), Value::from(age));
props.insert("city".to_string(), Value::from(city));
graph.add_vertex("Person", props);
}
graph
}
#[test]
fn test_gql_count_star() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN count(*)").unwrap();
assert_eq!(results.len(), 1, "COUNT(*) should return single result");
assert_eq!(results[0], Value::Int(5), "Should count all 5 persons");
}
#[test]
fn test_gql_count_star_with_alias() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(*) AS total")
.unwrap();
assert_eq!(results.len(), 1);
if let Value::Map(map) = &results[0] {
assert_eq!(map.get("total"), Some(&Value::Int(5)));
} else {
panic!("Expected Map result with alias");
}
}
#[test]
fn test_gql_count_property() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN count(p.name)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(5), "Should count all names");
}
#[test]
fn test_gql_count_distinct() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(DISTINCT p.city)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(3), "Should count 3 unique cities");
}
#[test]
fn test_gql_sum() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN sum(p.age)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(140), "Sum of ages should be 140");
}
#[test]
fn test_gql_sum_with_where() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 25 RETURN sum(p.age)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(93), "Sum of ages > 25 should be 93");
}
#[test]
fn test_gql_avg() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN avg(p.age)").unwrap();
assert_eq!(results.len(), 1);
if let Value::Float(avg) = results[0] {
assert!(
(avg - 28.0).abs() < 0.0001,
"Average should be 28.0, got {}",
avg
);
} else {
panic!("Expected Float result for AVG");
}
}
#[test]
fn test_gql_min() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN min(p.age)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(22), "Min age should be 22");
}
#[test]
fn test_gql_max() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN max(p.age)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(35), "Max age should be 35");
}
#[test]
fn test_gql_min_string() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN min(p.name)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0],
Value::String("Alice".to_string()),
"Min name should be Alice (alphabetically first)"
);
}
#[test]
fn test_gql_max_string() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph.gql("MATCH (p:Person) RETURN max(p.name)").unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0],
Value::String("Eve".to_string()),
"Max name should be Eve (alphabetically last)"
);
}
#[test]
fn test_gql_collect() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN collect(p.name)")
.unwrap();
assert_eq!(results.len(), 1);
if let Value::List(names) = &results[0] {
assert_eq!(names.len(), 5, "Should collect all 5 names");
let names_set: std::collections::HashSet<_> = names.iter().collect();
assert!(names_set.contains(&Value::String("Alice".to_string())));
assert!(names_set.contains(&Value::String("Bob".to_string())));
assert!(names_set.contains(&Value::String("Carol".to_string())));
assert!(names_set.contains(&Value::String("Dave".to_string())));
assert!(names_set.contains(&Value::String("Eve".to_string())));
} else {
panic!("Expected List result for COLLECT");
}
}
#[test]
fn test_gql_collect_distinct() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN collect(DISTINCT p.city)")
.unwrap();
assert_eq!(results.len(), 1);
if let Value::List(cities) = &results[0] {
assert_eq!(cities.len(), 3, "Should collect 3 unique cities");
let cities_set: std::collections::HashSet<_> = cities.iter().collect();
assert!(cities_set.contains(&Value::String("New York".to_string())));
assert!(cities_set.contains(&Value::String("Boston".to_string())));
assert!(cities_set.contains(&Value::String("Chicago".to_string())));
} else {
panic!("Expected List result for COLLECT DISTINCT");
}
}
#[test]
fn test_gql_multiple_aggregates() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(*) AS total, sum(p.age) AS total_age, avg(p.age) AS avg_age")
.unwrap();
assert_eq!(results.len(), 1);
if let Value::Map(map) = &results[0] {
assert_eq!(map.get("total"), Some(&Value::Int(5)));
assert_eq!(map.get("total_age"), Some(&Value::Int(140)));
if let Some(Value::Float(avg)) = map.get("avg_age") {
assert!((avg - 28.0).abs() < 0.0001, "Average should be 28.0");
} else {
panic!("Expected Float for avg_age");
}
} else {
panic!("Expected Map result for multiple aggregates");
}
}
#[test]
fn test_gql_count_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN count(*)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(0), "COUNT of empty set should be 0");
}
#[test]
fn test_gql_avg_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN avg(p.age)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Null, "AVG of empty set should be Null");
}
#[test]
fn test_gql_min_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN min(p.age)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Null, "MIN of empty set should be Null");
}
#[test]
fn test_gql_max_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN max(p.age)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Null, "MAX of empty set should be Null");
}
#[test]
fn test_gql_sum_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN sum(p.age)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(0), "SUM of empty set should be 0");
}
#[test]
fn test_gql_collect_empty() {
let graph = create_aggregation_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age > 100 RETURN collect(p.name)")
.unwrap();
assert_eq!(results.len(), 1);
assert_eq!(
results[0],
Value::List(vec![]),
"COLLECT of empty set should be empty list"
);
}
fn create_group_by_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let people = vec![
("Alice", 30i64, "New York"),
("Bob", 25i64, "Boston"),
("Carol", 35i64, "New York"),
("Dave", 28i64, "Boston"),
("Eve", 22i64, "Chicago"),
("Frank", 40i64, "New York"),
];
for (name, age, city) in people {
let mut props = HashMap::new();
props.insert("name".to_string(), Value::from(name));
props.insert("age".to_string(), Value::from(age));
props.insert("city".to_string(), Value::from(city));
graph.add_vertex("Person", props);
}
graph
}
#[test]
fn test_gql_group_by_count() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3, "Should have 3 city groups");
let mut city_counts: HashMap<String, i64> = HashMap::new();
for result in &results {
if let Value::Map(map) = result {
if let (Some(Value::String(city)), Some(Value::Int(count))) =
(map.get("city"), map.get("cnt"))
{
city_counts.insert(city.clone(), *count);
}
}
}
assert_eq!(city_counts.get("New York"), Some(&3));
assert_eq!(city_counts.get("Boston"), Some(&2));
assert_eq!(city_counts.get("Chicago"), Some(&1));
}
#[test]
fn test_gql_group_by_avg() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, avg(p.age) AS avg_age GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3, "Should have 3 city groups");
let mut city_avgs: HashMap<String, f64> = HashMap::new();
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::String(city)) = map.get("city") {
let avg = match map.get("avg_age") {
Some(Value::Float(f)) => *f,
Some(Value::Int(i)) => *i as f64,
_ => panic!("Expected numeric avg_age"),
};
city_avgs.insert(city.clone(), avg);
}
}
}
assert!((city_avgs.get("New York").unwrap() - 35.0).abs() < 0.001);
assert!((city_avgs.get("Boston").unwrap() - 26.5).abs() < 0.001);
assert!((city_avgs.get("Chicago").unwrap() - 22.0).abs() < 0.001);
}
#[test]
fn test_gql_group_by_sum() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, sum(p.age) AS total_age GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3);
let mut city_sums: HashMap<String, i64> = HashMap::new();
for result in &results {
if let Value::Map(map) = result {
if let (Some(Value::String(city)), Some(Value::Int(sum))) =
(map.get("city"), map.get("total_age"))
{
city_sums.insert(city.clone(), *sum);
}
}
}
assert_eq!(city_sums.get("New York"), Some(&105));
assert_eq!(city_sums.get("Boston"), Some(&53));
assert_eq!(city_sums.get("Chicago"), Some(&22));
}
#[test]
fn test_gql_group_by_min_max() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, min(p.age) AS min_age, max(p.age) AS max_age GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3);
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::String(city)) = map.get("city") {
let min = map.get("min_age").and_then(|v| {
if let Value::Int(i) = v {
Some(*i)
} else {
None
}
});
let max = map.get("max_age").and_then(|v| {
if let Value::Int(i) = v {
Some(*i)
} else {
None
}
});
match city.as_str() {
"New York" => {
assert_eq!(min, Some(30), "New York min should be 30");
assert_eq!(max, Some(40), "New York max should be 40");
}
"Boston" => {
assert_eq!(min, Some(25), "Boston min should be 25");
assert_eq!(max, Some(28), "Boston max should be 28");
}
"Chicago" => {
assert_eq!(min, Some(22), "Chicago min should be 22");
assert_eq!(max, Some(22), "Chicago max should be 22");
}
_ => panic!("Unexpected city: {}", city),
}
}
}
}
}
#[test]
fn test_gql_group_by_collect() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, collect(p.name) AS names GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3);
for result in &results {
if let Value::Map(map) = result {
if let (Some(Value::String(city)), Some(Value::List(names))) =
(map.get("city"), map.get("names"))
{
match city.as_str() {
"New York" => {
assert_eq!(names.len(), 3);
assert!(names.contains(&Value::String("Alice".to_string())));
assert!(names.contains(&Value::String("Carol".to_string())));
assert!(names.contains(&Value::String("Frank".to_string())));
}
"Boston" => {
assert_eq!(names.len(), 2);
assert!(names.contains(&Value::String("Bob".to_string())));
assert!(names.contains(&Value::String("Dave".to_string())));
}
"Chicago" => {
assert_eq!(names.len(), 1);
assert!(names.contains(&Value::String("Eve".to_string())));
}
_ => panic!("Unexpected city: {}", city),
}
}
}
}
}
#[test]
fn test_gql_group_by_with_where() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) WHERE p.age >= 25 RETURN p.city AS city, count(*) AS cnt GROUP BY p.city")
.unwrap();
assert_eq!(
results.len(),
2,
"Should have 2 groups (Chicago filtered out)"
);
let mut city_counts: HashMap<String, i64> = HashMap::new();
for result in &results {
if let Value::Map(map) = result {
if let (Some(Value::String(city)), Some(Value::Int(count))) =
(map.get("city"), map.get("cnt"))
{
city_counts.insert(city.clone(), *count);
}
}
}
assert_eq!(city_counts.get("New York"), Some(&3));
assert_eq!(city_counts.get("Boston"), Some(&2));
assert_eq!(city_counts.get("Chicago"), None); }
#[test]
fn test_gql_group_by_validation_error() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let result = graph.gql("MATCH (p:Person) RETURN p.city, p.name, count(*) GROUP BY p.city");
assert!(
result.is_err(),
"Should fail when expression not in GROUP BY"
);
let err = result.unwrap_err();
let err_msg = format!("{}", err);
assert!(
err_msg.contains("p.name") || err_msg.contains("GROUP BY"),
"Error should mention the problematic expression or GROUP BY: {}",
err_msg
);
}
#[test]
fn test_gql_group_by_with_order_by() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city ORDER BY cnt DESC")
.unwrap();
assert_eq!(results.len(), 3);
let counts: Vec<i64> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::Int(cnt)) = map.get("cnt") {
return Some(*cnt);
}
}
None
})
.collect();
assert_eq!(
counts,
vec![3, 2, 1],
"Should be ordered by count descending"
);
}
#[test]
fn test_gql_group_by_with_limit() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city ORDER BY cnt DESC LIMIT 2")
.unwrap();
assert_eq!(results.len(), 2, "Should have only 2 results due to LIMIT");
let counts: Vec<i64> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::Int(cnt)) = map.get("cnt") {
return Some(*cnt);
}
}
None
})
.collect();
assert_eq!(counts, vec![3, 2]);
}
#[test]
fn test_gql_group_by_no_alias() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city, count(*) GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3);
for result in &results {
if let Value::Map(map) = result {
assert!(
map.contains_key("p.city"),
"Map should have 'p.city' key: {:?}",
map
);
}
}
}
#[test]
fn test_gql_group_by_multiple_aggregates() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql(
"MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt, sum(p.age) AS total, avg(p.age) AS avg_age GROUP BY p.city",
)
.unwrap();
assert_eq!(results.len(), 3);
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::String(city)) = map.get("city") {
let cnt = map.get("cnt");
let total = map.get("total");
let avg = map.get("avg_age");
match city.as_str() {
"New York" => {
assert_eq!(cnt, Some(&Value::Int(3)));
assert_eq!(total, Some(&Value::Int(105))); if let Some(Value::Float(f)) = avg {
assert!((f - 35.0).abs() < 0.001);
}
}
"Boston" => {
assert_eq!(cnt, Some(&Value::Int(2)));
assert_eq!(total, Some(&Value::Int(53))); if let Some(Value::Float(f)) = avg {
assert!((f - 26.5).abs() < 0.001);
}
}
"Chicago" => {
assert_eq!(cnt, Some(&Value::Int(1)));
assert_eq!(total, Some(&Value::Int(22)));
if let Some(Value::Float(f)) = avg {
assert!((f - 22.0).abs() < 0.001);
}
}
_ => panic!("Unexpected city: {}", city),
}
}
}
}
}
#[test]
fn test_gql_group_by_single_return_count_only() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(*) AS cnt GROUP BY p.city")
.unwrap();
assert_eq!(results.len(), 3);
let mut counts: Vec<i64> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::Int(cnt)) = map.get("cnt") {
return Some(*cnt);
}
}
None
})
.collect();
counts.sort();
assert_eq!(counts, vec![1, 2, 3], "Should have counts 1, 2, 3");
}
#[test]
fn test_gql_having_count_filter() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) > 1")
.unwrap();
assert_eq!(results.len(), 2, "Should have 2 groups with count > 1");
let mut cities: Vec<String> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::String(city)) = map.get("city") {
return Some(city.clone());
}
}
None
})
.collect();
cities.sort();
assert_eq!(cities, vec!["Boston", "New York"]);
}
#[test]
fn test_gql_having_count_gte() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) >= 2")
.unwrap();
assert_eq!(results.len(), 2, "Should have 2 groups with count >= 2");
}
#[test]
fn test_gql_having_count_equals() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) = 3")
.unwrap();
assert_eq!(results.len(), 1, "Should have 1 group with count = 3");
if let Value::Map(map) = &results[0] {
assert_eq!(
map.get("city"),
Some(&Value::String("New York".to_string()))
);
assert_eq!(map.get("cnt"), Some(&Value::Int(3)));
} else {
panic!("Expected Value::Map");
}
}
#[test]
fn test_gql_having_avg_filter() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, avg(p.age) AS avg_age GROUP BY p.city HAVING avg(p.age) >= 30")
.unwrap();
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::Float(avg)) = map.get("avg_age") {
assert!(*avg >= 30.0, "avg_age should be >= 30, got {}", avg);
} else if let Some(Value::Int(avg)) = map.get("avg_age") {
assert!(*avg >= 30, "avg_age should be >= 30, got {}", avg);
}
}
}
}
#[test]
fn test_gql_having_with_alias() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING cnt > 1")
.unwrap();
assert_eq!(results.len(), 2, "Should have 2 groups with cnt > 1");
}
#[test]
fn test_gql_having_and_condition() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt, avg(p.age) AS avg_age GROUP BY p.city HAVING count(*) > 1 AND avg(p.age) < 35")
.unwrap();
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::Int(cnt)) = map.get("cnt") {
assert!(*cnt > 1, "cnt should be > 1");
}
if let Some(Value::Float(avg)) = map.get("avg_age") {
assert!(*avg < 35.0, "avg_age should be < 35");
}
}
}
}
#[test]
fn test_gql_having_or_condition() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) = 1 OR count(*) = 3")
.unwrap();
assert_eq!(
results.len(),
2,
"Should have 2 groups with count = 1 OR count = 3"
);
let mut counts: Vec<i64> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::Int(cnt)) = map.get("cnt") {
return Some(*cnt);
}
}
None
})
.collect();
counts.sort();
assert_eq!(counts, vec![1, 3], "Should have counts 1 and 3");
}
#[test]
fn test_gql_having_filters_all() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) > 10")
.unwrap();
assert_eq!(results.len(), 0, "Should have 0 groups with count > 10");
}
#[test]
fn test_gql_having_sum_filter() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, sum(p.age) AS total_age GROUP BY p.city HAVING sum(p.age) > 50")
.unwrap();
for result in &results {
if let Value::Map(map) = result {
if let Some(Value::Int(total)) = map.get("total_age") {
assert!(*total > 50, "total_age should be > 50, got {}", total);
}
}
}
}
#[test]
fn test_gql_having_with_order_by_limit() {
let graph = create_group_by_test_graph();
let snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt GROUP BY p.city HAVING count(*) > 1 ORDER BY cnt DESC LIMIT 1")
.unwrap();
assert_eq!(results.len(), 1, "Should have 1 result after LIMIT");
if let Value::Map(map) = &results[0] {
assert_eq!(
map.get("city"),
Some(&Value::String("New York".to_string()))
);
assert_eq!(map.get("cnt"), Some(&Value::Int(3)));
}
}
#[test]
fn test_gql_having_implicit_global_pass() {
let graph = create_group_by_test_graph();
let _snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(*) AS cnt HAVING count(*) > 1")
.unwrap();
assert_eq!(results.len(), 1);
if let Value::Map(map) = &results[0] {
assert_eq!(map.get("cnt"), Some(&Value::Int(6)));
} else {
panic!("Expected Value::Map row, got {:?}", results[0]);
}
}
#[test]
fn test_gql_having_implicit_global_filter() {
let graph = create_group_by_test_graph();
let _snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN count(*) AS cnt HAVING count(*) > 100")
.unwrap();
assert!(
results.is_empty(),
"Expected zero rows when HAVING fails, got {:?}",
results
);
}
#[test]
fn test_gql_having_implicit_grouped_filter() {
let graph = create_group_by_test_graph();
let _snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt HAVING count(*) > 1")
.unwrap();
assert_eq!(results.len(), 2, "Expected 2 cities with count > 1");
let mut cities: Vec<String> = results
.iter()
.filter_map(|r| {
if let Value::Map(map) = r {
if let Some(Value::String(city)) = map.get("city") {
return Some(city.clone());
}
}
None
})
.collect();
cities.sort();
assert_eq!(cities, vec!["Boston", "New York"]);
}
#[test]
fn test_gql_having_implicit_alias_reference() {
let graph = create_group_by_test_graph();
let _snapshot = graph.snapshot();
let results = graph
.gql("MATCH (p:Person) RETURN p.city AS city, count(*) AS cnt HAVING cnt >= 2")
.unwrap();
assert_eq!(results.len(), 2, "Expected 2 cities with cnt >= 2");
}