Skip to main content

qcraft_core/ast/
expr.rs

1use super::common::{FieldRef, OrderByDef};
2use super::conditions::Conditions;
3use super::custom::{CustomBinaryOp, CustomExpr};
4use super::query::QueryStmt;
5use super::value::Value;
6
7/// An expression in a SQL statement.
8#[derive(Debug, Clone)]
9pub enum Expr {
10    /// Literal value.
11    Value(Value),
12
13    /// Column reference.
14    Field(FieldRef),
15
16    /// Binary operation: `left op right`.
17    Binary {
18        left: Box<Expr>,
19        op: BinaryOp,
20        right: Box<Expr>,
21    },
22
23    /// Unary operation: `-expr`, `NOT expr`.
24    Unary { op: UnaryOp, expr: Box<Expr> },
25
26    /// Function call: `name(args...)`.
27    Func { name: String, args: Vec<Expr> },
28
29    /// Aggregate function: `COUNT(expr)`, `SUM(DISTINCT expr) FILTER (WHERE ...)`.
30    Aggregate(AggregationDef),
31
32    /// Type cast: `expr::type` (PG) or `CAST(expr AS type)`.
33    Cast { expr: Box<Expr>, to_type: String },
34
35    /// CASE WHEN ... THEN ... ELSE ... END.
36    Case(CaseDef),
37
38    /// Window function: `expr OVER (PARTITION BY ... ORDER BY ... frame)`.
39    Window(WindowDef),
40
41    /// EXISTS (subquery).
42    Exists(Box<QueryStmt>),
43
44    /// Scalar subquery.
45    SubQuery(Box<QueryStmt>),
46
47    /// ARRAY(subquery).
48    ArraySubQuery(Box<QueryStmt>),
49
50    /// Collation override: `expr COLLATE "name"`.
51    Collate { expr: Box<Expr>, collation: String },
52
53    /// Build a JSON array: PG `jsonb_build_array(...)`, SQLite `json_array(...)`.
54    JsonArray(Vec<Expr>),
55
56    /// Build a JSON object: PG `jsonb_build_object(k, v, ...)`, SQLite `json_object(k, v, ...)`.
57    JsonObject(Vec<(String, Expr)>),
58
59    /// Aggregate into JSON array: PG `jsonb_agg(...)`, SQLite `json_group_array(...)`.
60    JsonAgg {
61        expr: Box<Expr>,
62        distinct: bool,
63        filter: Option<Conditions>,
64        order_by: Option<Vec<OrderByDef>>,
65    },
66
67    /// Concatenate strings: PG `string_agg(expr, delim)`, SQLite `group_concat(expr, delim)`.
68    StringAgg {
69        expr: Box<Expr>,
70        delimiter: String,
71        distinct: bool,
72        filter: Option<Conditions>,
73        order_by: Option<Vec<OrderByDef>>,
74    },
75
76    /// JSON text extraction: `expr->>'path'` on both PG and SQLite.
77    /// Unlike `->` (which returns JSON), `->>` returns the value as text.
78    JsonPathText { expr: Box<Expr>, path: String },
79
80    /// Current timestamp: PG `now()`, SQLite `datetime('now')`.
81    Now,
82
83    /// Raw SQL with parameters (escape hatch).
84    Raw { sql: String, params: Vec<Value> },
85
86    /// User-defined expression (extension point).
87    Custom(Box<dyn CustomExpr>),
88}
89
90impl Expr {
91    /// Column reference: `table.field`.
92    pub fn field(table: &str, name: &str) -> Self {
93        Expr::Field(FieldRef::new(table, name))
94    }
95
96    /// Literal value.
97    pub fn value(val: impl Into<Value>) -> Self {
98        Expr::Value(val.into())
99    }
100
101    /// Raw SQL expression (no parameters).
102    pub fn raw(sql: impl Into<String>) -> Self {
103        Expr::Raw {
104            sql: sql.into(),
105            params: vec![],
106        }
107    }
108
109    /// Function call: `name(args...)`.
110    pub fn func(name: impl Into<String>, args: Vec<Expr>) -> Self {
111        Expr::Func {
112            name: name.into(),
113            args,
114        }
115    }
116
117    /// Type cast: `CAST(expr AS to_type)`.
118    pub fn cast(expr: Expr, to_type: impl Into<String>) -> Self {
119        Expr::Cast {
120            expr: Box::new(expr),
121            to_type: to_type.into(),
122        }
123    }
124
125    /// COUNT(expr).
126    pub fn count(expr: Expr) -> Self {
127        Expr::Aggregate(AggregationDef {
128            name: "COUNT".into(),
129            expression: Some(Box::new(expr)),
130            distinct: false,
131            filter: None,
132            args: None,
133            order_by: None,
134        })
135    }
136
137    /// COUNT(*).
138    pub fn count_all() -> Self {
139        Expr::Aggregate(AggregationDef {
140            name: "COUNT".into(),
141            expression: None,
142            distinct: false,
143            filter: None,
144            args: None,
145            order_by: None,
146        })
147    }
148
149    /// SUM(expr).
150    pub fn sum(expr: Expr) -> Self {
151        Expr::Aggregate(AggregationDef {
152            name: "SUM".into(),
153            expression: Some(Box::new(expr)),
154            distinct: false,
155            filter: None,
156            args: None,
157            order_by: None,
158        })
159    }
160
161    /// AVG(expr).
162    pub fn avg(expr: Expr) -> Self {
163        Expr::Aggregate(AggregationDef {
164            name: "AVG".into(),
165            expression: Some(Box::new(expr)),
166            distinct: false,
167            filter: None,
168            args: None,
169            order_by: None,
170        })
171    }
172
173    /// MIN(expr).
174    pub fn min(expr: Expr) -> Self {
175        Expr::Aggregate(AggregationDef {
176            name: "MIN".into(),
177            expression: Some(Box::new(expr)),
178            distinct: false,
179            filter: None,
180            args: None,
181            order_by: None,
182        })
183    }
184
185    /// MAX(expr).
186    pub fn max(expr: Expr) -> Self {
187        Expr::Aggregate(AggregationDef {
188            name: "MAX".into(),
189            expression: Some(Box::new(expr)),
190            distinct: false,
191            filter: None,
192            args: None,
193            order_by: None,
194        })
195    }
196
197    /// EXISTS (subquery).
198    pub fn exists(query: QueryStmt) -> Self {
199        Expr::Exists(Box::new(query))
200    }
201
202    /// Scalar subquery.
203    pub fn subquery(query: QueryStmt) -> Self {
204        Expr::SubQuery(Box::new(query))
205    }
206
207    /// Collation override: `expr COLLATE "name"`.
208    pub fn collate(self, collation: impl Into<String>) -> Self {
209        Expr::Collate {
210            expr: Box::new(self),
211            collation: collation.into(),
212        }
213    }
214
215    /// Build a JSON array from expressions.
216    pub fn json_array(items: Vec<Expr>) -> Self {
217        Expr::JsonArray(items)
218    }
219
220    /// Build a JSON object from key-value pairs.
221    pub fn json_object(pairs: Vec<(impl Into<String>, Expr)>) -> Self {
222        Expr::JsonObject(pairs.into_iter().map(|(k, v)| (k.into(), v)).collect())
223    }
224
225    /// Aggregate values into a JSON array.
226    pub fn json_agg(expr: Expr) -> Self {
227        Expr::JsonAgg {
228            expr: Box::new(expr),
229            distinct: false,
230            filter: None,
231            order_by: None,
232        }
233    }
234
235    /// Concatenate strings with a delimiter.
236    pub fn string_agg(expr: Expr, delimiter: impl Into<String>) -> Self {
237        Expr::StringAgg {
238            expr: Box::new(expr),
239            delimiter: delimiter.into(),
240            distinct: false,
241            filter: None,
242            order_by: None,
243        }
244    }
245
246    /// JSON text extraction: `expr->>'path'`.
247    pub fn json_path_text(expr: Expr, path: impl Into<String>) -> Self {
248        Expr::JsonPathText {
249            expr: Box::new(expr),
250            path: path.into(),
251        }
252    }
253
254    /// Current timestamp.
255    pub fn now() -> Self {
256        Expr::Now
257    }
258}
259
260impl From<Value> for Expr {
261    fn from(v: Value) -> Self {
262        Expr::Value(v)
263    }
264}
265
266impl From<FieldRef> for Expr {
267    fn from(f: FieldRef) -> Self {
268        Expr::Field(f)
269    }
270}
271
272/// Binary operators.
273#[derive(Debug, Clone)]
274pub enum BinaryOp {
275    Add,
276    Sub,
277    Mul,
278    Div,
279    Mod,
280    BitwiseAnd,
281    BitwiseOr,
282    ShiftLeft,
283    ShiftRight,
284    Concat,
285
286    /// User-defined binary operator (extension point).
287    Custom(Box<dyn CustomBinaryOp>),
288}
289
290/// Unary operators.
291#[derive(Debug, Clone, Copy, PartialEq, Eq)]
292pub enum UnaryOp {
293    Neg,
294    Not,
295    BitwiseNot,
296}
297
298/// Aggregate function definition.
299#[derive(Debug, Clone)]
300pub struct AggregationDef {
301    pub name: String,
302    pub expression: Option<Box<Expr>>,
303    pub distinct: bool,
304    pub filter: Option<Conditions>,
305    pub args: Option<Vec<Expr>>,
306    pub order_by: Option<Vec<OrderByDef>>,
307}
308
309impl AggregationDef {
310    pub fn new(name: impl Into<String>, expr: Expr) -> Self {
311        Self {
312            name: name.into(),
313            expression: Some(Box::new(expr)),
314            distinct: false,
315            filter: None,
316            args: None,
317            order_by: None,
318        }
319    }
320
321    pub fn count_all() -> Self {
322        Self {
323            name: "COUNT".into(),
324            expression: None,
325            distinct: false,
326            filter: None,
327            args: None,
328            order_by: None,
329        }
330    }
331
332    pub fn distinct(mut self) -> Self {
333        self.distinct = true;
334        self
335    }
336
337    pub fn filter(mut self, cond: Conditions) -> Self {
338        self.filter = Some(cond);
339        self
340    }
341
342    pub fn order_by(mut self, order: Vec<OrderByDef>) -> Self {
343        self.order_by = Some(order);
344        self
345    }
346}
347
348/// CASE expression.
349#[derive(Debug, Clone)]
350pub struct CaseDef {
351    pub cases: Vec<WhenClause>,
352    pub default: Option<Box<Expr>>,
353}
354
355/// WHEN condition THEN result.
356#[derive(Debug, Clone)]
357pub struct WhenClause {
358    pub condition: Conditions,
359    pub result: Expr,
360}
361
362/// Window function definition.
363#[derive(Debug, Clone)]
364pub struct WindowDef {
365    pub expression: Box<Expr>,
366    pub partition_by: Option<Vec<Expr>>,
367    pub order_by: Option<Vec<OrderByDef>>,
368    pub frame: Option<WindowFrameDef>,
369}
370
371/// Window frame specification.
372#[derive(Debug, Clone)]
373pub struct WindowFrameDef {
374    pub frame_type: WindowFrameType,
375    pub start: WindowFrameBound,
376    pub end: Option<WindowFrameBound>,
377}
378
379/// Window frame type.
380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
381pub enum WindowFrameType {
382    Rows,
383    Range,
384    Groups,
385}
386
387/// Window frame bound.
388#[derive(Debug, Clone, PartialEq, Eq)]
389pub enum WindowFrameBound {
390    CurrentRow,
391    Preceding(Option<u64>),
392    Following(Option<u64>),
393}