Skip to main content

qail_core/ast/
expr.rs

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