1use rust_decimal::Decimal;
7use rustledger_core::NaiveDate;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum Query {
13 Select(Box<SelectQuery>),
15 Journal(JournalQuery),
17 Balances(BalancesQuery),
19 Print(PrintQuery),
21 CreateTable(CreateTableStmt),
23 Insert(InsertStmt),
25}
26
27#[derive(Debug, Clone, PartialEq, Eq)]
29pub struct ColumnDef {
30 pub name: String,
32 pub type_hint: Option<String>,
34}
35
36#[derive(Debug, Clone, PartialEq)]
38pub struct CreateTableStmt {
39 pub table_name: String,
41 pub columns: Vec<ColumnDef>,
43 pub as_select: Option<Box<SelectQuery>>,
45}
46
47#[derive(Debug, Clone, PartialEq)]
49pub struct InsertStmt {
50 pub table_name: String,
52 pub columns: Option<Vec<String>>,
54 pub source: InsertSource,
56}
57
58#[derive(Debug, Clone, PartialEq)]
60pub enum InsertSource {
61 Values(Vec<Vec<Expr>>),
63 Select(Box<SelectQuery>),
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct SelectQuery {
70 pub distinct: bool,
72 pub targets: Vec<Target>,
74 pub from: Option<FromClause>,
76 pub where_clause: Option<Expr>,
78 pub group_by: Option<Vec<Expr>>,
80 pub having: Option<Expr>,
82 pub pivot_by: Option<Vec<Expr>>,
84 pub order_by: Option<Vec<OrderSpec>>,
86 pub limit: Option<u64>,
88}
89
90#[derive(Debug, Clone, PartialEq)]
92pub struct Target {
93 pub expr: Expr,
95 pub alias: Option<String>,
97}
98
99#[derive(Debug, Clone, PartialEq)]
101pub struct FromClause {
102 pub open_on: Option<NaiveDate>,
104 pub close_on: Option<NaiveDate>,
106 pub clear: bool,
108 pub filter: Option<Expr>,
110 pub subquery: Option<Box<SelectQuery>>,
112 pub table_name: Option<String>,
114}
115
116#[derive(Debug, Clone, PartialEq)]
118pub struct OrderSpec {
119 pub expr: Expr,
121 pub direction: SortDirection,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
127pub enum SortDirection {
128 #[default]
130 Asc,
131 Desc,
133}
134
135#[derive(Debug, Clone, PartialEq)]
137pub struct JournalQuery {
138 pub account_pattern: String,
140 pub at_function: Option<String>,
142 pub from: Option<FromClause>,
144}
145
146#[derive(Debug, Clone, PartialEq)]
148pub struct BalancesQuery {
149 pub at_function: Option<String>,
151 pub from: Option<FromClause>,
153}
154
155#[derive(Debug, Clone, PartialEq)]
157pub struct PrintQuery {
158 pub from: Option<FromClause>,
160}
161
162#[derive(Debug, Clone, PartialEq)]
164pub enum Expr {
165 Wildcard,
167 Column(String),
169 Literal(Literal),
171 Function(FunctionCall),
173 Window(WindowFunction),
175 BinaryOp(Box<BinaryOp>),
177 UnaryOp(Box<UnaryOp>),
179 Paren(Box<Self>),
181 Between {
183 value: Box<Self>,
185 low: Box<Self>,
187 high: Box<Self>,
189 },
190 Set(Vec<Self>),
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum Literal {
197 String(String),
199 Number(Decimal),
201 Integer(i64),
203 Date(NaiveDate),
205 Boolean(bool),
207 Null,
209}
210
211#[derive(Debug, Clone, PartialEq)]
213pub struct FunctionCall {
214 pub name: String,
216 pub args: Vec<Expr>,
218}
219
220#[derive(Debug, Clone, PartialEq)]
222pub struct WindowFunction {
223 pub name: String,
225 pub args: Vec<Expr>,
227 pub over: WindowSpec,
229}
230
231#[derive(Debug, Clone, PartialEq, Default)]
233pub struct WindowSpec {
234 pub partition_by: Option<Vec<Expr>>,
236 pub order_by: Option<Vec<OrderSpec>>,
238}
239
240#[derive(Debug, Clone, PartialEq)]
242pub struct BinaryOp {
243 pub left: Expr,
245 pub op: BinaryOperator,
247 pub right: Expr,
249}
250
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
253pub enum BinaryOperator {
254 Eq,
257 Ne,
259 Lt,
261 Le,
263 Gt,
265 Ge,
267 Regex,
269 NotRegex,
271 In,
273 NotIn,
275
276 And,
279 Or,
281
282 Add,
285 Sub,
287 Mul,
289 Div,
291 Mod,
293}
294
295#[derive(Debug, Clone, PartialEq)]
297pub struct UnaryOp {
298 pub op: UnaryOperator,
300 pub operand: Expr,
302}
303
304#[derive(Debug, Clone, Copy, PartialEq, Eq)]
306pub enum UnaryOperator {
307 Not,
309 Neg,
311 IsNull,
313 IsNotNull,
315}
316
317impl SelectQuery {
318 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 pub const fn distinct(mut self) -> Self {
335 self.distinct = true;
336 self
337 }
338
339 pub fn from(mut self, from: FromClause) -> Self {
341 self.from = Some(from);
342 self
343 }
344
345 pub fn where_clause(mut self, expr: Expr) -> Self {
347 self.where_clause = Some(expr);
348 self
349 }
350
351 pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
353 self.group_by = Some(exprs);
354 self
355 }
356
357 pub fn having(mut self, expr: Expr) -> Self {
359 self.having = Some(expr);
360 self
361 }
362
363 pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
365 self.pivot_by = Some(exprs);
366 self
367 }
368
369 pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
371 self.order_by = Some(specs);
372 self
373 }
374
375 pub const fn limit(mut self, n: u64) -> Self {
377 self.limit = Some(n);
378 self
379 }
380}
381
382impl Target {
383 pub const fn new(expr: Expr) -> Self {
385 Self { expr, alias: None }
386 }
387
388 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 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 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 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 pub const fn open_on(mut self, date: NaiveDate) -> Self {
436 self.open_on = Some(date);
437 self
438 }
439
440 pub const fn close_on(mut self, date: NaiveDate) -> Self {
442 self.close_on = Some(date);
443 self
444 }
445
446 pub const fn clear(mut self) -> Self {
448 self.clear = true;
449 self
450 }
451
452 pub fn filter(mut self, expr: Expr) -> Self {
454 self.filter = Some(expr);
455 self
456 }
457
458 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 pub fn column(name: impl Into<String>) -> Self {
474 Self::Column(name.into())
475 }
476
477 pub fn string(s: impl Into<String>) -> Self {
479 Self::Literal(Literal::String(s.into()))
480 }
481
482 pub const fn number(n: Decimal) -> Self {
484 Self::Literal(Literal::Number(n))
485 }
486
487 pub const fn integer(n: i64) -> Self {
489 Self::Literal(Literal::Integer(n))
490 }
491
492 pub const fn date(d: NaiveDate) -> Self {
494 Self::Literal(Literal::Date(d))
495 }
496
497 pub const fn boolean(b: bool) -> Self {
499 Self::Literal(Literal::Boolean(b))
500 }
501
502 pub const fn null() -> Self {
504 Self::Literal(Literal::Null)
505 }
506
507 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 pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
517 Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
518 }
519
520 pub fn unary(op: UnaryOperator, operand: Self) -> Self {
522 Self::UnaryOp(Box::new(UnaryOp { op, operand }))
523 }
524
525 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 pub const fn asc(expr: Expr) -> Self {
538 Self {
539 expr,
540 direction: SortDirection::Asc,
541 }
542 }
543
544 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 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 let single = Expr::Set(vec![Expr::string("EUR")]);
774 assert_eq!(single.to_string(), r#"("EUR")"#);
775
776 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 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}