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