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