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}
191
192#[derive(Debug, Clone, PartialEq, Eq)]
194pub enum Literal {
195 String(String),
197 Number(Decimal),
199 Integer(i64),
201 Date(NaiveDate),
203 Boolean(bool),
205 Null,
207}
208
209#[derive(Debug, Clone, PartialEq)]
211pub struct FunctionCall {
212 pub name: String,
214 pub args: Vec<Expr>,
216}
217
218#[derive(Debug, Clone, PartialEq)]
220pub struct WindowFunction {
221 pub name: String,
223 pub args: Vec<Expr>,
225 pub over: WindowSpec,
227}
228
229#[derive(Debug, Clone, PartialEq, Default)]
231pub struct WindowSpec {
232 pub partition_by: Option<Vec<Expr>>,
234 pub order_by: Option<Vec<OrderSpec>>,
236}
237
238#[derive(Debug, Clone, PartialEq)]
240pub struct BinaryOp {
241 pub left: Expr,
243 pub op: BinaryOperator,
245 pub right: Expr,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq)]
251pub enum BinaryOperator {
252 Eq,
255 Ne,
257 Lt,
259 Le,
261 Gt,
263 Ge,
265 Regex,
267 NotRegex,
269 In,
271 NotIn,
273
274 And,
277 Or,
279
280 Add,
283 Sub,
285 Mul,
287 Div,
289 Mod,
291}
292
293#[derive(Debug, Clone, PartialEq)]
295pub struct UnaryOp {
296 pub op: UnaryOperator,
298 pub operand: Expr,
300}
301
302#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum UnaryOperator {
305 Not,
307 Neg,
309 IsNull,
311 IsNotNull,
313}
314
315impl SelectQuery {
316 pub const fn new(targets: Vec<Target>) -> Self {
318 Self {
319 distinct: false,
320 targets,
321 from: None,
322 where_clause: None,
323 group_by: None,
324 having: None,
325 pivot_by: None,
326 order_by: None,
327 limit: None,
328 }
329 }
330
331 pub const fn distinct(mut self) -> Self {
333 self.distinct = true;
334 self
335 }
336
337 pub fn from(mut self, from: FromClause) -> Self {
339 self.from = Some(from);
340 self
341 }
342
343 pub fn where_clause(mut self, expr: Expr) -> Self {
345 self.where_clause = Some(expr);
346 self
347 }
348
349 pub fn group_by(mut self, exprs: Vec<Expr>) -> Self {
351 self.group_by = Some(exprs);
352 self
353 }
354
355 pub fn having(mut self, expr: Expr) -> Self {
357 self.having = Some(expr);
358 self
359 }
360
361 pub fn pivot_by(mut self, exprs: Vec<Expr>) -> Self {
363 self.pivot_by = Some(exprs);
364 self
365 }
366
367 pub fn order_by(mut self, specs: Vec<OrderSpec>) -> Self {
369 self.order_by = Some(specs);
370 self
371 }
372
373 pub const fn limit(mut self, n: u64) -> Self {
375 self.limit = Some(n);
376 self
377 }
378}
379
380impl Target {
381 pub const fn new(expr: Expr) -> Self {
383 Self { expr, alias: None }
384 }
385
386 pub fn with_alias(expr: Expr, alias: impl Into<String>) -> Self {
388 Self {
389 expr,
390 alias: Some(alias.into()),
391 }
392 }
393}
394
395impl FromClause {
396 pub const fn new() -> Self {
398 Self {
399 open_on: None,
400 close_on: None,
401 clear: false,
402 filter: None,
403 subquery: None,
404 table_name: None,
405 }
406 }
407
408 pub fn from_subquery(query: SelectQuery) -> Self {
410 Self {
411 open_on: None,
412 close_on: None,
413 clear: false,
414 filter: None,
415 subquery: Some(Box::new(query)),
416 table_name: None,
417 }
418 }
419
420 pub fn from_table(name: impl Into<String>) -> Self {
422 Self {
423 open_on: None,
424 close_on: None,
425 clear: false,
426 filter: None,
427 subquery: None,
428 table_name: Some(name.into()),
429 }
430 }
431
432 pub const fn open_on(mut self, date: NaiveDate) -> Self {
434 self.open_on = Some(date);
435 self
436 }
437
438 pub const fn close_on(mut self, date: NaiveDate) -> Self {
440 self.close_on = Some(date);
441 self
442 }
443
444 pub const fn clear(mut self) -> Self {
446 self.clear = true;
447 self
448 }
449
450 pub fn filter(mut self, expr: Expr) -> Self {
452 self.filter = Some(expr);
453 self
454 }
455
456 pub fn subquery(mut self, query: SelectQuery) -> Self {
458 self.subquery = Some(Box::new(query));
459 self
460 }
461}
462
463impl Default for FromClause {
464 fn default() -> Self {
465 Self::new()
466 }
467}
468
469impl Expr {
470 pub fn column(name: impl Into<String>) -> Self {
472 Self::Column(name.into())
473 }
474
475 pub fn string(s: impl Into<String>) -> Self {
477 Self::Literal(Literal::String(s.into()))
478 }
479
480 pub const fn number(n: Decimal) -> Self {
482 Self::Literal(Literal::Number(n))
483 }
484
485 pub const fn integer(n: i64) -> Self {
487 Self::Literal(Literal::Integer(n))
488 }
489
490 pub const fn date(d: NaiveDate) -> Self {
492 Self::Literal(Literal::Date(d))
493 }
494
495 pub const fn boolean(b: bool) -> Self {
497 Self::Literal(Literal::Boolean(b))
498 }
499
500 pub const fn null() -> Self {
502 Self::Literal(Literal::Null)
503 }
504
505 pub fn function(name: impl Into<String>, args: Vec<Self>) -> Self {
507 Self::Function(FunctionCall {
508 name: name.into(),
509 args,
510 })
511 }
512
513 pub fn binary(left: Self, op: BinaryOperator, right: Self) -> Self {
515 Self::BinaryOp(Box::new(BinaryOp { left, op, right }))
516 }
517
518 pub fn unary(op: UnaryOperator, operand: Self) -> Self {
520 Self::UnaryOp(Box::new(UnaryOp { op, operand }))
521 }
522
523 pub fn between(value: Self, low: Self, high: Self) -> Self {
525 Self::Between {
526 value: Box::new(value),
527 low: Box::new(low),
528 high: Box::new(high),
529 }
530 }
531}
532
533impl OrderSpec {
534 pub const fn asc(expr: Expr) -> Self {
536 Self {
537 expr,
538 direction: SortDirection::Asc,
539 }
540 }
541
542 pub const fn desc(expr: Expr) -> Self {
544 Self {
545 expr,
546 direction: SortDirection::Desc,
547 }
548 }
549}
550
551impl fmt::Display for Expr {
552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553 match self {
554 Self::Wildcard => write!(f, "*"),
555 Self::Column(name) => write!(f, "{name}"),
556 Self::Literal(lit) => write!(f, "{lit}"),
557 Self::Function(func) => {
558 write!(f, "{}(", func.name)?;
559 for (i, arg) in func.args.iter().enumerate() {
560 if i > 0 {
561 write!(f, ", ")?;
562 }
563 write!(f, "{arg}")?;
564 }
565 write!(f, ")")
566 }
567 Self::Window(wf) => {
568 write!(f, "{}(", wf.name)?;
569 for (i, arg) in wf.args.iter().enumerate() {
570 if i > 0 {
571 write!(f, ", ")?;
572 }
573 write!(f, "{arg}")?;
574 }
575 write!(f, ") OVER ()")
576 }
577 Self::BinaryOp(op) => write!(f, "({} {} {})", op.left, op.op, op.right),
578 Self::UnaryOp(op) => {
579 match op.op {
581 UnaryOperator::IsNull => write!(f, "{} IS NULL", op.operand),
582 UnaryOperator::IsNotNull => write!(f, "{} IS NOT NULL", op.operand),
583 _ => write!(f, "{}{}", op.op, op.operand),
584 }
585 }
586 Self::Paren(inner) => write!(f, "({inner})"),
587 Self::Between { value, low, high } => {
588 write!(f, "{value} BETWEEN {low} AND {high}")
589 }
590 }
591 }
592}
593
594impl fmt::Display for Literal {
595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596 match self {
597 Self::String(s) => write!(f, "\"{s}\""),
598 Self::Number(n) => write!(f, "{n}"),
599 Self::Integer(n) => write!(f, "{n}"),
600 Self::Date(d) => write!(f, "{d}"),
601 Self::Boolean(b) => write!(f, "{b}"),
602 Self::Null => write!(f, "NULL"),
603 }
604 }
605}
606
607impl fmt::Display for BinaryOperator {
608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609 let s = match self {
610 Self::Eq => "=",
611 Self::Ne => "!=",
612 Self::Lt => "<",
613 Self::Le => "<=",
614 Self::Gt => ">",
615 Self::Ge => ">=",
616 Self::Regex => "~",
617 Self::NotRegex => "!~",
618 Self::In => "IN",
619 Self::NotIn => "NOT IN",
620 Self::And => "AND",
621 Self::Or => "OR",
622 Self::Add => "+",
623 Self::Sub => "-",
624 Self::Mul => "*",
625 Self::Div => "/",
626 Self::Mod => "%",
627 };
628 write!(f, "{s}")
629 }
630}
631
632impl fmt::Display for UnaryOperator {
633 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
634 let s = match self {
635 Self::Not => "NOT ",
636 Self::Neg => "-",
637 Self::IsNull => " IS NULL",
638 Self::IsNotNull => " IS NOT NULL",
639 };
640 write!(f, "{s}")
641 }
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647 use rust_decimal_macros::dec;
648
649 #[test]
650 fn test_expr_display_wildcard() {
651 assert_eq!(Expr::Wildcard.to_string(), "*");
652 }
653
654 #[test]
655 fn test_expr_display_column() {
656 assert_eq!(Expr::Column("account".to_string()).to_string(), "account");
657 }
658
659 #[test]
660 fn test_expr_display_literals() {
661 assert_eq!(Expr::string("hello").to_string(), "\"hello\"");
662 assert_eq!(Expr::integer(42).to_string(), "42");
663 assert_eq!(Expr::number(dec!(3.14)).to_string(), "3.14");
664 assert_eq!(Expr::boolean(true).to_string(), "true");
665 assert_eq!(Expr::null().to_string(), "NULL");
666 }
667
668 #[test]
669 fn test_expr_display_date() {
670 let date = NaiveDate::from_ymd_opt(2024, 1, 15).unwrap();
671 assert_eq!(Expr::date(date).to_string(), "2024-01-15");
672 }
673
674 #[test]
675 fn test_expr_display_function_no_args() {
676 let func = Expr::function("now", vec![]);
677 assert_eq!(func.to_string(), "now()");
678 }
679
680 #[test]
681 fn test_expr_display_function_one_arg() {
682 let func = Expr::function("account_sortkey", vec![Expr::column("account")]);
683 assert_eq!(func.to_string(), "account_sortkey(account)");
684 }
685
686 #[test]
687 fn test_expr_display_function_multiple_args() {
688 let func = Expr::function(
689 "coalesce",
690 vec![Expr::column("a"), Expr::column("b"), Expr::integer(0)],
691 );
692 assert_eq!(func.to_string(), "coalesce(a, b, 0)");
693 }
694
695 #[test]
696 fn test_expr_display_window() {
697 let wf = Expr::Window(WindowFunction {
698 name: "row_number".to_string(),
699 args: vec![],
700 over: WindowSpec::default(),
701 });
702 assert_eq!(wf.to_string(), "row_number() OVER ()");
703 }
704
705 #[test]
706 fn test_expr_display_window_with_args() {
707 let wf = Expr::Window(WindowFunction {
708 name: "sum".to_string(),
709 args: vec![Expr::column("amount")],
710 over: WindowSpec::default(),
711 });
712 assert_eq!(wf.to_string(), "sum(amount) OVER ()");
713 }
714
715 #[test]
716 fn test_expr_display_binary_op() {
717 let expr = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::integer(1));
718 assert_eq!(expr.to_string(), "(a + 1)");
719 }
720
721 #[test]
722 fn test_expr_display_unary_not() {
723 let expr = Expr::unary(UnaryOperator::Not, Expr::column("flag"));
724 assert_eq!(expr.to_string(), "NOT flag");
725 }
726
727 #[test]
728 fn test_expr_display_unary_neg() {
729 let expr = Expr::unary(UnaryOperator::Neg, Expr::column("x"));
730 assert_eq!(expr.to_string(), "-x");
731 }
732
733 #[test]
734 fn test_expr_display_is_null() {
735 let expr = Expr::unary(UnaryOperator::IsNull, Expr::column("x"));
736 assert_eq!(expr.to_string(), "x IS NULL");
737 }
738
739 #[test]
740 fn test_expr_display_is_not_null() {
741 let expr = Expr::unary(UnaryOperator::IsNotNull, Expr::column("x"));
742 assert_eq!(expr.to_string(), "x IS NOT NULL");
743 }
744
745 #[test]
746 fn test_expr_display_paren() {
747 let inner = Expr::binary(Expr::column("a"), BinaryOperator::Add, Expr::column("b"));
748 let expr = Expr::Paren(Box::new(inner));
749 assert_eq!(expr.to_string(), "((a + b))");
750 }
751
752 #[test]
753 fn test_expr_display_between() {
754 let expr = Expr::between(Expr::column("x"), Expr::integer(1), Expr::integer(10));
755 assert_eq!(expr.to_string(), "x BETWEEN 1 AND 10");
756 }
757
758 #[test]
759 fn test_binary_operator_display() {
760 assert_eq!(BinaryOperator::Eq.to_string(), "=");
761 assert_eq!(BinaryOperator::Ne.to_string(), "!=");
762 assert_eq!(BinaryOperator::Lt.to_string(), "<");
763 assert_eq!(BinaryOperator::Le.to_string(), "<=");
764 assert_eq!(BinaryOperator::Gt.to_string(), ">");
765 assert_eq!(BinaryOperator::Ge.to_string(), ">=");
766 assert_eq!(BinaryOperator::Regex.to_string(), "~");
767 assert_eq!(BinaryOperator::NotRegex.to_string(), "!~");
768 assert_eq!(BinaryOperator::In.to_string(), "IN");
769 assert_eq!(BinaryOperator::NotIn.to_string(), "NOT IN");
770 assert_eq!(BinaryOperator::And.to_string(), "AND");
771 assert_eq!(BinaryOperator::Or.to_string(), "OR");
772 assert_eq!(BinaryOperator::Add.to_string(), "+");
773 assert_eq!(BinaryOperator::Sub.to_string(), "-");
774 assert_eq!(BinaryOperator::Mul.to_string(), "*");
775 assert_eq!(BinaryOperator::Div.to_string(), "/");
776 assert_eq!(BinaryOperator::Mod.to_string(), "%");
777 }
778
779 #[test]
780 fn test_unary_operator_display() {
781 assert_eq!(UnaryOperator::Not.to_string(), "NOT ");
782 assert_eq!(UnaryOperator::Neg.to_string(), "-");
783 assert_eq!(UnaryOperator::IsNull.to_string(), " IS NULL");
784 assert_eq!(UnaryOperator::IsNotNull.to_string(), " IS NOT NULL");
785 }
786
787 #[test]
788 fn test_literal_display() {
789 assert_eq!(Literal::String("test".to_string()).to_string(), "\"test\"");
790 assert_eq!(Literal::Number(dec!(1.5)).to_string(), "1.5");
791 assert_eq!(Literal::Integer(42).to_string(), "42");
792 assert_eq!(Literal::Boolean(false).to_string(), "false");
793 assert_eq!(Literal::Null.to_string(), "NULL");
794 }
795}