1use crate::ast::*;
10use crate::error::{GraphError, Result};
11use nom::{
12 branch::alt,
13 bytes::complete::{tag, tag_no_case, take_while1},
14 character::complete::{char, digit0, digit1, multispace0, multispace1, one_of},
15 combinator::{map, map_res, opt, peek, recognize},
16 multi::{many0, separated_list0, separated_list1},
17 sequence::{delimited, pair, preceded, 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 Ok(query)
39}
40
41fn cypher_query(input: &str) -> IResult<&str, CypherQuery> {
43 let (input, _) = multispace0(input)?;
44 let (input, match_clauses) = many0(match_clause)(input)?;
45 let (input, where_clause) = opt(where_clause)(input)?;
46 let (input, return_clause) = return_clause(input)?;
47 let (input, order_by) = opt(order_by_clause)(input)?;
48 let (input, (skip, limit)) = pagination_clauses(input)?;
49 let (input, _) = multispace0(input)?;
50
51 Ok((
52 input,
53 CypherQuery {
54 match_clauses,
55 where_clause,
56 return_clause,
57 limit,
58 order_by,
59 skip,
60 },
61 ))
62}
63
64fn match_clause(input: &str) -> IResult<&str, MatchClause> {
66 let (input, _) = multispace0(input)?;
67 let (input, _) = tag_no_case("MATCH")(input)?;
68 let (input, _) = multispace1(input)?;
69 let (input, patterns) = separated_list0(comma_ws, graph_pattern)(input)?;
70
71 Ok((input, MatchClause { patterns }))
72}
73
74fn graph_pattern(input: &str) -> IResult<&str, GraphPattern> {
76 alt((
77 map(path_pattern, GraphPattern::Path),
78 map(node_pattern, GraphPattern::Node),
79 ))(input)
80}
81
82fn path_pattern(input: &str) -> IResult<&str, PathPattern> {
84 let (input, start_node) = node_pattern(input)?;
85 let (input, segments) = many0(path_segment)(input)?;
86
87 if segments.is_empty() {
89 return Err(nom::Err::Error(nom::error::Error::new(
90 input,
91 nom::error::ErrorKind::Tag,
92 )));
93 }
94
95 Ok((
96 input,
97 PathPattern {
98 start_node,
99 segments,
100 },
101 ))
102}
103
104fn path_segment(input: &str) -> IResult<&str, PathSegment> {
106 let (input, relationship) = relationship_pattern(input)?;
107 let (input, end_node) = node_pattern(input)?;
108
109 Ok((
110 input,
111 PathSegment {
112 relationship,
113 end_node,
114 },
115 ))
116}
117
118fn node_pattern(input: &str) -> IResult<&str, NodePattern> {
120 let (input, _) = multispace0(input)?;
121 let (input, _) = char('(')(input)?;
122 let (input, _) = multispace0(input)?;
123 let (input, variable) = opt(identifier)(input)?;
124 let (input, labels) = many0(preceded(char(':'), identifier))(input)?;
125 let (input, _) = multispace0(input)?;
126 let (input, properties) = opt(property_map)(input)?;
127 let (input, _) = multispace0(input)?;
128 let (input, _) = char(')')(input)?;
129
130 Ok((
131 input,
132 NodePattern {
133 variable: variable.map(|s| s.to_string()),
134 labels: labels.into_iter().map(|s| s.to_string()).collect(),
135 properties: properties.unwrap_or_default(),
136 },
137 ))
138}
139
140fn relationship_pattern(input: &str) -> IResult<&str, RelationshipPattern> {
142 let (input, _) = multispace0(input)?;
143
144 let (input, (direction, content)) = alt((
146 map(
148 tuple((
149 char('-'),
150 delimited(char('['), relationship_content, char(']')),
151 tag("->"),
152 )),
153 |(_, content, _)| (RelationshipDirection::Outgoing, content),
154 ),
155 map(
157 tuple((
158 tag("<-"),
159 delimited(char('['), relationship_content, char(']')),
160 char('-'),
161 )),
162 |(_, content, _)| (RelationshipDirection::Incoming, content),
163 ),
164 map(
166 tuple((
167 char('-'),
168 delimited(char('['), relationship_content, char(']')),
169 char('-'),
170 )),
171 |(_, content, _)| (RelationshipDirection::Undirected, content),
172 ),
173 ))(input)?;
174
175 let (variable, types, properties, length) = content;
176
177 Ok((
178 input,
179 RelationshipPattern {
180 variable: variable.map(|s| s.to_string()),
181 types: types.into_iter().map(|s| s.to_string()).collect(),
182 direction,
183 properties: properties.unwrap_or_default(),
184 length,
185 },
186 ))
187}
188
189type RelationshipContentResult<'a> = (
191 Option<&'a str>,
192 Vec<&'a str>,
193 Option<HashMap<String, PropertyValue>>,
194 Option<LengthRange>,
195);
196
197fn relationship_content(input: &str) -> IResult<&str, RelationshipContentResult<'_>> {
199 let (input, _) = multispace0(input)?;
200 let (input, variable) = opt(identifier)(input)?;
201 let (input, types) = many0(preceded(char(':'), identifier))(input)?;
202 let (input, _) = multispace0(input)?;
203 let (input, length) = opt(length_range)(input)?;
204 let (input, _) = multispace0(input)?;
205 let (input, properties) = opt(property_map)(input)?;
206 let (input, _) = multispace0(input)?;
207
208 Ok((input, (variable, types, properties, length)))
209}
210
211fn property_map(input: &str) -> IResult<&str, HashMap<String, PropertyValue>> {
213 let (input, _) = multispace0(input)?;
214 let (input, _) = char('{')(input)?;
215 let (input, _) = multispace0(input)?;
216 let (input, pairs) = separated_list0(comma_ws, property_pair)(input)?;
217 let (input, _) = multispace0(input)?;
218 let (input, _) = char('}')(input)?;
219
220 Ok((input, pairs.into_iter().collect()))
221}
222
223fn property_pair(input: &str) -> IResult<&str, (String, PropertyValue)> {
225 let (input, _) = multispace0(input)?;
226 let (input, key) = identifier(input)?;
227 let (input, _) = multispace0(input)?;
228 let (input, _) = char(':')(input)?;
229 let (input, _) = multispace0(input)?;
230 let (input, value) = property_value(input)?;
231
232 Ok((input, (key.to_string(), value)))
233}
234
235fn property_value(input: &str) -> IResult<&str, PropertyValue> {
237 alt((
238 map(string_literal, PropertyValue::String),
239 map(float_literal, PropertyValue::Float), map(integer_literal, PropertyValue::Integer),
241 map(boolean_literal, PropertyValue::Boolean),
242 map(tag("null"), |_| PropertyValue::Null),
243 map(parameter, PropertyValue::Parameter),
244 ))(input)
245}
246
247fn where_clause(input: &str) -> IResult<&str, WhereClause> {
249 let (input, _) = multispace0(input)?;
250 let (input, _) = tag_no_case("WHERE")(input)?;
251 let (input, _) = multispace1(input)?;
252 let (input, expression) = boolean_expression(input)?;
253
254 Ok((input, WhereClause { expression }))
255}
256
257fn boolean_expression(input: &str) -> IResult<&str, BooleanExpression> {
259 boolean_or_expression(input)
260}
261
262fn boolean_or_expression(input: &str) -> IResult<&str, BooleanExpression> {
263 let (input, first) = boolean_and_expression(input)?;
264 let (input, rest) = many0(preceded(
265 tuple((multispace0, tag_no_case("OR"), multispace1)),
266 boolean_and_expression,
267 ))(input)?;
268 let expr = rest.into_iter().fold(first, |acc, item| {
269 BooleanExpression::Or(Box::new(acc), Box::new(item))
270 });
271 Ok((input, expr))
272}
273
274fn boolean_and_expression(input: &str) -> IResult<&str, BooleanExpression> {
275 let (input, first) = boolean_not_expression(input)?;
276 let (input, rest) = many0(preceded(
277 tuple((multispace0, tag_no_case("AND"), multispace1)),
278 boolean_not_expression,
279 ))(input)?;
280 let expr = rest.into_iter().fold(first, |acc, item| {
281 BooleanExpression::And(Box::new(acc), Box::new(item))
282 });
283 Ok((input, expr))
284}
285
286fn boolean_not_expression(input: &str) -> IResult<&str, BooleanExpression> {
287 let (input, _) = multispace0(input)?;
288 alt((
289 map(
290 preceded(
291 tuple((tag_no_case("NOT"), multispace1)),
292 boolean_not_expression,
293 ),
294 |expr| BooleanExpression::Not(Box::new(expr)),
295 ),
296 boolean_primary_expression,
297 ))(input)
298}
299
300fn boolean_primary_expression(input: &str) -> IResult<&str, BooleanExpression> {
301 let (input, _) = multispace0(input)?;
302 alt((
303 map(
304 delimited(
305 tuple((char('('), multispace0)),
306 boolean_expression,
307 tuple((multispace0, char(')'))),
308 ),
309 |expr| expr,
310 ),
311 comparison_expression,
312 ))(input)
313}
314
315fn comparison_expression(input: &str) -> IResult<&str, BooleanExpression> {
316 let (input, _) = multispace0(input)?;
317 let (input, left) = value_expression(input)?;
318 let (input, _) = multispace0(input)?;
319 let left_clone = left.clone();
320
321 if let Ok((input_after_in, (_, _, list))) =
322 tuple((tag_no_case("IN"), multispace0, value_expression_list))(input)
323 {
324 return Ok((
325 input_after_in,
326 BooleanExpression::In {
327 expression: left,
328 list,
329 },
330 ));
331 }
332 if let Ok((input_after_like, (_, _, pattern))) =
334 tuple((tag_no_case("LIKE"), multispace0, string_literal))(input)
335 {
336 return Ok((
337 input_after_like,
338 BooleanExpression::Like {
339 expression: left,
340 pattern,
341 },
342 ));
343 }
344 if let Ok((input_after_ilike, (_, _, pattern))) =
346 tuple((tag_no_case("ILIKE"), multispace0, string_literal))(input)
347 {
348 return Ok((
349 input_after_ilike,
350 BooleanExpression::ILike {
351 expression: left,
352 pattern,
353 },
354 ));
355 }
356 if let Ok((input_after_contains, (_, _, substring))) =
358 tuple((tag_no_case("CONTAINS"), multispace0, string_literal))(input)
359 {
360 return Ok((
361 input_after_contains,
362 BooleanExpression::Contains {
363 expression: left,
364 substring,
365 },
366 ));
367 }
368 if let Ok((input_after_starts, (_, _, _, _, prefix))) = tuple((
370 tag_no_case("STARTS"),
371 multispace1,
372 tag_no_case("WITH"),
373 multispace0,
374 string_literal,
375 ))(input)
376 {
377 return Ok((
378 input_after_starts,
379 BooleanExpression::StartsWith {
380 expression: left,
381 prefix,
382 },
383 ));
384 }
385 if let Ok((input_after_ends, (_, _, _, _, suffix))) = tuple((
387 tag_no_case("ENDS"),
388 multispace1,
389 tag_no_case("WITH"),
390 multispace0,
391 string_literal,
392 ))(input)
393 {
394 return Ok((
395 input_after_ends,
396 BooleanExpression::EndsWith {
397 expression: left,
398 suffix,
399 },
400 ));
401 }
402 if let Ok((rest, ())) = is_null_comparison(input) {
404 return Ok((rest, BooleanExpression::IsNull(left_clone)));
405 }
406 if let Ok((rest, ())) = is_not_null_comparison(input) {
408 return Ok((rest, BooleanExpression::IsNotNull(left_clone)));
409 }
410
411 let (input, operator) = comparison_operator(input)?;
412 let (input, _) = multispace0(input)?;
413 let (input, right) = value_expression(input)?;
414
415 Ok((
416 input,
417 BooleanExpression::Comparison {
418 left: left_clone,
419 operator,
420 right,
421 },
422 ))
423}
424
425fn comparison_operator(input: &str) -> IResult<&str, ComparisonOperator> {
427 alt((
428 map(tag("="), |_| ComparisonOperator::Equal),
429 map(tag("<>"), |_| ComparisonOperator::NotEqual),
430 map(tag("!="), |_| ComparisonOperator::NotEqual),
431 map(tag("<="), |_| ComparisonOperator::LessThanOrEqual),
432 map(tag(">="), |_| ComparisonOperator::GreaterThanOrEqual),
433 map(tag("<"), |_| ComparisonOperator::LessThan),
434 map(tag(">"), |_| ComparisonOperator::GreaterThan),
435 ))(input)
436}
437
438fn basic_value_expression(input: &str) -> IResult<&str, ValueExpression> {
440 alt((
441 parse_vector_literal, parse_parameter, function_call, map(property_value, ValueExpression::Literal), map(property_reference, ValueExpression::Property),
446 map(identifier, |id| ValueExpression::Variable(id.to_string())),
447 ))(input)
448}
449
450fn value_expression(input: &str) -> IResult<&str, ValueExpression> {
453 if let Ok((_, first_ident)) = peek(identifier)(input) {
456 let ident_lower = first_ident.to_lowercase();
457
458 match ident_lower.as_str() {
459 "vector_distance" => return parse_vector_distance(input),
460 "vector_similarity" => return parse_vector_similarity(input),
461 _ => {} }
463 }
464
465 basic_value_expression(input)
467}
468
469fn parse_distance_metric(input: &str) -> IResult<&str, DistanceMetric> {
471 alt((
472 map(tag_no_case("cosine"), |_| DistanceMetric::Cosine),
473 map(tag_no_case("l2"), |_| DistanceMetric::L2),
474 map(tag_no_case("dot"), |_| DistanceMetric::Dot),
475 ))(input)
476}
477
478fn parse_vector_distance(input: &str) -> IResult<&str, ValueExpression> {
480 let (input, _) = tag_no_case("vector_distance")(input)?;
481 let (input, _) = multispace0(input)?;
482 let (input, _) = char('(')(input)?;
483 let (input, _) = multispace0(input)?;
484
485 let (input, left) = basic_value_expression(input)?;
487 let (input, _) = multispace0(input)?;
488 let (input, _) = char(',')(input)?;
489 let (input, _) = multispace0(input)?;
490
491 let (input, right) = basic_value_expression(input)?;
493 let (input, _) = multispace0(input)?;
494 let (input, _) = char(',')(input)?;
495 let (input, _) = multispace0(input)?;
496
497 let (input, metric) = parse_distance_metric(input)?;
499 let (input, _) = multispace0(input)?;
500 let (input, _) = char(')')(input)?;
501
502 Ok((
503 input,
504 ValueExpression::VectorDistance {
505 left: Box::new(left),
506 right: Box::new(right),
507 metric,
508 },
509 ))
510}
511
512fn parse_vector_similarity(input: &str) -> IResult<&str, ValueExpression> {
514 let (input, _) = tag_no_case("vector_similarity")(input)?;
515 let (input, _) = multispace0(input)?;
516 let (input, _) = char('(')(input)?;
517 let (input, _) = multispace0(input)?;
518
519 let (input, left) = basic_value_expression(input)?;
521 let (input, _) = multispace0(input)?;
522 let (input, _) = char(',')(input)?;
523 let (input, _) = multispace0(input)?;
524
525 let (input, right) = basic_value_expression(input)?;
527 let (input, _) = multispace0(input)?;
528 let (input, _) = char(',')(input)?;
529 let (input, _) = multispace0(input)?;
530
531 let (input, metric) = parse_distance_metric(input)?;
533 let (input, _) = multispace0(input)?;
534 let (input, _) = char(')')(input)?;
535
536 Ok((
537 input,
538 ValueExpression::VectorSimilarity {
539 left: Box::new(left),
540 right: Box::new(right),
541 metric,
542 },
543 ))
544}
545
546fn parse_parameter(input: &str) -> IResult<&str, ValueExpression> {
548 let (input, _) = char('$')(input)?;
549 let (input, name) = identifier(input)?;
550 Ok((input, ValueExpression::Parameter(name.to_string())))
551}
552
553fn function_call(input: &str) -> IResult<&str, ValueExpression> {
555 let (input, name) = identifier(input)?;
556 let (input, _) = multispace0(input)?;
557 let (input, _) = char('(')(input)?;
558 let (input, _) = multispace0(input)?;
559
560 if let Ok((input_after_star, _)) = char::<_, nom::error::Error<&str>>('*')(input) {
562 if name.to_lowercase() == "count" {
564 let (input, _) = multispace0(input_after_star)?;
565 let (input, _) = char(')')(input)?;
566 return Ok((
567 input,
568 ValueExpression::Function {
569 name: name.to_string(),
570 args: vec![ValueExpression::Variable("*".to_string())],
571 },
572 ));
573 } else {
574 }
577 }
578
579 let (input, args) = separated_list0(
581 tuple((multispace0, char(','), multispace0)),
582 value_expression,
583 )(input)?;
584 let (input, _) = multispace0(input)?;
585 let (input, _) = char(')')(input)?;
586
587 Ok((
588 input,
589 ValueExpression::Function {
590 name: name.to_string(),
591 args,
592 },
593 ))
594}
595
596fn value_expression_list(input: &str) -> IResult<&str, Vec<ValueExpression>> {
597 delimited(
598 tuple((char('['), multispace0)),
599 separated_list1(
600 tuple((multispace0, char(','), multispace0)),
601 value_expression,
602 ),
603 tuple((multispace0, char(']'))),
604 )(input)
605}
606
607fn float32_literal(input: &str) -> IResult<&str, f32> {
609 map_res(
610 recognize(tuple((
611 opt(char('-')),
612 alt((
613 recognize(tuple((
615 digit1,
616 opt(tuple((char('.'), digit0))),
617 one_of("eE"),
618 opt(one_of("+-")),
619 digit1,
620 ))),
621 recognize(tuple((digit1, opt(tuple((char('.'), digit0)))))),
623 )),
624 ))),
625 |s: &str| s.parse::<f32>(),
626 )(input)
627}
628
629fn parse_vector_literal(input: &str) -> IResult<&str, ValueExpression> {
631 let (input, _) = char('[')(input)?;
632 let (input, _) = multispace0(input)?;
633
634 let (input, values) = separated_list1(
635 tuple((multispace0, char(','), multispace0)),
636 float32_literal,
637 )(input)?;
638
639 let (input, _) = multispace0(input)?;
640 let (input, _) = char(']')(input)?;
641
642 Ok((input, ValueExpression::VectorLiteral(values)))
643}
644
645fn property_reference(input: &str) -> IResult<&str, PropertyRef> {
647 let (input, variable) = identifier(input)?;
648 let (input, _) = char('.')(input)?;
649 let (input, property) = identifier(input)?;
650
651 Ok((
652 input,
653 PropertyRef {
654 variable: variable.to_string(),
655 property: property.to_string(),
656 },
657 ))
658}
659
660fn return_clause(input: &str) -> IResult<&str, ReturnClause> {
662 let (input, _) = multispace0(input)?;
663 let (input, _) = tag_no_case("RETURN")(input)?;
664 let (input, _) = multispace1(input)?;
665 let (input, distinct) = opt(tag_no_case("DISTINCT"))(input)?;
666 let (input, _) = if distinct.is_some() {
667 multispace1(input)?
668 } else {
669 (input, "")
670 };
671 let (input, items) = separated_list0(comma_ws, return_item)(input)?;
672
673 Ok((
674 input,
675 ReturnClause {
676 distinct: distinct.is_some(),
677 items,
678 },
679 ))
680}
681
682fn return_item(input: &str) -> IResult<&str, ReturnItem> {
684 let (input, expression) = value_expression(input)?;
685 let (input, _) = multispace0(input)?;
686 let (input, alias) = opt(preceded(
687 tuple((tag_no_case("AS"), multispace1)),
688 identifier,
689 ))(input)?;
690
691 Ok((
692 input,
693 ReturnItem {
694 expression,
695 alias: alias.map(|s| s.to_string()),
696 },
697 ))
698}
699
700fn is_null_comparison(input: &str) -> IResult<&str, ()> {
702 let (input, _) = multispace0(input)?;
703 let (input, _) = tag_no_case("IS")(input)?;
704 let (input, _) = multispace1(input)?;
705 let (input, _) = tag_no_case("NULL")(input)?;
706 let (input, _) = multispace0(input)?;
707
708 Ok((input, ()))
709}
710
711fn is_not_null_comparison(input: &str) -> IResult<&str, ()> {
713 let (input, _) = multispace0(input)?;
714 let (input, _) = tag_no_case("IS")(input)?;
715 let (input, _) = multispace1(input)?;
716 let (input, _) = tag_no_case("NOT")(input)?;
717 let (input, _) = multispace1(input)?;
718 let (input, _) = tag_no_case("NULL")(input)?;
719 let (input, _) = multispace0(input)?;
720
721 Ok((input, ()))
722}
723
724fn order_by_clause(input: &str) -> IResult<&str, OrderByClause> {
726 let (input, _) = multispace0(input)?;
727 let (input, _) = tag_no_case("ORDER")(input)?;
728 let (input, _) = multispace1(input)?;
729 let (input, _) = tag_no_case("BY")(input)?;
730 let (input, _) = multispace1(input)?;
731 let (input, items) = separated_list0(comma_ws, order_by_item)(input)?;
732
733 Ok((input, OrderByClause { items }))
734}
735
736fn order_by_item(input: &str) -> IResult<&str, OrderByItem> {
738 let (input, expression) = value_expression(input)?;
739 let (input, _) = multispace0(input)?;
740 let (input, direction) = opt(alt((
741 map(tag_no_case("ASC"), |_| SortDirection::Ascending),
742 map(tag_no_case("DESC"), |_| SortDirection::Descending),
743 )))(input)?;
744
745 Ok((
746 input,
747 OrderByItem {
748 expression,
749 direction: direction.unwrap_or(SortDirection::Ascending),
750 },
751 ))
752}
753
754fn limit_clause(input: &str) -> IResult<&str, u64> {
756 let (input, _) = multispace0(input)?;
757 let (input, _) = tag_no_case("LIMIT")(input)?;
758 let (input, _) = multispace1(input)?;
759 let (input, limit) = integer_literal(input)?;
760
761 Ok((input, limit as u64))
762}
763
764fn skip_clause(input: &str) -> IResult<&str, u64> {
766 let (input, _) = multispace0(input)?;
767 let (input, _) = tag_no_case("SKIP")(input)?;
768 let (input, _) = multispace1(input)?;
769 let (input, skip) = integer_literal(input)?;
770
771 Ok((input, skip as u64))
772}
773
774fn pagination_clauses(input: &str) -> IResult<&str, (Option<u64>, Option<u64>)> {
776 let (mut remaining, _) = multispace0(input)?;
777 let mut skip: Option<u64> = None;
778 let mut limit: Option<u64> = None;
779
780 loop {
781 let before = remaining;
782
783 if skip.is_none() {
784 if let Ok((i, s)) = skip_clause(remaining) {
785 skip = Some(s);
786 remaining = i;
787 continue;
788 }
789 }
790
791 if limit.is_none() {
792 if let Ok((i, l)) = limit_clause(remaining) {
793 limit = Some(l);
794 remaining = i;
795 continue;
796 }
797 }
798
799 if before == remaining {
800 break;
801 }
802 }
803
804 Ok((remaining, (skip, limit)))
805}
806
807fn identifier(input: &str) -> IResult<&str, &str> {
811 take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)
812}
813
814fn string_literal(input: &str) -> IResult<&str, String> {
816 alt((double_quoted_string, single_quoted_string))(input)
817}
818
819fn double_quoted_string(input: &str) -> IResult<&str, String> {
820 let (input, _) = char('"')(input)?;
821 let (input, content) = take_while1(|c| c != '"')(input)?;
822 let (input, _) = char('"')(input)?;
823 Ok((input, content.to_string()))
824}
825
826fn single_quoted_string(input: &str) -> IResult<&str, String> {
827 let (input, _) = char('\'')(input)?;
828 let (input, content) = take_while1(|c| c != '\'')(input)?;
829 let (input, _) = char('\'')(input)?;
830 Ok((input, content.to_string()))
831}
832
833fn integer_literal(input: &str) -> IResult<&str, i64> {
835 let (input, digits) = recognize(pair(
836 opt(char('-')),
837 take_while1(|c: char| c.is_ascii_digit()),
838 ))(input)?;
839
840 Ok((input, digits.parse().unwrap()))
841}
842
843fn float_literal(input: &str) -> IResult<&str, f64> {
845 let (input, number) = recognize(tuple((
846 opt(char('-')),
847 take_while1(|c: char| c.is_ascii_digit()),
848 char('.'),
849 take_while1(|c: char| c.is_ascii_digit()),
850 )))(input)?;
851
852 Ok((input, number.parse().unwrap()))
853}
854
855fn boolean_literal(input: &str) -> IResult<&str, bool> {
857 alt((
858 map(tag_no_case("true"), |_| true),
859 map(tag_no_case("false"), |_| false),
860 ))(input)
861}
862
863fn parameter(input: &str) -> IResult<&str, String> {
865 let (input, _) = char('$')(input)?;
866 let (input, name) = identifier(input)?;
867 Ok((input, name.to_string()))
868}
869
870fn comma_ws(input: &str) -> IResult<&str, ()> {
872 let (input, _) = multispace0(input)?;
873 let (input, _) = char(',')(input)?;
874 let (input, _) = multispace0(input)?;
875 Ok((input, ()))
876}
877
878fn length_range(input: &str) -> IResult<&str, LengthRange> {
880 let (input, _) = char('*')(input)?;
881 let (input, _) = multispace0(input)?;
882
883 alt((
885 map(
887 tuple((
888 nom::character::complete::u32,
889 tag(".."),
890 nom::character::complete::u32,
891 )),
892 |(min, _, max)| LengthRange {
893 min: Some(min),
894 max: Some(max),
895 },
896 ),
897 map(preceded(tag(".."), nom::character::complete::u32), |max| {
899 LengthRange {
900 min: None,
901 max: Some(max),
902 }
903 }),
904 map(
906 tuple((nom::character::complete::u32, tag(".."))),
907 |(min, _)| LengthRange {
908 min: Some(min),
909 max: None,
910 },
911 ),
912 map(nom::character::complete::u32, |min| LengthRange {
914 min: Some(min),
915 max: Some(min),
916 }),
917 map(multispace0, |_| LengthRange {
919 min: None,
920 max: None,
921 }),
922 ))(input)
923}
924
925#[cfg(test)]
926mod tests {
927 use super::*;
928 use crate::ast::{BooleanExpression, ComparisonOperator, PropertyValue, ValueExpression};
929
930 #[test]
931 fn test_parse_simple_node_query() {
932 let query = "MATCH (n:Person) RETURN n.name";
933 let result = parse_cypher_query(query).unwrap();
934
935 assert_eq!(result.match_clauses.len(), 1);
936 assert_eq!(result.return_clause.items.len(), 1);
937 }
938
939 #[test]
940 fn test_parse_node_with_properties() {
941 let query = r#"MATCH (n:Person {name: "John", age: 30}) RETURN n"#;
942 let result = parse_cypher_query(query).unwrap();
943
944 if let GraphPattern::Node(node) = &result.match_clauses[0].patterns[0] {
945 assert_eq!(node.labels, vec!["Person"]);
946 assert_eq!(node.properties.len(), 2);
947 } else {
948 panic!("Expected node pattern");
949 }
950 }
951
952 #[test]
953 fn test_parse_simple_relationship_query() {
954 let query = "MATCH (a:Person)-[r:KNOWS]->(b:Person) RETURN a.name, b.name";
955 let result = parse_cypher_query(query).unwrap();
956
957 assert_eq!(result.match_clauses.len(), 1);
958 assert_eq!(result.return_clause.items.len(), 2);
959
960 if let GraphPattern::Path(path) = &result.match_clauses[0].patterns[0] {
961 assert_eq!(path.segments.len(), 1);
962 assert_eq!(path.segments[0].relationship.types, vec!["KNOWS"]);
963 } else {
964 panic!("Expected path pattern");
965 }
966 }
967
968 #[test]
969 fn test_parse_variable_length_path() {
970 let query = "MATCH (a:Person)-[:FRIEND_OF*1..2]-(b:Person) RETURN a.name, b.name";
971 let result = parse_cypher_query(query).unwrap();
972
973 assert_eq!(result.match_clauses.len(), 1);
974
975 if let GraphPattern::Path(path) = &result.match_clauses[0].patterns[0] {
976 assert_eq!(path.segments.len(), 1);
977 assert_eq!(path.segments[0].relationship.types, vec!["FRIEND_OF"]);
978
979 let length = path.segments[0].relationship.length.as_ref().unwrap();
980 assert_eq!(length.min, Some(1));
981 assert_eq!(length.max, Some(2));
982 } else {
983 panic!("Expected path pattern");
984 }
985 }
986
987 #[test]
988 fn test_parse_query_with_where_clause() {
989 let query = "MATCH (n:Person) WHERE n.age > 30 RETURN n.name";
990 let result = parse_cypher_query(query).unwrap();
991
992 assert!(result.where_clause.is_some());
993 }
994
995 #[test]
996 fn test_parse_query_with_single_quoted_literal() {
997 let query = "MATCH (n:Person) WHERE n.name = 'Alice' RETURN n.name";
998 let result = parse_cypher_query(query).unwrap();
999
1000 assert!(result.where_clause.is_some());
1001 }
1002
1003 #[test]
1004 fn test_parse_query_with_and_conditions() {
1005 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";
1006 let result = parse_cypher_query(query).unwrap();
1007
1008 let where_clause = result.where_clause.expect("Expected WHERE clause");
1009 match where_clause.expression {
1010 BooleanExpression::And(left, right) => {
1011 match *left {
1012 BooleanExpression::Comparison {
1013 left: ValueExpression::Property(ref prop),
1014 operator,
1015 right: ValueExpression::Literal(PropertyValue::String(ref value)),
1016 } => {
1017 assert_eq!(prop.variable, "rel");
1018 assert_eq!(prop.property, "relationship_type");
1019 assert_eq!(operator, ComparisonOperator::Equal);
1020 assert_eq!(value, "WORKS_ON");
1021 }
1022 _ => panic!("Expected comparison for relationship_type filter"),
1023 }
1024
1025 match *right {
1026 BooleanExpression::Comparison {
1027 left: ValueExpression::Property(ref prop),
1028 operator,
1029 right: ValueExpression::Literal(PropertyValue::String(ref value)),
1030 } => {
1031 assert_eq!(prop.variable, "dst");
1032 assert_eq!(prop.property, "name_lower");
1033 assert_eq!(operator, ComparisonOperator::Equal);
1034 assert_eq!(value, "presto");
1035 }
1036 _ => panic!("Expected comparison for destination name filter"),
1037 }
1038 }
1039 other => panic!("Expected AND expression, got {:?}", other),
1040 }
1041 }
1042
1043 #[test]
1044 fn test_parse_query_with_in_clause() {
1045 let query = "MATCH (src:Entity)-[rel:RELATIONSHIP]->(dst:Entity) WHERE rel.relationship_type IN ['WORKS_FOR', 'PART_OF'] RETURN src.name";
1046 let result = parse_cypher_query(query).unwrap();
1047
1048 let where_clause = result.where_clause.expect("Expected WHERE clause");
1049 match where_clause.expression {
1050 BooleanExpression::In { expression, list } => {
1051 match expression {
1052 ValueExpression::Property(prop_ref) => {
1053 assert_eq!(prop_ref.variable, "rel");
1054 assert_eq!(prop_ref.property, "relationship_type");
1055 }
1056 _ => panic!("Expected property reference in IN expression"),
1057 }
1058 assert_eq!(list.len(), 2);
1059 match &list[0] {
1060 ValueExpression::Literal(PropertyValue::String(val)) => {
1061 assert_eq!(val, "WORKS_FOR");
1062 }
1063 _ => panic!("Expected first list item to be a string literal"),
1064 }
1065 match &list[1] {
1066 ValueExpression::Literal(PropertyValue::String(val)) => {
1067 assert_eq!(val, "PART_OF");
1068 }
1069 _ => panic!("Expected second list item to be a string literal"),
1070 }
1071 }
1072 other => panic!("Expected IN expression, got {:?}", other),
1073 }
1074 }
1075
1076 #[test]
1077 fn test_parse_query_with_is_null() {
1078 let query = "MATCH (n:Person) WHERE n.age IS NULL RETURN n.name";
1079 let result = parse_cypher_query(query).unwrap();
1080
1081 let where_clause = result.where_clause.expect("Expected WHERE clause");
1082
1083 match where_clause.expression {
1084 BooleanExpression::IsNull(expr) => match expr {
1085 ValueExpression::Property(prop_ref) => {
1086 assert_eq!(prop_ref.variable, "n");
1087 assert_eq!(prop_ref.property, "age");
1088 }
1089 _ => panic!("Expected property reference in IS NULL expression"),
1090 },
1091 other => panic!("Expected IS NULL expression, got {:?}", other),
1092 }
1093 }
1094
1095 #[test]
1096 fn test_parse_query_with_is_not_null() {
1097 let query = "MATCH (n:Person) WHERE n.age IS NOT NULL RETURN n.name";
1098 let result = parse_cypher_query(query).unwrap();
1099
1100 let where_clause = result.where_clause.expect("Expected WHERE clause");
1101
1102 match where_clause.expression {
1103 BooleanExpression::IsNotNull(expr) => match expr {
1104 ValueExpression::Property(prop_ref) => {
1105 assert_eq!(prop_ref.variable, "n");
1106 assert_eq!(prop_ref.property, "age");
1107 }
1108 _ => panic!("Expected property reference in IS NOT NULL expression"),
1109 },
1110 other => panic!("Expected IS NOT NULL expression, got {:?}", other),
1111 }
1112 }
1113
1114 #[test]
1115 fn test_parse_query_with_limit() {
1116 let query = "MATCH (n:Person) RETURN n.name LIMIT 10";
1117 let result = parse_cypher_query(query).unwrap();
1118
1119 assert_eq!(result.limit, Some(10));
1120 }
1121
1122 #[test]
1123 fn test_parse_query_with_skip() {
1124 let query = "MATCH (n:Person) RETURN n.name SKIP 5";
1125 let result = parse_cypher_query(query).unwrap();
1126
1127 assert_eq!(result.skip, Some(5));
1128 assert_eq!(result.limit, None);
1129 }
1130
1131 #[test]
1132 fn test_parse_query_with_skip_and_limit() {
1133 let query = "MATCH (n:Person) RETURN n.name SKIP 5 LIMIT 10";
1134 let result = parse_cypher_query(query).unwrap();
1135
1136 assert_eq!(result.skip, Some(5));
1137 assert_eq!(result.limit, Some(10));
1138 }
1139
1140 #[test]
1141 fn test_parse_query_with_skip_and_order_by() {
1142 let query = "MATCH (n:Person) RETURN n.name ORDER BY n.age SKIP 5";
1143 let result = parse_cypher_query(query).unwrap();
1144
1145 assert_eq!(result.skip, Some(5));
1146 assert!(result.order_by.is_some());
1147 }
1148
1149 #[test]
1150 fn test_parse_query_with_skip_order_by_and_limit() {
1151 let query = "MATCH (n:Person) RETURN n.name ORDER BY n.age SKIP 5 LIMIT 10";
1152 let result = parse_cypher_query(query).unwrap();
1153
1154 assert_eq!(result.skip, Some(5));
1155 assert_eq!(result.limit, Some(10));
1156 assert!(result.order_by.is_some());
1157 }
1158
1159 #[test]
1160 fn test_parse_count_star() {
1161 let query = "MATCH (n:Person) RETURN count(*) AS total";
1162 let result = parse_cypher_query(query).unwrap();
1163
1164 assert_eq!(result.return_clause.items.len(), 1);
1165 let item = &result.return_clause.items[0];
1166 assert_eq!(item.alias, Some("total".to_string()));
1167
1168 match &item.expression {
1169 ValueExpression::Function { name, args } => {
1170 assert_eq!(name, "count");
1171 assert_eq!(args.len(), 1);
1172 match &args[0] {
1173 ValueExpression::Variable(v) => assert_eq!(v, "*"),
1174 _ => panic!("Expected Variable(*) in count(*)"),
1175 }
1176 }
1177 _ => panic!("Expected Function expression"),
1178 }
1179 }
1180
1181 #[test]
1182 fn test_parse_count_property() {
1183 let query = "MATCH (n:Person) RETURN count(n.age)";
1184 let result = parse_cypher_query(query).unwrap();
1185
1186 assert_eq!(result.return_clause.items.len(), 1);
1187 let item = &result.return_clause.items[0];
1188
1189 match &item.expression {
1190 ValueExpression::Function { name, args } => {
1191 assert_eq!(name, "count");
1192 assert_eq!(args.len(), 1);
1193 match &args[0] {
1194 ValueExpression::Property(prop) => {
1195 assert_eq!(prop.variable, "n");
1196 assert_eq!(prop.property, "age");
1197 }
1198 _ => panic!("Expected Property in count(n.age)"),
1199 }
1200 }
1201 _ => panic!("Expected Function expression"),
1202 }
1203 }
1204
1205 #[test]
1206 fn test_parse_non_count_function_rejects_star() {
1207 let query = "MATCH (n:Person) RETURN foo(*)";
1209 let result = parse_cypher_query(query);
1210 assert!(result.is_err(), "foo(*) should not parse successfully");
1211 }
1212
1213 #[test]
1214 fn test_parse_count_with_multiple_args() {
1215 let query = "MATCH (n:Person) RETURN count(n.age, n.name)";
1218 let result = parse_cypher_query(query);
1219 assert!(
1220 result.is_ok(),
1221 "Parser should accept multiple args (validation happens in semantic phase)"
1222 );
1223
1224 let ast = result.unwrap();
1226 match &ast.return_clause.items[0].expression {
1227 ValueExpression::Function { name, args } => {
1228 assert_eq!(name, "count");
1229 assert_eq!(args.len(), 2);
1230 }
1231 _ => panic!("Expected Function expression"),
1232 }
1233 }
1234
1235 #[test]
1236 fn test_parse_like_pattern() {
1237 let query = "MATCH (n:Person) WHERE n.name LIKE 'A%' RETURN n.name";
1238 let result = parse_cypher_query(query);
1239 assert!(result.is_ok(), "LIKE pattern should parse successfully");
1240
1241 let ast = result.unwrap();
1242 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1243
1244 match where_clause.expression {
1245 BooleanExpression::Like {
1246 expression,
1247 pattern,
1248 } => {
1249 match expression {
1250 ValueExpression::Property(prop) => {
1251 assert_eq!(prop.variable, "n");
1252 assert_eq!(prop.property, "name");
1253 }
1254 _ => panic!("Expected property expression"),
1255 }
1256 assert_eq!(pattern, "A%");
1257 }
1258 _ => panic!("Expected LIKE expression"),
1259 }
1260 }
1261
1262 #[test]
1263 fn test_parse_like_with_double_quotes() {
1264 let query = r#"MATCH (n:Person) WHERE n.email LIKE "%@example.com" RETURN n.email"#;
1265 let result = parse_cypher_query(query);
1266 assert!(result.is_ok(), "LIKE with double quotes should parse");
1267
1268 let ast = result.unwrap();
1269 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1270
1271 match where_clause.expression {
1272 BooleanExpression::Like { pattern, .. } => {
1273 assert_eq!(pattern, "%@example.com");
1274 }
1275 _ => panic!("Expected LIKE expression"),
1276 }
1277 }
1278
1279 #[test]
1280 fn test_parse_like_in_complex_where() {
1281 let query = "MATCH (n:Person) WHERE n.age > 20 AND n.name LIKE 'J%' RETURN n.name";
1282 let result = parse_cypher_query(query);
1283 assert!(result.is_ok(), "LIKE in complex WHERE should parse");
1284
1285 let ast = result.unwrap();
1286 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1287
1288 match where_clause.expression {
1289 BooleanExpression::And(left, right) => {
1290 match *left {
1292 BooleanExpression::Comparison { .. } => {}
1293 _ => panic!("Expected comparison on left"),
1294 }
1295 match *right {
1297 BooleanExpression::Like { pattern, .. } => {
1298 assert_eq!(pattern, "J%");
1299 }
1300 _ => panic!("Expected LIKE expression on right"),
1301 }
1302 }
1303 _ => panic!("Expected AND expression"),
1304 }
1305 }
1306
1307 #[test]
1308 fn test_parse_contains() {
1309 let query = "MATCH (n:Person) WHERE n.name CONTAINS 'Jo' RETURN n.name";
1310 let result = parse_cypher_query(query);
1311 assert!(result.is_ok());
1312
1313 let query = result.unwrap();
1314 assert!(query.where_clause.is_some());
1315
1316 match &query.where_clause.unwrap().expression {
1317 BooleanExpression::Contains {
1318 expression,
1319 substring,
1320 } => {
1321 assert_eq!(substring, "Jo");
1322 match expression {
1323 ValueExpression::Property(prop) => {
1324 assert_eq!(prop.variable, "n");
1325 assert_eq!(prop.property, "name");
1326 }
1327 _ => panic!("Expected property reference"),
1328 }
1329 }
1330 _ => panic!("Expected CONTAINS expression"),
1331 }
1332 }
1333
1334 #[test]
1335 fn test_parse_starts_with() {
1336 let query = "MATCH (n:Person) WHERE n.name STARTS WITH 'Alice' RETURN n.name";
1337 let result = parse_cypher_query(query);
1338 assert!(result.is_ok());
1339
1340 let query = result.unwrap();
1341 assert!(query.where_clause.is_some());
1342
1343 match &query.where_clause.unwrap().expression {
1344 BooleanExpression::StartsWith { expression, prefix } => {
1345 assert_eq!(prefix, "Alice");
1346 match expression {
1347 ValueExpression::Property(prop) => {
1348 assert_eq!(prop.variable, "n");
1349 assert_eq!(prop.property, "name");
1350 }
1351 _ => panic!("Expected property reference"),
1352 }
1353 }
1354 _ => panic!("Expected STARTS WITH expression"),
1355 }
1356 }
1357
1358 #[test]
1359 fn test_parse_ends_with() {
1360 let query = "MATCH (n:Person) WHERE n.email ENDS WITH '@example.com' RETURN n.email";
1361 let result = parse_cypher_query(query);
1362 assert!(result.is_ok());
1363
1364 let query = result.unwrap();
1365 assert!(query.where_clause.is_some());
1366
1367 match &query.where_clause.unwrap().expression {
1368 BooleanExpression::EndsWith { expression, suffix } => {
1369 assert_eq!(suffix, "@example.com");
1370 match expression {
1371 ValueExpression::Property(prop) => {
1372 assert_eq!(prop.variable, "n");
1373 assert_eq!(prop.property, "email");
1374 }
1375 _ => panic!("Expected property reference"),
1376 }
1377 }
1378 _ => panic!("Expected ENDS WITH expression"),
1379 }
1380 }
1381
1382 #[test]
1383 fn test_parse_contains_case_insensitive_keyword() {
1384 let query = "MATCH (n:Person) WHERE n.name contains 'test' RETURN n.name";
1385 let result = parse_cypher_query(query);
1386 assert!(result.is_ok());
1387
1388 match &result.unwrap().where_clause.unwrap().expression {
1389 BooleanExpression::Contains { substring, .. } => {
1390 assert_eq!(substring, "test");
1391 }
1392 _ => panic!("Expected CONTAINS expression"),
1393 }
1394 }
1395
1396 #[test]
1397 fn test_parse_string_operators_in_complex_where() {
1398 let query =
1399 "MATCH (n:Person) WHERE n.name CONTAINS 'Jo' AND n.email ENDS WITH '.com' RETURN n";
1400 let result = parse_cypher_query(query);
1401 assert!(result.is_ok());
1402
1403 match &result.unwrap().where_clause.unwrap().expression {
1404 BooleanExpression::And(left, right) => {
1405 match **left {
1407 BooleanExpression::Contains { ref substring, .. } => {
1408 assert_eq!(substring, "Jo");
1409 }
1410 _ => panic!("Expected CONTAINS expression on left"),
1411 }
1412 match **right {
1414 BooleanExpression::EndsWith { ref suffix, .. } => {
1415 assert_eq!(suffix, ".com");
1416 }
1417 _ => panic!("Expected ENDS WITH expression on right"),
1418 }
1419 }
1420 _ => panic!("Expected AND expression"),
1421 }
1422 }
1423
1424 #[test]
1425 fn test_parse_ilike_pattern() {
1426 let query = "MATCH (n:Person) WHERE n.name ILIKE 'alice%' RETURN n.name";
1427 let result = parse_cypher_query(query);
1428 assert!(result.is_ok(), "ILIKE pattern should parse successfully");
1429
1430 let ast = result.unwrap();
1431 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1432
1433 match where_clause.expression {
1434 BooleanExpression::ILike {
1435 expression,
1436 pattern,
1437 } => {
1438 match expression {
1439 ValueExpression::Property(prop) => {
1440 assert_eq!(prop.variable, "n");
1441 assert_eq!(prop.property, "name");
1442 }
1443 _ => panic!("Expected property expression"),
1444 }
1445 assert_eq!(pattern, "alice%");
1446 }
1447 _ => panic!("Expected ILIKE expression"),
1448 }
1449 }
1450
1451 #[test]
1452 fn test_parse_like_and_ilike_together() {
1453 let query =
1454 "MATCH (n:Person) WHERE n.name LIKE 'Alice%' OR n.name ILIKE 'bob%' RETURN n.name";
1455 let result = parse_cypher_query(query);
1456 assert!(result.is_ok(), "LIKE and ILIKE together should parse");
1457
1458 let ast = result.unwrap();
1459 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1460
1461 match where_clause.expression {
1462 BooleanExpression::Or(left, right) => {
1463 match *left {
1465 BooleanExpression::Like { pattern, .. } => {
1466 assert_eq!(pattern, "Alice%");
1467 }
1468 _ => panic!("Expected LIKE expression on left"),
1469 }
1470 match *right {
1472 BooleanExpression::ILike { pattern, .. } => {
1473 assert_eq!(pattern, "bob%");
1474 }
1475 _ => panic!("Expected ILIKE expression on right"),
1476 }
1477 }
1478 _ => panic!("Expected OR expression"),
1479 }
1480 }
1481
1482 #[test]
1483 fn test_parse_vector_distance() {
1484 let query = "MATCH (p:Person) WHERE vector_distance(p.embedding, $query_vec, cosine) < 0.5 RETURN p.name";
1485 let result = parse_cypher_query(query);
1486 assert!(result.is_ok(), "vector_distance should parse successfully");
1487
1488 let ast = result.unwrap();
1489 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1490
1491 match where_clause.expression {
1493 BooleanExpression::Comparison { left, operator, .. } => {
1494 match left {
1495 ValueExpression::VectorDistance {
1496 left,
1497 right,
1498 metric,
1499 } => {
1500 assert_eq!(metric, DistanceMetric::Cosine);
1501 assert!(matches!(*left, ValueExpression::Property(_)));
1503 assert!(matches!(*right, ValueExpression::Parameter(_)));
1505 }
1506 _ => panic!("Expected VectorDistance"),
1507 }
1508 assert_eq!(operator, ComparisonOperator::LessThan);
1509 }
1510 _ => panic!("Expected comparison"),
1511 }
1512 }
1513
1514 #[test]
1515 fn test_parse_vector_similarity() {
1516 let query =
1517 "MATCH (p:Person) WHERE vector_similarity(p.embedding, $vec, l2) > 0.8 RETURN p";
1518 let result = parse_cypher_query(query);
1519 assert!(
1520 result.is_ok(),
1521 "vector_similarity should parse successfully"
1522 );
1523
1524 let ast = result.unwrap();
1525 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1526
1527 match where_clause.expression {
1528 BooleanExpression::Comparison { left, operator, .. } => {
1529 match left {
1530 ValueExpression::VectorSimilarity { metric, .. } => {
1531 assert_eq!(metric, DistanceMetric::L2);
1532 }
1533 _ => panic!("Expected VectorSimilarity"),
1534 }
1535 assert_eq!(operator, ComparisonOperator::GreaterThan);
1536 }
1537 _ => panic!("Expected comparison"),
1538 }
1539 }
1540
1541 #[test]
1542 fn test_parse_parameter() {
1543 let query = "MATCH (p:Person) WHERE p.age = $min_age RETURN p";
1544 let result = parse_cypher_query(query);
1545 assert!(result.is_ok(), "Parameter should parse successfully");
1546
1547 let ast = result.unwrap();
1548 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1549
1550 match where_clause.expression {
1551 BooleanExpression::Comparison { right, .. } => match right {
1552 ValueExpression::Parameter(name) => {
1553 assert_eq!(name, "min_age");
1554 }
1555 _ => panic!("Expected Parameter"),
1556 },
1557 _ => panic!("Expected comparison"),
1558 }
1559 }
1560
1561 #[test]
1562 fn test_vector_distance_metrics() {
1563 for metric in &["cosine", "l2", "dot"] {
1564 let query = format!(
1565 "MATCH (p:Person) RETURN vector_distance(p.emb, $v, {}) AS dist",
1566 metric
1567 );
1568 let result = parse_cypher_query(&query);
1569 assert!(result.is_ok(), "Failed to parse metric: {}", metric);
1570
1571 let ast = result.unwrap();
1572 let return_item = &ast.return_clause.items[0];
1573
1574 match &return_item.expression {
1575 ValueExpression::VectorDistance {
1576 metric: parsed_metric,
1577 ..
1578 } => {
1579 let expected = match *metric {
1580 "cosine" => DistanceMetric::Cosine,
1581 "l2" => DistanceMetric::L2,
1582 "dot" => DistanceMetric::Dot,
1583 _ => panic!("Unexpected metric"),
1584 };
1585 assert_eq!(*parsed_metric, expected);
1586 }
1587 _ => panic!("Expected VectorDistance"),
1588 }
1589 }
1590 }
1591
1592 #[test]
1593 fn test_vector_search_in_order_by() {
1594 let query = "MATCH (p:Person) RETURN p.name ORDER BY vector_distance(p.embedding, $query_vec, cosine) ASC LIMIT 10";
1595 let result = parse_cypher_query(query);
1596 assert!(result.is_ok(), "vector_distance in ORDER BY should parse");
1597
1598 let ast = result.unwrap();
1599 let order_by = ast.order_by.expect("Expected ORDER BY clause");
1600
1601 assert_eq!(order_by.items.len(), 1);
1602 match &order_by.items[0].expression {
1603 ValueExpression::VectorDistance { .. } => {
1604 }
1606 _ => panic!("Expected VectorDistance in ORDER BY"),
1607 }
1608 }
1609
1610 #[test]
1611 fn test_hybrid_query_with_vector_and_property_filters() {
1612 let query = "MATCH (p:Person) WHERE p.age > 25 AND vector_similarity(p.embedding, $query_vec, cosine) > 0.7 RETURN p.name";
1613 let result = parse_cypher_query(query);
1614 assert!(result.is_ok(), "Hybrid query should parse");
1615
1616 let ast = result.unwrap();
1617 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1618
1619 match where_clause.expression {
1621 BooleanExpression::And(left, right) => {
1622 match *left {
1624 BooleanExpression::Comparison { .. } => {}
1625 _ => panic!("Expected comparison on left"),
1626 }
1627 match *right {
1629 BooleanExpression::Comparison { left, .. } => match left {
1630 ValueExpression::VectorSimilarity { .. } => {}
1631 _ => panic!("Expected VectorSimilarity"),
1632 },
1633 _ => panic!("Expected comparison on right"),
1634 }
1635 }
1636 _ => panic!("Expected AND expression"),
1637 }
1638 }
1639
1640 #[test]
1641 fn test_parse_vector_literal() {
1642 let result = parse_vector_literal("[0.1, 0.2, 0.3]");
1643 assert!(result.is_ok());
1644 let (_, expr) = result.unwrap();
1645 match expr {
1646 ValueExpression::VectorLiteral(vec) => {
1647 assert_eq!(vec.len(), 3);
1648 assert_eq!(vec[0], 0.1);
1649 assert_eq!(vec[1], 0.2);
1650 assert_eq!(vec[2], 0.3);
1651 }
1652 _ => panic!("Expected VectorLiteral"),
1653 }
1654 }
1655
1656 #[test]
1657 fn test_parse_vector_literal_with_negative_values() {
1658 let result = parse_vector_literal("[-0.1, 0.2, -0.3]");
1659 assert!(result.is_ok());
1660 let (_, expr) = result.unwrap();
1661 match expr {
1662 ValueExpression::VectorLiteral(vec) => {
1663 assert_eq!(vec.len(), 3);
1664 assert_eq!(vec[0], -0.1);
1665 assert_eq!(vec[2], -0.3);
1666 }
1667 _ => panic!("Expected VectorLiteral"),
1668 }
1669 }
1670
1671 #[test]
1672 fn test_parse_vector_literal_scientific_notation() {
1673 let result = parse_vector_literal("[1e-3, 2.5e2, -3e-1]");
1674 assert!(result.is_ok());
1675 let (_, expr) = result.unwrap();
1676 match expr {
1677 ValueExpression::VectorLiteral(vec) => {
1678 assert_eq!(vec.len(), 3);
1679 assert!((vec[0] - 0.001).abs() < 1e-6);
1680 assert!((vec[1] - 250.0).abs() < 1e-6);
1681 assert!((vec[2] - (-0.3)).abs() < 1e-6);
1682 }
1683 _ => panic!("Expected VectorLiteral"),
1684 }
1685 }
1686
1687 #[test]
1688 fn test_vector_distance_with_literal() {
1689 let query =
1690 "MATCH (p:Person) WHERE vector_distance(p.embedding, [0.1, 0.2], l2) < 0.5 RETURN p";
1691 let result = parse_cypher_query(query);
1692 assert!(result.is_ok());
1693
1694 let ast = result.unwrap();
1695 let where_clause = ast.where_clause.expect("Expected WHERE clause");
1696
1697 match where_clause.expression {
1698 BooleanExpression::Comparison { left, operator, .. } => {
1699 match left {
1700 ValueExpression::VectorDistance {
1701 left,
1702 right,
1703 metric,
1704 } => {
1705 assert!(matches!(*left, ValueExpression::Property(_)));
1707 match *right {
1709 ValueExpression::VectorLiteral(vec) => {
1710 assert_eq!(vec.len(), 2);
1711 assert_eq!(vec[0], 0.1);
1712 assert_eq!(vec[1], 0.2);
1713 }
1714 _ => panic!("Expected VectorLiteral"),
1715 }
1716 assert_eq!(metric, DistanceMetric::L2);
1717 }
1718 _ => panic!("Expected VectorDistance"),
1719 }
1720 assert_eq!(operator, ComparisonOperator::LessThan);
1721 }
1722 _ => panic!("Expected comparison"),
1723 }
1724 }
1725}