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