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