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