qail_core/ast/
expr.rs

1use crate::ast::{AggregateFunc, Cage, Condition, ModKind, Value};
2use serde::{Deserialize, Serialize};
3
4/// Binary operators for expressions
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
6pub enum BinaryOp {
7    Concat,
8    Add,
9    Sub,
10    Mul,
11    Div,
12    /// Modulo (%)
13    Rem,
14}
15
16impl std::fmt::Display for BinaryOp {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        match self {
19            BinaryOp::Concat => write!(f, "||"),
20            BinaryOp::Add => write!(f, "+"),
21            BinaryOp::Sub => write!(f, "-"),
22            BinaryOp::Mul => write!(f, "*"),
23            BinaryOp::Div => write!(f, "/"),
24            BinaryOp::Rem => write!(f, "%"),
25        }
26    }
27}
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub enum Expr {
30    /// All columns (*)
31    Star,
32    Named(String),
33    /// An aliased expression (expr AS alias)
34    Aliased { name: String, alias: String },
35    /// An aggregate function (COUNT(col)) with optional FILTER and DISTINCT
36    Aggregate {
37        col: String,
38        func: AggregateFunc,
39        distinct: bool,
40        /// PostgreSQL FILTER (WHERE ...) clause for aggregates
41        filter: Option<Vec<Condition>>,
42        alias: Option<String>,
43    },
44    /// Type cast expression (expr::type)
45    Cast {
46        expr: Box<Expr>,
47        target_type: String,
48        alias: Option<String>,
49    },
50    Def {
51        name: String,
52        data_type: String,
53        constraints: Vec<Constraint>,
54    },
55    Mod { kind: ModKind, col: Box<Expr> },
56    /// Window Function Definition
57    Window {
58        name: String,
59        func: String,
60        /// Function arguments as expressions (e.g., for SUM(amount), use Expr::Named("amount"))
61        params: Vec<Expr>,
62        partition: Vec<String>,
63        order: Vec<Cage>,
64        frame: Option<WindowFrame>,
65    },
66    /// CASE WHEN expression
67    Case {
68        /// WHEN condition THEN expr pairs (Expr allows functions, values, identifiers)
69        when_clauses: Vec<(Condition, Box<Expr>)>,
70        /// ELSE expr (optional)
71        else_value: Option<Box<Expr>>,
72        /// Optional alias
73        alias: Option<String>,
74    },
75    /// JSON accessor (data->>'key' or data->'key' or chained data->'a'->0->>'b')
76    JsonAccess {
77        /// Base column name
78        column: String,
79        /// JSON path segments: (key, as_text)
80        /// as_text: true for ->> (extract as text), false for -> (extract as JSON)
81        /// For chained access like x->'a'->0->>'b', this is [("a", false), ("0", false), ("b", true)]
82        path_segments: Vec<(String, bool)>,
83        /// Optional alias
84        alias: Option<String>,
85    },
86    /// Function call expression (COALESCE, NULLIF, etc.)
87    FunctionCall {
88        /// Function name (coalesce, nullif, etc.)
89        name: String,
90        /// Arguments to the function (now supports nested expressions)
91        args: Vec<Expr>,
92        /// Optional alias
93        alias: Option<String>,
94    },
95    /// Special SQL function with keyword arguments (SUBSTRING, EXTRACT, TRIM, etc.)
96    /// e.g., SUBSTRING(expr FROM pos [FOR len]), EXTRACT(YEAR FROM date)
97    SpecialFunction {
98        /// Function name (SUBSTRING, EXTRACT, TRIM, etc.)
99        name: String,
100        /// Arguments as (optional_keyword, expr) pairs
101        /// e.g., [(None, col), (Some("FROM"), 2), (Some("FOR"), 5)]
102        args: Vec<(Option<String>, Box<Expr>)>,
103        /// Optional alias
104        alias: Option<String>,
105    },
106    /// Binary expression (left op right)
107    Binary {
108        left: Box<Expr>,
109        op: BinaryOp,
110        right: Box<Expr>,
111        alias: Option<String>,
112    },
113    /// Literal value (string, number) for use in expressions
114    /// e.g., '62', 0, 'active'
115    Literal(Value),
116    /// Array constructor: ARRAY[expr1, expr2, ...]
117    ArrayConstructor {
118        elements: Vec<Expr>,
119        alias: Option<String>,
120    },
121    /// Row constructor: ROW(expr1, expr2, ...) or (expr1, expr2, ...)
122    RowConstructor {
123        elements: Vec<Expr>,
124        alias: Option<String>,
125    },
126    /// Array/string subscript: arr[index]
127    Subscript {
128        expr: Box<Expr>,
129        index: Box<Expr>,
130        alias: Option<String>,
131    },
132    /// Collation: expr COLLATE "collation_name"
133    Collate {
134        expr: Box<Expr>,
135        collation: String,
136        alias: Option<String>,
137    },
138    /// Field selection from composite: (row).field
139    FieldAccess {
140        expr: Box<Expr>,
141        field: String,
142        alias: Option<String>,
143    },
144}
145
146impl std::fmt::Display for Expr {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        match self {
149            Expr::Star => write!(f, "*"),
150            Expr::Named(name) => write!(f, "{}", name),
151            Expr::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
152            Expr::Aggregate {
153                col,
154                func,
155                distinct,
156                filter,
157                alias,
158            } => {
159                if *distinct {
160                    write!(f, "{}(DISTINCT {})", func, col)?;
161                } else {
162                    write!(f, "{}({})", func, col)?;
163                }
164                if let Some(conditions) = filter {
165                    write!(
166                        f,
167                        " FILTER (WHERE {})",
168                        conditions
169                            .iter()
170                            .map(|c| c.to_string())
171                            .collect::<Vec<_>>()
172                            .join(" AND ")
173                    )?;
174                }
175                if let Some(a) = alias {
176                    write!(f, " AS {}", a)?;
177                }
178                Ok(())
179            }
180            Expr::Cast {
181                expr,
182                target_type,
183                alias,
184            } => {
185                write!(f, "{}::{}", expr, target_type)?;
186                if let Some(a) = alias {
187                    write!(f, " AS {}", a)?;
188                }
189                Ok(())
190            }
191            Expr::Def {
192                name,
193                data_type,
194                constraints,
195            } => {
196                write!(f, "{}:{}", name, data_type)?;
197                for c in constraints {
198                    write!(f, "^{}", c)?;
199                }
200                Ok(())
201            }
202            Expr::Mod { kind, col } => match kind {
203                ModKind::Add => write!(f, "+{}", col),
204                ModKind::Drop => write!(f, "-{}", col),
205            },
206            Expr::Window {
207                name,
208                func,
209                params,
210                partition,
211                order,
212                frame,
213            } => {
214                write!(f, "{}:{}(", name, func)?;
215                for (i, p) in params.iter().enumerate() {
216                    if i > 0 {
217                        write!(f, ", ")?;
218                    }
219                    write!(f, "{}", p)?;
220                }
221                write!(f, ")")?;
222
223                // Print partitions if any
224                if !partition.is_empty() {
225                    write!(f, "{{Part=")?;
226                    for (i, p) in partition.iter().enumerate() {
227                        if i > 0 {
228                            write!(f, ",")?;
229                        }
230                        write!(f, "{}", p)?;
231                    }
232                    if let Some(fr) = frame {
233                        write!(f, ", Frame={:?}", fr)?; // Debug format for now
234                    }
235                    write!(f, "}}")?;
236                } else if frame.is_some() {
237                    write!(f, "{{Frame={:?}}}", frame.as_ref().unwrap())?;
238                }
239
240                // Print order cages
241                for _cage in order {
242                    // Order cages are sort cages - display format TBD
243                }
244                Ok(())
245            }
246            Expr::Case {
247                when_clauses,
248                else_value,
249                alias,
250            } => {
251                write!(f, "CASE")?;
252                for (cond, val) in when_clauses {
253                    write!(f, " WHEN {} THEN {}", cond.left, val)?;
254                }
255                if let Some(e) = else_value {
256                    write!(f, " ELSE {}", e)?;
257                }
258                write!(f, " END")?;
259                if let Some(a) = alias {
260                    write!(f, " AS {}", a)?;
261                }
262                Ok(())
263            }
264            Expr::JsonAccess {
265                column,
266                path_segments,
267                alias,
268            } => {
269                write!(f, "{}", column)?;
270                for (path, as_text) in path_segments {
271                    let op = if *as_text { "->>" } else { "->" };
272                    // Integer indices should NOT be quoted (array access)
273                    // String keys should be quoted (object access)
274                    if path.parse::<i64>().is_ok() {
275                        write!(f, "{}{}", op, path)?;
276                    } else {
277                        write!(f, "{}'{}'", op, path)?;
278                    }
279                }
280                if let Some(a) = alias {
281                    write!(f, " AS {}", a)?;
282                }
283                Ok(())
284            }
285            Expr::FunctionCall { name, args, alias } => {
286                let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
287                write!(f, "{}({})", name.to_uppercase(), args_str.join(", "))?;
288                if let Some(a) = alias {
289                    write!(f, " AS {}", a)?;
290                }
291                Ok(())
292            }
293            Expr::SpecialFunction { name, args, alias } => {
294                write!(f, "{}(", name.to_uppercase())?;
295                for (i, (keyword, expr)) in args.iter().enumerate() {
296                    if i > 0 {
297                        write!(f, " ")?;
298                    }
299                    if let Some(kw) = keyword {
300                        write!(f, "{} ", kw)?;
301                    }
302                    write!(f, "{}", expr)?;
303                }
304                write!(f, ")")?;
305                if let Some(a) = alias {
306                    write!(f, " AS {}", a)?;
307                }
308                Ok(())
309            }
310            Expr::Binary {
311                left,
312                op,
313                right,
314                alias,
315            } => {
316                write!(f, "({} {} {})", left, op, right)?;
317                if let Some(a) = alias {
318                    write!(f, " AS {}", a)?;
319                }
320                Ok(())
321            }
322            Expr::Literal(value) => write!(f, "{}", value),
323            Expr::ArrayConstructor { elements, alias } => {
324                write!(f, "ARRAY[")?;
325                for (i, elem) in elements.iter().enumerate() {
326                    if i > 0 {
327                        write!(f, ", ")?;
328                    }
329                    write!(f, "{}", elem)?;
330                }
331                write!(f, "]")?;
332                if let Some(a) = alias {
333                    write!(f, " AS {}", a)?;
334                }
335                Ok(())
336            }
337            Expr::RowConstructor { elements, alias } => {
338                write!(f, "ROW(")?;
339                for (i, elem) in elements.iter().enumerate() {
340                    if i > 0 {
341                        write!(f, ", ")?;
342                    }
343                    write!(f, "{}", elem)?;
344                }
345                write!(f, ")")?;
346                if let Some(a) = alias {
347                    write!(f, " AS {}", a)?;
348                }
349                Ok(())
350            }
351            Expr::Subscript { expr, index, alias } => {
352                write!(f, "{}[{}]", expr, index)?;
353                if let Some(a) = alias {
354                    write!(f, " AS {}", a)?;
355                }
356                Ok(())
357            }
358            Expr::Collate { expr, collation, alias } => {
359                write!(f, "{} COLLATE \"{}\"", expr, collation)?;
360                if let Some(a) = alias {
361                    write!(f, " AS {}", a)?;
362                }
363                Ok(())
364            }
365            Expr::FieldAccess { expr, field, alias } => {
366                write!(f, "({}).{}", expr, field)?;
367                if let Some(a) = alias {
368                    write!(f, " AS {}", a)?;
369                }
370                Ok(())
371            }
372        }
373    }
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377pub enum Constraint {
378    PrimaryKey,
379    Unique,
380    Nullable,
381    Default(String),
382    Check(Vec<String>),
383    Comment(String),
384    References(String),
385    Generated(ColumnGeneration),
386}
387
388/// Generated column type (STORED or VIRTUAL)
389#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
390pub enum ColumnGeneration {
391    /// GENERATED ALWAYS AS (expr) STORED - computed and stored
392    Stored(String),
393    /// GENERATED ALWAYS AS (expr) - computed at query time (default in Postgres 18+)
394    Virtual(String),
395}
396
397/// Window frame definition for window functions
398#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
399pub enum WindowFrame {
400    /// ROWS BETWEEN start AND end
401    Rows { start: FrameBound, end: FrameBound },
402    /// RANGE BETWEEN start AND end
403    Range { start: FrameBound, end: FrameBound },
404}
405
406/// Window frame boundary
407#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
408pub enum FrameBound {
409    UnboundedPreceding,
410    Preceding(i32),
411    CurrentRow,
412    Following(i32),
413    UnboundedFollowing,
414}
415
416impl std::fmt::Display for Constraint {
417    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
418        match self {
419            Constraint::PrimaryKey => write!(f, "pk"),
420            Constraint::Unique => write!(f, "uniq"),
421            Constraint::Nullable => write!(f, "?"),
422            Constraint::Default(val) => write!(f, "={}", val),
423            Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
424            Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
425            Constraint::References(target) => write!(f, "ref({})", target),
426            Constraint::Generated(generation) => match generation {
427                ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
428                ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
429            },
430        }
431    }
432}
433
434/// Index definition for CREATE INDEX
435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
436pub struct IndexDef {
437    /// Index name
438    pub name: String,
439    /// Target table
440    pub table: String,
441    /// Columns to index (ordered)
442    pub columns: Vec<String>,
443    pub unique: bool,
444}
445
446/// Table-level constraints for composite keys
447#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448pub enum TableConstraint {
449    Unique(Vec<String>),
450    PrimaryKey(Vec<String>),
451}
452
453// ==================== From Implementations for Ergonomic API ====================
454
455impl From<&str> for Expr {
456    /// Convert a string reference to a Named expression.
457    /// Enables: `.select(["id", "name"])` instead of `.select([col("id"), col("name")])`
458    fn from(s: &str) -> Self {
459        Expr::Named(s.to_string())
460    }
461}
462
463impl From<String> for Expr {
464    fn from(s: String) -> Self {
465        Expr::Named(s)
466    }
467}
468
469impl From<&String> for Expr {
470    fn from(s: &String) -> Self {
471        Expr::Named(s.clone())
472    }
473}