#![allow(unused_variables)]
use std::collections::HashMap;
use std::sync::Arc;
use interstellar::gql::{
compile, compile_statement, compile_statement_with_params, compile_with_params, parse,
parse_statement, CompileError, Parameters,
};
use interstellar::storage::Graph;
use interstellar::value::Value;
fn create_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let alice = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Alice".to_string())),
("age".to_string(), Value::Int(30)),
("city".to_string(), Value::String("NYC".to_string())),
]),
);
let bob = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Bob".to_string())),
("age".to_string(), Value::Int(25)),
("city".to_string(), Value::String("LA".to_string())),
]),
);
let charlie = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Charlie".to_string())),
("age".to_string(), Value::Int(35)),
("city".to_string(), Value::String("NYC".to_string())),
]),
);
let diana = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Diana".to_string())),
("age".to_string(), Value::Int(28)),
("city".to_string(), Value::String("Chicago".to_string())),
]),
);
let gremlin = graph.add_vertex(
"Software",
HashMap::from([
("name".to_string(), Value::String("Gremlin".to_string())),
("lang".to_string(), Value::String("Java".to_string())),
]),
);
let rust_proj = graph.add_vertex(
"Software",
HashMap::from([
(
"name".to_string(),
Value::String("Interstellar".to_string()),
),
("lang".to_string(), Value::String("Rust".to_string())),
]),
);
graph
.add_edge(
alice,
bob,
"KNOWS",
HashMap::from([
("since".to_string(), Value::Int(2020)),
("weight".to_string(), Value::Float(0.8)),
]),
)
.unwrap();
graph
.add_edge(
alice,
charlie,
"KNOWS",
HashMap::from([("since".to_string(), Value::Int(2019))]),
)
.unwrap();
graph
.add_edge(
bob,
charlie,
"KNOWS",
HashMap::from([("since".to_string(), Value::Int(2021))]),
)
.unwrap();
graph
.add_edge(
charlie,
diana,
"KNOWS",
HashMap::from([("since".to_string(), Value::Int(2018))]),
)
.unwrap();
graph
.add_edge(
alice,
gremlin,
"CREATED",
HashMap::from([("year".to_string(), Value::Int(2015))]),
)
.unwrap();
graph
.add_edge(
bob,
rust_proj,
"CREATED",
HashMap::from([("year".to_string(), Value::Int(2023))]),
)
.unwrap();
graph
}
#[test]
fn test_compile_statement_basic_query() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement("MATCH (n:Person) RETURN n.name").unwrap();
let results = compile_statement(&stmt, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_compile_statement_with_params() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement("MATCH (n:Person) WHERE n.age >= $minAge RETURN n.name").unwrap();
let mut params = Parameters::new();
params.insert("minAge".to_string(), Value::Int(28));
let results = compile_statement_with_params(&stmt, &snapshot, ¶ms).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_compile_statement_mutation_error() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement("CREATE (n:Person {name: 'Eve'})").unwrap();
let result = compile_statement(&stmt, &snapshot);
assert!(result.is_err());
match result.unwrap_err() {
CompileError::UnsupportedFeature(msg) => {
assert!(msg.contains("Mutation"));
}
_ => panic!("Expected UnsupportedFeature error"),
}
}
#[test]
fn test_compile_statement_ddl_error() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement("CREATE NODE TYPE Person (name STRING NOT NULL, age INT)").unwrap();
let result = compile_statement(&stmt, &snapshot);
assert!(result.is_err());
match result.unwrap_err() {
CompileError::UnsupportedFeature(msg) => {
assert!(msg.contains("DDL"));
}
_ => panic!("Expected UnsupportedFeature error"),
}
}
#[test]
fn test_union_query_deduplication() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement(
r#"
MATCH (a:Person) WHERE a.city = 'NYC' RETURN a.name
UNION
MATCH (b:Person) WHERE b.age > 28 RETURN b.name
"#,
)
.unwrap();
let results = compile_statement(&stmt, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_union_all_keeps_duplicates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement(
r#"
MATCH (a:Person) WHERE a.city = 'NYC' RETURN a.name
UNION ALL
MATCH (b:Person) WHERE b.age > 28 RETURN b.name
"#,
)
.unwrap();
let results = compile_statement(&stmt, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_union_with_params() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let stmt = parse_statement(
r#"
MATCH (a:Person) WHERE a.age >= $age1 RETURN a.name
UNION
MATCH (b:Person) WHERE b.city = $city RETURN b.name
"#,
)
.unwrap();
let mut params = Parameters::new();
params.insert("age1".to_string(), Value::Int(35));
params.insert("city".to_string(), Value::String("LA".to_string()));
let results = compile_statement_with_params(&stmt, &snapshot, ¶ms).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_with_clause_simple_projection() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH n.name AS name, n.age AS age
RETURN name, age
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_with_clause_with_aggregation() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH n.city AS city, COUNT(*) AS cnt
RETURN city, cnt
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_with_clause_distinct() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH DISTINCT n.city AS city
RETURN city
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_with_clause_with_where() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH n.name AS name, n.age AS age WHERE age >= 28
RETURN name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_with_clause_with_order_and_limit() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH n.name AS name, n.age AS age ORDER BY age DESC LIMIT 2
RETURN name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_with_global_aggregation() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH COUNT(n) AS total
RETURN total
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(4));
}
#[test]
fn test_let_clause_simple() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
LET doubled = n.age * 2
RETURN n.name, doubled
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_let_clause_with_aggregate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
LET totalAge = SUM(n.age)
RETURN n.name, totalAge
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_call_clause_subquery() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:KNOWS]->(friend:Person)
RETURN friend.name AS friendName
}
RETURN p.name, friendName
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_group_by_with_having() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN n.city AS city, COUNT(*) AS cnt
GROUP BY n.city
HAVING COUNT(*) > 1
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
if let Value::Map(map) = &results[0] {
assert_eq!(map.get("city"), Some(&Value::String("NYC".to_string())));
}
}
#[test]
fn test_group_by_multiple_aggregates() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN n.city AS city, COUNT(*) AS cnt, AVG(n.age) AS avgAge
GROUP BY n.city
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3); }
#[test]
fn test_optional_match_basic() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
OPTIONAL MATCH (p)-[:CREATED]->(s:Software)
RETURN p.name, s.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_optional_match_with_filter() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
OPTIONAL MATCH (p)-[e:KNOWS]->(friend:Person)
RETURN p.name, friend.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_edge_variable_access() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (a:Person)-[e:KNOWS]->(b:Person)
RETURN a.name, e.since, b.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
for result in &results {
if let Value::Map(map) = result {
assert!(map.get("e.since").is_some() || map.get("since").is_some());
}
}
}
#[test]
fn test_edge_property_filter() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (a:Person)-[e:KNOWS]->(b:Person)
WHERE e.since >= 2020
RETURN a.name, e.since, b.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_with_path_clause() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (a:Person)-[:KNOWS]->(b:Person)
WITH PATH
RETURN a.name, b.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_unwind_inline_list() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
UNWIND [1, 2, 3] AS x
WHERE n.name = 'Alice'
RETURN n.name, x
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_unwind_property_list() {
let graph = Arc::new(Graph::new());
graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Alice".to_string())),
(
"hobbies".to_string(),
Value::List(vec![
Value::String("reading".to_string()),
Value::String("gaming".to_string()),
]),
),
]),
);
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
UNWIND n.hobbies AS hobby
RETURN n.name, hobby
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_unwind_null_property() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
UNWIND n.nonexistent AS x
WHERE n.name = 'Alice'
RETURN n.name, x
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_unwind_non_list_property() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
UNWIND n.age AS x
WHERE n.name = 'Alice'
RETURN n.name, x
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_aggregate_sum_with_floats() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (a:Person)-[e:KNOWS]->(b:Person)
RETURN SUM(e.weight)
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
if let Value::Float(sum) = &results[0] {
assert!((sum - 0.8).abs() < 0.001);
}
}
#[test]
fn test_aggregate_distinct_count() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN COUNT(DISTINCT n.city)
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(3)); }
#[test]
fn test_aggregate_min_max() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN MIN(n.age), MAX(n.age)
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_aggregate_avg_empty() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:NonExistent)
RETURN AVG(n.value)
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(results.is_empty() || results[0] == Value::Null);
}
#[test]
fn test_aggregate_collect() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WHERE n.city = 'NYC'
RETURN COLLECT(n.name)
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
if let Value::List(names) = &results[0] {
assert_eq!(names.len(), 2); }
}
#[test]
fn test_order_by_null_values() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
OPTIONAL MATCH (p)-[:CREATED]->(s:Software)
RETURN p.name, s.lang
ORDER BY s.lang
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_order_by_multiple_keys() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN n.city, n.name, n.age
ORDER BY n.city, n.age DESC
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_empty_pattern_error() {
let result = parse("MATCH RETURN 1");
assert!(result.is_err());
}
#[test]
fn test_unbound_variable_error() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Person) RETURN m.name").unwrap();
let result = compile(&query, &snapshot);
assert!(result.is_err());
}
#[test]
fn test_expression_with_binary_aggregate() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN COUNT(*) AS cnt
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
let cnt = match &results[0] {
Value::Int(n) => *n,
Value::Map(map) => map.get("cnt").and_then(|v| v.as_i64()).unwrap_or(0),
_ => 0,
};
assert_eq!(cnt, 4);
}
#[test]
fn test_case_expression() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN n.name,
CASE WHEN n.age >= 30 THEN 'Senior' ELSE 'Junior' END AS level
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_list_membership() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WHERE n.city IN ['NYC', 'LA']
RETURN n.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_not_in_list() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WHERE n.city NOT IN ['NYC', 'LA']
RETURN n.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_parameter_in_where() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WHERE n.name = $name
RETURN n.name
"#,
)
.unwrap();
let mut params = Parameters::new();
params.insert("name".to_string(), Value::String("Alice".to_string()));
let results = compile_with_params(&query, &snapshot, ¶ms).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_multiple_parameters() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WHERE n.age >= $minAge AND n.city = $city
RETURN n.name
"#,
)
.unwrap();
let mut params = Parameters::new();
params.insert("minAge".to_string(), Value::Int(25));
params.insert("city".to_string(), Value::String("NYC".to_string()));
let results = compile_with_params(&query, &snapshot, ¶ms).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_empty_rows_with_clause() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:NonExistent)
WITH n.name AS name
RETURN name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(results.is_empty());
}
#[test]
fn test_empty_rows_let_clause() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:NonExistent)
LET x = n.age * 2
RETURN n.name, x
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(results.is_empty());
}
#[test]
fn test_with_expression_keys() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
WITH n.name AS name, COUNT(*) AS cnt
RETURN name, cnt
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 4);
}
#[test]
fn test_return_distinct() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (n:Person)
RETURN DISTINCT n.city
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3); }
#[test]
fn test_variable_length_path() {
let graph = create_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (a:Person)-[:KNOWS*1..2]->(b:Person)
WHERE a.name = 'Alice'
RETURN b.name
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(results.len() >= 2);
}