use super::*;
use plexus_gql::parse_gql_with_metadata;
use plexus_ir::{lower_to_flatbuffer_with_match_graph_refs, lower_to_plan_with_match_graph_refs};
fn gql_to_bytes(gql: &str) -> Vec<u8> {
let (query, metadata) = parse_gql_with_metadata(gql).expect("gql parse");
lower_to_flatbuffer_with_match_graph_refs(&query, &metadata.match_graph_refs)
.expect("bridge lower_to_flatbuffer_with_match_graph_refs")
}
#[test]
fn gql_filter_to_execute_smoke() {
let gql =
"MATCH (n:Person)-[r:KNOWS]->(m:Person) FILTER m.age > 30 RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::String("Alice".to_string())]]);
}
#[test]
fn gql_use_graph_clause_to_execute_smoke() {
let gql = "USE GRAPH social MATCH (n:Person) RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_use_graph_clause_with_alias_to_execute_smoke() {
let gql = "USE GRAPH social AS g MATCH (n:Person) RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_graph_clause_to_execute_smoke() {
let gql = "GRAPH social MATCH (n:Person) RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_graph_clause_with_alias_to_execute_smoke() {
let gql = "GRAPH social AS g MATCH (n:Person) RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_let_to_execute_smoke() {
let gql = "MATCH (n:Person) LET score = n.age * 2 RETURN score ORDER BY score ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::Int(60)], vec![Value::Int(80)]]);
}
#[test]
fn gql_multi_let_to_execute_smoke() {
let gql = "MATCH (n:Person) LET score = n.age * 2, next_age = n.age + 1 RETURN score, next_age ORDER BY score ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::Int(60), Value::Int(31)],
vec![Value::Int(80), Value::Int(41)]
]
);
}
#[test]
fn gql_offset_to_execute_smoke() {
let gql = "MATCH (n:Person) RETURN n.name AS name ORDER BY name ASC OFFSET 1 LIMIT 1";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::String("Bob".to_string())]]);
}
#[test]
fn gql_next_clause_chain_to_execute_smoke() {
let gql = "MATCH (n:Person) RETURN n AS n NEXT RETURN n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_next_match_clause_chain_to_execute_smoke() {
let gql = "MATCH (n:Person) RETURN n AS n NEXT MATCH (n)-[:KNOWS]->(m:Person) RETURN n.name AS src, m.name AS dst ORDER BY src ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![
Value::String("Alice".to_string()),
Value::String("Bob".to_string())
],
vec![
Value::String("Bob".to_string()),
Value::String("Alice".to_string())
]
]
);
}
#[test]
fn gql_for_clause_to_execute_smoke() {
let gql =
"MATCH (n:Person) WITH COLLECT(n.name) AS names FOR x IN names RETURN x ORDER BY x ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_multi_for_clause_to_execute_smoke() {
let gql = "MATCH (n:Person) WITH COLLECT(n.name) AS names, COLLECT(n.age) AS ages FOR x IN names, y IN ages RETURN x, y ORDER BY x ASC, y ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string()), Value::Int(30)],
vec![Value::String("Alice".to_string()), Value::Int(40)],
vec![Value::String("Bob".to_string()), Value::Int(30)],
vec![Value::String("Bob".to_string()), Value::Int(40)]
]
);
}
#[test]
fn gql_set_clause_to_execute_smoke() {
let gql = "MATCH (n:Person) FILTER n.name = 'Alice' SET n.age = 31 RETURN n.age AS age";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::Int(31)]]);
let alice = engine
.graph
.nodes
.iter()
.find(|n| n.props.get("name") == Some(&Value::String("Alice".to_string())))
.expect("alice exists");
assert_eq!(alice.props.get("age"), Some(&Value::Int(31)));
}
#[test]
fn gql_delete_clause_to_execute_smoke() {
let gql = "MATCH (n:Person)-[r:KNOWS]->(m:Person) FILTER n.name = 'Alice' DELETE r RETURN n.name AS name";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let initial_rels = engine.graph.rels.len();
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::String("Alice".to_string())]]);
assert_eq!(engine.graph.rels.len(), initial_rels - 1);
}
#[test]
fn gql_insert_clause_to_execute_smoke() {
let gql = "MATCH (a:Person) FILTER a.name = 'Alice' INSERT (n:Person) SET n.name = 'Eve', n.age = 22 RETURN n.name AS name";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let initial_nodes = engine.graph.nodes.len();
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::String("Eve".to_string())]]);
assert_eq!(engine.graph.nodes.len(), initial_nodes + 1);
assert!(engine.graph.nodes.iter().any(|n| {
n.labels.contains("Person")
&& n.props.get("name") == Some(&Value::String("Eve".to_string()))
&& n.props.get("age") == Some(&Value::Int(22))
}));
}
#[test]
fn gql_next_after_with_to_execute_smoke() {
let gql = "MATCH (n:Person) WITH n NEXT MATCH (n)-[:KNOWS]->(m:Person) RETURN n.name AS src, m.name AS dst ORDER BY src ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![
Value::String("Alice".to_string()),
Value::String("Bob".to_string())
],
vec![
Value::String("Bob".to_string()),
Value::String("Alice".to_string())
]
]
);
}
#[test]
fn gql_graph_alias_in_expression_to_execute_smoke() {
let gql = "GRAPH social AS g MATCH (g.n:Person) RETURN g.n.name AS name ORDER BY name ASC";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(
out.rows,
vec![
vec![Value::String("Alice".to_string())],
vec![Value::String("Bob".to_string())]
]
);
}
#[test]
fn gql_detach_delete_clause_to_execute_smoke() {
let gql = "MATCH (n:Person) FILTER n.name = 'Alice' DETACH DELETE n RETURN 1 AS deleted";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let initial_node_count = engine.graph.nodes.len();
let initial_rel_count = engine.graph.rels.len();
let out = execute_serialized(&mut engine, &bytes).expect("execute");
assert_eq!(out.rows, vec![vec![Value::Int(1)]]);
assert_eq!(engine.graph.nodes.len(), initial_node_count - 1);
assert!(engine.graph.rels.len() < initial_rel_count);
assert!(!engine
.graph
.nodes
.iter()
.any(|n| n.props.get("name") == Some(&Value::String("Alice".to_string()))));
}
#[test]
fn gql_multi_graph_switching_rejected_by_engine_capability() {
let (query, metadata) = parse_gql_with_metadata(
"USE GRAPH social MATCH (n:Person) RETURN n AS n NEXT USE GRAPH other MATCH (m:Person) RETURN n.name, m.name",
)
.expect("gql parse");
assert_eq!(
metadata.match_graph_refs,
vec![Some("social".to_string()), Some("other".to_string())]
);
let bytes = lower_to_flatbuffer_with_match_graph_refs(&query, &metadata.match_graph_refs)
.expect("lower");
let mut engine = InMemoryEngine::new(fixture_graph());
let err =
execute_serialized(&mut engine, &bytes).expect_err("must fail: no supports_multi_graph");
let msg = err.to_string();
assert!(msg.contains("supports_multi_graph=false"), "{msg}");
}
#[test]
fn gql_standalone_insert_executes_via_const_row() {
let gql = "INSERT (n:Person) SET n.name = 'Charlie' RETURN n.name AS name";
let bytes = gql_to_bytes(gql);
let mut engine = InMemoryEngine::new(fixture_graph());
let initial_nodes = engine.graph.nodes.len();
let out = execute_serialized(&mut engine, &bytes).expect("standalone INSERT must execute");
assert_eq!(out.rows, vec![vec![Value::String("Charlie".to_string())]]);
assert_eq!(engine.graph.nodes.len(), initial_nodes + 1);
assert!(engine
.graph
.nodes
.iter()
.any(|n| n.labels.contains("Person")
&& n.props.get("name") == Some(&Value::String("Charlie".to_string()))));
}
#[test]
fn gql_graph_param_variable_rejected_by_engine_capability() {
let (query, metadata) =
parse_gql_with_metadata("USE GRAPH $g MATCH (n:Person) RETURN n.name").expect("gql parse");
assert_eq!(metadata.selected_graph, Some("$g".to_string()));
let plan =
lower_to_plan_with_match_graph_refs(&query, &metadata.match_graph_refs).expect("lower");
let plan_semver: PlanSemver = (&plan.version).into();
let mut caps = EngineCapabilities::full(VersionRange::new(plan_semver, plan_semver));
caps.supports_graph_ref = true;
caps.supports_graph_params = false;
let err = validate_plan_against_capabilities(&plan, &caps)
.expect_err("must fail: supports_graph_params=false");
let msg = err.to_string();
assert!(msg.contains("supports_graph_params"), "{msg}");
}