#![allow(unused_variables)]
use std::collections::HashMap;
use std::sync::Arc;
use interstellar::gql::{
execute_mutation, execute_mutation_with_schema, parse, parse_statement, CompileError,
MutationError,
};
use interstellar::schema::{PropertyType, SchemaBuilder, ValidationMode};
use interstellar::storage::{Graph, GraphMutWrapper, GraphStorage, GraphStorageMut};
use interstellar::value::Value;
fn create_test_graph() -> Arc<Graph> {
let graph = Arc::new(Graph::new());
let alice_id = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Alice".to_string())),
("age".to_string(), Value::Int(30)),
]),
);
let bob_id = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Bob".to_string())),
("age".to_string(), Value::Int(25)),
]),
);
let charlie_id = graph.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Charlie".to_string())),
("age".to_string(), Value::Int(35)),
]),
);
graph
.add_edge(
alice_id,
bob_id,
"KNOWS",
HashMap::from([("since".to_string(), Value::Int(2020))]),
)
.unwrap();
graph
.add_edge(
bob_id,
charlie_id,
"KNOWS",
HashMap::from([("since".to_string(), Value::Int(2021))]),
)
.unwrap();
graph
}
fn execute_gql(
storage: &mut GraphMutWrapper<'_>,
query: &str,
) -> Result<Vec<Value>, MutationError> {
let stmt = parse_statement(query).map_err(|e| {
MutationError::Compile(CompileError::UnsupportedFeature(format!(
"Parse error: {}",
e
)))
})?;
execute_mutation(&stmt, storage)
}
#[test]
fn test_execute_mutation_with_query_statement_error() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let query = parse("MATCH (n:Person) RETURN n").unwrap();
let result = execute_mutation(
&interstellar::gql::Statement::Query(Box::new(query)),
&mut storage,
);
assert!(result.is_err());
match result {
Err(MutationError::Compile(CompileError::UnsupportedFeature(msg))) => {
assert!(msg.contains("Expected mutation statement"));
}
_ => panic!("Expected UnsupportedFeature error"),
}
}
#[test]
fn test_execute_mutation_with_ddl_statement_error() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let stmt = parse_statement("CREATE NODE TYPE Person ()").unwrap();
let result = execute_mutation(&stmt, &mut storage);
assert!(result.is_err());
match result {
Err(MutationError::Compile(CompileError::UnsupportedFeature(msg))) => {
assert!(msg.contains("DDL statement"));
}
_ => panic!("Expected UnsupportedFeature error for DDL"),
}
}
#[test]
fn test_execute_mutation_with_schema_ddl_error() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let schema = SchemaBuilder::new()
.mode(ValidationMode::Strict)
.vertex("Person")
.done()
.build();
let stmt = parse_statement("CREATE NODE TYPE Person ()").unwrap();
let result = execute_mutation_with_schema(&stmt, &mut storage, Some(&schema));
assert!(result.is_err());
match result {
Err(MutationError::Compile(CompileError::UnsupportedFeature(msg))) => {
assert!(msg.contains("DDL statement"));
}
_ => panic!("Expected UnsupportedFeature error for DDL"),
}
}
#[test]
fn test_create_vertex_with_schema_validation() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let schema = SchemaBuilder::new()
.mode(ValidationMode::Strict)
.vertex("Person")
.property("name", PropertyType::String)
.property("age", PropertyType::Int)
.done()
.build();
let stmt = parse_statement("CREATE (n:Person {name: 'Alice', age: 30})").unwrap();
let result = execute_mutation_with_schema(&stmt, &mut storage, Some(&schema));
assert!(result.is_ok());
assert_eq!(storage.vertex_count(), 1);
}
#[test]
fn test_set_edge_property_with_schema() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let schema = SchemaBuilder::new()
.mode(ValidationMode::Strict)
.edge("KNOWS")
.property("since", PropertyType::Int)
.property("strength", PropertyType::Int)
.done()
.build();
let stmt =
parse_statement("MATCH (a:Person)-[r:KNOWS]->(b:Person) SET r.strength = 10").unwrap();
let result = execute_mutation_with_schema(&stmt, &mut storage, Some(&schema));
assert!(result.is_ok());
}
#[test]
fn test_create_edge_with_schema_validation() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let schema = SchemaBuilder::new()
.mode(ValidationMode::Strict)
.vertex("Person")
.property("name", PropertyType::String)
.done()
.edge("KNOWS")
.from(&["Person"])
.to(&["Person"])
.property("since", PropertyType::Int)
.done()
.build();
let stmt = parse_statement(
"CREATE (a:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(b:Person {name: 'Bob'})",
)
.unwrap();
let result = execute_mutation_with_schema(&stmt, &mut storage, Some(&schema));
assert!(result.is_ok());
assert_eq!(storage.edge_count(), 1);
}
#[test]
fn test_match_incoming_edge() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (b:Person {name: 'Bob'})<-[r:KNOWS]-(a:Person) SET b.incoming = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_bidirectional_edge() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (a:Person)-[r:KNOWS]-(b:Person) WHERE a.name = 'Bob' SET a.connected = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_is_null_expression() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Alice".to_string())),
("email".to_string(), Value::Null),
]),
);
storage.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Bob".to_string())),
(
"email".to_string(),
Value::String("bob@example.com".to_string()),
),
]),
);
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.email IS NULL SET n.needs_email = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_is_not_null_expression() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Person",
HashMap::from([("name".to_string(), Value::String("Alice".to_string()))]),
);
storage.add_vertex(
"Person",
HashMap::from([
("name".to_string(), Value::String("Bob".to_string())),
(
"email".to_string(),
Value::String("bob@example.com".to_string()),
),
]),
);
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.email IS NOT NULL SET n.has_email = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_in_list_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.name IN ['Alice', 'Bob'] SET n.in_group = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_not_in_list_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.name NOT IN ['Alice', 'Bob'] SET n.not_in_group = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_not_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE NOT n.age > 30 SET n.young = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_or_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.age < 26 OR n.age > 34 SET n.extreme_age = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_arithmetic_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) SET n.doubled_age = n.age * 2",
);
assert!(result.is_ok());
for vertex in storage.all_vertices() {
if let (Some(Value::Int(age)), Some(Value::Int(doubled))) = (
vertex.properties.get("age"),
vertex.properties.get("doubled_age"),
) {
assert_eq!(*doubled, *age * 2);
}
}
}
#[test]
fn test_match_with_subtraction_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) SET n.age_minus_ten = n.age - 10",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_division_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) SET n.half_age = n.age / 2");
assert!(result.is_ok());
}
#[test]
fn test_match_with_modulo_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) SET n.age_mod = n.age % 10");
assert!(result.is_ok());
}
#[test]
fn test_match_with_negation_expression() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) SET n.neg_age = -n.age");
assert!(result.is_ok());
}
#[test]
fn test_match_with_less_than_or_equal() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.age <= 30 SET n.thirty_or_less = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_greater_than_or_equal() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.age >= 30 SET n.thirty_or_more = true",
);
assert!(result.is_ok());
}
#[test]
fn test_match_with_not_equal() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.name <> 'Alice' SET n.not_alice = true",
);
assert!(result.is_ok());
}
#[test]
fn test_return_multiple_items() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let results = execute_gql(
&mut storage,
"CREATE (n:Person {name: 'Alice', age: 30}) RETURN n.name, n.age",
);
assert!(results.is_ok());
let results = results.unwrap();
assert_eq!(results.len(), 1);
if let Value::Map(map) = &results[0] {
assert!(map.contains_key("n.name"));
assert!(map.contains_key("n.age"));
} else {
panic!("Expected Map value");
}
}
#[test]
fn test_return_with_alias() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let results = execute_gql(
&mut storage,
"CREATE (n:Person {name: 'Alice', age: 30}) RETURN n.name AS person_name, n.age AS person_age",
);
assert!(results.is_ok());
let results = results.unwrap();
if let Value::Map(map) = &results[0] {
assert!(map.contains_key("person_name"));
assert!(map.contains_key("person_age"));
} else {
panic!("Expected Map value with aliases");
}
}
#[test]
fn test_return_empty_items() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let results = execute_gql(&mut storage, "CREATE (n:Person {name: 'Alice'})");
assert!(results.is_ok());
assert!(results.unwrap().is_empty());
}
#[test]
fn test_match_edge_and_set_property() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (a:Person)-[r:KNOWS]->(b:Person) SET r.verified = true",
);
assert!(result.is_ok());
for edge in storage.all_edges() {
assert_eq!(edge.properties.get("verified"), Some(&Value::Bool(true)));
}
}
#[test]
fn test_match_edge_property_filter() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (a:Person)-[r:KNOWS {since: 2020}]->(b:Person) SET a.knows_since_2020 = true",
);
assert!(result.is_ok());
}
#[test]
fn test_remove_edge_property() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (a:Person)-[r:KNOWS]->(b:Person) REMOVE r.since",
);
assert!(result.is_ok());
for edge in storage.all_edges() {
assert_eq!(edge.properties.get("since"), Some(&Value::Null));
}
}
#[test]
fn test_merge_creates_when_no_match() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MERGE (n:Person {name: 'Diana'}) ON CREATE SET n.new = true",
);
assert!(result.is_ok());
assert_eq!(storage.vertex_count(), 1);
let vertex = storage.all_vertices().next().unwrap();
assert_eq!(vertex.properties.get("new"), Some(&Value::Bool(true)));
}
#[test]
fn test_merge_matches_existing() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let initial_count = storage.vertex_count();
let result = execute_gql(
&mut storage,
"MERGE (n:Person {name: 'Alice'}) ON MATCH SET n.merged = true",
);
assert!(result.is_ok());
assert_eq!(storage.vertex_count(), initial_count);
let alice = storage
.all_vertices()
.find(|v| v.properties.get("name") == Some(&Value::String("Alice".to_string())))
.unwrap();
assert_eq!(alice.properties.get("merged"), Some(&Value::Bool(true)));
}
#[test]
fn test_set_unbound_variable_error() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) SET x.prop = 1");
assert!(matches!(result, Err(MutationError::UnboundVariable(_))));
}
#[test]
fn test_delete_unbound_variable_error() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) DELETE x");
assert!(matches!(result, Err(MutationError::UnboundVariable(_))));
}
#[test]
fn test_remove_unbound_variable_error() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) REMOVE x.prop");
assert!(matches!(result, Err(MutationError::UnboundVariable(_))));
}
#[test]
fn test_detach_delete_unbound_variable_error() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person) DETACH DELETE x");
assert!(matches!(result, Err(MutationError::UnboundVariable(_))));
}
#[test]
fn test_create_vertex_missing_label_error() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "CREATE (n)");
assert!(result.is_err());
}
#[test]
fn test_delete_vertex_with_edges_error() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(&mut storage, "MATCH (n:Person {name: 'Alice'}) DELETE n");
assert!(matches!(result, Err(MutationError::VertexHasEdges(_))));
}
#[test]
fn test_match_no_results() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person {name: 'NonExistent'}) SET n.found = true",
);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_create_edge_between_matched_vertices() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let initial_edge_count = storage.edge_count();
let result = execute_gql(
&mut storage,
"MATCH (a:Person {name: 'Alice'})-[:KNOWS]->(b:Person) CREATE (a)-[:FRIENDS]->(b)",
);
assert!(result.is_ok());
assert_eq!(storage.edge_count(), initial_edge_count + 1);
}
#[test]
fn test_create_incoming_edge() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"CREATE (a:Person {name: 'Alice'})<-[:FOLLOWS]-(b:Person {name: 'Bob'})",
);
assert!(result.is_ok());
assert_eq!(storage.vertex_count(), 2);
assert_eq!(storage.edge_count(), 1);
let edge = storage.all_edges().next().unwrap();
assert_eq!(edge.label, "FOLLOWS");
}
#[test]
fn test_create_with_edge_variable() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
let results = execute_gql(
&mut storage,
"CREATE (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'}) RETURN r",
);
assert!(results.is_ok());
let results = results.unwrap();
assert_eq!(results.len(), 1);
assert!(matches!(results[0], Value::Edge(_)));
}
#[test]
fn test_detach_delete_edge() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let initial_edge_count = storage.edge_count();
let result = execute_gql(
&mut storage,
"MATCH (a:Person)-[r:KNOWS]->(b:Person) WHERE a.name = 'Alice' DETACH DELETE r",
);
assert!(result.is_ok());
assert_eq!(storage.edge_count(), initial_edge_count - 1);
}
#[test]
fn test_detach_delete_removes_all_edges() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person {name: 'Bob'}) DETACH DELETE n",
);
assert!(result.is_ok());
assert_eq!(storage.vertex_count(), 2);
}
#[test]
fn test_match_with_float_comparison() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([
("name".to_string(), Value::String("A".to_string())),
("price".to_string(), Value::Float(10.5)),
]),
);
storage.add_vertex(
"Item",
HashMap::from([
("name".to_string(), Value::String("B".to_string())),
("price".to_string(), Value::Float(20.5)),
]),
);
let result = execute_gql(
&mut storage,
"MATCH (n:Item) WHERE n.price > 15.0 SET n.expensive = true",
);
assert!(result.is_ok());
}
#[test]
fn test_float_arithmetic() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([("value".to_string(), Value::Float(10.5))]),
);
let result = execute_gql(&mut storage, "MATCH (n:Item) SET n.doubled = n.value * 2.0");
assert!(result.is_ok());
let item = storage.all_vertices().next().unwrap();
if let Some(Value::Float(doubled)) = item.properties.get("doubled") {
assert!((doubled - 21.0).abs() < 0.01);
}
}
#[test]
fn test_int_float_mixed_comparison() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([
("int_val".to_string(), Value::Int(10)),
("float_val".to_string(), Value::Float(10.5)),
]),
);
let result = execute_gql(
&mut storage,
"MATCH (n:Item) WHERE n.int_val < n.float_val SET n.int_smaller = true",
);
assert!(result.is_ok());
}
#[test]
fn test_int_float_mixed_arithmetic() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([("int_val".to_string(), Value::Int(10))]),
);
let result = execute_gql(&mut storage, "MATCH (n:Item) SET n.sum = n.int_val + 5.5");
assert!(result.is_ok());
}
#[test]
fn test_string_comparison() {
let graph = create_test_graph();
let mut storage = graph.as_storage_mut();
let result = execute_gql(
&mut storage,
"MATCH (n:Person) WHERE n.name < 'Bob' SET n.before_bob = true",
);
assert!(result.is_ok());
let alice = storage
.all_vertices()
.find(|v| v.properties.get("name") == Some(&Value::String("Alice".to_string())))
.unwrap();
assert_eq!(alice.properties.get("before_bob"), Some(&Value::Bool(true)));
}
#[test]
fn test_division_by_zero_int() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([("value".to_string(), Value::Int(10))]),
);
let result = execute_gql(&mut storage, "MATCH (n:Item) SET n.result = n.value / 0");
assert!(result.is_ok());
let item = storage.all_vertices().next().unwrap();
assert_eq!(item.properties.get("result"), Some(&Value::Int(0)));
}
#[test]
fn test_modulo_by_zero() {
let graph = Arc::new(Graph::new());
let mut storage = graph.as_storage_mut();
storage.add_vertex(
"Item",
HashMap::from([("value".to_string(), Value::Int(10))]),
);
let result = execute_gql(&mut storage, "MATCH (n:Item) SET n.result = n.value % 0");
assert!(result.is_ok());
}