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    /// Raw SQL expression — escape hatch for expressions that cannot be
181    /// reverse-parsed into typed AST nodes (e.g. from pg_policies introspection).
182    /// Prefer typed variants wherever possible.
183    Raw(String),
184}
185
186impl std::fmt::Display for Expr {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            Expr::Star => write!(f, "*"),
190            Expr::Named(name) => write!(f, "{}", name),
191            Expr::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
192            Expr::Aggregate {
193                col,
194                func,
195                distinct,
196                filter,
197                alias,
198            } => {
199                if *distinct {
200                    write!(f, "{}(DISTINCT {})", func, col)?;
201                } else {
202                    write!(f, "{}({})", func, col)?;
203                }
204                if let Some(conditions) = filter {
205                    write!(
206                        f,
207                        " FILTER (WHERE {})",
208                        conditions
209                            .iter()
210                            .map(|c| c.to_string())
211                            .collect::<Vec<_>>()
212                            .join(" AND ")
213                    )?;
214                }
215                if let Some(a) = alias {
216                    write!(f, " AS {}", a)?;
217                }
218                Ok(())
219            }
220            Expr::Cast {
221                expr,
222                target_type,
223                alias,
224            } => {
225                write!(f, "{}::{}", expr, target_type)?;
226                if let Some(a) = alias {
227                    write!(f, " AS {}", a)?;
228                }
229                Ok(())
230            }
231            Expr::Def {
232                name,
233                data_type,
234                constraints,
235            } => {
236                write!(f, "{}:{}", name, data_type)?;
237                for c in constraints {
238                    write!(f, "^{}", c)?;
239                }
240                Ok(())
241            }
242            Expr::Mod { kind, col } => match kind {
243                ModKind::Add => write!(f, "+{}", col),
244                ModKind::Drop => write!(f, "-{}", col),
245            },
246            Expr::Window {
247                name,
248                func,
249                params,
250                partition,
251                order,
252                frame,
253            } => {
254                write!(f, "{}:{}(", name, func)?;
255                for (i, p) in params.iter().enumerate() {
256                    if i > 0 {
257                        write!(f, ", ")?;
258                    }
259                    write!(f, "{}", p)?;
260                }
261                write!(f, ")")?;
262
263                // Print partitions if any
264                if !partition.is_empty() {
265                    write!(f, "{{Part=")?;
266                    for (i, p) in partition.iter().enumerate() {
267                        if i > 0 {
268                            write!(f, ",")?;
269                        }
270                        write!(f, "{}", p)?;
271                    }
272                    if let Some(fr) = frame {
273                        write!(f, ", Frame={:?}", fr)?; // Debug format for now
274                    }
275                    write!(f, "}}")?;
276                } else if frame.is_some() {
277                    write!(f, "{{Frame={:?}}}", frame.as_ref().unwrap())?;
278                }
279
280                // Print order cages
281                for _cage in order {
282                    // Order cages are sort cages - display format TBD
283                }
284                Ok(())
285            }
286            Expr::Case {
287                when_clauses,
288                else_value,
289                alias,
290            } => {
291                write!(f, "CASE")?;
292                for (cond, val) in when_clauses {
293                    write!(f, " WHEN {} THEN {}", cond.left, val)?;
294                }
295                if let Some(e) = else_value {
296                    write!(f, " ELSE {}", e)?;
297                }
298                write!(f, " END")?;
299                if let Some(a) = alias {
300                    write!(f, " AS {}", a)?;
301                }
302                Ok(())
303            }
304            Expr::JsonAccess {
305                column,
306                path_segments,
307                alias,
308            } => {
309                write!(f, "{}", column)?;
310                for (path, as_text) in path_segments {
311                    let op = if *as_text { "->>" } else { "->" };
312                    // Integer indices should NOT be quoted (array access)
313                    // String keys should be quoted (object access)
314                    if path.parse::<i64>().is_ok() {
315                        write!(f, "{}{}", op, path)?;
316                    } else {
317                        write!(f, "{}'{}'", op, path)?;
318                    }
319                }
320                if let Some(a) = alias {
321                    write!(f, " AS {}", a)?;
322                }
323                Ok(())
324            }
325            Expr::FunctionCall { name, args, alias } => {
326                let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
327                write!(f, "{}({})", name.to_uppercase(), args_str.join(", "))?;
328                if let Some(a) = alias {
329                    write!(f, " AS {}", a)?;
330                }
331                Ok(())
332            }
333            Expr::SpecialFunction { name, args, alias } => {
334                write!(f, "{}(", name.to_uppercase())?;
335                for (i, (keyword, expr)) in args.iter().enumerate() {
336                    if i > 0 {
337                        write!(f, " ")?;
338                    }
339                    if let Some(kw) = keyword {
340                        write!(f, "{} ", kw)?;
341                    }
342                    write!(f, "{}", expr)?;
343                }
344                write!(f, ")")?;
345                if let Some(a) = alias {
346                    write!(f, " AS {}", a)?;
347                }
348                Ok(())
349            }
350            Expr::Binary {
351                left,
352                op,
353                right,
354                alias,
355            } => {
356                write!(f, "({} {} {})", left, op, right)?;
357                if let Some(a) = alias {
358                    write!(f, " AS {}", a)?;
359                }
360                Ok(())
361            }
362            Expr::Literal(value) => write!(f, "{}", value),
363            Expr::ArrayConstructor { elements, alias } => {
364                write!(f, "ARRAY[")?;
365                for (i, elem) in elements.iter().enumerate() {
366                    if i > 0 {
367                        write!(f, ", ")?;
368                    }
369                    write!(f, "{}", elem)?;
370                }
371                write!(f, "]")?;
372                if let Some(a) = alias {
373                    write!(f, " AS {}", a)?;
374                }
375                Ok(())
376            }
377            Expr::RowConstructor { elements, alias } => {
378                write!(f, "ROW(")?;
379                for (i, elem) in elements.iter().enumerate() {
380                    if i > 0 {
381                        write!(f, ", ")?;
382                    }
383                    write!(f, "{}", elem)?;
384                }
385                write!(f, ")")?;
386                if let Some(a) = alias {
387                    write!(f, " AS {}", a)?;
388                }
389                Ok(())
390            }
391            Expr::Subscript { expr, index, alias } => {
392                write!(f, "{}[{}]", expr, index)?;
393                if let Some(a) = alias {
394                    write!(f, " AS {}", a)?;
395                }
396                Ok(())
397            }
398            Expr::Collate { expr, collation, alias } => {
399                write!(f, "{} COLLATE \"{}\"", expr, collation)?;
400                if let Some(a) = alias {
401                    write!(f, " AS {}", a)?;
402                }
403                Ok(())
404            }
405            Expr::FieldAccess { expr, field, alias } => {
406                write!(f, "({}).{}", expr, field)?;
407                if let Some(a) = alias {
408                    write!(f, " AS {}", a)?;
409                }
410                Ok(())
411            }
412            Expr::Subquery { query, alias } => {
413                write!(f, "({})", query)?;
414                if let Some(a) = alias {
415                    write!(f, " AS {}", a)?;
416                }
417                Ok(())
418            }
419            Expr::Exists { query, negated, alias } => {
420                if *negated {
421                    write!(f, "NOT ")?;
422                }
423                write!(f, "EXISTS ({})", query)?;
424                if let Some(a) = alias {
425                    write!(f, " AS {}", a)?;
426                }
427                Ok(())
428            }
429            Expr::Raw(sql) => write!(f, "{}", sql),
430        }
431    }
432}
433
434#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
435pub enum Constraint {
436    PrimaryKey,
437    Unique,
438    Nullable,
439    Default(String),
440    Check(Vec<String>),
441    Comment(String),
442    References(String),
443    Generated(ColumnGeneration),
444}
445
446/// Generated column type (STORED or VIRTUAL)
447#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448pub enum ColumnGeneration {
449    /// GENERATED ALWAYS AS (expr) STORED - computed and stored
450    Stored(String),
451    /// GENERATED ALWAYS AS (expr) - computed at query time (default in Postgres 18+)
452    Virtual(String),
453}
454
455/// Window frame definition for window functions
456#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
457pub enum WindowFrame {
458    /// ROWS BETWEEN start AND end
459    Rows { start: FrameBound, end: FrameBound },
460    /// RANGE BETWEEN start AND end
461    Range { start: FrameBound, end: FrameBound },
462}
463
464/// Window frame boundary
465#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
466pub enum FrameBound {
467    UnboundedPreceding,
468    Preceding(i32),
469    CurrentRow,
470    Following(i32),
471    UnboundedFollowing,
472}
473
474impl std::fmt::Display for Constraint {
475    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
476        match self {
477            Constraint::PrimaryKey => write!(f, "pk"),
478            Constraint::Unique => write!(f, "uniq"),
479            Constraint::Nullable => write!(f, "?"),
480            Constraint::Default(val) => write!(f, "={}", val),
481            Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
482            Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
483            Constraint::References(target) => write!(f, "ref({})", target),
484            Constraint::Generated(generation) => match generation {
485                ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
486                ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
487            },
488        }
489    }
490}
491
492/// Index definition for CREATE INDEX
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
494pub struct IndexDef {
495    /// Index name
496    pub name: String,
497    /// Target table
498    pub table: String,
499    /// Columns to index (ordered)
500    pub columns: Vec<String>,
501    pub unique: bool,
502    /// Index type (e.g., "keyword", "integer", "float", "geo", "text")
503    #[serde(default)]
504    pub index_type: Option<String>,
505}
506
507/// Table-level constraints for composite keys
508#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
509pub enum TableConstraint {
510    Unique(Vec<String>),
511    PrimaryKey(Vec<String>),
512}
513
514// ==================== From Implementations for Ergonomic API ====================
515
516impl From<&str> for Expr {
517    /// Convert a string reference to a Named expression.
518    /// Enables: `.select(["id", "name"])` instead of `.select([col("id"), col("name")])`
519    fn from(s: &str) -> Self {
520        Expr::Named(s.to_string())
521    }
522}
523
524impl From<String> for Expr {
525    fn from(s: String) -> Self {
526        Expr::Named(s)
527    }
528}
529
530impl From<&String> for Expr {
531    fn from(s: &String) -> Self {
532        Expr::Named(s.clone())
533    }
534}
535
536// ==================== Function and Trigger Definitions ====================
537
538/// PostgreSQL function definition
539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
540pub struct FunctionDef {
541    pub name: String,
542    pub returns: String,  // e.g., "trigger", "integer", "void"
543    pub body: String,     // The function body (PL/pgSQL code)
544    pub language: Option<String>,  // Default: plpgsql
545}
546
547/// Trigger timing (BEFORE or AFTER)
548#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
549pub enum TriggerTiming {
550    Before,
551    After,
552    InsteadOf,
553}
554
555/// Trigger event types
556#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
557pub enum TriggerEvent {
558    Insert,
559    Update,
560    Delete,
561    Truncate,
562}
563
564/// PostgreSQL trigger definition
565#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
566pub struct TriggerDef {
567    pub name: String,
568    pub table: String,
569    pub timing: TriggerTiming,
570    pub events: Vec<TriggerEvent>,
571    pub for_each_row: bool,
572    pub execute_function: String,
573}
574