1use chumsky::prelude::*;
6use rust_decimal::Decimal;
7use std::str::FromStr;
8
9use crate::ast::{
10 BalancesQuery, BinaryOperator, ColumnDef, CreateTableStmt, Expr, FromClause, FunctionCall,
11 InsertSource, InsertStmt, JournalQuery, Literal, OrderSpec, PrintQuery, Query, SelectQuery,
12 SortDirection, Target, UnaryOperator, WindowFunction, WindowSpec,
13};
14use crate::error::{ParseError, ParseErrorKind};
15use rustledger_core::NaiveDate;
16
17type ParserInput<'a> = &'a str;
18type ParserExtra<'a> = extra::Err<Rich<'a, char>>;
19
20enum ComparisonSuffix {
22 Between(Expr, Expr),
23 Binary(BinaryOperator, Expr),
24 In(Expr),
26 NotIn(Expr),
28}
29
30const MAX_NESTING_DEPTH: usize = 128;
40
41fn nesting_exceeds_limit(source: &str) -> Option<usize> {
48 let mut depth: usize = 0;
49 let mut chars = source.char_indices();
50 while let Some((i, c)) = chars.next() {
51 match c {
52 '"' | '\'' => {
53 while let Some((_, sc)) = chars.next() {
55 if sc == '\\' {
56 chars.next(); } else if sc == c {
58 break;
59 }
60 }
61 }
62 '(' => {
63 depth += 1;
64 if depth > MAX_NESTING_DEPTH {
65 return Some(i);
66 }
67 }
68 ')' => depth = depth.saturating_sub(1),
69 _ => {}
70 }
71 }
72 None
73}
74
75pub fn parse(source: &str) -> Result<Query, ParseError> {
83 if let Some(offset) = nesting_exceeds_limit(source) {
84 return Err(ParseError::new(
85 ParseErrorKind::SyntaxError(format!(
86 "expression nesting too deep (exceeds maximum of {MAX_NESTING_DEPTH})"
87 )),
88 offset,
89 ));
90 }
91
92 let (result, errs) = query_parser()
93 .then_ignore(ws())
94 .then_ignore(end())
95 .parse(source)
96 .into_output_errors();
97
98 if let Some(query) = result {
99 Ok(query)
100 } else {
101 let err = errs.first().map(|e| {
102 let start = e.span().start;
103 let kind = if e.found().is_some() {
104 ParseErrorKind::SyntaxError(e.to_string())
107 } else if start >= source.len() {
108 ParseErrorKind::UnexpectedEof
110 } else if let Some(rest) = source.get(start..) {
111 let token = rest.split_whitespace().next().unwrap_or(rest);
116 ParseErrorKind::SyntaxError(format!("unexpected token '{token}'"))
117 } else {
118 ParseErrorKind::SyntaxError(e.to_string())
119 };
120 ParseError::new(kind, start)
121 });
122 Err(err.unwrap_or_else(|| ParseError::new(ParseErrorKind::UnexpectedEof, 0)))
123 }
124}
125
126fn ws<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
128 one_of(" \t\r\n").repeated().ignored()
129}
130
131fn ws1<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
133 one_of(" \t\r\n").repeated().at_least(1).ignored()
134}
135
136fn kw<'a>(keyword: &'static str) -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
138 text::ident().try_map(move |s: &str, span| {
139 if s.eq_ignore_ascii_case(keyword) {
140 Ok(())
141 } else {
142 Err(Rich::custom(span, format!("expected keyword '{keyword}'")))
143 }
144 })
145}
146
147fn digits<'a>() -> impl Parser<'a, ParserInput<'a>, &'a str, ParserExtra<'a>> + Clone {
149 one_of("0123456789").repeated().at_least(1).to_slice()
150}
151
152fn query_parser<'a>() -> impl Parser<'a, ParserInput<'a>, Query, ParserExtra<'a>> {
154 ws().ignore_then(choice((
155 create_table_stmt().map(Query::CreateTable),
156 insert_stmt().map(Query::Insert),
157 select_query().map(|sq| Query::Select(Box::new(sq))),
158 journal_query().map(Query::Journal),
159 balances_query().map(Query::Balances),
160 print_query().map(Query::Print),
161 )))
162 .then_ignore(ws())
163 .then_ignore(just(';').or_not())
164}
165
166fn select_query<'a>() -> impl Parser<'a, ParserInput<'a>, SelectQuery, ParserExtra<'a>> {
168 recursive(|select_parser| {
169 let subquery_from = ws1()
171 .ignore_then(kw("FROM"))
172 .ignore_then(ws1())
173 .ignore_then(just('('))
174 .ignore_then(ws())
175 .ignore_then(select_parser)
176 .then_ignore(ws())
177 .then_ignore(just(')'))
178 .map(|sq| Some(FromClause::from_subquery(sq)));
179
180 let table_from = ws1()
184 .ignore_then(kw("FROM"))
185 .ignore_then(ws1())
186 .ignore_then(table_identifier().try_map(|name, span| {
187 if !name.starts_with('#') && name.contains(':') {
191 Err(Rich::custom(
192 span,
193 "table names cannot contain ':' - this looks like an account filter expression",
194 ))
195 } else {
196 Ok(name)
197 }
198 }))
199 .then_ignore(
200 ws().then(choice((
202 kw("WHERE").ignored(),
203 kw("GROUP").ignored(),
204 kw("ORDER").ignored(),
205 kw("HAVING").ignored(),
206 kw("LIMIT").ignored(),
207 kw("PIVOT").ignored(),
208 end().ignored(),
209 )))
210 .rewind(),
211 )
212 .map(|name| Some(FromClause::from_table(name)));
213
214 let regular_from = from_clause().map(Some);
216
217 kw("SELECT")
218 .ignore_then(ws1())
219 .ignore_then(
220 kw("DISTINCT")
221 .then_ignore(ws())
222 .or_not()
223 .map(|d| d.is_some()),
224 )
225 .then(targets())
226 .then(
227 subquery_from
228 .or(table_from)
229 .or(regular_from)
230 .or_not()
231 .map(std::option::Option::flatten),
232 )
233 .then(where_clause().or_not())
234 .then(group_by_clause().or_not())
235 .then(having_clause().or_not())
236 .then(order_by_clause().or_not())
241 .then(pivot_by_clause().or_not())
242 .then(limit_clause().or_not())
243 .map(
244 |(
245 (
246 (
247 (((((distinct, targets), from), where_clause), group_by), having),
248 order_by,
249 ),
250 pivot_by,
251 ),
252 limit,
253 )| {
254 SelectQuery {
255 distinct,
256 targets,
257 from,
258 where_clause,
259 group_by,
260 having,
261 pivot_by,
262 order_by,
263 limit,
264 }
265 },
266 )
267 })
268}
269
270fn from_clause<'a>() -> impl Parser<'a, ParserInput<'a>, FromClause, ParserExtra<'a>> + Clone {
272 ws1()
273 .ignore_then(kw("FROM"))
274 .ignore_then(ws1())
275 .ignore_then(from_modifiers())
276}
277
278fn targets<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Target>, ParserExtra<'a>> + Clone {
280 target()
281 .separated_by(ws().then(just(',')).then(ws()))
282 .at_least(1)
283 .collect()
284}
285
286fn target<'a>() -> impl Parser<'a, ParserInput<'a>, Target, ParserExtra<'a>> + Clone {
288 expr()
289 .then(
290 ws1()
291 .ignore_then(kw("AS"))
292 .ignore_then(ws1())
293 .ignore_then(identifier())
294 .or_not(),
295 )
296 .map(|(expr, alias)| Target { expr, alias })
297}
298
299fn from_modifiers<'a>() -> impl Parser<'a, ParserInput<'a>, FromClause, ParserExtra<'a>> + Clone {
301 let open_on = kw("OPEN")
310 .ignore_then(ws1())
311 .ignore_then(kw("ON"))
312 .ignore_then(ws1())
313 .ignore_then(date_literal());
314
315 let close_on = ws()
316 .ignore_then(kw("CLOSE"))
317 .ignore_then(ws().then(kw("ON")).then(ws()).or_not())
318 .ignore_then(date_literal());
319
320 let clear = ws().ignore_then(kw("CLEAR"));
321
322 open_on
325 .or_not()
326 .then(close_on.or_not())
327 .then(clear.or_not().map(|c| c.is_some()))
328 .then(from_filter().or_not())
329 .map(|(((open_on, close_on), clear), filter)| FromClause {
330 open_on,
331 close_on,
332 clear,
333 filter,
334 subquery: None,
335 table_name: None,
336 })
337}
338
339fn from_filter<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
349 let clause_keyword = choice((
350 kw("WHERE").ignored(),
351 kw("GROUP").ignored(),
352 kw("ORDER").ignored(),
353 kw("HAVING").ignored(),
354 kw("LIMIT").ignored(),
355 kw("PIVOT").ignored(),
356 ));
357 ws().ignore_then(clause_keyword.not().rewind())
358 .ignore_then(expr())
359}
360
361fn where_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
363 ws1()
364 .ignore_then(kw("WHERE"))
365 .ignore_then(ws1())
366 .ignore_then(expr())
367}
368
369fn group_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
371 ws1()
372 .ignore_then(kw("GROUP"))
373 .ignore_then(ws1())
374 .ignore_then(kw("BY"))
375 .ignore_then(ws1())
376 .ignore_then(
377 expr()
378 .separated_by(ws().then(just(',')).then(ws()))
379 .at_least(1)
380 .collect(),
381 )
382}
383
384fn having_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
386 ws1()
387 .ignore_then(kw("HAVING"))
388 .ignore_then(ws1())
389 .ignore_then(expr())
390}
391
392fn pivot_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
394 ws1()
395 .ignore_then(kw("PIVOT"))
396 .ignore_then(ws1())
397 .ignore_then(kw("BY"))
398 .ignore_then(ws1())
399 .ignore_then(
400 expr()
401 .separated_by(ws().then(just(',')).then(ws()))
402 .at_least(1)
403 .collect(),
404 )
405}
406
407fn order_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<OrderSpec>, ParserExtra<'a>> + Clone
409{
410 ws1()
411 .ignore_then(kw("ORDER"))
412 .ignore_then(ws1())
413 .ignore_then(kw("BY"))
414 .ignore_then(ws1())
415 .ignore_then(
416 order_spec()
417 .separated_by(ws().then(just(',')).then(ws()))
418 .at_least(1)
419 .collect(),
420 )
421}
422
423fn order_spec<'a>() -> impl Parser<'a, ParserInput<'a>, OrderSpec, ParserExtra<'a>> + Clone {
425 expr()
426 .then(
427 ws1()
428 .ignore_then(choice((
429 kw("ASC").to(SortDirection::Asc),
430 kw("DESC").to(SortDirection::Desc),
431 )))
432 .or_not(),
433 )
434 .map(|(expr, dir)| OrderSpec {
435 expr,
436 direction: dir.unwrap_or_default(),
437 })
438}
439
440fn limit_clause<'a>() -> impl Parser<'a, ParserInput<'a>, u64, ParserExtra<'a>> + Clone {
442 ws1()
443 .ignore_then(kw("LIMIT"))
444 .ignore_then(ws1())
445 .ignore_then(integer())
446 .map(|n| n as u64)
447}
448
449fn journal_query<'a>() -> impl Parser<'a, ParserInput<'a>, JournalQuery, ParserExtra<'a>> + Clone {
451 kw("JOURNAL")
452 .ignore_then(
453 ws1().ignore_then(string_literal()).or_not(),
455 )
456 .then(at_function().or_not())
457 .then(
458 ws1()
459 .ignore_then(kw("FROM"))
460 .ignore_then(ws1())
461 .ignore_then(from_modifiers())
462 .or_not(),
463 )
464 .map(|((account_pattern, at_function), from)| JournalQuery {
465 account_pattern: account_pattern.unwrap_or_default(),
466 at_function,
467 from,
468 })
469}
470
471fn balances_query<'a>() -> impl Parser<'a, ParserInput<'a>, BalancesQuery, ParserExtra<'a>> + Clone
473{
474 let at_fn = ws1().then(kw("AT")).rewind().ignore_then(at_function());
478
479 let from = ws1().then(kw("FROM")).rewind().ignore_then(
480 ws1()
481 .ignore_then(kw("FROM"))
482 .ignore_then(ws1())
483 .ignore_then(from_modifiers()),
484 );
485
486 kw("BALANCES")
487 .ignore_then(at_fn.or_not())
488 .then(from.or_not())
489 .then(where_clause().or_not())
490 .map(|((at_function, from), where_clause)| BalancesQuery {
491 at_function,
492 from,
493 where_clause,
494 })
495}
496
497fn print_query<'a>() -> impl Parser<'a, ParserInput<'a>, PrintQuery, ParserExtra<'a>> + Clone {
499 kw("PRINT")
500 .ignore_then(
501 ws1()
502 .ignore_then(kw("FROM"))
503 .ignore_then(ws1())
504 .ignore_then(from_modifiers())
505 .or_not(),
506 )
507 .map(|from| PrintQuery { from })
508}
509
510fn create_table_stmt<'a>() -> impl Parser<'a, ParserInput<'a>, CreateTableStmt, ParserExtra<'a>> {
512 let column_def = identifier()
514 .then(ws().ignore_then(identifier()).or_not())
515 .map(|(name, type_hint)| ColumnDef { name, type_hint });
516
517 let column_list = just('(')
518 .ignore_then(ws())
519 .ignore_then(
520 column_def
521 .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
522 .collect::<Vec<_>>(),
523 )
524 .then_ignore(ws())
525 .then_ignore(just(')'));
526
527 let as_select = ws1()
528 .ignore_then(kw("AS"))
529 .ignore_then(ws1())
530 .ignore_then(select_query())
531 .map(Box::new);
532
533 kw("CREATE")
534 .ignore_then(ws1())
535 .ignore_then(kw("TABLE"))
536 .ignore_then(ws1())
537 .ignore_then(identifier())
538 .then(ws().ignore_then(column_list).or_not())
539 .then(as_select.or_not())
540 .map(|((table_name, columns), as_select)| CreateTableStmt {
541 table_name,
542 columns: columns.unwrap_or_default(),
543 as_select,
544 })
545}
546
547fn insert_stmt<'a>() -> impl Parser<'a, ParserInput<'a>, InsertStmt, ParserExtra<'a>> {
549 let column_list = just('(')
551 .ignore_then(ws())
552 .ignore_then(
553 identifier()
554 .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
555 .collect::<Vec<_>>(),
556 )
557 .then_ignore(ws())
558 .then_ignore(just(')'));
559
560 let value_row = just('(')
562 .ignore_then(ws())
563 .ignore_then(
564 expr()
565 .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
566 .collect::<Vec<_>>(),
567 )
568 .then_ignore(ws())
569 .then_ignore(just(')'));
570
571 let values_source = kw("VALUES")
572 .ignore_then(ws())
573 .ignore_then(
574 value_row
575 .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
576 .collect::<Vec<_>>(),
577 )
578 .map(InsertSource::Values);
579
580 let select_source = select_query().map(|sq| InsertSource::Select(Box::new(sq)));
582
583 let source = choice((values_source, select_source));
584
585 kw("INSERT")
586 .ignore_then(ws1())
587 .ignore_then(kw("INTO"))
588 .ignore_then(ws1())
589 .ignore_then(identifier())
590 .then(ws().ignore_then(column_list).or_not())
591 .then_ignore(ws())
592 .then(source)
593 .map(|((table_name, columns), source)| InsertStmt {
594 table_name,
595 columns,
596 source,
597 })
598}
599
600fn at_function<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
602 ws1()
603 .ignore_then(kw("AT"))
604 .ignore_then(ws1())
605 .ignore_then(identifier())
606}
607
608#[allow(clippy::large_stack_frames)]
610fn expr<'a>() -> Boxed<'a, 'a, ParserInput<'a>, Expr, ParserExtra<'a>> {
611 recursive(|expr| {
612 let primary = primary_expr(expr.clone()).boxed();
613
614 let unary = just('-')
616 .then_ignore(ws())
617 .or_not()
618 .then(primary)
619 .map(|(neg, e)| {
620 if neg.is_some() {
621 Expr::unary(UnaryOperator::Neg, e)
622 } else {
623 e
624 }
625 })
626 .boxed();
627
628 let multiplicative = unary
630 .clone()
631 .foldl(
632 ws().ignore_then(choice((
633 just('*').to(BinaryOperator::Mul),
634 just('/').to(BinaryOperator::Div),
635 just('%').to(BinaryOperator::Mod),
636 )))
637 .then_ignore(ws())
638 .then(unary)
639 .repeated(),
640 |left, (op, right)| Expr::binary(left, op, right),
641 )
642 .boxed();
643
644 let additive = multiplicative
646 .clone()
647 .foldl(
648 ws().ignore_then(choice((
649 just('+').to(BinaryOperator::Add),
650 just('-').to(BinaryOperator::Sub),
651 )))
652 .then_ignore(ws())
653 .then(multiplicative)
654 .repeated(),
655 |left, (op, right)| Expr::binary(left, op, right),
656 )
657 .boxed();
658
659 let comparison = additive
661 .clone()
662 .then(
663 choice((
664 ws1()
666 .ignore_then(kw("BETWEEN"))
667 .ignore_then(ws1())
668 .ignore_then(additive.clone())
669 .then_ignore(ws1())
670 .then_ignore(kw("AND"))
671 .then_ignore(ws1())
672 .then(additive.clone())
673 .map(|(low, high)| ComparisonSuffix::Between(low, high)),
674 ws1()
676 .ignore_then(kw("NOT"))
677 .ignore_then(ws1())
678 .ignore_then(kw("IN"))
679 .ignore_then(ws())
680 .ignore_then(choice((
681 set_literal(expr.clone()),
682 additive.clone(),
683 )))
684 .map(ComparisonSuffix::NotIn),
685 ws1()
687 .ignore_then(kw("IN"))
688 .ignore_then(ws())
689 .ignore_then(choice((
690 set_literal(expr.clone()),
691 additive.clone(),
692 )))
693 .map(ComparisonSuffix::In),
694 ws()
696 .ignore_then(comparison_op())
697 .then_ignore(ws())
698 .then(additive)
699 .map(|(op, right)| ComparisonSuffix::Binary(op, right)),
700 ))
701 .or_not(),
702 )
703 .map(|(left, suffix)| match suffix {
704 Some(ComparisonSuffix::Between(low, high)) => Expr::between(left, low, high),
705 Some(ComparisonSuffix::Binary(op, right)) => Expr::binary(left, op, right),
706 Some(ComparisonSuffix::In(right)) => Expr::binary(left, BinaryOperator::In, right),
707 Some(ComparisonSuffix::NotIn(right)) => {
708 Expr::binary(left, BinaryOperator::NotIn, right)
709 }
710 None => left,
711 })
712 .then(
714 ws1()
715 .ignore_then(kw("IS"))
716 .ignore_then(ws1())
717 .ignore_then(choice((
718 kw("NOT")
719 .ignore_then(ws1())
720 .ignore_then(kw("NULL"))
721 .to(UnaryOperator::IsNotNull),
722 kw("NULL").to(UnaryOperator::IsNull),
723 )))
724 .or_not(),
725 )
726 .map(|(expr, is_null)| {
727 if let Some(op) = is_null {
728 Expr::unary(op, expr)
729 } else {
730 expr
731 }
732 })
733 .boxed();
734
735 let not_expr = kw("NOT")
737 .ignore_then(ws1())
738 .repeated()
739 .collect::<Vec<_>>()
740 .then(comparison)
741 .map(|(nots, e)| {
742 nots.into_iter()
743 .fold(e, |acc, ()| Expr::unary(UnaryOperator::Not, acc))
744 })
745 .boxed();
746
747 let and_expr = not_expr
749 .clone()
750 .foldl(
751 ws1()
752 .ignore_then(kw("AND"))
753 .ignore_then(ws1())
754 .ignore_then(not_expr)
755 .repeated(),
756 |left, right| Expr::binary(left, BinaryOperator::And, right),
757 )
758 .boxed();
759
760 and_expr.clone().foldl(
762 ws1()
763 .ignore_then(kw("OR"))
764 .ignore_then(ws1())
765 .ignore_then(and_expr)
766 .repeated(),
767 |left, right| Expr::binary(left, BinaryOperator::Or, right),
768 )
769 })
770 .boxed()
771}
772
773fn comparison_op<'a>() -> impl Parser<'a, ParserInput<'a>, BinaryOperator, ParserExtra<'a>> + Clone
775{
776 choice((
777 just("!=").to(BinaryOperator::Ne),
779 just("!~").to(BinaryOperator::NotRegex),
780 just("<=").to(BinaryOperator::Le),
781 just(">=").to(BinaryOperator::Ge),
782 just('=').to(BinaryOperator::Eq),
784 just('<').to(BinaryOperator::Lt),
785 just('>').to(BinaryOperator::Gt),
786 just('~').to(BinaryOperator::Regex),
787 ))
788}
789
790fn set_literal<'a>(
800 expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
801) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
802 just('(')
803 .ignore_then(ws())
804 .ignore_then(
805 expr.clone()
807 .then(
808 ws().ignore_then(just(',')).ignore_then(ws()).ignore_then(
812 expr.separated_by(ws().then(just(',')).then(ws()))
813 .allow_trailing()
814 .collect::<Vec<_>>(),
815 ),
816 )
817 .map(|(first, rest)| {
818 let mut elements = Vec::with_capacity(1 + rest.len());
819 elements.push(first);
820 elements.extend(rest);
821 elements
822 }),
823 )
824 .then_ignore(ws())
825 .then_ignore(just(')'))
826 .map(Expr::Set)
827}
828
829fn primary_expr<'a>(
831 expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
832) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
833 choice((
834 just('(')
836 .ignore_then(ws())
837 .ignore_then(expr.clone())
838 .then_ignore(ws())
839 .then_ignore(just(')'))
840 .map(|e| Expr::Paren(Box::new(e))),
841 literal().map(Expr::Literal),
848 function_call_or_column(expr),
851 just('*').to(Expr::Wildcard),
853 ))
854}
855
856fn function_call_or_column<'a>(
858 expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
859) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
860 identifier()
861 .then(
862 ws().ignore_then(just('('))
863 .ignore_then(ws())
864 .ignore_then(function_args(expr))
865 .then_ignore(ws())
866 .then_ignore(just(')'))
867 .or_not(),
868 )
869 .then(
870 ws1()
872 .ignore_then(kw("OVER"))
873 .ignore_then(ws())
874 .ignore_then(just('('))
875 .ignore_then(ws())
876 .ignore_then(window_spec())
877 .then_ignore(ws())
878 .then_ignore(just(')'))
879 .or_not(),
880 )
881 .map(|((name, args), over)| {
882 if let Some(args) = args {
883 if let Some(window_spec) = over {
884 Expr::Window(WindowFunction {
886 name,
887 args,
888 over: window_spec,
889 })
890 } else {
891 Expr::Function(FunctionCall { name, args })
893 }
894 } else {
895 Expr::Column(name)
896 }
897 })
898}
899
900fn window_spec<'a>() -> impl Parser<'a, ParserInput<'a>, WindowSpec, ParserExtra<'a>> + Clone {
902 let partition_by = kw("PARTITION")
903 .ignore_then(ws1())
904 .ignore_then(kw("BY"))
905 .ignore_then(ws1())
906 .ignore_then(
907 simple_arg()
908 .separated_by(ws().then(just(',')).then(ws()))
909 .at_least(1)
910 .collect::<Vec<_>>(),
911 )
912 .then_ignore(ws());
913
914 let window_order_by = kw("ORDER")
915 .ignore_then(ws1())
916 .ignore_then(kw("BY"))
917 .ignore_then(ws1())
918 .ignore_then(
919 window_order_spec()
920 .separated_by(ws().then(just(',')).then(ws()))
921 .at_least(1)
922 .collect::<Vec<_>>(),
923 );
924
925 partition_by
926 .or_not()
927 .then(window_order_by.or_not())
928 .map(|(partition_by, order_by)| WindowSpec {
929 partition_by,
930 order_by,
931 })
932}
933
934fn window_order_spec<'a>() -> impl Parser<'a, ParserInput<'a>, OrderSpec, ParserExtra<'a>> + Clone {
936 simple_arg()
937 .then(
938 ws1()
939 .ignore_then(choice((
940 kw("ASC").to(SortDirection::Asc),
941 kw("DESC").to(SortDirection::Desc),
942 )))
943 .or_not(),
944 )
945 .map(|(expr, dir)| OrderSpec {
946 expr,
947 direction: dir.unwrap_or_default(),
948 })
949}
950
951fn function_args<'a>(
953 expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
954) -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
955 expr.separated_by(ws().then(just(',')).then(ws())).collect()
958}
959
960fn simple_arg<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
962 choice((
963 just('*').to(Expr::Wildcard),
964 identifier().map(Expr::Column),
965 literal().map(Expr::Literal),
966 ))
967}
968
969fn literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserExtra<'a>> + Clone {
971 choice((
972 kw("TRUE").to(Literal::Boolean(true)),
974 kw("FALSE").to(Literal::Boolean(false)),
975 kw("NULL").to(Literal::Null),
976 date_literal().map(Literal::Date),
978 number_literal(),
980 string_literal().map(Literal::String),
982 ))
983}
984
985fn identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
987 text::ident().map(|s: &str| s.to_string())
988}
989
990fn table_identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
993 choice((
994 just('#')
996 .ignore_then(text::ident())
997 .map(|s: &str| format!("#{s}")),
998 text::ident().map(|s: &str| s.to_string()),
1000 ))
1001}
1002
1003fn string_literal<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
1005 let double_quoted = just('"')
1007 .ignore_then(
1008 none_of("\"\\")
1009 .or(just('\\').ignore_then(any()))
1010 .repeated()
1011 .collect::<String>(),
1012 )
1013 .then_ignore(just('"'));
1014
1015 let single_quoted = just('\'')
1017 .ignore_then(
1018 none_of("'\\")
1019 .or(just('\\').ignore_then(any()))
1020 .repeated()
1021 .collect::<String>(),
1022 )
1023 .then_ignore(just('\''));
1024
1025 choice((double_quoted, single_quoted))
1026}
1027
1028fn date_literal<'a>() -> impl Parser<'a, ParserInput<'a>, NaiveDate, ParserExtra<'a>> + Clone {
1030 digits()
1031 .then_ignore(just('-'))
1032 .then(digits())
1033 .then_ignore(just('-'))
1034 .then(digits())
1035 .try_map(|((year, month), day): ((&str, &str), &str), span| {
1036 let year: i32 = year
1037 .parse()
1038 .map_err(|_| Rich::custom(span, "invalid year"))?;
1039 let month: u32 = month
1040 .parse()
1041 .map_err(|_| Rich::custom(span, "invalid month"))?;
1042 let day: u32 = day.parse().map_err(|_| Rich::custom(span, "invalid day"))?;
1043 rustledger_core::naive_date(year, month, day)
1044 .ok_or_else(|| Rich::custom(span, "invalid date"))
1045 })
1046}
1047
1048fn number_literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserExtra<'a>> + Clone {
1056 just('-')
1057 .or_not()
1058 .then(digits())
1059 .then(just('.').then(digits()).or_not())
1060 .try_map(
1061 |((neg, int_part), frac_part): ((Option<char>, &str), Option<(char, &str)>), span| {
1062 let mut s = String::new();
1063 if neg.is_some() {
1064 s.push('-');
1065 }
1066 s.push_str(int_part);
1067 match frac_part {
1068 None => match s.parse::<i64>() {
1069 Ok(i) => Ok(Literal::Integer(i)),
1070 Err(_) => Decimal::from_str(&s)
1072 .map(Literal::Number)
1073 .map_err(|_| Rich::custom(span, "invalid number")),
1074 },
1075 Some((_, frac)) => {
1076 s.push('.');
1077 s.push_str(frac);
1078 Decimal::from_str(&s)
1079 .map(Literal::Number)
1080 .map_err(|_| Rich::custom(span, "invalid number"))
1081 }
1082 }
1083 },
1084 )
1085}
1086
1087fn integer<'a>() -> impl Parser<'a, ParserInput<'a>, i64, ParserExtra<'a>> + Clone {
1089 digits().try_map(|s: &str, span| {
1090 s.parse::<i64>()
1091 .map_err(|_| Rich::custom(span, "invalid integer"))
1092 })
1093}
1094
1095#[cfg(test)]
1096mod tests {
1097 use super::*;
1098 use rust_decimal_macros::dec;
1099
1100 #[test]
1101 fn test_trailing_tokens_are_named_not_mislabeled_eof() {
1102 for (q, token, pos) in [
1107 ("SELECT account FOOBAR", "FOOBAR", 15usize),
1108 (
1109 "SELECT account, sum(position) GROUP BY account WHERE number > 0",
1110 "WHERE",
1111 47,
1112 ),
1113 ] {
1114 let err = parse(q).expect_err("should be a parse error");
1115 assert_eq!(err.position, pos, "span should point at the token in {q:?}");
1116 let ParseErrorKind::SyntaxError(ref m) = err.kind else {
1117 panic!(
1118 "expected SyntaxError naming {token:?}, got {:?} for {q:?}",
1119 err.kind
1120 );
1121 };
1122 assert!(
1123 m.contains(token),
1124 "error should name {token:?}, got {m:?} for {q:?}"
1125 );
1126 }
1127 }
1128
1129 #[test]
1130 fn test_simple_select() {
1131 let query = parse("SELECT * FROM year = 2024").unwrap();
1132 match query {
1133 Query::Select(sel) => {
1134 assert!(!sel.distinct);
1135 assert_eq!(sel.targets.len(), 1);
1136 assert!(matches!(sel.targets[0].expr, Expr::Wildcard));
1137 assert!(sel.from.is_some());
1138 }
1139 _ => panic!("Expected SELECT query"),
1140 }
1141 }
1142
1143 #[test]
1144 fn test_select_columns() {
1145 let query = parse("SELECT date, account, position").unwrap();
1146 match query {
1147 Query::Select(sel) => {
1148 assert_eq!(sel.targets.len(), 3);
1149 assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
1150 assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "account"));
1151 assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "position"));
1152 }
1153 _ => panic!("Expected SELECT query"),
1154 }
1155 }
1156
1157 #[test]
1158 fn test_select_with_alias() {
1159 let query = parse("SELECT SUM(position) AS total").unwrap();
1160 match query {
1161 Query::Select(sel) => {
1162 assert_eq!(sel.targets.len(), 1);
1163 assert_eq!(sel.targets[0].alias, Some("total".to_string()));
1164 match &sel.targets[0].expr {
1165 Expr::Function(f) => {
1166 assert_eq!(f.name, "SUM");
1167 assert_eq!(f.args.len(), 1);
1168 }
1169 _ => panic!("Expected function"),
1170 }
1171 }
1172 _ => panic!("Expected SELECT query"),
1173 }
1174 }
1175
1176 #[test]
1177 fn test_select_distinct() {
1178 let query = parse("SELECT DISTINCT account").unwrap();
1179 match query {
1180 Query::Select(sel) => {
1181 assert!(sel.distinct);
1182 }
1183 _ => panic!("Expected SELECT query"),
1184 }
1185 }
1186
1187 #[test]
1188 fn test_select_distinct_no_space() {
1189 let query = parse("SELECT DISTINCT(account) FROM postings").unwrap();
1191 match query {
1192 Query::Select(sel) => {
1193 assert!(sel.distinct);
1194 }
1195 _ => panic!("Expected SELECT query"),
1196 }
1197 }
1198
1199 #[test]
1200 fn test_select_distinct_coalesce_no_space() {
1201 let query = parse("SELECT DISTINCT(COALESCE(payee, narration)) as payee FROM transactions")
1203 .unwrap();
1204 match query {
1205 Query::Select(sel) => {
1206 assert!(sel.distinct);
1207 }
1208 _ => panic!("Expected SELECT query"),
1209 }
1210 }
1211
1212 #[test]
1213 fn test_where_clause() {
1214 let query = parse("SELECT * WHERE account ~ \"Expenses:\"").unwrap();
1215 match query {
1216 Query::Select(sel) => {
1217 assert!(sel.where_clause.is_some());
1218 match sel.where_clause.unwrap() {
1219 Expr::BinaryOp(op) => {
1220 assert_eq!(op.op, BinaryOperator::Regex);
1221 }
1222 _ => panic!("Expected binary op"),
1223 }
1224 }
1225 _ => panic!("Expected SELECT query"),
1226 }
1227 }
1228
1229 #[test]
1230 fn test_group_by() {
1231 let query = parse("SELECT account, SUM(position) GROUP BY account").unwrap();
1232 match query {
1233 Query::Select(sel) => {
1234 assert!(sel.group_by.is_some());
1235 assert_eq!(sel.group_by.unwrap().len(), 1);
1236 }
1237 _ => panic!("Expected SELECT query"),
1238 }
1239 }
1240
1241 #[test]
1242 fn test_order_by() {
1243 let query = parse("SELECT * ORDER BY date DESC, account ASC").unwrap();
1244 match query {
1245 Query::Select(sel) => {
1246 assert!(sel.order_by.is_some());
1247 let order = sel.order_by.unwrap();
1248 assert_eq!(order.len(), 2);
1249 assert_eq!(order[0].direction, SortDirection::Desc);
1250 assert_eq!(order[1].direction, SortDirection::Asc);
1251 }
1252 _ => panic!("Expected SELECT query"),
1253 }
1254 }
1255
1256 #[test]
1257 fn test_limit() {
1258 let query = parse("SELECT * LIMIT 100").unwrap();
1259 match query {
1260 Query::Select(sel) => {
1261 assert_eq!(sel.limit, Some(100));
1262 }
1263 _ => panic!("Expected SELECT query"),
1264 }
1265 }
1266
1267 #[test]
1268 fn test_from_open_close_clear() {
1269 let query = parse("SELECT * FROM OPEN ON 2024-01-01 CLOSE ON 2024-12-31 CLEAR").unwrap();
1270 match query {
1271 Query::Select(sel) => {
1272 let from = sel.from.unwrap();
1273 assert_eq!(
1274 from.open_on,
1275 Some(rustledger_core::naive_date(2024, 1, 1).unwrap())
1276 );
1277 assert_eq!(
1278 from.close_on,
1279 Some(rustledger_core::naive_date(2024, 12, 31).unwrap())
1280 );
1281 assert!(from.clear);
1282 }
1283 _ => panic!("Expected SELECT query"),
1284 }
1285 }
1286
1287 #[test]
1288 fn test_from_modifier_followed_by_clause_parses() {
1289 match parse("SELECT account FROM CLOSE ON 2021-01-01 ORDER BY account")
1293 .expect("FROM CLOSE ON <date> ORDER BY should parse")
1294 {
1295 Query::Select(sel) => {
1296 assert_eq!(
1297 sel.from.unwrap().close_on,
1298 Some(rustledger_core::naive_date(2021, 1, 1).unwrap())
1299 );
1300 assert!(
1301 sel.order_by.is_some(),
1302 "ORDER BY should be a clause, not consumed by the FROM filter"
1303 );
1304 }
1305 _ => panic!("Expected SELECT query"),
1306 }
1307 assert!(matches!(
1309 parse("SELECT account FROM OPEN ON 2020-06-01 WHERE account ~ \"Exp\""),
1310 Ok(Query::Select(_))
1311 ));
1312 assert!(matches!(
1313 parse("SELECT account FROM CLOSE ON 2021-01-01 GROUP BY account"),
1314 Ok(Query::Select(_))
1315 ));
1316 match parse("SELECT account FROM account ~ \"Exp\"").expect("FROM filter parses") {
1318 Query::Select(sel) => assert!(
1319 sel.from.unwrap().filter.is_some(),
1320 "a bare FROM expression should be the filter"
1321 ),
1322 _ => panic!("Expected SELECT query"),
1323 }
1324 }
1325
1326 #[test]
1327 fn test_from_year_filter() {
1328 let query = parse("SELECT date, account FROM year = 2024").unwrap();
1329 match query {
1330 Query::Select(sel) => {
1331 let from = sel.from.unwrap();
1332 assert!(from.filter.is_some(), "FROM filter should be present");
1333 match from.filter.unwrap() {
1334 Expr::BinaryOp(op) => {
1335 assert_eq!(op.op, BinaryOperator::Eq);
1336 assert!(matches!(op.left, Expr::Column(ref c) if c == "year"));
1337 match op.right {
1339 Expr::Literal(Literal::Integer(n)) => assert_eq!(n, 2024),
1340 Expr::Literal(Literal::Number(n)) => assert_eq!(n, dec!(2024)),
1341 other => panic!("Expected numeric literal, got {other:?}"),
1342 }
1343 }
1344 other => panic!("Expected BinaryOp, got {other:?}"),
1345 }
1346 }
1347 _ => panic!("Expected SELECT query"),
1348 }
1349 }
1350
1351 #[test]
1352 fn test_journal_query() {
1353 let query = parse("JOURNAL \"Assets:Bank\" AT cost").unwrap();
1354 match query {
1355 Query::Journal(j) => {
1356 assert_eq!(j.account_pattern, "Assets:Bank");
1357 assert_eq!(j.at_function, Some("cost".to_string()));
1358 }
1359 _ => panic!("Expected JOURNAL query"),
1360 }
1361 }
1362
1363 #[test]
1364 fn test_balances_query() {
1365 let query = parse("BALANCES AT units FROM year = 2024").unwrap();
1366 match query {
1367 Query::Balances(b) => {
1368 assert_eq!(b.at_function, Some("units".to_string()));
1369 assert!(b.from.is_some());
1370 }
1371 _ => panic!("Expected BALANCES query"),
1372 }
1373 }
1374
1375 #[test]
1376 fn test_print_query() {
1377 let query = parse("PRINT").unwrap();
1378 assert!(matches!(query, Query::Print(_)));
1379 }
1380
1381 #[test]
1382 fn test_complex_expression() {
1383 let query = parse("SELECT * WHERE date >= 2024-01-01 AND account ~ \"Expenses:\"").unwrap();
1384 match query {
1385 Query::Select(sel) => match sel.where_clause.unwrap() {
1386 Expr::BinaryOp(op) => {
1387 assert_eq!(op.op, BinaryOperator::And);
1388 }
1389 _ => panic!("Expected AND"),
1390 },
1391 _ => panic!("Expected SELECT query"),
1392 }
1393 }
1394
1395 #[test]
1396 fn test_integer_literal_parsing() {
1397 let query = parse("SELECT * WHERE year = 2024").unwrap();
1398 match query {
1399 Query::Select(sel) => match sel.where_clause.unwrap() {
1400 Expr::BinaryOp(op) => match op.right {
1401 Expr::Literal(Literal::Integer(n)) => {
1402 assert_eq!(n, 2024);
1403 }
1404 _ => panic!("Expected integer literal"),
1405 },
1406 _ => panic!("Expected binary op"),
1407 },
1408 _ => panic!("Expected SELECT query"),
1409 }
1410 }
1411
1412 #[test]
1413 fn test_integer_vs_decimal_literal() {
1414 let q = parse("SELECT * WHERE x = 42").unwrap();
1416 let Query::Select(sel) = q else {
1417 panic!("expected SELECT");
1418 };
1419 let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1420 panic!("expected binary op");
1421 };
1422 assert!(matches!(op.right, Expr::Literal(Literal::Integer(42))));
1423
1424 let q = parse("SELECT * WHERE x = 42.0").unwrap();
1426 let Query::Select(sel) = q else {
1427 panic!("expected SELECT");
1428 };
1429 let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1430 panic!("expected binary op");
1431 };
1432 match op.right {
1433 Expr::Literal(Literal::Number(n)) => assert_eq!(n, dec!(42.0)),
1434 other => panic!("expected Number literal, got {other:?}"),
1435 }
1436 }
1437
1438 #[test]
1439 fn test_integer_overflow_falls_back_to_number() {
1440 let q = parse("SELECT * WHERE x = 99999999999999999999").unwrap();
1442 let Query::Select(sel) = q else {
1443 panic!("expected SELECT");
1444 };
1445 let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1446 panic!("expected binary op");
1447 };
1448 assert!(matches!(op.right, Expr::Literal(Literal::Number(_))));
1449 }
1450
1451 #[test]
1452 fn test_negative_integer_literal() {
1453 let q = parse("SELECT * WHERE x = -42").unwrap();
1458 let Query::Select(sel) = q else {
1459 panic!("expected SELECT");
1460 };
1461 let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1462 panic!("expected binary op");
1463 };
1464 match op.right {
1465 Expr::UnaryOp(unary) => {
1466 assert_eq!(unary.op, UnaryOperator::Neg);
1467 assert!(matches!(unary.operand, Expr::Literal(Literal::Integer(42))));
1468 }
1469 other => panic!("expected Unary(Neg, Integer(42)), got {other:?}"),
1470 }
1471 }
1472
1473 #[test]
1474 fn test_semicolon_optional() {
1475 assert!(parse("SELECT *").is_ok());
1476 assert!(parse("SELECT *;").is_ok());
1477 }
1478
1479 #[test]
1480 fn test_subquery_basic() {
1481 let query = parse("SELECT * FROM (SELECT account, position)").unwrap();
1482 match query {
1483 Query::Select(sel) => {
1484 assert!(sel.from.is_some());
1485 let from = sel.from.unwrap();
1486 assert!(from.subquery.is_some());
1487 let subquery = from.subquery.unwrap();
1488 assert_eq!(subquery.targets.len(), 2);
1489 }
1490 _ => panic!("Expected SELECT query"),
1491 }
1492 }
1493
1494 #[test]
1495 fn test_subquery_with_groupby() {
1496 let query = parse(
1497 "SELECT account, total FROM (SELECT account, SUM(position) AS total GROUP BY account)",
1498 )
1499 .unwrap();
1500 match query {
1501 Query::Select(sel) => {
1502 assert_eq!(sel.targets.len(), 2);
1503 let from = sel.from.unwrap();
1504 assert!(from.subquery.is_some());
1505 let subquery = from.subquery.unwrap();
1506 assert!(subquery.group_by.is_some());
1507 }
1508 _ => panic!("Expected SELECT query"),
1509 }
1510 }
1511
1512 #[test]
1513 fn test_subquery_with_outer_where() {
1514 let query =
1515 parse("SELECT * FROM (SELECT * WHERE year = 2024) WHERE account ~ \"Expenses:\"")
1516 .unwrap();
1517 match query {
1518 Query::Select(sel) => {
1519 assert!(sel.where_clause.is_some());
1521 let from = sel.from.unwrap();
1523 let subquery = from.subquery.unwrap();
1524 assert!(subquery.where_clause.is_some());
1525 }
1526 _ => panic!("Expected SELECT query"),
1527 }
1528 }
1529
1530 #[test]
1531 fn test_nested_subquery() {
1532 let query = parse("SELECT * FROM (SELECT * FROM (SELECT account))").unwrap();
1534 match query {
1535 Query::Select(sel) => {
1536 let from = sel.from.unwrap();
1537 let subquery1 = from.subquery.unwrap();
1538 let from2 = subquery1.from.unwrap();
1539 assert!(from2.subquery.is_some());
1540 }
1541 _ => panic!("Expected SELECT query"),
1542 }
1543 }
1544
1545 #[test]
1546 fn test_nested_function_calls() {
1547 let query = parse("SELECT units(sum(position))").unwrap();
1549 match query {
1550 Query::Select(sel) => {
1551 assert_eq!(sel.targets.len(), 1);
1552 match &sel.targets[0].expr {
1553 Expr::Function(outer) => {
1554 assert_eq!(outer.name, "units");
1555 assert_eq!(outer.args.len(), 1);
1556 match &outer.args[0] {
1557 Expr::Function(inner) => {
1558 assert_eq!(inner.name, "sum");
1559 assert_eq!(inner.args.len(), 1);
1560 assert!(
1561 matches!(&inner.args[0], Expr::Column(c) if c == "position")
1562 );
1563 }
1564 _ => panic!("Expected inner function call"),
1565 }
1566 }
1567 _ => panic!("Expected outer function call"),
1568 }
1569 }
1570 _ => panic!("Expected SELECT query"),
1571 }
1572 }
1573
1574 #[test]
1575 fn test_deeply_nested_function_calls() {
1576 let query = parse("SELECT foo(bar(baz(x)))").unwrap();
1578 match query {
1579 Query::Select(sel) => {
1580 assert_eq!(sel.targets.len(), 1);
1581 match &sel.targets[0].expr {
1582 Expr::Function(f1) => {
1583 assert_eq!(f1.name, "foo");
1584 match &f1.args[0] {
1585 Expr::Function(f2) => {
1586 assert_eq!(f2.name, "bar");
1587 match &f2.args[0] {
1588 Expr::Function(f3) => {
1589 assert_eq!(f3.name, "baz");
1590 assert!(matches!(&f3.args[0], Expr::Column(c) if c == "x"));
1591 }
1592 _ => panic!("Expected f3"),
1593 }
1594 }
1595 _ => panic!("Expected f2"),
1596 }
1597 }
1598 _ => panic!("Expected f1"),
1599 }
1600 }
1601 _ => panic!("Expected SELECT query"),
1602 }
1603 }
1604
1605 #[test]
1606 fn test_function_with_arithmetic() {
1607 let query = parse("SELECT sum(amount * 2)").unwrap();
1609 match query {
1610 Query::Select(sel) => match &sel.targets[0].expr {
1611 Expr::Function(f) => {
1612 assert_eq!(f.name, "sum");
1613 assert!(matches!(&f.args[0], Expr::BinaryOp(_)));
1614 }
1615 _ => panic!("Expected function"),
1616 },
1617 _ => panic!("Expected SELECT query"),
1618 }
1619 }
1620
1621 #[test]
1622 fn test_is_null() {
1623 let query = parse("SELECT * WHERE payee IS NULL").unwrap();
1624 match query {
1625 Query::Select(sel) => match sel.where_clause.unwrap() {
1626 Expr::UnaryOp(op) => {
1627 assert_eq!(op.op, UnaryOperator::IsNull);
1628 assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1629 }
1630 _ => panic!("Expected unary op"),
1631 },
1632 _ => panic!("Expected SELECT query"),
1633 }
1634 }
1635
1636 #[test]
1637 fn test_is_not_null() {
1638 let query = parse("SELECT * WHERE payee IS NOT NULL").unwrap();
1639 match query {
1640 Query::Select(sel) => match sel.where_clause.unwrap() {
1641 Expr::UnaryOp(op) => {
1642 assert_eq!(op.op, UnaryOperator::IsNotNull);
1643 assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1644 }
1645 _ => panic!("Expected unary op"),
1646 },
1647 _ => panic!("Expected SELECT query"),
1648 }
1649 }
1650
1651 #[test]
1652 fn test_not_regex() {
1653 let query = parse("SELECT * WHERE account !~ \"Assets:\"").unwrap();
1654 match query {
1655 Query::Select(sel) => match sel.where_clause.unwrap() {
1656 Expr::BinaryOp(op) => {
1657 assert_eq!(op.op, BinaryOperator::NotRegex);
1658 }
1659 _ => panic!("Expected binary op"),
1660 },
1661 _ => panic!("Expected SELECT query"),
1662 }
1663 }
1664
1665 #[test]
1666 fn test_modulo() {
1667 let query = parse("SELECT year % 4").unwrap();
1668 match query {
1669 Query::Select(sel) => match &sel.targets[0].expr {
1670 Expr::BinaryOp(op) => {
1671 assert_eq!(op.op, BinaryOperator::Mod);
1672 }
1673 _ => panic!("Expected binary op"),
1674 },
1675 _ => panic!("Expected SELECT query"),
1676 }
1677 }
1678
1679 #[test]
1680 fn test_between() {
1681 let query = parse("SELECT * WHERE year BETWEEN 2020 AND 2024").unwrap();
1682 match query {
1683 Query::Select(sel) => match sel.where_clause.unwrap() {
1684 Expr::Between { value, low, high } => {
1685 assert!(matches!(*value, Expr::Column(c) if c == "year"));
1686 assert!(matches!(*low, Expr::Literal(Literal::Integer(_))));
1687 assert!(matches!(*high, Expr::Literal(Literal::Integer(_))));
1688 }
1689 _ => panic!("Expected BETWEEN"),
1690 },
1691 _ => panic!("Expected SELECT query"),
1692 }
1693 }
1694
1695 #[test]
1696 fn test_not_in() {
1697 let query = parse("SELECT * WHERE account NOT IN tags").unwrap();
1698 match query {
1699 Query::Select(sel) => match sel.where_clause.unwrap() {
1700 Expr::BinaryOp(op) => {
1701 assert_eq!(op.op, BinaryOperator::NotIn);
1702 }
1703 _ => panic!("Expected binary op"),
1704 },
1705 _ => panic!("Expected SELECT query"),
1706 }
1707 }
1708
1709 #[test]
1710 fn test_in_set_literal() {
1711 let query = parse("SELECT * WHERE currency IN ('EUR', 'USD')").unwrap();
1713 match query {
1714 Query::Select(sel) => match sel.where_clause.unwrap() {
1715 Expr::BinaryOp(op) => {
1716 assert_eq!(op.op, BinaryOperator::In);
1717 match op.right {
1718 Expr::Set(elements) => {
1719 assert_eq!(elements.len(), 2);
1720 }
1721 _ => panic!("Expected Set"),
1722 }
1723 }
1724 _ => panic!("Expected binary op"),
1725 },
1726 _ => panic!("Expected SELECT query"),
1727 }
1728
1729 let query = parse("SELECT * WHERE currency IN ('EUR',)").unwrap();
1731 match query {
1732 Query::Select(sel) => match sel.where_clause.unwrap() {
1733 Expr::BinaryOp(op) => {
1734 assert_eq!(op.op, BinaryOperator::In);
1735 match op.right {
1736 Expr::Set(elements) => {
1737 assert_eq!(elements.len(), 1);
1738 }
1739 _ => panic!("Expected Set"),
1740 }
1741 }
1742 _ => panic!("Expected binary op"),
1743 },
1744 _ => panic!("Expected SELECT query"),
1745 }
1746
1747 let query = parse("SELECT * WHERE 'x' IN (tags)").unwrap();
1749 match query {
1750 Query::Select(sel) => match sel.where_clause.unwrap() {
1751 Expr::BinaryOp(op) => {
1752 assert_eq!(op.op, BinaryOperator::In);
1753 match op.right {
1755 Expr::Paren(inner) => match *inner {
1756 Expr::Column(name) => assert_eq!(name, "tags"),
1757 _ => panic!("Expected Column inside Paren"),
1758 },
1759 other => panic!("Expected Paren, got {other:?}"),
1760 }
1761 }
1762 _ => panic!("Expected binary op"),
1763 },
1764 _ => panic!("Expected SELECT query"),
1765 }
1766 }
1767
1768 #[test]
1769 fn test_string_arg_function() {
1770 let query = parse("SELECT foo(x)").unwrap();
1772 match query {
1773 Query::Select(sel) => match &sel.targets[0].expr {
1774 Expr::Function(f) => {
1775 assert_eq!(f.name, "foo");
1776 }
1777 _ => panic!("Expected function"),
1778 },
1779 _ => panic!("Expected SELECT query"),
1780 }
1781
1782 let query = parse("SELECT foo('bar')").unwrap();
1784 match query {
1785 Query::Select(sel) => match &sel.targets[0].expr {
1786 Expr::Function(f) => {
1787 assert_eq!(f.name, "foo");
1788 assert!(matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "bar"));
1789 }
1790 _ => panic!("Expected function"),
1791 },
1792 _ => panic!("Expected SELECT query"),
1793 }
1794 }
1795
1796 #[test]
1797 fn test_meta_function() {
1798 let query = parse("SELECT meta('category')").unwrap();
1799 match query {
1800 Query::Select(sel) => match &sel.targets[0].expr {
1801 Expr::Function(f) => {
1802 assert_eq!(f.name.to_uppercase(), "META");
1803 assert_eq!(f.args.len(), 1);
1804 assert!(
1805 matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "category")
1806 );
1807 }
1808 _ => panic!("Expected function"),
1809 },
1810 _ => panic!("Expected SELECT query"),
1811 }
1812 }
1813
1814 #[test]
1815 fn test_entry_meta_function() {
1816 let query = parse("SELECT entry_meta('source')").unwrap();
1817 match query {
1818 Query::Select(sel) => match &sel.targets[0].expr {
1819 Expr::Function(f) => {
1820 assert_eq!(f.name.to_uppercase(), "ENTRY_META");
1821 assert_eq!(f.args.len(), 1);
1822 }
1823 _ => panic!("Expected function"),
1824 },
1825 _ => panic!("Expected SELECT query"),
1826 }
1827 }
1828
1829 #[test]
1830 fn test_convert_function() {
1831 let query = parse("SELECT convert(position, 'USD')").unwrap();
1832 match query {
1833 Query::Select(sel) => match &sel.targets[0].expr {
1834 Expr::Function(f) => {
1835 assert_eq!(f.name.to_uppercase(), "CONVERT");
1836 assert_eq!(f.args.len(), 2);
1837 }
1838 _ => panic!("Expected function"),
1839 },
1840 _ => panic!("Expected SELECT query"),
1841 }
1842 }
1843
1844 #[test]
1845 fn test_type_cast_functions() {
1846 let query = parse("SELECT int(number)").unwrap();
1848 match query {
1849 Query::Select(sel) => match &sel.targets[0].expr {
1850 Expr::Function(f) => {
1851 assert_eq!(f.name.to_uppercase(), "INT");
1852 assert_eq!(f.args.len(), 1);
1853 }
1854 _ => panic!("Expected function"),
1855 },
1856 _ => panic!("Expected SELECT query"),
1857 }
1858
1859 let query = parse("SELECT decimal('123.45')").unwrap();
1861 match query {
1862 Query::Select(sel) => match &sel.targets[0].expr {
1863 Expr::Function(f) => {
1864 assert_eq!(f.name.to_uppercase(), "DECIMAL");
1865 }
1866 _ => panic!("Expected function"),
1867 },
1868 _ => panic!("Expected SELECT query"),
1869 }
1870
1871 let query = parse("SELECT str(123)").unwrap();
1873 match query {
1874 Query::Select(sel) => match &sel.targets[0].expr {
1875 Expr::Function(f) => {
1876 assert_eq!(f.name.to_uppercase(), "STR");
1877 }
1878 _ => panic!("Expected function"),
1879 },
1880 _ => panic!("Expected SELECT query"),
1881 }
1882
1883 let query = parse("SELECT bool(1)").unwrap();
1885 match query {
1886 Query::Select(sel) => match &sel.targets[0].expr {
1887 Expr::Function(f) => {
1888 assert_eq!(f.name.to_uppercase(), "BOOL");
1889 }
1890 _ => panic!("Expected function"),
1891 },
1892 _ => panic!("Expected SELECT query"),
1893 }
1894 }
1895
1896 #[test]
1897 fn test_system_table_prices() {
1898 let query = parse("SELECT date, currency, amount FROM #prices").unwrap();
1900 match query {
1901 Query::Select(sel) => {
1902 assert_eq!(sel.targets.len(), 3);
1903 assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
1904 assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "currency"));
1905 assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "amount"));
1906 let from = sel.from.unwrap();
1907 assert_eq!(from.table_name, Some("#prices".to_string()));
1908 }
1909 _ => panic!("Expected SELECT query"),
1910 }
1911 }
1912
1913 #[test]
1914 fn test_system_table_with_where() {
1915 let query = parse("SELECT * FROM #prices WHERE currency = 'EUR'").unwrap();
1917 match query {
1918 Query::Select(sel) => {
1919 let from = sel.from.unwrap();
1920 assert_eq!(from.table_name, Some("#prices".to_string()));
1921 assert!(sel.where_clause.is_some());
1922 }
1923 _ => panic!("Expected SELECT query"),
1924 }
1925 }
1926
1927 #[test]
1928 fn test_regular_table_identifier() {
1929 let query = parse("SELECT * FROM MyTable WHERE x = 1").unwrap();
1931 match query {
1932 Query::Select(sel) => {
1933 let from = sel.from.unwrap();
1934 assert_eq!(from.table_name, Some("MyTable".to_string()));
1935 }
1936 _ => panic!("Expected SELECT query"),
1937 }
1938 }
1939
1940 #[test]
1944 fn deeply_nested_parens_rejected_without_stack_overflow() {
1945 let src = format!("SELECT {}", "(".repeat(2000));
1946 let err = parse(&src).expect_err("deeply nested parens should be rejected");
1947 assert!(
1948 matches!(err.kind, ParseErrorKind::SyntaxError(ref m) if m.contains("nesting too deep")),
1949 "expected a nesting-depth error, got: {:?}",
1950 err.kind
1951 );
1952 }
1953
1954 #[test]
1957 fn moderate_nesting_still_parses() {
1958 let depth = 100usize;
1960 let inner = "x".to_string();
1961 let body = format!("{}{}{}", "abs(".repeat(depth), inner, ")".repeat(depth));
1962 let src = format!("SELECT {body}");
1963 assert!(
1964 parse(&src).is_ok(),
1965 "a {depth}-deep nested query within the limit should parse"
1966 );
1967 }
1968
1969 #[test]
1972 fn parens_inside_string_literal_dont_count() {
1973 let src = format!("SELECT account WHERE narration = \"{}\"", "(".repeat(2000));
1974 assert!(
1977 nesting_exceeds_limit(&src).is_none(),
1978 "parens inside a string literal must not count toward the nesting limit"
1979 );
1980 assert!(parse(&src).is_ok(), "string-literal query should parse");
1982 }
1983}