use super::*;
use linkrs_core::Value;
#[test]
fn test_parse_simple_expression() {
let input = "1 + 2 * 3";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok());
let parse_result = result.expect("Simple expression parsing should succeed");
assert!(matches!(parse_result.expr, Expression::Binary { .. }));
}
#[test]
fn test_parse_struct_literal_and_field_access() {
let input = "STRUCT{name: 'x', addr: STRUCT{city: 'sh', geo: STRUCT{lat: 1.0, lon: 2.0}}}";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("STRUCT literal must parse");
match result.expr {
Expression::Literal(Value::Struct(s)) => {
assert_eq!(s.fields.len(), 2);
assert_eq!(s.fields[0].0, "name");
assert!(
matches!(s.fields[1].1, Value::Struct(_)),
"nested STRUCT must fold"
);
}
other => panic!("expected STRUCT literal, got {:?}", other),
}
let input = "p.addr.city";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("field access must parse");
match result.expr {
Expression::StructField { base, field } => {
assert_eq!(field, "city");
assert!(matches!(*base, Expression::Property { .. }));
}
other => panic!("expected StructField, got {:?}", other),
}
let input = "p.addr.geo.lat";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("chained field access must parse");
match result.expr {
Expression::StructField { base, field } => {
assert_eq!(field, "lat");
assert!(matches!(*base, Expression::StructField { .. }));
}
other => panic!("expected nested StructField, got {:?}", other),
}
let input = "p.name";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("property access must parse");
assert!(matches!(result.expr, Expression::Property { .. }));
}
#[test]
fn test_parse_array_literal_and_subscript() {
let input = "ARRAY[1, 2, 3]";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("ARRAY literal must parse");
match result.expr {
Expression::Literal(Value::Array(a)) => {
assert_eq!(
a.values,
vec![Value::BigInt(1), Value::BigInt(2), Value::BigInt(3)]
);
}
other => panic!("expected ARRAY literal, got {:?}", other),
}
let input = "arr[0]";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx).expect("subscript must parse");
assert!(matches!(result.expr, Expression::Subscript { .. }));
}
#[test]
fn test_parse_json_path_get() {
for input in ["m->'key'", "m->>\"key\"", "m#>'a.b'", "m#>>\"a.b\""] {
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(
result.is_ok(),
"parse failed for {input}: {:?}",
result.err()
);
let parse_result = result.expect("JSON access parsing should succeed");
assert!(matches!(parse_result.expr, Expression::Binary { .. }));
}
}
#[test]
fn test_parse_in_subquery() {
let input = "t.name IN { MATCH (p:person) RETURN p.name }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("IN subquery parsing should succeed");
match parse_result.expr {
Expression::In {
expr,
subquery,
negated,
} => {
assert!(!negated);
assert!(matches!(*expr, Expression::Property { .. }));
assert_eq!(subquery.patterns.len(), 1);
assert!(subquery.return_expr.is_some());
}
other => panic!("expected Expression::In, got {:?}", other),
}
}
#[test]
fn test_parse_not_in_subquery() {
let input = "t.name NOT IN { MATCH (p:person) RETURN p.name }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("NOT IN subquery parsing should succeed");
match parse_result.expr {
Expression::In {
expr,
subquery,
negated,
} => {
assert!(negated);
assert!(matches!(*expr, Expression::Property { .. }));
assert_eq!(subquery.patterns.len(), 1);
assert!(subquery.return_expr.is_some());
}
other => panic!("expected negated Expression::In, got {:?}", other),
}
}
#[test]
fn test_parse_parenthesized_expression() {
let input = "(1 + 2) * 3";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok());
let parse_result = result.expect("Parsing a bracketed expression should succeed");
assert!(matches!(parse_result.expr, Expression::Binary { .. }));
}
#[test]
fn test_parse_exists_with_where_clause() {
let input = "EXISTS { MATCH (q:person) WHERE q.age > 30 }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("EXISTS parsing should succeed");
match parse_result.expr {
Expression::Exists { body } => {
assert_eq!(body.patterns.len(), 1, "one pattern expected");
assert_eq!(body.patterns[0], "( q : person )");
assert!(
body.where_clause.is_some(),
"WHERE clause must be preserved"
);
}
other => panic!("expected Expression::Exists, got {:?}", other),
}
}
#[test]
fn test_parse_exists_with_return_expr() {
let input = "EXISTS { MATCH (q:person) WHERE q.age > 30 RETURN q.name }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("EXISTS parsing should succeed");
match parse_result.expr {
Expression::Exists { body } => {
assert_eq!(body.patterns.len(), 1);
assert!(body.where_clause.is_some());
assert!(body.return_expr.is_some(), "RETURN expr must be preserved");
}
other => panic!("expected Expression::Exists, got {:?}", other),
}
}
#[test]
fn test_parse_exists_bare_pattern() {
let input = "EXISTS { a:person-[:knows]->b:person }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("EXISTS parsing should succeed");
match parse_result.expr {
Expression::Exists { body } => {
assert_eq!(body.patterns.len(), 1);
assert_eq!(
body.patterns[0],
"( a : person ) - [ : knows ] -> ( b : person )"
);
assert!(body.where_clause.is_none());
}
other => panic!("expected Expression::Exists, got {:?}", other),
}
}
#[test]
fn test_subquery_pattern_round_trip_reparse() {
for input in [
"EXISTS { MATCH (q:person) }",
"EXISTS { a:person-[:knows]->b:person }",
"EXISTS { MATCH (a:person)-[:knows]->(b:person) WHERE b.age > 18 }",
] {
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(
result.is_ok(),
"parse failed for {input}: {:?}",
result.err()
);
let parse_result = result.expect("EXISTS parsing should succeed");
let body = match parse_result.expr {
Expression::Exists { body } => body,
other => panic!("expected Expression::Exists, got {:?}", other),
};
for pattern_str in &body.patterns {
let ctx = &mut ParseContext::new(pattern_str);
let mut parser = crate::parser::parsing::traversal_parser::TraversalParser::new();
let pattern = parser.parse_pattern(ctx);
assert!(
pattern.is_ok(),
"stored pattern `{pattern_str}` must be re-parseable: {:?}",
pattern.err()
);
}
}
}
#[test]
fn test_parse_scalar_subquery() {
let input = "SUBQUERY { MATCH (q:person) WHERE q.age > 30 RETURN q.name }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(result.is_ok(), "parse failed: {:?}", result.err());
let parse_result = result.expect("SUBQUERY parsing should succeed");
match parse_result.expr {
Expression::ScalarSubquery { body } => {
assert_eq!(body.patterns.len(), 1);
assert!(body.where_clause.is_some());
assert!(body.return_expr.is_some());
assert_eq!(body.id, 0, "planner assigns the id later");
}
other => panic!("expected Expression::ScalarSubquery, got {:?}", other),
}
}
#[test]
fn test_parse_scalar_subquery_requires_return() {
let input = "SUBQUERY { MATCH (q:person) WHERE q.age > 30 }";
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(
result.is_err(),
"SUBQUERY without RETURN must be rejected at parse time"
);
}
#[test]
fn test_parse_inline_pattern_predicate_rewrites_to_exists() {
let input = "(a)-[:knows]->(b)";
let ctx = &mut ParseContext::new(input);
let parse_result = parse_expression(ctx).expect("inline pattern must parse");
match parse_result.expr {
Expression::Exists { body } => {
assert_eq!(body.patterns.len(), 1);
assert_eq!(body.patterns[0], "(a)-[:knows]->(b)");
let reparsed = crate::parser::parsing::traversal_parser::TraversalParser::new()
.parse_pattern(&mut ParseContext::new(&body.patterns[0]));
assert!(
reparsed.is_ok(),
"rendered pattern must be re-parseable: {:?}",
reparsed.err()
);
}
other => panic!("expected EXISTS rewrite, got {:?}", other),
}
}
#[test]
fn test_parse_inline_pattern_predicate_combines_with_boolean_ops() {
for input in [
"(a)-[:knows]->(b) AND a.age > 20",
"NOT (a)-[:knows]->(b)",
"((a)-[:knows]->(b))",
] {
let ctx = &mut ParseContext::new(input);
let result = parse_expression(ctx);
assert!(
result.is_ok(),
"parse failed for {input}: {:?}",
result.err()
);
}
}
#[test]
fn test_parse_parenthesized_expressions_keep_meaning() {
let ctx = &mut ParseContext::new("(a)");
let parse_result = parse_expression(ctx).expect("parens must parse");
assert!(
matches!(parse_result.expr, Expression::Variable(_)),
"expected variable, got {:?}",
parse_result.expr
);
let ctx = &mut ParseContext::new("(a)-(b)");
let parse_result = parse_expression(ctx).expect("subtraction must parse");
assert!(
matches!(parse_result.expr, Expression::Binary { .. }),
"expected binary subtraction, got {:?}",
parse_result.expr
);
}
#[test]
fn test_parse_window_function_with_partition_and_order() {
let input = "row_number() OVER (PARTITION BY a ORDER BY b DESC)";
let ctx = &mut ParseContext::new(input);
let parse_result = parse_expression(ctx).expect("window function must parse");
match parse_result.expr {
Expression::WindowFunction {
over_partition_by,
over_order_by,
over_order_desc,
..
} => {
assert_eq!(over_partition_by.len(), 1);
assert_eq!(over_order_by.len(), 1);
assert_eq!(over_order_desc, vec![true]);
}
other => panic!("expected WindowFunction, got {:?}", other),
}
let input = "row_number() OVER (PARTITION BY a, b)";
let ctx = &mut ParseContext::new(input);
let parse_result = parse_expression(ctx).expect("window function must parse");
match parse_result.expr {
Expression::WindowFunction {
over_partition_by,
over_order_by,
..
} => {
assert_eq!(over_partition_by.len(), 2);
assert!(over_order_by.is_empty());
}
other => panic!("expected WindowFunction, got {:?}", other),
}
}