Skip to main content

rustledger_query/
parser.rs

1//! BQL Parser implementation.
2//!
3//! Uses chumsky for parser combinators.
4
5use 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
20/// Helper enum for parsing comparison suffix (BETWEEN, IN, or binary comparison).
21enum ComparisonSuffix {
22    Between(Expr, Expr),
23    Binary(BinaryOperator, Expr),
24    /// IN with right-hand side (set literal or expression).
25    In(Expr),
26    /// NOT IN with right-hand side (set literal or expression).
27    NotIn(Expr),
28}
29
30/// Parse a BQL query string.
31///
32/// # Errors
33///
34/// Returns a `ParseError` if the query string is malformed.
35pub fn parse(source: &str) -> Result<Query, ParseError> {
36    let (result, errs) = query_parser()
37        .then_ignore(ws())
38        .then_ignore(end())
39        .parse(source)
40        .into_output_errors();
41
42    if let Some(query) = result {
43        Ok(query)
44    } else {
45        let err = errs.first().map(|e| {
46            let kind = if e.found().is_none() {
47                ParseErrorKind::UnexpectedEof
48            } else {
49                ParseErrorKind::SyntaxError(e.to_string())
50            };
51            ParseError::new(kind, e.span().start)
52        });
53        Err(err.unwrap_or_else(|| ParseError::new(ParseErrorKind::UnexpectedEof, 0)))
54    }
55}
56
57/// Parse whitespace (spaces, tabs, newlines).
58fn ws<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
59    one_of(" \t\r\n").repeated().ignored()
60}
61
62/// Parse required whitespace.
63fn ws1<'a>() -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
64    one_of(" \t\r\n").repeated().at_least(1).ignored()
65}
66
67/// Case-insensitive keyword parser.
68fn kw<'a>(keyword: &'static str) -> impl Parser<'a, ParserInput<'a>, (), ParserExtra<'a>> + Clone {
69    text::ident().try_map(move |s: &str, span| {
70        if s.eq_ignore_ascii_case(keyword) {
71            Ok(())
72        } else {
73            Err(Rich::custom(span, format!("expected keyword '{keyword}'")))
74        }
75    })
76}
77
78/// Parse digits.
79fn digits<'a>() -> impl Parser<'a, ParserInput<'a>, &'a str, ParserExtra<'a>> + Clone {
80    one_of("0123456789").repeated().at_least(1).to_slice()
81}
82
83/// Parse the main query.
84fn query_parser<'a>() -> impl Parser<'a, ParserInput<'a>, Query, ParserExtra<'a>> {
85    ws().ignore_then(choice((
86        create_table_stmt().map(Query::CreateTable),
87        insert_stmt().map(Query::Insert),
88        select_query().map(|sq| Query::Select(Box::new(sq))),
89        journal_query().map(Query::Journal),
90        balances_query().map(Query::Balances),
91        print_query().map(Query::Print),
92    )))
93    .then_ignore(ws())
94    .then_ignore(just(';').or_not())
95}
96
97/// Parse a SELECT query with optional subquery support.
98fn select_query<'a>() -> impl Parser<'a, ParserInput<'a>, SelectQuery, ParserExtra<'a>> {
99    recursive(|select_parser| {
100        // Subquery in FROM clause: FROM (SELECT ...)
101        let subquery_from = ws1()
102            .ignore_then(kw("FROM"))
103            .ignore_then(ws1())
104            .ignore_then(just('('))
105            .ignore_then(ws())
106            .ignore_then(select_parser)
107            .then_ignore(ws())
108            .then_ignore(just(')'))
109            .map(|sq| Some(FromClause::from_subquery(sq)));
110
111        // Table name FROM clause: FROM tablename (where tablename is not a keyword)
112        // A table name is an identifier followed by WHERE/GROUP/ORDER/HAVING/LIMIT/PIVOT or end
113        // Supports system tables like #prices, #entries
114        let table_from = ws1()
115            .ignore_then(kw("FROM"))
116            .ignore_then(ws1())
117            .ignore_then(table_identifier().try_map(|name, span| {
118                // Check if this looks like a table name (uppercase convention or doesn't look like account)
119                // Table names should not contain ':' which accounts have
120                // System tables starting with '#' are always valid
121                if !name.starts_with('#') && name.contains(':') {
122                    Err(Rich::custom(
123                        span,
124                        "table names cannot contain ':' - this looks like an account filter expression",
125                    ))
126                } else {
127                    Ok(name)
128                }
129            }))
130            .then_ignore(
131                // Must be followed by WHERE, GROUP, ORDER, HAVING, LIMIT, PIVOT, or end
132                ws().then(choice((
133                    kw("WHERE").ignored(),
134                    kw("GROUP").ignored(),
135                    kw("ORDER").ignored(),
136                    kw("HAVING").ignored(),
137                    kw("LIMIT").ignored(),
138                    kw("PIVOT").ignored(),
139                    end().ignored(),
140                )))
141                .rewind(),
142            )
143            .map(|name| Some(FromClause::from_table(name)));
144
145        // Regular FROM clause
146        let regular_from = from_clause().map(Some);
147
148        kw("SELECT")
149            .ignore_then(ws1())
150            .ignore_then(
151                kw("DISTINCT")
152                    .then_ignore(ws())
153                    .or_not()
154                    .map(|d| d.is_some()),
155            )
156            .then(targets())
157            .then(
158                subquery_from
159                    .or(table_from)
160                    .or(regular_from)
161                    .or_not()
162                    .map(std::option::Option::flatten),
163            )
164            .then(where_clause().or_not())
165            .then(group_by_clause().or_not())
166            .then(having_clause().or_not())
167            // Clause order matches bean-query (#1034): ORDER BY before
168            // PIVOT BY. Pre-#1034 rledger had PIVOT BY before ORDER BY,
169            // which is bean-query-incompatible — flipping the order here
170            // gives upstream parity for queries that use both.
171            .then(order_by_clause().or_not())
172            .then(pivot_by_clause().or_not())
173            .then(limit_clause().or_not())
174            .map(
175                |(
176                    (
177                        (
178                            (((((distinct, targets), from), where_clause), group_by), having),
179                            order_by,
180                        ),
181                        pivot_by,
182                    ),
183                    limit,
184                )| {
185                    SelectQuery {
186                        distinct,
187                        targets,
188                        from,
189                        where_clause,
190                        group_by,
191                        having,
192                        pivot_by,
193                        order_by,
194                        limit,
195                    }
196                },
197            )
198    })
199}
200
201/// Parse FROM clause.
202fn from_clause<'a>() -> impl Parser<'a, ParserInput<'a>, FromClause, ParserExtra<'a>> + Clone {
203    ws1()
204        .ignore_then(kw("FROM"))
205        .ignore_then(ws1())
206        .ignore_then(from_modifiers())
207}
208
209/// Parse target expressions.
210fn targets<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Target>, ParserExtra<'a>> + Clone {
211    target()
212        .separated_by(ws().then(just(',')).then(ws()))
213        .at_least(1)
214        .collect()
215}
216
217/// Parse a single target.
218fn target<'a>() -> impl Parser<'a, ParserInput<'a>, Target, ParserExtra<'a>> + Clone {
219    expr()
220        .then(
221            ws1()
222                .ignore_then(kw("AS"))
223                .ignore_then(ws1())
224                .ignore_then(identifier())
225                .or_not(),
226        )
227        .map(|(expr, alias)| Target { expr, alias })
228}
229
230/// Parse FROM modifiers (OPEN ON, CLOSE ON, CLEAR, filter).
231fn from_modifiers<'a>() -> impl Parser<'a, ParserInput<'a>, FromClause, ParserExtra<'a>> + Clone {
232    let open_on = kw("OPEN")
233        .ignore_then(ws1())
234        .ignore_then(kw("ON"))
235        .ignore_then(ws1())
236        .ignore_then(date_literal())
237        .then_ignore(ws());
238
239    let close_on = kw("CLOSE")
240        .ignore_then(ws().then(kw("ON")).then(ws()).or_not())
241        .ignore_then(date_literal())
242        .then_ignore(ws());
243
244    let clear = kw("CLEAR").then_ignore(ws());
245
246    // Parse modifiers in order: OPEN ON, CLOSE ON, CLEAR, filter
247    // Or just a table name for user-created tables
248    open_on
249        .or_not()
250        .then(close_on.or_not())
251        .then(clear.or_not().map(|c| c.is_some()))
252        .then(from_filter().or_not())
253        .map(|(((open_on, close_on), clear), filter)| FromClause {
254            open_on,
255            close_on,
256            clear,
257            filter,
258            subquery: None,
259            table_name: None,
260        })
261}
262
263/// Parse FROM filter expression (predicates).
264fn from_filter<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
265    expr()
266}
267
268/// Parse WHERE clause.
269fn where_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
270    ws1()
271        .ignore_then(kw("WHERE"))
272        .ignore_then(ws1())
273        .ignore_then(expr())
274}
275
276/// Parse GROUP BY clause.
277fn group_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
278    ws1()
279        .ignore_then(kw("GROUP"))
280        .ignore_then(ws1())
281        .ignore_then(kw("BY"))
282        .ignore_then(ws1())
283        .ignore_then(
284            expr()
285                .separated_by(ws().then(just(',')).then(ws()))
286                .at_least(1)
287                .collect(),
288        )
289}
290
291/// Parse HAVING clause (filter on aggregated results).
292fn having_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
293    ws1()
294        .ignore_then(kw("HAVING"))
295        .ignore_then(ws1())
296        .ignore_then(expr())
297}
298
299/// Parse PIVOT BY clause (pivot table transformation).
300fn pivot_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
301    ws1()
302        .ignore_then(kw("PIVOT"))
303        .ignore_then(ws1())
304        .ignore_then(kw("BY"))
305        .ignore_then(ws1())
306        .ignore_then(
307            expr()
308                .separated_by(ws().then(just(',')).then(ws()))
309                .at_least(1)
310                .collect(),
311        )
312}
313
314/// Parse ORDER BY clause.
315fn order_by_clause<'a>() -> impl Parser<'a, ParserInput<'a>, Vec<OrderSpec>, ParserExtra<'a>> + Clone
316{
317    ws1()
318        .ignore_then(kw("ORDER"))
319        .ignore_then(ws1())
320        .ignore_then(kw("BY"))
321        .ignore_then(ws1())
322        .ignore_then(
323            order_spec()
324                .separated_by(ws().then(just(',')).then(ws()))
325                .at_least(1)
326                .collect(),
327        )
328}
329
330/// Parse a single ORDER BY spec.
331fn order_spec<'a>() -> impl Parser<'a, ParserInput<'a>, OrderSpec, ParserExtra<'a>> + Clone {
332    expr()
333        .then(
334            ws1()
335                .ignore_then(choice((
336                    kw("ASC").to(SortDirection::Asc),
337                    kw("DESC").to(SortDirection::Desc),
338                )))
339                .or_not(),
340        )
341        .map(|(expr, dir)| OrderSpec {
342            expr,
343            direction: dir.unwrap_or_default(),
344        })
345}
346
347/// Parse LIMIT clause.
348fn limit_clause<'a>() -> impl Parser<'a, ParserInput<'a>, u64, ParserExtra<'a>> + Clone {
349    ws1()
350        .ignore_then(kw("LIMIT"))
351        .ignore_then(ws1())
352        .ignore_then(integer())
353        .map(|n| n as u64)
354}
355
356/// Parse JOURNAL query.
357fn journal_query<'a>() -> impl Parser<'a, ParserInput<'a>, JournalQuery, ParserExtra<'a>> + Clone {
358    kw("JOURNAL")
359        .ignore_then(
360            // Account pattern is optional - can be JOURNAL or JOURNAL "pattern"
361            ws1().ignore_then(string_literal()).or_not(),
362        )
363        .then(at_function().or_not())
364        .then(
365            ws1()
366                .ignore_then(kw("FROM"))
367                .ignore_then(ws1())
368                .ignore_then(from_modifiers())
369                .or_not(),
370        )
371        .map(|((account_pattern, at_function), from)| JournalQuery {
372            account_pattern: account_pattern.unwrap_or_default(),
373            at_function,
374            from,
375        })
376}
377
378/// Parse BALANCES query.
379fn balances_query<'a>() -> impl Parser<'a, ParserInput<'a>, BalancesQuery, ParserExtra<'a>> + Clone
380{
381    // Use rewind-based lookahead so optional clauses don't consume whitespace
382    // that subsequent clauses need. Without this, `BALANCES WHERE ...` fails
383    // because at_function() consumes whitespace before failing on "WHERE" != "AT".
384    let at_fn = ws1().then(kw("AT")).rewind().ignore_then(at_function());
385
386    let from = ws1().then(kw("FROM")).rewind().ignore_then(
387        ws1()
388            .ignore_then(kw("FROM"))
389            .ignore_then(ws1())
390            .ignore_then(from_modifiers()),
391    );
392
393    kw("BALANCES")
394        .ignore_then(at_fn.or_not())
395        .then(from.or_not())
396        .then(where_clause().or_not())
397        .map(|((at_function, from), where_clause)| BalancesQuery {
398            at_function,
399            from,
400            where_clause,
401        })
402}
403
404/// Parse PRINT query.
405fn print_query<'a>() -> impl Parser<'a, ParserInput<'a>, PrintQuery, ParserExtra<'a>> + Clone {
406    kw("PRINT")
407        .ignore_then(
408            ws1()
409                .ignore_then(kw("FROM"))
410                .ignore_then(ws1())
411                .ignore_then(from_modifiers())
412                .or_not(),
413        )
414        .map(|from| PrintQuery { from })
415}
416
417/// Parse CREATE TABLE statement.
418fn create_table_stmt<'a>() -> impl Parser<'a, ParserInput<'a>, CreateTableStmt, ParserExtra<'a>> {
419    // CREATE TABLE name (col1, col2, ...) or CREATE TABLE name AS SELECT ...
420    let column_def = identifier()
421        .then(ws().ignore_then(identifier()).or_not())
422        .map(|(name, type_hint)| ColumnDef { name, type_hint });
423
424    let column_list = just('(')
425        .ignore_then(ws())
426        .ignore_then(
427            column_def
428                .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
429                .collect::<Vec<_>>(),
430        )
431        .then_ignore(ws())
432        .then_ignore(just(')'));
433
434    let as_select = ws1()
435        .ignore_then(kw("AS"))
436        .ignore_then(ws1())
437        .ignore_then(select_query())
438        .map(Box::new);
439
440    kw("CREATE")
441        .ignore_then(ws1())
442        .ignore_then(kw("TABLE"))
443        .ignore_then(ws1())
444        .ignore_then(identifier())
445        .then(ws().ignore_then(column_list).or_not())
446        .then(as_select.or_not())
447        .map(|((table_name, columns), as_select)| CreateTableStmt {
448            table_name,
449            columns: columns.unwrap_or_default(),
450            as_select,
451        })
452}
453
454/// Parse INSERT statement.
455fn insert_stmt<'a>() -> impl Parser<'a, ParserInput<'a>, InsertStmt, ParserExtra<'a>> {
456    // Column list: (col1, col2, ...)
457    let column_list = just('(')
458        .ignore_then(ws())
459        .ignore_then(
460            identifier()
461                .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
462                .collect::<Vec<_>>(),
463        )
464        .then_ignore(ws())
465        .then_ignore(just(')'));
466
467    // VALUES clause: VALUES (v1, v2), (v3, v4), ...
468    let value_row = just('(')
469        .ignore_then(ws())
470        .ignore_then(
471            expr()
472                .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
473                .collect::<Vec<_>>(),
474        )
475        .then_ignore(ws())
476        .then_ignore(just(')'));
477
478    let values_source = kw("VALUES")
479        .ignore_then(ws())
480        .ignore_then(
481            value_row
482                .separated_by(ws().ignore_then(just(',')).then_ignore(ws()))
483                .collect::<Vec<_>>(),
484        )
485        .map(InsertSource::Values);
486
487    // SELECT as source
488    let select_source = select_query().map(|sq| InsertSource::Select(Box::new(sq)));
489
490    let source = choice((values_source, select_source));
491
492    kw("INSERT")
493        .ignore_then(ws1())
494        .ignore_then(kw("INTO"))
495        .ignore_then(ws1())
496        .ignore_then(identifier())
497        .then(ws().ignore_then(column_list).or_not())
498        .then_ignore(ws())
499        .then(source)
500        .map(|((table_name, columns), source)| InsertStmt {
501            table_name,
502            columns,
503            source,
504        })
505}
506
507/// Parse AT function (e.g., AT cost, AT units).
508fn at_function<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
509    ws1()
510        .ignore_then(kw("AT"))
511        .ignore_then(ws1())
512        .ignore_then(identifier())
513}
514
515/// Parse an expression (with precedence climbing).
516#[allow(clippy::large_stack_frames)]
517fn expr<'a>() -> Boxed<'a, 'a, ParserInput<'a>, Expr, ParserExtra<'a>> {
518    recursive(|expr| {
519        let primary = primary_expr(expr.clone()).boxed();
520
521        // Unary minus
522        let unary = just('-')
523            .then_ignore(ws())
524            .or_not()
525            .then(primary)
526            .map(|(neg, e)| {
527                if neg.is_some() {
528                    Expr::unary(UnaryOperator::Neg, e)
529                } else {
530                    e
531                }
532            })
533            .boxed();
534
535        // Multiplicative: * / %
536        let multiplicative = unary
537            .clone()
538            .foldl(
539                ws().ignore_then(choice((
540                    just('*').to(BinaryOperator::Mul),
541                    just('/').to(BinaryOperator::Div),
542                    just('%').to(BinaryOperator::Mod),
543                )))
544                .then_ignore(ws())
545                .then(unary)
546                .repeated(),
547                |left, (op, right)| Expr::binary(left, op, right),
548            )
549            .boxed();
550
551        // Additive: + -
552        let additive = multiplicative
553            .clone()
554            .foldl(
555                ws().ignore_then(choice((
556                    just('+').to(BinaryOperator::Add),
557                    just('-').to(BinaryOperator::Sub),
558                )))
559                .then_ignore(ws())
560                .then(multiplicative)
561                .repeated(),
562                |left, (op, right)| Expr::binary(left, op, right),
563            )
564            .boxed();
565
566        // Comparison: = != < <= > >= ~ !~ IN NOT IN BETWEEN IS NULL
567        let comparison = additive
568            .clone()
569            .then(
570                choice((
571                    // BETWEEN ... AND
572                    ws1()
573                        .ignore_then(kw("BETWEEN"))
574                        .ignore_then(ws1())
575                        .ignore_then(additive.clone())
576                        .then_ignore(ws1())
577                        .then_ignore(kw("AND"))
578                        .then_ignore(ws1())
579                        .then(additive.clone())
580                        .map(|(low, high)| ComparisonSuffix::Between(low, high)),
581                    // NOT IN - try set literal first, then fall back to expression
582                    ws1()
583                        .ignore_then(kw("NOT"))
584                        .ignore_then(ws1())
585                        .ignore_then(kw("IN"))
586                        .ignore_then(ws())
587                        .ignore_then(choice((
588                            set_literal(expr.clone()),
589                            additive.clone(),
590                        )))
591                        .map(ComparisonSuffix::NotIn),
592                    // IN - try set literal first, then fall back to expression
593                    ws1()
594                        .ignore_then(kw("IN"))
595                        .ignore_then(ws())
596                        .ignore_then(choice((
597                            set_literal(expr.clone()),
598                            additive.clone(),
599                        )))
600                        .map(ComparisonSuffix::In),
601                    // Regular comparison operators
602                    ws()
603                        .ignore_then(comparison_op())
604                        .then_ignore(ws())
605                        .then(additive)
606                        .map(|(op, right)| ComparisonSuffix::Binary(op, right)),
607                ))
608                .or_not(),
609            )
610            .map(|(left, suffix)| match suffix {
611                Some(ComparisonSuffix::Between(low, high)) => Expr::between(left, low, high),
612                Some(ComparisonSuffix::Binary(op, right)) => Expr::binary(left, op, right),
613                Some(ComparisonSuffix::In(right)) => Expr::binary(left, BinaryOperator::In, right),
614                Some(ComparisonSuffix::NotIn(right)) => {
615                    Expr::binary(left, BinaryOperator::NotIn, right)
616                }
617                None => left,
618            })
619            // IS NULL / IS NOT NULL (postfix)
620            .then(
621                ws1()
622                    .ignore_then(kw("IS"))
623                    .ignore_then(ws1())
624                    .ignore_then(choice((
625                        kw("NOT")
626                            .ignore_then(ws1())
627                            .ignore_then(kw("NULL"))
628                            .to(UnaryOperator::IsNotNull),
629                        kw("NULL").to(UnaryOperator::IsNull),
630                    )))
631                    .or_not(),
632            )
633            .map(|(expr, is_null)| {
634                if let Some(op) = is_null {
635                    Expr::unary(op, expr)
636                } else {
637                    expr
638                }
639            })
640            .boxed();
641
642        // NOT
643        let not_expr = kw("NOT")
644            .ignore_then(ws1())
645            .repeated()
646            .collect::<Vec<_>>()
647            .then(comparison)
648            .map(|(nots, e)| {
649                nots.into_iter()
650                    .fold(e, |acc, ()| Expr::unary(UnaryOperator::Not, acc))
651            })
652            .boxed();
653
654        // AND
655        let and_expr = not_expr
656            .clone()
657            .foldl(
658                ws1()
659                    .ignore_then(kw("AND"))
660                    .ignore_then(ws1())
661                    .ignore_then(not_expr)
662                    .repeated(),
663                |left, right| Expr::binary(left, BinaryOperator::And, right),
664            )
665            .boxed();
666
667        // OR (lowest precedence)
668        and_expr.clone().foldl(
669            ws1()
670                .ignore_then(kw("OR"))
671                .ignore_then(ws1())
672                .ignore_then(and_expr)
673                .repeated(),
674            |left, right| Expr::binary(left, BinaryOperator::Or, right),
675        )
676    })
677    .boxed()
678}
679
680/// Parse comparison operators (excluding IN/NOT IN which are handled specially).
681fn comparison_op<'a>() -> impl Parser<'a, ParserInput<'a>, BinaryOperator, ParserExtra<'a>> + Clone
682{
683    choice((
684        // Multi-char operators first
685        just("!=").to(BinaryOperator::Ne),
686        just("!~").to(BinaryOperator::NotRegex),
687        just("<=").to(BinaryOperator::Le),
688        just(">=").to(BinaryOperator::Ge),
689        // Single-char operators
690        just('=').to(BinaryOperator::Eq),
691        just('<').to(BinaryOperator::Lt),
692        just('>').to(BinaryOperator::Gt),
693        just('~').to(BinaryOperator::Regex),
694    ))
695}
696
697/// Parse a set literal for IN operator, e.g., `('EUR', 'USD')`.
698///
699/// To distinguish from parenthesized expressions like `IN (tags)`, set literals
700/// require either:
701/// - Two or more comma-separated elements: `('EUR', 'USD')`
702/// - A single element with trailing comma: `('EUR',)`
703///
704/// This ensures `IN (tags)` is parsed as `IN <parenthesized-column>` rather than
705/// `IN <single-element-set>`.
706fn set_literal<'a>(
707    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
708) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
709    just('(')
710        .ignore_then(ws())
711        .ignore_then(
712            // Parse first element
713            expr.clone()
714                .then(
715                    // Then require either:
716                    // - comma + more elements (with optional trailing comma)
717                    // - trailing comma (for single-element sets)
718                    ws().ignore_then(just(',')).ignore_then(ws()).ignore_then(
719                        expr.separated_by(ws().then(just(',')).then(ws()))
720                            .allow_trailing()
721                            .collect::<Vec<_>>(),
722                    ),
723                )
724                .map(|(first, rest)| {
725                    let mut elements = Vec::with_capacity(1 + rest.len());
726                    elements.push(first);
727                    elements.extend(rest);
728                    elements
729                }),
730        )
731        .then_ignore(ws())
732        .then_ignore(just(')'))
733        .map(Expr::Set)
734}
735
736/// Parse primary expressions.
737fn primary_expr<'a>(
738    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
739) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
740    choice((
741        // Parenthesized expression
742        just('(')
743            .ignore_then(ws())
744            .ignore_then(expr.clone())
745            .then_ignore(ws())
746            .then_ignore(just(')'))
747            .map(|e| Expr::Paren(Box::new(e))),
748        // Function call or column reference (must come before wildcard check)
749        // Pass expr to allow nested function calls like units(sum(position))
750        function_call_or_column(expr),
751        // Literals
752        literal().map(Expr::Literal),
753        // Wildcard (fallback if nothing else matched)
754        just('*').to(Expr::Wildcard),
755    ))
756}
757
758/// Parse function call, window function, or column reference.
759fn function_call_or_column<'a>(
760    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
761) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
762    identifier()
763        .then(
764            ws().ignore_then(just('('))
765                .ignore_then(ws())
766                .ignore_then(function_args(expr))
767                .then_ignore(ws())
768                .then_ignore(just(')'))
769                .or_not(),
770        )
771        .then(
772            // Optional OVER clause for window functions
773            ws1()
774                .ignore_then(kw("OVER"))
775                .ignore_then(ws())
776                .ignore_then(just('('))
777                .ignore_then(ws())
778                .ignore_then(window_spec())
779                .then_ignore(ws())
780                .then_ignore(just(')'))
781                .or_not(),
782        )
783        .map(|((name, args), over)| {
784            if let Some(args) = args {
785                if let Some(window_spec) = over {
786                    // Window function
787                    Expr::Window(WindowFunction {
788                        name,
789                        args,
790                        over: window_spec,
791                    })
792                } else {
793                    // Regular function
794                    Expr::Function(FunctionCall { name, args })
795                }
796            } else {
797                Expr::Column(name)
798            }
799        })
800}
801
802/// Parse window specification (PARTITION BY and ORDER BY).
803fn window_spec<'a>() -> impl Parser<'a, ParserInput<'a>, WindowSpec, ParserExtra<'a>> + Clone {
804    let partition_by = kw("PARTITION")
805        .ignore_then(ws1())
806        .ignore_then(kw("BY"))
807        .ignore_then(ws1())
808        .ignore_then(
809            simple_arg()
810                .separated_by(ws().then(just(',')).then(ws()))
811                .at_least(1)
812                .collect::<Vec<_>>(),
813        )
814        .then_ignore(ws());
815
816    let window_order_by = kw("ORDER")
817        .ignore_then(ws1())
818        .ignore_then(kw("BY"))
819        .ignore_then(ws1())
820        .ignore_then(
821            window_order_spec()
822                .separated_by(ws().then(just(',')).then(ws()))
823                .at_least(1)
824                .collect::<Vec<_>>(),
825        );
826
827    partition_by
828        .or_not()
829        .then(window_order_by.or_not())
830        .map(|(partition_by, order_by)| WindowSpec {
831            partition_by,
832            order_by,
833        })
834}
835
836/// Parse ORDER BY spec within window (simple version).
837fn window_order_spec<'a>() -> impl Parser<'a, ParserInput<'a>, OrderSpec, ParserExtra<'a>> + Clone {
838    simple_arg()
839        .then(
840            ws1()
841                .ignore_then(choice((
842                    kw("ASC").to(SortDirection::Asc),
843                    kw("DESC").to(SortDirection::Desc),
844                )))
845                .or_not(),
846        )
847        .map(|(expr, dir)| OrderSpec {
848            expr,
849            direction: dir.unwrap_or_default(),
850        })
851}
852
853/// Parse function arguments.
854fn function_args<'a>(
855    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
856) -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
857    // Allow empty args or comma-separated full expressions
858    // This enables nested function calls like units(sum(position))
859    expr.separated_by(ws().then(just(',')).then(ws())).collect()
860}
861
862/// Parse a simple function argument (column, wildcard, or literal).
863fn simple_arg<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
864    choice((
865        just('*').to(Expr::Wildcard),
866        identifier().map(Expr::Column),
867        literal().map(Expr::Literal),
868    ))
869}
870
871/// Parse a literal.
872fn literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserExtra<'a>> + Clone {
873    choice((
874        // Keywords first
875        kw("TRUE").to(Literal::Boolean(true)),
876        kw("FALSE").to(Literal::Boolean(false)),
877        kw("NULL").to(Literal::Null),
878        // Date literal (must be before number to avoid parsing year as number)
879        date_literal().map(Literal::Date),
880        // Number — Integer if no decimal point, Number otherwise
881        number_literal(),
882        // String
883        string_literal().map(Literal::String),
884    ))
885}
886
887/// Parse an identifier (column name, function name).
888fn identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
889    text::ident().map(|s: &str| s.to_string())
890}
891
892/// Parse a table identifier, which can be a regular identifier or a system table
893/// starting with `#` (e.g., `#prices`, `#entries`).
894fn table_identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
895    choice((
896        // System table: #identifier (e.g., #prices)
897        just('#')
898            .ignore_then(text::ident())
899            .map(|s: &str| format!("#{s}")),
900        // Regular table identifier
901        text::ident().map(|s: &str| s.to_string()),
902    ))
903}
904
905/// Parse a string literal.
906fn string_literal<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
907    // Double-quoted string
908    let double_quoted = just('"')
909        .ignore_then(
910            none_of("\"\\")
911                .or(just('\\').ignore_then(any()))
912                .repeated()
913                .collect::<String>(),
914        )
915        .then_ignore(just('"'));
916
917    // Single-quoted string (SQL-style)
918    let single_quoted = just('\'')
919        .ignore_then(
920            none_of("'\\")
921                .or(just('\\').ignore_then(any()))
922                .repeated()
923                .collect::<String>(),
924        )
925        .then_ignore(just('\''));
926
927    choice((double_quoted, single_quoted))
928}
929
930/// Parse a date literal (YYYY-MM-DD).
931fn date_literal<'a>() -> impl Parser<'a, ParserInput<'a>, NaiveDate, ParserExtra<'a>> + Clone {
932    digits()
933        .then_ignore(just('-'))
934        .then(digits())
935        .then_ignore(just('-'))
936        .then(digits())
937        .try_map(|((year, month), day): ((&str, &str), &str), span| {
938            let year: i32 = year
939                .parse()
940                .map_err(|_| Rich::custom(span, "invalid year"))?;
941            let month: u32 = month
942                .parse()
943                .map_err(|_| Rich::custom(span, "invalid month"))?;
944            let day: u32 = day.parse().map_err(|_| Rich::custom(span, "invalid day"))?;
945            rustledger_core::naive_date(year, month, day)
946                .ok_or_else(|| Rich::custom(span, "invalid date"))
947        })
948}
949
950/// Parse a numeric literal as either `Literal::Integer` (no fractional part) or
951/// `Literal::Number` (has a fractional part, or whole-number value exceeds i64).
952///
953/// Distinguishing integer from decimal at the parser level matches BQL/SQL
954/// semantics and lets functions that strictly require integer arguments
955/// (e.g. `ROOT(account, n)`, `SUBSTR(s, start, len)`) work with literal
956/// arguments. See issue #938.
957fn number_literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserExtra<'a>> + Clone {
958    just('-')
959        .or_not()
960        .then(digits())
961        .then(just('.').then(digits()).or_not())
962        .try_map(
963            |((neg, int_part), frac_part): ((Option<char>, &str), Option<(char, &str)>), span| {
964                let mut s = String::new();
965                if neg.is_some() {
966                    s.push('-');
967                }
968                s.push_str(int_part);
969                match frac_part {
970                    None => match s.parse::<i64>() {
971                        Ok(i) => Ok(Literal::Integer(i)),
972                        // Whole-number value out of i64 range — fall back to Decimal.
973                        Err(_) => Decimal::from_str(&s)
974                            .map(Literal::Number)
975                            .map_err(|_| Rich::custom(span, "invalid number")),
976                    },
977                    Some((_, frac)) => {
978                        s.push('.');
979                        s.push_str(frac);
980                        Decimal::from_str(&s)
981                            .map(Literal::Number)
982                            .map_err(|_| Rich::custom(span, "invalid number"))
983                    }
984                }
985            },
986        )
987}
988
989/// Parse an integer.
990fn integer<'a>() -> impl Parser<'a, ParserInput<'a>, i64, ParserExtra<'a>> + Clone {
991    digits().try_map(|s: &str, span| {
992        s.parse::<i64>()
993            .map_err(|_| Rich::custom(span, "invalid integer"))
994    })
995}
996
997#[cfg(test)]
998mod tests {
999    use super::*;
1000    use rust_decimal_macros::dec;
1001
1002    #[test]
1003    fn test_simple_select() {
1004        let query = parse("SELECT * FROM year = 2024").unwrap();
1005        match query {
1006            Query::Select(sel) => {
1007                assert!(!sel.distinct);
1008                assert_eq!(sel.targets.len(), 1);
1009                assert!(matches!(sel.targets[0].expr, Expr::Wildcard));
1010                assert!(sel.from.is_some());
1011            }
1012            _ => panic!("Expected SELECT query"),
1013        }
1014    }
1015
1016    #[test]
1017    fn test_select_columns() {
1018        let query = parse("SELECT date, account, position").unwrap();
1019        match query {
1020            Query::Select(sel) => {
1021                assert_eq!(sel.targets.len(), 3);
1022                assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
1023                assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "account"));
1024                assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "position"));
1025            }
1026            _ => panic!("Expected SELECT query"),
1027        }
1028    }
1029
1030    #[test]
1031    fn test_select_with_alias() {
1032        let query = parse("SELECT SUM(position) AS total").unwrap();
1033        match query {
1034            Query::Select(sel) => {
1035                assert_eq!(sel.targets.len(), 1);
1036                assert_eq!(sel.targets[0].alias, Some("total".to_string()));
1037                match &sel.targets[0].expr {
1038                    Expr::Function(f) => {
1039                        assert_eq!(f.name, "SUM");
1040                        assert_eq!(f.args.len(), 1);
1041                    }
1042                    _ => panic!("Expected function"),
1043                }
1044            }
1045            _ => panic!("Expected SELECT query"),
1046        }
1047    }
1048
1049    #[test]
1050    fn test_select_distinct() {
1051        let query = parse("SELECT DISTINCT account").unwrap();
1052        match query {
1053            Query::Select(sel) => {
1054                assert!(sel.distinct);
1055            }
1056            _ => panic!("Expected SELECT query"),
1057        }
1058    }
1059
1060    #[test]
1061    fn test_select_distinct_no_space() {
1062        // Issue #640: DISTINCT(expr) without space should not be parsed as a function call
1063        let query = parse("SELECT DISTINCT(account) FROM postings").unwrap();
1064        match query {
1065            Query::Select(sel) => {
1066                assert!(sel.distinct);
1067            }
1068            _ => panic!("Expected SELECT query"),
1069        }
1070    }
1071
1072    #[test]
1073    fn test_select_distinct_coalesce_no_space() {
1074        // Issue #640: DISTINCT(COALESCE(payee, narration)) should work
1075        let query = parse("SELECT DISTINCT(COALESCE(payee, narration)) as payee FROM transactions")
1076            .unwrap();
1077        match query {
1078            Query::Select(sel) => {
1079                assert!(sel.distinct);
1080            }
1081            _ => panic!("Expected SELECT query"),
1082        }
1083    }
1084
1085    #[test]
1086    fn test_where_clause() {
1087        let query = parse("SELECT * WHERE account ~ \"Expenses:\"").unwrap();
1088        match query {
1089            Query::Select(sel) => {
1090                assert!(sel.where_clause.is_some());
1091                match sel.where_clause.unwrap() {
1092                    Expr::BinaryOp(op) => {
1093                        assert_eq!(op.op, BinaryOperator::Regex);
1094                    }
1095                    _ => panic!("Expected binary op"),
1096                }
1097            }
1098            _ => panic!("Expected SELECT query"),
1099        }
1100    }
1101
1102    #[test]
1103    fn test_group_by() {
1104        let query = parse("SELECT account, SUM(position) GROUP BY account").unwrap();
1105        match query {
1106            Query::Select(sel) => {
1107                assert!(sel.group_by.is_some());
1108                assert_eq!(sel.group_by.unwrap().len(), 1);
1109            }
1110            _ => panic!("Expected SELECT query"),
1111        }
1112    }
1113
1114    #[test]
1115    fn test_order_by() {
1116        let query = parse("SELECT * ORDER BY date DESC, account ASC").unwrap();
1117        match query {
1118            Query::Select(sel) => {
1119                assert!(sel.order_by.is_some());
1120                let order = sel.order_by.unwrap();
1121                assert_eq!(order.len(), 2);
1122                assert_eq!(order[0].direction, SortDirection::Desc);
1123                assert_eq!(order[1].direction, SortDirection::Asc);
1124            }
1125            _ => panic!("Expected SELECT query"),
1126        }
1127    }
1128
1129    #[test]
1130    fn test_limit() {
1131        let query = parse("SELECT * LIMIT 100").unwrap();
1132        match query {
1133            Query::Select(sel) => {
1134                assert_eq!(sel.limit, Some(100));
1135            }
1136            _ => panic!("Expected SELECT query"),
1137        }
1138    }
1139
1140    #[test]
1141    fn test_from_open_close_clear() {
1142        let query = parse("SELECT * FROM OPEN ON 2024-01-01 CLOSE ON 2024-12-31 CLEAR").unwrap();
1143        match query {
1144            Query::Select(sel) => {
1145                let from = sel.from.unwrap();
1146                assert_eq!(
1147                    from.open_on,
1148                    Some(rustledger_core::naive_date(2024, 1, 1).unwrap())
1149                );
1150                assert_eq!(
1151                    from.close_on,
1152                    Some(rustledger_core::naive_date(2024, 12, 31).unwrap())
1153                );
1154                assert!(from.clear);
1155            }
1156            _ => panic!("Expected SELECT query"),
1157        }
1158    }
1159
1160    #[test]
1161    fn test_from_year_filter() {
1162        let query = parse("SELECT date, account FROM year = 2024").unwrap();
1163        match query {
1164            Query::Select(sel) => {
1165                let from = sel.from.unwrap();
1166                assert!(from.filter.is_some(), "FROM filter should be present");
1167                match from.filter.unwrap() {
1168                    Expr::BinaryOp(op) => {
1169                        assert_eq!(op.op, BinaryOperator::Eq);
1170                        assert!(matches!(op.left, Expr::Column(ref c) if c == "year"));
1171                        // Right side can be Integer or Number (parser produces Number)
1172                        match op.right {
1173                            Expr::Literal(Literal::Integer(n)) => assert_eq!(n, 2024),
1174                            Expr::Literal(Literal::Number(n)) => assert_eq!(n, dec!(2024)),
1175                            other => panic!("Expected numeric literal, got {other:?}"),
1176                        }
1177                    }
1178                    other => panic!("Expected BinaryOp, got {other:?}"),
1179                }
1180            }
1181            _ => panic!("Expected SELECT query"),
1182        }
1183    }
1184
1185    #[test]
1186    fn test_journal_query() {
1187        let query = parse("JOURNAL \"Assets:Bank\" AT cost").unwrap();
1188        match query {
1189            Query::Journal(j) => {
1190                assert_eq!(j.account_pattern, "Assets:Bank");
1191                assert_eq!(j.at_function, Some("cost".to_string()));
1192            }
1193            _ => panic!("Expected JOURNAL query"),
1194        }
1195    }
1196
1197    #[test]
1198    fn test_balances_query() {
1199        let query = parse("BALANCES AT units FROM year = 2024").unwrap();
1200        match query {
1201            Query::Balances(b) => {
1202                assert_eq!(b.at_function, Some("units".to_string()));
1203                assert!(b.from.is_some());
1204            }
1205            _ => panic!("Expected BALANCES query"),
1206        }
1207    }
1208
1209    #[test]
1210    fn test_print_query() {
1211        let query = parse("PRINT").unwrap();
1212        assert!(matches!(query, Query::Print(_)));
1213    }
1214
1215    #[test]
1216    fn test_complex_expression() {
1217        let query = parse("SELECT * WHERE date >= 2024-01-01 AND account ~ \"Expenses:\"").unwrap();
1218        match query {
1219            Query::Select(sel) => match sel.where_clause.unwrap() {
1220                Expr::BinaryOp(op) => {
1221                    assert_eq!(op.op, BinaryOperator::And);
1222                }
1223                _ => panic!("Expected AND"),
1224            },
1225            _ => panic!("Expected SELECT query"),
1226        }
1227    }
1228
1229    #[test]
1230    fn test_integer_literal_parsing() {
1231        let query = parse("SELECT * WHERE year = 2024").unwrap();
1232        match query {
1233            Query::Select(sel) => match sel.where_clause.unwrap() {
1234                Expr::BinaryOp(op) => match op.right {
1235                    Expr::Literal(Literal::Integer(n)) => {
1236                        assert_eq!(n, 2024);
1237                    }
1238                    _ => panic!("Expected integer literal"),
1239                },
1240                _ => panic!("Expected binary op"),
1241            },
1242            _ => panic!("Expected SELECT query"),
1243        }
1244    }
1245
1246    #[test]
1247    fn test_integer_vs_decimal_literal() {
1248        // Whole-number literal → Integer
1249        let q = parse("SELECT * WHERE x = 42").unwrap();
1250        let Query::Select(sel) = q else {
1251            panic!("expected SELECT");
1252        };
1253        let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1254            panic!("expected binary op");
1255        };
1256        assert!(matches!(op.right, Expr::Literal(Literal::Integer(42))));
1257
1258        // Literal with decimal point → Number, even when whole-valued
1259        let q = parse("SELECT * WHERE x = 42.0").unwrap();
1260        let Query::Select(sel) = q else {
1261            panic!("expected SELECT");
1262        };
1263        let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1264            panic!("expected binary op");
1265        };
1266        match op.right {
1267            Expr::Literal(Literal::Number(n)) => assert_eq!(n, dec!(42.0)),
1268            other => panic!("expected Number literal, got {other:?}"),
1269        }
1270    }
1271
1272    #[test]
1273    fn test_integer_overflow_falls_back_to_number() {
1274        // i64::MAX is 9_223_372_036_854_775_807 — this exceeds it
1275        let q = parse("SELECT * WHERE x = 99999999999999999999").unwrap();
1276        let Query::Select(sel) = q else {
1277            panic!("expected SELECT");
1278        };
1279        let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1280            panic!("expected binary op");
1281        };
1282        assert!(matches!(op.right, Expr::Literal(Literal::Number(_))));
1283    }
1284
1285    #[test]
1286    fn test_negative_integer_literal() {
1287        // Negative integer literals: the expression-level unary-minus rule
1288        // strips the `-` before the literal parser runs, so `-42` becomes
1289        // `Unary(Neg, Integer(42))` rather than `Integer(-42)`. Both forms
1290        // evaluate to `Value::Integer(-42)`.
1291        let q = parse("SELECT * WHERE x = -42").unwrap();
1292        let Query::Select(sel) = q else {
1293            panic!("expected SELECT");
1294        };
1295        let Expr::BinaryOp(op) = sel.where_clause.unwrap() else {
1296            panic!("expected binary op");
1297        };
1298        match op.right {
1299            Expr::UnaryOp(unary) => {
1300                assert_eq!(unary.op, UnaryOperator::Neg);
1301                assert!(matches!(unary.operand, Expr::Literal(Literal::Integer(42))));
1302            }
1303            other => panic!("expected Unary(Neg, Integer(42)), got {other:?}"),
1304        }
1305    }
1306
1307    #[test]
1308    fn test_semicolon_optional() {
1309        assert!(parse("SELECT *").is_ok());
1310        assert!(parse("SELECT *;").is_ok());
1311    }
1312
1313    #[test]
1314    fn test_subquery_basic() {
1315        let query = parse("SELECT * FROM (SELECT account, position)").unwrap();
1316        match query {
1317            Query::Select(sel) => {
1318                assert!(sel.from.is_some());
1319                let from = sel.from.unwrap();
1320                assert!(from.subquery.is_some());
1321                let subquery = from.subquery.unwrap();
1322                assert_eq!(subquery.targets.len(), 2);
1323            }
1324            _ => panic!("Expected SELECT query"),
1325        }
1326    }
1327
1328    #[test]
1329    fn test_subquery_with_groupby() {
1330        let query = parse(
1331            "SELECT account, total FROM (SELECT account, SUM(position) AS total GROUP BY account)",
1332        )
1333        .unwrap();
1334        match query {
1335            Query::Select(sel) => {
1336                assert_eq!(sel.targets.len(), 2);
1337                let from = sel.from.unwrap();
1338                assert!(from.subquery.is_some());
1339                let subquery = from.subquery.unwrap();
1340                assert!(subquery.group_by.is_some());
1341            }
1342            _ => panic!("Expected SELECT query"),
1343        }
1344    }
1345
1346    #[test]
1347    fn test_subquery_with_outer_where() {
1348        let query =
1349            parse("SELECT * FROM (SELECT * WHERE year = 2024) WHERE account ~ \"Expenses:\"")
1350                .unwrap();
1351        match query {
1352            Query::Select(sel) => {
1353                // Outer WHERE
1354                assert!(sel.where_clause.is_some());
1355                // Subquery with its own WHERE
1356                let from = sel.from.unwrap();
1357                let subquery = from.subquery.unwrap();
1358                assert!(subquery.where_clause.is_some());
1359            }
1360            _ => panic!("Expected SELECT query"),
1361        }
1362    }
1363
1364    #[test]
1365    fn test_nested_subquery() {
1366        // Two levels of nesting
1367        let query = parse("SELECT * FROM (SELECT * FROM (SELECT account))").unwrap();
1368        match query {
1369            Query::Select(sel) => {
1370                let from = sel.from.unwrap();
1371                let subquery1 = from.subquery.unwrap();
1372                let from2 = subquery1.from.unwrap();
1373                assert!(from2.subquery.is_some());
1374            }
1375            _ => panic!("Expected SELECT query"),
1376        }
1377    }
1378
1379    #[test]
1380    fn test_nested_function_calls() {
1381        // Test units(sum(position)) pattern
1382        let query = parse("SELECT units(sum(position))").unwrap();
1383        match query {
1384            Query::Select(sel) => {
1385                assert_eq!(sel.targets.len(), 1);
1386                match &sel.targets[0].expr {
1387                    Expr::Function(outer) => {
1388                        assert_eq!(outer.name, "units");
1389                        assert_eq!(outer.args.len(), 1);
1390                        match &outer.args[0] {
1391                            Expr::Function(inner) => {
1392                                assert_eq!(inner.name, "sum");
1393                                assert_eq!(inner.args.len(), 1);
1394                                assert!(
1395                                    matches!(&inner.args[0], Expr::Column(c) if c == "position")
1396                                );
1397                            }
1398                            _ => panic!("Expected inner function call"),
1399                        }
1400                    }
1401                    _ => panic!("Expected outer function call"),
1402                }
1403            }
1404            _ => panic!("Expected SELECT query"),
1405        }
1406    }
1407
1408    #[test]
1409    fn test_deeply_nested_function_calls() {
1410        // Test three levels of nesting
1411        let query = parse("SELECT foo(bar(baz(x)))").unwrap();
1412        match query {
1413            Query::Select(sel) => {
1414                assert_eq!(sel.targets.len(), 1);
1415                match &sel.targets[0].expr {
1416                    Expr::Function(f1) => {
1417                        assert_eq!(f1.name, "foo");
1418                        match &f1.args[0] {
1419                            Expr::Function(f2) => {
1420                                assert_eq!(f2.name, "bar");
1421                                match &f2.args[0] {
1422                                    Expr::Function(f3) => {
1423                                        assert_eq!(f3.name, "baz");
1424                                        assert!(matches!(&f3.args[0], Expr::Column(c) if c == "x"));
1425                                    }
1426                                    _ => panic!("Expected f3"),
1427                                }
1428                            }
1429                            _ => panic!("Expected f2"),
1430                        }
1431                    }
1432                    _ => panic!("Expected f1"),
1433                }
1434            }
1435            _ => panic!("Expected SELECT query"),
1436        }
1437    }
1438
1439    #[test]
1440    fn test_function_with_arithmetic() {
1441        // Test function with arithmetic expression as argument
1442        let query = parse("SELECT sum(amount * 2)").unwrap();
1443        match query {
1444            Query::Select(sel) => match &sel.targets[0].expr {
1445                Expr::Function(f) => {
1446                    assert_eq!(f.name, "sum");
1447                    assert!(matches!(&f.args[0], Expr::BinaryOp(_)));
1448                }
1449                _ => panic!("Expected function"),
1450            },
1451            _ => panic!("Expected SELECT query"),
1452        }
1453    }
1454
1455    #[test]
1456    fn test_is_null() {
1457        let query = parse("SELECT * WHERE payee IS NULL").unwrap();
1458        match query {
1459            Query::Select(sel) => match sel.where_clause.unwrap() {
1460                Expr::UnaryOp(op) => {
1461                    assert_eq!(op.op, UnaryOperator::IsNull);
1462                    assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1463                }
1464                _ => panic!("Expected unary op"),
1465            },
1466            _ => panic!("Expected SELECT query"),
1467        }
1468    }
1469
1470    #[test]
1471    fn test_is_not_null() {
1472        let query = parse("SELECT * WHERE payee IS NOT NULL").unwrap();
1473        match query {
1474            Query::Select(sel) => match sel.where_clause.unwrap() {
1475                Expr::UnaryOp(op) => {
1476                    assert_eq!(op.op, UnaryOperator::IsNotNull);
1477                    assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1478                }
1479                _ => panic!("Expected unary op"),
1480            },
1481            _ => panic!("Expected SELECT query"),
1482        }
1483    }
1484
1485    #[test]
1486    fn test_not_regex() {
1487        let query = parse("SELECT * WHERE account !~ \"Assets:\"").unwrap();
1488        match query {
1489            Query::Select(sel) => match sel.where_clause.unwrap() {
1490                Expr::BinaryOp(op) => {
1491                    assert_eq!(op.op, BinaryOperator::NotRegex);
1492                }
1493                _ => panic!("Expected binary op"),
1494            },
1495            _ => panic!("Expected SELECT query"),
1496        }
1497    }
1498
1499    #[test]
1500    fn test_modulo() {
1501        let query = parse("SELECT year % 4").unwrap();
1502        match query {
1503            Query::Select(sel) => match &sel.targets[0].expr {
1504                Expr::BinaryOp(op) => {
1505                    assert_eq!(op.op, BinaryOperator::Mod);
1506                }
1507                _ => panic!("Expected binary op"),
1508            },
1509            _ => panic!("Expected SELECT query"),
1510        }
1511    }
1512
1513    #[test]
1514    fn test_between() {
1515        let query = parse("SELECT * WHERE year BETWEEN 2020 AND 2024").unwrap();
1516        match query {
1517            Query::Select(sel) => match sel.where_clause.unwrap() {
1518                Expr::Between { value, low, high } => {
1519                    assert!(matches!(*value, Expr::Column(c) if c == "year"));
1520                    assert!(matches!(*low, Expr::Literal(Literal::Integer(_))));
1521                    assert!(matches!(*high, Expr::Literal(Literal::Integer(_))));
1522                }
1523                _ => panic!("Expected BETWEEN"),
1524            },
1525            _ => panic!("Expected SELECT query"),
1526        }
1527    }
1528
1529    #[test]
1530    fn test_not_in() {
1531        let query = parse("SELECT * WHERE account NOT IN tags").unwrap();
1532        match query {
1533            Query::Select(sel) => match sel.where_clause.unwrap() {
1534                Expr::BinaryOp(op) => {
1535                    assert_eq!(op.op, BinaryOperator::NotIn);
1536                }
1537                _ => panic!("Expected binary op"),
1538            },
1539            _ => panic!("Expected SELECT query"),
1540        }
1541    }
1542
1543    #[test]
1544    fn test_in_set_literal() {
1545        // Multi-element set literal
1546        let query = parse("SELECT * WHERE currency IN ('EUR', 'USD')").unwrap();
1547        match query {
1548            Query::Select(sel) => match sel.where_clause.unwrap() {
1549                Expr::BinaryOp(op) => {
1550                    assert_eq!(op.op, BinaryOperator::In);
1551                    match op.right {
1552                        Expr::Set(elements) => {
1553                            assert_eq!(elements.len(), 2);
1554                        }
1555                        _ => panic!("Expected Set"),
1556                    }
1557                }
1558                _ => panic!("Expected binary op"),
1559            },
1560            _ => panic!("Expected SELECT query"),
1561        }
1562
1563        // Single-element set with trailing comma
1564        let query = parse("SELECT * WHERE currency IN ('EUR',)").unwrap();
1565        match query {
1566            Query::Select(sel) => match sel.where_clause.unwrap() {
1567                Expr::BinaryOp(op) => {
1568                    assert_eq!(op.op, BinaryOperator::In);
1569                    match op.right {
1570                        Expr::Set(elements) => {
1571                            assert_eq!(elements.len(), 1);
1572                        }
1573                        _ => panic!("Expected Set"),
1574                    }
1575                }
1576                _ => panic!("Expected binary op"),
1577            },
1578            _ => panic!("Expected SELECT query"),
1579        }
1580
1581        // Parenthesized column (not a set literal)
1582        let query = parse("SELECT * WHERE 'x' IN (tags)").unwrap();
1583        match query {
1584            Query::Select(sel) => match sel.where_clause.unwrap() {
1585                Expr::BinaryOp(op) => {
1586                    assert_eq!(op.op, BinaryOperator::In);
1587                    // Should be Paren(Column), not Set([Column])
1588                    match op.right {
1589                        Expr::Paren(inner) => match *inner {
1590                            Expr::Column(name) => assert_eq!(name, "tags"),
1591                            _ => panic!("Expected Column inside Paren"),
1592                        },
1593                        other => panic!("Expected Paren, got {other:?}"),
1594                    }
1595                }
1596                _ => panic!("Expected binary op"),
1597            },
1598            _ => panic!("Expected SELECT query"),
1599        }
1600    }
1601
1602    #[test]
1603    fn test_string_arg_function() {
1604        // First test a function with a column reference - should work
1605        let query = parse("SELECT foo(x)").unwrap();
1606        match query {
1607            Query::Select(sel) => match &sel.targets[0].expr {
1608                Expr::Function(f) => {
1609                    assert_eq!(f.name, "foo");
1610                }
1611                _ => panic!("Expected function"),
1612            },
1613            _ => panic!("Expected SELECT query"),
1614        }
1615
1616        // Now test a function with a string literal argument
1617        let query = parse("SELECT foo('bar')").unwrap();
1618        match query {
1619            Query::Select(sel) => match &sel.targets[0].expr {
1620                Expr::Function(f) => {
1621                    assert_eq!(f.name, "foo");
1622                    assert!(matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "bar"));
1623                }
1624                _ => panic!("Expected function"),
1625            },
1626            _ => panic!("Expected SELECT query"),
1627        }
1628    }
1629
1630    #[test]
1631    fn test_meta_function() {
1632        let query = parse("SELECT meta('category')").unwrap();
1633        match query {
1634            Query::Select(sel) => match &sel.targets[0].expr {
1635                Expr::Function(f) => {
1636                    assert_eq!(f.name.to_uppercase(), "META");
1637                    assert_eq!(f.args.len(), 1);
1638                    assert!(
1639                        matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "category")
1640                    );
1641                }
1642                _ => panic!("Expected function"),
1643            },
1644            _ => panic!("Expected SELECT query"),
1645        }
1646    }
1647
1648    #[test]
1649    fn test_entry_meta_function() {
1650        let query = parse("SELECT entry_meta('source')").unwrap();
1651        match query {
1652            Query::Select(sel) => match &sel.targets[0].expr {
1653                Expr::Function(f) => {
1654                    assert_eq!(f.name.to_uppercase(), "ENTRY_META");
1655                    assert_eq!(f.args.len(), 1);
1656                }
1657                _ => panic!("Expected function"),
1658            },
1659            _ => panic!("Expected SELECT query"),
1660        }
1661    }
1662
1663    #[test]
1664    fn test_convert_function() {
1665        let query = parse("SELECT convert(position, 'USD')").unwrap();
1666        match query {
1667            Query::Select(sel) => match &sel.targets[0].expr {
1668                Expr::Function(f) => {
1669                    assert_eq!(f.name.to_uppercase(), "CONVERT");
1670                    assert_eq!(f.args.len(), 2);
1671                }
1672                _ => panic!("Expected function"),
1673            },
1674            _ => panic!("Expected SELECT query"),
1675        }
1676    }
1677
1678    #[test]
1679    fn test_type_cast_functions() {
1680        // Test INT
1681        let query = parse("SELECT int(number)").unwrap();
1682        match query {
1683            Query::Select(sel) => match &sel.targets[0].expr {
1684                Expr::Function(f) => {
1685                    assert_eq!(f.name.to_uppercase(), "INT");
1686                    assert_eq!(f.args.len(), 1);
1687                }
1688                _ => panic!("Expected function"),
1689            },
1690            _ => panic!("Expected SELECT query"),
1691        }
1692
1693        // Test DECIMAL
1694        let query = parse("SELECT decimal('123.45')").unwrap();
1695        match query {
1696            Query::Select(sel) => match &sel.targets[0].expr {
1697                Expr::Function(f) => {
1698                    assert_eq!(f.name.to_uppercase(), "DECIMAL");
1699                }
1700                _ => panic!("Expected function"),
1701            },
1702            _ => panic!("Expected SELECT query"),
1703        }
1704
1705        // Test STR
1706        let query = parse("SELECT str(123)").unwrap();
1707        match query {
1708            Query::Select(sel) => match &sel.targets[0].expr {
1709                Expr::Function(f) => {
1710                    assert_eq!(f.name.to_uppercase(), "STR");
1711                }
1712                _ => panic!("Expected function"),
1713            },
1714            _ => panic!("Expected SELECT query"),
1715        }
1716
1717        // Test BOOL
1718        let query = parse("SELECT bool(1)").unwrap();
1719        match query {
1720            Query::Select(sel) => match &sel.targets[0].expr {
1721                Expr::Function(f) => {
1722                    assert_eq!(f.name.to_uppercase(), "BOOL");
1723                }
1724                _ => panic!("Expected function"),
1725            },
1726            _ => panic!("Expected SELECT query"),
1727        }
1728    }
1729
1730    #[test]
1731    fn test_system_table_prices() {
1732        // Test parsing SELECT FROM #prices (system table)
1733        let query = parse("SELECT date, currency, amount FROM #prices").unwrap();
1734        match query {
1735            Query::Select(sel) => {
1736                assert_eq!(sel.targets.len(), 3);
1737                assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
1738                assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "currency"));
1739                assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "amount"));
1740                let from = sel.from.unwrap();
1741                assert_eq!(from.table_name, Some("#prices".to_string()));
1742            }
1743            _ => panic!("Expected SELECT query"),
1744        }
1745    }
1746
1747    #[test]
1748    fn test_system_table_with_where() {
1749        // Test parsing system table with WHERE clause
1750        let query = parse("SELECT * FROM #prices WHERE currency = 'EUR'").unwrap();
1751        match query {
1752            Query::Select(sel) => {
1753                let from = sel.from.unwrap();
1754                assert_eq!(from.table_name, Some("#prices".to_string()));
1755                assert!(sel.where_clause.is_some());
1756            }
1757            _ => panic!("Expected SELECT query"),
1758        }
1759    }
1760
1761    #[test]
1762    fn test_regular_table_identifier() {
1763        // Test parsing a regular (non-system) table
1764        let query = parse("SELECT * FROM MyTable WHERE x = 1").unwrap();
1765        match query {
1766            Query::Select(sel) => {
1767                let from = sel.from.unwrap();
1768                assert_eq!(from.table_name, Some("MyTable".to_string()));
1769            }
1770            _ => panic!("Expected SELECT query"),
1771        }
1772    }
1773}