Skip to main content

omnigraph_compiler/query/
parser.rs

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;