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}
23
24impl QailCmd {
25 pub fn get(table: impl Into<String>) -> Self {
27 Self {
28 action: Action::Get,
29 table: table.into(),
30 joins: vec![],
31 columns: vec![],
32 cages: vec![],
33 }
34 }
35
36 pub fn set(table: impl Into<String>) -> Self {
38 Self {
39 action: Action::Set,
40 table: table.into(),
41 joins: vec![],
42 columns: vec![],
43 cages: vec![],
44 }
45 }
46
47 pub fn del(table: impl Into<String>) -> Self {
49 Self {
50 action: Action::Del,
51 table: table.into(),
52 joins: vec![],
53 columns: vec![],
54 cages: vec![],
55 }
56 }
57
58 pub fn add(table: impl Into<String>) -> Self {
60 Self {
61 action: Action::Add,
62 table: table.into(),
63 joins: vec![],
64 columns: vec![],
65 cages: vec![],
66 }
67 }
68 pub fn hook(mut self, cols: &[&str]) -> Self {
70 self.columns = cols.iter().map(|c| Column::Named(c.to_string())).collect();
71 self
72 }
73
74 pub fn cage(mut self, column: &str, value: impl Into<Value>) -> Self {
76 self.cages.push(Cage {
77 kind: CageKind::Filter,
78 conditions: vec![Condition {
79 column: column.to_string(),
80 op: Operator::Eq,
81 value: value.into(),
82 is_array_unnest: false,
83 }],
84 logical_op: LogicalOp::And,
85 });
86 self
87 }
88
89 pub fn limit(mut self, n: i64) -> Self {
91 self.cages.push(Cage {
92 kind: CageKind::Limit(n as usize),
93 conditions: vec![],
94 logical_op: LogicalOp::And,
95 });
96 self
97 }
98
99 pub fn sort_asc(mut self, column: &str) -> Self {
101 self.cages.push(Cage {
102 kind: CageKind::Sort(SortOrder::Asc),
103 conditions: vec![Condition {
104 column: column.to_string(),
105 op: Operator::Eq,
106 value: Value::Null,
107 is_array_unnest: false,
108 }],
109 logical_op: LogicalOp::And,
110 });
111 self
112 }
113
114 pub fn sort_desc(mut self, column: &str) -> Self {
116 self.cages.push(Cage {
117 kind: CageKind::Sort(SortOrder::Desc),
118 conditions: vec![Condition {
119 column: column.to_string(),
120 op: Operator::Eq,
121 value: Value::Null,
122 is_array_unnest: false,
123 }],
124 logical_op: LogicalOp::And,
125 });
126 self
127 }
128}
129
130#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct Join {
133 pub table: String,
134 pub kind: JoinKind,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub enum JoinKind {
139 Inner,
140 Left,
141 Right,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub enum Column {
147 Star,
149 Named(String),
151 Aliased { name: String, alias: String },
153 Aggregate { col: String, func: AggregateFunc },
155 Def {
157 name: String,
158 data_type: String,
159 constraints: Vec<Constraint>,
160 },
161 Mod {
163 kind: ModKind,
164 col: Box<Column>,
165 },
166 Window {
168 name: String,
169 func: String,
170 params: Vec<Value>,
171 partition: Vec<String>,
172 order: Vec<Cage>,
173 },
174}
175
176impl std::fmt::Display for Column {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 match self {
179 Column::Star => write!(f, "*"),
180 Column::Named(name) => write!(f, "{}", name),
181 Column::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
182 Column::Aggregate { col, func } => write!(f, "{}({})", func, col),
183 Column::Def {
184 name,
185 data_type,
186 constraints,
187 } => {
188 write!(f, "{}:{}", name, data_type)?;
189 for c in constraints {
190 write!(f, "^{}", c)?;
191 }
192 Ok(())
193 }
194 Column::Mod { kind, col } => match kind {
195 ModKind::Add => write!(f, "+{}", col),
196 ModKind::Drop => write!(f, "-{}", col),
197 },
198 Column::Window { name, func, params, partition, order } => {
199 write!(f, "{}:{}(", name, func)?;
200 for (i, p) in params.iter().enumerate() {
201 if i > 0 { write!(f, ", ")?; }
202 write!(f, "{}", p)?;
203 }
204 write!(f, ")")?;
205
206 if !partition.is_empty() {
208 write!(f, "{{Part=")?;
209 for (i, p) in partition.iter().enumerate() {
210 if i > 0 { write!(f, ",")?; }
211 write!(f, "{}", p)?;
212 }
213 write!(f, "}}")?;
214 }
215
216 for cage in order {
218 }
225 Ok(())
226 }
227 }
228 }
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub enum ModKind {
234 Add,
235 Drop,
236}
237
238#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub enum Constraint {
241 PrimaryKey,
242 Unique,
243 Nullable,
244}
245
246impl std::fmt::Display for Constraint {
247 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248 match self {
249 Constraint::PrimaryKey => write!(f, "pk"),
250 Constraint::Unique => write!(f, "uniq"),
251 Constraint::Nullable => write!(f, "?"),
252 }
253 }
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
258pub enum AggregateFunc {
259 Count,
260 Sum,
261 Avg,
262 Min,
263 Max,
264}
265
266impl std::fmt::Display for AggregateFunc {
267 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268 match self {
269 AggregateFunc::Count => write!(f, "COUNT"),
270 AggregateFunc::Sum => write!(f, "SUM"),
271 AggregateFunc::Avg => write!(f, "AVG"),
272 AggregateFunc::Min => write!(f, "MIN"),
273 AggregateFunc::Max => write!(f, "MAX"),
274 }
275 }
276}
277
278
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
282pub enum Action {
283 Get,
285 Set,
287 Del,
289 Add,
291 Gen,
293 Make,
295 Mod,
297 Over,
299 With,
301}
302
303impl std::fmt::Display for Action {
304 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305 match self {
306 Action::Get => write!(f, "GET"),
307 Action::Set => write!(f, "SET"),
308 Action::Del => write!(f, "DEL"),
309 Action::Add => write!(f, "ADD"),
310 Action::Gen => write!(f, "GEN"),
311 Action::Make => write!(f, "MAKE"),
312 Action::Mod => write!(f, "MOD"),
313 Action::Over => write!(f, "OVER"),
314 Action::With => write!(f, "WITH"),
315 }
316 }
317}
318
319
320
321#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct Cage {
324 pub kind: CageKind,
326 pub conditions: Vec<Condition>,
328 pub logical_op: LogicalOp,
330}
331
332#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub enum CageKind {
335 Filter,
337 Payload,
339 Sort(SortOrder),
341 Limit(usize),
343 Offset(usize),
345}
346
347#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
349pub enum SortOrder {
350 Asc,
351 Desc,
352}
353
354#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
356pub enum LogicalOp {
357 #[default]
358 And,
359 Or,
360}
361
362#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
364pub struct Condition {
365 pub column: String,
367 pub op: Operator,
369 pub value: Value,
371 #[serde(default)]
373 pub is_array_unnest: bool,
374}
375
376#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
378pub enum Operator {
379 Eq,
381 Ne,
383 Gt,
385 Gte,
387 Lt,
389 Lte,
391 Fuzzy,
393 In,
395 NotIn,
397 IsNull,
399 IsNotNull,
401}
402
403#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub enum Value {
406 Null,
408 Bool(bool),
410 Int(i64),
412 Float(f64),
414 String(String),
416 Param(usize),
418 Function(String),
420 Array(Vec<Value>),
422}
423
424impl From<bool> for Value {
425 fn from(b: bool) -> Self {
426 Value::Bool(b)
427 }
428}
429
430impl From<i32> for Value {
431 fn from(n: i32) -> Self {
432 Value::Int(n as i64)
433 }
434}
435
436impl From<i64> for Value {
437 fn from(n: i64) -> Self {
438 Value::Int(n)
439 }
440}
441
442impl From<f64> for Value {
443 fn from(n: f64) -> Self {
444 Value::Float(n)
445 }
446}
447
448impl From<&str> for Value {
449 fn from(s: &str) -> Self {
450 Value::String(s.to_string())
451 }
452}
453
454impl From<String> for Value {
455 fn from(s: String) -> Self {
456 Value::String(s)
457 }
458}
459
460impl std::fmt::Display for Value {
461 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462 match self {
463 Value::Null => write!(f, "NULL"),
464 Value::Bool(b) => write!(f, "{}", b),
465 Value::Int(n) => write!(f, "{}", n),
466 Value::Float(n) => write!(f, "{}", n),
467 Value::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
468 Value::Param(n) => write!(f, "${}", n),
469 Value::Function(name) => write!(f, "{}()", name),
470 Value::Array(arr) => {
471 write!(f, "ARRAY[")?;
472 for (i, v) in arr.iter().enumerate() {
473 if i > 0 {
474 write!(f, ", ")?;
475 }
476 write!(f, "{}", v)?;
477 }
478 write!(f, "]")
479 }
480 }
481 }
482}
483
484#[cfg(test)]
485mod tests {
486 use super::*;
487
488 #[test]
489 fn test_builder_pattern() {
490 let cmd = QailCmd::get("users")
491 .hook(&["id", "email"])
492 .cage("active", true)
493 .limit(10);
494
495 assert_eq!(cmd.action, Action::Get);
496 assert_eq!(cmd.table, "users");
497 assert_eq!(cmd.columns.len(), 2);
498 assert_eq!(cmd.cages.len(), 2);
499 }
500}