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    #[must_use]
335    pub const fn distinct(mut self) -> Self {
336        self.distinct = true;
337        self
338    }
339
340    /// Set the FROM clause.
341    #[must_use]
342    pub fn from(mut self, from: FromClause) -> Self {
343        self.from = Some(from);
344        self
345    }
346
347    /// Set the WHERE clause.
348    #[must_use]
349    pub fn where_clause(mut self, expr: Expr) -> Self {
350        self.where_clause = Some(expr);
351        self
352    }
353
354    /// Set the GROUP BY clause.
355    #[must_use]
356    pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
357        self.group_by = Some(exprs);
358        self
359    }
360
361    /// Set the HAVING clause.
362    #[must_use]
363    pub fn having(mut self, expr: Expr) -> Self {
364        self.having = Some(expr);
365        self
366    }
367
368    /// Set the PIVOT BY clause.
369    #[must_use]
370    pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
371        self.pivot_by = Some(exprs);
372        self
373    }
374
375    /// Set the ORDER BY clause.
376    #[must_use]
377    pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
378        self.order_by = Some(specs);
379        self
380    }
381
382    /// Set the LIMIT.
383    #[must_use]
384    pub const fn limit(mut self, n: u64) -> Self {
385        self.limit = Some(n);
386        self
387    }
388}
389
390impl Target {
391    /// Create a new target from an expression.
392    pub const fn new(expr: Expr) -> Self {
393        Self { expr, alias: None }
394    }
395
396    /// Create a target with an alias.
397    pub fn with_alias(expr: Expr, alias: impl Into<String>) -> Self {
398        Self {
399            expr,
400            alias: Some(alias.into()),
401        }
402    }
403}
404
405impl FromClause {
406    /// Create a new empty FROM clause.
407    pub const fn new() -> Self {
408        Self {
409            open_on: None,
410            close_on: None,
411            clear: false,
412            filter: None,
413            subquery: None,
414            table_name: None,
415        }
416    }
417
418    /// Create a FROM clause from a subquery.
419    pub fn from_subquery(query: SelectQuery) -> Self {
420        Self {
421            open_on: None,
422            close_on: None,
423            clear: false,
424            filter: None,
425            subquery: Some(Box::new(query)),
426            table_name: None,
427        }
428    }
429
430    /// Create a FROM clause from a table name.
431    pub fn from_table(name: impl Into<String>) -> Self {
432        Self {
433            open_on: None,
434            close_on: None,
435            clear: false,
436            filter: None,
437            subquery: None,
438            table_name: Some(name.into()),
439        }
440    }
441
442    /// Set the OPEN ON date.
443    pub const fn open_on(mut self, date: NaiveDate) -> Self {
444        self.open_on = Some(date);
445        self
446    }
447
448    /// Set the CLOSE ON date.
449    pub const fn close_on(mut self, date: NaiveDate) -> Self {
450        self.close_on = Some(date);
451        self
452    }
453
454    /// Set the CLEAR flag.
455    pub const fn clear(mut self) -> Self {
456        self.clear = true;
457        self
458    }
459
460    /// Set the filter expression.
461    pub fn filter(mut self, expr: Expr) -> Self {
462        self.filter = Some(expr);
463        self
464    }
465
466    /// Set the subquery.
467    pub fn subquery(mut self, query: SelectQuery) -> Self {
468        self.subquery = Some(Box::new(query));
469        self
470    }
471}
472
473impl Default for FromClause {
474    fn default() -> Self {
475        Self::new()
476    }
477}
478
479impl Expr {
480    /// Create a column reference.
481    pub fn column(name: impl Into<String>) -> Self {
482        Self::Column(name.into())
483    }
484
485    /// Create a string literal.
486    pub fn string(s: impl Into<String>) -> Self {
487        Self::Literal(Literal::String(s.into()))
488    }
489
490    /// Create a number literal.
491    pub const fn number(n: Decimal) -> Self {
492        Self::Literal(Literal::Number(n))
493    }
494
495    /// Create an integer literal.
496    pub const fn integer(n: i64) -> Self {
497        Self::Literal(Literal::Integer(n))
498    }
499
500    /// Create a date literal.
501    pub const fn date(d: NaiveDate) -> Self {
502        Self::Literal(Literal::Date(d))
503    }
504
505    /// Create a boolean literal.
506    pub const fn boolean(b: bool) -> Self {
507        Self::Literal(Literal::Boolean(b))
508    }
509
510    /// Create a NULL literal.
511    pub const fn null() -> Self {
512        Self::Literal(Literal::Null)
513    }
514
515    /// Create a function call.
516    pub fn function(name: impl Into<String>, args: Vec<Self>) -> Self {
517        Self::Function(FunctionCall {
518            name: name.into(),
519            args,
520        })
521    }
522
523    /// Create a binary operation.
524    pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
525        Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
526    }
527
528    /// Create a unary operation.
529    pub fn unary(op: UnaryOperator, operand: Self) -> Self {
530        Self::UnaryOp(Box::new(UnaryOp { op, operand }))
531    }
532
533    /// Create a BETWEEN ... AND expression.
534    pub fn between(value: Self, low: Self, high: Self) -> Self {
535        Self::Between {
536            value: Box::new(value),
537            low: Box::new(low),
538            high: Box::new(high),
539        }
540    }
541}
542
543impl OrderSpec {
544    /// Create an ascending order spec.
545    pub const fn asc(expr: Expr) -> Self {
546        Self {
547            expr,
548            direction: SortDirection::Asc,
549        }
550    }
551
552    /// Create a descending order spec.
553    pub const fn desc(expr: Expr) -> Self {
554        Self {
555            expr,
556            direction: SortDirection::Desc,
557        }
558    }
559}
560
561impl fmt::Display for Expr {
562    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563        match self {
564            Self::Wildcard => write!(f, "*"),
565            Self::Column(name) => write!(f, "{name}"),
566            Self::Literal(lit) => write!(f, "{lit}"),
567            Self::Function(func) => {
568                write!(f, "{}(", func.name)?;
569                for (i, arg) in func.args.iter().enumerate() {
570                    if i > 0 {
571                        write!(f, ", ")?;
572                    }
573                    write!(f, "{arg}")?;
574                }
575                write!(f, ")")
576            }
577            Self::Window(wf) => {
578                write!(f, "{}(", wf.name)?;
579                for (i, arg) in wf.args.iter().enumerate() {
580                    if i > 0 {
581                        write!(f, ", ")?;
582                    }
583                    write!(f, "{arg}")?;
584                }
585                write!(f, ") OVER ()")
586            }
587            Self::BinaryOp(op) => write!(f, "({} {} {})", op.left, op.op, op.right),
588            Self::UnaryOp(op) => {
589                // IS NULL and IS NOT NULL are postfix operators
590                match op.op {
591                    UnaryOperator::IsNull => write!(f, "{} IS NULL", op.operand),
592                    UnaryOperator::IsNotNull => write!(f, "{} IS NOT NULL", op.operand),
593                    _ => write!(f, "{}{}", op.op, op.operand),
594                }
595            }
596            Self::Paren(inner) => write!(f, "({inner})"),
597            Self::Between { value, low, high } => {
598                write!(f, "{value} BETWEEN {low} AND {high}")
599            }
600            Self::Set(elements) => {
601                write!(f, "(")?;
602                for (i, elem) in elements.iter().enumerate() {
603                    if i > 0 {
604                        write!(f, ", ")?;
605                    }
606                    write!(f, "{elem}")?;
607                }
608                write!(f, ")")
609            }
610        }
611    }
612}
613
614impl fmt::Display for Literal {
615    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616        match self {
617            Self::String(s) => write!(f, "\"{s}\""),
618            Self::Number(n) => write!(f, "{n}"),
619            Self::Integer(n) => write!(f, "{n}"),
620            Self::Date(d) => write!(f, "{d}"),
621            Self::Boolean(b) => write!(f, "{b}"),
622            Self::Null => write!(f, "NULL"),
623        }
624    }
625}
626
627impl fmt::Display for BinaryOperator {
628    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
629        let s = match self {
630            Self::Eq => "=",
631            Self::Ne => "!=",
632            Self::Lt => "<",
633            Self::Le => "<=",
634            Self::Gt => ">",
635            Self::Ge => ">=",
636            Self::Regex => "~",
637            Self::NotRegex => "!~",
638            Self::In => "IN",
639            Self::NotIn => "NOT IN",
640            Self::And => "AND",
641            Self::Or => "OR",
642            Self::Add => "+",
643            Self::Sub => "-",
644            Self::Mul => "*",
645            Self::Div => "/",
646            Self::Mod => "%",
647        };
648        write!(f, "{s}")
649    }
650}
651
652impl fmt::Display for UnaryOperator {
653    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
654        let s = match self {
655            Self::Not => "NOT ",
656            Self::Neg => "-",
657            Self::IsNull => " IS NULL",
658            Self::IsNotNull => " IS NOT NULL",
659        };
660        write!(f, "{s}")
661    }
662}
663
664#[cfg(test)]
665mod tests {
666    use super::*;
667    use rust_decimal_macros::dec;
668
669    #[test]
670    fn test_expr_display_wildcard() {
671        assert_eq!(Expr::Wildcard.to_string(), "*");
672    }
673
674    #[test]
675    fn test_expr_display_column() {
676        assert_eq!(Expr::Column("account".to_string()).to_string(), "account");
677    }
678
679    #[test]
680    fn test_expr_display_literals() {
681        assert_eq!(Expr::string("hello").to_string(), "\"hello\"");
682        assert_eq!(Expr::integer(42).to_string(), "42");
683        assert_eq!(Expr::number(dec!(3.14)).to_string(), "3.14");
684        assert_eq!(Expr::boolean(true).to_string(), "true");
685        assert_eq!(Expr::null().to_string(), "NULL");
686    }
687
688    #[test]
689    fn test_expr_display_date() {
690        let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
691        assert_eq!(Expr::date(date).to_string(), "2024-01-15");
692    }
693
694    #[test]
695    fn test_expr_display_function_no_args() {
696        let func = Expr::function("now", vec![]);
697        assert_eq!(func.to_string(), "now()");
698    }
699
700    #[test]
701    fn test_expr_display_function_one_arg() {
702        let func = Expr::function("account_sortkey", vec![Expr::column("account")]);
703        assert_eq!(func.to_string(), "account_sortkey(account)");
704    }
705
706    #[test]
707    fn test_expr_display_function_multiple_args() {
708        let func = Expr::function(
709            "coalesce",
710            vec![Expr::column("a"), Expr::column("b"), Expr::integer(0)],
711        );
712        assert_eq!(func.to_string(), "coalesce(a, b, 0)");
713    }
714
715    #[test]
716    fn test_expr_display_window() {
717        let wf = Expr::Window(WindowFunction {
718            name: "row_number".to_string(),
719            args: vec![],
720            over: WindowSpec::default(),
721        });
722        assert_eq!(wf.to_string(), "row_number() OVER ()");
723    }
724
725    #[test]
726    fn test_expr_display_window_with_args() {
727        let wf = Expr::Window(WindowFunction {
728            name: "sum".to_string(),
729            args: vec![Expr::column("amount")],
730            over: WindowSpec::default(),
731        });
732        assert_eq!(wf.to_string(), "sum(amount) OVER ()");
733    }
734
735    #[test]
736    fn test_expr_display_binary_op() {
737        let expr = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::integer(1));
738        assert_eq!(expr.to_string(), "(a + 1)");
739    }
740
741    #[test]
742    fn test_expr_display_unary_not() {
743        let expr = Expr::unary(UnaryOperator::Not, Expr::column("flag"));
744        assert_eq!(expr.to_string(), "NOT flag");
745    }
746
747    #[test]
748    fn test_expr_display_unary_neg() {
749        let expr = Expr::unary(UnaryOperator::Neg, Expr::column("x"));
750        assert_eq!(expr.to_string(), "-x");
751    }
752
753    #[test]
754    fn test_expr_display_is_null() {
755        let expr = Expr::unary(UnaryOperator::IsNull, Expr::column("x"));
756        assert_eq!(expr.to_string(), "x IS NULL");
757    }
758
759    #[test]
760    fn test_expr_display_is_not_null() {
761        let expr = Expr::unary(UnaryOperator::IsNotNull, Expr::column("x"));
762        assert_eq!(expr.to_string(), "x IS NOT NULL");
763    }
764
765    #[test]
766    fn test_expr_display_paren() {
767        let inner = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::column("b"));
768        let expr = Expr::Paren(Box::new(inner));
769        assert_eq!(expr.to_string(), "((a + b))");
770    }
771
772    #[test]
773    fn test_expr_display_between() {
774        let expr = Expr::between(Expr::column("x"), Expr::integer(1), Expr::integer(10));
775        assert_eq!(expr.to_string(), "x BETWEEN 1 AND 10");
776    }
777
778    #[test]
779    fn test_expr_display_set() {
780        // Empty set is not valid in parsing, but test single element
781        let single = Expr::Set(vec![Expr::string("EUR")]);
782        assert_eq!(single.to_string(), r#"("EUR")"#);
783
784        // Multiple elements
785        let multi = Expr::Set(vec![
786            Expr::string("EUR"),
787            Expr::string("USD"),
788            Expr::string("GBP"),
789        ]);
790        assert_eq!(multi.to_string(), r#"("EUR", "USD", "GBP")"#);
791
792        // Mixed types (integers)
793        let numeric = Expr::Set(vec![Expr::integer(2023), Expr::integer(2024)]);
794        assert_eq!(numeric.to_string(), "(2023, 2024)");
795    }
796
797    #[test]
798    fn test_binary_operator_display() {
799        assert_eq!(BinaryOperator::Eq.to_string(), "=");
800        assert_eq!(BinaryOperator::Ne.to_string(), "!=");
801        assert_eq!(BinaryOperator::Lt.to_string(), "<");
802        assert_eq!(BinaryOperator::Le.to_string(), "<=");
803        assert_eq!(BinaryOperator::Gt.to_string(), ">");
804        assert_eq!(BinaryOperator::Ge.to_string(), ">=");
805        assert_eq!(BinaryOperator::Regex.to_string(), "~");
806        assert_eq!(BinaryOperator::NotRegex.to_string(), "!~");
807        assert_eq!(BinaryOperator::In.to_string(), "IN");
808        assert_eq!(BinaryOperator::NotIn.to_string(), "NOT IN");
809        assert_eq!(BinaryOperator::And.to_string(), "AND");
810        assert_eq!(BinaryOperator::Or.to_string(), "OR");
811        assert_eq!(BinaryOperator::Add.to_string(), "+");
812        assert_eq!(BinaryOperator::Sub.to_string(), "-");
813        assert_eq!(BinaryOperator::Mul.to_string(), "*");
814        assert_eq!(BinaryOperator::Div.to_string(), "/");
815        assert_eq!(BinaryOperator::Mod.to_string(), "%");
816    }
817
818    #[test]
819    fn test_unary_operator_display() {
820        assert_eq!(UnaryOperator::Not.to_string(), "NOT ");
821        assert_eq!(UnaryOperator::Neg.to_string(), "-");
822        assert_eq!(UnaryOperator::IsNull.to_string(), " IS NULL");
823        assert_eq!(UnaryOperator::IsNotNull.to_string(), " IS NOT NULL");
824    }
825
826    #[test]
827    fn test_literal_display() {
828        assert_eq!(Literal::String("test".to_string()).to_string(), "\"test\"");
829        assert_eq!(Literal::Number(dec!(1.5)).to_string(), "1.5");
830        assert_eq!(Literal::Integer(42).to_string(), "42");
831        assert_eq!(Literal::Boolean(false).to_string(), "false");
832        assert_eq!(Literal::Null.to_string(), "NULL");
833    }
834}