use super::*;
fn query_error(graph: &DirGraph, query: &str) -> String {
let parsed = match parser::parse_cypher(query) {
Ok(parsed) => parsed,
Err(e) => return e.to_string(),
};
let no_params = HashMap::new();
match CypherExecutor::with_params(graph, &no_params, None).execute(&parsed) {
Ok(result) => panic!(
"query unexpectedly succeeded: {query}\n rows: {:?}",
result.rows
),
Err(e) => e,
}
}
fn one_cell(graph: &DirGraph, query: &str) -> Value {
let parsed = parser::parse_cypher(query)
.unwrap_or_else(|e| panic!("query failed to parse: {query}\n error: {e}"));
let no_params = HashMap::new();
let result = CypherExecutor::with_params(graph, &no_params, None)
.execute(&parsed)
.unwrap_or_else(|e| panic!("query failed: {query}\n error: {e}"));
assert_eq!(result.rows.len(), 1, "expected one row from: {query}");
assert_eq!(result.rows[0].len(), 1, "expected one column from: {query}");
result.rows[0][0].clone()
}
#[test]
fn duplicate_return_aliases_are_rejected() {
let graph = DirGraph::new();
for query in [
"RETURN 1 AS x, 2 AS x",
"RETURN 1 AS x, 2 AS y, 3 AS x",
"MATCH (n:Person) RETURN n.a AS x, n.b AS x",
"MATCH (n:Person) RETURN DISTINCT n.a AS x, n.b AS x",
"MATCH (n:Person) RETURN count(n) AS c, count(n) AS c",
"MATCH (n:Person) WITH n.a AS x, n.b AS x RETURN x",
"MATCH (n:Person) RETURN n.a, n.a",
"RETURN 1 AS `x`, 2 AS x",
] {
let error = query_error(&graph, query);
assert!(
error.contains("Multiple result columns with the same name are not supported"),
"expected a duplicate-column rejection for `{query}`, got: {error}"
);
}
}
#[test]
fn distinct_column_names_still_project() {
let graph = DirGraph::new();
let parsed = parser::parse_cypher("RETURN 1 AS x, 2 AS X, 3 AS y").unwrap();
let no_params = HashMap::new();
let result = CypherExecutor::with_params(&graph, &no_params, None)
.execute(&parsed)
.unwrap();
assert_eq!(result.columns, vec!["x", "X", "y"]);
assert_eq!(
result.rows[0],
vec![Value::Int64(1), Value::Int64(2), Value::Int64(3)]
);
}
fn timestamp(y: i32, m: u32, d: u32, hh: u32, mm: u32, ss: u32) -> Value {
Value::Timestamp(
chrono::NaiveDate::from_ymd_opt(y, m, d)
.unwrap()
.and_hms_opt(hh, mm, ss)
.unwrap(),
)
}
#[test]
fn datetime_preserves_time_and_normalises_the_zone_to_utc() {
let graph = DirGraph::new();
let cases: &[(&str, Value)] = &[
(
"RETURN datetime('2024-01-15T10:30:00') AS d",
timestamp(2024, 1, 15, 10, 30, 0),
),
(
"RETURN datetime('2024-01-15T10:30:00Z') AS d",
timestamp(2024, 1, 15, 10, 30, 0),
),
(
"RETURN datetime('2024-01-15T10:30:00+02:00') AS d",
timestamp(2024, 1, 15, 8, 30, 0),
),
(
"RETURN datetime('2024-01-15T01:30:00-05:00') AS d",
timestamp(2024, 1, 15, 6, 30, 0),
),
(
"RETURN datetime('2024-01-15T10:30:00.500Z') AS d",
timestamp(2024, 1, 15, 10, 30, 0),
),
(
"RETURN datetime('2024-01-15T10:30') AS d",
timestamp(2024, 1, 15, 10, 30, 0),
),
(
"RETURN datetime('2024-01-15') AS d",
timestamp(2024, 1, 15, 0, 0, 0),
),
("RETURN datetime('not-a-date') AS d", Value::Null),
("RETURN datetime('2024-01-15T25:99:99') AS d", Value::Null),
(
"RETURN datetime('10000-01-01T00:00:00') AS d",
timestamp(10000, 1, 1, 0, 0, 0),
),
];
for (query, expected) in cases {
assert_eq!(one_cell(&graph, query), *expected, "for: {query}");
}
}
#[test]
fn localdatetime_keeps_the_wall_clock_reading() {
let graph = DirGraph::new();
assert_eq!(
one_cell(
&graph,
"RETURN localdatetime('2024-01-15T10:30:00+02:00') AS d"
),
timestamp(2024, 1, 15, 10, 30, 0)
);
assert_eq!(
one_cell(&graph, "RETURN localdatetime('2024-01-15T10:30:00Z') AS d"),
timestamp(2024, 1, 15, 10, 30, 0)
);
assert_eq!(
one_cell(&graph, "RETURN localdatetime('2024-01-15') AS d"),
timestamp(2024, 1, 15, 0, 0, 0)
);
}
#[test]
fn integer_overflow_and_division_by_zero_are_query_errors() {
let graph = DirGraph::new();
let cases: &[(&str, &str)] = &[
(
"RETURN 9223372036854775807 + 1 AS n",
"Integer overflow in addition",
),
(
"RETURN (-9223372036854775807 - 1) - 1 AS n",
"Integer overflow in subtraction",
),
(
"RETURN 9223372036854775807 * 2 AS n",
"Integer overflow in multiplication",
),
(
"RETURN (-9223372036854775807 - 1) / -1 AS n",
"Integer overflow in division",
),
(
"RETURN (-9223372036854775807 - 1) % -1 AS n",
"Integer overflow in modulo",
),
("RETURN 1 / 0 AS n", "Integer division by zero"),
("RETURN 1 % 0 AS n", "Integer modulo by zero"),
];
for (query, expected) in cases {
let error = query_error(&graph, query);
assert!(
error.contains(expected),
"expected `{expected}` for `{query}`, got: {error}"
);
}
}
#[test]
fn ordinary_integer_arithmetic_is_unchanged() {
let graph = DirGraph::new();
for (query, expected) in [
(
"RETURN 9223372036854775806 + 1 AS n",
Value::Int64(i64::MAX),
),
("RETURN -7 / 2 AS n", Value::Int64(-3)),
("RETURN 1967 / 10 * 10 AS n", Value::Int64(1960)),
("RETURN 7 % 3 AS n", Value::Int64(1)),
("RETURN 1.0 / 0 AS n", Value::Null),
] {
assert_eq!(one_cell(&graph, query), expected, "for: {query}");
}
}