Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
405
crates/ruvector-graph/tests/cypher_execution_tests.rs
Normal file
405
crates/ruvector-graph/tests/cypher_execution_tests.rs
Normal file
@@ -0,0 +1,405 @@
|
||||
//! Cypher query execution correctness tests
|
||||
//!
|
||||
//! Tests to verify that Cypher queries execute correctly and return expected results.
|
||||
|
||||
use ruvector_graph::{Edge, GraphDB, Label, Node, Properties, PropertyValue};
|
||||
|
||||
fn setup_test_graph() -> GraphDB {
|
||||
let db = GraphDB::new();
|
||||
|
||||
// Create people
|
||||
let mut alice_props = Properties::new();
|
||||
alice_props.insert(
|
||||
"name".to_string(),
|
||||
PropertyValue::String("Alice".to_string()),
|
||||
);
|
||||
alice_props.insert("age".to_string(), PropertyValue::Integer(30));
|
||||
|
||||
let mut bob_props = Properties::new();
|
||||
bob_props.insert("name".to_string(), PropertyValue::String("Bob".to_string()));
|
||||
bob_props.insert("age".to_string(), PropertyValue::Integer(35));
|
||||
|
||||
let mut charlie_props = Properties::new();
|
||||
charlie_props.insert(
|
||||
"name".to_string(),
|
||||
PropertyValue::String("Charlie".to_string()),
|
||||
);
|
||||
charlie_props.insert("age".to_string(), PropertyValue::Integer(28));
|
||||
|
||||
db.create_node(Node::new(
|
||||
"alice".to_string(),
|
||||
vec![Label {
|
||||
name: "Person".to_string(),
|
||||
}],
|
||||
alice_props,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
db.create_node(Node::new(
|
||||
"bob".to_string(),
|
||||
vec![Label {
|
||||
name: "Person".to_string(),
|
||||
}],
|
||||
bob_props,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
db.create_node(Node::new(
|
||||
"charlie".to_string(),
|
||||
vec![Label {
|
||||
name: "Person".to_string(),
|
||||
}],
|
||||
charlie_props,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
// Create relationships
|
||||
db.create_edge(Edge::new(
|
||||
"e1".to_string(),
|
||||
"alice".to_string(),
|
||||
"bob".to_string(),
|
||||
"KNOWS".to_string(),
|
||||
Properties::new(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
db.create_edge(Edge::new(
|
||||
"e2".to_string(),
|
||||
"bob".to_string(),
|
||||
"charlie".to_string(),
|
||||
"KNOWS".to_string(),
|
||||
Properties::new(),
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
db
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_simple_match_all_nodes() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement query execution
|
||||
// let results = db.execute("MATCH (n) RETURN n").unwrap();
|
||||
// assert_eq!(results.len(), 3);
|
||||
|
||||
// For now, just verify the graph was set up correctly
|
||||
assert!(db.get_node("alice").is_some());
|
||||
assert!(db.get_node("bob").is_some());
|
||||
assert!(db.get_node("charlie").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_match_with_label_filter() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN n").unwrap();
|
||||
// assert_eq!(results.len(), 3);
|
||||
|
||||
assert!(db.get_node("alice").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_match_with_property_filter() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person {name: 'Alice'}) RETURN n").unwrap();
|
||||
// assert_eq!(results.len(), 1);
|
||||
|
||||
let alice = db.get_node("alice").unwrap();
|
||||
assert_eq!(
|
||||
alice.properties.get("name"),
|
||||
Some(&PropertyValue::String("Alice".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_match_with_where_clause() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) WHERE n.age > 30 RETURN n").unwrap();
|
||||
// Should return Bob (35)
|
||||
// assert_eq!(results.len(), 1);
|
||||
|
||||
let bob = db.get_node("bob").unwrap();
|
||||
if let Some(PropertyValue::Integer(age)) = bob.properties.get("age") {
|
||||
assert!(*age > 30);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_match_relationship() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (a)-[r:KNOWS]->(b) RETURN a, r, b").unwrap();
|
||||
// Should return 2 relationships
|
||||
|
||||
assert!(db.get_edge("e1").is_some());
|
||||
assert!(db.get_edge("e2").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_create_node() {
|
||||
let db = GraphDB::new();
|
||||
|
||||
// TODO: Implement
|
||||
// db.execute("CREATE (n:Person {name: 'David', age: 40})").unwrap();
|
||||
|
||||
// For now, create manually
|
||||
let mut props = Properties::new();
|
||||
props.insert(
|
||||
"name".to_string(),
|
||||
PropertyValue::String("David".to_string()),
|
||||
);
|
||||
props.insert("age".to_string(), PropertyValue::Integer(40));
|
||||
|
||||
db.create_node(Node::new(
|
||||
"david".to_string(),
|
||||
vec![Label {
|
||||
name: "Person".to_string(),
|
||||
}],
|
||||
props,
|
||||
))
|
||||
.unwrap();
|
||||
|
||||
let david = db.get_node("david").unwrap();
|
||||
assert_eq!(
|
||||
david.properties.get("name"),
|
||||
Some(&PropertyValue::String("David".to_string()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_count_aggregation() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN COUNT(n) AS count").unwrap();
|
||||
// assert_eq!(results[0]["count"], 3);
|
||||
|
||||
// Manual verification
|
||||
assert!(db.get_node("alice").is_some());
|
||||
assert!(db.get_node("bob").is_some());
|
||||
assert!(db.get_node("charlie").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_sum_aggregation() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN SUM(n.age) AS total_age").unwrap();
|
||||
// assert_eq!(results[0]["total_age"], 93); // 30 + 35 + 28
|
||||
|
||||
// Manual verification
|
||||
let ages: Vec<i64> = ["alice", "bob", "charlie"]
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
db.get_node(*id).and_then(|n| {
|
||||
if let Some(PropertyValue::Integer(age)) = n.properties.get("age") {
|
||||
Some(*age)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
assert_eq!(ages.iter().sum::<i64>(), 93);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_avg_aggregation() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN AVG(n.age) AS avg_age").unwrap();
|
||||
// assert_eq!(results[0]["avg_age"], 31.0); // (30 + 35 + 28) / 3
|
||||
|
||||
let ages: Vec<i64> = ["alice", "bob", "charlie"]
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
db.get_node(*id).and_then(|n| {
|
||||
if let Some(PropertyValue::Integer(age)) = n.properties.get("age") {
|
||||
Some(*age)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let avg = ages.iter().sum::<i64>() as f64 / ages.len() as f64;
|
||||
assert!((avg - 31.0).abs() < 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_order_by() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN n ORDER BY n.age ASC").unwrap();
|
||||
// First should be Charlie (28), last should be Bob (35)
|
||||
|
||||
let mut ages: Vec<i64> = ["alice", "bob", "charlie"]
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
db.get_node(*id).and_then(|n| {
|
||||
if let Some(PropertyValue::Integer(age)) = n.properties.get("age") {
|
||||
Some(*age)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
ages.sort();
|
||||
assert_eq!(ages, vec![28, 30, 35]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_limit() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH (n:Person) RETURN n LIMIT 2").unwrap();
|
||||
// assert_eq!(results.len(), 2);
|
||||
|
||||
assert!(db.get_node("alice").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_path_query() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("MATCH p = (a:Person)-[:KNOWS*1..2]->(b:Person) RETURN p").unwrap();
|
||||
// Should find paths: Alice->Bob, Bob->Charlie, Alice->Bob->Charlie
|
||||
|
||||
let e1 = db.get_edge("e1").unwrap();
|
||||
let e2 = db.get_edge("e2").unwrap();
|
||||
|
||||
assert_eq!(e1.from, "alice");
|
||||
assert_eq!(e1.to, "bob");
|
||||
assert_eq!(e2.from, "bob");
|
||||
assert_eq!(e2.to, "charlie");
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Complex Query Execution Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_execute_multi_hop_traversal() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// Find all people connected to Alice within 2 hops
|
||||
// let results = db.execute("
|
||||
// MATCH (alice:Person {name: 'Alice'})-[:KNOWS*1..2]->(connected)
|
||||
// RETURN DISTINCT connected.name
|
||||
// ").unwrap();
|
||||
|
||||
// Should find Bob (1 hop) and Charlie (2 hops)
|
||||
|
||||
assert!(db.get_node("bob").is_some());
|
||||
assert!(db.get_node("charlie").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_pattern_matching() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("
|
||||
// MATCH (a:Person)-[:KNOWS]->(b:Person)-[:KNOWS]->(c:Person)
|
||||
// RETURN a.name, c.name
|
||||
// ").unwrap();
|
||||
|
||||
// Should find Alice knows Charlie through Bob
|
||||
|
||||
assert!(db.get_edge("e1").is_some());
|
||||
assert!(db.get_edge("e2").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_collect_aggregation() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("
|
||||
// MATCH (p:Person)-[:KNOWS]->(friend)
|
||||
// RETURN p.name, COLLECT(friend.name) AS friends
|
||||
// ").unwrap();
|
||||
|
||||
// Alice: [Bob], Bob: [Charlie], Charlie: []
|
||||
|
||||
assert!(db.get_edge("e1").is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_optional_match() {
|
||||
let db = setup_test_graph();
|
||||
|
||||
// TODO: Implement
|
||||
// let results = db.execute("
|
||||
// MATCH (p:Person)
|
||||
// OPTIONAL MATCH (p)-[:KNOWS]->(friend)
|
||||
// RETURN p.name, friend.name
|
||||
// ").unwrap();
|
||||
|
||||
// Should return all people, some with null friends
|
||||
|
||||
assert!(db.get_node("charlie").is_some());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Result Verification Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_query_result_schema() {
|
||||
// TODO: Implement
|
||||
// Verify that query results have correct schema
|
||||
// let db = setup_test_graph();
|
||||
// let results = db.execute("MATCH (n:Person) RETURN n.name AS name, n.age AS age").unwrap();
|
||||
// assert!(results.has_column("name"));
|
||||
// assert!(results.has_column("age"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_result_ordering() {
|
||||
// TODO: Implement
|
||||
// Verify that ORDER BY is correctly applied
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_result_pagination() {
|
||||
// TODO: Implement
|
||||
// Verify SKIP and LIMIT work correctly together
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error Handling Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_execute_invalid_property_access() {
|
||||
// TODO: Implement
|
||||
// let db = setup_test_graph();
|
||||
// let result = db.execute("MATCH (n:Person) WHERE n.nonexistent > 5 RETURN n");
|
||||
// Should handle gracefully (return no results or error depending on semantics)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_execute_type_mismatch() {
|
||||
// TODO: Implement
|
||||
// let db = setup_test_graph();
|
||||
// let result = db.execute("MATCH (n:Person) WHERE n.name > 5 RETURN n");
|
||||
// Should handle type mismatch error
|
||||
}
|
||||
Reference in New Issue
Block a user