Skip to main content

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