1use pest::Parser;
2use pest::error::InputLocation;
3use pest_derive::Parser;
4
5use crate::error::{
6 NanoError, ParseDiagnostic, Result, SourceSpan, decode_string_literal, render_span,
7};
8
9use super::ast::*;
10
11#[derive(Parser)]
12#[grammar = "query/query.pest"]
13struct QueryParser;
14
15pub fn parse_query(input: &str) -> Result<QueryFile> {
16 parse_query_diagnostic(input).map_err(|e| NanoError::Parse(e.to_string()))
17}
18
19pub fn parse_query_diagnostic(input: &str) -> std::result::Result<QueryFile, ParseDiagnostic> {
20 let pairs = QueryParser::parse(Rule::query_file, input).map_err(pest_error_to_diagnostic)?;
21
22 let mut queries = Vec::new();
23 for pair in pairs {
24 if let Rule::query_file = pair.as_rule() {
25 for inner in pair.into_inner() {
26 if let Rule::query_decl = inner.as_rule() {
27 queries.push(parse_query_decl(inner).map_err(nano_error_to_diagnostic)?);
28 }
29 }
30 }
31 }
32 Ok(QueryFile { queries })
33}
34
35fn pest_error_to_diagnostic(err: pest::error::Error<Rule>) -> ParseDiagnostic {
36 let span = match err.location {
37 InputLocation::Pos(pos) => Some(render_span(SourceSpan::new(pos, pos))),
38 InputLocation::Span((start, end)) => Some(render_span(SourceSpan::new(start, end))),
39 };
40 ParseDiagnostic::new(err.to_string(), span)
41}
42
43fn nano_error_to_diagnostic(err: NanoError) -> ParseDiagnostic {
44 ParseDiagnostic::new(err.to_string(), None)
45}
46
47fn parse_query_decl(pair: pest::iterators::Pair<Rule>) -> Result<QueryDecl> {
48 let mut inner = pair.into_inner();
49 let name = inner.next().unwrap().as_str().to_string();
50
51 let mut description = None;
52 let mut instruction = None;
53 let mut params = Vec::new();
54 let mut match_clause = Vec::new();
55 let mut return_clause = Vec::new();
56 let mut order_clause = Vec::new();
57 let mut limit = None;
58 let mut mutations = Vec::new();
59
60 for item in inner {
61 match item.as_rule() {
62 Rule::param_list => {
63 for p in item.into_inner() {
64 if let Rule::param = p.as_rule() {
65 params.push(parse_param(p)?);
66 }
67 }
68 }
69 Rule::query_annotation => {
70 let (annotation_name, value) = parse_query_annotation(item)?;
71 match annotation_name {
72 "description" => {
73 if description.replace(value).is_some() {
74 return Err(NanoError::Parse(format!(
75 "query `{}` cannot include duplicate @description annotations",
76 name
77 )));
78 }
79 }
80 "instruction" => {
81 if instruction.replace(value).is_some() {
82 return Err(NanoError::Parse(format!(
83 "query `{}` cannot include duplicate @instruction annotations",
84 name
85 )));
86 }
87 }
88 other => {
89 return Err(NanoError::Parse(format!(
90 "unsupported query annotation: @{}",
91 other
92 )));
93 }
94 }
95 }
96 Rule::query_body => {
97 let body = item
98 .into_inner()
99 .next()
100 .ok_or_else(|| NanoError::Parse("query body cannot be empty".to_string()))?;
101 match body.as_rule() {
102 Rule::read_query_body => {
103 for section in body.into_inner() {
104 match section.as_rule() {
105 Rule::match_clause => {
106 for c in section.into_inner() {
107 if let Rule::clause = c.as_rule() {
108 match_clause.push(parse_clause(c)?);
109 }
110 }
111 }
112 Rule::return_clause => {
113 for proj in section.into_inner() {
114 if let Rule::projection = proj.as_rule() {
115 return_clause.push(parse_projection(proj)?);
116 }
117 }
118 }
119 Rule::order_clause => {
120 for ord in section.into_inner() {
121 if let Rule::ordering = ord.as_rule() {
122 order_clause.push(parse_ordering(ord)?);
123 }
124 }
125 }
126 Rule::limit_clause => {
127 let int_pair = section.into_inner().next().unwrap();
128 limit =
129 Some(int_pair.as_str().parse::<u64>().map_err(|e| {
130 NanoError::Parse(format!("invalid limit: {}", e))
131 })?);
132 }
133 _ => {}
134 }
135 }
136 }
137 Rule::mutation_body => {
138 for mutation_pair in body.into_inner() {
139 if let Rule::mutation_stmt = mutation_pair.as_rule() {
140 let stmt = mutation_pair.into_inner().next().ok_or_else(|| {
141 NanoError::Parse(
142 "mutation statement cannot be empty".to_string(),
143 )
144 })?;
145 mutations.push(parse_mutation_stmt(stmt)?);
146 }
147 }
148 }
149 _ => {}
150 }
151 }
152 _ => {}
153 }
154 }
155
156 Ok(QueryDecl {
157 name,
158 description,
159 instruction,
160 params,
161 match_clause,
162 return_clause,
163 order_clause,
164 limit,
165 mutations,
166 })
167}
168
169fn parse_query_annotation(pair: pest::iterators::Pair<Rule>) -> Result<(&'static str, String)> {
170 let inner = pair
171 .into_inner()
172 .next()
173 .ok_or_else(|| NanoError::Parse("query annotation cannot be empty".to_string()))?;
174 match inner.as_rule() {
175 Rule::description_annotation => {
176 let value = inner
177 .into_inner()
178 .next()
179 .ok_or_else(|| {
180 NanoError::Parse("@description requires a string literal".to_string())
181 })
182 .map(|value| parse_string_lit(value.as_str()))??;
183 Ok(("description", value))
184 }
185 Rule::instruction_annotation => {
186 let value = inner
187 .into_inner()
188 .next()
189 .ok_or_else(|| {
190 NanoError::Parse("@instruction requires a string literal".to_string())
191 })
192 .map(|value| parse_string_lit(value.as_str()))??;
193 Ok(("instruction", value))
194 }
195 other => Err(NanoError::Parse(format!(
196 "unexpected query annotation rule: {:?}",
197 other
198 ))),
199 }
200}
201
202fn parse_param(pair: pest::iterators::Pair<Rule>) -> Result<Param> {
203 let mut inner = pair.into_inner();
204 let var = inner.next().unwrap().as_str();
205 let name = var.strip_prefix('$').unwrap_or(var).to_string();
206 let type_ref = inner.next().unwrap();
207 let nullable = type_ref.as_str().trim_end().ends_with('?');
208 let mut type_inner = type_ref.into_inner();
209 let core = type_inner
210 .next()
211 .ok_or_else(|| NanoError::Parse("parameter type is missing".to_string()))?;
212 let base = match core.as_rule() {
213 Rule::base_type => core.as_str().to_string(),
214 Rule::list_type => {
215 let inner = core
216 .into_inner()
217 .next()
218 .ok_or_else(|| NanoError::Parse("list type missing item type".to_string()))?;
219 format!("[{}]", inner.as_str().trim())
220 }
221 Rule::vector_type => {
222 let vector = core
223 .into_inner()
224 .next()
225 .ok_or_else(|| NanoError::Parse("Vector type missing dimension".to_string()))?;
226 format!("Vector({})", vector.as_str().trim())
227 }
228 other => {
229 return Err(NanoError::Parse(format!(
230 "unexpected param type rule: {:?}",
231 other
232 )));
233 }
234 };
235
236 Ok(Param {
237 name,
238 type_name: base,
239 nullable,
240 })
241}
242
243fn parse_clause(pair: pest::iterators::Pair<Rule>) -> Result<Clause> {
244 let inner = pair.into_inner().next().unwrap();
245 match inner.as_rule() {
246 Rule::binding => Ok(Clause::Binding(parse_binding(inner)?)),
247 Rule::traversal => Ok(Clause::Traversal(parse_traversal(inner)?)),
248 Rule::filter => Ok(Clause::Filter(parse_filter(inner)?)),
249 Rule::text_search_clause => Ok(parse_text_search_clause(inner)?),
250 Rule::negation => {
251 let mut clauses = Vec::new();
252 for c in inner.into_inner() {
253 if let Rule::clause = c.as_rule() {
254 clauses.push(parse_clause(c)?);
255 }
256 }
257 Ok(Clause::Negation(clauses))
258 }
259 _ => Err(NanoError::Parse(format!(
260 "unexpected clause rule: {:?}",
261 inner.as_rule()
262 ))),
263 }
264}
265
266fn parse_text_search_clause(pair: pest::iterators::Pair<Rule>) -> Result<Clause> {
267 let inner = pair
268 .into_inner()
269 .next()
270 .ok_or_else(|| NanoError::Parse("text search clause cannot be empty".to_string()))?;
271 let expr = match inner.as_rule() {
272 Rule::search_call => parse_search_call(inner)?,
273 Rule::fuzzy_call => parse_fuzzy_call(inner)?,
274 Rule::match_text_call => parse_match_text_call(inner)?,
275 other => {
276 return Err(NanoError::Parse(format!(
277 "unexpected text search clause rule: {:?}",
278 other
279 )));
280 }
281 };
282
283 Ok(Clause::Filter(Filter {
284 left: expr,
285 op: CompOp::Eq,
286 right: Expr::Literal(Literal::Bool(true)),
287 }))
288}
289
290fn parse_binding(pair: pest::iterators::Pair<Rule>) -> Result<Binding> {
291 let mut inner = pair.into_inner();
292 let var = inner.next().unwrap().as_str();
293 let variable = var.strip_prefix('$').unwrap_or(var).to_string();
294 let type_name = inner.next().unwrap().as_str().to_string();
295
296 let mut prop_matches = Vec::new();
297 for item in inner {
298 if let Rule::prop_match_list = item.as_rule() {
299 for pm in item.into_inner() {
300 if let Rule::prop_match = pm.as_rule() {
301 prop_matches.push(parse_prop_match(pm)?);
302 }
303 }
304 }
305 }
306
307 Ok(Binding {
308 variable,
309 type_name,
310 prop_matches,
311 })
312}
313
314fn parse_prop_match(pair: pest::iterators::Pair<Rule>) -> Result<PropMatch> {
315 let mut inner = pair.into_inner();
316 let prop_name = inner.next().unwrap().as_str().to_string();
317 let value_pair = inner.next().unwrap();
318 let value = parse_match_value(value_pair)?;
319
320 Ok(PropMatch { prop_name, value })
321}
322
323fn parse_mutation_stmt(pair: pest::iterators::Pair<Rule>) -> Result<Mutation> {
324 match pair.as_rule() {
325 Rule::insert_stmt => parse_insert_mutation(pair).map(Mutation::Insert),
326 Rule::update_stmt => parse_update_mutation(pair).map(Mutation::Update),
327 Rule::delete_stmt => parse_delete_mutation(pair).map(Mutation::Delete),
328 other => Err(NanoError::Parse(format!(
329 "unexpected mutation statement rule: {:?}",
330 other
331 ))),
332 }
333}
334
335fn parse_insert_mutation(pair: pest::iterators::Pair<Rule>) -> Result<InsertMutation> {
336 let mut inner = pair.into_inner();
337 let type_name = inner.next().unwrap().as_str().to_string();
338 let mut assignments = Vec::new();
339 for item in inner {
340 if let Rule::mutation_assignment = item.as_rule() {
341 assignments.push(parse_mutation_assignment(item)?);
342 }
343 }
344 Ok(InsertMutation {
345 type_name,
346 assignments,
347 })
348}
349
350fn parse_update_mutation(pair: pest::iterators::Pair<Rule>) -> Result<UpdateMutation> {
351 let mut inner = pair.into_inner();
352 let type_name = inner.next().unwrap().as_str().to_string();
353
354 let mut assignments = Vec::new();
355 let mut predicate = None;
356
357 for item in inner {
358 match item.as_rule() {
359 Rule::mutation_assignment => assignments.push(parse_mutation_assignment(item)?),
360 Rule::mutation_predicate => predicate = Some(parse_mutation_predicate(item)?),
361 _ => {}
362 }
363 }
364
365 let predicate = predicate.ok_or_else(|| {
366 NanoError::Parse("update mutation requires a where predicate".to_string())
367 })?;
368
369 Ok(UpdateMutation {
370 type_name,
371 assignments,
372 predicate,
373 })
374}
375
376fn parse_delete_mutation(pair: pest::iterators::Pair<Rule>) -> Result<DeleteMutation> {
377 let mut inner = pair.into_inner();
378 let type_name = inner.next().unwrap().as_str().to_string();
379 let predicate = inner
380 .next()
381 .ok_or_else(|| NanoError::Parse("delete mutation requires a where predicate".to_string()))
382 .and_then(parse_mutation_predicate)?;
383 Ok(DeleteMutation {
384 type_name,
385 predicate,
386 })
387}
388
389fn parse_mutation_assignment(pair: pest::iterators::Pair<Rule>) -> Result<MutationAssignment> {
390 let mut inner = pair.into_inner();
391 let property = inner.next().unwrap().as_str().to_string();
392 let value = parse_match_value(inner.next().unwrap())?;
393 Ok(MutationAssignment { property, value })
394}
395
396fn parse_mutation_predicate(pair: pest::iterators::Pair<Rule>) -> Result<MutationPredicate> {
397 let mut inner = pair.into_inner();
398 let property = inner.next().unwrap().as_str().to_string();
399 let op = parse_comp_op(inner.next().unwrap())?;
400 let value = parse_match_value(inner.next().unwrap())?;
401 Ok(MutationPredicate {
402 property,
403 op,
404 value,
405 })
406}
407
408fn parse_match_value(pair: pest::iterators::Pair<Rule>) -> Result<MatchValue> {
409 let value_inner = pair.into_inner().next().unwrap();
410 match value_inner.as_rule() {
411 Rule::variable => {
412 let v = value_inner.as_str();
413 Ok(MatchValue::Variable(
414 v.strip_prefix('$').unwrap_or(v).to_string(),
415 ))
416 }
417 Rule::now_call => Ok(MatchValue::Now),
418 Rule::literal => Ok(MatchValue::Literal(parse_literal(value_inner)?)),
419 _ => Err(NanoError::Parse(format!(
420 "unexpected match value: {:?}",
421 value_inner.as_rule()
422 ))),
423 }
424}
425
426fn parse_traversal(pair: pest::iterators::Pair<Rule>) -> Result<Traversal> {
427 let mut inner = pair.into_inner();
428 let src_var = inner.next().unwrap().as_str();
429 let src = src_var.strip_prefix('$').unwrap_or(src_var).to_string();
430 let edge_name = inner.next().unwrap().as_str().to_string();
431 let mut min_hops = 1u32;
432 let mut max_hops = Some(1u32);
433
434 let next = inner.next().unwrap();
435 let dst_pair = if let Rule::traversal_bounds = next.as_rule() {
436 let (min, max) = parse_traversal_bounds(next)?;
437 min_hops = min;
438 max_hops = max;
439 inner
440 .next()
441 .ok_or_else(|| NanoError::Parse("traversal missing destination variable".to_string()))?
442 } else {
443 next
444 };
445
446 let dst_var = dst_pair.as_str();
447 let dst = dst_var.strip_prefix('$').unwrap_or(dst_var).to_string();
448
449 Ok(Traversal {
450 src,
451 edge_name,
452 dst,
453 min_hops,
454 max_hops,
455 })
456}
457
458fn parse_traversal_bounds(pair: pest::iterators::Pair<Rule>) -> Result<(u32, Option<u32>)> {
459 let mut inner = pair.into_inner();
460 let min = inner
461 .next()
462 .ok_or_else(|| NanoError::Parse("traversal bound missing min hop".to_string()))?
463 .as_str()
464 .parse::<u32>()
465 .map_err(|e| NanoError::Parse(format!("invalid traversal min bound: {}", e)))?;
466 let max = inner
467 .next()
468 .map(|p| {
469 p.as_str()
470 .parse::<u32>()
471 .map_err(|e| NanoError::Parse(format!("invalid traversal max bound: {}", e)))
472 })
473 .transpose()?;
474 Ok((min, max))
475}
476
477fn parse_filter(pair: pest::iterators::Pair<Rule>) -> Result<Filter> {
478 let mut inner = pair.into_inner();
479 let left = parse_expr(inner.next().unwrap())?;
480 let op = parse_filter_op(inner.next().unwrap())?;
481 let right = parse_expr(inner.next().unwrap())?;
482
483 Ok(Filter { left, op, right })
484}
485
486fn parse_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
487 let inner = pair.into_inner().next().unwrap();
488 match inner.as_rule() {
489 Rule::now_call => Ok(Expr::Now),
490 Rule::prop_access => {
491 let mut parts = inner.into_inner();
492 let var = parts.next().unwrap().as_str();
493 let variable = var.strip_prefix('$').unwrap_or(var).to_string();
494 let property = parts.next().unwrap().as_str().to_string();
495 Ok(Expr::PropAccess { variable, property })
496 }
497 Rule::variable => {
498 let v = inner.as_str();
499 Ok(Expr::Variable(v.strip_prefix('$').unwrap_or(v).to_string()))
500 }
501 Rule::literal => Ok(Expr::Literal(parse_literal(inner)?)),
502 Rule::agg_call => {
503 let mut parts = inner.into_inner();
504 let func = match parts.next().unwrap().as_str() {
505 "count" => AggFunc::Count,
506 "sum" => AggFunc::Sum,
507 "avg" => AggFunc::Avg,
508 "min" => AggFunc::Min,
509 "max" => AggFunc::Max,
510 other => return Err(NanoError::Parse(format!("unknown aggregate: {}", other))),
511 };
512 let arg = parse_expr(parts.next().unwrap())?;
513 Ok(Expr::Aggregate {
514 func,
515 arg: Box::new(arg),
516 })
517 }
518 Rule::search_call => parse_search_call(inner),
519 Rule::fuzzy_call => parse_fuzzy_call(inner),
520 Rule::match_text_call => parse_match_text_call(inner),
521 Rule::nearest_ordering => parse_nearest_ordering(inner),
522 Rule::bm25_call => parse_bm25_call(inner),
523 Rule::rrf_call => parse_rrf_call(inner),
524 Rule::ident => Ok(Expr::AliasRef(inner.as_str().to_string())),
525 _ => Err(NanoError::Parse(format!(
526 "unexpected expr rule: {:?}",
527 inner.as_rule()
528 ))),
529 }
530}
531
532fn parse_search_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
533 let mut args = pair.into_inner();
534 let field = args
535 .next()
536 .ok_or_else(|| NanoError::Parse("search() missing field argument".to_string()))?;
537 let query = args
538 .next()
539 .ok_or_else(|| NanoError::Parse("search() missing query argument".to_string()))?;
540 if args.next().is_some() {
541 return Err(NanoError::Parse(
542 "search() accepts exactly 2 arguments".to_string(),
543 ));
544 }
545 Ok(Expr::Search {
546 field: Box::new(parse_expr(field)?),
547 query: Box::new(parse_expr(query)?),
548 })
549}
550
551fn parse_fuzzy_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
552 let mut args = pair.into_inner();
553 let field = args
554 .next()
555 .ok_or_else(|| NanoError::Parse("fuzzy() missing field argument".to_string()))?;
556 let query = args
557 .next()
558 .ok_or_else(|| NanoError::Parse("fuzzy() missing query argument".to_string()))?;
559 let max_edits = args.next().map(parse_expr).transpose()?.map(Box::new);
560 if args.next().is_some() {
561 return Err(NanoError::Parse(
562 "fuzzy() accepts at most 3 arguments".to_string(),
563 ));
564 }
565 Ok(Expr::Fuzzy {
566 field: Box::new(parse_expr(field)?),
567 query: Box::new(parse_expr(query)?),
568 max_edits,
569 })
570}
571
572fn parse_match_text_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
573 let mut args = pair.into_inner();
574 let field = args
575 .next()
576 .ok_or_else(|| NanoError::Parse("match_text() missing field argument".to_string()))?;
577 let query = args
578 .next()
579 .ok_or_else(|| NanoError::Parse("match_text() missing query argument".to_string()))?;
580 if args.next().is_some() {
581 return Err(NanoError::Parse(
582 "match_text() accepts exactly 2 arguments".to_string(),
583 ));
584 }
585 Ok(Expr::MatchText {
586 field: Box::new(parse_expr(field)?),
587 query: Box::new(parse_expr(query)?),
588 })
589}
590
591fn parse_bm25_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
592 let mut args = pair.into_inner();
593 let field = args
594 .next()
595 .ok_or_else(|| NanoError::Parse("bm25() missing field argument".to_string()))?;
596 let query = args
597 .next()
598 .ok_or_else(|| NanoError::Parse("bm25() missing query argument".to_string()))?;
599 if args.next().is_some() {
600 return Err(NanoError::Parse(
601 "bm25() accepts exactly 2 arguments".to_string(),
602 ));
603 }
604 Ok(Expr::Bm25 {
605 field: Box::new(parse_expr(field)?),
606 query: Box::new(parse_expr(query)?),
607 })
608}
609
610fn parse_rank_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
611 let inner = if pair.as_rule() == Rule::rank_expr {
612 pair.into_inner()
613 .next()
614 .ok_or_else(|| NanoError::Parse("rank expression cannot be empty".to_string()))?
615 } else {
616 pair
617 };
618 match inner.as_rule() {
619 Rule::nearest_ordering => parse_nearest_ordering(inner),
620 Rule::bm25_call => parse_bm25_call(inner),
621 other => Err(NanoError::Parse(format!(
622 "rrf() rank expression must be nearest(...) or bm25(...), got {:?}",
623 other
624 ))),
625 }
626}
627
628fn parse_rrf_call(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
629 let mut args = pair.into_inner();
630 let primary = args
631 .next()
632 .ok_or_else(|| NanoError::Parse("rrf() missing primary rank expression".to_string()))?;
633 let secondary = args
634 .next()
635 .ok_or_else(|| NanoError::Parse("rrf() missing secondary rank expression".to_string()))?;
636 let k = args.next().map(parse_expr).transpose()?.map(Box::new);
637 if args.next().is_some() {
638 return Err(NanoError::Parse(
639 "rrf() accepts at most 3 arguments".to_string(),
640 ));
641 }
642 Ok(Expr::Rrf {
643 primary: Box::new(parse_rank_expr(primary)?),
644 secondary: Box::new(parse_rank_expr(secondary)?),
645 k,
646 })
647}
648
649fn parse_comp_op(pair: pest::iterators::Pair<Rule>) -> Result<CompOp> {
650 match pair.as_str() {
651 "=" => Ok(CompOp::Eq),
652 "!=" => Ok(CompOp::Ne),
653 ">" => Ok(CompOp::Gt),
654 "<" => Ok(CompOp::Lt),
655 ">=" => Ok(CompOp::Ge),
656 "<=" => Ok(CompOp::Le),
657 other => Err(NanoError::Parse(format!("unknown operator: {}", other))),
658 }
659}
660
661fn parse_filter_op(pair: pest::iterators::Pair<Rule>) -> Result<CompOp> {
662 match pair.as_str() {
663 "contains" => Ok(CompOp::Contains),
664 _ => parse_comp_op(pair),
665 }
666}
667
668fn parse_literal(pair: pest::iterators::Pair<Rule>) -> Result<Literal> {
669 let inner = pair.into_inner().next().unwrap();
670 match inner.as_rule() {
671 Rule::string_lit => Ok(Literal::String(parse_string_lit(inner.as_str())?)),
672 Rule::integer => {
673 let n: i64 = inner
674 .as_str()
675 .parse()
676 .map_err(|e| NanoError::Parse(format!("invalid integer: {}", e)))?;
677 Ok(Literal::Integer(n))
678 }
679 Rule::float_lit => {
680 let f: f64 = inner
681 .as_str()
682 .parse()
683 .map_err(|e| NanoError::Parse(format!("invalid float: {}", e)))?;
684 Ok(Literal::Float(f))
685 }
686 Rule::bool_lit => {
687 let b = match inner.as_str() {
688 "true" => true,
689 "false" => false,
690 other => {
691 return Err(NanoError::Parse(format!(
692 "invalid boolean literal: {}",
693 other
694 )));
695 }
696 };
697 Ok(Literal::Bool(b))
698 }
699 Rule::date_lit => {
700 let date_str = inner
701 .into_inner()
702 .next()
703 .map(|s| parse_string_lit(s.as_str()))
704 .ok_or_else(|| NanoError::Parse("date literal requires a string".to_string()))?;
705 Ok(Literal::Date(date_str?))
706 }
707 Rule::datetime_lit => {
708 let dt_str = inner
709 .into_inner()
710 .next()
711 .map(|s| parse_string_lit(s.as_str()))
712 .ok_or_else(|| {
713 NanoError::Parse("datetime literal requires a string".to_string())
714 })?;
715 Ok(Literal::DateTime(dt_str?))
716 }
717 Rule::list_lit => {
718 let mut items = Vec::new();
719 for item in inner.into_inner() {
720 if item.as_rule() == Rule::literal {
721 items.push(parse_literal(item)?);
722 }
723 }
724 Ok(Literal::List(items))
725 }
726 _ => Err(NanoError::Parse(format!(
727 "unexpected literal: {:?}",
728 inner.as_rule()
729 ))),
730 }
731}
732
733fn parse_string_lit(raw: &str) -> Result<String> {
734 decode_string_literal(raw)
735}
736
737fn parse_projection(pair: pest::iterators::Pair<Rule>) -> Result<Projection> {
738 let mut inner = pair.into_inner();
739 let expr = parse_expr(inner.next().unwrap())?;
740 let alias = inner.next().map(|p| p.as_str().to_string());
741
742 Ok(Projection { expr, alias })
743}
744
745fn parse_ordering(pair: pest::iterators::Pair<Rule>) -> Result<Ordering> {
746 let mut inner = pair.into_inner();
747 let first = inner
748 .next()
749 .ok_or_else(|| NanoError::Parse("ordering cannot be empty".to_string()))?;
750 let (expr, descending) = match first.as_rule() {
751 Rule::nearest_ordering => (parse_nearest_ordering(first)?, false),
752 Rule::expr => {
753 let expr = parse_expr(first)?;
754 let direction = inner.next().map(|p| p.as_str().to_string());
755 if matches!(expr, Expr::Nearest { .. }) && direction.is_some() {
756 return Err(NanoError::Parse(
757 "nearest() ordering does not accept asc/desc modifiers".to_string(),
758 ));
759 }
760 let descending = matches!(direction.as_deref(), Some("desc"));
761 (expr, descending)
762 }
763 other => {
764 return Err(NanoError::Parse(format!(
765 "unexpected ordering rule: {:?}",
766 other
767 )));
768 }
769 };
770
771 Ok(Ordering { expr, descending })
772}
773
774fn parse_nearest_ordering(pair: pest::iterators::Pair<Rule>) -> Result<Expr> {
775 let mut inner = pair.into_inner();
776 let prop = inner
777 .next()
778 .ok_or_else(|| NanoError::Parse("nearest() missing property".to_string()))?;
779 let mut prop_parts = prop.into_inner();
780 let var = prop_parts
781 .next()
782 .ok_or_else(|| NanoError::Parse("nearest() missing variable".to_string()))?
783 .as_str();
784 let variable = var.strip_prefix('$').unwrap_or(var).to_string();
785 let property = prop_parts
786 .next()
787 .ok_or_else(|| NanoError::Parse("nearest() missing property name".to_string()))?
788 .as_str()
789 .to_string();
790
791 let query = inner
792 .next()
793 .ok_or_else(|| NanoError::Parse("nearest() missing query expression".to_string()))?;
794 Ok(Expr::Nearest {
795 variable,
796 property,
797 query: Box::new(parse_expr(query)?),
798 })
799}
800
801#[cfg(test)]
802#[path = "parser_tests.rs"]
803mod tests;