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 cte: Option<CTEDef>,
43}
44
45impl QailCmd {
46 pub fn get(table: impl Into<String>) -> Self {
48 Self {
49 action: Action::Get,
50 table: table.into(),
51 joins: vec![],
52 columns: vec![],
53 cages: vec![],
54 distinct: false,
55 index_def: None,
56 table_constraints: vec![],
57 set_ops: vec![],
58 having: vec![],
59 group_by_mode: GroupByMode::Simple,
60 cte: None,
61 }
62 }
63
64 pub fn set(table: impl Into<String>) -> Self {
66 Self {
67 action: Action::Set,
68 table: table.into(),
69 joins: vec![],
70 columns: vec![],
71 cages: vec![],
72 distinct: false,
73 index_def: None,
74 table_constraints: vec![],
75 set_ops: vec![],
76 having: vec![],
77 group_by_mode: GroupByMode::Simple,
78 cte: None,
79 }
80 }
81
82 pub fn del(table: impl Into<String>) -> Self {
84 Self {
85 action: Action::Del,
86 table: table.into(),
87 joins: vec![],
88 columns: vec![],
89 cages: vec![],
90 distinct: false,
91 index_def: None,
92 table_constraints: vec![],
93 set_ops: vec![],
94 having: vec![],
95 group_by_mode: GroupByMode::Simple,
96 cte: None,
97 }
98 }
99
100 pub fn add(table: impl Into<String>) -> Self {
102 Self {
103 action: Action::Add,
104 table: table.into(),
105 joins: vec![],
106 columns: vec![],
107 cages: vec![],
108 distinct: false,
109 index_def: None,
110 table_constraints: vec![],
111 set_ops: vec![],
112 having: vec![],
113 group_by_mode: GroupByMode::Simple,
114 cte: None,
115 }
116 }
117 pub fn hook(mut self, cols: &[&str]) -> Self {
119 self.columns = cols.iter().map(|c| Column::Named(c.to_string())).collect();
120 self
121 }
122
123 pub fn cage(mut self, column: &str, value: impl Into<Value>) -> Self {
125 self.cages.push(Cage {
126 kind: CageKind::Filter,
127 conditions: vec![Condition {
128 column: column.to_string(),
129 op: Operator::Eq,
130 value: value.into(),
131 is_array_unnest: false,
132 }],
133 logical_op: LogicalOp::And,
134 });
135 self
136 }
137
138 pub fn limit(mut self, n: i64) -> Self {
140 self.cages.push(Cage {
141 kind: CageKind::Limit(n as usize),
142 conditions: vec![],
143 logical_op: LogicalOp::And,
144 });
145 self
146 }
147
148 pub fn sort_asc(mut self, column: &str) -> Self {
150 self.cages.push(Cage {
151 kind: CageKind::Sort(SortOrder::Asc),
152 conditions: vec![Condition {
153 column: column.to_string(),
154 op: Operator::Eq,
155 value: Value::Null,
156 is_array_unnest: false,
157 }],
158 logical_op: LogicalOp::And,
159 });
160 self
161 }
162
163 pub fn sort_desc(mut self, column: &str) -> Self {
165 self.cages.push(Cage {
166 kind: CageKind::Sort(SortOrder::Desc),
167 conditions: vec![Condition {
168 column: column.to_string(),
169 op: Operator::Eq,
170 value: Value::Null,
171 is_array_unnest: false,
172 }],
173 logical_op: LogicalOp::And,
174 });
175 self
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct Join {
182 pub table: String,
183 pub kind: JoinKind,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187pub enum JoinKind {
188 Inner,
189 Left,
190 Right,
191 Lateral,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
197pub enum SetOp {
198 Union,
200 UnionAll,
202 Intersect,
204 Except,
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
210pub enum GroupByMode {
211 #[default]
213 Simple,
214 Rollup,
216 Cube,
218}
219
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
222pub struct CTEDef {
223 pub name: String,
225 pub recursive: bool,
227 pub columns: Vec<String>,
229 pub base_query: Box<QailCmd>,
231 pub recursive_query: Option<Box<QailCmd>>,
233 pub source_table: Option<String>,
235}
236
237#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
239pub enum Column {
240 Star,
242 Named(String),
244 Aliased { name: String, alias: String },
246 Aggregate { col: String, func: AggregateFunc },
248 Def {
250 name: String,
251 data_type: String,
252 constraints: Vec<Constraint>,
253 },
254 Mod {
256 kind: ModKind,
257 col: Box<Column>,
258 },
259 Window {
261 name: String,
262 func: String,
263 params: Vec<Value>,
264 partition: Vec<String>,
265 order: Vec<Cage>,
266 },
267 Case {
269 when_clauses: Vec<(Condition, Value)>,
271 else_value: Option<Box<Value>>,
273 alias: Option<String>,
275 },
276}
277
278impl std::fmt::Display for Column {
279 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280 match self {
281 Column::Star => write!(f, "*"),
282 Column::Named(name) => write!(f, "{}", name),
283 Column::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
284 Column::Aggregate { col, func } => write!(f, "{}({})", func, col),
285 Column::Def {
286 name,
287 data_type,
288 constraints,
289 } => {
290 write!(f, "{}:{}", name, data_type)?;
291 for c in constraints {
292 write!(f, "^{}", c)?;
293 }
294 Ok(())
295 }
296 Column::Mod { kind, col } => match kind {
297 ModKind::Add => write!(f, "+{}", col),
298 ModKind::Drop => write!(f, "-{}", col),
299 },
300 Column::Window { name, func, params, partition, order } => {
301 write!(f, "{}:{}(", name, func)?;
302 for (i, p) in params.iter().enumerate() {
303 if i > 0 { write!(f, ", ")?; }
304 write!(f, "{}", p)?;
305 }
306 write!(f, ")")?;
307
308 if !partition.is_empty() {
310 write!(f, "{{Part=")?;
311 for (i, p) in partition.iter().enumerate() {
312 if i > 0 { write!(f, ",")?; }
313 write!(f, "{}", p)?;
314 }
315 write!(f, "}}")?;
316 }
317
318 for _cage in order {
320 }
322 Ok(())
323 }
324 Column::Case { when_clauses, else_value, alias } => {
325 write!(f, "CASE")?;
326 for (cond, val) in when_clauses {
327 write!(f, " WHEN {} THEN {}", cond.column, val)?;
328 }
329 if let Some(e) = else_value {
330 write!(f, " ELSE {}", e)?;
331 }
332 write!(f, " END")?;
333 if let Some(a) = alias {
334 write!(f, " AS {}", a)?;
335 }
336 Ok(())
337 }
338 }
339 }
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
344pub enum ModKind {
345 Add,
346 Drop,
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
351pub enum Constraint {
352 PrimaryKey,
353 Unique,
354 Nullable,
355 Default(String),
357 Check(Vec<String>),
359 Comment(String),
361 Generated(ColumnGeneration),
363}
364
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
367pub enum ColumnGeneration {
368 Stored(String),
370 Virtual(String),
372}
373
374#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
376pub enum WindowFrame {
377 Rows { start: FrameBound, end: FrameBound },
379 Range { start: FrameBound, end: FrameBound },
381}
382
383#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
385pub enum FrameBound {
386 UnboundedPreceding,
387 Preceding(i32),
388 CurrentRow,
389 Following(i32),
390 UnboundedFollowing,
391}
392
393impl std::fmt::Display for Constraint {
394 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395 match self {
396 Constraint::PrimaryKey => write!(f, "pk"),
397 Constraint::Unique => write!(f, "uniq"),
398 Constraint::Nullable => write!(f, "?"),
399 Constraint::Default(val) => write!(f, "={}", val),
400 Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
401 Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
402 Constraint::Generated(generation) => match generation {
403 ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
404 ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
405 },
406 }
407 }
408}
409
410#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
412pub struct IndexDef {
413 pub name: String,
415 pub table: String,
417 pub columns: Vec<String>,
419 pub unique: bool,
421}
422
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
425pub enum TableConstraint {
426 Unique(Vec<String>),
428 PrimaryKey(Vec<String>),
430}
431
432#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
434pub enum AggregateFunc {
435 Count,
436 Sum,
437 Avg,
438 Min,
439 Max,
440}
441
442impl std::fmt::Display for AggregateFunc {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 match self {
445 AggregateFunc::Count => write!(f, "COUNT"),
446 AggregateFunc::Sum => write!(f, "SUM"),
447 AggregateFunc::Avg => write!(f, "AVG"),
448 AggregateFunc::Min => write!(f, "MIN"),
449 AggregateFunc::Max => write!(f, "MAX"),
450 }
451 }
452}
453
454
455
456#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
458pub enum Action {
459 Get,
461 Set,
463 Del,
465 Add,
467 Gen,
469 Make,
471 Drop,
473 Mod,
475 Over,
477 With,
479 Index,
482 TxnStart,
484 TxnCommit,
485 TxnRollback,
486 Put,
487 DropCol,
488 RenameCol,
489 JsonTable,
492}
493
494impl std::fmt::Display for Action {
495 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
496 match self {
497 Action::Get => write!(f, "GET"),
498 Action::Set => write!(f, "SET"),
499 Action::Del => write!(f, "DEL"),
500 Action::Add => write!(f, "ADD"),
501 Action::Gen => write!(f, "GEN"),
502 Action::Make => write!(f, "MAKE"),
503 Action::Drop => write!(f, "DROP"),
504 Action::Mod => write!(f, "MOD"),
505 Action::Over => write!(f, "OVER"),
506 Action::With => write!(f, "WITH"),
507 Action::Index => write!(f, "INDEX"),
508 Action::TxnStart => write!(f, "TXN_START"),
509 Action::TxnCommit => write!(f, "TXN_COMMIT"),
510 Action::TxnRollback => write!(f, "TXN_ROLLBACK"),
511 Action::Put => write!(f, "PUT"),
512 Action::DropCol => write!(f, "DROP_COL"),
513 Action::RenameCol => write!(f, "RENAME_COL"),
514 Action::JsonTable => write!(f, "JSON_TABLE"),
515 }
516 }
517}
518
519
520
521#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
523pub struct Cage {
524 pub kind: CageKind,
526 pub conditions: Vec<Condition>,
528 pub logical_op: LogicalOp,
530}
531
532#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
534pub enum CageKind {
535 Filter,
537 Payload,
539 Sort(SortOrder),
541 Limit(usize),
543 Offset(usize),
545 Sample(usize),
547 Qualify,
549 Partition,
551}
552
553#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
555pub enum SortOrder {
556 Asc,
557 Desc,
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
562pub enum LogicalOp {
563 #[default]
564 And,
565 Or,
566}
567
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
570pub struct Condition {
571 pub column: String,
573 pub op: Operator,
575 pub value: Value,
577 #[serde(default)]
579 pub is_array_unnest: bool,
580}
581
582#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
584pub enum Operator {
585 Eq,
587 Ne,
589 Gt,
591 Gte,
593 Lt,
595 Lte,
597 Fuzzy,
599 In,
601 NotIn,
603 IsNull,
605 IsNotNull,
607 Contains,
609 KeyExists,
611 JsonExists,
613 JsonQuery,
615 JsonValue,
617}
618
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
621pub enum Value {
622 Null,
624 Bool(bool),
626 Int(i64),
628 Float(f64),
630 String(String),
632 Param(usize),
634 Function(String),
636 Array(Vec<Value>),
638 Subquery(Box<QailCmd>),
640}
641
642impl From<bool> for Value {
643 fn from(b: bool) -> Self {
644 Value::Bool(b)
645 }
646}
647
648impl From<i32> for Value {
649 fn from(n: i32) -> Self {
650 Value::Int(n as i64)
651 }
652}
653
654impl From<i64> for Value {
655 fn from(n: i64) -> Self {
656 Value::Int(n)
657 }
658}
659
660impl From<f64> for Value {
661 fn from(n: f64) -> Self {
662 Value::Float(n)
663 }
664}
665
666impl From<&str> for Value {
667 fn from(s: &str) -> Self {
668 Value::String(s.to_string())
669 }
670}
671
672impl From<String> for Value {
673 fn from(s: String) -> Self {
674 Value::String(s)
675 }
676}
677
678impl std::fmt::Display for Value {
679 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
680 match self {
681 Value::Null => write!(f, "NULL"),
682 Value::Bool(b) => write!(f, "{}", b),
683 Value::Int(n) => write!(f, "{}", n),
684 Value::Float(n) => write!(f, "{}", n),
685 Value::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
686 Value::Param(n) => write!(f, "${}", n),
687 Value::Function(name) => write!(f, "{}()", name),
688 Value::Array(arr) => {
689 write!(f, "ARRAY[")?;
690 for (i, v) in arr.iter().enumerate() {
691 if i > 0 {
692 write!(f, ", ")?;
693 }
694 write!(f, "{}", v)?;
695 }
696 write!(f, "]")
697 }
698 Value::Subquery(cmd) => write!(f, "({})", cmd.table), }
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn test_builder_pattern() {
709 let cmd = QailCmd::get("users")
710 .hook(&["id", "email"])
711 .cage("active", true)
712 .limit(10);
713
714 assert_eq!(cmd.action, Action::Get);
715 assert_eq!(cmd.table, "users");
716 assert_eq!(cmd.columns.len(), 2);
717 assert_eq!(cmd.cages.len(), 2);
718 }
719}