lance_graph/
parser.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4//! Cypher query parser
5//!
6//! This module provides parsing functionality for Cypher queries using nom parser combinators.
7//! It supports a subset of Cypher syntax focused on graph pattern matching and property access.
8
9use 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
22/// Parse a complete Cypher query
23pub 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
41// Top-level parser for a complete Cypher query
42fn 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
64// Parse a MATCH clause
65fn 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
74// Parse a graph pattern (node or path)
75fn 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
82// Parse a path pattern (only if there are segments)
83fn 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    // Only succeed if we actually have path segments
88    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
104// Parse a path segment (relationship + node)
105fn 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
118// Parse a node pattern: (variable:Label {prop: value})
119fn 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
140// Parse a relationship pattern: -[variable:TYPE {prop: value}]->
141fn relationship_pattern(input: &str) -> IResult<&str, RelationshipPattern> {
142    let (input, _) = multispace0(input)?;
143
144    // Parse direction and bracket content
145    let (input, (direction, content)) = alt((
146        // Outgoing: -[...]->
147        map(
148            tuple((
149                char('-'),
150                delimited(char('['), relationship_content, char(']')),
151                tag("->"),
152            )),
153            |(_, content, _)| (RelationshipDirection::Outgoing, content),
154        ),
155        // Incoming: <-[...]-
156        map(
157            tuple((
158                tag("<-"),
159                delimited(char('['), relationship_content, char(']')),
160                char('-'),
161            )),
162            |(_, content, _)| (RelationshipDirection::Incoming, content),
163        ),
164        // Undirected: -[...]-
165        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
189// Type alias for complex relationship content return type
190type RelationshipContentResult<'a> = (
191    Option<&'a str>,
192    Vec<&'a str>,
193    Option<HashMap<String, PropertyValue>>,
194    Option<LengthRange>,
195);
196
197// Parse relationship content inside brackets
198fn 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
211// Parse a property map: {key: value, key2: value2}
212fn 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
223// Parse a property key-value pair
224fn 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
235// Parse a property value
236fn property_value(input: &str) -> IResult<&str, PropertyValue> {
237    alt((
238        map(string_literal, PropertyValue::String),
239        map(float_literal, PropertyValue::Float), // Try float BEFORE integer (more specific)
240        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
247// Parse a WHERE clause
248fn 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
257// Parse a boolean expression with OR precedence
258fn 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    // Match LIKE pattern
333    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    // Match ILIKE pattern (case-insensitive LIKE)
345    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    // Match CONTAINS substring
357    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    // Match STARTS WITH prefix (note: multi-word operator)
369    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    // Match ENDS WITH suffix (note: multi-word operator)
386    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    // Match is null
403    if let Ok((rest, ())) = is_null_comparison(input) {
404        return Ok((rest, BooleanExpression::IsNull(left_clone)));
405    }
406    // Match is not null
407    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
425// Parse a comparison operator
426fn 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
438// Parse a basic value expression (without vector functions to avoid circular dependency)
439fn basic_value_expression(input: &str) -> IResult<&str, ValueExpression> {
440    alt((
441        parse_vector_literal, // Try vector literal first [0.1, 0.2]
442        parse_parameter,      // Try $parameter
443        function_call,        // Regular function calls
444        map(property_value, ValueExpression::Literal), // Try literals BEFORE property references
445        map(property_reference, ValueExpression::Property),
446        map(identifier, |id| ValueExpression::Variable(id.to_string())),
447    ))(input)
448}
449
450// Parse a value expression
451// Optimization: Use peek to avoid expensive backtracking for non-vector queries
452fn value_expression(input: &str) -> IResult<&str, ValueExpression> {
453    // Peek at first identifier to dispatch to correct parser
454    // This eliminates failed parser attempts for every non-vector expression
455    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            _ => {} // Not a vector function, continue to basic expressions
462        }
463    }
464
465    // Fast path for common expressions
466    basic_value_expression(input)
467}
468
469// Parse distance metric: cosine, l2, dot
470fn 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
478// Parse vector_distance(expr, expr, metric)
479fn 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    // Parse left expression - use basic_value_expression to avoid circular dependency
486    let (input, left) = basic_value_expression(input)?;
487    let (input, _) = multispace0(input)?;
488    let (input, _) = char(',')(input)?;
489    let (input, _) = multispace0(input)?;
490
491    // Parse right expression - use basic_value_expression to avoid circular dependency
492    let (input, right) = basic_value_expression(input)?;
493    let (input, _) = multispace0(input)?;
494    let (input, _) = char(',')(input)?;
495    let (input, _) = multispace0(input)?;
496
497    // Parse metric
498    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
512// Parse vector_similarity(expr, expr, metric)
513fn 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    // Parse left expression - use basic_value_expression to avoid circular dependency
520    let (input, left) = basic_value_expression(input)?;
521    let (input, _) = multispace0(input)?;
522    let (input, _) = char(',')(input)?;
523    let (input, _) = multispace0(input)?;
524
525    // Parse right expression - use basic_value_expression to avoid circular dependency
526    let (input, right) = basic_value_expression(input)?;
527    let (input, _) = multispace0(input)?;
528    let (input, _) = char(',')(input)?;
529    let (input, _) = multispace0(input)?;
530
531    // Parse metric
532    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
546// Parse parameter reference: $name
547fn 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
553// Parse a function call: function_name(args)
554fn 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    // Handle COUNT(*) special case - only allow * for COUNT function
561    if let Ok((input_after_star, _)) = char::<_, nom::error::Error<&str>>('*')(input) {
562        // Validate that this is COUNT function
563        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            // Not COUNT - fail parsing to try regular argument parsing
575            // This will naturally fail since * is not a valid value_expression
576        }
577    }
578
579    // Parse regular function arguments
580    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
607// Parse a float32 literal for vectors
608fn float32_literal(input: &str) -> IResult<&str, f32> {
609    map_res(
610        recognize(tuple((
611            opt(char('-')),
612            alt((
613                // Scientific notation: 1e-3, 2.5e2
614                recognize(tuple((
615                    digit1,
616                    opt(tuple((char('.'), digit0))),
617                    one_of("eE"),
618                    opt(one_of("+-")),
619                    digit1,
620                ))),
621                // Regular float: 1.23 or integer: 123
622                recognize(tuple((digit1, opt(tuple((char('.'), digit0)))))),
623            )),
624        ))),
625        |s: &str| s.parse::<f32>(),
626    )(input)
627}
628
629// Parse vector literal: [0.1, 0.2, 0.3]
630fn 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
645// Parse a property reference: variable.property
646fn 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
660// Parse a RETURN clause
661fn 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
682// Parse a return item
683fn 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
700// Match IS NULL in WHERE clause
701fn 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
711// Match IS NOT NULL in WHERE clause
712fn 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
724// Parse an ORDER BY clause
725fn 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
736// Parse an order by item
737fn 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
754// Parse a LIMIT clause
755fn 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
764// Parse a SKIP clause
765fn 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
774// Parse pagination clauses (SKIP and LIMIT)
775fn 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
807// Helper parsers
808
809// Parse an identifier
810fn identifier(input: &str) -> IResult<&str, &str> {
811    take_while1(|c: char| c.is_alphanumeric() || c == '_')(input)
812}
813
814// Parse a string literal
815fn 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
833// Parse an integer literal
834fn 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
843// Parse a float literal
844fn 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
855// Parse a boolean literal
856fn 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
863// Parse a parameter reference
864fn 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
870// Parse comma with optional whitespace
871fn 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
878// Parse variable-length path syntax: *1..2, *..3, *2.., *
879fn length_range(input: &str) -> IResult<&str, LengthRange> {
880    let (input, _) = char('*')(input)?;
881    let (input, _) = multispace0(input)?;
882
883    // Parse different length patterns
884    alt((
885        // *min..max (e.g., *1..3)
886        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        // *..max (e.g., *..3)
898        map(preceded(tag(".."), nom::character::complete::u32), |max| {
899            LengthRange {
900                min: None,
901                max: Some(max),
902            }
903        }),
904        // *min.. (e.g., *2..)
905        map(
906            tuple((nom::character::complete::u32, tag(".."))),
907            |(min, _)| LengthRange {
908                min: Some(min),
909                max: None,
910            },
911        ),
912        // *min (e.g., *2)
913        map(nom::character::complete::u32, |min| LengthRange {
914            min: Some(min),
915            max: Some(min),
916        }),
917        // * (unlimited)
918        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        // FOO(*) should fail to parse since * is only allowed for COUNT
1208        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        // COUNT with multiple arguments parses successfully
1216        // but will be rejected during semantic validation
1217        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        // Verify the AST structure
1225        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                // Left should be age > 20
1291                match *left {
1292                    BooleanExpression::Comparison { .. } => {}
1293                    _ => panic!("Expected comparison on left"),
1294                }
1295                // Right should be LIKE
1296                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                // Left should be CONTAINS
1406                match **left {
1407                    BooleanExpression::Contains { ref substring, .. } => {
1408                        assert_eq!(substring, "Jo");
1409                    }
1410                    _ => panic!("Expected CONTAINS expression on left"),
1411                }
1412                // Right should be ENDS WITH
1413                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                // Left should be LIKE (case-sensitive)
1464                match *left {
1465                    BooleanExpression::Like { pattern, .. } => {
1466                        assert_eq!(pattern, "Alice%");
1467                    }
1468                    _ => panic!("Expected LIKE expression on left"),
1469                }
1470                // Right should be ILIKE (case-insensitive)
1471                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        // Verify it's a comparison with vector_distance
1492        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                        // Verify left is property reference
1502                        assert!(matches!(*left, ValueExpression::Property(_)));
1503                        // Verify right is parameter
1504                        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                // Success
1605            }
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        // Should be an AND expression
1620        match where_clause.expression {
1621            BooleanExpression::And(left, right) => {
1622                // Left should be age > 25
1623                match *left {
1624                    BooleanExpression::Comparison { .. } => {}
1625                    _ => panic!("Expected comparison on left"),
1626                }
1627                // Right should be vector_similarity > 0.7
1628                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                        // Left should be property reference
1706                        assert!(matches!(*left, ValueExpression::Property(_)));
1707                        // Right should be vector literal
1708                        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}