1use pest::iterators::Pair;
2use pest::Parser;
3use pest_derive::Parser;
4
5use crate::ast::*;
6use crate::error::QueryError;
7
8#[derive(Parser)]
9#[grammar = "cypher.pest"]
10struct CypherParser;
11
12pub fn parse(input: &str) -> Result<Statement, QueryError> {
13 let mut pairs = CypherParser::parse(Rule::query, input)
14 .map_err(|e| QueryError::Parse(e.to_string()))?;
15 let query_pair = pairs.next().expect("query rule always produces one pair");
16 let statement_pair = query_pair
17 .into_inner()
18 .find(|p| p.as_rule() == Rule::statement)
19 .expect("query grammar guarantees a statement");
20 parse_statement(statement_pair)
21}
22
23pub fn parse_many(input: &str) -> Result<Vec<Statement>, QueryError> {
27 let mut pairs = CypherParser::parse(Rule::queries, input)
28 .map_err(|e| QueryError::Parse(e.to_string()))?;
29 let queries_pair = pairs.next().expect("queries rule always produces one pair");
30 queries_pair
31 .into_inner()
32 .filter(|p| p.as_rule() == Rule::statement)
33 .map(parse_statement)
34 .collect()
35}
36
37fn parse_statement(pair: Pair<Rule>) -> Result<Statement, QueryError> {
38 let inner = pair.into_inner().next().expect("statement has one child");
39 match inner.as_rule() {
40 Rule::create_stmt => parse_create_stmt(inner),
41 Rule::match_stmt => parse_match_stmt(inner),
42 r => unreachable!("unexpected statement child rule {r:?}"),
43 }
44}
45
46fn parse_create_stmt(pair: Pair<Rule>) -> Result<Statement, QueryError> {
47 Ok(Statement::Create(parse_create_patterns(pair)?))
48}
49
50fn parse_create_patterns(pair: Pair<Rule>) -> Result<Vec<Pattern>, QueryError> {
55 pair.into_inner()
56 .filter(|p| p.as_rule() == Rule::pattern)
57 .map(parse_pattern)
58 .collect()
59}
60
61fn parse_match_stmt(pair: Pair<Rule>) -> Result<Statement, QueryError> {
62 let mut clauses = Vec::new();
63 let mut tail = None;
64 let mut order_by = None;
65 let mut limit = None;
66 for p in pair.into_inner() {
67 match p.as_rule() {
68 Rule::clause => clauses.push(parse_clause(p)?),
69 Rule::tail_clause => tail = Some(parse_tail_clause(p)?),
70 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
71 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
72 r => unreachable!("unexpected match_stmt child rule {r:?}"),
73 }
74 }
75
76 let with_count = clauses.iter().filter(|c| clause_with(c).is_some()).count();
87 if with_count > 1 {
88 return Err(QueryError::Parse(
89 "chaining past one WITH boundary in a single MATCH isn't supported yet".into(),
90 ));
91 }
92 for i in 0..clauses.len() {
93 let (QueryClause::Match(part), Some(QueryClause::Match(next))) = (&clauses[i], clauses.get(i + 1)) else {
94 continue;
95 };
96 if part.with.is_none() && !next.optional {
97 return Err(QueryError::Parse(
98 "multiple MATCH clauses must be separated by WITH".into(),
99 ));
100 }
101 }
102
103 if tail.is_none() && !clauses.iter().any(|c| matches!(c, QueryClause::Merge(_))) {
109 return Err(QueryError::Parse(
110 "a query needs a RETURN/DELETE/SET tail, unless it has a MERGE clause with nothing after it".into(),
111 ));
112 }
113
114 Ok(Statement::Match {
115 clauses,
116 tail,
117 order_by,
118 limit,
119 })
120}
121
122fn clause_with(clause: &QueryClause) -> Option<&WithClause> {
123 match clause {
124 QueryClause::Match(part) => part.with.as_ref(),
125 QueryClause::Unwind(u) => u.with.as_ref(),
126 QueryClause::Merge(m) => m.with.as_ref(),
127 }
128}
129
130fn parse_clause(pair: Pair<Rule>) -> Result<QueryClause, QueryError> {
131 let inner = pair.into_inner().next().expect("clause has one child");
132 match inner.as_rule() {
133 Rule::match_part => Ok(QueryClause::Match(parse_match_part(inner)?)),
134 Rule::unwind_clause => Ok(QueryClause::Unwind(parse_unwind_clause(inner)?)),
135 Rule::merge_clause => Ok(QueryClause::Merge(parse_merge_clause(inner)?)),
136 r => unreachable!("unexpected clause child rule {r:?}"),
137 }
138}
139
140fn parse_merge_clause(pair: Pair<Rule>) -> Result<MergeClause, QueryError> {
145 let mut inner = pair.into_inner();
146 let pattern = parse_pattern(inner.next().expect("merge_clause has a pattern"))?;
147 if pattern.hops.len() > 1 {
148 return Err(QueryError::Parse(
149 "MERGE with more than one relationship hop isn't supported yet — split it into a MATCH \
150 for the already-known part and a MERGE for one new hop"
151 .into(),
152 ));
153 }
154 let mut on_create = Vec::new();
155 let mut on_match = Vec::new();
156 let mut with = None;
157 for p in inner {
158 match p.as_rule() {
159 Rule::on_create_clause => {
160 on_create = p.into_inner().filter(|p| p.as_rule() == Rule::set_item).map(parse_set_item).collect::<Result<_, _>>()?;
161 }
162 Rule::on_match_clause => {
163 on_match = p.into_inner().filter(|p| p.as_rule() == Rule::set_item).map(parse_set_item).collect::<Result<_, _>>()?;
164 }
165 Rule::with_clause => with = Some(parse_with_clause(p)?),
166 r => unreachable!("unexpected merge_clause child rule {r:?}"),
167 }
168 }
169 Ok(MergeClause {
170 pattern,
171 on_create,
172 on_match,
173 with,
174 })
175}
176
177fn parse_unwind_clause(pair: Pair<Rule>) -> Result<UnwindClause, QueryError> {
178 let mut inner = pair.into_inner();
179 let source = parse_unwind_source(inner.next().expect("unwind_clause has an unwind_source"))?;
180 let var = inner.next().expect("unwind_clause has an AS identifier").as_str().to_string();
181 let mut where_clause = None;
182 let mut with = None;
183 for p in inner {
184 match p.as_rule() {
185 Rule::with_where_clause => {
186 let expr_pair = p.into_inner().next().expect("WHERE has a with_expr");
187 where_clause = Some(parse_with_expr(expr_pair)?);
188 }
189 Rule::with_clause => with = Some(parse_with_clause(p)?),
190 r => unreachable!("unexpected unwind_clause child rule {r:?}"),
191 }
192 }
193 Ok(UnwindClause {
194 source,
195 var,
196 where_clause,
197 with,
198 })
199}
200
201fn parse_unwind_source(pair: Pair<Rule>) -> Result<UnwindSource, QueryError> {
202 let inner = pair.into_inner().next().expect("unwind_source has one child");
203 match inner.as_rule() {
204 Rule::list_literal => Ok(UnwindSource::List(
205 inner
206 .into_inner()
207 .filter(|p| p.as_rule() == Rule::literal)
208 .map(parse_literal)
209 .collect::<Result<Vec<_>, _>>()?,
210 )),
211 Rule::null_literal => Ok(UnwindSource::List(vec![])),
214 Rule::identifier => Ok(UnwindSource::Var(inner.as_str().to_string())),
215 r => unreachable!("unexpected unwind_source child rule {r:?}"),
216 }
217}
218
219fn parse_match_part(pair: Pair<Rule>) -> Result<QueryPart, QueryError> {
220 let mut optional = false;
221 let mut path_var = None;
222 let mut shortest_path = false;
223 let mut patterns = Vec::new();
224 let mut where_clause = None;
225 let mut with = None;
226 for p in pair.into_inner() {
227 match p.as_rule() {
228 Rule::match_keyword => {
229 optional = p.as_str().to_ascii_uppercase().starts_with("OPTIONAL");
230 }
231 Rule::path_pattern => {
232 let (var, is_shortest, pattern) = parse_path_pattern(p)?;
233 path_var = var;
234 shortest_path = is_shortest;
235 patterns.push(pattern);
236 }
237 Rule::pattern => patterns.push(parse_pattern(p)?),
238 Rule::where_clause => {
239 let expr_pair = p.into_inner().next().expect("WHERE has an expr");
240 where_clause = Some(parse_expr(expr_pair)?);
241 }
242 Rule::with_clause => with = Some(parse_with_clause(p)?),
243 r => unreachable!("unexpected match_part child rule {r:?}"),
244 }
245 }
246 let pattern = splice_patterns(patterns)?;
247 if shortest_path {
248 validate_shortest_path_pattern(&pattern)?;
249 } else if path_var.is_some() {
250 validate_named_path_pattern(&pattern)?;
251 }
252 Ok(QueryPart {
253 optional,
254 path_var,
255 shortest_path,
256 pattern,
257 where_clause,
258 with,
259 })
260}
261
262fn parse_path_pattern(pair: Pair<Rule>) -> Result<(Option<String>, bool, Pattern), QueryError> {
263 let mut var = None;
264 let mut shortest_path = false;
265 let mut pattern = None;
266 for p in pair.into_inner() {
267 match p.as_rule() {
268 Rule::identifier => var = Some(p.as_str().to_string()),
269 Rule::shortest_path_wrapper => {
270 shortest_path = true;
271 let inner_pattern = p.into_inner().next().expect("shortest_path_wrapper has a pattern");
272 pattern = Some(parse_pattern(inner_pattern)?);
273 }
274 Rule::pattern => pattern = Some(parse_pattern(p)?),
275 r => unreachable!("unexpected path_pattern child rule {r:?}"),
276 }
277 }
278 Ok((var, shortest_path, pattern.expect("path_pattern always has a pattern or shortest_path_wrapper")))
279}
280
281fn validate_shortest_path_pattern(pattern: &Pattern) -> Result<(), QueryError> {
287 if pattern.hops.len() != 1 || pattern.hops[0].0.hop_range.is_none() {
288 return Err(QueryError::Parse(
289 "shortestPath() requires exactly one variable-length relationship pattern (e.g. (a)-[:TYPE*..5]-(b))"
290 .into(),
291 ));
292 }
293 Ok(())
294}
295
296fn validate_named_path_pattern(pattern: &Pattern) -> Result<(), QueryError> {
300 if pattern.hops.iter().any(|(rel, _)| rel.hop_range.is_some()) {
301 return Err(QueryError::Parse(
302 "named-path capture (`p = ...`) over a variable-length relationship pattern isn't supported yet \
303 — use shortestPath() instead, or drop the path variable"
304 .into(),
305 ));
306 }
307 Ok(())
308}
309
310fn parse_with_clause(pair: Pair<Rule>) -> Result<WithClause, QueryError> {
311 let mut items = Vec::new();
312 let mut where_clause = None;
313 let mut order_by = None;
314 let mut limit = None;
315 for p in pair.into_inner() {
316 match p.as_rule() {
317 Rule::return_item => items.push(parse_return_item(p)?),
318 Rule::with_where_clause => {
319 let expr_pair = p.into_inner().next().expect("WITH...WHERE has a with_expr");
320 where_clause = Some(parse_with_expr(expr_pair)?);
321 }
322 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
323 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
324 r => unreachable!("unexpected with_clause child rule {r:?}"),
325 }
326 }
327 Ok(WithClause {
328 items,
329 where_clause,
330 order_by,
331 limit,
332 })
333}
334
335fn parse_with_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
336 parse_with_or_expr(pair.into_inner().next().expect("with_expr has a with_or_expr"))
338}
339
340fn parse_with_or_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
341 let mut parts = pair.into_inner();
342 let mut acc = parse_with_and_expr(parts.next().expect("with_or_expr has at least one with_and_expr"))?;
343 for rest in parts {
344 acc = WithExpr::Or(Box::new(acc), Box::new(parse_with_and_expr(rest)?));
345 }
346 Ok(acc)
347}
348
349fn parse_with_and_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
350 let mut parts = pair.into_inner();
351 let mut acc = parse_with_unary_expr(parts.next().expect("with_and_expr has at least one with_unary_expr"))?;
352 for rest in parts {
353 acc = WithExpr::And(Box::new(acc), Box::new(parse_with_unary_expr(rest)?));
354 }
355 Ok(acc)
356}
357
358fn parse_with_unary_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
359 let inner = pair.into_inner().next().expect("with_unary_expr has one child");
360 match inner.as_rule() {
361 Rule::with_unary_expr => Ok(WithExpr::Not(Box::new(parse_with_unary_expr(inner)?))),
362 Rule::with_comparison => parse_with_comparison(inner),
363 Rule::with_expr => parse_with_expr(inner),
364 r => unreachable!("unexpected with_unary_expr child rule {r:?}"),
365 }
366}
367
368fn parse_with_comparison(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
369 let mut inner = pair.into_inner();
370 let lhs = parse_return_expr(inner.next().expect("with_comparison has a return_expr"))?;
371 let op_pair = inner.next().expect("with_comparison has a compare_op");
372 let op = parse_compare_op(op_pair);
373 let literal = parse_literal(inner.next().expect("with_comparison has a literal"))?;
374 Ok(WithExpr::Compare(lhs, op, literal))
375}
376
377fn parse_order_by_clause(pair: Pair<Rule>) -> Result<Vec<(ReturnExpr, SortDir)>, QueryError> {
378 pair.into_inner()
379 .filter(|c| c.as_rule() == Rule::sort_item)
380 .map(parse_sort_item)
381 .collect()
382}
383
384fn parse_limit_clause(pair: Pair<Rule>) -> Result<i64, QueryError> {
385 let n_pair = pair.into_inner().next().expect("LIMIT has an int_literal");
386 let n = n_pair
387 .as_str()
388 .parse::<i64>()
389 .map_err(|_| QueryError::Parse("invalid LIMIT value".into()))?;
390 if n < 0 {
391 return Err(QueryError::Parse("LIMIT can't be negative".into()));
392 }
393 Ok(n)
394}
395
396fn splice_patterns(mut patterns: Vec<Pattern>) -> Result<Pattern, QueryError> {
406 if patterns.is_empty() {
407 return Err(QueryError::Parse("MATCH requires a pattern".into()));
408 }
409 let mut combined = patterns.remove(0);
410 for next in patterns {
411 let Some(start_var) = next.start.var.clone() else {
412 return Err(QueryError::Parse(
413 "a comma-separated MATCH pattern must start from a named variable".into(),
414 ));
415 };
416 let last_var = combined
417 .hops
418 .last()
419 .map(|(_, n)| n.var.clone())
420 .unwrap_or_else(|| combined.start.var.clone());
421 if last_var.as_deref() != Some(start_var.as_str()) {
422 return Err(QueryError::Parse(format!(
423 "comma-separated MATCH pattern must continue from the previous pattern's last \
424 variable ('{}'), not '{start_var}' — general cross-joins aren't supported",
425 last_var.unwrap_or_default()
426 )));
427 }
428 let target = match combined.hops.last_mut() {
429 Some((_, node)) => node,
430 None => &mut combined.start,
431 };
432 target.labels.extend(next.start.labels);
433 target.props.extend(next.start.props);
434 combined.hops.extend(next.hops);
435 }
436 Ok(combined)
437}
438
439fn parse_sort_item(pair: Pair<Rule>) -> Result<(ReturnExpr, SortDir), QueryError> {
440 let mut inner = pair.into_inner();
441 let expr = parse_return_expr(inner.next().expect("sort_item has a return_expr"))?;
442 let dir = match inner.next() {
443 Some(d) if d.as_str().eq_ignore_ascii_case("desc") => SortDir::Desc,
444 _ => SortDir::Asc,
445 };
446 Ok((expr, dir))
447}
448
449fn parse_tail_clause(pair: Pair<Rule>) -> Result<Tail, QueryError> {
450 let inner = pair.into_inner().next().expect("tail_clause has one child");
451 match inner.as_rule() {
452 Rule::return_clause => {
453 let children: Vec<_> = inner.into_inner().collect();
454 let distinct = children.iter().any(|p| p.as_rule() == Rule::distinct_kw);
455 let items = children
456 .into_iter()
457 .filter(|p| p.as_rule() == Rule::return_item)
458 .map(parse_return_item)
459 .collect::<Result<Vec<_>, _>>()?;
460 Ok(Tail::Return(items, distinct))
461 }
462 Rule::detach_delete_clause => {
463 let vars = inner
464 .into_inner()
465 .filter(|p| p.as_rule() == Rule::identifier)
466 .map(|p| p.as_str().to_string())
467 .collect();
468 Ok(Tail::DetachDelete(vars))
469 }
470 Rule::delete_clause => {
471 let vars = inner
472 .into_inner()
473 .filter(|p| p.as_rule() == Rule::identifier)
474 .map(|p| p.as_str().to_string())
475 .collect();
476 Ok(Tail::Delete(vars))
477 }
478 Rule::set_clause => {
479 let items = inner
480 .into_inner()
481 .filter(|p| p.as_rule() == Rule::set_item)
482 .map(parse_set_item)
483 .collect::<Result<Vec<_>, _>>()?;
484 Ok(Tail::Set(items))
485 }
486 Rule::remove_clause => {
487 let items = inner.into_inner().filter(|p| p.as_rule() == Rule::remove_item).map(parse_remove_item).collect();
488 Ok(Tail::Remove(items))
489 }
490 Rule::create_stmt => Ok(Tail::Create(parse_create_patterns(inner)?)),
491 r => unreachable!("unexpected tail_clause child rule {r:?}"),
492 }
493}
494
495fn parse_set_item(pair: Pair<Rule>) -> Result<SetItem, QueryError> {
496 let mut inner = pair.into_inner();
497 let first = inner.next().expect("set_item has at least one child");
498 match first.as_rule() {
499 Rule::prop_access => {
500 let literal_pair = inner.next().expect("set_item's prop_access form has a literal");
501 Ok(SetItem::Prop(parse_prop_access(first), parse_literal(literal_pair)?))
502 }
503 Rule::set_label_item => {
504 let (var, labels) = parse_set_label_item(first);
505 Ok(SetItem::Labels(var, labels))
506 }
507 r => unreachable!("unexpected set_item child rule {r:?}"),
508 }
509}
510
511fn parse_set_label_item(pair: Pair<Rule>) -> (String, Vec<String>) {
512 let mut inner = pair.into_inner();
513 let var = inner.next().expect("set_label_item has a var identifier").as_str().to_string();
514 let labels = inner.map(|p| p.as_str().to_string()).collect();
515 (var, labels)
516}
517
518fn parse_remove_item(pair: Pair<Rule>) -> RemoveItem {
519 let inner = pair.into_inner().next().expect("remove_item has one child");
520 match inner.as_rule() {
521 Rule::prop_access => RemoveItem::Prop(parse_prop_access(inner)),
522 Rule::set_label_item => {
523 let (var, labels) = parse_set_label_item(inner);
524 RemoveItem::Labels(var, labels)
525 }
526 r => unreachable!("unexpected remove_item child rule {r:?}"),
527 }
528}
529
530fn parse_return_item(pair: Pair<Rule>) -> Result<ReturnItem, QueryError> {
531 let mut inner = pair.into_inner();
532 let expr_pair = inner.next().expect("return_item has a return_expr");
533 let expr = parse_return_expr(expr_pair)?;
534 let alias = inner.next().map(|p| p.as_str().to_string());
535 Ok(ReturnItem { expr, alias })
536}
537
538fn parse_return_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
539 let inner = pair.into_inner().next().expect("return_expr has one child");
540 match inner.as_rule() {
541 Rule::case_expr => parse_case_expr(inner),
542 Rule::function_call => parse_function_call(inner),
543 Rule::prop_access => Ok(ReturnExpr::Prop(parse_prop_access(inner))),
544 Rule::literal => Ok(ReturnExpr::Lit(parse_literal(inner)?)),
545 Rule::identifier => Ok(ReturnExpr::Var(inner.as_str().to_string())),
546 r => unreachable!("unexpected return_expr child rule {r:?}"),
547 }
548}
549
550fn parse_case_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
551 let mut inner = pair.into_inner();
552 let test = parse_return_expr(inner.next().expect("case_expr has a test expr"))?;
553 let mut whens = Vec::new();
554 let mut else_ = None;
555 for p in inner {
556 match p.as_rule() {
557 Rule::case_when => {
558 let mut when_inner = p.into_inner();
559 let when = parse_return_expr(when_inner.next().expect("case_when has a WHEN expr"))?;
560 let then = parse_return_expr(when_inner.next().expect("case_when has a THEN expr"))?;
561 whens.push((when, then));
562 }
563 _ => else_ = Some(Box::new(parse_return_expr(p)?)),
565 }
566 }
567 Ok(ReturnExpr::Case {
568 test: Some(Box::new(test)),
569 whens,
570 else_,
571 })
572}
573
574fn parse_function_call(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
575 let mut inner = pair.into_inner();
576 let name = inner.next().expect("function_call has a name").as_str().to_string();
577 let call_args = inner.next().expect("function_call has call_args");
578 let is_star = call_args.as_str().trim() == "*";
579 if is_star {
580 if !name.eq_ignore_ascii_case("count") {
581 return Err(QueryError::Parse(format!(
582 "'{name}(*)' isn't valid — '*' is only meaningful for count(*)"
583 )));
584 }
585 return Ok(ReturnExpr::CountStar);
586 }
587 let mut distinct = false;
588 let mut args = Vec::new();
589 for p in call_args.into_inner() {
590 match p.as_rule() {
591 Rule::distinct_kw => distinct = true,
592 _ => args.push(parse_return_expr(p)?),
593 }
594 }
595 if distinct && !is_aggregate_name(&name) {
596 return Err(QueryError::Parse(format!(
597 "'{name}(DISTINCT ...)' isn't valid — DISTINCT is only meaningful inside an aggregate function"
598 )));
599 }
600 Ok(ReturnExpr::Call { name, args, distinct })
601}
602
603fn parse_prop_access(pair: Pair<Rule>) -> PropAccess {
604 let mut inner = pair.into_inner();
605 let var = inner.next().expect("prop_access has a var").as_str().to_string();
606 let prop = inner.next().expect("prop_access has a prop").as_str().to_string();
607 PropAccess { var, prop }
608}
609
610fn parse_pattern(pair: Pair<Rule>) -> Result<Pattern, QueryError> {
611 let mut inner = pair.into_inner();
612 let start = parse_node_pattern(inner.next().expect("pattern has a start node"))?;
613 let mut hops = Vec::new();
614 loop {
615 let Some(rel_pair) = inner.next() else { break };
616 let node_pair = inner
617 .next()
618 .ok_or_else(|| QueryError::Parse("dangling relationship in pattern".into()))?;
619 hops.push((parse_rel_pattern(rel_pair)?, parse_node_pattern(node_pair)?));
620 }
621 Ok(Pattern { start, hops })
622}
623
624fn parse_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, QueryError> {
625 let mut var = None;
626 let mut labels = Vec::new();
627 let mut props = Vec::new();
628 for p in pair.into_inner() {
629 match p.as_rule() {
630 Rule::node_var => var = Some(p.as_str().to_string()),
631 Rule::node_label => {
632 labels.push(p.into_inner().next().expect("node_label has an identifier").as_str().to_string())
633 }
634 Rule::prop_map => props = parse_prop_map(p)?,
635 r => unreachable!("unexpected node_pattern child rule {r:?}"),
636 }
637 }
638 Ok(NodePattern { var, labels, props })
639}
640
641fn parse_rel_pattern(pair: Pair<Rule>) -> Result<RelPattern, QueryError> {
642 let inner = pair.into_inner().next().expect("rel_pattern has one child");
643 let direction = match inner.as_rule() {
644 Rule::rel_right => RelDirection::Right,
645 Rule::rel_left => RelDirection::Left,
646 Rule::rel_either => RelDirection::Either,
647 r => unreachable!("unexpected rel_pattern child rule {r:?}"),
648 };
649 let mut var = None;
650 let mut rel_type = None;
651 let mut props = Vec::new();
652 let mut hop_range = None;
653 for p in inner.into_inner() {
654 match p.as_rule() {
655 Rule::rel_var => var = Some(p.as_str().to_string()),
656 Rule::rel_type => {
657 rel_type = Some(p.into_inner().next().expect("rel_type has an identifier").as_str().to_string())
658 }
659 Rule::rel_range => hop_range = Some(parse_rel_range(p.as_str())?),
660 Rule::prop_map => props = parse_prop_map(p)?,
661 r => unreachable!("unexpected rel_right/rel_left/rel_either child rule {r:?}"),
662 }
663 }
664 Ok(RelPattern {
665 var,
666 rel_type,
667 props,
668 direction,
669 hop_range,
670 })
671}
672
673fn parse_rel_range(text: &str) -> Result<(u32, Option<u32>), QueryError> {
678 let rest = &text[1..]; if rest.is_empty() {
680 return Ok((0, None));
681 }
682 if let Some(idx) = rest.find("..") {
683 let min_str = &rest[..idx];
684 let max_str = &rest[idx + 2..];
685 let min = if min_str.is_empty() {
686 0
687 } else {
688 min_str
689 .parse()
690 .map_err(|_| QueryError::Parse("invalid variable-length min hop count".into()))?
691 };
692 let max = if max_str.is_empty() {
693 None
694 } else {
695 Some(
696 max_str
697 .parse()
698 .map_err(|_| QueryError::Parse("invalid variable-length max hop count".into()))?,
699 )
700 };
701 Ok((min, max))
702 } else {
703 let n: u32 = rest
704 .parse()
705 .map_err(|_| QueryError::Parse("invalid variable-length hop count".into()))?;
706 Ok((n, Some(n)))
707 }
708}
709
710fn parse_prop_map(pair: Pair<Rule>) -> Result<Vec<(String, Literal)>, QueryError> {
711 pair.into_inner()
712 .filter(|p| p.as_rule() == Rule::prop_kv)
713 .map(|p| {
714 let mut inner = p.into_inner();
715 let key = inner.next().expect("prop_kv has a key").as_str().to_string();
716 let value = parse_literal(inner.next().expect("prop_kv has a value"))?;
717 Ok((key, value))
718 })
719 .collect()
720}
721
722fn unescape_string(s: &str) -> Result<String, QueryError> {
731 if !s.contains('\\') {
732 return Ok(s.to_string());
733 }
734 let mut out = String::with_capacity(s.len());
735 let mut chars = s.chars();
736 while let Some(c) = chars.next() {
737 if c != '\\' {
738 out.push(c);
739 continue;
740 }
741 match chars.next() {
742 Some('\\') => out.push('\\'),
743 Some('\'') => out.push('\''),
744 Some('"') => out.push('"'),
745 Some('n') => out.push('\n'),
746 Some('r') => out.push('\r'),
747 Some('t') => out.push('\t'),
748 Some('b') => out.push('\u{8}'),
749 Some('f') => out.push('\u{c}'),
750 Some(other) => {
751 return Err(QueryError::Parse(format!("unrecognized string escape '\\{other}'")))
752 }
753 None => return Err(QueryError::Parse("string ends with a trailing '\\'".into())),
754 }
755 }
756 Ok(out)
757}
758
759fn parse_literal(pair: Pair<Rule>) -> Result<Literal, QueryError> {
760 let inner = pair.into_inner().next().expect("literal has one child");
761 Ok(match inner.as_rule() {
762 Rule::int_literal => Literal::Int(
763 inner
764 .as_str()
765 .parse()
766 .map_err(|_| QueryError::Parse("invalid integer literal".into()))?,
767 ),
768 Rule::float_literal => Literal::Float(
769 inner
770 .as_str()
771 .parse()
772 .map_err(|_| QueryError::Parse("invalid float literal".into()))?,
773 ),
774 Rule::string_literal => {
775 let s = inner.as_str();
776 Literal::String(unescape_string(&s[1..s.len() - 1])?)
777 }
778 Rule::bool_literal => Literal::Bool(inner.as_str().eq_ignore_ascii_case("true")),
779 Rule::null_literal => Literal::Null,
780 Rule::param => {
781 let name = inner.into_inner().next().expect("param has an identifier").as_str().to_string();
782 Literal::Param(name)
783 }
784 r => unreachable!("unexpected literal child rule {r:?}"),
785 })
786}
787
788fn parse_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
789 parse_or_expr(pair.into_inner().next().expect("expr has an or_expr"))
791}
792
793fn parse_or_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
794 let mut parts = pair.into_inner();
795 let mut acc = parse_and_expr(parts.next().expect("or_expr has at least one and_expr"))?;
796 for rest in parts {
797 acc = Expr::Or(Box::new(acc), Box::new(parse_and_expr(rest)?));
798 }
799 Ok(acc)
800}
801
802fn parse_and_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
803 let mut parts = pair.into_inner();
804 let mut acc = parse_unary_expr(parts.next().expect("and_expr has at least one unary_expr"))?;
805 for rest in parts {
806 acc = Expr::And(Box::new(acc), Box::new(parse_unary_expr(rest)?));
807 }
808 Ok(acc)
809}
810
811fn parse_unary_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
812 let inner = pair.into_inner().next().expect("unary_expr has one child");
813 match inner.as_rule() {
814 Rule::unary_expr => Ok(Expr::Not(Box::new(parse_unary_expr(inner)?))),
815 Rule::comparison => parse_comparison(inner),
816 Rule::expr => parse_expr(inner),
817 r => unreachable!("unexpected unary_expr child rule {r:?}"),
818 }
819}
820
821fn parse_comparison(pair: Pair<Rule>) -> Result<Expr, QueryError> {
822 let mut inner = pair.into_inner();
823 let prop_access = parse_prop_access(inner.next().expect("comparison has a prop_access"));
824 let op = parse_compare_op(inner.next().expect("comparison has a compare_op"));
825 let literal = parse_literal(inner.next().expect("comparison has a literal"))?;
826 Ok(Expr::Compare(prop_access, op, literal))
827}
828
829fn parse_compare_op(pair: Pair<Rule>) -> CompareOp {
830 let normalized = pair.as_str().split_whitespace().collect::<Vec<_>>().join(" ").to_ascii_uppercase();
835 match normalized.as_str() {
836 "=" => CompareOp::Eq,
837 "<>" => CompareOp::Ne,
838 "<" => CompareOp::Lt,
839 "<=" => CompareOp::Le,
840 ">" => CompareOp::Gt,
841 ">=" => CompareOp::Ge,
842 "STARTS WITH" => CompareOp::StartsWith,
843 "ENDS WITH" => CompareOp::EndsWith,
844 "CONTAINS" => CompareOp::Contains,
845 other => unreachable!("unexpected compare_op {other:?}"),
846 }
847}