#![allow(unused_variables)]
use interstellar::gql::{compile, parse, CompileError};
use interstellar::storage::Graph;
use interstellar::value::Value;
use std::collections::HashMap;
use std::sync::Arc;
#[test]
fn test_compile_simple_match() {
let graph = Arc::new(Graph::new());
let mut props = HashMap::new();
props.insert("name".to_string(), Value::from("Alice"));
graph.add_vertex("Person", props.clone());
let mut props2 = HashMap::new();
props2.insert("name".to_string(), Value::from("Bob"));
graph.add_vertex("Person", props2);
let mut props3 = HashMap::new();
props3.insert("name".to_string(), Value::from("Acme"));
graph.add_vertex("Company", props3);
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Person) RETURN n").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_compile_no_label() {
let graph = Arc::new(Graph::new());
let props1 = HashMap::new();
graph.add_vertex("Person", props1);
let props2 = HashMap::new();
graph.add_vertex("Company", props2);
let snapshot = graph.snapshot();
let query = parse("MATCH (n) RETURN n").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_compile_undefined_variable() {
let graph = Arc::new(Graph::new());
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Person) RETURN x").unwrap();
let result = compile(&query, &snapshot);
assert!(matches!(
result,
Err(CompileError::UndefinedVariable { .. })
));
}
fn create_math_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let mut props = HashMap::new();
props.insert("value".to_string(), Value::from(16));
props.insert("x".to_string(), Value::from(3));
props.insert("y".to_string(), Value::from(4));
graph.add_vertex("Number", props);
let mut props2 = HashMap::new();
props2.insert("value".to_string(), Value::from(25));
props2.insert("x".to_string(), Value::from(5));
props2.insert("y".to_string(), Value::from(12));
graph.add_vertex("Number", props2);
graph
}
#[test]
fn test_power_operator_integers() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN 2 ^ 3").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Int(8));
}
}
#[test]
fn test_power_operator_with_property() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) WHERE n.x = 3 RETURN n.x ^ 2").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Int(9));
}
#[test]
fn test_sqrt_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) WHERE n.value = 16 RETURN sqrt(n.value)").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
assert_eq!(results[0], Value::Float(4.0));
}
#[test]
fn test_sqrt_negative_returns_null() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN sqrt(-1)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Null);
}
}
#[test]
fn test_pow_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN pow(2, 10)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Int(1024));
}
}
#[test]
fn test_log_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN log(e())").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - 1.0).abs() < 0.0001);
} else {
panic!("Expected Float, got {:?}", result);
}
}
}
#[test]
fn test_log10_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN log10(100)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(2.0));
}
}
#[test]
fn test_exp_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN exp(0)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(1.0));
}
}
#[test]
fn test_sin_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN sin(0)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(0.0));
}
}
#[test]
fn test_cos_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN cos(0)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(1.0));
}
}
#[test]
fn test_tan_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN tan(0)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(0.0));
}
}
#[test]
fn test_pi_constant() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN pi()").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - std::f64::consts::PI).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_e_constant() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN e()").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - std::f64::consts::E).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_degrees_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN degrees(pi())").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - 180.0).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_radians_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN radians(180)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - std::f64::consts::PI).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_pythagorean_calculation() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) WHERE n.x = 3 RETURN sqrt(n.x ^ 2 + n.y ^ 2)").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
if let Value::Float(f) = &results[0] {
assert!((f - 5.0).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
#[test]
fn test_math_function_basic() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN MATH('_ * 2', 21)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Float(42.0));
}
}
#[test]
fn test_math_function_with_variables() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN MATH('sqrt(a^2 + b^2)', 3, 4)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - 5.0).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_math_function_with_properties() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query =
parse("MATCH (n:Number) WHERE n.x = 3 RETURN MATH('sqrt(a^2 + b^2)', n.x, n.y)").unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
if let Value::Float(f) = &results[0] {
assert!((f - 5.0).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
#[test]
fn test_math_function_parse_error() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN MATH('invalid syntax +++', 1)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Null);
}
}
#[test]
fn test_math_function_domain_error() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN MATH('log(_)', 0)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Null);
}
}
#[test]
fn test_atan2_function() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN atan2(1, 1)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - std::f64::consts::FRAC_PI_4).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
#[test]
fn test_asin_domain_check() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN asin(2)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
assert_eq!(*result, Value::Null);
}
}
#[test]
fn test_combined_math_expression() {
let graph = create_math_test_graph();
let snapshot = graph.snapshot();
let query = parse("MATCH (n:Number) RETURN sin(pi() / 2)").unwrap();
let results = compile(&query, &snapshot).unwrap();
for result in &results {
if let Value::Float(f) = result {
assert!((f - 1.0).abs() < 0.0001);
} else {
panic!("Expected Float");
}
}
}
fn create_call_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let mut alice_props = HashMap::new();
alice_props.insert("name".to_string(), Value::from("Alice"));
alice_props.insert("age".to_string(), Value::from(30));
let alice = graph.add_vertex("Person", alice_props);
let mut bob_props = HashMap::new();
bob_props.insert("name".to_string(), Value::from("Bob"));
bob_props.insert("age".to_string(), Value::from(25));
let bob = graph.add_vertex("Person", bob_props);
let mut charlie_props = HashMap::new();
charlie_props.insert("name".to_string(), Value::from("Charlie"));
charlie_props.insert("age".to_string(), Value::from(35));
let charlie = graph.add_vertex("Person", charlie_props);
let mut matrix_props = HashMap::new();
matrix_props.insert("title".to_string(), Value::from("The Matrix"));
matrix_props.insert("year".to_string(), Value::from(1999));
let matrix = graph.add_vertex("Movie", matrix_props);
let mut inception_props = HashMap::new();
inception_props.insert("title".to_string(), Value::from("Inception"));
inception_props.insert("year".to_string(), Value::from(2010));
let inception = graph.add_vertex("Movie", inception_props);
let mut likes_props = HashMap::new();
likes_props.insert("rating".to_string(), Value::from(5));
let _ = graph.add_edge(alice, matrix, "LIKES", likes_props.clone());
likes_props.insert("rating".to_string(), Value::from(4));
let _ = graph.add_edge(alice, inception, "LIKES", likes_props.clone());
likes_props.insert("rating".to_string(), Value::from(5));
let _ = graph.add_edge(bob, matrix, "LIKES", likes_props.clone());
let _ = charlie;
let _ = graph.add_edge(alice, bob, "KNOWS", HashMap::new());
let _ = graph.add_edge(bob, charlie, "KNOWS", HashMap::new());
graph
}
#[test]
fn test_call_uncorrelated_basic() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
MATCH (m:Movie)
RETURN m.title AS movieTitle
}
RETURN p.name, movieTitle
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 6);
}
#[test]
fn test_call_correlated_basic() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN m.title AS likedMovie
}
RETURN p.name, likedMovie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_call_with_aggregation() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN count(m) AS movieCount
}
RETURN p.name, movieCount
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(results.len() >= 2); }
#[test]
fn test_call_error_undefined_importing_variable() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH x
MATCH (x)-[:LIKES]->(m:Movie)
RETURN m.title AS likedMovie
}
RETURN p.name, likedMovie
"#,
)
.unwrap();
let result = compile(&query, &snapshot);
assert!(matches!(
result,
Err(CompileError::UndefinedVariable { name, .. }) if name == "x"
));
}
#[test]
fn test_call_error_variable_shadowing() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
MATCH (m:Movie)
RETURN m.title AS p
}
RETURN p.name
"#,
)
.unwrap();
let result = compile(&query, &snapshot);
assert!(matches!(
result,
Err(CompileError::DuplicateVariable { name, .. }) if name == "p"
));
}
#[test]
fn test_call_with_where_in_subquery() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
WHERE m.year > 2000
RETURN m.title AS recentMovie
}
RETURN p.name, recentMovie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_call_multiple_clauses() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN count(m) AS likeCount
}
CALL {
WITH p
MATCH (p)-[:KNOWS]->(f:Person)
RETURN count(f) AS friendCount
}
RETURN p.name, likeCount, friendCount
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_call_uncorrelated_with_filter() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
MATCH (m:Movie)
WHERE m.year < 2005
RETURN m.title AS oldMovie
}
RETURN p.name, oldMovie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_call_with_order_and_limit() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN m.title AS movie
ORDER BY m.year DESC
LIMIT 1
}
RETURN p.name, movie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 1);
}
#[test]
fn test_call_empty_subquery_excludes_row() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
WHERE m.year > 2020
RETURN m.title AS futureMovie
}
RETURN p.name, futureMovie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 0);
}
#[test]
fn test_call_importing_with_alias() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p AS person
MATCH (person)-[:LIKES]->(m:Movie)
RETURN m.title AS movie
}
RETURN p.name, movie
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}
#[test]
fn test_call_sum_aggregation() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p
MATCH (p)-[r:LIKES]->(m:Movie)
RETURN sum(r.rating) AS totalRating
}
RETURN p.name, totalRating
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_call_with_union() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN m.title AS item, 'movie' AS kind
UNION
WITH p
MATCH (p)-[:KNOWS]->(f:Person)
RETURN f.name AS item, 'friend' AS kind
}
RETURN p.name, item, kind
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_call_with_union_all() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
WITH p
MATCH (p)-[:LIKES]->(m:Movie)
RETURN 'liked' AS action
UNION ALL
WITH p
MATCH (p)-[:KNOWS]->(f:Person)
RETURN 'knows' AS action
}
RETURN p.name, action
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 3);
}
#[test]
fn test_call_with_union_uncorrelated() {
let graph = create_call_test_graph();
let snapshot = graph.snapshot();
let query = parse(
r#"
MATCH (p:Person)
WHERE p.name = 'Alice'
CALL {
MATCH (m:Movie)
WHERE m.year < 2005
RETURN m.title AS title
UNION
MATCH (m:Movie)
WHERE m.year >= 2005
RETURN m.title AS title
}
RETURN p.name, title
"#,
)
.unwrap();
let results = compile(&query, &snapshot).unwrap();
assert_eq!(results.len(), 2);
}