qail_core/ast/
expr.rs

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