qail_core/ast/cmd/
query.rs

1//! Query builder methods for Qail.
2//!
3//! Common fluent methods: columns, filter, join, order_by, limit, etc.
4
5use crate::ast::{
6    Cage, CageKind, Condition, Expr, Join, JoinKind, LogicalOp, Operator, Qail, SortOrder,
7    Value,
8};
9
10impl Qail {
11    pub fn limit(mut self, n: i64) -> Self {
12        self.cages.push(Cage {
13            kind: CageKind::Limit(n as usize),
14            conditions: vec![],
15            logical_op: LogicalOp::And,
16        });
17        self
18    }
19
20    #[deprecated(since = "0.11.0", note = "Use .order_asc(column) instead")]
21    pub fn sort_asc(mut self, column: &str) -> Self {
22        self.cages.push(Cage {
23            kind: CageKind::Sort(SortOrder::Asc),
24            conditions: vec![Condition {
25                left: Expr::Named(column.to_string()),
26                op: Operator::Eq,
27                value: Value::Null,
28                is_array_unnest: false,
29            }],
30            logical_op: LogicalOp::And,
31        });
32        self
33    }
34
35    pub fn select_all(mut self) -> Self {
36        self.columns.push(Expr::Star);
37        self
38    }
39
40    pub fn columns<I, S>(mut self, cols: I) -> Self
41    where
42        I: IntoIterator<Item = S>,
43        S: AsRef<str>,
44    {
45        self.columns.extend(
46            cols.into_iter()
47                .map(|c| Expr::Named(c.as_ref().to_string())),
48        );
49        self
50    }
51
52    pub fn column(mut self, col: impl AsRef<str>) -> Self {
53        self.columns.push(Expr::Named(col.as_ref().to_string()));
54        self
55    }
56
57    pub fn filter(
58        mut self,
59        column: impl AsRef<str>,
60        op: Operator,
61        value: impl Into<Value>,
62    ) -> Self {
63        let filter_cage = self
64            .cages
65            .iter_mut()
66            .find(|c| matches!(c.kind, CageKind::Filter));
67
68        let condition = Condition {
69            left: Expr::Named(column.as_ref().to_string()),
70            op,
71            value: value.into(),
72            is_array_unnest: false,
73        };
74
75        if let Some(cage) = filter_cage {
76            cage.conditions.push(condition);
77        } else {
78            self.cages.push(Cage {
79                kind: CageKind::Filter,
80                conditions: vec![condition],
81                logical_op: LogicalOp::And,
82            });
83        }
84        self
85    }
86
87    pub fn or_filter(
88        mut self,
89        column: impl AsRef<str>,
90        op: Operator,
91        value: impl Into<Value>,
92    ) -> Self {
93        self.cages.push(Cage {
94            kind: CageKind::Filter,
95            conditions: vec![Condition {
96                left: Expr::Named(column.as_ref().to_string()),
97                op,
98                value: value.into(),
99                is_array_unnest: false,
100            }],
101            logical_op: LogicalOp::Or,
102        });
103        self
104    }
105
106    pub fn where_eq(self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
107        self.filter(column, Operator::Eq, value)
108    }
109
110    pub fn order_by(mut self, column: impl AsRef<str>, order: SortOrder) -> Self {
111        self.cages.push(Cage {
112            kind: CageKind::Sort(order),
113            conditions: vec![Condition {
114                left: Expr::Named(column.as_ref().to_string()),
115                op: Operator::Eq,
116                value: Value::Null,
117                is_array_unnest: false,
118            }],
119            logical_op: LogicalOp::And,
120        });
121        self
122    }
123
124    pub fn order_desc(self, column: impl AsRef<str>) -> Self {
125        self.order_by(column, SortOrder::Desc)
126    }
127
128    pub fn order_asc(self, column: impl AsRef<str>) -> Self {
129        self.order_by(column, SortOrder::Asc)
130    }
131
132    pub fn offset(mut self, n: i64) -> Self {
133        self.cages.push(Cage {
134            kind: CageKind::Offset(n as usize),
135            conditions: vec![],
136            logical_op: LogicalOp::And,
137        });
138        self
139    }
140
141    pub fn group_by<I, S>(mut self, cols: I) -> Self
142    where
143        I: IntoIterator<Item = S>,
144        S: AsRef<str>,
145    {
146        let conditions: Vec<Condition> = cols
147            .into_iter()
148            .map(|c| Condition {
149                left: Expr::Named(c.as_ref().to_string()),
150                op: Operator::Eq,
151                value: Value::Null,
152                is_array_unnest: false,
153            })
154            .collect();
155
156        self.cages.push(Cage {
157            kind: CageKind::Partition,
158            conditions,
159            logical_op: LogicalOp::And,
160        });
161        self
162    }
163
164    pub fn distinct_on_all(mut self) -> Self {
165        self.distinct = true;
166        self
167    }
168
169    pub fn join(
170        mut self,
171        kind: JoinKind,
172        table: impl AsRef<str>,
173        left_col: impl AsRef<str>,
174        right_col: impl AsRef<str>,
175    ) -> Self {
176        self.joins.push(Join {
177            kind,
178            table: table.as_ref().to_string(),
179            on: Some(vec![Condition {
180                left: Expr::Named(left_col.as_ref().to_string()),
181                op: Operator::Eq,
182                value: Value::Column(right_col.as_ref().to_string()),
183                is_array_unnest: false,
184            }]),
185            on_true: false,
186        });
187        self
188    }
189
190    pub fn left_join(
191        self,
192        table: impl AsRef<str>,
193        left_col: impl AsRef<str>,
194        right_col: impl AsRef<str>,
195    ) -> Self {
196        self.join(JoinKind::Left, table, left_col, right_col)
197    }
198
199    pub fn inner_join(
200        self,
201        table: impl AsRef<str>,
202        left_col: impl AsRef<str>,
203        right_col: impl AsRef<str>,
204    ) -> Self {
205        self.join(JoinKind::Inner, table, left_col, right_col)
206    }
207
208    pub fn returning<I, S>(mut self, cols: I) -> Self
209    where
210        I: IntoIterator<Item = S>,
211        S: AsRef<str>,
212    {
213        self.returning = Some(
214            cols.into_iter()
215                .map(|c| Expr::Named(c.as_ref().to_string()))
216                .collect(),
217        );
218        self
219    }
220
221    pub fn returning_all(mut self) -> Self {
222        self.returning = Some(vec![Expr::Star]);
223        self
224    }
225
226    pub fn values<I, V>(mut self, vals: I) -> Self
227    where
228        I: IntoIterator<Item = V>,
229        V: Into<Value>,
230    {
231        self.cages.push(Cage {
232            kind: CageKind::Payload,
233            conditions: vals
234                .into_iter()
235                .enumerate()
236                .map(|(i, v)| Condition {
237                    left: Expr::Named(format!("${}", i + 1)),
238                    op: Operator::Eq,
239                    value: v.into(),
240                    is_array_unnest: false,
241                })
242                .collect(),
243            logical_op: LogicalOp::And,
244        });
245        self
246    }
247
248    pub fn set_value(mut self, column: impl AsRef<str>, value: impl Into<Value>) -> Self {
249        let payload_cage = self
250            .cages
251            .iter_mut()
252            .find(|c| matches!(c.kind, CageKind::Payload));
253
254        let condition = Condition {
255            left: Expr::Named(column.as_ref().to_string()),
256            op: Operator::Eq,
257            value: value.into(),
258            is_array_unnest: false,
259        };
260
261        if let Some(cage) = payload_cage {
262            cage.conditions.push(condition);
263        } else {
264            self.cages.push(Cage {
265                kind: CageKind::Payload,
266                conditions: vec![condition],
267                logical_op: LogicalOp::And,
268            });
269        }
270        self
271    }
272}