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