1use crate::ast::{
6 Cage, CageKind, Condition, Expr, Join, JoinKind, LogicalOp, Operator, Qail, SortOrder, Value,
7};
8
9impl Qail {
10 pub fn limit(mut self, n: i64) -> Self {
12 self.cages.push(Cage {
13 kind: CageKind::Limit(usize::try_from(n).unwrap_or(0)),
14 conditions: vec![],
15 logical_op: LogicalOp::And,
16 });
17 self
18 }
19
20 pub fn select_all(mut self) -> Self {
22 self.columns.push(Expr::Star);
23 self
24 }
25
26 pub fn columns<I, S>(mut self, cols: I) -> Self
28 where
29 I: IntoIterator<Item = S>,
30 S: AsRef<str>,
31 {
32 self.columns.extend(
33 cols.into_iter()
34 .map(|c| Expr::Named(c.as_ref().to_string())),
35 );
36 self
37 }
38
39 pub fn column(mut self, col: impl AsRef<str>) -> Self {
41 self.columns.push(Expr::Named(col.as_ref().to_string()));
42 self
43 }
44
45 pub fn select_expr(mut self, expr: impl Into<Expr>) -> Self {
68 self.columns.push(expr.into());
69 self
70 }
71
72 pub fn select_exprs<I, E>(mut self, exprs: I) -> Self
82 where
83 I: IntoIterator<Item = E>,
84 E: Into<Expr>,
85 {
86 self.columns.extend(exprs.into_iter().map(|e| e.into()));
87 self
88 }
89
90 pub fn filter(
92 mut self,
93 column: impl AsRef<str>,
94 op: Operator,
95 value: impl Into<Value>,
96 ) -> Self {
97 let filter_cage = self
98 .cages
99 .iter_mut()
100 .find(|c| matches!(c.kind, CageKind::Filter) && c.logical_op == LogicalOp::And);
101
102 let condition = Condition {
103 left: Expr::Named(column.as_ref().to_string()),
104 op,
105 value: value.into(),
106 is_array_unnest: false,
107 };
108
109 if let Some(cage) = filter_cage {
110 cage.conditions.push(condition);
111 } else {
112 self.cages.push(Cage {
113 kind: CageKind::Filter,
114 conditions: vec![condition],
115 logical_op: LogicalOp::And,
116 });
117 }
118 self
119 }
120
121 pub fn or_filter(
123 mut self,
124 column: impl AsRef<str>,
125 op: Operator,
126 value: impl Into<Value>,
127 ) -> Self {
128 let condition = Condition {
129 left: Expr::Named(column.as_ref().to_string()),
130 op,
131 value: value.into(),
132 is_array_unnest: false,
133 };
134
135 let or_filter_cage = self
136 .cages
137 .iter_mut()
138 .find(|c| matches!(c.kind, CageKind::Filter) && c.logical_op == LogicalOp::Or);
139
140 if let Some(cage) = or_filter_cage {
141 cage.conditions.push(condition);
142 } else {
143 self.cages.push(Cage {
144 kind: CageKind::Filter,
145 conditions: vec![condition],
146 logical_op: LogicalOp::Or,
147 });
148 }
149 self
150 }
151
152 pub fn where_eq(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
154 self.filter(column, Operator::Eq, value)
155 }
156
157 pub fn eq(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
159 self.filter(column, Operator::Eq, value)
160 }
161
162 pub fn ne(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
164 self.filter(column, Operator::Ne, value)
165 }
166
167 pub fn gt(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
169 self.filter(column, Operator::Gt, value)
170 }
171 pub fn gte(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
173 self.filter(column, Operator::Gte, value)
174 }
175
176 pub fn lt(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
178 self.filter(column, Operator::Lt, value)
179 }
180
181 pub fn lte(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
183 self.filter(column, Operator::Lte, value)
184 }
185
186 pub fn is_null(self, column: impl AsRef<str>) -> Self {
188 self.filter(column, Operator::IsNull, Value::Null)
189 }
190
191 pub fn is_not_null(self, column: impl AsRef<str>) -> Self {
193 self.filter(column, Operator::IsNotNull, Value::Null)
194 }
195
196 pub fn like(self, column: impl AsRef<str>, pattern: impl Into<Value>) -> Self {
198 self.filter(column, Operator::Like, pattern)
199 }
200
201 pub fn ilike(self, column: impl AsRef<str>, pattern: impl Into<Value>) -> Self {
203 self.filter(column, Operator::ILike, pattern)
204 }
205
206 pub fn array_elem_contained_in_text(
211 self,
212 array_column: impl AsRef<str>,
213 text: impl Into<Value>,
214 ) -> Self {
215 self.filter_cond(Condition {
216 left: Expr::Named(array_column.as_ref().to_string()),
217 op: Operator::ArrayElemContainedInText,
218 value: text.into(),
219 is_array_unnest: true,
220 })
221 }
222
223 pub fn in_vals<I, V>(self, column: impl AsRef<str>, values: I) -> Self
230 where
231 I: IntoIterator<Item = V>,
232 V: Into<Value>,
233 {
234 let arr: Vec<Value> = values.into_iter().map(|v| v.into()).collect();
235 self.filter(column, Operator::In, Value::Array(arr))
236 }
237
238 pub fn order_by(mut self, column: impl AsRef<str>, order: SortOrder) -> Self {
240 self.cages.push(Cage {
241 kind: CageKind::Sort(order),
242 conditions: vec![Condition {
243 left: Expr::Named(column.as_ref().to_string()),
244 op: Operator::Eq,
245 value: Value::Null,
246 is_array_unnest: false,
247 }],
248 logical_op: LogicalOp::And,
249 });
250 self
251 }
252
253 pub fn order_desc(self, column: impl AsRef<str>) -> Self {
255 self.order_by(column, SortOrder::Desc)
256 }
257
258 pub fn order_asc(self, column: impl AsRef<str>) -> Self {
260 self.order_by(column, SortOrder::Asc)
261 }
262
263 pub fn offset(mut self, n: i64) -> Self {
265 self.cages.push(Cage {
266 kind: CageKind::Offset(usize::try_from(n).unwrap_or(0)),
267 conditions: vec![],
268 logical_op: LogicalOp::And,
269 });
270 self
271 }
272
273 pub fn group_by<I, S>(mut self, cols: I) -> Self
275 where
276 I: IntoIterator<Item = S>,
277 S: AsRef<str>,
278 {
279 let conditions: Vec<Condition> = cols
280 .into_iter()
281 .map(|c| Condition {
282 left: Expr::Named(c.as_ref().to_string()),
283 op: Operator::Eq,
284 value: Value::Null,
285 is_array_unnest: false,
286 })
287 .collect();
288
289 self.cages.push(Cage {
290 kind: CageKind::Partition,
291 conditions,
292 logical_op: LogicalOp::And,
293 });
294 self
295 }
296
297 pub fn distinct_on_all(mut self) -> Self {
299 self.distinct = true;
300 self
301 }
302
303 pub fn join(
305 mut self,
306 kind: JoinKind,
307 table: impl AsRef<str>,
308 left_col: impl AsRef<str>,
309 right_col: impl AsRef<str>,
310 ) -> Self {
311 self.joins.push(Join {
312 kind,
313 table: table.as_ref().to_string(),
314 on: Some(vec![Condition {
315 left: Expr::Named(left_col.as_ref().to_string()),
316 op: Operator::Eq,
317 value: Value::Column(right_col.as_ref().to_string()),
318 is_array_unnest: false,
319 }]),
320 on_true: false,
321 });
322 self
323 }
324
325 pub fn left_join(
327 self,
328 table: impl AsRef<str>,
329 left_col: impl AsRef<str>,
330 right_col: impl AsRef<str>,
331 ) -> Self {
332 self.join(JoinKind::Left, table, left_col, right_col)
333 }
334
335 pub fn inner_join(
337 self,
338 table: impl AsRef<str>,
339 left_col: impl AsRef<str>,
340 right_col: impl AsRef<str>,
341 ) -> Self {
342 self.join(JoinKind::Inner, table, left_col, right_col)
343 }
344
345 pub fn join_on(self, related_table: impl AsRef<str>) -> crate::error::QailBuildResult<Self> {
364 let related = related_table.as_ref();
365
366 if let Some((from_col, to_col)) =
368 crate::schema::lookup_relation_state(&self.table, related)?
369 {
370 return Ok(self.left_join(related, &from_col, &to_col));
371 }
372
373 if let Some((from_col, to_col)) =
375 crate::schema::lookup_relation_state(related, &self.table)?
376 {
377 return Ok(self.left_join(related, &to_col, &from_col));
378 }
379
380 Err(crate::error::QailBuildError::RelationNotFound {
381 from_table: self.table,
382 to_table: related.to_string(),
383 })
384 }
385
386 pub fn join_on_optional(self, related_table: impl AsRef<str>) -> Self {
391 let related = related_table.as_ref();
392
393 match crate::schema::lookup_relation_state(&self.table, related) {
395 Ok(Some((from_col, to_col))) => return self.left_join(related, &from_col, &to_col),
396 Ok(None) => {}
397 Err(msg) => {
398 eprintln!("QAIL: join_on_optional skipped — {}", msg);
399 return self;
400 }
401 }
402
403 match crate::schema::lookup_relation_state(related, &self.table) {
405 Ok(Some((from_col, to_col))) => return self.left_join(related, &to_col, &from_col),
406 Ok(None) => {}
407 Err(msg) => {
408 eprintln!("QAIL: join_on_optional skipped — {}", msg);
409 return self;
410 }
411 }
412
413 self
415 }
416
417 pub fn returning<I, S>(mut self, cols: I) -> Self
419 where
420 I: IntoIterator<Item = S>,
421 S: AsRef<str>,
422 {
423 self.returning = Some(
424 cols.into_iter()
425 .map(|c| Expr::Named(c.as_ref().to_string()))
426 .collect(),
427 );
428 self
429 }
430
431 pub fn returning_all(mut self) -> Self {
433 self.returning = Some(vec![Expr::Star]);
434 self
435 }
436
437 pub fn values<I, V>(mut self, vals: I) -> Self
439 where
440 I: IntoIterator<Item = V>,
441 V: Into<Value>,
442 {
443 self.cages.push(Cage {
444 kind: CageKind::Payload,
445 conditions: vals
446 .into_iter()
447 .enumerate()
448 .map(|(i, v)| Condition {
449 left: Expr::Named(format!("${}", i + 1)),
450 op: Operator::Eq,
451 value: v.into(),
452 is_array_unnest: false,
453 })
454 .collect(),
455 logical_op: LogicalOp::And,
456 });
457 self
458 }
459
460 pub fn set_value(mut self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
462 let payload_cage = self
463 .cages
464 .iter_mut()
465 .find(|c| matches!(c.kind, CageKind::Payload));
466
467 let condition = Condition {
468 left: Expr::Named(column.as_ref().to_string()),
469 op: Operator::Eq,
470 value: value.into(),
471 is_array_unnest: false,
472 };
473
474 if let Some(cage) = payload_cage {
475 cage.conditions.push(condition);
476 } else {
477 self.cages.push(Cage {
478 kind: CageKind::Payload,
479 conditions: vec![condition],
480 logical_op: LogicalOp::And,
481 });
482 }
483 self
484 }
485
486 pub fn set_opt<T>(self, column: impl AsRef<str>, value: Option<T>) -> Self
489 where
490 T: Into<Value>,
491 {
492 match value {
493 Some(v) => self.set_value(column, v),
494 None => self, }
496 }
497
498 pub fn set_coalesce(mut self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
510 use crate::ast::builders::coalesce;
511
512 let col_name = column.as_ref().to_string();
513 let coalesce_expr =
514 coalesce([Expr::Literal(value.into()), Expr::Named(col_name.clone())]).build();
515
516 let payload_cage = self
517 .cages
518 .iter_mut()
519 .find(|c| matches!(c.kind, CageKind::Payload));
520
521 let condition = Condition {
522 left: Expr::Named(col_name),
523 op: Operator::Eq,
524 value: Value::Expr(Box::new(coalesce_expr)),
525 is_array_unnest: false,
526 };
527
528 if let Some(cage) = payload_cage {
529 cage.conditions.push(condition);
530 } else {
531 self.cages.push(Cage {
532 kind: CageKind::Payload,
533 conditions: vec![condition],
534 logical_op: LogicalOp::And,
535 });
536 }
537 self
538 }
539
540 pub fn set_coalesce_opt<T>(self, column: impl AsRef<str>, value: Option<T>) -> Self
545 where
546 T: Into<Value>,
547 {
548 match value {
549 Some(v) => self.set_coalesce(column, v),
550 None => self, }
552 }
553
554 pub fn on_conflict_update<S>(mut self, conflict_cols: &[S], updates: &[(S, Expr)]) -> Self
564 where
565 S: AsRef<str>,
566 {
567 use super::{ConflictAction, OnConflict};
568
569 self.on_conflict = Some(OnConflict {
570 columns: conflict_cols
571 .iter()
572 .map(|c| c.as_ref().to_string())
573 .collect(),
574 action: ConflictAction::DoUpdate {
575 assignments: updates
576 .iter()
577 .map(|(col, expr)| (col.as_ref().to_string(), expr.clone()))
578 .collect(),
579 },
580 });
581 self
582 }
583
584 pub fn on_conflict_nothing<S>(mut self, conflict_cols: &[S]) -> Self
593 where
594 S: AsRef<str>,
595 {
596 use super::{ConflictAction, OnConflict};
597
598 self.on_conflict = Some(OnConflict {
599 columns: conflict_cols
600 .iter()
601 .map(|c| c.as_ref().to_string())
602 .collect(),
603 action: ConflictAction::DoNothing,
604 });
605 self
606 }
607}