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        params: Vec<Value>,
61        partition: Vec<String>,
62        order: Vec<Cage>,
63        frame: Option<WindowFrame>,
64    },
65    /// CASE WHEN expression
66    Case {
67        /// WHEN condition THEN expr pairs (Expr allows functions, values, identifiers)
68        when_clauses: Vec<(Condition, Box<Expr>)>,
69        /// ELSE expr (optional)
70        else_value: Option<Box<Expr>>,
71        /// Optional alias
72        alias: Option<String>,
73    },
74    /// JSON accessor (data->>'key' or data->'key' or chained data->'a'->0->>'b')
75    JsonAccess {
76        /// Base column name
77        column: String,
78        /// JSON path segments: (key, as_text)
79        /// as_text: true for ->> (extract as text), false for -> (extract as JSON)
80        /// For chained access like x->'a'->0->>'b', this is [("a", false), ("0", false), ("b", true)]
81        path_segments: Vec<(String, bool)>,
82        /// Optional alias
83        alias: Option<String>,
84    },
85    /// Function call expression (COALESCE, NULLIF, etc.)
86    FunctionCall {
87        /// Function name (coalesce, nullif, etc.)
88        name: String,
89        /// Arguments to the function (now supports nested expressions)
90        args: Vec<Expr>,
91        /// Optional alias
92        alias: Option<String>,
93    },
94    /// Special SQL function with keyword arguments (SUBSTRING, EXTRACT, TRIM, etc.)
95    /// e.g., SUBSTRING(expr FROM pos [FOR len]), EXTRACT(YEAR FROM date)
96    SpecialFunction {
97        /// Function name (SUBSTRING, EXTRACT, TRIM, etc.)
98        name: String,
99        /// Arguments as (optional_keyword, expr) pairs
100        /// e.g., [(None, col), (Some("FROM"), 2), (Some("FOR"), 5)]
101        args: Vec<(Option<String>, Box<Expr>)>,
102        /// Optional alias
103        alias: Option<String>,
104    },
105    /// Binary expression (left op right)
106    Binary {
107        left: Box<Expr>,
108        op: BinaryOp,
109        right: Box<Expr>,
110        alias: Option<String>,
111    },
112    /// Literal value (string, number) for use in expressions
113    /// e.g., '62', 0, 'active'
114    Literal(Value),
115}
116
117impl std::fmt::Display for Expr {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        match self {
120            Expr::Star => write!(f, "*"),
121            Expr::Named(name) => write!(f, "{}", name),
122            Expr::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
123            Expr::Aggregate {
124                col,
125                func,
126                distinct,
127                filter,
128                alias,
129            } => {
130                if *distinct {
131                    write!(f, "{}(DISTINCT {})", func, col)?;
132                } else {
133                    write!(f, "{}({})", func, col)?;
134                }
135                if let Some(conditions) = filter {
136                    write!(
137                        f,
138                        " FILTER (WHERE {})",
139                        conditions
140                            .iter()
141                            .map(|c| c.to_string())
142                            .collect::<Vec<_>>()
143                            .join(" AND ")
144                    )?;
145                }
146                if let Some(a) = alias {
147                    write!(f, " AS {}", a)?;
148                }
149                Ok(())
150            }
151            Expr::Cast {
152                expr,
153                target_type,
154                alias,
155            } => {
156                write!(f, "{}::{}", expr, target_type)?;
157                if let Some(a) = alias {
158                    write!(f, " AS {}", a)?;
159                }
160                Ok(())
161            }
162            Expr::Def {
163                name,
164                data_type,
165                constraints,
166            } => {
167                write!(f, "{}:{}", name, data_type)?;
168                for c in constraints {
169                    write!(f, "^{}", c)?;
170                }
171                Ok(())
172            }
173            Expr::Mod { kind, col } => match kind {
174                ModKind::Add => write!(f, "+{}", col),
175                ModKind::Drop => write!(f, "-{}", col),
176            },
177            Expr::Window {
178                name,
179                func,
180                params,
181                partition,
182                order,
183                frame,
184            } => {
185                write!(f, "{}:{}(", name, func)?;
186                for (i, p) in params.iter().enumerate() {
187                    if i > 0 {
188                        write!(f, ", ")?;
189                    }
190                    write!(f, "{}", p)?;
191                }
192                write!(f, ")")?;
193
194                // Print partitions if any
195                if !partition.is_empty() {
196                    write!(f, "{{Part=")?;
197                    for (i, p) in partition.iter().enumerate() {
198                        if i > 0 {
199                            write!(f, ",")?;
200                        }
201                        write!(f, "{}", p)?;
202                    }
203                    if let Some(fr) = frame {
204                        write!(f, ", Frame={:?}", fr)?; // Debug format for now
205                    }
206                    write!(f, "}}")?;
207                } else if frame.is_some() {
208                    write!(f, "{{Frame={:?}}}", frame.as_ref().unwrap())?;
209                }
210
211                // Print order cages
212                for _cage in order {
213                    // Order cages are sort cages - display format TBD
214                }
215                Ok(())
216            }
217            Expr::Case {
218                when_clauses,
219                else_value,
220                alias,
221            } => {
222                write!(f, "CASE")?;
223                for (cond, val) in when_clauses {
224                    write!(f, " WHEN {} THEN {}", cond.left, val)?;
225                }
226                if let Some(e) = else_value {
227                    write!(f, " ELSE {}", e)?;
228                }
229                write!(f, " END")?;
230                if let Some(a) = alias {
231                    write!(f, " AS {}", a)?;
232                }
233                Ok(())
234            }
235            Expr::JsonAccess {
236                column,
237                path_segments,
238                alias,
239            } => {
240                write!(f, "{}", column)?;
241                for (path, as_text) in path_segments {
242                    let op = if *as_text { "->>" } else { "->" };
243                    // Integer indices should NOT be quoted (array access)
244                    // String keys should be quoted (object access)
245                    if path.parse::<i64>().is_ok() {
246                        write!(f, "{}{}", op, path)?;
247                    } else {
248                        write!(f, "{}'{}'", op, path)?;
249                    }
250                }
251                if let Some(a) = alias {
252                    write!(f, " AS {}", a)?;
253                }
254                Ok(())
255            }
256            Expr::FunctionCall { name, args, alias } => {
257                let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
258                write!(f, "{}({})", name.to_uppercase(), args_str.join(", "))?;
259                if let Some(a) = alias {
260                    write!(f, " AS {}", a)?;
261                }
262                Ok(())
263            }
264            Expr::SpecialFunction { name, args, alias } => {
265                write!(f, "{}(", name.to_uppercase())?;
266                for (i, (keyword, expr)) in args.iter().enumerate() {
267                    if i > 0 {
268                        write!(f, " ")?;
269                    }
270                    if let Some(kw) = keyword {
271                        write!(f, "{} ", kw)?;
272                    }
273                    write!(f, "{}", expr)?;
274                }
275                write!(f, ")")?;
276                if let Some(a) = alias {
277                    write!(f, " AS {}", a)?;
278                }
279                Ok(())
280            }
281            Expr::Binary {
282                left,
283                op,
284                right,
285                alias,
286            } => {
287                write!(f, "({} {} {})", left, op, right)?;
288                if let Some(a) = alias {
289                    write!(f, " AS {}", a)?;
290                }
291                Ok(())
292            }
293            Expr::Literal(value) => write!(f, "{}", value),
294        }
295    }
296}
297
298#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
299pub enum Constraint {
300    PrimaryKey,
301    Unique,
302    Nullable,
303    Default(String),
304    Check(Vec<String>),
305    Comment(String),
306    References(String),
307    Generated(ColumnGeneration),
308}
309
310/// Generated column type (STORED or VIRTUAL)
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312pub enum ColumnGeneration {
313    /// GENERATED ALWAYS AS (expr) STORED - computed and stored
314    Stored(String),
315    /// GENERATED ALWAYS AS (expr) - computed at query time (default in Postgres 18+)
316    Virtual(String),
317}
318
319/// Window frame definition for window functions
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
321pub enum WindowFrame {
322    /// ROWS BETWEEN start AND end
323    Rows { start: FrameBound, end: FrameBound },
324    /// RANGE BETWEEN start AND end
325    Range { start: FrameBound, end: FrameBound },
326}
327
328/// Window frame boundary
329#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
330pub enum FrameBound {
331    UnboundedPreceding,
332    Preceding(i32),
333    CurrentRow,
334    Following(i32),
335    UnboundedFollowing,
336}
337
338impl std::fmt::Display for Constraint {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        match self {
341            Constraint::PrimaryKey => write!(f, "pk"),
342            Constraint::Unique => write!(f, "uniq"),
343            Constraint::Nullable => write!(f, "?"),
344            Constraint::Default(val) => write!(f, "={}", val),
345            Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
346            Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
347            Constraint::References(target) => write!(f, "ref({})", target),
348            Constraint::Generated(generation) => match generation {
349                ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
350                ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
351            },
352        }
353    }
354}
355
356/// Index definition for CREATE INDEX
357#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
358pub struct IndexDef {
359    /// Index name
360    pub name: String,
361    /// Target table
362    pub table: String,
363    /// Columns to index (ordered)
364    pub columns: Vec<String>,
365    pub unique: bool,
366}
367
368/// Table-level constraints for composite keys
369#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
370pub enum TableConstraint {
371    Unique(Vec<String>),
372    PrimaryKey(Vec<String>),
373}
374
375// ==================== From Implementations for Ergonomic API ====================
376
377impl From<&str> for Expr {
378    /// Convert a string reference to a Named expression.
379    /// Enables: `.select(["id", "name"])` instead of `.select([col("id"), col("name")])`
380    fn from(s: &str) -> Self {
381        Expr::Named(s.to_string())
382    }
383}
384
385impl From<String> for Expr {
386    fn from(s: String) -> Self {
387        Expr::Named(s)
388    }
389}
390
391impl From<&String> for Expr {
392    fn from(s: &String) -> Self {
393        Expr::Named(s.clone())
394    }
395}