1use crate::ast::*;
10use crate::error::{GraphError, Result};
11use nom::{
12 branch::alt,
13 bytes::complete::{tag, tag_no_case, take_while, take_while1},
14 character::complete::{char, digit0, digit1, multispace0, multispace1, one_of, satisfy},
15 combinator::{map, map_res, not, opt, peek, recognize},
16 multi::{many0, many1, separated_list0, separated_list1},
17 sequence::{delimited, pair, preceded, terminated, tuple},
18 IResult,
19};
20use std::collections::HashMap;
21
22pub fn parse_cypher_query(input: &str) -> Result<CypherQuery> {
24 let (remaining, query) = cypher_query(input).map_err(|e| GraphError::ParseError {
25 message: format!("Failed to parse Cypher query: {}", e),
26 position: 0,
27 location: snafu::Location::new(file!(), line!(), column!()),
28 })?;
29
30 if !remaining.trim().is_empty() {
31 return Err(GraphError::ParseError {
32 message: format!("Unexpected input after query: {}", remaining),
33 position: input.len() - remaining.len(),
34 location: snafu::Location::new(file!(), line!(), column!()),
35 });
36 }
37
38 let mut query = query;
39 normalize_inline_properties(&mut query);
40 Ok(query)
41}
42
43fn normalize_inline_properties(query: &mut CypherQuery) {
44 let stages = query
45 .further_with_stages
46 .iter_mut()
47 .flat_map(|s| &mut s.reading_clauses);
48 let mut anonymous = 0;
49 for clause in query
50 .reading_clauses
51 .iter_mut()
52 .chain(&mut query.post_with_reading_clauses)
53 .chain(stages)
54 {
55 match clause {
56 ReadingClause::Match(m) => move_variable_properties_to_where(m, &mut anonymous),
57 ReadingClause::Call(inner) => normalize_inline_properties(inner),
58 ReadingClause::Unwind(_) => {}
59 }
60 }
61 for branch in &mut query.union_branches {
62 normalize_inline_properties(&mut branch.query);
63 }
64}
65
66fn move_variable_properties_to_where(clause: &mut MatchClause, anonymous: &mut usize) {
70 let mut conditions = Vec::new();
71 for pattern in &mut clause.patterns {
72 let nodes: Vec<&mut NodePattern> = match pattern {
73 GraphPattern::Node(n) => vec![n],
74 GraphPattern::Path(p) => std::iter::once(&mut p.start_node)
75 .chain(p.segments.iter_mut().map(|s| &mut s.end_node))
76 .collect(),
77 };
78 for node in nodes {
79 let keys: Vec<String> = node
80 .properties
81 .iter()
82 .filter(|(_, v)| {
83 matches!(v, PropertyValue::Variable(_) | PropertyValue::Property(_))
84 })
85 .map(|(k, _)| k.clone())
86 .collect();
87 for key in keys {
88 let value = match node.properties.remove(&key) {
89 Some(PropertyValue::Variable(v)) => ValueExpression::Variable(v),
90 Some(PropertyValue::Property(p)) => ValueExpression::Property(p),
91 _ => unreachable!("filtered above"),
92 };
93 let variable = node.variable.get_or_insert_with(|| {
94 *anonymous += 1;
95 format!("__inline_{anonymous}")
96 });
97 conditions.push(BooleanExpression::Comparison {
98 left: ValueExpression::Property(PropertyRef {
99 variable: variable.clone(),
100 property: key,
101 }),
102 operator: ComparisonOperator::Equal,
103 right: value,
104 });
105 }
106 }
107 }
108 let existing = clause.where_clause.take().map(|w| w.expression);
109 clause.where_clause = conditions
110 .into_iter()
111 .chain(existing)
112 .reduce(|a, b| BooleanExpression::And(Box::new(a), Box::new(b)))
113 .map(|expression| WhereClause { expression });
114}
115
116fn cypher_query(input: &str) -> IResult<&str, CypherQuery> {
118 let (input, mut query) = single_query(input)?;
119 let (input, branches) = many0(pair(union_header, single_query))(input)?;
120 query.union_branches = branches
121 .into_iter()
122 .map(|(all, query)| UnionBranch { all, query })
123 .collect();
124 Ok((input, query))
125}
126
127fn union_header(input: &str) -> IResult<&str, bool> {
128 let (input, _) = multispace0(input)?;
129 let (input, _) = tag_no_case("UNION")(input)?;
130 let (input, _) = multispace1(input)?;
131 let (input, all) = opt(terminated(tag_no_case("ALL"), multispace1))(input)?;
132 Ok((input, all.is_some()))
133}
134
135fn single_query(input: &str) -> IResult<&str, CypherQuery> {
136 let (input, _) = multispace0(input)?;
137 let (input, reading_clauses) = many0(reading_clause)(input)?;
138 let (input, pre_with_where) = opt(where_clause)(input)?;
139
140 let (input, with_result) = opt(with_clause)(input)?;
142 let (input, post_with_reading_clauses, post_with_where) = match with_result {
144 Some(_) => {
145 let (input, readings) = many0(reading_clause)(input)?;
146 let (input, where_cl) = opt(where_clause)(input)?;
147 (input, readings, where_cl)
148 }
149 None => (input, vec![], None),
150 };
151 let (input, further_with_stages) = match with_result {
152 Some(_) => many0(with_stage)(input)?,
153 None => (input, vec![]),
154 };
155
156 let (input, return_clause) = return_clause(input)?;
157 let (input, order_by) = opt(order_by_clause)(input)?;
158 let (input, (skip, limit)) = pagination_clauses(input)?;
159 let (input, _) = multispace0(input)?;
160
161 Ok((
162 input,
163 CypherQuery {
164 reading_clauses,
165 where_clause: pre_with_where,
166 with_clause: with_result,
167 post_with_reading_clauses,
168 post_with_where_clause: post_with_where,
169 further_with_stages,
170 union_branches: Vec::new(),
171 return_clause,
172 limit,
173 order_by,
174 skip,
175 },
176 ))
177}
178
179fn reading_clause(input: &str) -> IResult<&str, ReadingClause> {
181 alt((
182 map(match_clause, ReadingClause::Match),
183 map(unwind_clause, ReadingClause::Unwind),
184 map(call_clause, |query| ReadingClause::Call(Box::new(query))),
185 ))(input)
186}
187
188fn call_clause(input: &str) -> IResult<&str, CypherQuery> {
189 let (input, _) = multispace0(input)?;
190 let (input, _) = tag_no_case("CALL")(input)?;
191 let (input, _) = multispace0(input)?;
192 delimited(char('{'), cypher_query, preceded(multispace0, char('}')))(input)
193}
194
195fn match_clause(input: &str) -> IResult<&str, MatchClause> {
197 let (input, _) = multispace0(input)?;
198 let (input, optional) = opt(terminated(tag_no_case("OPTIONAL"), multispace1))(input)?;
199 let (input, _) = tag_no_case("MATCH")(input)?;
200 let (input, _) = multispace1(input)?;
201 let (input, patterns) = separated_list0(comma_ws, graph_pattern)(input)?;
202 let (input, where_clause) = if optional.is_some() {
204 opt(where_clause)(input)?
205 } else {
206 opt(terminated(where_clause, peek(reading_clause)))(input)?
207 };
208
209 Ok((
210 input,
211 MatchClause {
212 patterns,
213 optional: optional.is_some(),
214 where_clause,
215 },
216 ))
217}
218
219fn unwind_clause(input: &str) -> IResult<&str, UnwindClause> {
221 let (input, _) = multispace0(input)?;
222 let (input, _) = tag_no_case("UNWIND")(input)?;
223 let (input, _) = multispace1(input)?;
224 let (input, expression) = value_expression(input)?;
225 let (input, _) = multispace1(input)?;
226 let (input, _) = tag_no_case("AS")(input)?;
227 let (input, _) = multispace1(input)?;
228 let (input, alias) = identifier(input)?;
229
230 Ok((
231 input,
232 UnwindClause {
233 expression,
234 alias: alias.to_string(),
235 },
236 ))
237}
238
239fn graph_pattern(input: &str) -> IResult<&str, GraphPattern> {
241 alt((
242 map(shortest_path_pattern, GraphPattern::Path),
243 map(named_path_pattern, GraphPattern::Path),
244 map(path_pattern, GraphPattern::Path),
245 map(node_pattern, GraphPattern::Node),
246 ))(input)
247}
248
249fn shortest_path_pattern(input: &str) -> IResult<&str, PathPattern> {
251 let (input, _) = multispace0(input)?;
252 let (input, variable) = opt(terminated(
253 identifier,
254 tuple((multispace0, char('='), multispace0)),
255 ))(input)?;
256 let (input, all) = alt((
257 map(tag_no_case("allShortestPaths"), |_| true),
258 map(tag_no_case("shortestPath"), |_| false),
259 ))(input)?;
260 let (input, mut path) = delimited(
261 tuple((multispace0, char('('), multispace0)),
262 path_pattern,
263 tuple((multispace0, char(')'))),
264 )(input)?;
265 path.shortest = Some(ShortestPathSpec {
266 all,
267 variable: variable.map(str::to_string),
268 });
269 Ok((input, path))
270}
271
272fn named_path_pattern(input: &str) -> IResult<&str, PathPattern> {
274 let (input, variable) = delimited(
275 multispace0,
276 identifier,
277 tuple((multispace0, char('='), multispace0)),
278 )(input)?;
279 let (input, mut path) = path_pattern(input)?;
280 path.variable = Some(variable.to_string());
281 Ok((input, path))
282}
283
284fn path_pattern(input: &str) -> IResult<&str, PathPattern> {
286 let (input, start_node) = node_pattern(input)?;
287 let (input, segments) = many0(path_segment)(input)?;
288
289 if segments.is_empty() {
291 return Err(nom::Err::Error(nom::error::Error::new(
292 input,
293 nom::error::ErrorKind::Tag,
294 )));
295 }
296
297 Ok((
298 input,
299 PathPattern {
300 start_node,
301 segments,
302 shortest: None,
303 variable: None,
304 },
305 ))
306}
307
308fn path_segment(input: &str) -> IResult<&str, PathSegment> {
310 let (input, relationship) = relationship_pattern(input)?;
311 let (input, end_node) = node_pattern(input)?;
312
313 Ok((
314 input,
315 PathSegment {
316 relationship,
317 end_node,
318 },
319 ))
320}
321
322fn node_pattern(input: &str) -> IResult<&str, NodePattern> {
324 let (input, _) = multispace0(input)?;
325 let (input, _) = char('(')(input)?;
326 let (input, _) = multispace0(input)?;
327 let (input, variable) = opt(identifier)(input)?;
328 let (input, labels) = many0(preceded(char(':'), identifier))(input)?;
329 let (input, _) = multispace0(input)?;
330 let (input, properties) = opt(property_map)(input)?;
331 let (input, _) = multispace0(input)?;
332 let (input, _) = char(')')(input)?;
333
334 Ok((
335 input,
336 NodePattern {
337 variable: variable.map(|s| s.to_string()),
338 labels: labels.into_iter().map(|s| s.to_string()).collect(),
339 properties: properties.unwrap_or_default(),
340 },
341 ))
342}
343
344fn relationship_pattern(input: &str) -> IResult<&str, RelationshipPattern> {
346 let (input, _) = multispace0(input)?;
347
348 let (input, (direction, content)) = alt((
350 map(
352 tuple((
353 char('-'),
354 delimited(char('['), relationship_content, char(']')),
355 tag("->"),
356 )),
357 |(_, content, _)| (RelationshipDirection::Outgoing, content),
358 ),
359 map(
361 tuple((
362 tag("<-"),
363 delimited(char('['), relationship_content, char(']')),
364 char('-'),
365 )),
366 |(_, content, _)| (RelationshipDirection::Incoming, content),
367 ),
368 map(
370 tuple((
371 char('-'),
372 delimited(char('['), relationship_content, char(']')),
373 char('-'),
374 )),
375 |(_, content, _)| (RelationshipDirection::Undirected, content),
376 ),
377 ))(input)?;
378
379 let (variable, types, properties, length) = content;
380
381 Ok((
382 input,
383 RelationshipPattern {
384 variable: variable.map(|s| s.to_string()),
385 types: types.into_iter().map(|s| s.to_string()).collect(),
386 direction,
387 properties: properties.unwrap_or_default(),
388 length,
389 },
390 ))
391}
392
393type RelationshipContentResult<'a> = (
395 Option<&'a str>,
396 Vec<&'a str>,
397 Option<HashMap<String, PropertyValue>>,
398 Option<LengthRange>,
399);
400
401fn relationship_content(input: &str) -> IResult<&str, RelationshipContentResult<'_>> {
403 let (input, _) = multispace0(input)?;
404 let (input, variable) = opt(identifier)(input)?;
405 let (input, first) = opt(preceded(char(':'), identifier))(input)?;
407 let (input, rest) = many0(preceded(
408 alt((
409 map(
410 tuple((multispace0, char('|'), multispace0, opt(char(':')))),
411 |_| (),
412 ),
413 map(char(':'), |_| ()),
414 )),
415 identifier,
416 ))(input)?;
417 let types: Vec<&str> = first.into_iter().chain(rest).collect();
418 let (input, _) = multispace0(input)?;
419 let (input, length) = opt(length_range)(input)?;
420 let (input, _) = multispace0(input)?;
421 let (input, properties) = opt(property_map)(input)?;
422 let (input, _) = multispace0(input)?;
423
424 Ok((input, (variable, types, properties, length)))
425}
426
427fn property_map(input: &str) -> IResult<&str, HashMap<String, PropertyValue>> {
429 let (input, _) = multispace0(input)?;
430 let (input, _) = char('{')(input)?;
431 let (input, _) = multispace0(input)?;
432 let (input, pairs) = separated_list0(comma_ws, property_pair)(input)?;
433 let (input, _) = multispace0(input)?;
434 let (input, _) = char('}')(input)?;
435
436 Ok((input, pairs.into_iter().collect()))
437}
438
439fn property_pair(input: &str) -> IResult<&str, (String, PropertyValue)> {
441 let (input, _) = multispace0(input)?;
442 let (input, key) = identifier(input)?;
443 let (input, _) = multispace0(input)?;
444 let (input, _) = char(':')(input)?;
445 let (input, _) = multispace0(input)?;
446 let (input, value) = alt((
447 property_value,
448 map(property_reference, PropertyValue::Property),
449 map(identifier, |v| PropertyValue::Variable(v.to_string())),
450 ))(input)?;
451
452 Ok((input, (key.to_string(), value)))
453}
454
455fn property_value(input: &str) -> IResult<&str, PropertyValue> {
457 alt((
458 map(string_literal, PropertyValue::String),
459 map(float_literal, PropertyValue::Float), map(integer_literal, PropertyValue::Integer),
461 map(boolean_literal, PropertyValue::Boolean),
462 map(
464 terminated(
465 tag_no_case("null"),
466 not(peek(satisfy(|c: char| c.is_alphanumeric() || c == '_'))),
467 ),
468 |_| PropertyValue::Null,
469 ),
470 map(parameter, PropertyValue::Parameter),
471 ))(input)
472}
473
474fn where_clause(input: &str) -> IResult<&str, WhereClause> {
476 let (input, _) = multispace0(input)?;
477 let (input, _) = tag_no_case("WHERE")(input)?;
478 let (input, _) = multispace1(input)?;
479 let (input, expression) = boolean_expression(input)?;
480
481 Ok((input, WhereClause { expression }))
482}
483
484fn boolean_expression(input: &str) -> IResult<&str, BooleanExpression> {
486 boolean_or_expression(input)
487}
488
489fn boolean_or_expression(input: &str) -> IResult<&str, BooleanExpression> {
490 let (input, first) = boolean_and_expression(input)?;
491 let (input, rest) = many0(preceded(
492 tuple((multispace0, tag_no_case("OR"), multispace1)),
493 boolean_and_expression,
494 ))(input)?;
495 let expr = rest.into_iter().fold(first, |acc, item| {
496 BooleanExpression::Or(Box::new(acc), Box::new(item))
497 });
498 Ok((input, expr))
499}
500
501fn boolean_and_expression(input: &str) -> IResult<&str, BooleanExpression> {
502 let (input, first) = boolean_not_expression(input)?;
503 let (input, rest) = many0(preceded(
504 tuple((multispace0, tag_no_case("AND"), multispace1)),
505 boolean_not_expression,
506 ))(input)?;
507 let expr = rest.into_iter().fold(first, |acc, item| {
508 BooleanExpression::And(Box::new(acc), Box::new(item))
509 });
510 Ok((input, expr))
511}
512
513fn boolean_not_expression(input: &str) -> IResult<&str, BooleanExpression> {
514 let (input, _) = multispace0(input)?;
515 alt((
516 map(
517 preceded(
518 tuple((tag_no_case("NOT"), multispace1)),
519 boolean_not_expression,
520 ),
521 |expr| BooleanExpression::Not(Box::new(expr)),
522 ),
523 boolean_primary_expression,
524 ))(input)
525}
526
527fn boolean_primary_expression(input: &str) -> IResult<&str, BooleanExpression> {
528 let (input, _) = multispace0(input)?;
529 alt((
530 map(
531 delimited(
532 tuple((char('('), multispace0)),
533 boolean_expression,
534 tuple((multispace0, char(')'))),
535 ),
536 |expr| expr,
537 ),
538 exists_subquery,
539 map(path_pattern, |path| BooleanExpression::PatternExists {
541 patterns: vec![GraphPattern::Path(path)],
542 where_clause: None,
543 }),
544 comparison_expression,
545 map(list_predicate, |left| BooleanExpression::Comparison {
546 left,
547 operator: ComparisonOperator::Equal,
548 right: ValueExpression::Literal(PropertyValue::Boolean(true)),
549 }),
550 ))(input)
551}
552
553fn exists_subquery(input: &str) -> IResult<&str, BooleanExpression> {
555 let (input, _) = tuple((tag_no_case("EXISTS"), multispace0, char('{'), multispace0))(input)?;
556 let (input, _) = opt(tuple((tag_no_case("MATCH"), multispace1)))(input)?;
557 let (input, patterns) = separated_list1(comma_ws, graph_pattern)(input)?;
558 let (input, where_clause) = opt(where_clause)(input)?;
559 let (input, _) = tuple((multispace0, char('}')))(input)?;
560 Ok((
561 input,
562 BooleanExpression::PatternExists {
563 patterns,
564 where_clause: where_clause.map(|w| Box::new(w.expression)),
565 },
566 ))
567}
568
569fn comparison_expression(input: &str) -> IResult<&str, BooleanExpression> {
570 let (input, _) = multispace0(input)?;
571 let (input, left) = value_expression(input)?;
572 let (input, _) = multispace0(input)?;
573 let left_clone = left.clone();
574
575 if let Ok((input_after_in, (_, _, list))) =
576 tuple((tag_no_case("IN"), multispace0, value_expression_list))(input)
577 {
578 return Ok((
579 input_after_in,
580 BooleanExpression::In {
581 expression: left,
582 list,
583 },
584 ));
585 }
586 if let Ok((input_after_in, (_, _, list))) =
587 tuple((tag_no_case("IN"), multispace1, value_expression))(input)
588 {
589 return Ok((
590 input_after_in,
591 BooleanExpression::Member {
592 expression: left,
593 list,
594 },
595 ));
596 }
597 if let Ok((input_after_like, (_, _, pattern))) =
599 tuple((tag_no_case("LIKE"), multispace0, string_literal))(input)
600 {
601 return Ok((
602 input_after_like,
603 BooleanExpression::Like {
604 expression: left,
605 pattern,
606 },
607 ));
608 }
609 if let Ok((input_after_ilike, (_, _, pattern))) =
611 tuple((tag_no_case("ILIKE"), multispace0, string_literal))(input)
612 {
613 return Ok((
614 input_after_ilike,
615 BooleanExpression::ILike {
616 expression: left,
617 pattern,
618 },
619 ));
620 }
621 if let Ok((input_after_contains, (_, _, substring))) =
623 tuple((tag_no_case("CONTAINS"), multispace0, string_literal))(input)
624 {
625 return Ok((
626 input_after_contains,
627 BooleanExpression::Contains {
628 expression: left,
629 substring,
630 },
631 ));
632 }
633 if let Ok((input_after_starts, (_, _, _, _, prefix))) = tuple((
635 tag_no_case("STARTS"),
636 multispace1,
637 tag_no_case("WITH"),
638 multispace0,
639 string_literal,
640 ))(input)
641 {
642 return Ok((
643 input_after_starts,
644 BooleanExpression::StartsWith {
645 expression: left,
646 prefix,
647 },
648 ));
649 }
650 if let Ok((input_after_ends, (_, _, _, _, suffix))) = tuple((
652 tag_no_case("ENDS"),
653 multispace1,
654 tag_no_case("WITH"),
655 multispace0,
656 string_literal,
657 ))(input)
658 {
659 return Ok((
660 input_after_ends,
661 BooleanExpression::EndsWith {
662 expression: left,
663 suffix,
664 },
665 ));
666 }
667 if let Ok((rest, ())) = is_null_comparison(input) {
669 return Ok((rest, BooleanExpression::IsNull(left_clone)));
670 }
671 if let Ok((rest, ())) = is_not_null_comparison(input) {
673 return Ok((rest, BooleanExpression::IsNotNull(left_clone)));
674 }
675
676 let (input, operator) = comparison_operator(input)?;
677 let (input, _) = multispace0(input)?;
678 let (mut input, mut right) = value_expression(input)?;
679 let mut expr = BooleanExpression::Comparison {
680 left: left_clone,
681 operator,
682 right: right.clone(),
683 };
684 while let Ok((rest, (_, operator, _, next))) = tuple((
686 multispace0,
687 comparison_operator,
688 multispace0,
689 value_expression,
690 ))(input)
691 {
692 expr = BooleanExpression::And(
693 Box::new(expr),
694 Box::new(BooleanExpression::Comparison {
695 left: right,
696 operator,
697 right: next.clone(),
698 }),
699 );
700 right = next;
701 input = rest;
702 }
703 Ok((input, expr))
704}
705
706fn comparison_operator(input: &str) -> IResult<&str, ComparisonOperator> {
708 alt((
709 map(tag("="), |_| ComparisonOperator::Equal),
710 map(tag("<>"), |_| ComparisonOperator::NotEqual),
711 map(tag("!="), |_| ComparisonOperator::NotEqual),
712 map(tag("<="), |_| ComparisonOperator::LessThanOrEqual),
713 map(tag(">="), |_| ComparisonOperator::GreaterThanOrEqual),
714 map(tag("<"), |_| ComparisonOperator::LessThan),
715 map(tag(">"), |_| ComparisonOperator::GreaterThan),
716 ))(input)
717}
718
719fn basic_value_expression(input: &str) -> IResult<&str, ValueExpression> {
721 alt((
722 parse_vector_literal, pattern_comprehension,
724 list_comprehension,
725 list_literal, map_literal,
727 reduce_expression,
728 list_predicate,
729 parse_parameter, function_call, map(property_value, ValueExpression::Literal), map(property_reference, ValueExpression::Property),
733 map(identifier, |id| ValueExpression::Variable(id.to_string())),
734 ))(input)
735}
736
737fn expression(input: &str) -> IResult<&str, ValueExpression> {
740 alt((
741 map(boolean_expression, |b| {
742 ValueExpression::Boolean(Box::new(b))
743 }),
744 value_expression,
745 ))(input)
746}
747
748fn pattern_comprehension(input: &str) -> IResult<&str, ValueExpression> {
750 let (input, _) = tuple((char('['), multispace0))(input)?;
751 let (input, pattern) = path_pattern(input)?;
752 let (input, filter) = opt(where_clause)(input)?;
753 let (input, map) = preceded(tuple((multispace0, char('|'), multispace0)), expression)(input)?;
754 let (input, _) = tuple((multispace0, char(']')))(input)?;
755 Ok((
756 input,
757 ValueExpression::PatternComprehension {
758 pattern: Box::new(pattern),
759 filter: filter.map(|w| Box::new(w.expression)),
760 map: Box::new(map),
761 },
762 ))
763}
764
765fn list_comprehension(input: &str) -> IResult<&str, ValueExpression> {
767 let (input, (_, _, variable, _, _, _, list)) = tuple((
768 char('['),
769 multispace0,
770 identifier,
771 multispace1,
772 tag_no_case("IN"),
773 multispace1,
774 value_expression,
775 ))(input)?;
776 let (input, filter) = opt(where_clause)(input)?;
777 let (input, map) = opt(preceded(
778 tuple((multispace0, char('|'), multispace0)),
779 expression,
780 ))(input)?;
781 let (input, _) = tuple((multispace0, char(']')))(input)?;
782 Ok((
783 input,
784 ValueExpression::ListComprehension {
785 variable: variable.to_string(),
786 list: Box::new(list),
787 filter: filter.map(|w| Box::new(w.expression)),
788 map: map.map(Box::new),
789 },
790 ))
791}
792
793fn reduce_expression(input: &str) -> IResult<&str, ValueExpression> {
794 let (input, _) = tuple((tag_no_case("reduce"), multispace0, char('('), multispace0))(input)?;
795 let (input, accumulator) = identifier(input)?;
796 let (input, initial) =
797 preceded(tuple((multispace0, char('='), multispace0)), expression)(input)?;
798 let (input, variable) = preceded(comma_ws, identifier)(input)?;
799 let (input, list) = preceded(
800 tuple((multispace1, tag_no_case("IN"), multispace1)),
801 value_expression,
802 )(input)?;
803 let (input, mut body) =
804 preceded(tuple((multispace0, char('|'), multispace0)), expression)(input)?;
805 let (input, _) = tuple((multispace0, char(')')))(input)?;
806 mark_locals(&mut body, &[accumulator, variable]);
807 Ok((
808 input,
809 ValueExpression::Reduce {
810 accumulator: accumulator.to_lowercase(),
811 initial: Box::new(initial),
812 variable: variable.to_lowercase(),
813 list: Box::new(list),
814 expression: Box::new(body),
815 },
816 ))
817}
818
819fn list_predicate(input: &str) -> IResult<&str, ValueExpression> {
820 let (input, kind) = alt((
821 tag_no_case("all"),
822 tag_no_case("any"),
823 tag_no_case("none"),
824 tag_no_case("single"),
825 ))(input)?;
826 let (input, variable) =
827 preceded(tuple((multispace0, char('('), multispace0)), identifier)(input)?;
828 let (input, list) = preceded(
829 tuple((multispace1, tag_no_case("IN"), multispace1)),
830 value_expression,
831 )(input)?;
832 let (input, mut predicate) = preceded(
833 tuple((multispace1, tag_no_case("WHERE"), multispace1)),
834 expression,
835 )(input)?;
836 let (input, _) = tuple((multispace0, char(')')))(input)?;
837 mark_locals(&mut predicate, &[variable]);
838 Ok((
839 input,
840 ValueExpression::ListPredicate {
841 kind: kind.to_lowercase(),
842 variable: variable.to_lowercase(),
843 list: Box::new(list),
844 predicate: Box::new(predicate),
845 },
846 ))
847}
848
849fn map_literal(input: &str) -> IResult<&str, ValueExpression> {
851 let entry = tuple((
852 multispace0,
853 identifier,
854 multispace0,
855 char(':'),
856 multispace0,
857 expression,
858 ));
859 let (input, entries) = delimited(
860 tuple((char('{'), multispace0)),
861 separated_list0(tuple((multispace0, char(','))), entry),
862 tuple((multispace0, char('}'))),
863 )(input)?;
864 Ok((
865 input,
866 ValueExpression::Map(
867 entries
868 .into_iter()
869 .map(|(_, k, _, _, _, v)| (k.to_string(), v))
870 .collect(),
871 ),
872 ))
873}
874
875fn value_expression(input: &str) -> IResult<&str, ValueExpression> {
878 arithmetic_level(
879 input,
880 &[
881 ('+', ArithmeticOperator::Add),
882 ('-', ArithmeticOperator::Subtract),
883 ],
884 )
885}
886
887fn arithmetic_level<'a>(
888 input: &'a str,
889 operators: &[(char, ArithmeticOperator)],
890) -> IResult<&'a str, ValueExpression> {
891 let tighter = |i: &'a str| {
892 if operators[0].0 == '+' {
893 arithmetic_level(
894 i,
895 &[
896 ('*', ArithmeticOperator::Multiply),
897 ('/', ArithmeticOperator::Divide),
898 ('%', ArithmeticOperator::Modulo),
899 ],
900 )
901 } else {
902 primary_value_expression(i)
903 }
904 };
905 let (mut input, mut left) = tighter(input)?;
906 loop {
907 let Ok((after_op, (_, op, _))) = tuple((
908 multispace0,
909 one_of::<_, _, nom::error::Error<&str>>("+-*/%"),
910 multispace0,
911 ))(input) else {
912 break;
913 };
914 let Some((_, operator)) = operators.iter().find(|(c, _)| *c == op) else {
915 break;
916 };
917 let Ok((rest, right)) = tighter(after_op) else {
919 break;
920 };
921 left = ValueExpression::Arithmetic {
922 left: Box::new(left),
923 operator: operator.clone(),
924 right: Box::new(right),
925 };
926 input = rest;
927 }
928 Ok((input, left))
929}
930
931fn primary_value_expression(input: &str) -> IResult<&str, ValueExpression> {
933 let (mut input, mut value) = alt((
934 delimited(
935 tuple((char('('), multispace0)),
936 value_expression,
937 tuple((multispace0, char(')'))),
938 ),
939 case_expression,
940 single_value_expression,
941 ))(input)?;
942 loop {
945 if let Ok((rest, index)) = delimited(
946 tuple((char('['), multispace0)),
947 value_expression,
948 tuple((multispace0, char(']'))),
949 )(input)
950 {
951 value = ValueExpression::ScalarFunction {
952 name: "__index".to_string(),
953 args: vec![value, index],
954 };
955 input = rest;
956 } else if let Ok((rest, field)) = preceded(char('.'), identifier)(input) {
957 value = ValueExpression::Field {
958 base: Box::new(value),
959 field: field.to_string(),
960 };
961 input = rest;
962 } else {
963 break;
964 }
965 }
966 Ok((input, value))
967}
968
969fn case_expression(input: &str) -> IResult<&str, ValueExpression> {
971 let keyword = |k: &'static str| tuple((multispace0, tag_no_case(k), multispace1));
972 let (input, _) = tuple((tag_no_case("CASE"), multispace1))(input)?;
973 let (input, operand) = opt(terminated(expression, peek(keyword("WHEN"))))(input)?;
974 let (input, whens) = match &operand {
975 None => many1(tuple((
976 preceded(keyword("WHEN"), boolean_expression),
977 preceded(keyword("THEN"), expression),
978 )))(input)?,
979 Some(operand) => {
980 let (input, pairs) = many1(tuple((
981 preceded(keyword("WHEN"), expression),
982 preceded(keyword("THEN"), expression),
983 )))(input)?;
984 let whens = pairs
985 .into_iter()
986 .map(|(value, then)| {
987 let condition = BooleanExpression::Comparison {
988 left: operand.clone(),
989 operator: ComparisonOperator::Equal,
990 right: value,
991 };
992 (condition, then)
993 })
994 .collect();
995 (input, whens)
996 }
997 };
998 let (input, otherwise) = opt(preceded(keyword("ELSE"), expression))(input)?;
999 let (input, _) = tuple((multispace0, tag_no_case("END")))(input)?;
1000 Ok((
1001 input,
1002 ValueExpression::Case {
1003 whens,
1004 otherwise: otherwise.map(Box::new),
1005 },
1006 ))
1007}
1008
1009fn single_value_expression(input: &str) -> IResult<&str, ValueExpression> {
1011 if let Ok((_, first_ident)) = peek(identifier)(input) {
1014 let ident_lower = first_ident.to_lowercase();
1015
1016 match ident_lower.as_str() {
1017 "vector_distance" => return parse_vector_distance(input),
1018 "vector_similarity" => return parse_vector_similarity(input),
1019 _ => {} }
1021 }
1022
1023 basic_value_expression(input)
1025}
1026
1027fn parse_distance_metric(input: &str) -> IResult<&str, DistanceMetric> {
1029 alt((
1030 map(tag_no_case("cosine"), |_| DistanceMetric::Cosine),
1031 map(tag_no_case("l2"), |_| DistanceMetric::L2),
1032 map(tag_no_case("dot"), |_| DistanceMetric::Dot),
1033 ))(input)
1034}
1035
1036fn parse_vector_distance(input: &str) -> IResult<&str, ValueExpression> {
1038 let (input, _) = tag_no_case("vector_distance")(input)?;
1039 let (input, _) = multispace0(input)?;
1040 let (input, _) = char('(')(input)?;
1041 let (input, _) = multispace0(input)?;
1042
1043 let (input, left) = basic_value_expression(input)?;
1045 let (input, _) = multispace0(input)?;
1046 let (input, _) = char(',')(input)?;
1047 let (input, _) = multispace0(input)?;
1048
1049 let (input, right) = basic_value_expression(input)?;
1051 let (input, _) = multispace0(input)?;
1052 let (input, _) = char(',')(input)?;
1053 let (input, _) = multispace0(input)?;
1054
1055 let (input, metric) = parse_distance_metric(input)?;
1057 let (input, _) = multispace0(input)?;
1058 let (input, _) = char(')')(input)?;
1059
1060 Ok((
1061 input,
1062 ValueExpression::VectorDistance {
1063 left: Box::new(left),
1064 right: Box::new(right),
1065 metric,
1066 },
1067 ))
1068}
1069
1070fn parse_vector_similarity(input: &str) -> IResult<&str, ValueExpression> {
1072 let (input, _) = tag_no_case("vector_similarity")(input)?;
1073 let (input, _) = multispace0(input)?;
1074 let (input, _) = char('(')(input)?;
1075 let (input, _) = multispace0(input)?;
1076
1077 let (input, left) = basic_value_expression(input)?;
1079 let (input, _) = multispace0(input)?;
1080 let (input, _) = char(',')(input)?;
1081 let (input, _) = multispace0(input)?;
1082
1083 let (input, right) = basic_value_expression(input)?;
1085 let (input, _) = multispace0(input)?;
1086 let (input, _) = char(',')(input)?;
1087 let (input, _) = multispace0(input)?;
1088
1089 let (input, metric) = parse_distance_metric(input)?;
1091 let (input, _) = multispace0(input)?;
1092 let (input, _) = char(')')(input)?;
1093
1094 Ok((
1095 input,
1096 ValueExpression::VectorSimilarity {
1097 left: Box::new(left),
1098 right: Box::new(right),
1099 metric,
1100 },
1101 ))
1102}
1103
1104fn parse_parameter(input: &str) -> IResult<&str, ValueExpression> {
1106 let (input, name) = parameter(input)?;
1107 Ok((input, ValueExpression::Parameter(name)))
1108}
1109
1110fn function_call(input: &str) -> IResult<&str, ValueExpression> {
1112 let (input, name) = recognize(pair(identifier, many0(preceded(char('.'), identifier))))(input)?;
1114 let (input, _) = multispace0(input)?;
1115 let (input, _) = char('(')(input)?;
1116 let (input, _) = multispace0(input)?;
1117
1118 let (input, distinct) = opt(tag_no_case("DISTINCT"))(input)?;
1120 let distinct = distinct.is_some();
1121 let (input, _) = if distinct {
1122 multispace1(input)?
1123 } else {
1124 (input, "")
1125 };
1126
1127 if let Ok((input_after_star, _)) = char::<_, nom::error::Error<&str>>('*')(input) {
1129 if name.to_lowercase() == "count" {
1131 let (input, _) = multispace0(input_after_star)?;
1132 let (input, _) = char(')')(input)?;
1133 return Ok((
1134 input,
1135 ValueExpression::AggregateFunction {
1136 name: name.to_string(),
1137 args: vec![ValueExpression::Variable("*".to_string())],
1138 distinct,
1139 },
1140 ));
1141 } else {
1142 }
1145 }
1146
1147 let (input, args) =
1149 separated_list0(tuple((multispace0, char(','), multispace0)), expression)(input)?;
1150 let (input, _) = multispace0(input)?;
1151 let (input, _) = char(')')(input)?;
1152
1153 use crate::ast::{classify_function, FunctionType};
1155 match classify_function(name) {
1156 FunctionType::Aggregate => Ok((
1157 input,
1158 ValueExpression::AggregateFunction {
1159 name: name.to_string(),
1160 args,
1161 distinct,
1162 },
1163 )),
1164 FunctionType::Scalar => {
1165 if distinct {
1167 return Err(nom::Err::Failure(nom::error::Error::new(
1168 input,
1169 nom::error::ErrorKind::Verify,
1170 )));
1171 }
1172 Ok((
1173 input,
1174 ValueExpression::ScalarFunction {
1175 name: name.to_string(),
1176 args,
1177 },
1178 ))
1179 }
1180 FunctionType::Unknown => {
1181 if distinct {
1184 return Err(nom::Err::Failure(nom::error::Error::new(
1185 input,
1186 nom::error::ErrorKind::Verify,
1187 )));
1188 }
1189 Ok((
1190 input,
1191 ValueExpression::ScalarFunction {
1192 name: name.to_string(),
1193 args,
1194 },
1195 ))
1196 }
1197 }
1198}
1199
1200fn value_expression_list(input: &str) -> IResult<&str, Vec<ValueExpression>> {
1201 delimited(
1202 tuple((char('['), multispace0)),
1203 separated_list1(
1204 tuple((multispace0, char(','), multispace0)),
1205 value_expression,
1206 ),
1207 tuple((multispace0, char(']'))),
1208 )(input)
1209}
1210
1211fn float32_literal(input: &str) -> IResult<&str, f32> {
1213 map_res(
1214 recognize(tuple((
1215 opt(char('-')),
1216 alt((
1217 recognize(tuple((
1219 digit1,
1220 opt(tuple((char('.'), digit0))),
1221 one_of("eE"),
1222 opt(one_of("+-")),
1223 digit1,
1224 ))),
1225 recognize(tuple((digit1, opt(tuple((char('.'), digit0)))))),
1227 )),
1228 ))),
1229 |s: &str| s.parse::<f32>(),
1230 )(input)
1231}
1232
1233fn parse_vector_literal(input: &str) -> IResult<&str, ValueExpression> {
1235 let literal = input;
1236 let (input, _) = char('[')(input)?;
1237 let (input, _) = multispace0(input)?;
1238
1239 let (input, values) = separated_list1(
1240 tuple((multispace0, char(','), multispace0)),
1241 float32_literal,
1242 )(input)?;
1243
1244 let (input, _) = multispace0(input)?;
1245 let (input, _) = char(']')(input)?;
1246
1247 if !literal[..literal.len() - input.len()]
1249 .chars()
1250 .any(|c| matches!(c, '.' | 'e' | 'E'))
1251 {
1252 return Err(nom::Err::Error(nom::error::Error::new(
1253 literal,
1254 nom::error::ErrorKind::Verify,
1255 )));
1256 }
1257
1258 Ok((input, ValueExpression::VectorLiteral(values)))
1259}
1260
1261fn list_literal(input: &str) -> IResult<&str, ValueExpression> {
1263 let (input, items) = delimited(
1264 tuple((char('['), multispace0)),
1265 separated_list0(tuple((multispace0, char(','), multispace0)), expression),
1266 tuple((multispace0, char(']'))),
1267 )(input)?;
1268 Ok((
1269 input,
1270 ValueExpression::ScalarFunction {
1271 name: "__list".to_string(),
1272 args: items,
1273 },
1274 ))
1275}
1276
1277fn property_reference(input: &str) -> IResult<&str, PropertyRef> {
1279 let (input, variable) = identifier(input)?;
1280 let (input, _) = char('.')(input)?;
1281 let (input, property) = identifier(input)?;
1282
1283 Ok((
1284 input,
1285 PropertyRef {
1286 variable: variable.to_string(),
1287 property: property.to_string(),
1288 },
1289 ))
1290}
1291
1292fn with_clause(input: &str) -> IResult<&str, WithClause> {
1294 let (input, _) = multispace0(input)?;
1295 let (input, _) = tag_no_case("WITH")(input)?;
1296 let (input, _) = multispace1(input)?;
1297 let (input, distinct) = opt(terminated(tag_no_case("DISTINCT"), multispace1))(input)?;
1298 let (input, items) = separated_list0(comma_ws, return_item)(input)?;
1299 let (input, order_by) = opt(order_by_clause)(input)?;
1300 let (input, limit) = opt(limit_clause)(input)?;
1301 let (input, where_clause) = opt(where_clause)(input)?;
1302
1303 Ok((
1304 input,
1305 WithClause {
1306 distinct: distinct.is_some(),
1307 items,
1308 order_by,
1309 limit,
1310 where_clause,
1311 },
1312 ))
1313}
1314
1315fn with_stage(input: &str) -> IResult<&str, WithStage> {
1317 let (input, with_clause) = with_clause(input)?;
1318 let (input, reading_clauses) = many0(reading_clause)(input)?;
1319 let (input, where_clause) = opt(where_clause)(input)?;
1320 Ok((
1321 input,
1322 WithStage {
1323 with_clause,
1324 reading_clauses,
1325 where_clause,
1326 },
1327 ))
1328}
1329
1330fn return_clause(input: &str) -> IResult<&str, ReturnClause> {
1332 let (input, _) = multispace0(input)?;
1333 let (input, _) = tag_no_case("RETURN")(input)?;
1334 let (input, _) = multispace1(input)?;
1335 let (input, distinct) = opt(tag_no_case("DISTINCT"))(input)?;
1336 let (input, _) = if distinct.is_some() {
1337 multispace1(input)?
1338 } else {
1339 (input, "")
1340 };
1341 let (input, items) = separated_list0(comma_ws, return_item)(input)?;
1342
1343 Ok((
1344 input,
1345 ReturnClause {
1346 distinct: distinct.is_some(),
1347 items,
1348 },
1349 ))
1350}
1351
1352fn return_item(input: &str) -> IResult<&str, ReturnItem> {
1354 let (input, expression) = expression(input)?;
1355 let (input, _) = multispace0(input)?;
1356 let (input, alias) = opt(preceded(
1357 tuple((tag_no_case("AS"), multispace1)),
1358 identifier,
1359 ))(input)?;
1360
1361 Ok((
1362 input,
1363 ReturnItem {
1364 expression,
1365 alias: alias.map(|s| s.to_string()),
1366 },
1367 ))
1368}
1369
1370fn is_null_comparison(input: &str) -> IResult<&str, ()> {
1372 let (input, _) = multispace0(input)?;
1373 let (input, _) = tag_no_case("IS")(input)?;
1374 let (input, _) = multispace1(input)?;
1375 let (input, _) = tag_no_case("NULL")(input)?;
1376 let (input, _) = multispace0(input)?;
1377
1378 Ok((input, ()))
1379}
1380
1381fn is_not_null_comparison(input: &str) -> IResult<&str, ()> {
1383 let (input, _) = multispace0(input)?;
1384 let (input, _) = tag_no_case("IS")(input)?;
1385 let (input, _) = multispace1(input)?;
1386 let (input, _) = tag_no_case("NOT")(input)?;
1387 let (input, _) = multispace1(input)?;
1388 let (input, _) = tag_no_case("NULL")(input)?;
1389 let (input, _) = multispace0(input)?;
1390
1391 Ok((input, ()))
1392}
1393
1394fn order_by_clause(input: &str) -> IResult<&str, OrderByClause> {
1396 let (input, _) = multispace0(input)?;
1397 let (input, _) = tag_no_case("ORDER")(input)?;
1398 let (input, _) = multispace1(input)?;
1399 let (input, _) = tag_no_case("BY")(input)?;
1400 let (input, _) = multispace1(input)?;
1401 let (input, items) = separated_list0(comma_ws, order_by_item)(input)?;
1402
1403 Ok((input, OrderByClause { items }))
1404}
1405
1406fn order_by_item(input: &str) -> IResult<&str, OrderByItem> {
1408 let (input, expression) = value_expression(input)?;
1409 let (input, _) = multispace0(input)?;
1410 let (input, direction) = opt(alt((
1411 map(tag_no_case("ASC"), |_| SortDirection::Ascending),
1412 map(tag_no_case("DESC"), |_| SortDirection::Descending),
1413 )))(input)?;
1414
1415 Ok((
1416 input,
1417 OrderByItem {
1418 expression,
1419 direction: direction.unwrap_or(SortDirection::Ascending),
1420 },
1421 ))
1422}
1423
1424fn limit_clause(input: &str) -> IResult<&str, RowCount> {
1426 let (input, _) = multispace0(input)?;
1427 let (input, _) = tag_no_case("LIMIT")(input)?;
1428 let (input, _) = multispace1(input)?;
1429 row_count(input)
1430}
1431
1432fn skip_clause(input: &str) -> IResult<&str, RowCount> {
1434 let (input, _) = multispace0(input)?;
1435 let (input, _) = tag_no_case("SKIP")(input)?;
1436 let (input, _) = multispace1(input)?;
1437 row_count(input)
1438}
1439
1440fn row_count(input: &str) -> IResult<&str, RowCount> {
1441 if let Ok((remaining, name)) = parameter(input) {
1442 return Ok((remaining, RowCount::Parameter(name)));
1443 }
1444 let (remaining, value) = integer_literal(input)?;
1445 let value = u64::try_from(value).map_err(|_| {
1446 nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Verify))
1447 })?;
1448 Ok((remaining, RowCount::Literal(value)))
1449}
1450
1451fn pagination_clauses(input: &str) -> IResult<&str, (Option<RowCount>, Option<RowCount>)> {
1453 let (mut remaining, _) = multispace0(input)?;
1454 let mut skip: Option<RowCount> = None;
1455 let mut limit: Option<RowCount> = None;
1456
1457 loop {
1458 let before = remaining;
1459
1460 if skip.is_none() {
1461 if let Ok((i, s)) = skip_clause(remaining) {
1462 skip = Some(s);
1463 remaining = i;
1464 continue;
1465 }
1466 }
1467
1468 if limit.is_none() {
1469 if let Ok((i, l)) = limit_clause(remaining) {
1470 limit = Some(l);
1471 remaining = i;
1472 continue;
1473 }
1474 }
1475
1476 if before == remaining {
1477 break;
1478 }
1479 }
1480
1481 Ok((remaining, (skip, limit)))
1482}
1483
1484fn identifier(input: &str) -> IResult<&str, &str> {
1488 take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)
1489}
1490
1491fn string_literal(input: &str) -> IResult<&str, String> {
1493 alt((double_quoted_string, single_quoted_string))(input)
1494}
1495
1496fn double_quoted_string(input: &str) -> IResult<&str, String> {
1497 let (input, _) = char('"')(input)?;
1498 let (input, content) = take_while(|c| c != '"')(input)?;
1499 let (input, _) = char('"')(input)?;
1500 Ok((input, content.to_string()))
1501}
1502
1503fn single_quoted_string(input: &str) -> IResult<&str, String> {
1504 let (input, _) = char('\'')(input)?;
1505 let (input, content) = take_while(|c| c != '\'')(input)?;
1506 let (input, _) = char('\'')(input)?;
1507 Ok((input, content.to_string()))
1508}
1509
1510fn integer_literal(input: &str) -> IResult<&str, i64> {
1512 let (input, digits) = recognize(pair(
1513 opt(char('-')),
1514 take_while1(|c: char| c.is_ascii_digit()),
1515 ))(input)?;
1516
1517 let value = digits.parse().map_err(|_| {
1519 nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Digit))
1520 })?;
1521 Ok((input, value))
1522}
1523
1524fn float_literal(input: &str) -> IResult<&str, f64> {
1526 let (input, number) = recognize(tuple((
1527 opt(char('-')),
1528 take_while1(|c: char| c.is_ascii_digit()),
1529 char('.'),
1530 take_while1(|c: char| c.is_ascii_digit()),
1531 )))(input)?;
1532
1533 let value: f64 = number.parse().map_err(|_| {
1534 nom::Err::Failure(nom::error::Error::new(input, nom::error::ErrorKind::Float))
1535 })?;
1536 if !value.is_finite() {
1537 return Err(nom::Err::Failure(nom::error::Error::new(
1538 input,
1539 nom::error::ErrorKind::Float,
1540 )));
1541 }
1542 Ok((input, value))
1543}
1544
1545fn boolean_literal(input: &str) -> IResult<&str, bool> {
1547 alt((
1548 map(tag_no_case("true"), |_| true),
1549 map(tag_no_case("false"), |_| false),
1550 ))(input)
1551}
1552
1553fn parameter(input: &str) -> IResult<&str, String> {
1555 map(preceded(char('$'), identifier), |s| s.to_string())(input)
1557}
1558
1559fn comma_ws(input: &str) -> IResult<&str, ()> {
1561 let (input, _) = multispace0(input)?;
1562 let (input, _) = char(',')(input)?;
1563 let (input, _) = multispace0(input)?;
1564 Ok((input, ()))
1565}
1566
1567fn length_range(input: &str) -> IResult<&str, LengthRange> {
1569 let (input, _) = char('*')(input)?;
1570 let (input, _) = multispace0(input)?;
1571
1572 alt((
1574 map(
1576 tuple((
1577 nom::character::complete::u32,
1578 tag(".."),
1579 nom::character::complete::u32,
1580 )),
1581 |(min, _, max)| LengthRange {
1582 min: Some(min),
1583 max: Some(max),
1584 },
1585 ),
1586 map(preceded(tag(".."), nom::character::complete::u32), |max| {
1588 LengthRange {
1589 min: None,
1590 max: Some(max),
1591 }
1592 }),
1593 map(
1595 tuple((nom::character::complete::u32, tag(".."))),
1596 |(min, _)| LengthRange {
1597 min: Some(min),
1598 max: None,
1599 },
1600 ),
1601 map(nom::character::complete::u32, |min| LengthRange {
1603 min: Some(min),
1604 max: Some(min),
1605 }),
1606 map(multispace0, |_| LengthRange {
1608 min: None,
1609 max: None,
1610 }),
1611 ))(input)
1612}
1613
1614#[cfg(test)]
1615mod tests {
1616 use super::*;
1617 use crate::ast::{BooleanExpression, ComparisonOperator, PropertyValue, ValueExpression};
1618
1619 #[test]
1620 fn test_parse_simple_node_query() {
1621 let query = "MATCH (n:Person) RETURN n.name";
1622 let result = parse_cypher_query(query).unwrap();
1623
1624 assert_eq!(result.reading_clauses.len(), 1);
1625 assert_eq!(result.return_clause.items.len(), 1);
1626 }
1627
1628 #[test]
1629 fn test_parse_node_with_properties() {
1630 let query = r#"MATCH (n:Person {name: "John", age: 30}) RETURN n"#;
1631 let result = parse_cypher_query(query).unwrap();
1632
1633 if let ReadingClause::Match(match_clause) = &result.reading_clauses[0] {
1634 if let GraphPattern::Node(node) = &match_clause.patterns[0] {
1635 assert_eq!(node.labels, vec!["Person"]);
1636 assert_eq!(node.properties.len(), 2);
1637 } else {
1638 panic!("Expected node pattern");
1639 }
1640 } else {
1641 panic!("Expected match clause");
1642 }
1643 }
1644
1645 #[test]
1646 fn test_parse_simple_relationship_query() {
1647 let query = "MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name, b.name";
1648 let result = parse_cypher_query(query).unwrap();
1649
1650 assert_eq!(result.reading_clauses.len(), 1);
1651 assert_eq!(result.return_clause.items.len(), 2);
1652
1653 if let ReadingClause::Match(match_clause) = &result.reading_clauses[0] {
1654 if let GraphPattern::Path(path) = &match_clause.patterns[0] {
1655 assert_eq!(path.segments.len(), 1);
1656 assert_eq!(path.segments[0].relationship.types, vec!["KNOWS"]);
1657 } else {
1658 panic!("Expected path pattern");
1659 }
1660 } else {
1661 panic!("Expected match clause");
1662 }
1663 }
1664
1665 #[test]
1666 fn test_parse_variable_length_path() {
1667 let query = "MATCH (a:Person)-[:FRIEND_OF*1..2]-(b:Person) RETURN a.name, b.name";
1668 let result = parse_cypher_query(query).unwrap();
1669
1670 assert_eq!(result.reading_clauses.len(), 1);
1671
1672 if let ReadingClause::Match(match_clause) = &result.reading_clauses[0] {
1673 if let GraphPattern::Path(path) = &match_clause.patterns[0] {
1674 assert_eq!(path.segments.len(), 1);
1675 assert_eq!(path.segments[0].relationship.types, vec!["FRIEND_OF"]);
1676
1677 let length = path.segments[0].relationship.length.as_ref().unwrap();
1678 assert_eq!(length.min, Some(1));
1679 assert_eq!(length.max, Some(2));
1680 } else {
1681 panic!("Expected path pattern");
1682 }
1683 } else {
1684 panic!("Expected match clause");
1685 }
1686 }
1687
1688 #[test]
1689 fn test_parse_query_with_where_clause() {
1690 let query = "MATCH (n:Person) WHERE n.age > 30 RETURN n.name";
1691 let result = parse_cypher_query(query).unwrap();
1692
1693 assert!(result.where_clause.is_some());
1694 }
1695
1696 #[test]
1697 fn test_parse_query_with_single_quoted_literal() {
1698 let query = "MATCH (n:Person) WHERE n.name = 'Alice' RETURN n.name";
1699 let result = parse_cypher_query(query).unwrap();
1700
1701 assert!(result.where_clause.is_some());
1702 }
1703
1704 #[test]
1705 fn test_parse_query_with_and_conditions() {
1706 let query = "MATCH (src:Entity)-[rel:RELATIONSHIP]->(dst:Entity) WHERE rel.relationship_type = 'WORKS_ON' AND dst.name_lower = 'presto' RETURN src.name, src.entity_id";
1707 let result = parse_cypher_query(query).unwrap();
1708
1709 let where_clause = result.where_clause.expect("Expected WHERE clause");
1710 match where_clause.expression {
1711 BooleanExpression::And(left, right) => {
1712 match *left {
1713 BooleanExpression::Comparison {
1714 left: ValueExpression::Property(ref prop),
1715 operator,
1716 right: ValueExpression::Literal(PropertyValue::String(ref value)),
1717 } => {
1718 assert_eq!(prop.variable, "rel");
1719 assert_eq!(prop.property, "relationship_type");
1720 assert_eq!(operator, ComparisonOperator::Equal);
1721 assert_eq!(value, "WORKS_ON");
1722 }
1723 _ => panic!("Expected comparison for relationship_type filter"),
1724 }
1725
1726 match *right {
1727 BooleanExpression::Comparison {
1728 left: ValueExpression::Property(ref prop),
1729 operator,
1730 right: ValueExpression::Literal(PropertyValue::String(ref value)),
1731 } => {
1732 assert_eq!(prop.variable, "dst");
1733 assert_eq!(prop.property, "name_lower");
1734 assert_eq!(operator, ComparisonOperator::Equal);
1735 assert_eq!(value, "presto");
1736 }
1737 _ => panic!("Expected comparison for destination name filter"),
1738 }
1739 }
1740 other => panic!("Expected AND expression, got {:?}", other),
1741 }
1742 }
1743
1744 #[test]
1745 fn test_parse_query_with_in_clause() {
1746 let query = "MATCH (src:Entity)-[rel:RELATIONSHIP]->(dst:Entity) WHERE rel.relationship_type IN ['WORKS_FOR', 'PART_OF'] RETURN src.name";
1747 let result = parse_cypher_query(query).unwrap();
1748
1749 let where_clause = result.where_clause.expect("Expected WHERE clause");
1750 match where_clause.expression {
1751 BooleanExpression::In { expression, list } => {
1752 match expression {
1753 ValueExpression::Property(prop_ref) => {
1754 assert_eq!(prop_ref.variable, "rel");
1755 assert_eq!(prop_ref.property, "relationship_type");
1756 }
1757 _ => panic!("Expected property reference in IN expression"),
1758 }
1759 assert_eq!(list.len(), 2);
1760 match &list[0] {
1761 ValueExpression::Literal(PropertyValue::String(val)) => {
1762 assert_eq!(val, "WORKS_FOR");
1763 }
1764 _ => panic!("Expected first list item to be a string literal"),
1765 }
1766 match &list[1] {
1767 ValueExpression::Literal(PropertyValue::String(val)) => {
1768 assert_eq!(val, "PART_OF");
1769 }
1770 _ => panic!("Expected second list item to be a string literal"),
1771 }
1772 }
1773 other => panic!("Expected IN expression, got {:?}", other),
1774 }
1775 }
1776
1777 #[test]
1778 fn test_parse_query_with_is_null() {
1779 let query = "MATCH (n:Person) WHERE n.age IS NULL RETURN n.name";
1780 let result = parse_cypher_query(query).unwrap();
1781
1782 let where_clause = result.where_clause.expect("Expected WHERE clause");
1783
1784 match where_clause.expression {
1785 BooleanExpression::IsNull(expr) => match expr {
1786 ValueExpression::Property(prop_ref) => {
1787 assert_eq!(prop_ref.variable, "n");
1788 assert_eq!(prop_ref.property, "age");
1789 }
1790 _ => panic!("Expected property reference in IS NULL expression"),
1791 },
1792 other => panic!("Expected IS NULL expression, got {:?}", other),
1793 }
1794 }
1795
1796 #[test]
1797 fn test_parse_query_with_is_not_null() {
1798 let query = "MATCH (n:Person) WHERE n.age IS NOT NULL RETURN n.name";
1799 let result = parse_cypher_query(query).unwrap();
1800
1801 let where_clause = result.where_clause.expect("Expected WHERE clause");
1802
1803 match where_clause.expression {
1804 BooleanExpression::IsNotNull(expr) => match expr {
1805 ValueExpression::Property(prop_ref) => {
1806 assert_eq!(prop_ref.variable, "n");
1807 assert_eq!(prop_ref.property, "age");
1808 }
1809 _ => panic!("Expected property reference in IS NOT NULL expression"),
1810 },
1811 other => panic!("Expected IS NOT NULL expression, got {:?}", other),
1812 }
1813 }
1814
1815 #[test]
1816 fn test_parse_query_with_limit() {
1817 let query = "MATCH (n:Person) RETURN n.name LIMIT 10";
1818 let result = parse_cypher_query(query).unwrap();
1819
1820 assert_eq!(result.limit, Some(RowCount::Literal(10)));
1821 }
1822
1823 #[test]
1824 fn test_parse_query_with_skip() {
1825 let query = "MATCH (n:Person) RETURN n.name SKIP 5";
1826 let result = parse_cypher_query(query).unwrap();
1827
1828 assert_eq!(result.skip, Some(RowCount::Literal(5)));
1829 assert_eq!(result.limit, None);
1830 }
1831
1832 #[test]
1833 fn test_parse_query_with_skip_and_limit() {
1834 let query = "MATCH (n:Person) RETURN n.name SKIP 5 LIMIT 10";
1835 let result = parse_cypher_query(query).unwrap();
1836
1837 assert_eq!(result.skip, Some(RowCount::Literal(5)));
1838 assert_eq!(result.limit, Some(RowCount::Literal(10)));
1839 }
1840
1841 #[test]
1842 fn test_parse_query_with_skip_and_order_by() {
1843 let query = "MATCH (n:Person) RETURN n.name ORDER BY n.age SKIP 5";
1844 let result = parse_cypher_query(query).unwrap();
1845
1846 assert_eq!(result.skip, Some(RowCount::Literal(5)));
1847 assert!(result.order_by.is_some());
1848 }
1849
1850 #[test]
1851 fn test_parse_query_with_skip_order_by_and_limit() {
1852 let query = "MATCH (n:Person) RETURN n.name ORDER BY n.age SKIP 5 LIMIT 10";
1853 let result = parse_cypher_query(query).unwrap();
1854
1855 assert_eq!(result.skip, Some(RowCount::Literal(5)));
1856 assert_eq!(result.limit, Some(RowCount::Literal(10)));
1857 assert!(result.order_by.is_some());
1858 }
1859
1860 #[test]
1861 fn test_parse_count_star() {
1862 let query = "MATCH (n:Person) RETURN count(*) AS total";
1863 let result = parse_cypher_query(query).unwrap();
1864
1865 assert_eq!(result.return_clause.items.len(), 1);
1866 let item = &result.return_clause.items[0];
1867 assert_eq!(item.alias, Some("total".to_string()));
1868
1869 match &item.expression {
1870 ValueExpression::AggregateFunction { name, args, .. } => {
1871 assert_eq!(name, "count");
1872 assert_eq!(args.len(), 1);
1873 match &args[0] {
1874 ValueExpression::Variable(v) => assert_eq!(v, "*"),
1875 _ => panic!("Expected Variable(*) in count(*)"),
1876 }
1877 }
1878 _ => panic!("Expected AggregateFunction expression"),
1879 }
1880 }
1881
1882 #[test]
1883 fn test_parse_count_property() {
1884 let query = "MATCH (n:Person) RETURN count(n.age)";
1885 let result = parse_cypher_query(query).unwrap();
1886
1887 assert_eq!(result.return_clause.items.len(), 1);
1888 let item = &result.return_clause.items[0];
1889
1890 match &item.expression {
1891 ValueExpression::AggregateFunction { name, args, .. } => {
1892 assert_eq!(name, "count");
1893 assert_eq!(args.len(), 1);
1894 match &args[0] {
1895 ValueExpression::Property(prop) => {
1896 assert_eq!(prop.variable, "n");
1897 assert_eq!(prop.property, "age");
1898 }
1899 _ => panic!("Expected Property in count(n.age)"),
1900 }
1901 }
1902 _ => panic!("Expected AggregateFunction expression"),
1903 }
1904 }
1905
1906 #[test]
1907 fn test_parse_non_count_function_rejects_star() {
1908 let query = "MATCH (n:Person) RETURN foo(*)";
1910 let result = parse_cypher_query(query);
1911 assert!(result.is_err(), "foo(*) should not parse successfully");
1912 }
1913
1914 #[test]
1915 fn test_parse_count_with_multiple_args() {
1916 let query = "MATCH (n:Person) RETURN count(n.age, n.name)";
1919 let result = parse_cypher_query(query);
1920 assert!(
1921 result.is_ok(),
1922 "Parser should accept multiple args (validation happens in semantic phase)"
1923 );
1924
1925 let ast = result.unwrap();
1927 match &ast.return_clause.items[0].expression {
1928 ValueExpression::AggregateFunction { name, args, .. } => {
1929 assert_eq!(name, "count");
1930 assert_eq!(args.len(), 2);
1931 }
1932 _ => panic!("Expected AggregateFunction expression"),
1933 }
1934 }
1935
1936 #[test]
1937 fn test_parser_rejects_distinct_on_scalar() {
1938 let query = "RETURN toLower(DISTINCT p.name)";
1940 let result = parse_cypher_query(query);
1941 assert!(
1942 result.is_err(),
1943 "Parser should reject DISTINCT on scalar functions"
1944 );
1945
1946 let query2 = "RETURN upper(DISTINCT p.name)";
1947 let result2 = parse_cypher_query(query2);
1948 assert!(
1949 result2.is_err(),
1950 "Parser should reject DISTINCT on scalar functions"
1951 );
1952 }
1953
1954 #[test]
1955 fn test_parse_like_pattern() {
1956 let query = "MATCH (n:Person) WHERE n.name LIKE 'A%' RETURN n.name";
1957 let result = parse_cypher_query(query);
1958 assert!(result.is_ok(), "LIKE pattern should parse successfully");
1959
1960 let ast = result.unwrap();
1961 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1962
1963 match where_clause.expression {
1964 BooleanExpression::Like {
1965 expression,
1966 pattern,
1967 } => {
1968 match expression {
1969 ValueExpression::Property(prop) => {
1970 assert_eq!(prop.variable, "n");
1971 assert_eq!(prop.property, "name");
1972 }
1973 _ => panic!("Expected property expression"),
1974 }
1975 assert_eq!(pattern, "A%");
1976 }
1977 _ => panic!("Expected LIKE expression"),
1978 }
1979 }
1980
1981 #[test]
1982 fn test_parse_like_with_double_quotes() {
1983 let query = r#"MATCH (n:Person) WHERE n.email LIKE "%@example.com" RETURN n.email"#;
1984 let result = parse_cypher_query(query);
1985 assert!(result.is_ok(), "LIKE with double quotes should parse");
1986
1987 let ast = result.unwrap();
1988 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1989
1990 match where_clause.expression {
1991 BooleanExpression::Like { pattern, .. } => {
1992 assert_eq!(pattern, "%@example.com");
1993 }
1994 _ => panic!("Expected LIKE expression"),
1995 }
1996 }
1997
1998 #[test]
1999 fn test_parse_like_in_complex_where() {
2000 let query = "MATCH (n:Person) WHERE n.age > 20 AND n.name LIKE 'J%' RETURN n.name";
2001 let result = parse_cypher_query(query);
2002 assert!(result.is_ok(), "LIKE in complex WHERE should parse");
2003
2004 let ast = result.unwrap();
2005 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2006
2007 match where_clause.expression {
2008 BooleanExpression::And(left, right) => {
2009 match *left {
2011 BooleanExpression::Comparison { .. } => {}
2012 _ => panic!("Expected comparison on left"),
2013 }
2014 match *right {
2016 BooleanExpression::Like { pattern, .. } => {
2017 assert_eq!(pattern, "J%");
2018 }
2019 _ => panic!("Expected LIKE expression on right"),
2020 }
2021 }
2022 _ => panic!("Expected AND expression"),
2023 }
2024 }
2025
2026 #[test]
2027 fn test_parse_contains() {
2028 let query = "MATCH (n:Person) WHERE n.name CONTAINS 'Jo' RETURN n.name";
2029 let result = parse_cypher_query(query);
2030 assert!(result.is_ok());
2031
2032 let query = result.unwrap();
2033 assert!(query.where_clause.is_some());
2034
2035 match &query.where_clause.unwrap().expression {
2036 BooleanExpression::Contains {
2037 expression,
2038 substring,
2039 } => {
2040 assert_eq!(substring, "Jo");
2041 match expression {
2042 ValueExpression::Property(prop) => {
2043 assert_eq!(prop.variable, "n");
2044 assert_eq!(prop.property, "name");
2045 }
2046 _ => panic!("Expected property reference"),
2047 }
2048 }
2049 _ => panic!("Expected CONTAINS expression"),
2050 }
2051 }
2052
2053 #[test]
2054 fn test_parse_starts_with() {
2055 let query = "MATCH (n:Person) WHERE n.name STARTS WITH 'Alice' RETURN n.name";
2056 let result = parse_cypher_query(query);
2057 assert!(result.is_ok());
2058
2059 let query = result.unwrap();
2060 assert!(query.where_clause.is_some());
2061
2062 match &query.where_clause.unwrap().expression {
2063 BooleanExpression::StartsWith { expression, prefix } => {
2064 assert_eq!(prefix, "Alice");
2065 match expression {
2066 ValueExpression::Property(prop) => {
2067 assert_eq!(prop.variable, "n");
2068 assert_eq!(prop.property, "name");
2069 }
2070 _ => panic!("Expected property reference"),
2071 }
2072 }
2073 _ => panic!("Expected STARTS WITH expression"),
2074 }
2075 }
2076
2077 #[test]
2078 fn test_parse_ends_with() {
2079 let query = "MATCH (n:Person) WHERE n.email ENDS WITH '@example.com' RETURN n.email";
2080 let result = parse_cypher_query(query);
2081 assert!(result.is_ok());
2082
2083 let query = result.unwrap();
2084 assert!(query.where_clause.is_some());
2085
2086 match &query.where_clause.unwrap().expression {
2087 BooleanExpression::EndsWith { expression, suffix } => {
2088 assert_eq!(suffix, "@example.com");
2089 match expression {
2090 ValueExpression::Property(prop) => {
2091 assert_eq!(prop.variable, "n");
2092 assert_eq!(prop.property, "email");
2093 }
2094 _ => panic!("Expected property reference"),
2095 }
2096 }
2097 _ => panic!("Expected ENDS WITH expression"),
2098 }
2099 }
2100
2101 #[test]
2102 fn test_parse_contains_case_insensitive_keyword() {
2103 let query = "MATCH (n:Person) WHERE n.name contains 'test' RETURN n.name";
2104 let result = parse_cypher_query(query);
2105 assert!(result.is_ok());
2106
2107 match &result.unwrap().where_clause.unwrap().expression {
2108 BooleanExpression::Contains { substring, .. } => {
2109 assert_eq!(substring, "test");
2110 }
2111 _ => panic!("Expected CONTAINS expression"),
2112 }
2113 }
2114
2115 #[test]
2116 fn test_parse_string_operators_in_complex_where() {
2117 let query =
2118 "MATCH (n:Person) WHERE n.name CONTAINS 'Jo' AND n.email ENDS WITH '.com' RETURN n";
2119 let result = parse_cypher_query(query);
2120 assert!(result.is_ok());
2121
2122 match &result.unwrap().where_clause.unwrap().expression {
2123 BooleanExpression::And(left, right) => {
2124 match **left {
2126 BooleanExpression::Contains { ref substring, .. } => {
2127 assert_eq!(substring, "Jo");
2128 }
2129 _ => panic!("Expected CONTAINS expression on left"),
2130 }
2131 match **right {
2133 BooleanExpression::EndsWith { ref suffix, .. } => {
2134 assert_eq!(suffix, ".com");
2135 }
2136 _ => panic!("Expected ENDS WITH expression on right"),
2137 }
2138 }
2139 _ => panic!("Expected AND expression"),
2140 }
2141 }
2142
2143 #[test]
2144 fn test_parse_ilike_pattern() {
2145 let query = "MATCH (n:Person) WHERE n.name ILIKE 'alice%' RETURN n.name";
2146 let result = parse_cypher_query(query);
2147 assert!(result.is_ok(), "ILIKE pattern should parse successfully");
2148
2149 let ast = result.unwrap();
2150 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2151
2152 match where_clause.expression {
2153 BooleanExpression::ILike {
2154 expression,
2155 pattern,
2156 } => {
2157 match expression {
2158 ValueExpression::Property(prop) => {
2159 assert_eq!(prop.variable, "n");
2160 assert_eq!(prop.property, "name");
2161 }
2162 _ => panic!("Expected property expression"),
2163 }
2164 assert_eq!(pattern, "alice%");
2165 }
2166 _ => panic!("Expected ILIKE expression"),
2167 }
2168 }
2169
2170 #[test]
2171 fn test_parse_like_and_ilike_together() {
2172 let query =
2173 "MATCH (n:Person) WHERE n.name LIKE 'Alice%' OR n.name ILIKE 'bob%' RETURN n.name";
2174 let result = parse_cypher_query(query);
2175 assert!(result.is_ok(), "LIKE and ILIKE together should parse");
2176
2177 let ast = result.unwrap();
2178 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2179
2180 match where_clause.expression {
2181 BooleanExpression::Or(left, right) => {
2182 match *left {
2184 BooleanExpression::Like { pattern, .. } => {
2185 assert_eq!(pattern, "Alice%");
2186 }
2187 _ => panic!("Expected LIKE expression on left"),
2188 }
2189 match *right {
2191 BooleanExpression::ILike { pattern, .. } => {
2192 assert_eq!(pattern, "bob%");
2193 }
2194 _ => panic!("Expected ILIKE expression on right"),
2195 }
2196 }
2197 _ => panic!("Expected OR expression"),
2198 }
2199 }
2200
2201 #[test]
2202 fn test_parse_vector_distance() {
2203 let query = "MATCH (p:Person) WHERE vector_distance(p.embedding, $query_vec, cosine) < 0.5 RETURN p.name";
2204 let result = parse_cypher_query(query);
2205 assert!(result.is_ok(), "vector_distance should parse successfully");
2206
2207 let ast = result.unwrap();
2208 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2209
2210 match where_clause.expression {
2212 BooleanExpression::Comparison { left, operator, .. } => {
2213 match left {
2214 ValueExpression::VectorDistance {
2215 left,
2216 right,
2217 metric,
2218 } => {
2219 assert_eq!(metric, DistanceMetric::Cosine);
2220 assert!(matches!(*left, ValueExpression::Property(_)));
2222 assert!(matches!(*right, ValueExpression::Parameter(_)));
2224 }
2225 _ => panic!("Expected VectorDistance"),
2226 }
2227 assert_eq!(operator, ComparisonOperator::LessThan);
2228 }
2229 _ => panic!("Expected comparison"),
2230 }
2231 }
2232
2233 #[test]
2234 fn test_parse_vector_similarity() {
2235 let query =
2236 "MATCH (p:Person) WHERE vector_similarity(p.embedding, $vec, l2) > 0.8 RETURN p";
2237 let result = parse_cypher_query(query);
2238 assert!(
2239 result.is_ok(),
2240 "vector_similarity should parse successfully"
2241 );
2242
2243 let ast = result.unwrap();
2244 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2245
2246 match where_clause.expression {
2247 BooleanExpression::Comparison { left, operator, .. } => {
2248 match left {
2249 ValueExpression::VectorSimilarity { metric, .. } => {
2250 assert_eq!(metric, DistanceMetric::L2);
2251 }
2252 _ => panic!("Expected VectorSimilarity"),
2253 }
2254 assert_eq!(operator, ComparisonOperator::GreaterThan);
2255 }
2256 _ => panic!("Expected comparison"),
2257 }
2258 }
2259
2260 #[test]
2261 fn test_parse_parameter() {
2262 let query = "MATCH (p:Person) WHERE p.age = $min_age RETURN p";
2263 let result = parse_cypher_query(query);
2264 assert!(result.is_ok(), "Parameter should parse successfully");
2265
2266 let ast = result.unwrap();
2267 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2268
2269 match where_clause.expression {
2270 BooleanExpression::Comparison { right, .. } => match right {
2271 ValueExpression::Parameter(name) => {
2272 assert_eq!(name, "min_age");
2273 }
2274 _ => panic!("Expected Parameter"),
2275 },
2276 _ => panic!("Expected comparison"),
2277 }
2278 }
2279
2280 #[test]
2281 fn test_parse_multiple_parameters() {
2282 let query = "MATCH (p:Person) WHERE p.age > $min_age AND p.age < $max_age RETURN p";
2283 let result = parse_cypher_query(query);
2284 assert!(
2285 result.is_ok(),
2286 "Multiple parameters should parse successfully"
2287 );
2288
2289 let ast = result.unwrap();
2290 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2291
2292 match where_clause.expression {
2293 BooleanExpression::And(left, right) => {
2294 match *left {
2296 BooleanExpression::Comparison {
2297 right: val_right, ..
2298 } => match val_right {
2299 ValueExpression::Parameter(name) => {
2300 assert_eq!(name, "min_age");
2301 }
2302 _ => panic!("Expected Parameter min_age"),
2303 },
2304 _ => panic!("Expected comparison on left"),
2305 }
2306
2307 match *right {
2309 BooleanExpression::Comparison {
2310 right: val_right, ..
2311 } => match val_right {
2312 ValueExpression::Parameter(name) => {
2313 assert_eq!(name, "max_age");
2314 }
2315 _ => panic!("Expected Parameter max_age"),
2316 },
2317 _ => panic!("Expected comparison on right"),
2318 }
2319 }
2320 _ => panic!("Expected AND expression"),
2321 }
2322 }
2323
2324 #[test]
2325 fn test_parse_parameter_formats() {
2326 let query = "MATCH (p:Person) WHERE p.age > $min_age RETURN p";
2328 let result = parse_cypher_query(query);
2329 assert!(result.is_ok(), "$param should parse successfully");
2330 }
2331
2332 #[test]
2333 fn test_vector_distance_metrics() {
2334 for metric in &["cosine", "l2", "dot"] {
2335 let query = format!(
2336 "MATCH (p:Person) RETURN vector_distance(p.emb, $v, {}) AS dist",
2337 metric
2338 );
2339 let result = parse_cypher_query(&query);
2340 assert!(result.is_ok(), "Failed to parse metric: {}", metric);
2341
2342 let ast = result.unwrap();
2343 let return_item = &ast.return_clause.items[0];
2344
2345 match &return_item.expression {
2346 ValueExpression::VectorDistance {
2347 metric: parsed_metric,
2348 ..
2349 } => {
2350 let expected = match *metric {
2351 "cosine" => DistanceMetric::Cosine,
2352 "l2" => DistanceMetric::L2,
2353 "dot" => DistanceMetric::Dot,
2354 _ => panic!("Unexpected metric"),
2355 };
2356 assert_eq!(*parsed_metric, expected);
2357 }
2358 _ => panic!("Expected VectorDistance"),
2359 }
2360 }
2361 }
2362
2363 #[test]
2364 fn test_vector_search_in_order_by() {
2365 let query = "MATCH (p:Person) RETURN p.name ORDER BY vector_distance(p.embedding, $query_vec, cosine) ASC LIMIT 10";
2366 let result = parse_cypher_query(query);
2367 assert!(result.is_ok(), "vector_distance in ORDER BY should parse");
2368
2369 let ast = result.unwrap();
2370 let order_by = ast.order_by.expect("Expected ORDER BY clause");
2371
2372 assert_eq!(order_by.items.len(), 1);
2373 match &order_by.items[0].expression {
2374 ValueExpression::VectorDistance { .. } => {
2375 }
2377 _ => panic!("Expected VectorDistance in ORDER BY"),
2378 }
2379 }
2380
2381 #[test]
2382 fn test_hybrid_query_with_vector_and_property_filters() {
2383 let query = "MATCH (p:Person) WHERE p.age > 25 AND vector_similarity(p.embedding, $query_vec, cosine) > 0.7 RETURN p.name";
2384 let result = parse_cypher_query(query);
2385 assert!(result.is_ok(), "Hybrid query should parse");
2386
2387 let ast = result.unwrap();
2388 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2389
2390 match where_clause.expression {
2392 BooleanExpression::And(left, right) => {
2393 match *left {
2395 BooleanExpression::Comparison { .. } => {}
2396 _ => panic!("Expected comparison on left"),
2397 }
2398 match *right {
2400 BooleanExpression::Comparison { left, .. } => match left {
2401 ValueExpression::VectorSimilarity { .. } => {}
2402 _ => panic!("Expected VectorSimilarity"),
2403 },
2404 _ => panic!("Expected comparison on right"),
2405 }
2406 }
2407 _ => panic!("Expected AND expression"),
2408 }
2409 }
2410
2411 #[test]
2412 fn unions_preserve_modes_and_quoted_keywords() {
2413 let query = parse_cypher_query(
2414 "RETURN 'UNION' AS n UNION ALL RETURN 'ALL' AS n UNION RETURN 'UNION ALL' AS n",
2415 )
2416 .unwrap();
2417 assert_eq!(query.union_branches.len(), 2);
2418 assert!(query.union_branches[0].all);
2419 assert!(!query.union_branches[1].all);
2420 assert!(parse_cypher_query("RETURN 1 AS n UNION ALL").is_err());
2421 }
2422
2423 #[test]
2424 fn pagination_accepts_parameters_without_losing_names() {
2425 let query = parse_cypher_query("RETURN 1 AS n SKIP $offset LIMIT $count").unwrap();
2426 assert_eq!(query.skip, Some(RowCount::Parameter("offset".to_string())));
2427 assert_eq!(query.limit, Some(RowCount::Parameter("count".to_string())));
2428 assert!(parse_cypher_query("RETURN 1 LIMIT -1").is_err());
2429 assert!(parse_cypher_query("RETURN 1 LIMIT 1.5").is_err());
2430 }
2431
2432 #[test]
2433 fn test_integer_lists_preserve_precision() {
2434 for literal in ["[1, 2, 3]", "[-19791209300775, 16777217]", "[]"] {
2435 assert!(parse_vector_literal(literal).is_err());
2436 let (remaining, expression) = value_expression(literal).unwrap();
2437 assert!(remaining.is_empty());
2438 let ValueExpression::ScalarFunction { name, args } = expression else {
2439 panic!("expected an ordinary list");
2440 };
2441 assert_eq!(name, "__list");
2442 assert!(args
2443 .iter()
2444 .all(|v| matches!(v, ValueExpression::Literal(PropertyValue::Integer(_)))));
2445 }
2446 let (_, expression) = value_expression("[19791209300775]").unwrap();
2447 assert_eq!(
2448 expression,
2449 ValueExpression::ScalarFunction {
2450 name: "__list".to_string(),
2451 args: vec![ValueExpression::Literal(PropertyValue::Integer(
2452 19791209300775
2453 ))],
2454 }
2455 );
2456 for literal in ["[1, 2.0]", "[1e-3, 2]", "[-1.0, 2]"] {
2457 assert!(matches!(
2458 value_expression(literal).unwrap().1,
2459 ValueExpression::VectorLiteral(_)
2460 ));
2461 }
2462 }
2463
2464 #[test]
2465 fn test_parse_vector_literal() {
2466 let result = parse_vector_literal("[0.1, 0.2, 0.3]");
2467 assert!(result.is_ok());
2468 let (_, expr) = result.unwrap();
2469 match expr {
2470 ValueExpression::VectorLiteral(vec) => {
2471 assert_eq!(vec.len(), 3);
2472 assert_eq!(vec[0], 0.1);
2473 assert_eq!(vec[1], 0.2);
2474 assert_eq!(vec[2], 0.3);
2475 }
2476 _ => panic!("Expected VectorLiteral"),
2477 }
2478 }
2479
2480 #[test]
2481 fn test_parse_vector_literal_with_negative_values() {
2482 let result = parse_vector_literal("[-0.1, 0.2, -0.3]");
2483 assert!(result.is_ok());
2484 let (_, expr) = result.unwrap();
2485 match expr {
2486 ValueExpression::VectorLiteral(vec) => {
2487 assert_eq!(vec.len(), 3);
2488 assert_eq!(vec[0], -0.1);
2489 assert_eq!(vec[2], -0.3);
2490 }
2491 _ => panic!("Expected VectorLiteral"),
2492 }
2493 }
2494
2495 #[test]
2496 fn test_parse_vector_literal_scientific_notation() {
2497 let result = parse_vector_literal("[1e-3, 2.5e2, -3e-1]");
2498 assert!(result.is_ok());
2499 let (_, expr) = result.unwrap();
2500 match expr {
2501 ValueExpression::VectorLiteral(vec) => {
2502 assert_eq!(vec.len(), 3);
2503 assert!((vec[0] - 0.001).abs() < 1e-6);
2504 assert!((vec[1] - 250.0).abs() < 1e-6);
2505 assert!((vec[2] - (-0.3)).abs() < 1e-6);
2506 }
2507 _ => panic!("Expected VectorLiteral"),
2508 }
2509 }
2510
2511 #[test]
2512 fn test_vector_distance_with_literal() {
2513 let query =
2514 "MATCH (p:Person) WHERE vector_distance(p.embedding, [0.1, 0.2], l2) < 0.5 RETURN p";
2515 let result = parse_cypher_query(query);
2516 assert!(result.is_ok());
2517
2518 let ast = result.unwrap();
2519 let where_clause = ast.where_clause.expect("Expected WHERE clause");
2520
2521 match where_clause.expression {
2522 BooleanExpression::Comparison { left, operator, .. } => {
2523 match left {
2524 ValueExpression::VectorDistance {
2525 left,
2526 right,
2527 metric,
2528 } => {
2529 assert!(matches!(*left, ValueExpression::Property(_)));
2531 match *right {
2533 ValueExpression::VectorLiteral(vec) => {
2534 assert_eq!(vec.len(), 2);
2535 assert_eq!(vec[0], 0.1);
2536 assert_eq!(vec[1], 0.2);
2537 }
2538 _ => panic!("Expected VectorLiteral"),
2539 }
2540 assert_eq!(metric, DistanceMetric::L2);
2541 }
2542 _ => panic!("Expected VectorDistance"),
2543 }
2544 assert_eq!(operator, ComparisonOperator::LessThan);
2545 }
2546 _ => panic!("Expected comparison"),
2547 }
2548 }
2549
2550 #[test]
2552 fn test_parse_unwind_simple() {
2553 let query = "UNWIND [1, 2, 3] AS num RETURN num";
2554 let ast = parse_cypher_query(query);
2555 assert!(ast.is_ok(), "Failed to parse simple UNWIND query");
2556 }
2557
2558 #[test]
2559 fn test_parse_unwind_after_match() {
2560 let query = "MATCH (n) UNWIND n.list AS item RETURN item";
2561 let ast = parse_cypher_query(query);
2562 assert!(ast.is_ok(), "Failed to parse UNWIND after MATCH");
2563 }
2564
2565 #[test]
2566 fn out_of_range_numbers_are_parse_errors_not_panics() {
2567 for q in [
2568 "MATCH (n:Person) RETURN n LIMIT 99999999999999999999",
2569 "MATCH (n:Person) RETURN n LIMIT -1",
2570 "MATCH (n:Person) RETURN n SKIP -1",
2571 "MATCH (n:Person) WHERE n.age > 99999999999999999999 RETURN n",
2572 ] {
2573 assert!(parse_cypher_query(q).is_err(), "{q}");
2574 }
2575 let huge = format!(
2576 "MATCH (n:Person) WHERE n.x > {}.0 RETURN n",
2577 "9".repeat(400)
2578 );
2579 assert!(parse_cypher_query(&huge).is_err());
2580 }
2581
2582 #[test]
2583 fn relationship_type_alternation() {
2584 for q in [
2585 "MATCH (a)-[:A|B]->(b) RETURN b",
2586 "MATCH (a)-[:A|:B]->(b) RETURN b",
2587 "MATCH (a)-[r:A | B *1..2]->(b) RETURN b",
2588 "MATCH (a)-[:A:B]->(b) RETURN b",
2589 ] {
2590 let query = parse_cypher_query(q).unwrap_or_else(|e| panic!("{q}: {e}"));
2591 let ReadingClause::Match(m) = &query.reading_clauses[0] else {
2592 panic!("{q}: not a MATCH");
2593 };
2594 let GraphPattern::Path(path) = &m.patterns[0] else {
2595 panic!("{q}: not a path");
2596 };
2597 assert_eq!(path.segments[0].relationship.types, vec!["A", "B"], "{q}");
2598 }
2599 }
2600
2601 #[test]
2602 fn chained_with_stages_and_with_distinct() {
2603 let q = "MATCH (a)-[:R]->(b) WITH DISTINCT b WHERE b.x > 1 \
2604 MATCH (b)-[:R]->(c) WITH c, count(*) AS n WITH c WHERE n > 2 RETURN c";
2605 let query = parse_cypher_query(q).unwrap();
2606 let first = query.with_clause.as_ref().unwrap();
2607 assert!(first.distinct && first.where_clause.is_some());
2608 assert_eq!(query.post_with_reading_clauses.len(), 1);
2609 assert_eq!(query.further_with_stages.len(), 2);
2610 let [second, third] = &query.further_with_stages[..] else {
2611 unreachable!()
2612 };
2613 assert!(!second.with_clause.distinct && second.reading_clauses.is_empty());
2614 assert!(third.with_clause.where_clause.is_some());
2615 }
2616
2617 #[test]
2618 fn optional_match_owns_its_where_and_inner_where_attaches_to_match() {
2619 let q = "MATCH (a) WHERE a.x > 1 OPTIONAL MATCH (a)-[:R]->(b) WHERE b.y = 2 \
2620 MATCH (a)-[:S]->(c) WHERE c.z = 3 RETURN a";
2621 let query = parse_cypher_query(q).unwrap();
2622 let clauses: Vec<&MatchClause> = query
2623 .reading_clauses
2624 .iter()
2625 .map(|c| match c {
2626 ReadingClause::Match(m) => m,
2627 ReadingClause::Unwind(_) | ReadingClause::Call(_) => unreachable!(),
2628 })
2629 .collect();
2630 assert_eq!(clauses.len(), 3);
2631 assert!(!clauses[0].optional && clauses[0].where_clause.is_some());
2632 assert!(clauses[1].optional && clauses[1].where_clause.is_some());
2633 assert!(clauses[2].where_clause.is_none() && query.where_clause.is_some());
2635 }
2636
2637 #[test]
2638 fn null_literal_any_case_but_not_a_name_prefix() {
2639 for q in [
2640 "MATCH (n) RETURN NULL AS x",
2641 "MATCH (n) RETURN null AS x",
2642 "MATCH (n) RETURN Null AS x",
2643 ] {
2644 let query = parse_cypher_query(q).unwrap();
2645 assert_eq!(
2646 query.return_clause.items[0].expression,
2647 ValueExpression::Literal(PropertyValue::Null),
2648 "{q}"
2649 );
2650 }
2651 let query = parse_cypher_query("MATCH (n) WITH 1 AS nullCount RETURN nullCount").unwrap();
2652 assert_eq!(
2653 query.return_clause.items[0].expression,
2654 ValueExpression::Variable("nullCount".into())
2655 );
2656 }
2657 #[test]
2658 fn reduce_keeps_inputs_in_outer_scope_and_marks_local_properties() {
2659 let q =
2660 parse_cypher_query("WITH {n:1} AS x RETURN reduce(s=x.n, x IN [x] | s+x.n)").unwrap();
2661 let ValueExpression::Reduce {
2662 initial,
2663 list,
2664 expression,
2665 ..
2666 } = &q.return_clause.items[0].expression
2667 else {
2668 panic!("expected reduce");
2669 };
2670 assert!(matches!(initial.as_ref(), ValueExpression::Property(p) if p.variable == "x"));
2671 let mut input_locals = 0;
2672 transform_value(&mut list.as_ref().clone(), &mut |v| {
2673 input_locals += usize::from(matches!(v, ValueExpression::LocalVariable(_)))
2674 });
2675 assert_eq!(input_locals, 0);
2676 let mut body_locals = Vec::new();
2677 transform_value(&mut expression.as_ref().clone(), &mut |v| {
2678 if let ValueExpression::LocalVariable(n) = v {
2679 body_locals.push(n.clone());
2680 }
2681 });
2682 assert_eq!(body_locals, ["s", "x"]);
2683 }
2684}