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}