Skip to main content

rustledger_query/
ast.rs

1//! BQL Abstract Syntax Tree types.
2//!
3//! This module defines the AST for Beancount Query Language (BQL),
4//! a SQL-like query language for financial data analysis.
5
6use rust_decimal::Decimal;
7use rustledger_core::NaiveDate;
8use std::fmt;
9
10/// A complete BQL query.
11#[derive(Debug, Clone, PartialEq)]
12pub enum Query {
13    /// SELECT query (boxed to reduce enum size).
14    Select(Box<SelectQuery>),
15    /// JOURNAL shorthand query.
16    Journal(JournalQuery),
17    /// BALANCES shorthand query.
18    Balances(BalancesQuery),
19    /// PRINT shorthand query.
20    Print(PrintQuery),
21    /// CREATE TABLE statement.
22    CreateTable(CreateTableStmt),
23    /// INSERT statement.
24    Insert(InsertStmt),
25}
26
27/// Column definition for CREATE TABLE.
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ColumnDef {
30    /// Column name.
31    pub name: String,
32    /// Optional type hint (BQL is dynamically typed, but hints are allowed).
33    pub type_hint: Option<String>,
34}
35
36/// CREATE TABLE statement.
37#[derive(Debug, Clone, PartialEq)]
38pub struct CreateTableStmt {
39    /// Table name.
40    pub table_name: String,
41    /// Column definitions.
42    pub columns: Vec<ColumnDef>,
43    /// Optional AS SELECT (create from query).
44    pub as_select: Option<Box<SelectQuery>>,
45}
46
47/// INSERT statement.
48#[derive(Debug, Clone, PartialEq)]
49pub struct InsertStmt {
50    /// Target table name.
51    pub table_name: String,
52    /// Optional column list (if omitted, uses all columns in order).
53    pub columns: Option<Vec<String>>,
54    /// Source data: either VALUES or SELECT.
55    pub source: InsertSource,
56}
57
58/// Source data for INSERT.
59#[derive(Debug, Clone, PartialEq)]
60pub enum InsertSource {
61    /// VALUES clause with literal rows.
62    Values(Vec<Vec<Expr>>),
63    /// SELECT query as source.
64    Select(Box<SelectQuery>),
65}
66
67/// A SELECT query.
68#[derive(Debug, Clone, PartialEq)]
69pub struct SelectQuery {
70    /// Whether DISTINCT was specified.
71    pub distinct: bool,
72    /// Target columns/expressions.
73    pub targets: Vec<Target>,
74    /// FROM clause (transaction-level filtering).
75    pub from: Option<FromClause>,
76    /// WHERE clause (posting-level filtering).
77    pub where_clause: Option<Expr>,
78    /// GROUP BY clause.
79    pub group_by: Option<Vec<Expr>>,
80    /// HAVING clause (filter on aggregated results).
81    pub having: Option<Expr>,
82    /// PIVOT BY clause (pivot table transformation).
83    pub pivot_by: Option<Vec<Expr>>,
84    /// ORDER BY clause.
85    pub order_by: Option<Vec<OrderSpec>>,
86    /// LIMIT clause.
87    pub limit: Option<u64>,
88}
89
90/// A target in the SELECT clause.
91#[derive(Debug, Clone, PartialEq)]
92pub struct Target {
93    /// The expression to select.
94    pub expr: Expr,
95    /// Optional alias (AS name).
96    pub alias: Option<String>,
97}
98
99/// FROM clause with transaction-level modifiers.
100#[derive(Debug, Clone, PartialEq)]
101pub struct FromClause {
102    /// OPEN ON date - summarize entries before this date.
103    pub open_on: Option<NaiveDate>,
104    /// CLOSE ON date - truncate entries after this date.
105    pub close_on: Option<NaiveDate>,
106    /// CLEAR - transfer income/expense to equity.
107    pub clear: bool,
108    /// Filter expression.
109    pub filter: Option<Expr>,
110    /// Subquery (derived table).
111    pub subquery: Option<Box<SelectQuery>>,
112    /// Table name (for querying user-created tables).
113    pub table_name: Option<String>,
114}
115
116/// ORDER BY specification.
117#[derive(Debug, Clone, PartialEq)]
118pub struct OrderSpec {
119    /// Expression to order by.
120    pub expr: Expr,
121    /// Sort direction.
122    pub direction: SortDirection,
123}
124
125/// Sort direction.
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
127pub enum SortDirection {
128    /// Ascending (default).
129    #[default]
130    Asc,
131    /// Descending.
132    Desc,
133}
134
135/// JOURNAL shorthand query.
136#[derive(Debug, Clone, PartialEq)]
137pub struct JournalQuery {
138    /// Account pattern to filter by.
139    pub account_pattern: String,
140    /// Optional aggregation function (AT cost, AT units, etc.).
141    pub at_function: Option<String>,
142    /// Optional FROM clause.
143    pub from: Option<FromClause>,
144}
145
146/// BALANCES shorthand query.
147#[derive(Debug, Clone, PartialEq)]
148pub struct BalancesQuery {
149    /// Optional aggregation function.
150    pub at_function: Option<String>,
151    /// Optional FROM clause.
152    pub from: Option<FromClause>,
153}
154
155/// PRINT shorthand query.
156#[derive(Debug, Clone, PartialEq)]
157pub struct PrintQuery {
158    /// Optional FROM clause.
159    pub from: Option<FromClause>,
160}
161
162/// An expression in BQL.
163#[derive(Debug, Clone, PartialEq)]
164pub enum Expr {
165    /// Wildcard (*).
166    Wildcard,
167    /// Column reference.
168    Column(String),
169    /// Literal value.
170    Literal(Literal),
171    /// Function call.
172    Function(FunctionCall),
173    /// Window function call (with OVER clause).
174    Window(WindowFunction),
175    /// Binary operation.
176    BinaryOp(Box<BinaryOp>),
177    /// Unary operation.
178    UnaryOp(Box<UnaryOp>),
179    /// Parenthesized expression.
180    Paren(Box<Self>),
181    /// BETWEEN ... AND expression.
182    Between {
183        /// Value to test.
184        value: Box<Self>,
185        /// Lower bound.
186        low: Box<Self>,
187        /// Upper bound.
188        high: Box<Self>,
189    },
190    /// Set literal for IN operator, e.g., `('EUR', 'USD')`.
191    Set(Vec<Self>),
192}
193
194/// A literal value.
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum Literal {
197    /// String literal.
198    String(String),
199    /// Numeric literal.
200    Number(Decimal),
201    /// Integer literal.
202    Integer(i64),
203    /// Date literal.
204    Date(NaiveDate),
205    /// Boolean literal.
206    Boolean(bool),
207    /// NULL literal.
208    Null,
209}
210
211/// A function call.
212#[derive(Debug, Clone, PartialEq)]
213pub struct FunctionCall {
214    /// Function name.
215    pub name: String,
216    /// Arguments.
217    pub args: Vec<Expr>,
218}
219
220/// A window function call (function with OVER clause).
221#[derive(Debug, Clone, PartialEq)]
222pub struct WindowFunction {
223    /// Function name (`ROW_NUMBER`, RANK, SUM, etc.).
224    pub name: String,
225    /// Function arguments.
226    pub args: Vec<Expr>,
227    /// Window specification.
228    pub over: WindowSpec,
229}
230
231/// Window specification for OVER clause.
232#[derive(Debug, Clone, PartialEq, Default)]
233pub struct WindowSpec {
234    /// PARTITION BY expressions.
235    pub partition_by: Option<Vec<Expr>>,
236    /// ORDER BY specifications.
237    pub order_by: Option<Vec<OrderSpec>>,
238}
239
240/// A binary operation.
241#[derive(Debug, Clone, PartialEq)]
242pub struct BinaryOp {
243    /// Left operand.
244    pub left: Expr,
245    /// Operator.
246    pub op: BinaryOperator,
247    /// Right operand.
248    pub right: Expr,
249}
250
251/// Binary operators.
252#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum BinaryOperator {
254    // Comparison
255    /// Equal (=).
256    Eq,
257    /// Not equal (!=).
258    Ne,
259    /// Less than (<).
260    Lt,
261    /// Less than or equal (<=).
262    Le,
263    /// Greater than (>).
264    Gt,
265    /// Greater than or equal (>=).
266    Ge,
267    /// Regular expression match (~).
268    Regex,
269    /// Regular expression not match (!~).
270    NotRegex,
271    /// IN operator.
272    In,
273    /// NOT IN operator.
274    NotIn,
275
276    // Logical
277    /// Logical AND.
278    And,
279    /// Logical OR.
280    Or,
281
282    // Arithmetic
283    /// Addition (+).
284    Add,
285    /// Subtraction (-).
286    Sub,
287    /// Multiplication (*).
288    Mul,
289    /// Division (/).
290    Div,
291    /// Modulo (%).
292    Mod,
293}
294
295/// A unary operation.
296#[derive(Debug, Clone, PartialEq)]
297pub struct UnaryOp {
298    /// Operator.
299    pub op: UnaryOperator,
300    /// Operand.
301    pub operand: Expr,
302}
303
304/// Unary operators.
305#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum UnaryOperator {
307    /// Logical NOT.
308    Not,
309    /// Negation (-).
310    Neg,
311    /// IS NULL.
312    IsNull,
313    /// IS NOT NULL.
314    IsNotNull,
315}
316
317impl SelectQuery {
318    /// Create a new SELECT query with the given targets.
319    pub const fn new(targets: Vec<Target>) -> Self {
320        Self {
321            distinct: false,
322            targets,
323            from: None,
324            where_clause: None,
325            group_by: None,
326            having: None,
327            pivot_by: None,
328            order_by: None,
329            limit: None,
330        }
331    }
332
333    /// Set the DISTINCT flag.
334    pub const fn distinct(mut self) -> Self {
335        self.distinct = true;
336        self
337    }
338
339    /// Set the FROM clause.
340    pub fn from(mut self, from: FromClause) -> Self {
341        self.from = Some(from);
342        self
343    }
344
345    /// Set the WHERE clause.
346    pub fn where_clause(mut self, expr: Expr) -> Self {
347        self.where_clause = Some(expr);
348        self
349    }
350
351    /// Set the GROUP BY clause.
352    pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
353        self.group_by = Some(exprs);
354        self
355    }
356
357    /// Set the HAVING clause.
358    pub fn having(mut self, expr: Expr) -> Self {
359        self.having = Some(expr);
360        self
361    }
362
363    /// Set the PIVOT BY clause.
364    pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
365        self.pivot_by = Some(exprs);
366        self
367    }
368
369    /// Set the ORDER BY clause.
370    pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
371        self.order_by = Some(specs);
372        self
373    }
374
375    /// Set the LIMIT.
376    pub const fn limit(mut self, n: u64) -> Self {
377        self.limit = Some(n);
378        self
379    }
380}
381
382impl Target {
383    /// Create a new target from an expression.
384    pub const fn new(expr: Expr) -> Self {
385        Self { expr, alias: None }
386    }
387
388    /// Create a target with an alias.
389    pub fn with_alias(expr: Expr, alias: impl Into<String>) -> Self {
390        Self {
391            expr,
392            alias: Some(alias.into()),
393        }
394    }
395}
396
397impl FromClause {
398    /// Create a new empty FROM clause.
399    pub const fn new() -> Self {
400        Self {
401            open_on: None,
402            close_on: None,
403            clear: false,
404            filter: None,
405            subquery: None,
406            table_name: None,
407        }
408    }
409
410    /// Create a FROM clause from a subquery.
411    pub fn from_subquery(query: SelectQuery) -> Self {
412        Self {
413            open_on: None,
414            close_on: None,
415            clear: false,
416            filter: None,
417            subquery: Some(Box::new(query)),
418            table_name: None,
419        }
420    }
421
422    /// Create a FROM clause from a table name.
423    pub fn from_table(name: impl Into<String>) -> Self {
424        Self {
425            open_on: None,
426            close_on: None,
427            clear: false,
428            filter: None,
429            subquery: None,
430            table_name: Some(name.into()),
431        }
432    }
433
434    /// Set the OPEN ON date.
435    pub const fn open_on(mut self, date: NaiveDate) -> Self {
436        self.open_on = Some(date);
437        self
438    }
439
440    /// Set the CLOSE ON date.
441    pub const fn close_on(mut self, date: NaiveDate) -> Self {
442        self.close_on = Some(date);
443        self
444    }
445
446    /// Set the CLEAR flag.
447    pub const fn clear(mut self) -> Self {
448        self.clear = true;
449        self
450    }
451
452    /// Set the filter expression.
453    pub fn filter(mut self, expr: Expr) -> Self {
454        self.filter = Some(expr);
455        self
456    }
457
458    /// Set the subquery.
459    pub fn subquery(mut self, query: SelectQuery) -> Self {
460        self.subquery = Some(Box::new(query));
461        self
462    }
463}
464
465impl Default for FromClause {
466    fn default() -> Self {
467        Self::new()
468    }
469}
470
471impl Expr {
472    /// Create a column reference.
473    pub fn column(name: impl Into<String>) -> Self {
474        Self::Column(name.into())
475    }
476
477    /// Create a string literal.
478    pub fn string(s: impl Into<String>) -> Self {
479        Self::Literal(Literal::String(s.into()))
480    }
481
482    /// Create a number literal.
483    pub const fn number(n: Decimal) -> Self {
484        Self::Literal(Literal::Number(n))
485    }
486
487    /// Create an integer literal.
488    pub const fn integer(n: i64) -> Self {
489        Self::Literal(Literal::Integer(n))
490    }
491
492    /// Create a date literal.
493    pub const fn date(d: NaiveDate) -> Self {
494        Self::Literal(Literal::Date(d))
495    }
496
497    /// Create a boolean literal.
498    pub const fn boolean(b: bool) -> Self {
499        Self::Literal(Literal::Boolean(b))
500    }
501
502    /// Create a NULL literal.
503    pub const fn null() -> Self {
504        Self::Literal(Literal::Null)
505    }
506
507    /// Create a function call.
508    pub fn function(name: impl Into<String>, args: Vec<Self>) -> Self {
509        Self::Function(FunctionCall {
510            name: name.into(),
511            args,
512        })
513    }
514
515    /// Create a binary operation.
516    pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
517        Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
518    }
519
520    /// Create a unary operation.
521    pub fn unary(op: UnaryOperator, operand: Self) -> Self {
522        Self::UnaryOp(Box::new(UnaryOp { op, operand }))
523    }
524
525    /// Create a BETWEEN ... AND expression.
526    pub fn between(value: Self, low: Self, high: Self) -> Self {
527        Self::Between {
528            value: Box::new(value),
529            low: Box::new(low),
530            high: Box::new(high),
531        }
532    }
533}
534
535impl OrderSpec {
536    /// Create an ascending order spec.
537    pub const fn asc(expr: Expr) -> Self {
538        Self {
539            expr,
540            direction: SortDirection::Asc,
541        }
542    }
543
544    /// Create a descending order spec.
545    pub const fn desc(expr: Expr) -> Self {
546        Self {
547            expr,
548            direction: SortDirection::Desc,
549        }
550    }
551}
552
553impl fmt::Display for Expr {
554    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
555        match self {
556            Self::Wildcard => write!(f, "*"),
557            Self::Column(name) => write!(f, "{name}"),
558            Self::Literal(lit) => write!(f, "{lit}"),
559            Self::Function(func) => {
560                write!(f, "{}(", func.name)?;
561                for (i, arg) in func.args.iter().enumerate() {
562                    if i > 0 {
563                        write!(f, ", ")?;
564                    }
565                    write!(f, "{arg}")?;
566                }
567                write!(f, ")")
568            }
569            Self::Window(wf) => {
570                write!(f, "{}(", wf.name)?;
571                for (i, arg) in wf.args.iter().enumerate() {
572                    if i > 0 {
573                        write!(f, ", ")?;
574                    }
575                    write!(f, "{arg}")?;
576                }
577                write!(f, ") OVER ()")
578            }
579            Self::BinaryOp(op) => write!(f, "({} {} {})", op.left, op.op, op.right),
580            Self::UnaryOp(op) => {
581                // IS NULL and IS NOT NULL are postfix operators
582                match op.op {
583                    UnaryOperator::IsNull => write!(f, "{} IS NULL", op.operand),
584                    UnaryOperator::IsNotNull => write!(f, "{} IS NOT NULL", op.operand),
585                    _ => write!(f, "{}{}", op.op, op.operand),
586                }
587            }
588            Self::Paren(inner) => write!(f, "({inner})"),
589            Self::Between { value, low, high } => {
590                write!(f, "{value} BETWEEN {low} AND {high}")
591            }
592            Self::Set(elements) => {
593                write!(f, "(")?;
594                for (i, elem) in elements.iter().enumerate() {
595                    if i > 0 {
596                        write!(f, ", ")?;
597                    }
598                    write!(f, "{elem}")?;
599                }
600                write!(f, ")")
601            }
602        }
603    }
604}
605
606impl fmt::Display for Literal {
607    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
608        match self {
609            Self::String(s) => write!(f, "\"{s}\""),
610            Self::Number(n) => write!(f, "{n}"),
611            Self::Integer(n) => write!(f, "{n}"),
612            Self::Date(d) => write!(f, "{d}"),
613            Self::Boolean(b) => write!(f, "{b}"),
614            Self::Null => write!(f, "NULL"),
615        }
616    }
617}
618
619impl fmt::Display for BinaryOperator {
620    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
621        let s = match self {
622            Self::Eq => "=",
623            Self::Ne => "!=",
624            Self::Lt => "<",
625            Self::Le => "<=",
626            Self::Gt => ">",
627            Self::Ge => ">=",
628            Self::Regex => "~",
629            Self::NotRegex => "!~",
630            Self::In => "IN",
631            Self::NotIn => "NOT IN",
632            Self::And => "AND",
633            Self::Or => "OR",
634            Self::Add => "+",
635            Self::Sub => "-",
636            Self::Mul => "*",
637            Self::Div => "/",
638            Self::Mod => "%",
639        };
640        write!(f, "{s}")
641    }
642}
643
644impl fmt::Display for UnaryOperator {
645    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
646        let s = match self {
647            Self::Not => "NOT ",
648            Self::Neg => "-",
649            Self::IsNull => " IS NULL",
650            Self::IsNotNull => " IS NOT NULL",
651        };
652        write!(f, "{s}")
653    }
654}
655
656#[cfg(test)]
657mod tests {
658    use super::*;
659    use rust_decimal_macros::dec;
660
661    #[test]
662    fn test_expr_display_wildcard() {
663        assert_eq!(Expr::Wildcard.to_string(), "*");
664    }
665
666    #[test]
667    fn test_expr_display_column() {
668        assert_eq!(Expr::Column("account".to_string()).to_string(), "account");
669    }
670
671    #[test]
672    fn test_expr_display_literals() {
673        assert_eq!(Expr::string("hello").to_string(), "\"hello\"");
674        assert_eq!(Expr::integer(42).to_string(), "42");
675        assert_eq!(Expr::number(dec!(3.14)).to_string(), "3.14");
676        assert_eq!(Expr::boolean(true).to_string(), "true");
677        assert_eq!(Expr::null().to_string(), "NULL");
678    }
679
680    #[test]
681    fn test_expr_display_date() {
682        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
683        assert_eq!(Expr::date(date).to_string(), "2024-01-15");
684    }
685
686    #[test]
687    fn test_expr_display_function_no_args() {
688        let func = Expr::function("now", vec![]);
689        assert_eq!(func.to_string(), "now()");
690    }
691
692    #[test]
693    fn test_expr_display_function_one_arg() {
694        let func = Expr::function("account_sortkey", vec![Expr::column("account")]);
695        assert_eq!(func.to_string(), "account_sortkey(account)");
696    }
697
698    #[test]
699    fn test_expr_display_function_multiple_args() {
700        let func = Expr::function(
701            "coalesce",
702            vec![Expr::column("a"), Expr::column("b"), Expr::integer(0)],
703        );
704        assert_eq!(func.to_string(), "coalesce(a, b, 0)");
705    }
706
707    #[test]
708    fn test_expr_display_window() {
709        let wf = Expr::Window(WindowFunction {
710            name: "row_number".to_string(),
711            args: vec![],
712            over: WindowSpec::default(),
713        });
714        assert_eq!(wf.to_string(), "row_number() OVER ()");
715    }
716
717    #[test]
718    fn test_expr_display_window_with_args() {
719        let wf = Expr::Window(WindowFunction {
720            name: "sum".to_string(),
721            args: vec![Expr::column("amount")],
722            over: WindowSpec::default(),
723        });
724        assert_eq!(wf.to_string(), "sum(amount) OVER ()");
725    }
726
727    #[test]
728    fn test_expr_display_binary_op() {
729        let expr = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::integer(1));
730        assert_eq!(expr.to_string(), "(a + 1)");
731    }
732
733    #[test]
734    fn test_expr_display_unary_not() {
735        let expr = Expr::unary(UnaryOperator::Not, Expr::column("flag"));
736        assert_eq!(expr.to_string(), "NOT flag");
737    }
738
739    #[test]
740    fn test_expr_display_unary_neg() {
741        let expr = Expr::unary(UnaryOperator::Neg, Expr::column("x"));
742        assert_eq!(expr.to_string(), "-x");
743    }
744
745    #[test]
746    fn test_expr_display_is_null() {
747        let expr = Expr::unary(UnaryOperator::IsNull, Expr::column("x"));
748        assert_eq!(expr.to_string(), "x IS NULL");
749    }
750
751    #[test]
752    fn test_expr_display_is_not_null() {
753        let expr = Expr::unary(UnaryOperator::IsNotNull, Expr::column("x"));
754        assert_eq!(expr.to_string(), "x IS NOT NULL");
755    }
756
757    #[test]
758    fn test_expr_display_paren() {
759        let inner = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::column("b"));
760        let expr = Expr::Paren(Box::new(inner));
761        assert_eq!(expr.to_string(), "((a + b))");
762    }
763
764    #[test]
765    fn test_expr_display_between() {
766        let expr = Expr::between(Expr::column("x"), Expr::integer(1), Expr::integer(10));
767        assert_eq!(expr.to_string(), "x BETWEEN 1 AND 10");
768    }
769
770    #[test]
771    fn test_expr_display_set() {
772        // Empty set is not valid in parsing, but test single element
773        let single = Expr::Set(vec![Expr::string("EUR")]);
774        assert_eq!(single.to_string(), r#"("EUR")"#);
775
776        // Multiple elements
777        let multi = Expr::Set(vec![
778            Expr::string("EUR"),
779            Expr::string("USD"),
780            Expr::string("GBP"),
781        ]);
782        assert_eq!(multi.to_string(), r#"("EUR", "USD", "GBP")"#);
783
784        // Mixed types (integers)
785        let numeric = Expr::Set(vec![Expr::integer(2023), Expr::integer(2024)]);
786        assert_eq!(numeric.to_string(), "(2023, 2024)");
787    }
788
789    #[test]
790    fn test_binary_operator_display() {
791        assert_eq!(BinaryOperator::Eq.to_string(), "=");
792        assert_eq!(BinaryOperator::Ne.to_string(), "!=");
793        assert_eq!(BinaryOperator::Lt.to_string(), "<");
794        assert_eq!(BinaryOperator::Le.to_string(), "<=");
795        assert_eq!(BinaryOperator::Gt.to_string(), ">");
796        assert_eq!(BinaryOperator::Ge.to_string(), ">=");
797        assert_eq!(BinaryOperator::Regex.to_string(), "~");
798        assert_eq!(BinaryOperator::NotRegex.to_string(), "!~");
799        assert_eq!(BinaryOperator::In.to_string(), "IN");
800        assert_eq!(BinaryOperator::NotIn.to_string(), "NOT IN");
801        assert_eq!(BinaryOperator::And.to_string(), "AND");
802        assert_eq!(BinaryOperator::Or.to_string(), "OR");
803        assert_eq!(BinaryOperator::Add.to_string(), "+");
804        assert_eq!(BinaryOperator::Sub.to_string(), "-");
805        assert_eq!(BinaryOperator::Mul.to_string(), "*");
806        assert_eq!(BinaryOperator::Div.to_string(), "/");
807        assert_eq!(BinaryOperator::Mod.to_string(), "%");
808    }
809
810    #[test]
811    fn test_unary_operator_display() {
812        assert_eq!(UnaryOperator::Not.to_string(), "NOT ");
813        assert_eq!(UnaryOperator::Neg.to_string(), "-");
814        assert_eq!(UnaryOperator::IsNull.to_string(), " IS NULL");
815        assert_eq!(UnaryOperator::IsNotNull.to_string(), " IS NOT NULL");
816    }
817
818    #[test]
819    fn test_literal_display() {
820        assert_eq!(Literal::String("test".to_string()).to_string(), "\"test\"");
821        assert_eq!(Literal::Number(dec!(1.5)).to_string(), "1.5");
822        assert_eq!(Literal::Integer(42).to_string(), "42");
823        assert_eq!(Literal::Boolean(false).to_string(), "false");
824        assert_eq!(Literal::Null.to_string(), "NULL");
825    }
826}