1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct QailCmd {
11 pub action: Action,
13 pub table: String,
15 pub columns: Vec<Column>,
17 #[serde(default)]
19 pub joins: Vec<Join>,
20 pub cages: Vec<Cage>,
22 #[serde(default)]
24 pub distinct: bool,
25 #[serde(default)]
27 pub index_def: Option<IndexDef>,
28 #[serde(default)]
30 pub table_constraints: Vec<TableConstraint>,
31 #[serde(default)]
33 pub set_ops: Vec<(SetOp, Box<QailCmd>)>,
34 #[serde(default)]
36 pub having: Vec<Condition>,
37 #[serde(default)]
39 pub group_by_mode: GroupByMode,
40 #[serde(default)]
42 pub ctes: Vec<CTEDef>,
43 #[serde(default)]
45 pub distinct_on: Vec<String>,
46}
47
48impl QailCmd {
49 pub fn get(table: impl Into<String>) -> Self {
51 Self {
52 action: Action::Get,
53 table: table.into(),
54 joins: vec![],
55 columns: vec![],
56 cages: vec![],
57 distinct: false,
58 index_def: None,
59 table_constraints: vec![],
60 set_ops: vec![],
61 having: vec![],
62 group_by_mode: GroupByMode::Simple,
63 ctes: vec![],
64 distinct_on: vec![],
65 }
66 }
67
68 pub fn set(table: impl Into<String>) -> Self {
70 Self {
71 action: Action::Set,
72 table: table.into(),
73 joins: vec![],
74 columns: vec![],
75 cages: vec![],
76 distinct: false,
77 index_def: None,
78 table_constraints: vec![],
79 set_ops: vec![],
80 having: vec![],
81 group_by_mode: GroupByMode::Simple,
82 ctes: vec![],
83 distinct_on: vec![],
84 }
85 }
86
87 pub fn del(table: impl Into<String>) -> Self {
89 Self {
90 action: Action::Del,
91 table: table.into(),
92 joins: vec![],
93 columns: vec![],
94 cages: vec![],
95 distinct: false,
96 index_def: None,
97 table_constraints: vec![],
98 set_ops: vec![],
99 having: vec![],
100 group_by_mode: GroupByMode::Simple,
101 ctes: vec![],
102 distinct_on: vec![],
103 }
104 }
105
106 pub fn add(table: impl Into<String>) -> Self {
108 Self {
109 action: Action::Add,
110 table: table.into(),
111 joins: vec![],
112 columns: vec![],
113 cages: vec![],
114 distinct: false,
115 index_def: None,
116 table_constraints: vec![],
117 set_ops: vec![],
118 having: vec![],
119 group_by_mode: GroupByMode::Simple,
120 ctes: vec![],
121 distinct_on: vec![],
122 }
123 }
124 pub fn hook(mut self, cols: &[&str]) -> Self {
126 self.columns = cols.iter().map(|c| Column::Named(c.to_string())).collect();
127 self
128 }
129
130 pub fn cage(mut self, column: &str, value: impl Into<Value>) -> Self {
132 self.cages.push(Cage {
133 kind: CageKind::Filter,
134 conditions: vec![Condition {
135 column: column.to_string(),
136 op: Operator::Eq,
137 value: value.into(),
138 is_array_unnest: false,
139 }],
140 logical_op: LogicalOp::And,
141 });
142 self
143 }
144
145 pub fn limit(mut self, n: i64) -> Self {
147 self.cages.push(Cage {
148 kind: CageKind::Limit(n as usize),
149 conditions: vec![],
150 logical_op: LogicalOp::And,
151 });
152 self
153 }
154
155 pub fn sort_asc(mut self, column: &str) -> Self {
157 self.cages.push(Cage {
158 kind: CageKind::Sort(SortOrder::Asc),
159 conditions: vec![Condition {
160 column: column.to_string(),
161 op: Operator::Eq,
162 value: Value::Null,
163 is_array_unnest: false,
164 }],
165 logical_op: LogicalOp::And,
166 });
167 self
168 }
169
170 pub fn sort_desc(mut self, column: &str) -> Self {
172 self.cages.push(Cage {
173 kind: CageKind::Sort(SortOrder::Desc),
174 conditions: vec![Condition {
175 column: column.to_string(),
176 op: Operator::Eq,
177 value: Value::Null,
178 is_array_unnest: false,
179 }],
180 logical_op: LogicalOp::And,
181 });
182 self
183 }
184
185 pub fn as_cte(self, name: impl Into<String>) -> Self {
199 let cte_name = name.into();
200 let columns: Vec<String> = self.columns.iter().filter_map(|c| {
201 match c {
202 Column::Named(n) => Some(n.clone()),
203 Column::Aliased { alias, .. } => Some(alias.clone()),
204 _ => None,
205 }
206 }).collect();
207
208 Self {
209 action: Action::With,
210 table: cte_name.clone(),
211 columns: vec![],
212 joins: vec![],
213 cages: vec![],
214 distinct: false,
215 index_def: None,
216 table_constraints: vec![],
217 set_ops: vec![],
218 having: vec![],
219 group_by_mode: GroupByMode::Simple,
220 distinct_on: vec![],
221 ctes: vec![CTEDef {
222 name: cte_name,
223 recursive: false,
224 columns,
225 base_query: Box::new(self),
226 recursive_query: None,
227 source_table: None,
228 }],
229 }
230 }
231
232 pub fn recursive(mut self, recursive_part: QailCmd) -> Self {
241 if let Some(cte) = self.ctes.last_mut() {
242 cte.recursive = true;
243 cte.recursive_query = Some(Box::new(recursive_part));
244 }
245 self
246 }
247
248 pub fn from_cte(mut self, cte_name: impl Into<String>) -> Self {
250 if let Some(cte) = self.ctes.last_mut() {
251 cte.source_table = Some(cte_name.into());
252 }
253 self
254 }
255
256 pub fn select_from_cte(mut self, columns: &[&str]) -> Self {
263 self.columns = columns.iter().map(|c| Column::Named(c.to_string())).collect();
264 self
265 }
266}
267
268#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub struct Join {
271 pub table: String,
272 pub kind: JoinKind,
273 #[serde(default)]
274 pub on: Option<Vec<Condition>>,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub enum JoinKind {
279 Inner,
280 Left,
281 Right,
282 Lateral,
284 Full,
286 Cross,
288}
289
290#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
292pub enum SetOp {
293 Union,
295 UnionAll,
297 Intersect,
299 Except,
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
305pub enum GroupByMode {
306 #[default]
308 Simple,
309 Rollup,
311 Cube,
313}
314
315#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
317pub struct CTEDef {
318 pub name: String,
320 pub recursive: bool,
322 pub columns: Vec<String>,
324 pub base_query: Box<QailCmd>,
326 pub recursive_query: Option<Box<QailCmd>>,
328 pub source_table: Option<String>,
330}
331
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub enum Column {
335 Star,
337 Named(String),
339 Aliased { name: String, alias: String },
341 Aggregate { col: String, func: AggregateFunc },
343 Def {
345 name: String,
346 data_type: String,
347 constraints: Vec<Constraint>,
348 },
349 Mod {
351 kind: ModKind,
352 col: Box<Column>,
353 },
354 Window {
356 name: String,
357 func: String,
358 params: Vec<Value>,
359 partition: Vec<String>,
360 order: Vec<Cage>,
361 frame: Option<WindowFrame>,
362 },
363 Case {
365 when_clauses: Vec<(Condition, Value)>,
367 else_value: Option<Box<Value>>,
369 alias: Option<String>,
371 },
372 JsonAccess {
374 column: String,
376 path: String,
378 as_text: bool,
380 alias: Option<String>,
382 },
383 FunctionCall {
385 name: String,
387 args: Vec<String>,
389 alias: Option<String>,
391 },
392}
393
394impl std::fmt::Display for Column {
395 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396 match self {
397 Column::Star => write!(f, "*"),
398 Column::Named(name) => write!(f, "{}", name),
399 Column::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
400 Column::Aggregate { col, func } => write!(f, "{}({})", func, col),
401 Column::Def {
402 name,
403 data_type,
404 constraints,
405 } => {
406 write!(f, "{}:{}", name, data_type)?;
407 for c in constraints {
408 write!(f, "^{}", c)?;
409 }
410 Ok(())
411 }
412 Column::Mod { kind, col } => match kind {
413 ModKind::Add => write!(f, "+{}", col),
414 ModKind::Drop => write!(f, "-{}", col),
415 },
416 Column::Window { name, func, params, partition, order, frame } => {
417 write!(f, "{}:{}(", name, func)?;
418 for (i, p) in params.iter().enumerate() {
419 if i > 0 { write!(f, ", ")?; }
420 write!(f, "{}", p)?;
421 }
422 write!(f, ")")?;
423
424 if !partition.is_empty() {
426 write!(f, "{{Part=")?;
427 for (i, p) in partition.iter().enumerate() {
428 if i > 0 { write!(f, ",")?; }
429 write!(f, "{}", p)?;
430 }
431 if let Some(fr) = frame {
432 write!(f, ", Frame={:?}", fr)?; }
434 write!(f, "}}")?;
435 } else if frame.is_some() {
436 write!(f, "{{Frame={:?}}}", frame.as_ref().unwrap())?;
437 }
438
439 for _cage in order {
441 }
443 Ok(())
444 }
445 Column::Case { when_clauses, else_value, alias } => {
446 write!(f, "CASE")?;
447 for (cond, val) in when_clauses {
448 write!(f, " WHEN {} THEN {}", cond.column, val)?;
449 }
450 if let Some(e) = else_value {
451 write!(f, " ELSE {}", e)?;
452 }
453 write!(f, " END")?;
454 if let Some(a) = alias {
455 write!(f, " AS {}", a)?;
456 }
457 Ok(())
458 }
459 Column::JsonAccess { column, path, as_text, alias } => {
460 let op = if *as_text { "->>" } else { "->" };
461 write!(f, "{}{}{}", column, op, path)?;
462 if let Some(a) = alias {
463 write!(f, " AS {}", a)?;
464 }
465 Ok(())
466 }
467 Column::FunctionCall { name, args, alias } => {
468 write!(f, "{}({})", name.to_uppercase(), args.join(", "))?;
469 if let Some(a) = alias {
470 write!(f, " AS {}", a)?;
471 }
472 Ok(())
473 }
474 }
475 }
476}
477
478#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
480pub enum ModKind {
481 Add,
482 Drop,
483}
484
485#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
487pub enum Constraint {
488 PrimaryKey,
489 Unique,
490 Nullable,
491 Default(String),
493 Check(Vec<String>),
495 Comment(String),
497 Generated(ColumnGeneration),
499}
500
501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
503pub enum ColumnGeneration {
504 Stored(String),
506 Virtual(String),
508}
509
510#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub enum WindowFrame {
513 Rows { start: FrameBound, end: FrameBound },
515 Range { start: FrameBound, end: FrameBound },
517}
518
519#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
521pub enum FrameBound {
522 UnboundedPreceding,
523 Preceding(i32),
524 CurrentRow,
525 Following(i32),
526 UnboundedFollowing,
527}
528
529impl std::fmt::Display for Constraint {
530 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531 match self {
532 Constraint::PrimaryKey => write!(f, "pk"),
533 Constraint::Unique => write!(f, "uniq"),
534 Constraint::Nullable => write!(f, "?"),
535 Constraint::Default(val) => write!(f, "={}", val),
536 Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
537 Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
538 Constraint::Generated(generation) => match generation {
539 ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
540 ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
541 },
542 }
543 }
544}
545
546#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
548pub struct IndexDef {
549 pub name: String,
551 pub table: String,
553 pub columns: Vec<String>,
555 pub unique: bool,
557}
558
559#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561pub enum TableConstraint {
562 Unique(Vec<String>),
564 PrimaryKey(Vec<String>),
566}
567
568#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
570pub enum AggregateFunc {
571 Count,
572 Sum,
573 Avg,
574 Min,
575 Max,
576}
577
578impl std::fmt::Display for AggregateFunc {
579 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580 match self {
581 AggregateFunc::Count => write!(f, "COUNT"),
582 AggregateFunc::Sum => write!(f, "SUM"),
583 AggregateFunc::Avg => write!(f, "AVG"),
584 AggregateFunc::Min => write!(f, "MIN"),
585 AggregateFunc::Max => write!(f, "MAX"),
586 }
587 }
588}
589
590
591
592#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
594pub enum Action {
595 Get,
597 Set,
599 Del,
601 Add,
603 Gen,
605 Make,
607 Drop,
609 Mod,
611 Over,
613 With,
615 Index,
618 TxnStart,
620 TxnCommit,
621 TxnRollback,
622 Put,
623 DropCol,
624 RenameCol,
625 JsonTable,
628}
629
630impl std::fmt::Display for Action {
631 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
632 match self {
633 Action::Get => write!(f, "GET"),
634 Action::Set => write!(f, "SET"),
635 Action::Del => write!(f, "DEL"),
636 Action::Add => write!(f, "ADD"),
637 Action::Gen => write!(f, "GEN"),
638 Action::Make => write!(f, "MAKE"),
639 Action::Drop => write!(f, "DROP"),
640 Action::Mod => write!(f, "MOD"),
641 Action::Over => write!(f, "OVER"),
642 Action::With => write!(f, "WITH"),
643 Action::Index => write!(f, "INDEX"),
644 Action::TxnStart => write!(f, "TXN_START"),
645 Action::TxnCommit => write!(f, "TXN_COMMIT"),
646 Action::TxnRollback => write!(f, "TXN_ROLLBACK"),
647 Action::Put => write!(f, "PUT"),
648 Action::DropCol => write!(f, "DROP_COL"),
649 Action::RenameCol => write!(f, "RENAME_COL"),
650 Action::JsonTable => write!(f, "JSON_TABLE"),
651 }
652 }
653}
654
655
656
657#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
659pub struct Cage {
660 pub kind: CageKind,
662 pub conditions: Vec<Condition>,
664 pub logical_op: LogicalOp,
666}
667
668#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
670pub enum CageKind {
671 Filter,
673 Payload,
675 Sort(SortOrder),
677 Limit(usize),
679 Offset(usize),
681 Sample(usize),
683 Qualify,
685 Partition,
687}
688
689#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
691pub enum SortOrder {
692 Asc,
693 Desc,
694 AscNullsFirst,
696 AscNullsLast,
698 DescNullsFirst,
700 DescNullsLast,
702}
703
704#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
706pub enum LogicalOp {
707 #[default]
708 And,
709 Or,
710}
711
712#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
714pub struct Condition {
715 pub column: String,
717 pub op: Operator,
719 pub value: Value,
721 #[serde(default)]
723 pub is_array_unnest: bool,
724}
725
726#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
728pub enum Operator {
729 Eq,
731 Ne,
733 Gt,
735 Gte,
737 Lt,
739 Lte,
741 Fuzzy,
743 In,
745 NotIn,
747 IsNull,
749 IsNotNull,
751 Contains,
753 KeyExists,
755 JsonExists,
757 JsonQuery,
759 JsonValue,
761}
762
763#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
765pub enum Value {
766 Null,
768 Bool(bool),
770 Int(i64),
772 Float(f64),
774 String(String),
776 Param(usize),
778 Function(String),
780 Array(Vec<Value>),
782 Subquery(Box<QailCmd>),
784 Column(String),
786}
787
788impl From<bool> for Value {
789 fn from(b: bool) -> Self {
790 Value::Bool(b)
791 }
792}
793
794impl From<i32> for Value {
795 fn from(n: i32) -> Self {
796 Value::Int(n as i64)
797 }
798}
799
800impl From<i64> for Value {
801 fn from(n: i64) -> Self {
802 Value::Int(n)
803 }
804}
805
806impl From<f64> for Value {
807 fn from(n: f64) -> Self {
808 Value::Float(n)
809 }
810}
811
812impl From<&str> for Value {
813 fn from(s: &str) -> Self {
814 Value::String(s.to_string())
815 }
816}
817
818impl From<String> for Value {
819 fn from(s: String) -> Self {
820 Value::String(s)
821 }
822}
823
824impl std::fmt::Display for Value {
825 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
826 match self {
827 Value::Null => write!(f, "NULL"),
828 Value::Bool(b) => write!(f, "{}", b),
829 Value::Int(n) => write!(f, "{}", n),
830 Value::Float(n) => write!(f, "{}", n),
831 Value::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
832 Value::Param(n) => write!(f, "${}", n),
833 Value::Function(name) => write!(f, "{}()", name),
834 Value::Array(arr) => {
835 write!(f, "ARRAY[")?;
836 for (i, v) in arr.iter().enumerate() {
837 if i > 0 {
838 write!(f, ", ")?;
839 }
840 write!(f, "{}", v)?;
841 }
842 write!(f, "]")
843 }
844 Value::Subquery(cmd) => write!(f, "({})", cmd.table), Value::Column(col) => write!(f, "\"{}\"", col), }
847 }
848}
849
850#[cfg(test)]
851mod tests {
852 use super::*;
853
854 #[test]
855 fn test_builder_pattern() {
856 let cmd = QailCmd::get("users")
857 .hook(&["id", "email"])
858 .cage("active", true)
859 .limit(10);
860
861 assert_eq!(cmd.action, Action::Get);
862 assert_eq!(cmd.table, "users");
863 assert_eq!(cmd.columns.len(), 2);
864 assert_eq!(cmd.cages.len(), 2);
865 }
866}