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 pub where_clause: Option<Expr>,
155}
156
157#[derive(Debug, Clone, PartialEq)]
159pub struct PrintQuery {
160 pub from: Option<FromClause>,
162}
163
164#[derive(Debug, Clone, PartialEq)]
166pub enum Expr {
167 Wildcard,
169 Column(String),
171 Literal(Literal),
173 Function(FunctionCall),
175 Window(WindowFunction),
177 BinaryOp(Box<BinaryOp>),
179 UnaryOp(Box<UnaryOp>),
181 Paren(Box<Self>),
183 Between {
185 value: Box<Self>,
187 low: Box<Self>,
189 high: Box<Self>,
191 },
192 Set(Vec<Self>),
194}
195
196#[derive(Debug, Clone, PartialEq, Eq)]
198pub enum Literal {
199 String(String),
201 Number(Decimal),
203 Integer(i64),
205 Date(NaiveDate),
207 Boolean(bool),
209 Null,
211}
212
213#[derive(Debug, Clone, PartialEq)]
215pub struct FunctionCall {
216 pub name: String,
218 pub args: Vec<Expr>,
220}
221
222#[derive(Debug, Clone, PartialEq)]
224pub struct WindowFunction {
225 pub name: String,
227 pub args: Vec<Expr>,
229 pub over: WindowSpec,
231}
232
233#[derive(Debug, Clone, PartialEq, Default)]
235pub struct WindowSpec {
236 pub partition_by: Option<Vec<Expr>>,
238 pub order_by: Option<Vec<OrderSpec>>,
240}
241
242#[derive(Debug, Clone, PartialEq)]
244pub struct BinaryOp {
245 pub left: Expr,
247 pub op: BinaryOperator,
249 pub right: Expr,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255pub enum BinaryOperator {
256 Eq,
259 Ne,
261 Lt,
263 Le,
265 Gt,
267 Ge,
269 Regex,
271 NotRegex,
273 In,
275 NotIn,
277
278 And,
281 Or,
283
284 Add,
287 Sub,
289 Mul,
291 Div,
293 Mod,
295}
296
297#[derive(Debug, Clone, PartialEq)]
299pub struct UnaryOp {
300 pub op: UnaryOperator,
302 pub operand: Expr,
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
308pub enum UnaryOperator {
309 Not,
311 Neg,
313 IsNull,
315 IsNotNull,
317}
318
319impl SelectQuery {
320 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 #[must_use]
337 pub const fn distinct(mut self) -> Self {
338 self.distinct = true;
339 self
340 }
341
342 #[must_use]
344 pub fn from(mut self, from: FromClause) -> Self {
345 self.from = Some(from);
346 self
347 }
348
349 #[must_use]
351 pub fn where_clause(mut self, expr: Expr) -> Self {
352 self.where_clause = Some(expr);
353 self
354 }
355
356 #[must_use]
358 pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
359 self.group_by = Some(exprs);
360 self
361 }
362
363 #[must_use]
365 pub fn having(mut self, expr: Expr) -> Self {
366 self.having = Some(expr);
367 self
368 }
369
370 #[must_use]
372 pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
373 self.pivot_by = Some(exprs);
374 self
375 }
376
377 #[must_use]
379 pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
380 self.order_by = Some(specs);
381 self
382 }
383
384 #[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 pub const fn new(expr: Expr) -> Self {
395 Self { expr, alias: None }
396 }
397
398 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 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 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 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 pub const fn open_on(mut self, date: NaiveDate) -> Self {
446 self.open_on = Some(date);
447 self
448 }
449
450 pub const fn close_on(mut self, date: NaiveDate) -> Self {
452 self.close_on = Some(date);
453 self
454 }
455
456 pub const fn clear(mut self) -> Self {
458 self.clear = true;
459 self
460 }
461
462 pub fn filter(mut self, expr: Expr) -> Self {
464 self.filter = Some(expr);
465 self
466 }
467
468 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 pub fn column(name: impl Into<String>) -> Self {
484 Self::Column(name.into())
485 }
486
487 pub fn string(s: impl Into<String>) -> Self {
489 Self::Literal(Literal::String(s.into()))
490 }
491
492 pub const fn number(n: Decimal) -> Self {
494 Self::Literal(Literal::Number(n))
495 }
496
497 pub const fn integer(n: i64) -> Self {
499 Self::Literal(Literal::Integer(n))
500 }
501
502 pub const fn date(d: NaiveDate) -> Self {
504 Self::Literal(Literal::Date(d))
505 }
506
507 pub const fn boolean(b: bool) -> Self {
509 Self::Literal(Literal::Boolean(b))
510 }
511
512 pub const fn null() -> Self {
514 Self::Literal(Literal::Null)
515 }
516
517 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 pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
527 Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
528 }
529
530 pub fn unary(op: UnaryOperator, operand: Self) -> Self {
532 Self::UnaryOp(Box::new(UnaryOp { op, operand }))
533 }
534
535 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 pub const fn asc(expr: Expr) -> Self {
548 Self {
549 expr,
550 direction: SortDirection::Asc,
551 }
552 }
553
554 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 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 let single = Expr::Set(vec![Expr::string("EUR")]);
784 assert_eq!(single.to_string(), r#"("EUR")"#);
785
786 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 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}