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