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>,
106 pub close_on: Option<NaiveDate>,
111 pub clear: bool,
113 pub filter: Option<Expr>,
115 pub subquery: Option<Box<SelectQuery>>,
117 pub table_name: Option<String>,
119}
120
121#[derive(Debug, Clone, PartialEq)]
123pub struct OrderSpec {
124 pub expr: Expr,
126 pub direction: SortDirection,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
132pub enum SortDirection {
133 #[default]
135 Asc,
136 Desc,
138}
139
140#[derive(Debug, Clone, PartialEq)]
142pub struct JournalQuery {
143 pub account_pattern: String,
145 pub at_function: Option<String>,
147 pub from: Option<FromClause>,
149}
150
151#[derive(Debug, Clone, PartialEq)]
153pub struct BalancesQuery {
154 pub at_function: Option<String>,
156 pub from: Option<FromClause>,
158 pub where_clause: Option<Expr>,
160}
161
162#[derive(Debug, Clone, PartialEq)]
164pub struct PrintQuery {
165 pub from: Option<FromClause>,
167}
168
169#[derive(Debug, Clone, PartialEq)]
171pub enum Expr {
172 Wildcard,
174 Column(String),
176 Literal(Literal),
178 Function(FunctionCall),
180 Window(WindowFunction),
182 BinaryOp(Box<BinaryOp>),
184 UnaryOp(Box<UnaryOp>),
186 Paren(Box<Self>),
188 Between {
190 value: Box<Self>,
192 low: Box<Self>,
194 high: Box<Self>,
196 },
197 Set(Vec<Self>),
199}
200
201#[derive(Debug, Clone, PartialEq, Eq)]
203pub enum Literal {
204 String(String),
206 Number(Decimal),
208 Integer(i64),
210 Date(NaiveDate),
212 Boolean(bool),
214 Null,
216}
217
218#[derive(Debug, Clone, PartialEq)]
220pub struct FunctionCall {
221 pub name: String,
223 pub args: Vec<Expr>,
225}
226
227#[derive(Debug, Clone, PartialEq)]
229pub struct WindowFunction {
230 pub name: String,
232 pub args: Vec<Expr>,
234 pub over: WindowSpec,
236}
237
238#[derive(Debug, Clone, PartialEq, Default)]
240pub struct WindowSpec {
241 pub partition_by: Option<Vec<Expr>>,
243 pub order_by: Option<Vec<OrderSpec>>,
245}
246
247#[derive(Debug, Clone, PartialEq)]
249pub struct BinaryOp {
250 pub left: Expr,
252 pub op: BinaryOperator,
254 pub right: Expr,
256}
257
258#[derive(Debug, Clone, Copy, PartialEq, Eq)]
260pub enum BinaryOperator {
261 Eq,
264 Ne,
266 Lt,
268 Le,
270 Gt,
272 Ge,
274 Regex,
276 NotRegex,
278 In,
280 NotIn,
282
283 And,
286 Or,
288
289 Add,
292 Sub,
294 Mul,
296 Div,
298 Mod,
300}
301
302#[derive(Debug, Clone, PartialEq)]
304pub struct UnaryOp {
305 pub op: UnaryOperator,
307 pub operand: Expr,
309}
310
311#[derive(Debug, Clone, Copy, PartialEq, Eq)]
313pub enum UnaryOperator {
314 Not,
316 Neg,
318 IsNull,
320 IsNotNull,
322}
323
324impl SelectQuery {
325 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 #[must_use]
342 pub const fn distinct(mut self) -> Self {
343 self.distinct = true;
344 self
345 }
346
347 #[must_use]
349 pub fn from(mut self, from: FromClause) -> Self {
350 self.from = Some(from);
351 self
352 }
353
354 #[must_use]
356 pub fn where_clause(mut self, expr: Expr) -> Self {
357 self.where_clause = Some(expr);
358 self
359 }
360
361 #[must_use]
363 pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
364 self.group_by = Some(exprs);
365 self
366 }
367
368 #[must_use]
370 pub fn having(mut self, expr: Expr) -> Self {
371 self.having = Some(expr);
372 self
373 }
374
375 #[must_use]
377 pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
378 self.pivot_by = Some(exprs);
379 self
380 }
381
382 #[must_use]
384 pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
385 self.order_by = Some(specs);
386 self
387 }
388
389 #[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 pub const fn new(expr: Expr) -> Self {
400 Self { expr, alias: None }
401 }
402
403 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 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 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 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 pub const fn open_on(mut self, date: NaiveDate) -> Self {
451 self.open_on = Some(date);
452 self
453 }
454
455 pub const fn close_on(mut self, date: NaiveDate) -> Self {
457 self.close_on = Some(date);
458 self
459 }
460
461 pub const fn clear(mut self) -> Self {
463 self.clear = true;
464 self
465 }
466
467 pub fn filter(mut self, expr: Expr) -> Self {
469 self.filter = Some(expr);
470 self
471 }
472
473 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 pub fn column(name: impl Into<String>) -> Self {
489 Self::Column(name.into())
490 }
491
492 pub fn string(s: impl Into<String>) -> Self {
494 Self::Literal(Literal::String(s.into()))
495 }
496
497 pub const fn number(n: Decimal) -> Self {
499 Self::Literal(Literal::Number(n))
500 }
501
502 pub const fn integer(n: i64) -> Self {
504 Self::Literal(Literal::Integer(n))
505 }
506
507 pub const fn date(d: NaiveDate) -> Self {
509 Self::Literal(Literal::Date(d))
510 }
511
512 pub const fn boolean(b: bool) -> Self {
514 Self::Literal(Literal::Boolean(b))
515 }
516
517 pub const fn null() -> Self {
519 Self::Literal(Literal::Null)
520 }
521
522 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 pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
532 Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
533 }
534
535 pub fn unary(op: UnaryOperator, operand: Self) -> Self {
537 Self::UnaryOp(Box::new(UnaryOp { op, operand }))
538 }
539
540 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 pub const fn asc(expr: Expr) -> Self {
553 Self {
554 expr,
555 direction: SortDirection::Asc,
556 }
557 }
558
559 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 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 let single = Expr::Set(vec![Expr::string("EUR")]);
789 assert_eq!(single.to_string(), r#"("EUR")"#);
790
791 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 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}