use super::*;
fn star_graph() -> DirGraph {
let mut graph = DirGraph::new();
let query = parser::parse_cypher(
"CREATE (x:N {id:'x'}) CREATE (y:N {id:'y'}) CREATE (z:N {id:'z'}) \
CREATE (x)-[:R {w:1}]->(y) CREATE (x)-[:R {w:2}]->(z)",
)
.unwrap();
execute_mutable(
&mut graph,
&query,
HashMap::new(),
crate::graph::algorithms::Interrupt::default(),
)
.unwrap();
graph
}
fn run(graph: &DirGraph, source: &str, optimize: bool) -> CypherResult {
let params = HashMap::new();
let mut query = parser::parse_cypher(source)
.unwrap_or_else(|e| panic!("failed to parse: {source}\n error: {e}"));
if optimize {
crate::graph::languages::cypher::planner::optimize(&mut query, graph, ¶ms);
}
CypherExecutor::with_params(graph, ¶ms, None)
.execute(&query)
.unwrap_or_else(|e| panic!("failed to execute: {source}\n error: {e}"))
}
fn assert_result(graph: &DirGraph, source: &str, columns: &[&str], rows: Vec<Vec<Value>>) {
for optimize in [false, true] {
let result = run(graph, source, optimize);
assert_eq!(
result.columns, columns,
"columns for `{source}` (optimize={optimize})"
);
assert_eq!(
result.rows, rows,
"rows for `{source}` (optimize={optimize})"
);
}
}
fn assert_rows(graph: &DirGraph, source: &str, rows: Vec<Vec<Value>>) {
for optimize in [false, true] {
let result = run(graph, source, optimize);
assert_eq!(
result.rows, rows,
"rows for `{source}` (optimize={optimize})"
);
}
}
fn assert_columns(graph: &DirGraph, source: &str, columns: &[&str]) {
for optimize in [false, true] {
let result = run(graph, source, optimize);
assert_eq!(
result.columns, columns,
"columns for `{source}` (optimize={optimize})"
);
}
}
fn s(text: &str) -> Value {
Value::String(text.to_string())
}
fn i(n: i64) -> Value {
Value::Int64(n)
}
#[test]
fn a_mixed_star_carries_the_scalar_names_in_scope() {
let graph = star_graph();
assert_result(
&graph,
"UNWIND [1] AS a WITH *, a + 1 AS b RETURN a, b",
&["a", "b"],
vec![vec![i(1), i(2)]],
);
assert_result(
&graph,
"UNWIND [1] AS a WITH *, a + 1 AS b, a + 2 AS c RETURN a, b, c",
&["a", "b", "c"],
vec![vec![i(1), i(2), i(3)]],
);
assert_result(
&graph,
"UNWIND [1] AS a WITH a + 1 AS b, * RETURN a, b",
&["a", "b"],
vec![vec![i(1), i(2)]],
);
assert_result(
&graph,
"MATCH (n:N) WITH count(*) AS c WITH *, c + 1 AS d RETURN c, d",
&["c", "d"],
vec![vec![i(3), i(4)]],
);
assert_result(
&graph,
"MATCH (n:N {id:'x'}) WITH n, n.id AS s WITH *, 1 AS k RETURN n.id, s, k",
&["n.id", "s", "k"],
vec![vec![s("x"), s("x"), i(1)]],
);
}
#[test]
fn a_mixed_star_never_reaches_the_output_as_a_column() {
let graph = star_graph();
assert_result(
&graph,
"UNWIND [1] AS a RETURN *, a + 1 AS b",
&["a", "b"],
vec![vec![i(1), i(2)]],
);
assert_result(
&graph,
"UNWIND [1] AS a WITH *, a + 1 AS b RETURN *",
&["a", "b"],
vec![vec![i(1), i(2)]],
);
}
#[test]
fn an_explicit_item_wins_over_the_name_the_star_would_carry() {
let graph = star_graph();
assert_result(
&graph,
"UNWIND [1] AS a WITH *, a AS a RETURN a",
&["a"],
vec![vec![i(1)]],
);
assert_result(
&graph,
"UNWIND [1] AS a WITH *, a + 1 AS a RETURN a",
&["a"],
vec![vec![i(2)]],
);
assert_result(
&graph,
"UNWIND [1] AS a RETURN *, a + 1 AS a",
&["a"],
vec![vec![i(2)]],
);
assert_result(
&graph,
"MATCH (n:N {id:'x'}) RETURN *, n.id AS n",
&["n"],
vec![vec![s("x")]],
);
}
#[test]
fn a_star_carries_the_path_variable_too() {
let graph = star_graph();
assert_columns(
&graph,
"MATCH p = (a:N {id:'x'})-[:R]->(b) RETURN *",
&["a", "b", "p"],
);
assert_columns(
&graph,
"MATCH p = (a:N {id:'x'})-[:R]->(b) RETURN *, 1 AS k",
&["a", "b", "p", "k"],
);
assert_columns(
&graph,
"MATCH (:N {id:'x'})-[r:R]->() WITH r RETURN *",
&["r"],
);
}
#[test]
fn a_star_beside_an_aggregate_groups_by_the_scope() {
let graph = star_graph();
assert_rows(
&graph,
"MATCH (n:N) WITH *, count(*) AS c RETURN n.id, c ORDER BY n.id",
vec![vec![s("x"), i(1)], vec![s("y"), i(1)], vec![s("z"), i(1)]],
);
assert_columns(
&graph,
"MATCH (n:N {id:'x'}) RETURN *, count(*) AS c",
&["n", "c"],
);
}
#[test]
fn a_mixed_star_keeps_every_binding_in_scope() {
let graph = star_graph();
assert_rows(
&graph,
"MATCH (n:N {id:'x'}) WITH *, 1 AS k MATCH (n)-[:R]->(m) RETURN m.id ORDER BY m.id",
vec![vec![s("y")], vec![s("z")]],
);
assert_rows(
&graph,
"MATCH (:N {id:'x'})-[r:R]->(b) WITH *, 1 AS k RETURN r.w, b.id ORDER BY b.id",
vec![vec![i(1), s("y")], vec![i(2), s("z")]],
);
assert_rows(
&graph,
"MATCH p = (:N {id:'x'})-[:R]->(b) WITH *, 1 AS k RETURN length(p) AS l, b.id ORDER BY b.id",
vec![vec![i(1), s("y")], vec![i(1), s("z")]],
);
assert_rows(
&graph,
"MATCH (:N {id:'x'}) CALL { MATCH (a:N {id:'x'})-[:R]->(m) RETURN *, 1 AS k } \
RETURN m.id ORDER BY m.id",
vec![vec![s("y")], vec![s("z")]],
);
}
#[test]
fn a_subquery_star_that_re_exports_an_import_is_still_refused() {
let graph = star_graph();
for optimize in [false, true] {
let params = HashMap::new();
let mut query = parser::parse_cypher(
"MATCH (n:N {id:'x'}) CALL { WITH n MATCH (n)-[:R]->(m) RETURN *, 1 AS k } \
RETURN m.id",
)
.unwrap();
if optimize {
crate::graph::languages::cypher::planner::optimize(&mut query, &graph, ¶ms);
}
let error = CypherExecutor::with_params(&graph, ¶ms, None)
.execute(&query)
.expect_err("re-exporting an imported variable must be refused");
assert!(
error.contains("already exists in the outer scope"),
"optimize={optimize}: {error}"
);
}
}
#[test]
fn the_spellings_that_were_already_right_stay_right() {
let graph = star_graph();
assert_result(
&graph,
"UNWIND [1] AS a WITH * RETURN a",
&["a"],
vec![vec![i(1)]],
);
assert_result(
&graph,
"UNWIND [1] AS a WITH * RETURN *",
&["a"],
vec![vec![i(1)]],
);
assert_rows(
&graph,
"MATCH (n:N) WITH *, n.id AS i ORDER BY i LIMIT 2 RETURN n.id, i",
vec![vec![s("x"), s("x")], vec![s("y"), s("y")]],
);
assert_rows(
&graph,
"MATCH (n:N) WITH *, n.id AS i WHERE i = 'y' RETURN n.id, i",
vec![vec![s("y"), s("y")]],
);
}
#[test]
fn a_mixed_star_distinct_deduplicates_on_the_scope() {
let graph = star_graph();
assert_rows(
&graph,
"MATCH (n:N) WITH DISTINCT *, 1 AS k RETURN count(*) AS c",
vec![vec![i(3)]],
);
assert_rows(
&graph,
"MATCH (:N {id:'x'})-[:R]->() WITH DISTINCT 1 AS k RETURN count(*) AS c",
vec![vec![i(1)]],
);
}