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![first];
698                    elements.extend(rest);
699                    elements
700                }),
701        )
702        .then_ignore(ws())
703        .then_ignore(just(')'))
704        .map(Expr::Set)
705}
706
707/// Parse primary expressions.
708fn primary_expr<'a>(
709    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
710) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
711    choice((
712        // Parenthesized expression
713        just('(')
714            .ignore_then(ws())
715            .ignore_then(expr.clone())
716            .then_ignore(ws())
717            .then_ignore(just(')'))
718            .map(|e| Expr::Paren(Box::new(e))),
719        // Function call or column reference (must come before wildcard check)
720        // Pass expr to allow nested function calls like units(sum(position))
721        function_call_or_column(expr),
722        // Literals
723        literal().map(Expr::Literal),
724        // Wildcard (fallback if nothing else matched)
725        just('*').to(Expr::Wildcard),
726    ))
727}
728
729/// Parse function call, window function, or column reference.
730fn function_call_or_column<'a>(
731    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
732) -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
733    identifier()
734        .then(
735            ws().ignore_then(just('('))
736                .ignore_then(ws())
737                .ignore_then(function_args(expr))
738                .then_ignore(ws())
739                .then_ignore(just(')'))
740                .or_not(),
741        )
742        .then(
743            // Optional OVER clause for window functions
744            ws1()
745                .ignore_then(kw("OVER"))
746                .ignore_then(ws())
747                .ignore_then(just('('))
748                .ignore_then(ws())
749                .ignore_then(window_spec())
750                .then_ignore(ws())
751                .then_ignore(just(')'))
752                .or_not(),
753        )
754        .map(|((name, args), over)| {
755            if let Some(args) = args {
756                if let Some(window_spec) = over {
757                    // Window function
758                    Expr::Window(WindowFunction {
759                        name,
760                        args,
761                        over: window_spec,
762                    })
763                } else {
764                    // Regular function
765                    Expr::Function(FunctionCall { name, args })
766                }
767            } else {
768                Expr::Column(name)
769            }
770        })
771}
772
773/// Parse window specification (PARTITION BY and ORDER BY).
774fn window_spec<'a>() -> impl Parser<'a, ParserInput<'a>, WindowSpec, ParserExtra<'a>> + Clone {
775    let partition_by = kw("PARTITION")
776        .ignore_then(ws1())
777        .ignore_then(kw("BY"))
778        .ignore_then(ws1())
779        .ignore_then(
780            simple_arg()
781                .separated_by(ws().then(just(',')).then(ws()))
782                .at_least(1)
783                .collect::<Vec<_>>(),
784        )
785        .then_ignore(ws());
786
787    let window_order_by = kw("ORDER")
788        .ignore_then(ws1())
789        .ignore_then(kw("BY"))
790        .ignore_then(ws1())
791        .ignore_then(
792            window_order_spec()
793                .separated_by(ws().then(just(',')).then(ws()))
794                .at_least(1)
795                .collect::<Vec<_>>(),
796        );
797
798    partition_by
799        .or_not()
800        .then(window_order_by.or_not())
801        .map(|(partition_by, order_by)| WindowSpec {
802            partition_by,
803            order_by,
804        })
805}
806
807/// Parse ORDER BY spec within window (simple version).
808fn window_order_spec<'a>() -> impl Parser<'a, ParserInput<'a>, OrderSpec, ParserExtra<'a>> + Clone {
809    simple_arg()
810        .then(
811            ws1()
812                .ignore_then(choice((
813                    kw("ASC").to(SortDirection::Asc),
814                    kw("DESC").to(SortDirection::Desc),
815                )))
816                .or_not(),
817        )
818        .map(|(expr, dir)| OrderSpec {
819            expr,
820            direction: dir.unwrap_or_default(),
821        })
822}
823
824/// Parse function arguments.
825fn function_args<'a>(
826    expr: impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone + 'a,
827) -> impl Parser<'a, ParserInput<'a>, Vec<Expr>, ParserExtra<'a>> + Clone {
828    // Allow empty args or comma-separated full expressions
829    // This enables nested function calls like units(sum(position))
830    expr.separated_by(ws().then(just(',')).then(ws())).collect()
831}
832
833/// Parse a simple function argument (column, wildcard, or literal).
834fn simple_arg<'a>() -> impl Parser<'a, ParserInput<'a>, Expr, ParserExtra<'a>> + Clone {
835    choice((
836        just('*').to(Expr::Wildcard),
837        identifier().map(Expr::Column),
838        literal().map(Expr::Literal),
839    ))
840}
841
842/// Parse a literal.
843fn literal<'a>() -> impl Parser<'a, ParserInput<'a>, Literal, ParserExtra<'a>> + Clone {
844    choice((
845        // Keywords first
846        kw("TRUE").to(Literal::Boolean(true)),
847        kw("FALSE").to(Literal::Boolean(false)),
848        kw("NULL").to(Literal::Null),
849        // Date literal (must be before number to avoid parsing year as number)
850        date_literal().map(Literal::Date),
851        // Number
852        decimal().map(Literal::Number),
853        // String
854        string_literal().map(Literal::String),
855    ))
856}
857
858/// Parse an identifier (column name, function name).
859fn identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
860    text::ident().map(|s: &str| s.to_string())
861}
862
863/// Parse a table identifier, which can be a regular identifier or a system table
864/// starting with `#` (e.g., `#prices`, `#entries`).
865fn table_identifier<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
866    choice((
867        // System table: #identifier (e.g., #prices)
868        just('#')
869            .ignore_then(text::ident())
870            .map(|s: &str| format!("#{s}")),
871        // Regular table identifier
872        text::ident().map(|s: &str| s.to_string()),
873    ))
874}
875
876/// Parse a string literal.
877fn string_literal<'a>() -> impl Parser<'a, ParserInput<'a>, String, ParserExtra<'a>> + Clone {
878    // Double-quoted string
879    let double_quoted = just('"')
880        .ignore_then(
881            none_of("\"\\")
882                .or(just('\\').ignore_then(any()))
883                .repeated()
884                .collect::<String>(),
885        )
886        .then_ignore(just('"'));
887
888    // Single-quoted string (SQL-style)
889    let single_quoted = just('\'')
890        .ignore_then(
891            none_of("'\\")
892                .or(just('\\').ignore_then(any()))
893                .repeated()
894                .collect::<String>(),
895        )
896        .then_ignore(just('\''));
897
898    choice((double_quoted, single_quoted))
899}
900
901/// Parse a date literal (YYYY-MM-DD).
902fn date_literal<'a>() -> impl Parser<'a, ParserInput<'a>, NaiveDate, ParserExtra<'a>> + Clone {
903    digits()
904        .then_ignore(just('-'))
905        .then(digits())
906        .then_ignore(just('-'))
907        .then(digits())
908        .try_map(|((year, month), day): ((&str, &str), &str), span| {
909            let year: i32 = year
910                .parse()
911                .map_err(|_| Rich::custom(span, "invalid year"))?;
912            let month: u32 = month
913                .parse()
914                .map_err(|_| Rich::custom(span, "invalid month"))?;
915            let day: u32 = day.parse().map_err(|_| Rich::custom(span, "invalid day"))?;
916            NaiveDate::from_ymd_opt(year, month, day)
917                .ok_or_else(|| Rich::custom(span, "invalid date"))
918        })
919}
920
921/// Parse a decimal number.
922fn decimal<'a>() -> impl Parser<'a, ParserInput<'a>, Decimal, ParserExtra<'a>> + Clone {
923    just('-')
924        .or_not()
925        .then(digits())
926        .then(just('.').then(digits()).or_not())
927        .try_map(
928            |((neg, int_part), frac_part): ((Option<char>, &str), Option<(char, &str)>), span| {
929                let mut s = String::new();
930                if neg.is_some() {
931                    s.push('-');
932                }
933                s.push_str(int_part);
934                if let Some((_, frac)) = frac_part {
935                    s.push('.');
936                    s.push_str(frac);
937                }
938                Decimal::from_str(&s).map_err(|_| Rich::custom(span, "invalid number"))
939            },
940        )
941}
942
943/// Parse an integer.
944fn integer<'a>() -> impl Parser<'a, ParserInput<'a>, i64, ParserExtra<'a>> + Clone {
945    digits().try_map(|s: &str, span| {
946        s.parse::<i64>()
947            .map_err(|_| Rich::custom(span, "invalid integer"))
948    })
949}
950
951#[cfg(test)]
952mod tests {
953    use super::*;
954    use rust_decimal_macros::dec;
955
956    #[test]
957    fn test_simple_select() {
958        let query = parse("SELECT * FROM year = 2024").unwrap();
959        match query {
960            Query::Select(sel) => {
961                assert!(!sel.distinct);
962                assert_eq!(sel.targets.len(), 1);
963                assert!(matches!(sel.targets[0].expr, Expr::Wildcard));
964                assert!(sel.from.is_some());
965            }
966            _ => panic!("Expected SELECT query"),
967        }
968    }
969
970    #[test]
971    fn test_select_columns() {
972        let query = parse("SELECT date, account, position").unwrap();
973        match query {
974            Query::Select(sel) => {
975                assert_eq!(sel.targets.len(), 3);
976                assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
977                assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "account"));
978                assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "position"));
979            }
980            _ => panic!("Expected SELECT query"),
981        }
982    }
983
984    #[test]
985    fn test_select_with_alias() {
986        let query = parse("SELECT SUM(position) AS total").unwrap();
987        match query {
988            Query::Select(sel) => {
989                assert_eq!(sel.targets.len(), 1);
990                assert_eq!(sel.targets[0].alias, Some("total".to_string()));
991                match &sel.targets[0].expr {
992                    Expr::Function(f) => {
993                        assert_eq!(f.name, "SUM");
994                        assert_eq!(f.args.len(), 1);
995                    }
996                    _ => panic!("Expected function"),
997                }
998            }
999            _ => panic!("Expected SELECT query"),
1000        }
1001    }
1002
1003    #[test]
1004    fn test_select_distinct() {
1005        let query = parse("SELECT DISTINCT account").unwrap();
1006        match query {
1007            Query::Select(sel) => {
1008                assert!(sel.distinct);
1009            }
1010            _ => panic!("Expected SELECT query"),
1011        }
1012    }
1013
1014    #[test]
1015    fn test_select_distinct_no_space() {
1016        // Issue #640: DISTINCT(expr) without space should not be parsed as a function call
1017        let query = parse("SELECT DISTINCT(account) FROM postings").unwrap();
1018        match query {
1019            Query::Select(sel) => {
1020                assert!(sel.distinct);
1021            }
1022            _ => panic!("Expected SELECT query"),
1023        }
1024    }
1025
1026    #[test]
1027    fn test_select_distinct_coalesce_no_space() {
1028        // Issue #640: DISTINCT(COALESCE(payee, narration)) should work
1029        let query = parse("SELECT DISTINCT(COALESCE(payee, narration)) as payee FROM transactions")
1030            .unwrap();
1031        match query {
1032            Query::Select(sel) => {
1033                assert!(sel.distinct);
1034            }
1035            _ => panic!("Expected SELECT query"),
1036        }
1037    }
1038
1039    #[test]
1040    fn test_where_clause() {
1041        let query = parse("SELECT * WHERE account ~ \"Expenses:\"").unwrap();
1042        match query {
1043            Query::Select(sel) => {
1044                assert!(sel.where_clause.is_some());
1045                match sel.where_clause.unwrap() {
1046                    Expr::BinaryOp(op) => {
1047                        assert_eq!(op.op, BinaryOperator::Regex);
1048                    }
1049                    _ => panic!("Expected binary op"),
1050                }
1051            }
1052            _ => panic!("Expected SELECT query"),
1053        }
1054    }
1055
1056    #[test]
1057    fn test_group_by() {
1058        let query = parse("SELECT account, SUM(position) GROUP BY account").unwrap();
1059        match query {
1060            Query::Select(sel) => {
1061                assert!(sel.group_by.is_some());
1062                assert_eq!(sel.group_by.unwrap().len(), 1);
1063            }
1064            _ => panic!("Expected SELECT query"),
1065        }
1066    }
1067
1068    #[test]
1069    fn test_order_by() {
1070        let query = parse("SELECT * ORDER BY date DESC, account ASC").unwrap();
1071        match query {
1072            Query::Select(sel) => {
1073                assert!(sel.order_by.is_some());
1074                let order = sel.order_by.unwrap();
1075                assert_eq!(order.len(), 2);
1076                assert_eq!(order[0].direction, SortDirection::Desc);
1077                assert_eq!(order[1].direction, SortDirection::Asc);
1078            }
1079            _ => panic!("Expected SELECT query"),
1080        }
1081    }
1082
1083    #[test]
1084    fn test_limit() {
1085        let query = parse("SELECT * LIMIT 100").unwrap();
1086        match query {
1087            Query::Select(sel) => {
1088                assert_eq!(sel.limit, Some(100));
1089            }
1090            _ => panic!("Expected SELECT query"),
1091        }
1092    }
1093
1094    #[test]
1095    fn test_from_open_close_clear() {
1096        let query = parse("SELECT * FROM OPEN ON 2024-01-01 CLOSE ON 2024-12-31 CLEAR").unwrap();
1097        match query {
1098            Query::Select(sel) => {
1099                let from = sel.from.unwrap();
1100                assert_eq!(
1101                    from.open_on,
1102                    Some(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap())
1103                );
1104                assert_eq!(
1105                    from.close_on,
1106                    Some(NaiveDate::from_ymd_opt(2024, 12, 31).unwrap())
1107                );
1108                assert!(from.clear);
1109            }
1110            _ => panic!("Expected SELECT query"),
1111        }
1112    }
1113
1114    #[test]
1115    fn test_from_year_filter() {
1116        let query = parse("SELECT date, account FROM year = 2024").unwrap();
1117        match query {
1118            Query::Select(sel) => {
1119                let from = sel.from.unwrap();
1120                assert!(from.filter.is_some(), "FROM filter should be present");
1121                match from.filter.unwrap() {
1122                    Expr::BinaryOp(op) => {
1123                        assert_eq!(op.op, BinaryOperator::Eq);
1124                        assert!(matches!(op.left, Expr::Column(ref c) if c == "year"));
1125                        // Right side can be Integer or Number (parser produces Number)
1126                        match op.right {
1127                            Expr::Literal(Literal::Integer(n)) => assert_eq!(n, 2024),
1128                            Expr::Literal(Literal::Number(n)) => assert_eq!(n, dec!(2024)),
1129                            other => panic!("Expected numeric literal, got {other:?}"),
1130                        }
1131                    }
1132                    other => panic!("Expected BinaryOp, got {other:?}"),
1133                }
1134            }
1135            _ => panic!("Expected SELECT query"),
1136        }
1137    }
1138
1139    #[test]
1140    fn test_journal_query() {
1141        let query = parse("JOURNAL \"Assets:Bank\" AT cost").unwrap();
1142        match query {
1143            Query::Journal(j) => {
1144                assert_eq!(j.account_pattern, "Assets:Bank");
1145                assert_eq!(j.at_function, Some("cost".to_string()));
1146            }
1147            _ => panic!("Expected JOURNAL query"),
1148        }
1149    }
1150
1151    #[test]
1152    fn test_balances_query() {
1153        let query = parse("BALANCES AT units FROM year = 2024").unwrap();
1154        match query {
1155            Query::Balances(b) => {
1156                assert_eq!(b.at_function, Some("units".to_string()));
1157                assert!(b.from.is_some());
1158            }
1159            _ => panic!("Expected BALANCES query"),
1160        }
1161    }
1162
1163    #[test]
1164    fn test_print_query() {
1165        let query = parse("PRINT").unwrap();
1166        assert!(matches!(query, Query::Print(_)));
1167    }
1168
1169    #[test]
1170    fn test_complex_expression() {
1171        let query = parse("SELECT * WHERE date >= 2024-01-01 AND account ~ \"Expenses:\"").unwrap();
1172        match query {
1173            Query::Select(sel) => match sel.where_clause.unwrap() {
1174                Expr::BinaryOp(op) => {
1175                    assert_eq!(op.op, BinaryOperator::And);
1176                }
1177                _ => panic!("Expected AND"),
1178            },
1179            _ => panic!("Expected SELECT query"),
1180        }
1181    }
1182
1183    #[test]
1184    fn test_number_literal() {
1185        let query = parse("SELECT * WHERE year = 2024").unwrap();
1186        match query {
1187            Query::Select(sel) => match sel.where_clause.unwrap() {
1188                Expr::BinaryOp(op) => match op.right {
1189                    Expr::Literal(Literal::Number(n)) => {
1190                        assert_eq!(n, dec!(2024));
1191                    }
1192                    _ => panic!("Expected number literal"),
1193                },
1194                _ => panic!("Expected binary op"),
1195            },
1196            _ => panic!("Expected SELECT query"),
1197        }
1198    }
1199
1200    #[test]
1201    fn test_semicolon_optional() {
1202        assert!(parse("SELECT *").is_ok());
1203        assert!(parse("SELECT *;").is_ok());
1204    }
1205
1206    #[test]
1207    fn test_subquery_basic() {
1208        let query = parse("SELECT * FROM (SELECT account, position)").unwrap();
1209        match query {
1210            Query::Select(sel) => {
1211                assert!(sel.from.is_some());
1212                let from = sel.from.unwrap();
1213                assert!(from.subquery.is_some());
1214                let subquery = from.subquery.unwrap();
1215                assert_eq!(subquery.targets.len(), 2);
1216            }
1217            _ => panic!("Expected SELECT query"),
1218        }
1219    }
1220
1221    #[test]
1222    fn test_subquery_with_groupby() {
1223        let query = parse(
1224            "SELECT account, total FROM (SELECT account, SUM(position) AS total GROUP BY account)",
1225        )
1226        .unwrap();
1227        match query {
1228            Query::Select(sel) => {
1229                assert_eq!(sel.targets.len(), 2);
1230                let from = sel.from.unwrap();
1231                assert!(from.subquery.is_some());
1232                let subquery = from.subquery.unwrap();
1233                assert!(subquery.group_by.is_some());
1234            }
1235            _ => panic!("Expected SELECT query"),
1236        }
1237    }
1238
1239    #[test]
1240    fn test_subquery_with_outer_where() {
1241        let query =
1242            parse("SELECT * FROM (SELECT * WHERE year = 2024) WHERE account ~ \"Expenses:\"")
1243                .unwrap();
1244        match query {
1245            Query::Select(sel) => {
1246                // Outer WHERE
1247                assert!(sel.where_clause.is_some());
1248                // Subquery with its own WHERE
1249                let from = sel.from.unwrap();
1250                let subquery = from.subquery.unwrap();
1251                assert!(subquery.where_clause.is_some());
1252            }
1253            _ => panic!("Expected SELECT query"),
1254        }
1255    }
1256
1257    #[test]
1258    fn test_nested_subquery() {
1259        // Two levels of nesting
1260        let query = parse("SELECT * FROM (SELECT * FROM (SELECT account))").unwrap();
1261        match query {
1262            Query::Select(sel) => {
1263                let from = sel.from.unwrap();
1264                let subquery1 = from.subquery.unwrap();
1265                let from2 = subquery1.from.unwrap();
1266                assert!(from2.subquery.is_some());
1267            }
1268            _ => panic!("Expected SELECT query"),
1269        }
1270    }
1271
1272    #[test]
1273    fn test_nested_function_calls() {
1274        // Test units(sum(position)) pattern
1275        let query = parse("SELECT units(sum(position))").unwrap();
1276        match query {
1277            Query::Select(sel) => {
1278                assert_eq!(sel.targets.len(), 1);
1279                match &sel.targets[0].expr {
1280                    Expr::Function(outer) => {
1281                        assert_eq!(outer.name, "units");
1282                        assert_eq!(outer.args.len(), 1);
1283                        match &outer.args[0] {
1284                            Expr::Function(inner) => {
1285                                assert_eq!(inner.name, "sum");
1286                                assert_eq!(inner.args.len(), 1);
1287                                assert!(
1288                                    matches!(&inner.args[0], Expr::Column(c) if c == "position")
1289                                );
1290                            }
1291                            _ => panic!("Expected inner function call"),
1292                        }
1293                    }
1294                    _ => panic!("Expected outer function call"),
1295                }
1296            }
1297            _ => panic!("Expected SELECT query"),
1298        }
1299    }
1300
1301    #[test]
1302    fn test_deeply_nested_function_calls() {
1303        // Test three levels of nesting
1304        let query = parse("SELECT foo(bar(baz(x)))").unwrap();
1305        match query {
1306            Query::Select(sel) => {
1307                assert_eq!(sel.targets.len(), 1);
1308                match &sel.targets[0].expr {
1309                    Expr::Function(f1) => {
1310                        assert_eq!(f1.name, "foo");
1311                        match &f1.args[0] {
1312                            Expr::Function(f2) => {
1313                                assert_eq!(f2.name, "bar");
1314                                match &f2.args[0] {
1315                                    Expr::Function(f3) => {
1316                                        assert_eq!(f3.name, "baz");
1317                                        assert!(matches!(&f3.args[0], Expr::Column(c) if c == "x"));
1318                                    }
1319                                    _ => panic!("Expected f3"),
1320                                }
1321                            }
1322                            _ => panic!("Expected f2"),
1323                        }
1324                    }
1325                    _ => panic!("Expected f1"),
1326                }
1327            }
1328            _ => panic!("Expected SELECT query"),
1329        }
1330    }
1331
1332    #[test]
1333    fn test_function_with_arithmetic() {
1334        // Test function with arithmetic expression as argument
1335        let query = parse("SELECT sum(amount * 2)").unwrap();
1336        match query {
1337            Query::Select(sel) => match &sel.targets[0].expr {
1338                Expr::Function(f) => {
1339                    assert_eq!(f.name, "sum");
1340                    assert!(matches!(&f.args[0], Expr::BinaryOp(_)));
1341                }
1342                _ => panic!("Expected function"),
1343            },
1344            _ => panic!("Expected SELECT query"),
1345        }
1346    }
1347
1348    #[test]
1349    fn test_is_null() {
1350        let query = parse("SELECT * WHERE payee IS NULL").unwrap();
1351        match query {
1352            Query::Select(sel) => match sel.where_clause.unwrap() {
1353                Expr::UnaryOp(op) => {
1354                    assert_eq!(op.op, UnaryOperator::IsNull);
1355                    assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1356                }
1357                _ => panic!("Expected unary op"),
1358            },
1359            _ => panic!("Expected SELECT query"),
1360        }
1361    }
1362
1363    #[test]
1364    fn test_is_not_null() {
1365        let query = parse("SELECT * WHERE payee IS NOT NULL").unwrap();
1366        match query {
1367            Query::Select(sel) => match sel.where_clause.unwrap() {
1368                Expr::UnaryOp(op) => {
1369                    assert_eq!(op.op, UnaryOperator::IsNotNull);
1370                    assert!(matches!(&op.operand, Expr::Column(c) if c == "payee"));
1371                }
1372                _ => panic!("Expected unary op"),
1373            },
1374            _ => panic!("Expected SELECT query"),
1375        }
1376    }
1377
1378    #[test]
1379    fn test_not_regex() {
1380        let query = parse("SELECT * WHERE account !~ \"Assets:\"").unwrap();
1381        match query {
1382            Query::Select(sel) => match sel.where_clause.unwrap() {
1383                Expr::BinaryOp(op) => {
1384                    assert_eq!(op.op, BinaryOperator::NotRegex);
1385                }
1386                _ => panic!("Expected binary op"),
1387            },
1388            _ => panic!("Expected SELECT query"),
1389        }
1390    }
1391
1392    #[test]
1393    fn test_modulo() {
1394        let query = parse("SELECT year % 4").unwrap();
1395        match query {
1396            Query::Select(sel) => match &sel.targets[0].expr {
1397                Expr::BinaryOp(op) => {
1398                    assert_eq!(op.op, BinaryOperator::Mod);
1399                }
1400                _ => panic!("Expected binary op"),
1401            },
1402            _ => panic!("Expected SELECT query"),
1403        }
1404    }
1405
1406    #[test]
1407    fn test_between() {
1408        let query = parse("SELECT * WHERE year BETWEEN 2020 AND 2024").unwrap();
1409        match query {
1410            Query::Select(sel) => match sel.where_clause.unwrap() {
1411                Expr::Between { value, low, high } => {
1412                    assert!(matches!(*value, Expr::Column(c) if c == "year"));
1413                    assert!(matches!(*low, Expr::Literal(Literal::Number(_))));
1414                    assert!(matches!(*high, Expr::Literal(Literal::Number(_))));
1415                }
1416                _ => panic!("Expected BETWEEN"),
1417            },
1418            _ => panic!("Expected SELECT query"),
1419        }
1420    }
1421
1422    #[test]
1423    fn test_not_in() {
1424        let query = parse("SELECT * WHERE account NOT IN tags").unwrap();
1425        match query {
1426            Query::Select(sel) => match sel.where_clause.unwrap() {
1427                Expr::BinaryOp(op) => {
1428                    assert_eq!(op.op, BinaryOperator::NotIn);
1429                }
1430                _ => panic!("Expected binary op"),
1431            },
1432            _ => panic!("Expected SELECT query"),
1433        }
1434    }
1435
1436    #[test]
1437    fn test_in_set_literal() {
1438        // Multi-element set literal
1439        let query = parse("SELECT * WHERE currency IN ('EUR', 'USD')").unwrap();
1440        match query {
1441            Query::Select(sel) => match sel.where_clause.unwrap() {
1442                Expr::BinaryOp(op) => {
1443                    assert_eq!(op.op, BinaryOperator::In);
1444                    match op.right {
1445                        Expr::Set(elements) => {
1446                            assert_eq!(elements.len(), 2);
1447                        }
1448                        _ => panic!("Expected Set"),
1449                    }
1450                }
1451                _ => panic!("Expected binary op"),
1452            },
1453            _ => panic!("Expected SELECT query"),
1454        }
1455
1456        // Single-element set with trailing comma
1457        let query = parse("SELECT * WHERE currency IN ('EUR',)").unwrap();
1458        match query {
1459            Query::Select(sel) => match sel.where_clause.unwrap() {
1460                Expr::BinaryOp(op) => {
1461                    assert_eq!(op.op, BinaryOperator::In);
1462                    match op.right {
1463                        Expr::Set(elements) => {
1464                            assert_eq!(elements.len(), 1);
1465                        }
1466                        _ => panic!("Expected Set"),
1467                    }
1468                }
1469                _ => panic!("Expected binary op"),
1470            },
1471            _ => panic!("Expected SELECT query"),
1472        }
1473
1474        // Parenthesized column (not a set literal)
1475        let query = parse("SELECT * WHERE 'x' IN (tags)").unwrap();
1476        match query {
1477            Query::Select(sel) => match sel.where_clause.unwrap() {
1478                Expr::BinaryOp(op) => {
1479                    assert_eq!(op.op, BinaryOperator::In);
1480                    // Should be Paren(Column), not Set([Column])
1481                    match op.right {
1482                        Expr::Paren(inner) => match *inner {
1483                            Expr::Column(name) => assert_eq!(name, "tags"),
1484                            _ => panic!("Expected Column inside Paren"),
1485                        },
1486                        other => panic!("Expected Paren, got {other:?}"),
1487                    }
1488                }
1489                _ => panic!("Expected binary op"),
1490            },
1491            _ => panic!("Expected SELECT query"),
1492        }
1493    }
1494
1495    #[test]
1496    fn test_string_arg_function() {
1497        // First test a function with a column reference - should work
1498        let query = parse("SELECT foo(x)").unwrap();
1499        match query {
1500            Query::Select(sel) => match &sel.targets[0].expr {
1501                Expr::Function(f) => {
1502                    assert_eq!(f.name, "foo");
1503                }
1504                _ => panic!("Expected function"),
1505            },
1506            _ => panic!("Expected SELECT query"),
1507        }
1508
1509        // Now test a function with a string literal argument
1510        let query = parse("SELECT foo('bar')").unwrap();
1511        match query {
1512            Query::Select(sel) => match &sel.targets[0].expr {
1513                Expr::Function(f) => {
1514                    assert_eq!(f.name, "foo");
1515                    assert!(matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "bar"));
1516                }
1517                _ => panic!("Expected function"),
1518            },
1519            _ => panic!("Expected SELECT query"),
1520        }
1521    }
1522
1523    #[test]
1524    fn test_meta_function() {
1525        let query = parse("SELECT meta('category')").unwrap();
1526        match query {
1527            Query::Select(sel) => match &sel.targets[0].expr {
1528                Expr::Function(f) => {
1529                    assert_eq!(f.name.to_uppercase(), "META");
1530                    assert_eq!(f.args.len(), 1);
1531                    assert!(
1532                        matches!(&f.args[0], Expr::Literal(Literal::String(s)) if s == "category")
1533                    );
1534                }
1535                _ => panic!("Expected function"),
1536            },
1537            _ => panic!("Expected SELECT query"),
1538        }
1539    }
1540
1541    #[test]
1542    fn test_entry_meta_function() {
1543        let query = parse("SELECT entry_meta('source')").unwrap();
1544        match query {
1545            Query::Select(sel) => match &sel.targets[0].expr {
1546                Expr::Function(f) => {
1547                    assert_eq!(f.name.to_uppercase(), "ENTRY_META");
1548                    assert_eq!(f.args.len(), 1);
1549                }
1550                _ => panic!("Expected function"),
1551            },
1552            _ => panic!("Expected SELECT query"),
1553        }
1554    }
1555
1556    #[test]
1557    fn test_convert_function() {
1558        let query = parse("SELECT convert(position, 'USD')").unwrap();
1559        match query {
1560            Query::Select(sel) => match &sel.targets[0].expr {
1561                Expr::Function(f) => {
1562                    assert_eq!(f.name.to_uppercase(), "CONVERT");
1563                    assert_eq!(f.args.len(), 2);
1564                }
1565                _ => panic!("Expected function"),
1566            },
1567            _ => panic!("Expected SELECT query"),
1568        }
1569    }
1570
1571    #[test]
1572    fn test_type_cast_functions() {
1573        // Test INT
1574        let query = parse("SELECT int(number)").unwrap();
1575        match query {
1576            Query::Select(sel) => match &sel.targets[0].expr {
1577                Expr::Function(f) => {
1578                    assert_eq!(f.name.to_uppercase(), "INT");
1579                    assert_eq!(f.args.len(), 1);
1580                }
1581                _ => panic!("Expected function"),
1582            },
1583            _ => panic!("Expected SELECT query"),
1584        }
1585
1586        // Test DECIMAL
1587        let query = parse("SELECT decimal('123.45')").unwrap();
1588        match query {
1589            Query::Select(sel) => match &sel.targets[0].expr {
1590                Expr::Function(f) => {
1591                    assert_eq!(f.name.to_uppercase(), "DECIMAL");
1592                }
1593                _ => panic!("Expected function"),
1594            },
1595            _ => panic!("Expected SELECT query"),
1596        }
1597
1598        // Test STR
1599        let query = parse("SELECT str(123)").unwrap();
1600        match query {
1601            Query::Select(sel) => match &sel.targets[0].expr {
1602                Expr::Function(f) => {
1603                    assert_eq!(f.name.to_uppercase(), "STR");
1604                }
1605                _ => panic!("Expected function"),
1606            },
1607            _ => panic!("Expected SELECT query"),
1608        }
1609
1610        // Test BOOL
1611        let query = parse("SELECT bool(1)").unwrap();
1612        match query {
1613            Query::Select(sel) => match &sel.targets[0].expr {
1614                Expr::Function(f) => {
1615                    assert_eq!(f.name.to_uppercase(), "BOOL");
1616                }
1617                _ => panic!("Expected function"),
1618            },
1619            _ => panic!("Expected SELECT query"),
1620        }
1621    }
1622
1623    #[test]
1624    fn test_system_table_prices() {
1625        // Test parsing SELECT FROM #prices (system table)
1626        let query = parse("SELECT date, currency, amount FROM #prices").unwrap();
1627        match query {
1628            Query::Select(sel) => {
1629                assert_eq!(sel.targets.len(), 3);
1630                assert!(matches!(&sel.targets[0].expr, Expr::Column(c) if c == "date"));
1631                assert!(matches!(&sel.targets[1].expr, Expr::Column(c) if c == "currency"));
1632                assert!(matches!(&sel.targets[2].expr, Expr::Column(c) if c == "amount"));
1633                let from = sel.from.unwrap();
1634                assert_eq!(from.table_name, Some("#prices".to_string()));
1635            }
1636            _ => panic!("Expected SELECT query"),
1637        }
1638    }
1639
1640    #[test]
1641    fn test_system_table_with_where() {
1642        // Test parsing system table with WHERE clause
1643        let query = parse("SELECT * FROM #prices WHERE currency = 'EUR'").unwrap();
1644        match query {
1645            Query::Select(sel) => {
1646                let from = sel.from.unwrap();
1647                assert_eq!(from.table_name, Some("#prices".to_string()));
1648                assert!(sel.where_clause.is_some());
1649            }
1650            _ => panic!("Expected SELECT query"),
1651        }
1652    }
1653
1654    #[test]
1655    fn test_regular_table_identifier() {
1656        // Test parsing a regular (non-system) table
1657        let query = parse("SELECT * FROM MyTable WHERE x = 1").unwrap();
1658        match query {
1659            Query::Select(sel) => {
1660                let from = sel.from.unwrap();
1661                assert_eq!(from.table_name, Some("MyTable".to_string()));
1662            }
1663            _ => panic!("Expected SELECT query"),
1664        }
1665    }
1666}