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 let patterns = pair
48 .into_inner()
49 .filter(|p| p.as_rule() == Rule::pattern)
50 .map(parse_pattern)
51 .collect::<Result<Vec<_>, _>>()?;
52 Ok(Statement::Create(patterns))
53}
54
55fn parse_match_stmt(pair: Pair<Rule>) -> Result<Statement, QueryError> {
56 let mut parts = Vec::new();
57 let mut tail = None;
58 let mut order_by = None;
59 let mut limit = None;
60 for p in pair.into_inner() {
61 match p.as_rule() {
62 Rule::match_part => parts.push(parse_match_part(p)?),
63 Rule::tail_clause => tail = Some(parse_tail_clause(p)?),
64 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
65 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
66 r => unreachable!("unexpected match_stmt child rule {r:?}"),
67 }
68 }
69
70 let with_count = parts.iter().filter(|p| p.with.is_some()).count();
79 if with_count > 1 {
80 return Err(QueryError::Parse(
81 "chaining past one WITH boundary in a single MATCH isn't supported yet".into(),
82 ));
83 }
84 for (i, part) in parts.iter().enumerate() {
85 if i + 1 < parts.len() && part.with.is_none() && !parts[i + 1].optional {
86 return Err(QueryError::Parse(
87 "multiple MATCH clauses must be separated by WITH".into(),
88 ));
89 }
90 }
91
92 Ok(Statement::Match {
93 parts,
94 tail: tail.ok_or_else(|| QueryError::Parse("MATCH requires RETURN/DELETE/SET".into()))?,
95 order_by,
96 limit,
97 })
98}
99
100fn parse_match_part(pair: Pair<Rule>) -> Result<QueryPart, QueryError> {
101 let mut optional = false;
102 let mut patterns = Vec::new();
103 let mut where_clause = None;
104 let mut with = None;
105 for p in pair.into_inner() {
106 match p.as_rule() {
107 Rule::match_keyword => {
108 optional = p.as_str().to_ascii_uppercase().starts_with("OPTIONAL");
109 }
110 Rule::pattern => patterns.push(parse_pattern(p)?),
111 Rule::where_clause => {
112 let expr_pair = p.into_inner().next().expect("WHERE has an expr");
113 where_clause = Some(parse_expr(expr_pair)?);
114 }
115 Rule::with_clause => with = Some(parse_with_clause(p)?),
116 r => unreachable!("unexpected match_part child rule {r:?}"),
117 }
118 }
119 let pattern = splice_patterns(patterns)?;
120 Ok(QueryPart {
121 optional,
122 pattern,
123 where_clause,
124 with,
125 })
126}
127
128fn parse_with_clause(pair: Pair<Rule>) -> Result<WithClause, QueryError> {
129 let mut items = Vec::new();
130 let mut where_clause = None;
131 let mut order_by = None;
132 let mut limit = None;
133 for p in pair.into_inner() {
134 match p.as_rule() {
135 Rule::return_item => items.push(parse_return_item(p)?),
136 Rule::with_where_clause => {
137 let expr_pair = p.into_inner().next().expect("WITH...WHERE has a with_expr");
138 where_clause = Some(parse_with_expr(expr_pair)?);
139 }
140 Rule::order_by_clause => order_by = Some(parse_order_by_clause(p)?),
141 Rule::limit_clause => limit = Some(parse_limit_clause(p)?),
142 r => unreachable!("unexpected with_clause child rule {r:?}"),
143 }
144 }
145 Ok(WithClause {
146 items,
147 where_clause,
148 order_by,
149 limit,
150 })
151}
152
153fn parse_with_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
154 parse_with_or_expr(pair.into_inner().next().expect("with_expr has a with_or_expr"))
156}
157
158fn parse_with_or_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
159 let mut parts = pair.into_inner();
160 let mut acc = parse_with_and_expr(parts.next().expect("with_or_expr has at least one with_and_expr"))?;
161 for rest in parts {
162 acc = WithExpr::Or(Box::new(acc), Box::new(parse_with_and_expr(rest)?));
163 }
164 Ok(acc)
165}
166
167fn parse_with_and_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
168 let mut parts = pair.into_inner();
169 let mut acc = parse_with_unary_expr(parts.next().expect("with_and_expr has at least one with_unary_expr"))?;
170 for rest in parts {
171 acc = WithExpr::And(Box::new(acc), Box::new(parse_with_unary_expr(rest)?));
172 }
173 Ok(acc)
174}
175
176fn parse_with_unary_expr(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
177 let inner = pair.into_inner().next().expect("with_unary_expr has one child");
178 match inner.as_rule() {
179 Rule::with_unary_expr => Ok(WithExpr::Not(Box::new(parse_with_unary_expr(inner)?))),
180 Rule::with_comparison => parse_with_comparison(inner),
181 Rule::with_expr => parse_with_expr(inner),
182 r => unreachable!("unexpected with_unary_expr child rule {r:?}"),
183 }
184}
185
186fn parse_with_comparison(pair: Pair<Rule>) -> Result<WithExpr, QueryError> {
187 let mut inner = pair.into_inner();
188 let lhs = parse_return_expr(inner.next().expect("with_comparison has a return_expr"))?;
189 let op_pair = inner.next().expect("with_comparison has a compare_op");
190 let op = parse_compare_op(op_pair);
191 let literal = parse_literal(inner.next().expect("with_comparison has a literal"))?;
192 Ok(WithExpr::Compare(lhs, op, literal))
193}
194
195fn parse_order_by_clause(pair: Pair<Rule>) -> Result<Vec<(ReturnExpr, SortDir)>, QueryError> {
196 pair.into_inner()
197 .filter(|c| c.as_rule() == Rule::sort_item)
198 .map(parse_sort_item)
199 .collect()
200}
201
202fn parse_limit_clause(pair: Pair<Rule>) -> Result<i64, QueryError> {
203 let n_pair = pair.into_inner().next().expect("LIMIT has an int_literal");
204 n_pair
205 .as_str()
206 .parse::<i64>()
207 .map_err(|_| QueryError::Parse("invalid LIMIT value".into()))
208}
209
210fn splice_patterns(mut patterns: Vec<Pattern>) -> Result<Pattern, QueryError> {
220 if patterns.is_empty() {
221 return Err(QueryError::Parse("MATCH requires a pattern".into()));
222 }
223 let mut combined = patterns.remove(0);
224 for next in patterns {
225 let Some(start_var) = next.start.var.clone() else {
226 return Err(QueryError::Parse(
227 "a comma-separated MATCH pattern must start from a named variable".into(),
228 ));
229 };
230 let last_var = combined
231 .hops
232 .last()
233 .map(|(_, n)| n.var.clone())
234 .unwrap_or_else(|| combined.start.var.clone());
235 if last_var.as_deref() != Some(start_var.as_str()) {
236 return Err(QueryError::Parse(format!(
237 "comma-separated MATCH pattern must continue from the previous pattern's last \
238 variable ('{}'), not '{start_var}' — general cross-joins aren't supported",
239 last_var.unwrap_or_default()
240 )));
241 }
242 let target = match combined.hops.last_mut() {
243 Some((_, node)) => node,
244 None => &mut combined.start,
245 };
246 target.labels.extend(next.start.labels);
247 target.props.extend(next.start.props);
248 combined.hops.extend(next.hops);
249 }
250 Ok(combined)
251}
252
253fn parse_sort_item(pair: Pair<Rule>) -> Result<(ReturnExpr, SortDir), QueryError> {
254 let mut inner = pair.into_inner();
255 let expr = parse_return_expr(inner.next().expect("sort_item has a return_expr"))?;
256 let dir = match inner.next() {
257 Some(d) if d.as_str().eq_ignore_ascii_case("desc") => SortDir::Desc,
258 _ => SortDir::Asc,
259 };
260 Ok((expr, dir))
261}
262
263fn parse_tail_clause(pair: Pair<Rule>) -> Result<Tail, QueryError> {
264 let inner = pair.into_inner().next().expect("tail_clause has one child");
265 match inner.as_rule() {
266 Rule::return_clause => {
267 let items = inner
268 .into_inner()
269 .filter(|p| p.as_rule() == Rule::return_item)
270 .map(parse_return_item)
271 .collect::<Result<Vec<_>, _>>()?;
272 Ok(Tail::Return(items))
273 }
274 Rule::detach_delete_clause => {
275 let vars = inner
276 .into_inner()
277 .filter(|p| p.as_rule() == Rule::identifier)
278 .map(|p| p.as_str().to_string())
279 .collect();
280 Ok(Tail::DetachDelete(vars))
281 }
282 Rule::delete_clause => {
283 let vars = inner
284 .into_inner()
285 .filter(|p| p.as_rule() == Rule::identifier)
286 .map(|p| p.as_str().to_string())
287 .collect();
288 Ok(Tail::Delete(vars))
289 }
290 Rule::set_clause => {
291 let items = inner
292 .into_inner()
293 .filter(|p| p.as_rule() == Rule::set_item)
294 .map(parse_set_item)
295 .collect::<Result<Vec<_>, _>>()?;
296 Ok(Tail::Set(items))
297 }
298 r => unreachable!("unexpected tail_clause child rule {r:?}"),
299 }
300}
301
302fn parse_set_item(pair: Pair<Rule>) -> Result<(PropAccess, Literal), QueryError> {
303 let mut inner = pair.into_inner();
304 let prop_access_pair = inner.next().expect("set_item has a prop_access");
305 let literal_pair = inner.next().expect("set_item has a literal");
306 Ok((parse_prop_access(prop_access_pair), parse_literal(literal_pair)?))
307}
308
309fn parse_return_item(pair: Pair<Rule>) -> Result<ReturnItem, QueryError> {
310 let mut inner = pair.into_inner();
311 let expr_pair = inner.next().expect("return_item has a return_expr");
312 let expr = parse_return_expr(expr_pair)?;
313 let alias = inner.next().map(|p| p.as_str().to_string());
314 Ok(ReturnItem { expr, alias })
315}
316
317fn parse_return_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
318 let inner = pair.into_inner().next().expect("return_expr has one child");
319 match inner.as_rule() {
320 Rule::case_expr => parse_case_expr(inner),
321 Rule::function_call => parse_function_call(inner),
322 Rule::prop_access => Ok(ReturnExpr::Prop(parse_prop_access(inner))),
323 Rule::literal => Ok(ReturnExpr::Lit(parse_literal(inner)?)),
324 Rule::identifier => Ok(ReturnExpr::Var(inner.as_str().to_string())),
325 r => unreachable!("unexpected return_expr child rule {r:?}"),
326 }
327}
328
329fn parse_case_expr(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
330 let mut inner = pair.into_inner();
331 let test = parse_return_expr(inner.next().expect("case_expr has a test expr"))?;
332 let mut whens = Vec::new();
333 let mut else_ = None;
334 for p in inner {
335 match p.as_rule() {
336 Rule::case_when => {
337 let mut when_inner = p.into_inner();
338 let when = parse_return_expr(when_inner.next().expect("case_when has a WHEN expr"))?;
339 let then = parse_return_expr(when_inner.next().expect("case_when has a THEN expr"))?;
340 whens.push((when, then));
341 }
342 _ => else_ = Some(Box::new(parse_return_expr(p)?)),
344 }
345 }
346 Ok(ReturnExpr::Case {
347 test: Some(Box::new(test)),
348 whens,
349 else_,
350 })
351}
352
353fn parse_function_call(pair: Pair<Rule>) -> Result<ReturnExpr, QueryError> {
354 let mut inner = pair.into_inner();
355 let name = inner.next().expect("function_call has a name").as_str().to_string();
356 let call_args = inner.next().expect("function_call has call_args");
357 let is_star = call_args.as_str().trim() == "*";
358 if is_star {
359 if !name.eq_ignore_ascii_case("count") {
360 return Err(QueryError::Parse(format!(
361 "'{name}(*)' isn't valid — '*' is only meaningful for count(*)"
362 )));
363 }
364 return Ok(ReturnExpr::CountStar);
365 }
366 let mut distinct = false;
367 let mut args = Vec::new();
368 for p in call_args.into_inner() {
369 match p.as_rule() {
370 Rule::distinct_kw => distinct = true,
371 _ => args.push(parse_return_expr(p)?),
372 }
373 }
374 if distinct && !is_aggregate_name(&name) {
375 return Err(QueryError::Parse(format!(
376 "'{name}(DISTINCT ...)' isn't valid — DISTINCT is only meaningful inside an aggregate function"
377 )));
378 }
379 Ok(ReturnExpr::Call { name, args, distinct })
380}
381
382fn parse_prop_access(pair: Pair<Rule>) -> PropAccess {
383 let mut inner = pair.into_inner();
384 let var = inner.next().expect("prop_access has a var").as_str().to_string();
385 let prop = inner.next().expect("prop_access has a prop").as_str().to_string();
386 PropAccess { var, prop }
387}
388
389fn parse_pattern(pair: Pair<Rule>) -> Result<Pattern, QueryError> {
390 let mut inner = pair.into_inner();
391 let start = parse_node_pattern(inner.next().expect("pattern has a start node"))?;
392 let mut hops = Vec::new();
393 loop {
394 let Some(rel_pair) = inner.next() else { break };
395 let node_pair = inner
396 .next()
397 .ok_or_else(|| QueryError::Parse("dangling relationship in pattern".into()))?;
398 hops.push((parse_rel_pattern(rel_pair)?, parse_node_pattern(node_pair)?));
399 }
400 Ok(Pattern { start, hops })
401}
402
403fn parse_node_pattern(pair: Pair<Rule>) -> Result<NodePattern, QueryError> {
404 let mut var = None;
405 let mut labels = Vec::new();
406 let mut props = Vec::new();
407 for p in pair.into_inner() {
408 match p.as_rule() {
409 Rule::node_var => var = Some(p.as_str().to_string()),
410 Rule::node_label => {
411 labels.push(p.into_inner().next().expect("node_label has an identifier").as_str().to_string())
412 }
413 Rule::prop_map => props = parse_prop_map(p)?,
414 r => unreachable!("unexpected node_pattern child rule {r:?}"),
415 }
416 }
417 Ok(NodePattern { var, labels, props })
418}
419
420fn parse_rel_pattern(pair: Pair<Rule>) -> Result<RelPattern, QueryError> {
421 let inner = pair.into_inner().next().expect("rel_pattern has one child");
422 let direction = match inner.as_rule() {
423 Rule::rel_right => RelDirection::Right,
424 Rule::rel_left => RelDirection::Left,
425 Rule::rel_either => RelDirection::Either,
426 r => unreachable!("unexpected rel_pattern child rule {r:?}"),
427 };
428 let mut var = None;
429 let mut rel_type = None;
430 let mut props = Vec::new();
431 let mut hop_range = None;
432 for p in inner.into_inner() {
433 match p.as_rule() {
434 Rule::rel_var => var = Some(p.as_str().to_string()),
435 Rule::rel_type => {
436 rel_type = Some(p.into_inner().next().expect("rel_type has an identifier").as_str().to_string())
437 }
438 Rule::rel_range => hop_range = Some(parse_rel_range(p.as_str())?),
439 Rule::prop_map => props = parse_prop_map(p)?,
440 r => unreachable!("unexpected rel_right/rel_left/rel_either child rule {r:?}"),
441 }
442 }
443 Ok(RelPattern {
444 var,
445 rel_type,
446 props,
447 direction,
448 hop_range,
449 })
450}
451
452fn parse_rel_range(text: &str) -> Result<(u32, Option<u32>), QueryError> {
457 let rest = &text[1..]; if rest.is_empty() {
459 return Ok((0, None));
460 }
461 if let Some(idx) = rest.find("..") {
462 let min_str = &rest[..idx];
463 let max_str = &rest[idx + 2..];
464 let min = if min_str.is_empty() {
465 0
466 } else {
467 min_str
468 .parse()
469 .map_err(|_| QueryError::Parse("invalid variable-length min hop count".into()))?
470 };
471 let max = if max_str.is_empty() {
472 None
473 } else {
474 Some(
475 max_str
476 .parse()
477 .map_err(|_| QueryError::Parse("invalid variable-length max hop count".into()))?,
478 )
479 };
480 Ok((min, max))
481 } else {
482 let n: u32 = rest
483 .parse()
484 .map_err(|_| QueryError::Parse("invalid variable-length hop count".into()))?;
485 Ok((n, Some(n)))
486 }
487}
488
489fn parse_prop_map(pair: Pair<Rule>) -> Result<Vec<(String, Literal)>, QueryError> {
490 pair.into_inner()
491 .filter(|p| p.as_rule() == Rule::prop_kv)
492 .map(|p| {
493 let mut inner = p.into_inner();
494 let key = inner.next().expect("prop_kv has a key").as_str().to_string();
495 let value = parse_literal(inner.next().expect("prop_kv has a value"))?;
496 Ok((key, value))
497 })
498 .collect()
499}
500
501fn parse_literal(pair: Pair<Rule>) -> Result<Literal, QueryError> {
502 let inner = pair.into_inner().next().expect("literal has one child");
503 Ok(match inner.as_rule() {
504 Rule::int_literal => Literal::Int(
505 inner
506 .as_str()
507 .parse()
508 .map_err(|_| QueryError::Parse("invalid integer literal".into()))?,
509 ),
510 Rule::float_literal => Literal::Float(
511 inner
512 .as_str()
513 .parse()
514 .map_err(|_| QueryError::Parse("invalid float literal".into()))?,
515 ),
516 Rule::string_literal => {
517 let s = inner.as_str();
518 Literal::String(s[1..s.len() - 1].to_string())
519 }
520 Rule::bool_literal => Literal::Bool(inner.as_str().eq_ignore_ascii_case("true")),
521 Rule::null_literal => Literal::Null,
522 Rule::param => {
523 let name = inner.into_inner().next().expect("param has an identifier").as_str().to_string();
524 Literal::Param(name)
525 }
526 r => unreachable!("unexpected literal child rule {r:?}"),
527 })
528}
529
530fn parse_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
531 parse_or_expr(pair.into_inner().next().expect("expr has an or_expr"))
533}
534
535fn parse_or_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
536 let mut parts = pair.into_inner();
537 let mut acc = parse_and_expr(parts.next().expect("or_expr has at least one and_expr"))?;
538 for rest in parts {
539 acc = Expr::Or(Box::new(acc), Box::new(parse_and_expr(rest)?));
540 }
541 Ok(acc)
542}
543
544fn parse_and_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
545 let mut parts = pair.into_inner();
546 let mut acc = parse_unary_expr(parts.next().expect("and_expr has at least one unary_expr"))?;
547 for rest in parts {
548 acc = Expr::And(Box::new(acc), Box::new(parse_unary_expr(rest)?));
549 }
550 Ok(acc)
551}
552
553fn parse_unary_expr(pair: Pair<Rule>) -> Result<Expr, QueryError> {
554 let inner = pair.into_inner().next().expect("unary_expr has one child");
555 match inner.as_rule() {
556 Rule::unary_expr => Ok(Expr::Not(Box::new(parse_unary_expr(inner)?))),
557 Rule::comparison => parse_comparison(inner),
558 Rule::expr => parse_expr(inner),
559 r => unreachable!("unexpected unary_expr child rule {r:?}"),
560 }
561}
562
563fn parse_comparison(pair: Pair<Rule>) -> Result<Expr, QueryError> {
564 let mut inner = pair.into_inner();
565 let prop_access = parse_prop_access(inner.next().expect("comparison has a prop_access"));
566 let op = parse_compare_op(inner.next().expect("comparison has a compare_op"));
567 let literal = parse_literal(inner.next().expect("comparison has a literal"))?;
568 Ok(Expr::Compare(prop_access, op, literal))
569}
570
571fn parse_compare_op(pair: Pair<Rule>) -> CompareOp {
572 match pair.as_str() {
573 "=" => CompareOp::Eq,
574 "<>" => CompareOp::Ne,
575 "<" => CompareOp::Lt,
576 "<=" => CompareOp::Le,
577 ">" => CompareOp::Gt,
578 ">=" => CompareOp::Ge,
579 other => unreachable!("unexpected compare_op {other:?}"),
580 }
581}