Skip to main content

lora_parser/
parser.rs

1use crate::error::ParseError;
2use lora_ast::*;
3use pest::iterators::Pair;
4use pest::Parser;
5use smallvec::SmallVec;
6
7#[derive(pest_derive::Parser)]
8#[grammar = "cypher.pest"]
9struct LoraParser;
10
11pub fn parse_query(input: &str) -> Result<Document, ParseError> {
12    let mut pairs = LoraParser::parse(Rule::query, input)
13        .map_err(|e| ParseError::new(e.to_string(), 0, input.len()))?;
14
15    let pair = pairs
16        .next()
17        .ok_or_else(|| ParseError::new("expected query", 0, input.len()))?;
18
19    lower_query(pair)
20}
21
22fn lower_query(pair: Pair<Rule>) -> Result<Document, ParseError> {
23    let span = pair_span(&pair);
24    let mut inner = pair.into_inner();
25
26    let stmt = inner
27        .find(|p| p.as_rule() == Rule::statement)
28        .ok_or_else(|| ParseError::new("expected statement", span.start, span.end))?;
29
30    Ok(Document {
31        statement: lower_statement(stmt)?,
32        span,
33    })
34}
35
36fn lower_statement(pair: Pair<Rule>) -> Result<Statement, ParseError> {
37    let inner = single_inner(pair)?;
38    match inner.as_rule() {
39        Rule::regular_query => Ok(Statement::Query(Query::Regular(lower_regular_query(
40            inner,
41        )?))),
42        Rule::standalone_call => Ok(Statement::Query(Query::StandaloneCall(
43            lower_standalone_call(inner)?,
44        ))),
45        _ => Err(unexpected_rule("statement", inner)),
46    }
47}
48
49fn lower_regular_query(pair: Pair<Rule>) -> Result<RegularQuery, ParseError> {
50    let span = pair_span(&pair);
51    let mut inner = pair.into_inner();
52
53    let first = inner
54        .next()
55        .ok_or_else(|| ParseError::new("expected single query", span.start, span.end))?;
56
57    let head = lower_single_query(first)?;
58    let mut unions = Vec::new();
59
60    while let Some(union_pair) = inner.next() {
61        let uq = inner
62            .next()
63            .ok_or_else(|| ParseError::new("expected query after UNION", span.start, span.end))?;
64
65        let all = union_pair
66            .clone()
67            .into_inner()
68            .any(|p| p.as_rule() == Rule::ALL);
69        let union_span = Span::new(union_pair.as_span().start(), uq.as_span().end());
70
71        unions.push(UnionPart {
72            all,
73            query: lower_single_query(uq)?,
74            span: union_span,
75        });
76    }
77
78    Ok(RegularQuery { head, unions, span })
79}
80
81fn lower_single_query(pair: Pair<Rule>) -> Result<SingleQuery, ParseError> {
82    match pair.as_rule() {
83        Rule::single_query => lower_single_query(single_inner(pair)?),
84        Rule::single_part_query => Ok(SingleQuery::SinglePart(lower_single_part_query(pair)?)),
85        Rule::multi_part_query => Ok(SingleQuery::MultiPart(lower_multi_part_query(pair)?)),
86        _ => Err(unexpected_rule("single_query", pair)),
87    }
88}
89
90fn lower_multi_part_query(pair: Pair<Rule>) -> Result<MultiPartQuery, ParseError> {
91    let span = pair_span(&pair);
92    let mut parts = Vec::new();
93    let mut tail = None;
94
95    for p in pair.into_inner() {
96        match p.as_rule() {
97            Rule::query_part => parts.push(lower_query_part(p)?),
98            Rule::single_part_query => tail = Some(lower_single_part_query(p)?),
99            _ => {}
100        }
101    }
102
103    Ok(MultiPartQuery {
104        parts,
105        tail: Box::new(tail.ok_or_else(|| {
106            ParseError::new("expected single-part tail query", span.start, span.end)
107        })?),
108        span,
109    })
110}
111
112fn lower_query_part(pair: Pair<Rule>) -> Result<QueryPart, ParseError> {
113    let span = pair_span(&pair);
114    let mut reading_clauses = Vec::new();
115    let mut updating_clauses = Vec::new();
116    let mut with_clause = None;
117
118    for p in pair.into_inner() {
119        match p.as_rule() {
120            Rule::reading_clause => reading_clauses.push(lower_reading_clause(p)?),
121            Rule::updating_clause => updating_clauses.push(lower_updating_clause(p)?),
122            Rule::with_clause => with_clause = Some(lower_with_clause(p)?),
123            _ => {}
124        }
125    }
126
127    Ok(QueryPart {
128        reading_clauses,
129        updating_clauses,
130        with_clause: with_clause
131            .ok_or_else(|| ParseError::new("expected WITH clause", span.start, span.end))?,
132        span,
133    })
134}
135
136fn lower_single_part_query(pair: Pair<Rule>) -> Result<SinglePartQuery, ParseError> {
137    let span = pair_span(&pair);
138    let mut reading_clauses = Vec::new();
139    let mut updating_clauses = Vec::new();
140    let mut return_clause = None;
141
142    for p in pair.into_inner() {
143        match p.as_rule() {
144            Rule::reading_clause => reading_clauses.push(lower_reading_clause(p)?),
145            Rule::updating_clause => updating_clauses.push(lower_updating_clause(p)?),
146            Rule::return_clause => return_clause = Some(lower_return_clause(p)?),
147            _ => {}
148        }
149    }
150
151    Ok(SinglePartQuery {
152        reading_clauses,
153        updating_clauses,
154        return_clause,
155        span,
156    })
157}
158
159fn lower_reading_clause(pair: Pair<Rule>) -> Result<ReadingClause, ParseError> {
160    let inner = single_inner(pair)?;
161    match inner.as_rule() {
162        Rule::match_clause => Ok(ReadingClause::Match(lower_match(inner)?)),
163        Rule::unwind_clause => Ok(ReadingClause::Unwind(lower_unwind(inner)?)),
164        Rule::in_query_call => Ok(ReadingClause::InQueryCall(lower_in_query_call(inner)?)),
165        _ => Err(unexpected_rule("reading_clause", inner)),
166    }
167}
168
169fn lower_updating_clause(pair: Pair<Rule>) -> Result<UpdatingClause, ParseError> {
170    let inner = single_inner(pair)?;
171    match inner.as_rule() {
172        Rule::create_clause => Ok(UpdatingClause::Create(lower_create(inner)?)),
173        Rule::merge_clause => Ok(UpdatingClause::Merge(lower_merge(inner)?)),
174        Rule::delete_clause => Ok(UpdatingClause::Delete(lower_delete(inner)?)),
175        Rule::set_clause => Ok(UpdatingClause::Set(lower_set(inner)?)),
176        Rule::remove_clause => Ok(UpdatingClause::Remove(lower_remove(inner)?)),
177        _ => Err(unexpected_rule("updating_clause", inner)),
178    }
179}
180
181fn lower_match(pair: Pair<Rule>) -> Result<Match, ParseError> {
182    let span = pair_span(&pair);
183    let mut optional = false;
184    let mut pattern = None;
185    let mut where_ = None;
186
187    for p in pair.into_inner() {
188        match p.as_rule() {
189            Rule::OPTIONAL => optional = true,
190            Rule::pattern => pattern = Some(lower_pattern(p)?),
191            Rule::where_clause => where_ = Some(lower_where_clause(p)?),
192            _ => {}
193        }
194    }
195
196    Ok(Match {
197        optional,
198        pattern: pattern
199            .ok_or_else(|| ParseError::new("expected pattern", span.start, span.end))?,
200        where_,
201        span,
202    })
203}
204
205fn lower_unwind(pair: Pair<Rule>) -> Result<Unwind, ParseError> {
206    let span = pair_span(&pair);
207    let mut expr = None;
208    let mut alias = None;
209
210    for p in pair.into_inner() {
211        match p.as_rule() {
212            Rule::expression => expr = Some(lower_expression(p)?),
213            Rule::variable => alias = Some(lower_variable(p)?),
214            _ => {}
215        }
216    }
217
218    Ok(Unwind {
219        expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
220        alias: alias.ok_or_else(|| ParseError::new("expected alias", span.start, span.end))?,
221        span,
222    })
223}
224
225fn lower_create(pair: Pair<Rule>) -> Result<Create, ParseError> {
226    let span = pair_span(&pair);
227    let pattern = pair
228        .into_inner()
229        .find(|p| p.as_rule() == Rule::pattern)
230        .ok_or_else(|| ParseError::new("expected pattern", span.start, span.end))?;
231
232    Ok(Create {
233        pattern: lower_pattern(pattern)?,
234        span,
235    })
236}
237
238fn lower_merge(pair: Pair<Rule>) -> Result<Merge, ParseError> {
239    let span = pair_span(&pair);
240    let mut pattern_part = None;
241    let mut actions = Vec::new();
242
243    for p in pair.into_inner() {
244        match p.as_rule() {
245            Rule::pattern_part => pattern_part = Some(lower_pattern_part(p)?),
246            Rule::merge_action => actions.push(lower_merge_action(p)?),
247            _ => {}
248        }
249    }
250
251    Ok(Merge {
252        pattern_part: pattern_part
253            .ok_or_else(|| ParseError::new("expected pattern part", span.start, span.end))?,
254        actions,
255        span,
256    })
257}
258
259fn lower_merge_action(pair: Pair<Rule>) -> Result<MergeAction, ParseError> {
260    let span = pair_span(&pair);
261    let mut on_match = false;
262    let mut set = None;
263
264    for p in pair.into_inner() {
265        match p.as_rule() {
266            Rule::MATCH => on_match = true,
267            Rule::CREATE => on_match = false,
268            Rule::set_clause => set = Some(lower_set(p)?),
269            _ => {}
270        }
271    }
272
273    Ok(MergeAction {
274        on_match,
275        set: set.ok_or_else(|| ParseError::new("expected SET clause", span.start, span.end))?,
276        span,
277    })
278}
279
280fn lower_delete(pair: Pair<Rule>) -> Result<Delete, ParseError> {
281    let span = pair_span(&pair);
282    let mut detach = false;
283    let mut expressions = Vec::new();
284
285    for p in pair.into_inner() {
286        match p.as_rule() {
287            Rule::DETACH => detach = true,
288            Rule::expression => expressions.push(lower_expression(p)?),
289            _ => {}
290        }
291    }
292
293    Ok(Delete {
294        detach,
295        expressions,
296        span,
297    })
298}
299
300fn lower_set(pair: Pair<Rule>) -> Result<Set, ParseError> {
301    let span = pair_span(&pair);
302    let mut items = Vec::new();
303
304    for p in pair.into_inner() {
305        if p.as_rule() == Rule::set_item {
306            items.push(lower_set_item(p)?);
307        }
308    }
309
310    Ok(Set { items, span })
311}
312fn lower_set_item(pair: Pair<Rule>) -> Result<SetItem, ParseError> {
313    let span = pair_span(&pair);
314    let inner: Vec<_> = pair.into_inner().collect();
315
316    match inner.as_slice() {
317        [var, labels]
318            if var.as_rule() == Rule::variable && labels.as_rule() == Rule::node_labels =>
319        {
320            let variable = lower_variable(var.clone())?;
321            let labels: Vec<String> = lower_node_labels(labels.clone())?
322                .into_iter()
323                .flat_map(|g| g.into_iter())
324                .collect();
325            Ok(SetItem::SetLabels {
326                variable,
327                labels,
328                span,
329            })
330        }
331
332        [var, op, value]
333            if var.as_rule() == Rule::variable
334                && op.as_rule() == Rule::plus_eq
335                && value.as_rule() == Rule::expression =>
336        {
337            let variable = lower_variable(var.clone())?;
338            let value = lower_expression(value.clone())?;
339            Ok(SetItem::MutateVariable {
340                variable,
341                value,
342                span,
343            })
344        }
345
346        [var, op, value]
347            if var.as_rule() == Rule::variable
348                && op.as_rule() == Rule::eq
349                && value.as_rule() == Rule::expression =>
350        {
351            let variable = lower_variable(var.clone())?;
352            let value = lower_expression(value.clone())?;
353            Ok(SetItem::SetVariable {
354                variable,
355                value,
356                span,
357            })
358        }
359
360        [target, op, value]
361            if target.as_rule() == Rule::property_set_target
362                && op.as_rule() == Rule::eq
363                && value.as_rule() == Rule::expression =>
364        {
365            let target = lower_property_set_target(target.clone())?;
366            let value = lower_expression(value.clone())?;
367            Ok(SetItem::SetProperty {
368                target,
369                value,
370                span,
371            })
372        }
373
374        _ => Err(ParseError::new("invalid SET item", span.start, span.end)),
375    }
376}
377
378fn lower_property_set_target(pair: Pair<Rule>) -> Result<Expr, ParseError> {
379    let span = pair_span(&pair);
380    let mut inner = pair.into_inner();
381
382    let first = inner
383        .next()
384        .ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
385    let mut expr = Expr::Variable(lower_variable(first)?);
386
387    for p in inner {
388        if p.as_rule() == Rule::property_lookup {
389            let p_span = pair_span(&p);
390            let key_pair = p
391                .into_inner()
392                .find(|q| q.as_rule() == Rule::property_key_name)
393                .ok_or_else(|| {
394                    ParseError::new("expected property key", p_span.start, p_span.end)
395                })?;
396
397            let key = lower_schema_name(key_pair)?;
398            let merged = merge_spans(expr.span(), p_span);
399
400            expr = Expr::Property {
401                expr: Box::new(expr),
402                key,
403                span: merged,
404            };
405        }
406    }
407
408    Ok(expr)
409}
410
411fn lower_remove(pair: Pair<Rule>) -> Result<Remove, ParseError> {
412    let span = pair_span(&pair);
413    let mut items = Vec::new();
414
415    for p in pair.into_inner() {
416        if p.as_rule() == Rule::remove_item {
417            items.push(lower_remove_item(p)?);
418        }
419    }
420
421    Ok(Remove { items, span })
422}
423
424fn lower_remove_item(pair: Pair<Rule>) -> Result<RemoveItem, ParseError> {
425    let span = pair_span(&pair);
426    let inner: Vec<_> = pair.into_inner().collect();
427
428    if inner.len() == 2
429        && inner[0].as_rule() == Rule::variable
430        && inner[1].as_rule() == Rule::node_labels
431    {
432        let variable = lower_variable(inner[0].clone())?;
433        let labels: Vec<String> = lower_node_labels(inner[1].clone())?
434            .into_iter()
435            .flat_map(|g| g.into_iter())
436            .collect();
437        return Ok(RemoveItem::Labels {
438            variable,
439            labels,
440            span,
441        });
442    }
443
444    if inner.len() == 1 {
445        return Ok(RemoveItem::Property {
446            expr: lower_expression(inner[0].clone())?,
447            span,
448        });
449    }
450
451    Err(ParseError::new("invalid REMOVE item", span.start, span.end))
452}
453
454fn lower_in_query_call(pair: Pair<Rule>) -> Result<InQueryCall, ParseError> {
455    let span = pair_span(&pair);
456    let mut procedure = None;
457    let mut yield_items = Vec::new();
458    let mut where_ = None;
459
460    for p in pair.into_inner() {
461        match p.as_rule() {
462            Rule::procedure_invocation => procedure = Some(lower_procedure_invocation(p)?),
463            Rule::yield_clause => {
464                let (items, _all) = lower_yield_clause(p)?;
465                yield_items = items;
466            }
467            Rule::where_clause => where_ = Some(lower_where_clause(p)?),
468            _ => {}
469        }
470    }
471
472    Ok(InQueryCall {
473        procedure: procedure.ok_or_else(|| {
474            ParseError::new("expected procedure invocation", span.start, span.end)
475        })?,
476        yield_items,
477        where_,
478        span,
479    })
480}
481
482fn lower_standalone_call(pair: Pair<Rule>) -> Result<StandaloneCall, ParseError> {
483    let span = pair_span(&pair);
484    let mut procedure = None;
485    let mut yield_items = Vec::new();
486    let mut yield_all = false;
487
488    for p in pair.into_inner() {
489        match p.as_rule() {
490            Rule::procedure_invocation => {
491                procedure = Some(ProcedureInvocationKind::Explicit(
492                    lower_procedure_invocation(p)?,
493                ));
494            }
495            Rule::procedure_name => {
496                procedure = Some(ProcedureInvocationKind::Implicit(lower_procedure_name(p)?));
497            }
498            Rule::yield_clause => {
499                let (items, all) = lower_yield_clause(p)?;
500                yield_items = items;
501                yield_all = all;
502            }
503            _ => {}
504        }
505    }
506
507    Ok(StandaloneCall {
508        procedure: procedure
509            .ok_or_else(|| ParseError::new("expected procedure", span.start, span.end))?,
510        yield_items,
511        yield_all,
512        span,
513    })
514}
515
516fn lower_procedure_invocation(pair: Pair<Rule>) -> Result<ProcedureInvocation, ParseError> {
517    let span = pair_span(&pair);
518    let mut name = None;
519    let mut args = Vec::new();
520
521    for p in pair.into_inner() {
522        match p.as_rule() {
523            Rule::procedure_name => name = Some(lower_procedure_name(p)?),
524            Rule::expression => args.push(lower_expression(p)?),
525            _ => {}
526        }
527    }
528
529    Ok(ProcedureInvocation {
530        name: name
531            .ok_or_else(|| ParseError::new("expected procedure name", span.start, span.end))?,
532        args,
533        span,
534    })
535}
536
537fn lower_procedure_name(pair: Pair<Rule>) -> Result<ProcedureName, ParseError> {
538    let span = pair_span(&pair);
539    let parts = lower_name_parts(pair)?;
540    Ok(ProcedureName { parts, span })
541}
542
543fn lower_yield_clause(pair: Pair<Rule>) -> Result<(Vec<YieldItem>, bool), ParseError> {
544    let mut items = Vec::new();
545    let mut all = false;
546
547    for p in pair.into_inner() {
548        match p.as_rule() {
549            Rule::STAR => all = true,
550            Rule::yield_items => {
551                for q in p.into_inner() {
552                    if q.as_rule() == Rule::yield_item {
553                        items.push(lower_yield_item(q)?);
554                    }
555                }
556            }
557            _ => {}
558        }
559    }
560
561    Ok((items, all))
562}
563
564fn lower_yield_item(pair: Pair<Rule>) -> Result<YieldItem, ParseError> {
565    let span = pair_span(&pair);
566    let mut symbolic = None;
567    let mut alias = None;
568
569    for p in pair.into_inner() {
570        match p.as_rule() {
571            Rule::symbolic_name => symbolic = Some(lower_symbolic_name(p)?),
572            Rule::variable => alias = Some(lower_variable(p)?),
573            _ => {}
574        }
575    }
576
577    match (symbolic, alias) {
578        (Some(field), Some(alias)) => Ok(YieldItem {
579            field: Some(field),
580            alias,
581            span,
582        }),
583        (Some(name), None) => Ok(YieldItem {
584            field: None,
585            alias: Variable { name, span },
586            span,
587        }),
588        _ => Err(ParseError::new("invalid YIELD item", span.start, span.end)),
589    }
590}
591
592fn lower_with_clause(pair: Pair<Rule>) -> Result<With, ParseError> {
593    let span = pair_span(&pair);
594    let mut body = None;
595    let mut where_ = None;
596
597    for p in pair.into_inner() {
598        match p.as_rule() {
599            Rule::projection_body => body = Some(lower_projection_body(p)?),
600            Rule::where_clause => where_ = Some(lower_where_clause(p)?),
601            _ => {}
602        }
603    }
604
605    Ok(With {
606        body: body
607            .ok_or_else(|| ParseError::new("expected projection body", span.start, span.end))?,
608        where_,
609        span,
610    })
611}
612
613fn lower_where_clause(pair: Pair<Rule>) -> Result<Expr, ParseError> {
614    let span = pair_span(&pair);
615
616    let expr = pair
617        .into_inner()
618        .find(|p| p.as_rule() == Rule::expression)
619        .ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
620
621    lower_expression(expr)
622}
623
624fn lower_return_clause(pair: Pair<Rule>) -> Result<Return, ParseError> {
625    let span = pair_span(&pair);
626    let body = pair
627        .into_inner()
628        .find(|p| p.as_rule() == Rule::projection_body)
629        .ok_or_else(|| ParseError::new("expected projection body", span.start, span.end))?;
630
631    Ok(Return {
632        body: lower_projection_body(body)?,
633        span,
634    })
635}
636
637fn lower_projection_body(pair: Pair<Rule>) -> Result<ProjectionBody, ParseError> {
638    let span = pair_span(&pair);
639    let mut distinct = false;
640    let mut items = Vec::new();
641    let mut order = Vec::new();
642    let mut skip = None;
643    let mut limit = None;
644
645    for p in pair.into_inner() {
646        match p.as_rule() {
647            Rule::DISTINCT => distinct = true,
648            Rule::projection_items => items = lower_projection_items(p)?,
649            Rule::order_clause => order = lower_order_clause(p)?,
650            Rule::skip_clause => {
651                let expr = p
652                    .into_inner()
653                    .find(|q| q.as_rule() == Rule::expression)
654                    .ok_or_else(|| {
655                        ParseError::new("expected skip expression", span.start, span.end)
656                    })?;
657                skip = Some(lower_expression(expr)?);
658            }
659            Rule::limit_clause => {
660                let expr = p
661                    .into_inner()
662                    .find(|q| q.as_rule() == Rule::expression)
663                    .ok_or_else(|| {
664                        ParseError::new("expected limit expression", span.start, span.end)
665                    })?;
666                limit = Some(lower_expression(expr)?);
667            }
668            _ => {}
669        }
670    }
671
672    Ok(ProjectionBody {
673        distinct,
674        items,
675        order,
676        skip,
677        limit,
678        span,
679    })
680}
681
682fn lower_projection_items(pair: Pair<Rule>) -> Result<Vec<ProjectionItem>, ParseError> {
683    let mut out = Vec::new();
684    for p in pair.into_inner() {
685        if p.as_rule() == Rule::projection_item {
686            out.push(lower_projection_item(p)?);
687        }
688    }
689    Ok(out)
690}
691
692fn lower_projection_item(pair: Pair<Rule>) -> Result<ProjectionItem, ParseError> {
693    let span = pair_span(&pair);
694    let mut expr = None;
695    let mut alias = None;
696    let mut saw_star = false;
697
698    for p in pair.into_inner() {
699        match p.as_rule() {
700            Rule::star => saw_star = true,
701            Rule::expression => expr = Some(lower_expression(p)?),
702            Rule::variable => alias = Some(lower_variable(p)?),
703            _ => {}
704        }
705    }
706
707    if saw_star {
708        return Ok(ProjectionItem::Star { span });
709    }
710
711    Ok(ProjectionItem::Expr {
712        expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
713        alias,
714        span,
715    })
716}
717
718fn lower_order_clause(pair: Pair<Rule>) -> Result<Vec<SortItem>, ParseError> {
719    let mut out = Vec::new();
720    for p in pair.into_inner() {
721        if p.as_rule() == Rule::sort_item {
722            out.push(lower_sort_item(p)?);
723        }
724    }
725    Ok(out)
726}
727
728fn lower_sort_item(pair: Pair<Rule>) -> Result<SortItem, ParseError> {
729    let span = pair_span(&pair);
730    let mut expr = None;
731    let mut direction = SortDirection::Asc;
732
733    for p in pair.into_inner() {
734        match p.as_rule() {
735            Rule::expression => expr = Some(lower_expression(p)?),
736            Rule::DESC | Rule::DESCENDING => direction = SortDirection::Desc,
737            Rule::ASC | Rule::ASCENDING => direction = SortDirection::Asc,
738            _ => {}
739        }
740    }
741
742    Ok(SortItem {
743        expr: expr.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
744        direction,
745        span,
746    })
747}
748
749fn lower_pattern(pair: Pair<Rule>) -> Result<Pattern, ParseError> {
750    let span = pair_span(&pair);
751    let mut parts = Vec::new();
752
753    for p in pair.into_inner() {
754        if p.as_rule() == Rule::pattern_part {
755            parts.push(lower_pattern_part(p)?);
756        }
757    }
758
759    Ok(Pattern { parts, span })
760}
761
762fn lower_pattern_part(pair: Pair<Rule>) -> Result<PatternPart, ParseError> {
763    let span = pair_span(&pair);
764    let mut binding = None;
765    let mut element = None;
766    let mut saw_eq = false;
767
768    for p in pair.into_inner() {
769        match p.as_rule() {
770            Rule::variable if !saw_eq && binding.is_none() => binding = Some(lower_variable(p)?),
771            Rule::eq => saw_eq = true,
772            Rule::anonymous_pattern_part => {
773                for inner in p.into_inner() {
774                    match inner.as_rule() {
775                        Rule::pattern_element => element = Some(lower_pattern_element(inner)?),
776                        Rule::shortest_path_pattern => {
777                            element = Some(lower_shortest_path_pattern(inner)?)
778                        }
779                        _ => {}
780                    }
781                }
782            }
783            Rule::pattern_element => element = Some(lower_pattern_element(p)?),
784            _ => {}
785        }
786    }
787
788    Ok(PatternPart {
789        binding,
790        element: element
791            .ok_or_else(|| ParseError::new("expected pattern element", span.start, span.end))?,
792        span,
793    })
794}
795
796fn lower_shortest_path_pattern(pair: Pair<Rule>) -> Result<PatternElement, ParseError> {
797    let span = pair_span(&pair);
798    let mut all = false;
799    let mut inner_element = None;
800
801    for p in pair.into_inner() {
802        match p.as_rule() {
803            Rule::SHORTEST_PATH => all = false,
804            Rule::ALL_SHORTEST_PATHS => all = true,
805            Rule::pattern_element => inner_element = Some(lower_pattern_element(p)?),
806            _ => {}
807        }
808    }
809
810    Ok(PatternElement::ShortestPath {
811        all,
812        element: Box::new(inner_element.ok_or_else(|| {
813            ParseError::new(
814                "expected pattern element in shortestPath",
815                span.start,
816                span.end,
817            )
818        })?),
819        span,
820    })
821}
822
823fn lower_pattern_element(pair: Pair<Rule>) -> Result<PatternElement, ParseError> {
824    let span = pair_span(&pair);
825    let mut inners = pair.into_inner().peekable();
826
827    let first = inners
828        .next()
829        .ok_or_else(|| ParseError::new("expected pattern element", span.start, span.end))?;
830
831    match first.as_rule() {
832        Rule::node_pattern => {
833            let head = lower_node_pattern(first)?;
834            let mut chain = Vec::new();
835
836            for p in inners {
837                if p.as_rule() == Rule::pattern_element_chain {
838                    chain.push(lower_pattern_element_chain(p)?);
839                }
840            }
841
842            Ok(PatternElement::NodeChain { head, chain, span })
843        }
844        Rule::pattern_element => {
845            let inner = lower_pattern_element(first)?;
846            Ok(PatternElement::Parenthesized(Box::new(inner), span))
847        }
848        _ => Err(unexpected_rule("pattern_element", first)),
849    }
850}
851
852fn lower_pattern_element_chain(pair: Pair<Rule>) -> Result<PatternElementChain, ParseError> {
853    let span = pair_span(&pair);
854    let mut relationship = None;
855    let mut node = None;
856
857    for p in pair.into_inner() {
858        match p.as_rule() {
859            Rule::relationship_pattern => relationship = Some(lower_relationship_pattern(p)?),
860            Rule::node_pattern => node = Some(lower_node_pattern(p)?),
861            _ => {}
862        }
863    }
864
865    Ok(PatternElementChain {
866        relationship: relationship.ok_or_else(|| {
867            ParseError::new("expected relationship pattern", span.start, span.end)
868        })?,
869        node: node.ok_or_else(|| ParseError::new("expected node pattern", span.start, span.end))?,
870        span,
871    })
872}
873
874fn lower_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, ParseError> {
875    let span = pair_span(&pair);
876    let mut variable = None;
877    let mut labels = SmallVec::new();
878    let mut properties = None;
879
880    for p in pair.into_inner() {
881        match p.as_rule() {
882            Rule::variable => variable = Some(lower_variable(p)?),
883            Rule::node_labels => labels = lower_node_labels(p)?,
884            Rule::properties => properties = Some(lower_properties(p)?),
885            _ => {}
886        }
887    }
888
889    Ok(NodePattern {
890        variable,
891        labels,
892        properties,
893        span,
894    })
895}
896
897fn lower_node_labels(pair: Pair<Rule>) -> Result<SmallVec<SmallVec<String, 2>, 2>, ParseError> {
898    let mut out: SmallVec<SmallVec<String, 2>, 2> = SmallVec::new();
899    for p in pair.into_inner() {
900        if p.as_rule() == Rule::node_label_set {
901            let mut group = SmallVec::new();
902            for q in p.into_inner() {
903                if q.as_rule() == Rule::label_name {
904                    group.push(lower_schema_name(q)?);
905                }
906            }
907            if !group.is_empty() {
908                out.push(group);
909            }
910        }
911    }
912    Ok(out)
913}
914
915fn lower_relationship_pattern(pair: Pair<Rule>) -> Result<RelationshipPattern, ParseError> {
916    let span = pair_span(&pair);
917    let mut left = false;
918    let mut right = false;
919    let mut detail = None;
920
921    for p in pair.into_inner() {
922        match p.as_rule() {
923            Rule::left_arrow => left = true,
924            Rule::right_arrow => right = true,
925            Rule::relationship_detail => detail = Some(lower_relationship_detail(p)?),
926            _ => {}
927        }
928    }
929
930    let direction = match (left, right) {
931        (true, false) => Direction::Left,
932        (false, true) => Direction::Right,
933        _ => Direction::Undirected,
934    };
935
936    Ok(RelationshipPattern {
937        direction,
938        detail,
939        span,
940    })
941}
942
943fn lower_relationship_detail(pair: Pair<Rule>) -> Result<RelationshipDetail, ParseError> {
944    let span = pair_span(&pair);
945    let mut variable = None;
946    let mut types = SmallVec::new();
947    let mut range = None;
948    let mut properties = None;
949
950    for p in pair.into_inner() {
951        match p.as_rule() {
952            Rule::variable => variable = Some(lower_variable(p)?),
953            Rule::relationship_types => types = lower_relationship_types(p)?,
954            Rule::range_literal => range = Some(lower_range_literal(p)?),
955            Rule::properties => properties = Some(lower_properties(p)?),
956            _ => {}
957        }
958    }
959
960    Ok(RelationshipDetail {
961        variable,
962        types,
963        range,
964        properties,
965        span,
966    })
967}
968
969fn lower_relationship_types(pair: Pair<Rule>) -> Result<SmallVec<String, 2>, ParseError> {
970    let mut out = SmallVec::new();
971    for p in pair.into_inner() {
972        if p.as_rule() == Rule::rel_type_name {
973            out.push(lower_schema_name(p)?);
974        }
975    }
976    Ok(out)
977}
978
979fn lower_range_literal(pair: Pair<Rule>) -> Result<RangeLiteral, ParseError> {
980    let span = pair_span(&pair);
981    let raw = pair.as_str().trim();
982    let body = raw.strip_prefix('*').unwrap_or(raw);
983
984    let (start, end) = if let Some((lhs, rhs)) = body.split_once("..") {
985        let start = if lhs.is_empty() {
986            None
987        } else {
988            Some(
989                lhs.parse::<u64>()
990                    .map_err(|_| ParseError::new("invalid range start", span.start, span.end))?,
991            )
992        };
993        let end = if rhs.is_empty() {
994            None
995        } else {
996            Some(
997                rhs.parse::<u64>()
998                    .map_err(|_| ParseError::new("invalid range end", span.start, span.end))?,
999            )
1000        };
1001        (start, end)
1002    } else if body.is_empty() {
1003        (None, None)
1004    } else {
1005        (
1006            Some(
1007                body.parse::<u64>()
1008                    .map_err(|_| ParseError::new("invalid range bound", span.start, span.end))?,
1009            ),
1010            None,
1011        )
1012    };
1013
1014    Ok(RangeLiteral { start, end, span })
1015}
1016
1017fn lower_properties(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1018    let inner = single_inner(pair)?;
1019    match inner.as_rule() {
1020        Rule::map_literal => lower_map_literal(inner),
1021        Rule::parameter => lower_parameter(inner),
1022        _ => Err(unexpected_rule("properties", inner)),
1023    }
1024}
1025
1026fn lower_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1027    match pair.as_rule() {
1028        Rule::expression => lower_expression(single_inner(pair)?),
1029        Rule::case_expression => lower_expression(single_inner(pair)?),
1030        Rule::simple_case_expression => lower_simple_case_expression(pair),
1031        Rule::generic_case_expression => lower_generic_case_expression(pair),
1032        Rule::or_expression => {
1033            lower_left_assoc(pair, lower_expression, &[(Rule::OR, BinaryOp::Or)])
1034        }
1035        Rule::xor_expression => {
1036            lower_left_assoc(pair, lower_expression, &[(Rule::XOR, BinaryOp::Xor)])
1037        }
1038        Rule::and_expression => {
1039            lower_left_assoc(pair, lower_expression, &[(Rule::AND, BinaryOp::And)])
1040        }
1041        Rule::not_expression => lower_not_expression(pair),
1042        Rule::comparison_expression => lower_comparison_expression(pair),
1043        Rule::add_expression => lower_add_expression(pair),
1044        Rule::mul_expression => lower_mul_expression(pair),
1045        Rule::pow_expression => lower_pow_expression(pair),
1046        Rule::unary_expression => lower_unary_expression(pair),
1047        Rule::postfix_expression => lower_postfix_expression(pair),
1048        Rule::atom => lower_expression(single_inner(pair)?),
1049        Rule::list_predicate => lower_list_predicate(pair),
1050        Rule::reduce_expression => lower_reduce_expression(pair),
1051        Rule::variable => Ok(Expr::Variable(lower_variable(pair)?)),
1052        Rule::literal => lower_literal(single_inner(pair)?),
1053        Rule::parameter => lower_parameter(pair),
1054        Rule::function_invocation => lower_function_invocation(pair),
1055        Rule::parenthesized_expression => {
1056            let span = pair_span(&pair);
1057            let expr = pair
1058                .into_inner()
1059                .find(|p| p.as_rule() == Rule::expression)
1060                .ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
1061            lower_expression(expr)
1062        }
1063        Rule::exists_subquery => lower_exists_subquery(pair),
1064        _ => Err(unexpected_rule("expression", pair)),
1065    }
1066}
1067
1068fn lower_left_assoc(
1069    pair: Pair<Rule>,
1070    recurse: fn(Pair<Rule>) -> Result<Expr, ParseError>,
1071    ops: &[(Rule, BinaryOp)],
1072) -> Result<Expr, ParseError> {
1073    let span = pair_span(&pair);
1074    let mut inner = pair.into_inner();
1075    let first = inner
1076        .next()
1077        .ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
1078    let mut expr = recurse(first)?;
1079
1080    while let Some(op_pair) = inner.next() {
1081        let rhs_pair = inner
1082            .next()
1083            .ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
1084        let op = ops
1085            .iter()
1086            .find_map(|(r, op)| {
1087                if *r == op_pair.as_rule() {
1088                    Some(*op)
1089                } else {
1090                    None
1091                }
1092            })
1093            .ok_or_else(|| {
1094                ParseError::new(
1095                    "unknown operator",
1096                    op_pair.as_span().start(),
1097                    op_pair.as_span().end(),
1098                )
1099            })?;
1100
1101        let rhs = recurse(rhs_pair)?;
1102        let merged = merge_spans(expr.span(), rhs.span());
1103        expr = Expr::Binary {
1104            lhs: Box::new(expr),
1105            op,
1106            rhs: Box::new(rhs),
1107            span: merged,
1108        };
1109    }
1110
1111    Ok(expr)
1112}
1113
1114fn lower_not_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1115    let span = pair_span(&pair);
1116    let mut not_count = 0usize;
1117    let mut tail = None;
1118
1119    for p in pair.into_inner() {
1120        match p.as_rule() {
1121            Rule::NOT => not_count += 1,
1122            _ => tail = Some(p),
1123        }
1124    }
1125
1126    let mut expr = lower_expression(
1127        tail.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
1128    )?;
1129
1130    for _ in 0..not_count {
1131        expr = Expr::Unary {
1132            op: UnaryOp::Not,
1133            expr: Box::new(expr),
1134            span,
1135        };
1136    }
1137
1138    Ok(expr)
1139}
1140
1141fn lower_comparison_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1142    let span = pair_span(&pair);
1143    let mut inner = pair.into_inner();
1144
1145    let first = inner
1146        .next()
1147        .ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
1148    let mut expr = lower_expression(first)?;
1149
1150    for tail in inner {
1151        if tail.as_rule() != Rule::comparison_tail {
1152            continue;
1153        }
1154
1155        let tail_span = pair_span(&tail);
1156        let parts: Vec<_> = tail.into_inner().collect();
1157
1158        let (op, rhs): (BinaryOp, Option<Expr>) = match parts.as_slice() {
1159            [p0, p1] if p0.as_rule() == Rule::comparison_op => {
1160                let op = match p0.as_str() {
1161                    "=" => BinaryOp::Eq,
1162                    "<>" => BinaryOp::Ne,
1163                    "<" => BinaryOp::Lt,
1164                    ">" => BinaryOp::Gt,
1165                    "<=" => BinaryOp::Le,
1166                    ">=" => BinaryOp::Ge,
1167                    _ => {
1168                        return Err(ParseError::new(
1169                            "unknown comparison operator",
1170                            tail_span.start,
1171                            tail_span.end,
1172                        ))
1173                    }
1174                };
1175                (op, Some(lower_expression(p1.clone())?))
1176            }
1177            [p0, p1] if p0.as_rule() == Rule::IN => {
1178                (BinaryOp::In, Some(lower_expression(p1.clone())?))
1179            }
1180            [p0, p1, p2] if p0.as_rule() == Rule::STARTS && p1.as_rule() == Rule::WITH => {
1181                (BinaryOp::StartsWith, Some(lower_expression(p2.clone())?))
1182            }
1183            [p0, p1, p2] if p0.as_rule() == Rule::ENDS && p1.as_rule() == Rule::WITH => {
1184                (BinaryOp::EndsWith, Some(lower_expression(p2.clone())?))
1185            }
1186            [p0, p1] if p0.as_rule() == Rule::CONTAINS => {
1187                (BinaryOp::Contains, Some(lower_expression(p1.clone())?))
1188            }
1189            [p0, p1] if p0.as_rule() == Rule::IS && p1.as_rule() == Rule::NULL => {
1190                (BinaryOp::IsNull, None)
1191            }
1192            [p0, p1, p2]
1193                if p0.as_rule() == Rule::IS
1194                    && p1.as_rule() == Rule::NOT
1195                    && p2.as_rule() == Rule::NULL =>
1196            {
1197                (BinaryOp::IsNotNull, None)
1198            }
1199            [p0, p1] if p0.as_rule() == Rule::regex_match => {
1200                (BinaryOp::RegexMatch, Some(lower_expression(p1.clone())?))
1201            }
1202            _ => {
1203                return Err(ParseError::new(
1204                    "invalid comparison tail",
1205                    tail_span.start,
1206                    tail_span.end,
1207                ))
1208            }
1209        };
1210
1211        match rhs {
1212            Some(rhs) => {
1213                let merged = merge_spans(expr.span(), rhs.span());
1214                expr = Expr::Binary {
1215                    lhs: Box::new(expr),
1216                    op,
1217                    rhs: Box::new(rhs),
1218                    span: merged,
1219                };
1220            }
1221            None => {
1222                let rhs = Expr::Null(tail_span);
1223                let merged = merge_spans(expr.span(), rhs.span());
1224                expr = Expr::Binary {
1225                    lhs: Box::new(expr),
1226                    op,
1227                    rhs: Box::new(rhs),
1228                    span: merged,
1229                };
1230            }
1231        }
1232    }
1233
1234    Ok(expr)
1235}
1236
1237fn lower_add_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1238    let span = pair_span(&pair);
1239    let mut inner = pair.into_inner();
1240    let first = inner
1241        .next()
1242        .ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
1243    let mut expr = lower_expression(first)?;
1244
1245    while let Some(op_pair) = inner.next() {
1246        let rhs_pair = inner
1247            .next()
1248            .ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
1249        let op = match op_pair.as_rule() {
1250            Rule::add => BinaryOp::Add,
1251            Rule::sub => BinaryOp::Sub,
1252            _ => return Err(unexpected_rule("add/sub op", op_pair)),
1253        };
1254
1255        let rhs = lower_expression(rhs_pair)?;
1256        let merged = merge_spans(expr.span(), rhs.span());
1257        expr = Expr::Binary {
1258            lhs: Box::new(expr),
1259            op,
1260            rhs: Box::new(rhs),
1261            span: merged,
1262        };
1263    }
1264
1265    Ok(expr)
1266}
1267
1268fn lower_mul_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1269    let span = pair_span(&pair);
1270    let mut inner = pair.into_inner();
1271    let first = inner
1272        .next()
1273        .ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
1274    let mut expr = lower_expression(first)?;
1275
1276    while let Some(op_pair) = inner.next() {
1277        let rhs_pair = inner
1278            .next()
1279            .ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
1280        let op = match op_pair.as_rule() {
1281            Rule::mul => BinaryOp::Mul,
1282            Rule::div => BinaryOp::Div,
1283            Rule::modulo => BinaryOp::Mod,
1284            _ => return Err(unexpected_rule("mul/div/mod op", op_pair)),
1285        };
1286
1287        let rhs = lower_expression(rhs_pair)?;
1288        let merged = merge_spans(expr.span(), rhs.span());
1289        expr = Expr::Binary {
1290            lhs: Box::new(expr),
1291            op,
1292            rhs: Box::new(rhs),
1293            span: merged,
1294        };
1295    }
1296
1297    Ok(expr)
1298}
1299
1300fn lower_pow_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1301    let span = pair_span(&pair);
1302    let mut inner = pair.into_inner();
1303    let first = inner
1304        .next()
1305        .ok_or_else(|| ParseError::new("expected lhs", span.start, span.end))?;
1306    let mut expr = lower_expression(first)?;
1307
1308    while let Some(_pow) = inner.next() {
1309        let rhs_pair = inner
1310            .next()
1311            .ok_or_else(|| ParseError::new("expected rhs", span.start, span.end))?;
1312        let rhs = lower_expression(rhs_pair)?;
1313        let merged = merge_spans(expr.span(), rhs.span());
1314        expr = Expr::Binary {
1315            lhs: Box::new(expr),
1316            op: BinaryOp::Pow,
1317            rhs: Box::new(rhs),
1318            span: merged,
1319        };
1320    }
1321
1322    Ok(expr)
1323}
1324
1325fn lower_unary_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1326    let span = pair_span(&pair);
1327    let mut ops = Vec::new();
1328    let mut tail = None;
1329
1330    for p in pair.into_inner() {
1331        match p.as_rule() {
1332            Rule::add => ops.push(UnaryOp::Pos),
1333            Rule::sub => ops.push(UnaryOp::Neg),
1334            _ => tail = Some(p),
1335        }
1336    }
1337
1338    let mut expr = lower_expression(
1339        tail.ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?,
1340    )?;
1341
1342    for op in ops.into_iter().rev() {
1343        expr = Expr::Unary {
1344            op,
1345            expr: Box::new(expr),
1346            span,
1347        };
1348    }
1349
1350    Ok(expr)
1351}
1352
1353fn lower_postfix_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1354    let span = pair_span(&pair);
1355    let mut inner = pair.into_inner();
1356
1357    let atom = inner
1358        .next()
1359        .ok_or_else(|| ParseError::new("expected atom", span.start, span.end))?;
1360
1361    let mut expr = lower_expression(atom)?;
1362
1363    for p in inner {
1364        match p.as_rule() {
1365            Rule::postfix_op => {
1366                let inner_pair = single_inner(p)?;
1367                match inner_pair.as_rule() {
1368                    Rule::property_lookup => {
1369                        let p_span = pair_span(&inner_pair);
1370                        let key_pair = inner_pair
1371                            .into_inner()
1372                            .find(|q| q.as_rule() == Rule::property_key_name)
1373                            .ok_or_else(|| {
1374                                ParseError::new("expected property key", p_span.start, p_span.end)
1375                            })?;
1376                        let key = lower_schema_name(key_pair)?;
1377                        let merged = merge_spans(expr.span(), p_span);
1378                        expr = Expr::Property {
1379                            expr: Box::new(expr),
1380                            key,
1381                            span: merged,
1382                        };
1383                    }
1384                    Rule::map_projection_postfix => {
1385                        let mp_span = pair_span(&inner_pair);
1386                        let mut selectors = Vec::new();
1387                        for sel_pair in inner_pair.into_inner() {
1388                            if sel_pair.as_rule() == Rule::map_projection_selector {
1389                                selectors.push(lower_map_projection_selector(sel_pair)?);
1390                            }
1391                        }
1392                        let merged = merge_spans(expr.span(), mp_span);
1393                        expr = Expr::MapProjection {
1394                            base: Box::new(expr),
1395                            selectors,
1396                            span: merged,
1397                        };
1398                    }
1399                    Rule::index_or_slice => {
1400                        let is_span = pair_span(&inner_pair);
1401                        let inner_op = single_inner(inner_pair)?;
1402                        match inner_op.as_rule() {
1403                            Rule::slice_op => {
1404                                let parts: Vec<_> = inner_op.into_inner().collect();
1405                                let mut from_expr = None;
1406                                let mut to_expr = None;
1407                                let mut seen_dots = false;
1408                                for p in parts {
1409                                    if p.as_rule() == Rule::slice_dots {
1410                                        seen_dots = true;
1411                                    } else if p.as_rule() == Rule::expression {
1412                                        if !seen_dots {
1413                                            from_expr = Some(Box::new(lower_expression(p)?));
1414                                        } else {
1415                                            to_expr = Some(Box::new(lower_expression(p)?));
1416                                        }
1417                                    }
1418                                }
1419                                let merged = merge_spans(expr.span(), is_span);
1420                                expr = Expr::Slice {
1421                                    expr: Box::new(expr),
1422                                    from: from_expr,
1423                                    to: to_expr,
1424                                    span: merged,
1425                                };
1426                            }
1427                            Rule::index_op => {
1428                                let idx_pair = inner_op
1429                                    .into_inner()
1430                                    .find(|p| p.as_rule() == Rule::expression);
1431                                if let Some(idx) = idx_pair {
1432                                    let merged = merge_spans(expr.span(), is_span);
1433                                    expr = Expr::Index {
1434                                        expr: Box::new(expr),
1435                                        index: Box::new(lower_expression(idx)?),
1436                                        span: merged,
1437                                    };
1438                                }
1439                            }
1440                            _ => {}
1441                        }
1442                    }
1443                    _ => {}
1444                }
1445            }
1446            Rule::property_lookup => {
1447                let p_span = pair_span(&p);
1448                let key_pair = p
1449                    .into_inner()
1450                    .find(|q| q.as_rule() == Rule::property_key_name)
1451                    .ok_or_else(|| {
1452                        ParseError::new("expected property key", p_span.start, p_span.end)
1453                    })?;
1454                let key = lower_schema_name(key_pair)?;
1455                let merged = merge_spans(expr.span(), p_span);
1456                expr = Expr::Property {
1457                    expr: Box::new(expr),
1458                    key,
1459                    span: merged,
1460                };
1461            }
1462            _ => {}
1463        }
1464    }
1465
1466    Ok(expr)
1467}
1468
1469fn lower_map_projection_selector(pair: Pair<Rule>) -> Result<MapProjectionSelector, ParseError> {
1470    let parts: Vec<_> = pair.into_inner().collect();
1471
1472    // .* (dot + STAR)
1473    if parts.len() == 1 && parts[0].as_rule() == Rule::STAR {
1474        return Ok(MapProjectionSelector::AllProperties);
1475    }
1476
1477    // .name (dot is consumed, only property_key_name remains)
1478    if parts.len() == 1 && parts[0].as_rule() == Rule::property_key_name {
1479        let name = lower_schema_name(parts[0].clone())?;
1480        return Ok(MapProjectionSelector::Property(name));
1481    }
1482
1483    // key: expr (property_key_name + expression)
1484    if parts.len() >= 2 && parts[0].as_rule() == Rule::property_key_name {
1485        let key = lower_schema_name(parts[0].clone())?;
1486        let expr_pair = parts.into_iter().find(|p| p.as_rule() == Rule::expression);
1487        if let Some(ep) = expr_pair {
1488            return Ok(MapProjectionSelector::Literal(key, lower_expression(ep)?));
1489        }
1490    }
1491
1492    Err(ParseError::new("invalid map projection selector", 0, 0))
1493}
1494
1495fn lower_simple_case_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1496    let span = pair_span(&pair);
1497    let mut inner = pair.into_inner();
1498
1499    let first = inner
1500        .next()
1501        .ok_or_else(|| ParseError::new("expected CASE", span.start, span.end))?;
1502    if first.as_rule() != Rule::CASE {
1503        return Err(unexpected_rule("CASE", first));
1504    }
1505
1506    let input_pair = inner
1507        .next()
1508        .ok_or_else(|| ParseError::new("expected CASE input expression", span.start, span.end))?;
1509    let input = lower_expression(input_pair)?;
1510
1511    let mut alternatives = Vec::new();
1512    let mut else_expr = None;
1513
1514    while let Some(p) = inner.next() {
1515        match p.as_rule() {
1516            Rule::WHEN => {
1517                let when_expr = inner
1518                    .next()
1519                    .ok_or_else(|| {
1520                        ParseError::new("expected WHEN expression", span.start, span.end)
1521                    })
1522                    .and_then(lower_expression)?;
1523
1524                let then_kw = inner
1525                    .next()
1526                    .ok_or_else(|| ParseError::new("expected THEN", span.start, span.end))?;
1527                if then_kw.as_rule() != Rule::THEN {
1528                    return Err(unexpected_rule("THEN", then_kw));
1529                }
1530
1531                let then_expr = inner
1532                    .next()
1533                    .ok_or_else(|| {
1534                        ParseError::new("expected THEN expression", span.start, span.end)
1535                    })
1536                    .and_then(lower_expression)?;
1537
1538                alternatives.push((when_expr, then_expr));
1539            }
1540            Rule::ELSE => {
1541                let expr = inner
1542                    .next()
1543                    .ok_or_else(|| {
1544                        ParseError::new("expected ELSE expression", span.start, span.end)
1545                    })
1546                    .and_then(lower_expression)?;
1547                else_expr = Some(Box::new(expr));
1548            }
1549            Rule::END => {}
1550            _ => {}
1551        }
1552    }
1553
1554    Ok(Expr::Case {
1555        input: Some(Box::new(input)),
1556        alternatives,
1557        else_expr,
1558        span,
1559    })
1560}
1561
1562fn lower_generic_case_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1563    let span = pair_span(&pair);
1564    let mut inner = pair.into_inner();
1565
1566    let mut alternatives = Vec::new();
1567    let mut else_expr = None;
1568
1569    while let Some(p) = inner.next() {
1570        match p.as_rule() {
1571            Rule::WHEN => {
1572                let when_expr = inner
1573                    .next()
1574                    .ok_or_else(|| {
1575                        ParseError::new("expected WHEN expression", span.start, span.end)
1576                    })
1577                    .and_then(lower_expression)?;
1578
1579                let then_kw = inner
1580                    .next()
1581                    .ok_or_else(|| ParseError::new("expected THEN", span.start, span.end))?;
1582                if then_kw.as_rule() != Rule::THEN {
1583                    return Err(unexpected_rule("THEN", then_kw));
1584                }
1585
1586                let then_expr = inner
1587                    .next()
1588                    .ok_or_else(|| {
1589                        ParseError::new("expected THEN expression", span.start, span.end)
1590                    })
1591                    .and_then(lower_expression)?;
1592
1593                alternatives.push((when_expr, then_expr));
1594            }
1595            Rule::ELSE => {
1596                let expr = inner
1597                    .next()
1598                    .ok_or_else(|| {
1599                        ParseError::new("expected ELSE expression", span.start, span.end)
1600                    })
1601                    .and_then(lower_expression)?;
1602                else_expr = Some(Box::new(expr));
1603            }
1604            Rule::END => {}
1605            _ => {}
1606        }
1607    }
1608
1609    Ok(Expr::Case {
1610        input: None,
1611        alternatives,
1612        else_expr,
1613        span,
1614    })
1615}
1616
1617fn lower_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1618    match pair.as_rule() {
1619        Rule::number_literal => lower_literal(single_inner(pair)?),
1620        Rule::integer_literal => {
1621            let v = lower_integer_literal(pair.clone())?;
1622            Ok(Expr::Integer(v, pair_span(&pair)))
1623        }
1624        Rule::double_literal => {
1625            let s = pair.as_str();
1626            let v: f64 = s.parse().map_err(|_| {
1627                ParseError::new(
1628                    "invalid float",
1629                    pair.as_span().start(),
1630                    pair.as_span().end(),
1631                )
1632            })?;
1633            Ok(Expr::Float(v, pair_span(&pair)))
1634        }
1635        Rule::string_literal => lower_string_literal(pair),
1636        Rule::boolean_literal => {
1637            let s = pair.as_str();
1638            Ok(Expr::Bool(s.eq_ignore_ascii_case("true"), pair_span(&pair)))
1639        }
1640        Rule::null_literal => Ok(Expr::Null(pair_span(&pair))),
1641        Rule::map_literal => lower_map_literal(pair),
1642        Rule::list_literal => lower_list_literal(pair),
1643        _ => Err(unexpected_rule("literal", pair)),
1644    }
1645}
1646
1647fn lower_string_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1648    let span = pair_span(&pair);
1649    let inner_pair = single_inner(pair)?;
1650    let raw = inner_pair.as_str();
1651
1652    let inner = &raw[1..raw.len() - 1];
1653    let mut out = String::new();
1654    let mut chars = inner.chars();
1655
1656    while let Some(ch) = chars.next() {
1657        if ch == '\\' {
1658            let esc = chars
1659                .next()
1660                .ok_or_else(|| ParseError::new("invalid escape", span.start, span.end))?;
1661            match esc {
1662                '\\' => out.push('\\'),
1663                '\'' => out.push('\''),
1664                '"' => out.push('"'),
1665                'n' => out.push('\n'),
1666                'r' => out.push('\r'),
1667                't' => out.push('\t'),
1668                other => out.push(other),
1669            }
1670        } else {
1671            out.push(ch);
1672        }
1673    }
1674
1675    Ok(Expr::String(out, span))
1676}
1677
1678fn lower_map_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1679    let span = pair_span(&pair);
1680    let mut items = Vec::new();
1681    let mut key: Option<String> = None;
1682
1683    for p in pair.into_inner() {
1684        match p.as_rule() {
1685            Rule::property_key_name => key = Some(lower_schema_name(p)?),
1686            Rule::expression => {
1687                let k = key
1688                    .take()
1689                    .ok_or_else(|| ParseError::new("expected map key", span.start, span.end))?;
1690                items.push((k, lower_expression(p)?));
1691            }
1692            _ => {}
1693        }
1694    }
1695
1696    Ok(Expr::Map(items, span))
1697}
1698
1699fn lower_list_literal(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1700    let span = pair_span(&pair);
1701    let inner: Vec<_> = pair.into_inner().collect();
1702
1703    if inner.len() == 1 && inner[0].as_rule() == Rule::list_comprehension {
1704        return lower_list_comprehension(inner.into_iter().next().unwrap(), span);
1705    }
1706
1707    if inner.len() == 1 && inner[0].as_rule() == Rule::pattern_comprehension {
1708        return lower_pattern_comprehension(inner.into_iter().next().unwrap(), span);
1709    }
1710
1711    let mut items = Vec::new();
1712    for p in inner {
1713        if p.as_rule() == Rule::expression {
1714            items.push(lower_expression(p)?);
1715        }
1716    }
1717
1718    Ok(Expr::List(items, span))
1719}
1720
1721fn lower_pattern_comprehension(pair: Pair<Rule>, outer_span: Span) -> Result<Expr, ParseError> {
1722    let mut pattern = None;
1723    let mut where_ = None;
1724    let mut map_expr = None;
1725
1726    let mut inner = pair.into_inner().peekable();
1727    while let Some(p) = inner.next() {
1728        match p.as_rule() {
1729            Rule::pattern_element => {
1730                pattern = Some(lower_pattern_element(p)?);
1731            }
1732            Rule::WHERE => {
1733                let expr_pair = inner.next().ok_or_else(|| {
1734                    ParseError::new(
1735                        "expected WHERE expression",
1736                        outer_span.start,
1737                        outer_span.end,
1738                    )
1739                })?;
1740                where_ = Some(Box::new(lower_expression(expr_pair)?));
1741            }
1742            Rule::expression => {
1743                map_expr = Some(Box::new(lower_expression(p)?));
1744            }
1745            _ => {}
1746        }
1747    }
1748
1749    Ok(Expr::PatternComprehension {
1750        pattern: Box::new(pattern.ok_or_else(|| {
1751            ParseError::new(
1752                "expected pattern in comprehension",
1753                outer_span.start,
1754                outer_span.end,
1755            )
1756        })?),
1757        where_,
1758        map_expr: map_expr.ok_or_else(|| {
1759            ParseError::new(
1760                "expected map expression after |",
1761                outer_span.start,
1762                outer_span.end,
1763            )
1764        })?,
1765        span: outer_span,
1766    })
1767}
1768
1769fn lower_list_comprehension(pair: Pair<Rule>, outer_span: Span) -> Result<Expr, ParseError> {
1770    let mut inner = pair.into_inner();
1771
1772    let var_pair = inner
1773        .next()
1774        .ok_or_else(|| ParseError::new("expected variable", outer_span.start, outer_span.end))?;
1775    let variable = lower_variable(var_pair)?;
1776
1777    // skip IN
1778    let _in_kw = inner.next();
1779
1780    let list_pair = inner.next().ok_or_else(|| {
1781        ParseError::new("expected list expression", outer_span.start, outer_span.end)
1782    })?;
1783    let list = lower_expression(list_pair)?;
1784
1785    let mut filter = None;
1786    let mut map_expr = None;
1787
1788    // Remaining tokens: optional WHERE expr, optional | expr
1789    while let Some(p) = inner.next() {
1790        match p.as_rule() {
1791            Rule::WHERE => {
1792                let filter_pair = inner.next().ok_or_else(|| {
1793                    ParseError::new(
1794                        "expected filter expression",
1795                        outer_span.start,
1796                        outer_span.end,
1797                    )
1798                })?;
1799                filter = Some(Box::new(lower_expression(filter_pair)?));
1800            }
1801            Rule::pipe => {
1802                let map_pair = inner.next().ok_or_else(|| {
1803                    ParseError::new("expected map expression", outer_span.start, outer_span.end)
1804                })?;
1805                map_expr = Some(Box::new(lower_expression(map_pair)?));
1806            }
1807            _ if p.as_rule() == Rule::expression => {
1808                // This is the map expression after pipe was consumed silently
1809                map_expr = Some(Box::new(lower_expression(p)?));
1810            }
1811            _ => {}
1812        }
1813    }
1814
1815    Ok(Expr::ListComprehension {
1816        variable,
1817        list: Box::new(list),
1818        filter,
1819        map_expr,
1820        span: outer_span,
1821    })
1822}
1823
1824fn lower_parameter(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1825    let span = pair_span(&pair);
1826    let raw = pair.as_str();
1827    Ok(Expr::Parameter(raw[1..].to_string(), span))
1828}
1829
1830fn lower_list_predicate(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1831    let span = pair_span(&pair);
1832    let mut inner = pair.into_inner();
1833
1834    let kind_pair = inner
1835        .next()
1836        .ok_or_else(|| ParseError::new("expected list predicate kind", span.start, span.end))?;
1837    let kind = match kind_pair.as_rule() {
1838        Rule::ANY_ => ListPredicateKind::Any,
1839        Rule::ALL => ListPredicateKind::All,
1840        Rule::NONE => ListPredicateKind::None,
1841        Rule::SINGLE => ListPredicateKind::Single,
1842        _ => {
1843            return Err(ParseError::new(
1844                "expected ANY/ALL/NONE/SINGLE",
1845                span.start,
1846                span.end,
1847            ))
1848        }
1849    };
1850
1851    let var_pair = inner
1852        .next()
1853        .ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
1854    let variable = lower_variable(var_pair)?;
1855
1856    // skip IN keyword
1857    let _in_kw = inner.next();
1858
1859    let list_pair = inner
1860        .next()
1861        .ok_or_else(|| ParseError::new("expected list expression", span.start, span.end))?;
1862    let list = lower_expression(list_pair)?;
1863
1864    // skip WHERE keyword
1865    let _where_kw = inner.next();
1866
1867    let pred_pair = inner
1868        .next()
1869        .ok_or_else(|| ParseError::new("expected predicate expression", span.start, span.end))?;
1870    let predicate = lower_expression(pred_pair)?;
1871
1872    Ok(Expr::ListPredicate {
1873        kind,
1874        variable,
1875        list: Box::new(list),
1876        predicate: Box::new(predicate),
1877        span,
1878    })
1879}
1880
1881fn lower_reduce_expression(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1882    let span = pair_span(&pair);
1883    let mut inner = pair.into_inner();
1884
1885    // skip REDUCE keyword
1886    let _reduce_kw = inner.next();
1887
1888    let acc_pair = inner
1889        .next()
1890        .ok_or_else(|| ParseError::new("expected accumulator variable", span.start, span.end))?;
1891    let accumulator = lower_variable(acc_pair)?;
1892
1893    // skip = (eq)
1894    let _eq = inner.next();
1895
1896    let init_pair = inner
1897        .next()
1898        .ok_or_else(|| ParseError::new("expected init expression", span.start, span.end))?;
1899    let init = lower_expression(init_pair)?;
1900
1901    // skip comma (already consumed by pest)
1902
1903    let var_pair = inner
1904        .next()
1905        .ok_or_else(|| ParseError::new("expected variable", span.start, span.end))?;
1906    let variable = lower_variable(var_pair)?;
1907
1908    // skip IN
1909    let _in_kw = inner.next();
1910
1911    let list_pair = inner
1912        .next()
1913        .ok_or_else(|| ParseError::new("expected list expression", span.start, span.end))?;
1914    let list = lower_expression(list_pair)?;
1915
1916    // skip pipe (already consumed by pest)
1917
1918    let expr_pair = inner
1919        .next()
1920        .ok_or_else(|| ParseError::new("expected reduce expression", span.start, span.end))?;
1921    let expr = lower_expression(expr_pair)?;
1922
1923    Ok(Expr::Reduce {
1924        accumulator,
1925        init: Box::new(init),
1926        variable,
1927        list: Box::new(list),
1928        expr: Box::new(expr),
1929        span,
1930    })
1931}
1932
1933fn lower_exists_subquery(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1934    let span = pair_span(&pair);
1935    let mut pattern = None;
1936    let mut where_ = None;
1937
1938    for p in pair.into_inner() {
1939        match p.as_rule() {
1940            Rule::pattern => pattern = Some(lower_pattern(p)?),
1941            Rule::where_clause => {
1942                let inner = p
1943                    .into_inner()
1944                    .find(|q| q.as_rule() == Rule::expression)
1945                    .ok_or_else(|| ParseError::new("expected expression", span.start, span.end))?;
1946                where_ = Some(Box::new(lower_expression(inner)?));
1947            }
1948            _ => {}
1949        }
1950    }
1951
1952    Ok(Expr::ExistsSubquery {
1953        pattern: pattern
1954            .ok_or_else(|| ParseError::new("expected pattern in EXISTS", span.start, span.end))?,
1955        where_,
1956        span,
1957    })
1958}
1959
1960fn lower_function_invocation(pair: Pair<Rule>) -> Result<Expr, ParseError> {
1961    let span = pair_span(&pair);
1962    let mut name = Vec::new();
1963    let mut distinct = false;
1964    let mut args = Vec::new();
1965    let mut star = false;
1966
1967    for p in pair.into_inner() {
1968        match p.as_rule() {
1969            Rule::function_name => name = lower_name_parts(p)?,
1970            Rule::DISTINCT => distinct = true,
1971            Rule::STAR => star = true,
1972            Rule::expression => args.push(lower_expression(p)?),
1973            _ => {}
1974        }
1975    }
1976
1977    // count(*) is represented as a FunctionCall with empty args.
1978    // The executor already handles count with no args as count-star
1979    // (counting all rows regardless of null values).
1980    // Reject * for non-count functions.
1981    if star && !name.iter().any(|n| n.eq_ignore_ascii_case("count")) {
1982        return Err(ParseError::new(
1983            format!(
1984                "* is only valid as an argument to count(), not {}()",
1985                name.join(".")
1986            ),
1987            span.start,
1988            span.end,
1989        ));
1990    }
1991
1992    Ok(Expr::FunctionCall {
1993        name,
1994        distinct,
1995        args,
1996        span,
1997    })
1998}
1999
2000fn lower_name_parts(pair: Pair<Rule>) -> Result<Vec<String>, ParseError> {
2001    let mut parts = Vec::new();
2002    for p in pair.into_inner() {
2003        match p.as_rule() {
2004            Rule::namespace => {
2005                for q in p.into_inner() {
2006                    if q.as_rule() == Rule::symbolic_name {
2007                        parts.push(lower_symbolic_name(q)?);
2008                    }
2009                }
2010            }
2011            Rule::symbolic_name => parts.push(lower_symbolic_name(p)?),
2012            _ => {}
2013        }
2014    }
2015    Ok(parts)
2016}
2017
2018fn lower_variable(pair: Pair<Rule>) -> Result<Variable, ParseError> {
2019    let span = pair_span(&pair);
2020    let name_pair = single_inner(pair)?;
2021    Ok(Variable {
2022        name: lower_symbolic_name(name_pair)?,
2023        span,
2024    })
2025}
2026
2027fn lower_symbolic_name(pair: Pair<Rule>) -> Result<String, ParseError> {
2028    match pair.as_rule() {
2029        Rule::symbolic_name => lower_symbolic_name(single_inner(pair)?),
2030        Rule::unescaped_symbolic_name => Ok(pair.as_str().to_string()),
2031        Rule::escaped_symbolic_name => {
2032            let s = pair.as_str();
2033            Ok(s[1..s.len() - 1].to_string())
2034        }
2035        Rule::COUNT | Rule::ANY_ | Rule::NONE | Rule::SINGLE => Ok(pair.as_str().to_string()),
2036        _ => Err(unexpected_rule("symbolic_name", pair)),
2037    }
2038}
2039
2040fn lower_schema_name(pair: Pair<Rule>) -> Result<String, ParseError> {
2041    match pair.as_rule() {
2042        Rule::schema_name => lower_schema_name(single_inner(pair)?),
2043        Rule::symbolic_name => lower_symbolic_name(pair),
2044        Rule::reserved_word => Ok(pair.as_str().to_string()),
2045        Rule::label_name | Rule::rel_type_name | Rule::property_key_name => {
2046            lower_schema_name(single_inner(pair)?)
2047        }
2048        _ => Err(unexpected_rule("schema_name", pair)),
2049    }
2050}
2051
2052fn lower_integer_literal(pair: Pair<Rule>) -> Result<i64, ParseError> {
2053    match pair.as_rule() {
2054        Rule::integer_literal => lower_integer_literal(single_inner(pair)?),
2055        Rule::decimal_integer => pair.as_str().parse::<i64>().map_err(|_| {
2056            ParseError::new(
2057                "invalid decimal integer",
2058                pair.as_span().start(),
2059                pair.as_span().end(),
2060            )
2061        }),
2062        Rule::hex_integer => i64::from_str_radix(&pair.as_str()[2..], 16).map_err(|_| {
2063            ParseError::new(
2064                "invalid hex integer",
2065                pair.as_span().start(),
2066                pair.as_span().end(),
2067            )
2068        }),
2069        Rule::octal_integer => i64::from_str_radix(&pair.as_str()[1..], 8).map_err(|_| {
2070            ParseError::new(
2071                "invalid octal integer",
2072                pair.as_span().start(),
2073                pair.as_span().end(),
2074            )
2075        }),
2076        _ => Err(unexpected_rule("integer_literal", pair)),
2077    }
2078}
2079
2080fn single_inner(pair: Pair<Rule>) -> Result<Pair<Rule>, ParseError> {
2081    let span = pair.as_span();
2082    pair.into_inner()
2083        .next()
2084        .ok_or_else(|| ParseError::new("expected inner rule", span.start(), span.end()))
2085}
2086
2087fn pair_span(pair: &Pair<Rule>) -> Span {
2088    Span::new(pair.as_span().start(), pair.as_span().end())
2089}
2090
2091fn merge_spans(a: Span, b: Span) -> Span {
2092    Span {
2093        start: a.start.min(b.start),
2094        end: a.end.max(b.end),
2095    }
2096}
2097
2098fn unexpected_rule(expected: &str, pair: Pair<Rule>) -> ParseError {
2099    ParseError::new(
2100        format!("expected {expected}, got {:?}", pair.as_rule()),
2101        pair.as_span().start(),
2102        pair.as_span().end(),
2103    )
2104}
2105
2106#[cfg(test)]
2107mod tests {
2108    use super::*;
2109
2110    fn as_regular_single_part(doc: Document) -> SinglePartQuery {
2111        let Statement::Query(Query::Regular(rq)) = doc.statement else {
2112            panic!("expected regular query");
2113        };
2114        let SingleQuery::SinglePart(sp) = rq.head else {
2115            panic!("expected single-part query");
2116        };
2117        sp
2118    }
2119
2120    fn as_regular_multi_part(doc: Document) -> MultiPartQuery {
2121        let Statement::Query(Query::Regular(rq)) = doc.statement else {
2122            panic!("expected regular query");
2123        };
2124        let SingleQuery::MultiPart(mp) = rq.head else {
2125            panic!("expected multi-part query");
2126        };
2127        mp
2128    }
2129
2130    fn as_standalone_call(doc: Document) -> StandaloneCall {
2131        let Statement::Query(Query::StandaloneCall(call)) = doc.statement else {
2132            panic!("expected standalone CALL");
2133        };
2134        call
2135    }
2136
2137    fn first_match_clause(sp: &SinglePartQuery) -> &Match {
2138        let Some(first) = sp.reading_clauses.first() else {
2139            panic!("expected at least one reading clause");
2140        };
2141        let ReadingClause::Match(m) = first else {
2142            panic!("expected first reading clause to be MATCH");
2143        };
2144        m
2145    }
2146
2147    fn first_return_expr(sp: &SinglePartQuery) -> &Expr {
2148        let ret = sp.return_clause.as_ref().expect("expected RETURN clause");
2149        let Some(first) = ret.body.items.first() else {
2150            panic!("expected at least one projection item");
2151        };
2152        let ProjectionItem::Expr { expr, .. } = first else {
2153            panic!("expected first projection item to be an expression");
2154        };
2155        expr
2156    }
2157
2158    #[test]
2159    fn parse_basic_match_return() {
2160        let doc = parse_query("MATCH (n) RETURN n").unwrap();
2161        let sp = as_regular_single_part(doc);
2162
2163        assert_eq!(sp.reading_clauses.len(), 1);
2164        assert!(sp.updating_clauses.is_empty());
2165        assert!(sp.return_clause.is_some());
2166
2167        let m = first_match_clause(&sp);
2168        assert!(!m.optional);
2169        assert_eq!(m.pattern.parts.len(), 1);
2170        assert!(m.where_.is_none());
2171
2172        match first_return_expr(&sp) {
2173            Expr::Variable(v) => assert_eq!(v.name, "n"),
2174            other => panic!("expected RETURN variable n, got {other:?}"),
2175        }
2176    }
2177
2178    #[test]
2179    fn parse_match_with_label() {
2180        let doc = parse_query("MATCH (n:User) RETURN n").unwrap();
2181        let sp = as_regular_single_part(doc);
2182
2183        let m = first_match_clause(&sp);
2184        let PatternElement::NodeChain { head, chain, .. } = &m.pattern.parts[0].element else {
2185            panic!("expected node chain");
2186        };
2187
2188        assert!(chain.is_empty());
2189        assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("n"));
2190        assert_eq!(head.labels.len(), 1);
2191        assert_eq!(head.labels[0][0], "User");
2192        assert!(head.properties.is_none());
2193    }
2194
2195    #[test]
2196    fn parse_match_multiple_labels() {
2197        let doc = parse_query("MATCH (n:User:Admin) RETURN n").unwrap();
2198        let sp = as_regular_single_part(doc);
2199
2200        let m = first_match_clause(&sp);
2201        let PatternElement::NodeChain { head, .. } = &m.pattern.parts[0].element else {
2202            panic!("expected node chain");
2203        };
2204
2205        assert_eq!(head.labels.len(), 2);
2206        assert_eq!(head.labels[0][0], "User");
2207        assert_eq!(head.labels[1][0], "Admin");
2208    }
2209
2210    #[test]
2211    fn parse_optional_match() {
2212        let doc = parse_query("OPTIONAL MATCH (n) RETURN n").unwrap();
2213        let sp = as_regular_single_part(doc);
2214
2215        let m = first_match_clause(&sp);
2216        assert!(m.optional);
2217        assert!(m.where_.is_none());
2218    }
2219
2220    #[test]
2221    fn parse_match_relationship() {
2222        let doc = parse_query("MATCH (a)-[:FOLLOWS]->(b) RETURN a, b").unwrap();
2223        let sp = as_regular_single_part(doc);
2224
2225        let m = first_match_clause(&sp);
2226        let PatternElement::NodeChain { head, chain, .. } = &m.pattern.parts[0].element else {
2227            panic!("expected node chain");
2228        };
2229
2230        assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("a"));
2231        assert_eq!(chain.len(), 1);
2232
2233        let rel = chain[0]
2234            .relationship
2235            .detail
2236            .as_ref()
2237            .expect("expected relationship detail");
2238
2239        assert_eq!(rel.types.len(), 1);
2240        assert_eq!(rel.types[0], "FOLLOWS");
2241        assert!(matches!(chain[0].relationship.direction, Direction::Right));
2242        assert_eq!(
2243            chain[0].node.variable.as_ref().map(|v| v.name.as_str()),
2244            Some("b")
2245        );
2246    }
2247
2248    #[test]
2249    fn parse_match_left_relationship() {
2250        let doc = parse_query("MATCH (a)<-[:FOLLOWS]-(b) RETURN a, b").unwrap();
2251        let sp = as_regular_single_part(doc);
2252
2253        let m = first_match_clause(&sp);
2254        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2255            panic!("expected node chain");
2256        };
2257
2258        assert_eq!(chain.len(), 1);
2259        assert!(matches!(chain[0].relationship.direction, Direction::Left));
2260    }
2261
2262    #[test]
2263    fn parse_match_undirected_relationship() {
2264        let doc = parse_query("MATCH (a)-[:KNOWS]-(b) RETURN a, b").unwrap();
2265        let sp = as_regular_single_part(doc);
2266
2267        let m = first_match_clause(&sp);
2268        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2269            panic!("expected node chain");
2270        };
2271
2272        assert_eq!(chain.len(), 1);
2273        assert!(matches!(
2274            chain[0].relationship.direction,
2275            Direction::Undirected
2276        ));
2277    }
2278
2279    #[test]
2280    fn parse_relationship_with_variable_and_range() {
2281        let doc = parse_query("MATCH (a)-[r:FOLLOWS*1..3]->(b) RETURN r").unwrap();
2282        let sp = as_regular_single_part(doc);
2283
2284        let m = first_match_clause(&sp);
2285        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2286            panic!("expected node chain");
2287        };
2288
2289        let rel = chain[0]
2290            .relationship
2291            .detail
2292            .as_ref()
2293            .expect("expected relationship detail");
2294
2295        assert_eq!(rel.variable.as_ref().map(|v| v.name.as_str()), Some("r"));
2296        assert_eq!(rel.types.len(), 1);
2297        assert_eq!(rel.types[0], "FOLLOWS");
2298
2299        let range = rel.range.as_ref().expect("expected range");
2300        assert_eq!(range.start, Some(1));
2301        assert_eq!(range.end, Some(3));
2302    }
2303
2304    #[test]
2305    fn parse_relationship_range_upper_only() {
2306        let doc = parse_query("MATCH (a)-[:FOLLOWS*..3]->(b) RETURN a").unwrap();
2307        let sp = as_regular_single_part(doc);
2308
2309        let m = first_match_clause(&sp);
2310        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2311            panic!("expected node chain");
2312        };
2313
2314        let range = chain[0]
2315            .relationship
2316            .detail
2317            .as_ref()
2318            .and_then(|d| d.range.as_ref())
2319            .expect("expected range");
2320
2321        assert_eq!(range.start, None);
2322        assert_eq!(range.end, Some(3));
2323    }
2324
2325    #[test]
2326    fn parse_relationship_range_lower_only() {
2327        let doc = parse_query("MATCH (a)-[:FOLLOWS*3..]->(b) RETURN a").unwrap();
2328        let sp = as_regular_single_part(doc);
2329
2330        let m = first_match_clause(&sp);
2331        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2332            panic!("expected node chain");
2333        };
2334
2335        let range = chain[0]
2336            .relationship
2337            .detail
2338            .as_ref()
2339            .and_then(|d| d.range.as_ref())
2340            .expect("expected range");
2341
2342        assert_eq!(range.start, Some(3));
2343        assert_eq!(range.end, None);
2344    }
2345
2346    #[test]
2347    fn parse_relationship_range_unbounded() {
2348        let doc = parse_query("MATCH (a)-[:FOLLOWS*]->(b) RETURN a").unwrap();
2349        let sp = as_regular_single_part(doc);
2350
2351        let m = first_match_clause(&sp);
2352        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2353            panic!("expected node chain");
2354        };
2355
2356        let range = chain[0]
2357            .relationship
2358            .detail
2359            .as_ref()
2360            .and_then(|d| d.range.as_ref())
2361            .expect("expected range");
2362
2363        assert_eq!(range.start, None);
2364        assert_eq!(range.end, None);
2365    }
2366
2367    #[test]
2368    fn parse_pattern_binding() {
2369        let doc = parse_query("MATCH p = (a)-[:FOLLOWS]->(b) RETURN p").unwrap();
2370        let sp = as_regular_single_part(doc);
2371
2372        let m = first_match_clause(&sp);
2373        assert_eq!(m.pattern.parts.len(), 1);
2374        assert_eq!(
2375            m.pattern.parts[0].binding.as_ref().map(|v| v.name.as_str()),
2376            Some("p")
2377        );
2378
2379        match first_return_expr(&sp) {
2380            Expr::Variable(v) => assert_eq!(v.name, "p"),
2381            other => panic!("expected RETURN variable p, got {other:?}"),
2382        }
2383    }
2384
2385    #[test]
2386    fn parse_parenthesized_pattern_element() {
2387        let doc = parse_query("MATCH ((n)) RETURN n").unwrap();
2388        let sp = as_regular_single_part(doc);
2389
2390        let m = first_match_clause(&sp);
2391        match &m.pattern.parts[0].element {
2392            PatternElement::Parenthesized(inner, _) => match inner.as_ref() {
2393                PatternElement::NodeChain { head, chain, .. } => {
2394                    assert!(chain.is_empty());
2395                    assert_eq!(head.variable.as_ref().map(|v| v.name.as_str()), Some("n"));
2396                }
2397                other => panic!("expected node chain inside parentheses, got {other:?}"),
2398            },
2399            other => panic!("expected parenthesized pattern element, got {other:?}"),
2400        }
2401    }
2402
2403    #[test]
2404    fn parse_where_expression() {
2405        let doc = parse_query("MATCH (n) WHERE 1 + 2 > 2 RETURN n").unwrap();
2406        let sp = as_regular_single_part(doc);
2407
2408        let m = first_match_clause(&sp);
2409        let where_ = m.where_.as_ref().expect("expected WHERE clause");
2410
2411        match where_ {
2412            Expr::Binary { lhs, op, rhs, .. } => {
2413                assert!(matches!(op, BinaryOp::Gt));
2414
2415                match lhs.as_ref() {
2416                    Expr::Binary {
2417                        lhs: add_lhs,
2418                        op: add_op,
2419                        rhs: add_rhs,
2420                        ..
2421                    } => {
2422                        assert!(matches!(add_op, BinaryOp::Add));
2423                        assert!(matches!(add_lhs.as_ref(), Expr::Integer(1, _)));
2424                        assert!(matches!(add_rhs.as_ref(), Expr::Integer(2, _)));
2425                    }
2426                    other => panic!("expected lhs to be addition expression, got {other:?}"),
2427                }
2428
2429                assert!(matches!(rhs.as_ref(), Expr::Integer(2, _)));
2430            }
2431            other => panic!("expected binary WHERE expression, got {other:?}"),
2432        }
2433    }
2434
2435    #[test]
2436    fn parse_where_boolean_precedence() {
2437        let doc = parse_query("MATCH (n) WHERE NOT n.active AND n.age >= 18 RETURN n").unwrap();
2438        let sp = as_regular_single_part(doc);
2439
2440        let m = first_match_clause(&sp);
2441        let where_ = m.where_.as_ref().expect("expected WHERE clause");
2442
2443        match where_ {
2444            Expr::Binary { lhs, op, rhs, .. } => {
2445                assert!(matches!(op, BinaryOp::And));
2446
2447                match lhs.as_ref() {
2448                    Expr::Unary {
2449                        op: UnaryOp::Not,
2450                        expr,
2451                        ..
2452                    } => match expr.as_ref() {
2453                        Expr::Property { key, .. } => assert_eq!(key, "active"),
2454                        other => panic!("expected property under NOT, got {other:?}"),
2455                    },
2456                    other => panic!("expected NOT expression on lhs, got {other:?}"),
2457                }
2458
2459                match rhs.as_ref() {
2460                    Expr::Binary {
2461                        lhs: cmp_lhs,
2462                        op: cmp_op,
2463                        rhs: cmp_rhs,
2464                        ..
2465                    } => {
2466                        assert!(matches!(cmp_op, BinaryOp::Ge));
2467                        assert!(matches!(cmp_rhs.as_ref(), Expr::Integer(18, _)));
2468                        match cmp_lhs.as_ref() {
2469                            Expr::Property { key, .. } => assert_eq!(key, "age"),
2470                            other => panic!("expected property on comparison lhs, got {other:?}"),
2471                        }
2472                    }
2473                    other => panic!("expected comparison expression on rhs, got {other:?}"),
2474                }
2475            }
2476            other => panic!("expected binary WHERE expression, got {other:?}"),
2477        }
2478    }
2479
2480    #[test]
2481    fn parse_where_parenthesized_expression() {
2482        let doc = parse_query("MATCH (n) WHERE (1 + 2) * 3 > 5 RETURN n").unwrap();
2483        let sp = as_regular_single_part(doc);
2484
2485        let m = first_match_clause(&sp);
2486        let where_ = m.where_.as_ref().expect("expected WHERE clause");
2487
2488        match where_ {
2489            Expr::Binary { lhs, op, rhs, .. } => {
2490                assert!(matches!(op, BinaryOp::Gt));
2491                assert!(matches!(rhs.as_ref(), Expr::Integer(5, _)));
2492
2493                match lhs.as_ref() {
2494                    Expr::Binary {
2495                        lhs: mul_lhs,
2496                        op: mul_op,
2497                        rhs: mul_rhs,
2498                        ..
2499                    } => {
2500                        assert!(matches!(mul_op, BinaryOp::Mul));
2501                        assert!(matches!(mul_rhs.as_ref(), Expr::Integer(3, _)));
2502
2503                        match mul_lhs.as_ref() {
2504                            Expr::Binary {
2505                                lhs: add_lhs,
2506                                op: add_op,
2507                                rhs: add_rhs,
2508                                ..
2509                            } => {
2510                                assert!(matches!(add_op, BinaryOp::Add));
2511                                assert!(matches!(add_lhs.as_ref(), Expr::Integer(1, _)));
2512                                assert!(matches!(add_rhs.as_ref(), Expr::Integer(2, _)));
2513                            }
2514                            other => {
2515                                panic!("expected addition under multiplication, got {other:?}")
2516                            }
2517                        }
2518                    }
2519                    other => panic!("expected multiplication lhs, got {other:?}"),
2520                }
2521            }
2522            other => panic!("expected binary WHERE expression, got {other:?}"),
2523        }
2524    }
2525
2526    #[test]
2527    fn parse_where_in_operator() {
2528        let doc = parse_query("MATCH (n) WHERE n.age IN [1, 2, 3] RETURN n").unwrap();
2529        let sp = as_regular_single_part(doc);
2530
2531        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2532        match where_ {
2533            Expr::Binary { lhs, op, rhs, .. } => {
2534                assert!(matches!(op, BinaryOp::In));
2535                assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "age"));
2536                assert!(matches!(rhs.as_ref(), Expr::List(_, _)));
2537            }
2538            other => panic!("expected IN expression, got {other:?}"),
2539        }
2540    }
2541
2542    #[test]
2543    fn parse_where_contains_operator() {
2544        let doc = parse_query("MATCH (n) WHERE n.name CONTAINS 'al' RETURN n").unwrap();
2545        let sp = as_regular_single_part(doc);
2546
2547        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2548        match where_ {
2549            Expr::Binary { op, rhs, .. } => {
2550                assert!(matches!(op, BinaryOp::Contains));
2551                assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "al"));
2552            }
2553            other => panic!("expected CONTAINS expression, got {other:?}"),
2554        }
2555    }
2556
2557    #[test]
2558    fn parse_where_starts_with_operator() {
2559        let doc = parse_query("MATCH (n) WHERE n.name STARTS WITH 'a' RETURN n").unwrap();
2560        let sp = as_regular_single_part(doc);
2561
2562        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2563        match where_ {
2564            Expr::Binary { op, rhs, .. } => {
2565                assert!(matches!(op, BinaryOp::StartsWith));
2566                assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "a"));
2567            }
2568            other => panic!("expected STARTS WITH expression, got {other:?}"),
2569        }
2570    }
2571
2572    #[test]
2573    fn parse_where_ends_with_operator() {
2574        let doc = parse_query("MATCH (n) WHERE n.name ENDS WITH 'z' RETURN n").unwrap();
2575        let sp = as_regular_single_part(doc);
2576
2577        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2578        match where_ {
2579            Expr::Binary { op, rhs, .. } => {
2580                assert!(matches!(op, BinaryOp::EndsWith));
2581                assert!(matches!(rhs.as_ref(), Expr::String(s, _) if s == "z"));
2582            }
2583            other => panic!("expected ENDS WITH expression, got {other:?}"),
2584        }
2585    }
2586
2587    #[test]
2588    fn parse_where_is_null() {
2589        let doc = parse_query("MATCH (n) WHERE n.name IS NULL RETURN n").unwrap();
2590        let sp = as_regular_single_part(doc);
2591
2592        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2593        match where_ {
2594            Expr::Binary { lhs, op, rhs, .. } => {
2595                assert!(matches!(op, BinaryOp::IsNull));
2596                assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "name"));
2597                assert!(matches!(rhs.as_ref(), Expr::Null(_)));
2598            }
2599            other => panic!("expected IS NULL expression, got {other:?}"),
2600        }
2601    }
2602
2603    #[test]
2604    fn parse_where_is_not_null() {
2605        let doc = parse_query("MATCH (n) WHERE n.name IS NOT NULL RETURN n").unwrap();
2606        let sp = as_regular_single_part(doc);
2607
2608        let where_ = first_match_clause(&sp).where_.as_ref().unwrap();
2609        match where_ {
2610            Expr::Binary { lhs, op, rhs, .. } => {
2611                assert!(matches!(op, BinaryOp::IsNotNull));
2612                assert!(matches!(lhs.as_ref(), Expr::Property { key, .. } if key == "name"));
2613                assert!(matches!(rhs.as_ref(), Expr::Null(_)));
2614            }
2615            other => panic!("expected IS NOT NULL expression, got {other:?}"),
2616        }
2617    }
2618
2619    #[test]
2620    fn parse_limit() {
2621        let doc = parse_query("MATCH (n) RETURN n LIMIT 10").unwrap();
2622        let sp = as_regular_single_part(doc);
2623
2624        let ret = sp.return_clause.expect("expected RETURN clause");
2625        assert!(matches!(ret.body.limit, Some(Expr::Integer(10, _))));
2626        assert!(ret.body.skip.is_none());
2627    }
2628
2629    #[test]
2630    fn parse_distinct_return() {
2631        let doc = parse_query("MATCH (n) RETURN DISTINCT n").unwrap();
2632        let sp = as_regular_single_part(doc);
2633
2634        let ret = sp.return_clause.expect("expected RETURN clause");
2635        assert!(ret.body.distinct);
2636        assert_eq!(ret.body.items.len(), 1);
2637    }
2638
2639    #[test]
2640    fn parse_return_star() {
2641        let doc = parse_query("MATCH (n) RETURN *").unwrap();
2642        let sp = as_regular_single_part(doc);
2643
2644        let ret = sp.return_clause.expect("expected RETURN clause");
2645        assert_eq!(ret.body.items.len(), 1);
2646        assert!(matches!(ret.body.items[0], ProjectionItem::Star { .. }));
2647    }
2648
2649    #[test]
2650    fn parse_return_star_and_expr() {
2651        let doc = parse_query("MATCH (n) RETURN *, n.name").unwrap();
2652        let sp = as_regular_single_part(doc);
2653
2654        let ret = sp.return_clause.expect("expected RETURN clause");
2655        assert_eq!(ret.body.items.len(), 2);
2656        assert!(matches!(ret.body.items[0], ProjectionItem::Star { .. }));
2657        assert!(matches!(
2658            &ret.body.items[1],
2659            ProjectionItem::Expr {
2660                expr: Expr::Property { key, .. },
2661                ..
2662            } if key == "name"
2663        ));
2664    }
2665
2666    #[test]
2667    fn parse_multiple_projection_items() {
2668        let doc = parse_query("MATCH (n) RETURN n, n.name AS name").unwrap();
2669        let sp = as_regular_single_part(doc);
2670
2671        let ret = sp.return_clause.expect("expected RETURN clause");
2672        assert_eq!(ret.body.items.len(), 2);
2673
2674        match &ret.body.items[0] {
2675            ProjectionItem::Expr { expr, alias, .. } => {
2676                assert!(alias.is_none());
2677                assert!(matches!(expr, Expr::Variable(_)));
2678            }
2679            other => panic!("expected projection expr, got {other:?}"),
2680        }
2681
2682        match &ret.body.items[1] {
2683            ProjectionItem::Expr { expr, alias, .. } => {
2684                assert_eq!(alias.as_ref().map(|v| v.name.as_str()), Some("name"));
2685                match expr {
2686                    Expr::Property { key, .. } => assert_eq!(key, "name"),
2687                    other => panic!("expected property projection, got {other:?}"),
2688                }
2689            }
2690            other => panic!("expected projection expr, got {other:?}"),
2691        }
2692    }
2693
2694    #[test]
2695    fn parse_order_skip_limit() {
2696        let doc = parse_query("MATCH (n) RETURN n ORDER BY n.name DESC SKIP 5 LIMIT 10").unwrap();
2697        let sp = as_regular_single_part(doc);
2698
2699        let ret = sp.return_clause.expect("expected RETURN clause");
2700        assert_eq!(ret.body.order.len(), 1);
2701        assert!(matches!(ret.body.order[0].direction, SortDirection::Desc));
2702        assert!(matches!(ret.body.skip, Some(Expr::Integer(5, _))));
2703        assert!(matches!(ret.body.limit, Some(Expr::Integer(10, _))));
2704
2705        match &ret.body.order[0].expr {
2706            Expr::Property { key, .. } => assert_eq!(key, "name"),
2707            other => panic!("expected ORDER BY property lookup, got {other:?}"),
2708        }
2709    }
2710
2711    #[test]
2712    fn parse_order_multiple_sort_items() {
2713        let doc = parse_query("MATCH (n) RETURN n ORDER BY n.last ASC, n.first DESC").unwrap();
2714        let sp = as_regular_single_part(doc);
2715
2716        let ret = sp.return_clause.expect("expected RETURN clause");
2717        assert_eq!(ret.body.order.len(), 2);
2718        assert!(matches!(ret.body.order[0].direction, SortDirection::Asc));
2719        assert!(matches!(ret.body.order[1].direction, SortDirection::Desc));
2720    }
2721
2722    #[test]
2723    fn parse_create_clause() {
2724        let doc = parse_query("CREATE (n:User {name: 'alice'}) RETURN n").unwrap();
2725        let sp = as_regular_single_part(doc);
2726
2727        assert_eq!(sp.updating_clauses.len(), 1);
2728
2729        let UpdatingClause::Create(create) = &sp.updating_clauses[0] else {
2730            panic!("expected CREATE clause");
2731        };
2732        let PatternElement::NodeChain { head, chain, .. } = &create.pattern.parts[0].element else {
2733            panic!("expected node chain");
2734        };
2735
2736        assert!(chain.is_empty());
2737        assert_eq!(head.labels[0][0], "User");
2738        assert!(head.properties.is_some());
2739    }
2740
2741    #[test]
2742    fn parse_create_without_return() {
2743        let doc = parse_query("CREATE (n:User)").unwrap();
2744        let sp = as_regular_single_part(doc);
2745
2746        assert_eq!(sp.updating_clauses.len(), 1);
2747        assert!(sp.return_clause.is_none());
2748    }
2749
2750    #[test]
2751    fn parse_merge_clause() {
2752        let doc = parse_query("MERGE (n:User {id: 1}) RETURN n").unwrap();
2753        let sp = as_regular_single_part(doc);
2754
2755        assert_eq!(sp.updating_clauses.len(), 1);
2756        let UpdatingClause::Merge(merge) = &sp.updating_clauses[0] else {
2757            panic!("expected MERGE clause");
2758        };
2759
2760        let PatternElement::NodeChain { head, chain, .. } = &merge.pattern_part.element else {
2761            panic!("expected node chain");
2762        };
2763        assert!(chain.is_empty());
2764        assert_eq!(head.labels[0][0], "User");
2765        assert!(head.properties.is_some());
2766    }
2767
2768    #[test]
2769    fn parse_merge_with_actions() {
2770        let doc = parse_query(
2771            "MERGE (n:User {id: 1}) ON MATCH SET n.name = 'alice' ON CREATE SET n:New RETURN n",
2772        )
2773        .unwrap();
2774        let sp = as_regular_single_part(doc);
2775
2776        let UpdatingClause::Merge(merge) = &sp.updating_clauses[0] else {
2777            panic!("expected MERGE clause");
2778        };
2779
2780        assert_eq!(merge.actions.len(), 2);
2781        assert!(merge.actions[0].on_match);
2782        assert!(!merge.actions[1].on_match);
2783        assert_eq!(merge.actions[0].set.items.len(), 1);
2784        assert_eq!(merge.actions[1].set.items.len(), 1);
2785    }
2786
2787    #[test]
2788    fn parse_delete_clause() {
2789        let doc = parse_query("MATCH (n) DELETE n").unwrap();
2790        let sp = as_regular_single_part(doc);
2791
2792        let UpdatingClause::Delete(delete) = &sp.updating_clauses[0] else {
2793            panic!("expected DELETE clause");
2794        };
2795
2796        assert!(!delete.detach);
2797        assert_eq!(delete.expressions.len(), 1);
2798        assert!(matches!(delete.expressions[0], Expr::Variable(_)));
2799    }
2800
2801    #[test]
2802    fn parse_detach_delete_clause() {
2803        let doc = parse_query("MATCH (n) DETACH DELETE n").unwrap();
2804        let sp = as_regular_single_part(doc);
2805
2806        let UpdatingClause::Delete(delete) = &sp.updating_clauses[0] else {
2807            panic!("expected DELETE clause");
2808        };
2809
2810        assert!(delete.detach);
2811        assert_eq!(delete.expressions.len(), 1);
2812    }
2813
2814    #[test]
2815    fn parse_set_variable_clause() {
2816        let doc = parse_query("MATCH (n) SET n = {name: 'alice'} RETURN n").unwrap();
2817        let sp = as_regular_single_part(doc);
2818
2819        let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
2820            panic!("expected SET clause");
2821        };
2822
2823        assert_eq!(set.items.len(), 1);
2824        match &set.items[0] {
2825            SetItem::SetVariable {
2826                variable, value, ..
2827            } => {
2828                assert_eq!(variable.name, "n");
2829                assert!(matches!(value, Expr::Map(_, _)));
2830            }
2831            other => panic!("expected SetVariable, got {other:?}"),
2832        }
2833    }
2834
2835    #[test]
2836    fn parse_set_property_clause() {
2837        let doc = parse_query("MATCH (n) SET n.name = 'alice' RETURN n").unwrap();
2838        let sp = as_regular_single_part(doc);
2839
2840        let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
2841            panic!("expected SET clause");
2842        };
2843
2844        assert_eq!(set.items.len(), 1);
2845        match &set.items[0] {
2846            SetItem::SetProperty { target, value, .. } => {
2847                assert!(matches!(target, Expr::Property { key, .. } if key == "name"));
2848                assert!(matches!(value, Expr::String(s, _) if s == "alice"));
2849            }
2850            other => panic!("expected SetProperty, got {other:?}"),
2851        }
2852    }
2853
2854    #[test]
2855    fn parse_set_mutate_variable_clause() {
2856        let doc = parse_query("MATCH (n) SET n += {age: 42} RETURN n").unwrap();
2857        let sp = as_regular_single_part(doc);
2858
2859        let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
2860            panic!("expected SET clause");
2861        };
2862
2863        assert_eq!(set.items.len(), 1);
2864        match &set.items[0] {
2865            SetItem::MutateVariable {
2866                variable, value, ..
2867            } => {
2868                assert_eq!(variable.name, "n");
2869                assert!(matches!(value, Expr::Map(_, _)));
2870            }
2871            other => panic!("expected MutateVariable, got {other:?}"),
2872        }
2873    }
2874
2875    #[test]
2876    fn parse_set_labels_clause() {
2877        let doc = parse_query("MATCH (n) SET n:User:Admin RETURN n").unwrap();
2878        let sp = as_regular_single_part(doc);
2879
2880        let UpdatingClause::Set(set) = &sp.updating_clauses[0] else {
2881            panic!("expected SET clause");
2882        };
2883
2884        assert_eq!(set.items.len(), 1);
2885        match &set.items[0] {
2886            SetItem::SetLabels {
2887                variable, labels, ..
2888            } => {
2889                assert_eq!(variable.name, "n");
2890                assert_eq!(labels, &vec!["User".to_string(), "Admin".to_string()]);
2891            }
2892            other => panic!("expected SetLabels, got {other:?}"),
2893        }
2894    }
2895
2896    #[test]
2897    fn parse_remove_labels_clause() {
2898        let doc = parse_query("MATCH (n) REMOVE n:User:Admin RETURN n").unwrap();
2899        let sp = as_regular_single_part(doc);
2900
2901        let UpdatingClause::Remove(remove) = &sp.updating_clauses[0] else {
2902            panic!("expected REMOVE clause");
2903        };
2904
2905        assert_eq!(remove.items.len(), 1);
2906        match &remove.items[0] {
2907            RemoveItem::Labels {
2908                variable, labels, ..
2909            } => {
2910                assert_eq!(variable.name, "n");
2911                assert_eq!(labels, &vec!["User".to_string(), "Admin".to_string()]);
2912            }
2913            other => panic!("expected RemoveItem::Labels, got {other:?}"),
2914        }
2915    }
2916
2917    #[test]
2918    fn parse_remove_property_clause() {
2919        let doc = parse_query("MATCH (n) REMOVE n.name RETURN n").unwrap();
2920        let sp = as_regular_single_part(doc);
2921
2922        let UpdatingClause::Remove(remove) = &sp.updating_clauses[0] else {
2923            panic!("expected REMOVE clause");
2924        };
2925
2926        assert_eq!(remove.items.len(), 1);
2927        match &remove.items[0] {
2928            RemoveItem::Property { expr, .. } => {
2929                assert!(matches!(expr, Expr::Property { key, .. } if key == "name"));
2930            }
2931            other => panic!("expected RemoveItem::Property, got {other:?}"),
2932        }
2933    }
2934
2935    #[test]
2936    fn parse_node_properties_map() {
2937        let doc = parse_query("MATCH (n:User {name: 'alice', age: 42}) RETURN n").unwrap();
2938        let sp = as_regular_single_part(doc);
2939
2940        let m = first_match_clause(&sp);
2941        let PatternElement::NodeChain { head, .. } = &m.pattern.parts[0].element else {
2942            panic!("expected node chain");
2943        };
2944
2945        match head.properties.as_ref().expect("expected node properties") {
2946            Expr::Map(items, _) => {
2947                assert_eq!(items.len(), 2);
2948                assert_eq!(items[0].0, "name");
2949                assert!(matches!(items[0].1, Expr::String(_, _)));
2950                assert_eq!(items[1].0, "age");
2951                assert!(matches!(items[1].1, Expr::Integer(42, _)));
2952            }
2953            other => panic!("expected map literal properties, got {other:?}"),
2954        }
2955    }
2956
2957    #[test]
2958    fn parse_relationship_properties_map() {
2959        let doc = parse_query("MATCH (a)-[:FOLLOWS {since: 2020}]->(b) RETURN a").unwrap();
2960        let sp = as_regular_single_part(doc);
2961
2962        let m = first_match_clause(&sp);
2963        let PatternElement::NodeChain { chain, .. } = &m.pattern.parts[0].element else {
2964            panic!("expected node chain");
2965        };
2966
2967        let rel = chain[0]
2968            .relationship
2969            .detail
2970            .as_ref()
2971            .expect("expected relationship detail");
2972
2973        match rel
2974            .properties
2975            .as_ref()
2976            .expect("expected relationship properties")
2977        {
2978            Expr::Map(items, _) => {
2979                assert_eq!(items.len(), 1);
2980                assert_eq!(items[0].0, "since");
2981                assert!(matches!(items[0].1, Expr::Integer(2020, _)));
2982            }
2983            other => panic!("expected relationship map properties, got {other:?}"),
2984        }
2985    }
2986
2987    #[test]
2988    fn parse_unwind_clause() {
2989        let doc = parse_query("UNWIND [1, 2, 3] AS n RETURN n").unwrap();
2990        let sp = as_regular_single_part(doc);
2991
2992        assert_eq!(sp.reading_clauses.len(), 1);
2993
2994        let ReadingClause::Unwind(unwind) = &sp.reading_clauses[0] else {
2995            panic!("expected UNWIND clause");
2996        };
2997
2998        assert_eq!(unwind.alias.name, "n");
2999
3000        match &unwind.expr {
3001            Expr::List(items, _) => {
3002                assert_eq!(items.len(), 3);
3003                assert!(matches!(items[0], Expr::Integer(1, _)));
3004                assert!(matches!(items[1], Expr::Integer(2, _)));
3005                assert!(matches!(items[2], Expr::Integer(3, _)));
3006            }
3007            other => panic!("expected list expression, got {other:?}"),
3008        }
3009    }
3010
3011    #[test]
3012    fn parse_unary_operators() {
3013        let doc = parse_query("RETURN -1, +2").unwrap();
3014        let sp = as_regular_single_part(doc);
3015
3016        let ret = sp.return_clause.expect("expected RETURN clause");
3017        assert_eq!(ret.body.items.len(), 2);
3018
3019        match &ret.body.items[0] {
3020            ProjectionItem::Expr { expr, .. } => match expr {
3021                Expr::Unary {
3022                    op: UnaryOp::Neg,
3023                    expr,
3024                    ..
3025                } => assert!(matches!(expr.as_ref(), Expr::Integer(1, _))),
3026                other => panic!("expected unary negation, got {other:?}"),
3027            },
3028            other => panic!("expected projection expr, got {other:?}"),
3029        }
3030
3031        match &ret.body.items[1] {
3032            ProjectionItem::Expr { expr, .. } => match expr {
3033                Expr::Unary {
3034                    op: UnaryOp::Pos,
3035                    expr,
3036                    ..
3037                } => assert!(matches!(expr.as_ref(), Expr::Integer(2, _))),
3038                other => panic!("expected unary positive, got {other:?}"),
3039            },
3040            other => panic!("expected projection expr, got {other:?}"),
3041        }
3042    }
3043
3044    #[test]
3045    fn parse_power_operator() {
3046        let doc = parse_query("RETURN 2 ^ 3 ^ 4").unwrap();
3047        let sp = as_regular_single_part(doc);
3048
3049        match first_return_expr(&sp) {
3050            Expr::Binary { lhs, op, rhs, .. } => {
3051                assert!(matches!(op, BinaryOp::Pow));
3052                assert!(matches!(rhs.as_ref(), Expr::Integer(4, _)));
3053                assert!(matches!(
3054                    lhs.as_ref(),
3055                    Expr::Binary {
3056                        op: BinaryOp::Pow,
3057                        ..
3058                    }
3059                ));
3060            }
3061            other => panic!("expected power expression, got {other:?}"),
3062        }
3063    }
3064
3065    #[test]
3066    fn parse_function_call_and_alias() {
3067        let doc = parse_query("MATCH (n) RETURN count(n) AS c").unwrap();
3068        let sp = as_regular_single_part(doc);
3069
3070        let ret = sp.return_clause.expect("expected RETURN clause");
3071        assert_eq!(ret.body.items.len(), 1);
3072
3073        let ProjectionItem::Expr { expr, alias, .. } = &ret.body.items[0] else {
3074            panic!("expected projection expr");
3075        };
3076
3077        assert_eq!(alias.as_ref().map(|v| v.name.as_str()), Some("c"));
3078
3079        match expr {
3080            Expr::FunctionCall {
3081                name,
3082                distinct,
3083                args,
3084                ..
3085            } => {
3086                assert_eq!(name, &vec!["count".to_string()]);
3087                assert!(!distinct);
3088                assert_eq!(args.len(), 1);
3089                assert!(matches!(args[0], Expr::Variable(_)));
3090            }
3091            other => panic!("expected function call, got {other:?}"),
3092        }
3093    }
3094
3095    #[test]
3096    fn parse_distinct_function_call() {
3097        let doc = parse_query("MATCH (n) RETURN count(DISTINCT n) AS c").unwrap();
3098        let sp = as_regular_single_part(doc);
3099
3100        let ret = sp.return_clause.expect("expected RETURN clause");
3101        let ProjectionItem::Expr { expr, .. } = &ret.body.items[0] else {
3102            panic!("expected projection expr");
3103        };
3104
3105        match expr {
3106            Expr::FunctionCall {
3107                name,
3108                distinct,
3109                args,
3110                ..
3111            } => {
3112                assert_eq!(name, &vec!["count".to_string()]);
3113                assert!(*distinct);
3114                assert_eq!(args.len(), 1);
3115            }
3116            other => panic!("expected function call, got {other:?}"),
3117        }
3118    }
3119
3120    #[test]
3121    fn parse_namespaced_function_call() {
3122        let doc = parse_query("RETURN my.ns.func(1, 2)").unwrap();
3123        let sp = as_regular_single_part(doc);
3124
3125        match first_return_expr(&sp) {
3126            Expr::FunctionCall { name, args, .. } => {
3127                assert_eq!(
3128                    name,
3129                    &vec!["my".to_string(), "ns".to_string(), "func".to_string()]
3130                );
3131                assert_eq!(args.len(), 2);
3132                assert!(matches!(args[0], Expr::Integer(1, _)));
3133                assert!(matches!(args[1], Expr::Integer(2, _)));
3134            }
3135            other => panic!("expected namespaced function call, got {other:?}"),
3136        }
3137    }
3138
3139    #[test]
3140    fn parse_parameter_and_property_lookup() {
3141        let doc = parse_query("MATCH (n) WHERE n.age >= $minAge RETURN n.name").unwrap();
3142        let sp = as_regular_single_part(doc);
3143
3144        let m = first_match_clause(&sp);
3145        let where_ = m.where_.as_ref().expect("expected WHERE clause");
3146
3147        match where_ {
3148            Expr::Binary { lhs, op, rhs, .. } => {
3149                assert!(matches!(op, BinaryOp::Ge));
3150
3151                match lhs.as_ref() {
3152                    Expr::Property { key, .. } => assert_eq!(key, "age"),
3153                    other => panic!("expected property lookup on lhs, got {other:?}"),
3154                }
3155
3156                match rhs.as_ref() {
3157                    Expr::Parameter(name, _) => assert_eq!(name, "minAge"),
3158                    other => panic!("expected parameter on rhs, got {other:?}"),
3159                }
3160            }
3161            other => panic!("expected binary WHERE expression, got {other:?}"),
3162        }
3163
3164        let ret = sp.return_clause.expect("expected RETURN clause");
3165        let ProjectionItem::Expr { expr, .. } = &ret.body.items[0] else {
3166            panic!("expected projection expr");
3167        };
3168
3169        match expr {
3170            Expr::Property { key, .. } => assert_eq!(key, "name"),
3171            other => panic!("expected property lookup in RETURN, got {other:?}"),
3172        }
3173    }
3174
3175    #[test]
3176    fn parse_numeric_parameter() {
3177        let doc = parse_query("RETURN $1").unwrap();
3178        let sp = as_regular_single_part(doc);
3179
3180        match first_return_expr(&sp) {
3181            Expr::Parameter(name, _) => assert_eq!(name, "1"),
3182            other => panic!("expected numeric parameter, got {other:?}"),
3183        }
3184    }
3185
3186    #[test]
3187    fn parse_map_and_list_literals() {
3188        let doc = parse_query("RETURN {name: 'alice', nums: [1, 2, 3]}").unwrap();
3189        let sp = as_regular_single_part(doc);
3190
3191        match first_return_expr(&sp) {
3192            Expr::Map(items, _) => {
3193                assert_eq!(items.len(), 2);
3194                assert_eq!(items[0].0, "name");
3195                assert!(matches!(items[0].1, Expr::String(_, _)));
3196
3197                assert_eq!(items[1].0, "nums");
3198                match &items[1].1 {
3199                    Expr::List(values, _) => {
3200                        assert_eq!(values.len(), 3);
3201                        assert!(matches!(values[0], Expr::Integer(1, _)));
3202                        assert!(matches!(values[1], Expr::Integer(2, _)));
3203                        assert!(matches!(values[2], Expr::Integer(3, _)));
3204                    }
3205                    other => panic!("expected nested list, got {other:?}"),
3206                }
3207            }
3208            other => panic!("expected map literal, got {other:?}"),
3209        }
3210    }
3211
3212    #[test]
3213    fn parse_case_expression() {
3214        let doc = parse_query("RETURN CASE WHEN 1 = 1 THEN 'yes' ELSE 'no' END").unwrap();
3215        let sp = as_regular_single_part(doc);
3216
3217        match first_return_expr(&sp) {
3218            Expr::Case {
3219                input,
3220                alternatives,
3221                else_expr,
3222                ..
3223            } => {
3224                assert!(input.is_none());
3225                assert_eq!(alternatives.len(), 1);
3226                assert!(else_expr.is_some());
3227            }
3228            other => panic!("expected CASE expression, got {other:?}"),
3229        }
3230    }
3231
3232    #[test]
3233    fn parse_case_expression_with_input() {
3234        let doc =
3235            parse_query("RETURN CASE n.age WHEN 1 THEN 'one' WHEN 2 THEN 'two' ELSE 'other' END")
3236                .unwrap();
3237        let sp = as_regular_single_part(doc);
3238
3239        match first_return_expr(&sp) {
3240            Expr::Case {
3241                input,
3242                alternatives,
3243                else_expr,
3244                ..
3245            } => {
3246                assert!(input.is_some());
3247                assert_eq!(alternatives.len(), 2);
3248                assert!(else_expr.is_some());
3249            }
3250            other => panic!("expected CASE expression, got {other:?}"),
3251        }
3252    }
3253
3254    #[test]
3255    fn parse_literals() {
3256        let doc = parse_query("RETURN 42, 3.14, true, false, null, 'x'").unwrap();
3257        let sp = as_regular_single_part(doc);
3258
3259        let ret = sp.return_clause.expect("expected RETURN clause");
3260        assert_eq!(ret.body.items.len(), 6);
3261
3262        match &ret.body.items[0] {
3263            ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Integer(42, _))),
3264            other => panic!("expected projection expr, got {other:?}"),
3265        }
3266        match &ret.body.items[1] {
3267            ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Float(_, _))),
3268            other => panic!("expected projection expr, got {other:?}"),
3269        }
3270        match &ret.body.items[2] {
3271            ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Bool(true, _))),
3272            other => panic!("expected projection expr, got {other:?}"),
3273        }
3274        match &ret.body.items[3] {
3275            ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Bool(false, _))),
3276            other => panic!("expected projection expr, got {other:?}"),
3277        }
3278        match &ret.body.items[4] {
3279            ProjectionItem::Expr { expr, .. } => assert!(matches!(expr, Expr::Null(_))),
3280            other => panic!("expected projection expr, got {other:?}"),
3281        }
3282        match &ret.body.items[5] {
3283            ProjectionItem::Expr { expr, .. } => match expr {
3284                Expr::String(s, _) => assert_eq!(s, "x"),
3285                other => panic!("expected string literal, got {other:?}"),
3286            },
3287            other => panic!("expected projection expr, got {other:?}"),
3288        }
3289    }
3290
3291    #[test]
3292    fn parse_string_escapes() {
3293        let doc = parse_query(r#"RETURN "a\nb", 'it\'s', "\\""#).unwrap();
3294        let sp = as_regular_single_part(doc);
3295
3296        let ret = sp.return_clause.expect("expected RETURN clause");
3297        assert_eq!(ret.body.items.len(), 3);
3298
3299        match &ret.body.items[0] {
3300            ProjectionItem::Expr {
3301                expr: Expr::String(s, _),
3302                ..
3303            } => assert_eq!(s, "a\nb"),
3304            other => panic!("expected escaped string, got {other:?}"),
3305        }
3306        match &ret.body.items[1] {
3307            ProjectionItem::Expr {
3308                expr: Expr::String(s, _),
3309                ..
3310            } => assert_eq!(s, "it's"),
3311            other => panic!("expected escaped string, got {other:?}"),
3312        }
3313        match &ret.body.items[2] {
3314            ProjectionItem::Expr {
3315                expr: Expr::String(s, _),
3316                ..
3317            } => assert_eq!(s, "\\"),
3318            other => panic!("expected escaped string, got {other:?}"),
3319        }
3320    }
3321
3322    #[test]
3323    fn parse_union_query() {
3324        let doc = parse_query("MATCH (a) RETURN a UNION MATCH (b) RETURN b").unwrap();
3325
3326        let Statement::Query(Query::Regular(rq)) = doc.statement else {
3327            panic!("expected regular query");
3328        };
3329
3330        assert_eq!(rq.unions.len(), 1);
3331        assert!(!rq.unions[0].all);
3332
3333        let SingleQuery::SinglePart(head) = rq.head else {
3334            panic!("expected single-part head");
3335        };
3336        let head_ret = head.return_clause.expect("expected head return");
3337        assert_eq!(head_ret.body.items.len(), 1);
3338
3339        let SingleQuery::SinglePart(union_q) = &rq.unions[0].query else {
3340            panic!("expected single-part union");
3341        };
3342        let union_ret = union_q
3343            .return_clause
3344            .as_ref()
3345            .expect("expected union return");
3346        assert_eq!(union_ret.body.items.len(), 1);
3347    }
3348
3349    #[test]
3350    fn parse_union_all_query() {
3351        let doc = parse_query("MATCH (a) RETURN a UNION ALL MATCH (b) RETURN b").unwrap();
3352
3353        let Statement::Query(Query::Regular(rq)) = doc.statement else {
3354            panic!("expected regular query");
3355        };
3356
3357        assert_eq!(rq.unions.len(), 1);
3358        assert!(rq.unions[0].all);
3359    }
3360
3361    #[test]
3362    fn parse_with_clause_in_multi_part_query() {
3363        let doc = parse_query("MATCH (n) WITH n RETURN n").unwrap();
3364        let mp = as_regular_multi_part(doc);
3365
3366        assert_eq!(mp.parts.len(), 1);
3367        assert_eq!(mp.parts[0].reading_clauses.len(), 1);
3368        assert!(mp.parts[0].updating_clauses.is_empty());
3369        assert_eq!(mp.parts[0].with_clause.body.items.len(), 1);
3370        assert!(mp.parts[0].with_clause.where_.is_none());
3371        assert!(mp.tail.return_clause.is_some());
3372    }
3373
3374    #[test]
3375    fn parse_with_where_clause_in_multi_part_query() {
3376        let doc = parse_query("MATCH (n) WITH n WHERE n.age >= 18 RETURN n").unwrap();
3377        let mp = as_regular_multi_part(doc);
3378
3379        let where_ = mp.parts[0]
3380            .with_clause
3381            .where_
3382            .as_ref()
3383            .expect("expected WITH WHERE clause");
3384        match where_ {
3385            Expr::Binary { op, .. } => assert!(matches!(op, BinaryOp::Ge)),
3386            other => panic!("expected binary expression, got {other:?}"),
3387        }
3388    }
3389
3390    #[test]
3391    fn parse_multi_part_with_update() {
3392        let doc = parse_query("MATCH (n) SET n:Seen WITH n RETURN n").unwrap();
3393        let mp = as_regular_multi_part(doc);
3394
3395        assert_eq!(mp.parts.len(), 1);
3396        assert_eq!(mp.parts[0].reading_clauses.len(), 1);
3397        assert_eq!(mp.parts[0].updating_clauses.len(), 1);
3398        assert!(mp.tail.return_clause.is_some());
3399    }
3400
3401    #[test]
3402    fn parse_standalone_call_explicit() {
3403        let doc = parse_query("CALL db.labels()").unwrap();
3404        let call = as_standalone_call(doc);
3405
3406        match call.procedure {
3407            ProcedureInvocationKind::Explicit(proc_) => {
3408                assert_eq!(
3409                    proc_.name.parts,
3410                    vec!["db".to_string(), "labels".to_string()]
3411                );
3412                assert!(proc_.args.is_empty());
3413            }
3414            other => panic!("expected explicit procedure invocation, got {other:?}"),
3415        }
3416
3417        assert!(call.yield_items.is_empty());
3418        assert!(!call.yield_all);
3419    }
3420
3421    #[test]
3422    fn parse_standalone_call_implicit() {
3423        let doc = parse_query("CALL db.labels").unwrap();
3424        let call = as_standalone_call(doc);
3425
3426        match call.procedure {
3427            ProcedureInvocationKind::Implicit(name) => {
3428                assert_eq!(name.parts, vec!["db".to_string(), "labels".to_string()]);
3429            }
3430            other => panic!("expected implicit procedure name, got {other:?}"),
3431        }
3432    }
3433
3434    #[test]
3435    fn parse_standalone_call_yield_all() {
3436        let doc = parse_query("CALL db.labels() YIELD *").unwrap();
3437        let call = as_standalone_call(doc);
3438
3439        assert!(call.yield_all);
3440        assert!(call.yield_items.is_empty());
3441    }
3442
3443    #[test]
3444    fn parse_standalone_call_yield_items() {
3445        let doc = parse_query("CALL db.labels() YIELD label, value AS v").unwrap();
3446        let call = as_standalone_call(doc);
3447
3448        assert!(!call.yield_all);
3449        assert_eq!(call.yield_items.len(), 2);
3450
3451        assert_eq!(call.yield_items[0].field, None);
3452        assert_eq!(call.yield_items[0].alias.name, "label");
3453
3454        assert_eq!(call.yield_items[1].field.as_deref(), Some("value"));
3455        assert_eq!(call.yield_items[1].alias.name, "v");
3456    }
3457
3458    #[test]
3459    fn parse_in_query_call() {
3460        let doc = parse_query("CALL db.labels() YIELD label RETURN label").unwrap();
3461        let sp = as_regular_single_part(doc);
3462
3463        assert_eq!(sp.reading_clauses.len(), 1);
3464        let ReadingClause::InQueryCall(call) = &sp.reading_clauses[0] else {
3465            panic!("expected in-query CALL");
3466        };
3467
3468        assert_eq!(
3469            call.procedure.name.parts,
3470            vec!["db".to_string(), "labels".to_string()]
3471        );
3472        assert_eq!(call.yield_items.len(), 1);
3473        assert!(call.where_.is_none());
3474    }
3475
3476    #[test]
3477    fn parse_in_query_call_with_where() {
3478        let doc = parse_query("CALL db.labels() YIELD label WHERE label IS NOT NULL RETURN label")
3479            .unwrap();
3480        let sp = as_regular_single_part(doc);
3481
3482        let ReadingClause::InQueryCall(call) = &sp.reading_clauses[0] else {
3483            panic!("expected in-query CALL");
3484        };
3485        let where_ = call.where_.as_ref().expect("expected WHERE on CALL");
3486
3487        match where_ {
3488            Expr::Binary { op, .. } => assert!(matches!(op, BinaryOp::IsNotNull)),
3489            other => panic!("expected binary WHERE expression, got {other:?}"),
3490        }
3491    }
3492
3493    #[test]
3494    fn parse_semicolon() {
3495        let doc = parse_query("MATCH (n) RETURN n;").unwrap();
3496        let sp = as_regular_single_part(doc);
3497        assert!(sp.return_clause.is_some());
3498    }
3499}