Skip to main content

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