qail_core/
ast.rs

1//! Abstract Syntax Tree for QAIL commands.
2//!
3//! This module defines the core data structures that represent
4//! a parsed QAIL query.
5
6use serde::{Deserialize, Serialize};
7
8/// The primary command structure representing a parsed QAIL query.
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub struct QailCmd {
11    /// The action to perform (GET, SET, DEL, ADD)
12    pub action: Action,
13    /// Target table name
14    pub table: String,
15    /// Columns to select/return
16    pub columns: Vec<Column>,
17    /// Joins to other tables
18    #[serde(default)]
19    pub joins: Vec<Join>,
20    /// Cages (filters, sorts, limits, payloads)
21    pub cages: Vec<Cage>,
22    /// Whether to use DISTINCT in SELECT
23    #[serde(default)]
24    pub distinct: bool,
25    /// Index definition (for Action::Index)
26    #[serde(default)]
27    pub index_def: Option<IndexDef>,
28    /// Table-level constraints (for Action::Make)
29    #[serde(default)]
30    pub table_constraints: Vec<TableConstraint>,
31    /// Set operations (UNION, INTERSECT, EXCEPT) chained queries
32    #[serde(default)]
33    pub set_ops: Vec<(SetOp, Box<QailCmd>)>,
34    /// HAVING clause conditions (filter on aggregates)
35    #[serde(default)]
36    pub having: Vec<Condition>,
37    /// GROUP BY mode (Simple, Rollup, Cube)
38    #[serde(default)]
39    pub group_by_mode: GroupByMode,
40    /// CTE definitions (for WITH/WITH RECURSIVE queries)
41    #[serde(default)]
42    pub ctes: Vec<CTEDef>,
43    /// DISTINCT ON columns (Postgres-specific)
44    #[serde(default)]
45    pub distinct_on: Vec<String>,
46}
47
48impl QailCmd {
49    /// Create a new GET command for the given table.
50    pub fn get(table: impl Into<String>) -> Self {
51        Self {
52            action: Action::Get,
53            table: table.into(),
54            joins: vec![],
55            columns: vec![],
56            cages: vec![],
57            distinct: false,
58            index_def: None,
59            table_constraints: vec![],
60            set_ops: vec![],
61            having: vec![],
62            group_by_mode: GroupByMode::Simple,
63            ctes: vec![],
64            distinct_on: vec![],
65        }
66    }
67
68    /// Create a new SET (update) command for the given table.
69    pub fn set(table: impl Into<String>) -> Self {
70        Self {
71            action: Action::Set,
72            table: table.into(),
73            joins: vec![],
74            columns: vec![],
75            cages: vec![],
76            distinct: false,
77            index_def: None,
78            table_constraints: vec![],
79            set_ops: vec![],
80            having: vec![],
81            group_by_mode: GroupByMode::Simple,
82            ctes: vec![],
83            distinct_on: vec![],
84        }
85    }
86
87    /// Create a new DEL (delete) command for the given table.
88    pub fn del(table: impl Into<String>) -> Self {
89        Self {
90            action: Action::Del,
91            table: table.into(),
92            joins: vec![],
93            columns: vec![],
94            cages: vec![],
95            distinct: false,
96            index_def: None,
97            table_constraints: vec![],
98            set_ops: vec![],
99            having: vec![],
100            group_by_mode: GroupByMode::Simple,
101            ctes: vec![],
102            distinct_on: vec![],
103        }
104    }
105
106    /// Create a new ADD (insert) command for the given table.
107    pub fn add(table: impl Into<String>) -> Self {
108        Self {
109            action: Action::Add,
110            table: table.into(),
111            joins: vec![],
112            columns: vec![],
113            cages: vec![],
114            distinct: false,
115            index_def: None,
116            table_constraints: vec![],
117            set_ops: vec![],
118            having: vec![],
119            group_by_mode: GroupByMode::Simple,
120            ctes: vec![],
121            distinct_on: vec![],
122        }
123    }
124    /// Add columns to hook (select).
125    pub fn hook(mut self, cols: &[&str]) -> Self {
126        self.columns = cols.iter().map(|c| Column::Named(c.to_string())).collect();
127        self
128    }
129
130    /// Add a filter cage.
131    pub fn cage(mut self, column: &str, value: impl Into<Value>) -> Self {
132        self.cages.push(Cage {
133            kind: CageKind::Filter,
134            conditions: vec![Condition {
135                column: column.to_string(),
136                op: Operator::Eq,
137                value: value.into(),
138                is_array_unnest: false,
139            }],
140            logical_op: LogicalOp::And,
141        });
142        self
143    }
144
145    /// Add a limit cage.
146    pub fn limit(mut self, n: i64) -> Self {
147        self.cages.push(Cage {
148            kind: CageKind::Limit(n as usize),
149            conditions: vec![],
150            logical_op: LogicalOp::And,
151        });
152        self
153    }
154
155    /// Add a sort cage (ascending).
156    pub fn sort_asc(mut self, column: &str) -> Self {
157        self.cages.push(Cage {
158            kind: CageKind::Sort(SortOrder::Asc),
159            conditions: vec![Condition {
160                column: column.to_string(),
161                op: Operator::Eq,
162                value: Value::Null,
163                is_array_unnest: false,
164            }],
165            logical_op: LogicalOp::And,
166        });
167        self
168    }
169
170    /// Add a sort cage (descending).
171    pub fn sort_desc(mut self, column: &str) -> Self {
172        self.cages.push(Cage {
173            kind: CageKind::Sort(SortOrder::Desc),
174            conditions: vec![Condition {
175                column: column.to_string(),
176                op: Operator::Eq,
177                value: Value::Null,
178                is_array_unnest: false,
179            }],
180            logical_op: LogicalOp::And,
181        });
182        self
183    }
184
185    // =========================================================================
186    // CTE (Common Table Expression) Builder Methods
187    // =========================================================================
188
189    /// Wrap this query as a CTE with the given name.
190    /// 
191    /// # Example
192    /// ```ignore
193    /// let cte = QailCmd::get("employees")
194    ///     .hook(&["id", "name"])
195    ///     .cage("manager_id", Value::Null)
196    ///     .as_cte("emp_tree");
197    /// ```
198    pub fn as_cte(self, name: impl Into<String>) -> Self {
199        let cte_name = name.into();
200        let columns: Vec<String> = self.columns.iter().filter_map(|c| {
201            match c {
202                Column::Named(n) => Some(n.clone()),
203                Column::Aliased { alias, .. } => Some(alias.clone()),
204                _ => None,
205            }
206        }).collect();
207        
208        Self {
209            action: Action::With,
210            table: cte_name.clone(),
211            columns: vec![],
212            joins: vec![],
213            cages: vec![],
214            distinct: false,
215            index_def: None,
216            table_constraints: vec![],
217            set_ops: vec![],
218            having: vec![],
219            group_by_mode: GroupByMode::Simple,
220            distinct_on: vec![],
221            ctes: vec![CTEDef {
222                name: cte_name,
223                recursive: false,
224                columns,
225                base_query: Box::new(self),
226                recursive_query: None,
227                source_table: None,
228            }],
229        }
230    }
231
232    /// Make this CTE recursive and add the recursive part.
233    /// 
234    /// # Example
235    /// ```ignore
236    /// let recursive_cte = base_query
237    ///     .as_cte("emp_tree")
238    ///     .recursive(recursive_query);
239    /// ```
240    pub fn recursive(mut self, recursive_part: QailCmd) -> Self {
241        if let Some(cte) = self.ctes.last_mut() {
242            cte.recursive = true;
243            cte.recursive_query = Some(Box::new(recursive_part));
244        }
245        self
246    }
247
248    /// Set the source table for recursive join (self-reference).
249    pub fn from_cte(mut self, cte_name: impl Into<String>) -> Self {
250        if let Some(cte) = self.ctes.last_mut() {
251            cte.source_table = Some(cte_name.into());
252        }
253        self
254    }
255
256    /// Chain a final SELECT from the CTE.
257    /// 
258    /// # Example
259    /// ```ignore
260    /// let final_query = cte.select_from_cte(&["id", "name", "level"]);
261    /// ```
262    pub fn select_from_cte(mut self, columns: &[&str]) -> Self {
263        self.columns = columns.iter().map(|c| Column::Named(c.to_string())).collect();
264        self
265    }
266}
267
268/// A join definition.
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270pub struct Join {
271    pub table: String,
272    pub kind: JoinKind,
273    #[serde(default)]
274    pub on: Option<Vec<Condition>>,
275}
276
277#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub enum JoinKind {
279    Inner,
280    Left,
281    Right,
282    /// LATERAL join (Postgres, MySQL 8+)
283    Lateral,
284    /// FULL OUTER JOIN
285    Full,
286    /// CROSS JOIN
287    Cross,
288}
289
290/// Set operation type for combining queries
291#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
292pub enum SetOp {
293    /// UNION (removes duplicates)
294    Union,
295    /// UNION ALL (keeps duplicates)
296    UnionAll,
297    /// INTERSECT (common rows)
298    Intersect,
299    /// EXCEPT (rows in first but not second)
300    Except,
301}
302
303/// GROUP BY mode for advanced aggregations
304#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
305pub enum GroupByMode {
306    /// Standard GROUP BY
307    #[default]
308    Simple,
309    /// ROLLUP - hierarchical subtotals
310    Rollup,
311    /// CUBE - all combinations of subtotals
312    Cube,
313}
314
315/// CTE (Common Table Expression) definition
316#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
317pub struct CTEDef {
318    /// CTE name (the alias used in the query)
319    pub name: String,
320    /// Whether this is a RECURSIVE CTE
321    pub recursive: bool,
322    /// Column list for the CTE (optional)
323    pub columns: Vec<String>,
324    /// Base query (non-recursive part)
325    pub base_query: Box<QailCmd>,
326    /// Recursive part (UNION ALL with self-reference)
327    pub recursive_query: Option<Box<QailCmd>>,
328    /// Source table for recursive join (references CTE name)
329    pub source_table: Option<String>,
330}
331
332/// A column reference.
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub enum Column {
335    /// All columns (*)
336    Star,
337    /// A named column
338    Named(String),
339    /// An aliased column (col AS alias)
340    Aliased { name: String, alias: String },
341    /// An aggregate function (COUNT(col))
342    Aggregate { col: String, func: AggregateFunc },
343    /// Column Definition (for Make keys)
344    Def {
345        name: String,
346        data_type: String,
347        constraints: Vec<Constraint>,
348    },
349    /// Column Modification (for Mod keys)
350    Mod {
351        kind: ModKind,
352        col: Box<Column>,
353    },
354    /// Window Function Definition
355    Window {
356        name: String,
357        func: String,
358        params: Vec<Value>,
359        partition: Vec<String>,
360        order: Vec<Cage>,
361        frame: Option<WindowFrame>,
362    },
363    /// CASE WHEN expression
364    Case {
365        /// WHEN condition THEN value pairs
366        when_clauses: Vec<(Condition, Value)>,
367        /// ELSE value (optional)
368        else_value: Option<Box<Value>>,
369        /// Optional alias
370        alias: Option<String>,
371    },
372    /// JSON accessor (data->>'key' or data->'key')
373    JsonAccess {
374        /// Base column name
375        column: String,
376        /// JSON path/key
377        path: String,
378        /// true for ->> (as text), false for -> (as JSON)
379        as_text: bool,
380        /// Optional alias
381        alias: Option<String>,
382    },
383    /// Function call expression (COALESCE, NULLIF, etc.)
384    FunctionCall {
385        /// Function name (coalesce, nullif, etc.)
386        name: String,
387        /// Arguments to the function
388        args: Vec<String>,
389        /// Optional alias
390        alias: Option<String>,
391    },
392}
393
394impl std::fmt::Display for Column {
395    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
396        match self {
397            Column::Star => write!(f, "*"),
398            Column::Named(name) => write!(f, "{}", name),
399            Column::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
400            Column::Aggregate { col, func } => write!(f, "{}({})", func, col),
401            Column::Def {
402                name,
403                data_type,
404                constraints,
405            } => {
406                write!(f, "{}:{}", name, data_type)?;
407                for c in constraints {
408                    write!(f, "^{}", c)?;
409                }
410                Ok(())
411            }
412            Column::Mod { kind, col } => match kind {
413                ModKind::Add => write!(f, "+{}", col),
414                ModKind::Drop => write!(f, "-{}", col),
415            },
416            Column::Window { name, func, params, partition, order, frame } => {
417                write!(f, "{}:{}(", name, func)?;
418                for (i, p) in params.iter().enumerate() {
419                    if i > 0 { write!(f, ", ")?; }
420                    write!(f, "{}", p)?;
421                }
422                write!(f, ")")?;
423                
424                // Print partitions if any
425                if !partition.is_empty() {
426                    write!(f, "{{Part=")?;
427                    for (i, p) in partition.iter().enumerate() {
428                        if i > 0 { write!(f, ",")?; }
429                        write!(f, "{}", p)?;
430                    }
431                    if let Some(fr) = frame {
432                        write!(f, ", Frame={:?}", fr)?; // Debug format for now
433                    }
434                    write!(f, "}}")?;
435                } else if frame.is_some() {
436                     write!(f, "{{Frame={:?}}}", frame.as_ref().unwrap())?;
437                }
438
439                // Print order cages
440                for _cage in order {
441                    // Order cages are sort cages - display format TBD
442                }
443                Ok(())
444            }
445            Column::Case { when_clauses, else_value, alias } => {
446                write!(f, "CASE")?;
447                for (cond, val) in when_clauses {
448                    write!(f, " WHEN {} THEN {}", cond.column, val)?;
449                }
450                if let Some(e) = else_value {
451                    write!(f, " ELSE {}", e)?;
452                }
453                write!(f, " END")?;
454                if let Some(a) = alias {
455                    write!(f, " AS {}", a)?;
456                }
457                Ok(())
458            }
459            Column::JsonAccess { column, path, as_text, alias } => {
460                let op = if *as_text { "->>" } else { "->" };
461                write!(f, "{}{}{}", column, op, path)?;
462                if let Some(a) = alias {
463                    write!(f, " AS {}", a)?;
464                }
465                Ok(())
466            }
467            Column::FunctionCall { name, args, alias } => {
468                write!(f, "{}({})", name.to_uppercase(), args.join(", "))?;
469                if let Some(a) = alias {
470                    write!(f, " AS {}", a)?;
471                }
472                Ok(())
473            }
474        }
475    }
476}
477
478/// Column modification type
479#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
480pub enum ModKind {
481    Add,
482    Drop,
483}
484
485/// Column definition constraints
486#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
487pub enum Constraint {
488    PrimaryKey,
489    Unique,
490    Nullable,
491    /// DEFAULT value (e.g., `= uuid()`, `= 0`, `= now()`)
492    Default(String),
493    /// CHECK constraint with allowed values (e.g., `^check("a","b")`)
494    Check(Vec<String>),
495    /// Column comment (COMMENT ON COLUMN)
496    Comment(String),
497    /// Generated column expression (GENERATED ALWAYS AS)
498    Generated(ColumnGeneration),
499}
500
501/// Generated column type (STORED or VIRTUAL)
502#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
503pub enum ColumnGeneration {
504    /// GENERATED ALWAYS AS (expr) STORED - computed and stored
505    Stored(String),
506    /// GENERATED ALWAYS AS (expr) - computed at query time (default in Postgres 18+)
507    Virtual(String),
508}
509
510/// Window frame definition for window functions
511#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
512pub enum WindowFrame {
513    /// ROWS BETWEEN start AND end
514    Rows { start: FrameBound, end: FrameBound },
515    /// RANGE BETWEEN start AND end
516    Range { start: FrameBound, end: FrameBound },
517}
518
519/// Window frame boundary
520#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
521pub enum FrameBound {
522    UnboundedPreceding,
523    Preceding(i32),
524    CurrentRow,
525    Following(i32),
526    UnboundedFollowing,
527}
528
529impl std::fmt::Display for Constraint {
530    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
531        match self {
532            Constraint::PrimaryKey => write!(f, "pk"),
533            Constraint::Unique => write!(f, "uniq"),
534            Constraint::Nullable => write!(f, "?"),
535            Constraint::Default(val) => write!(f, "={}", val),
536            Constraint::Check(vals) => write!(f, "check({})", vals.join(",")),
537            Constraint::Comment(text) => write!(f, "comment(\"{}\")", text),
538            Constraint::Generated(generation) => match generation {
539                ColumnGeneration::Stored(expr) => write!(f, "gen({})", expr),
540                ColumnGeneration::Virtual(expr) => write!(f, "vgen({})", expr),
541            },
542        }
543    }
544}
545
546/// Index definition for CREATE INDEX
547#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
548pub struct IndexDef {
549    /// Index name
550    pub name: String,
551    /// Target table
552    pub table: String,
553    /// Columns to index (ordered)
554    pub columns: Vec<String>,
555    /// Whether this is a UNIQUE index
556    pub unique: bool,
557}
558
559/// Table-level constraints for composite keys
560#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
561pub enum TableConstraint {
562    /// UNIQUE (col1, col2, ...)
563    Unique(Vec<String>),
564    /// PRIMARY KEY (col1, col2, ...)
565    PrimaryKey(Vec<String>),
566}
567
568/// Aggregate functions.
569#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
570pub enum AggregateFunc {
571    Count,
572    Sum,
573    Avg,
574    Min,
575    Max,
576}
577
578impl std::fmt::Display for AggregateFunc {
579    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
580        match self {
581            AggregateFunc::Count => write!(f, "COUNT"),
582            AggregateFunc::Sum => write!(f, "SUM"),
583            AggregateFunc::Avg => write!(f, "AVG"),
584            AggregateFunc::Min => write!(f, "MIN"),
585            AggregateFunc::Max => write!(f, "MAX"),
586        }
587    }
588}
589
590
591
592/// The action type (SQL operation).
593#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
594pub enum Action {
595    /// SELECT query
596    Get,
597    /// UPDATE query  
598    Set,
599    /// DELETE query
600    Del,
601    /// INSERT query
602    Add,
603    /// Generate Rust struct from table schema
604    Gen,
605    /// Create Table (Make)
606    Make,
607    /// Drop Table (Drop)
608    Drop,
609    /// Modify Table (Mod)
610    Mod,
611    /// Window Function (Over)
612    Over,
613    /// CTE (With)
614    With,
615    /// Create Index
616    /// Create Index
617    Index,
618    // Transactions
619    TxnStart,
620    TxnCommit,
621    TxnRollback,
622    Put,
623    DropCol,
624    RenameCol,
625    // Additional clauses
626    /// JSON_TABLE - convert JSON to relational rows
627    JsonTable,
628}
629
630impl std::fmt::Display for Action {
631    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
632        match self {
633            Action::Get => write!(f, "GET"),
634            Action::Set => write!(f, "SET"),
635            Action::Del => write!(f, "DEL"),
636            Action::Add => write!(f, "ADD"),
637            Action::Gen => write!(f, "GEN"),
638            Action::Make => write!(f, "MAKE"),
639            Action::Drop => write!(f, "DROP"),
640            Action::Mod => write!(f, "MOD"),
641            Action::Over => write!(f, "OVER"),
642            Action::With => write!(f, "WITH"),
643            Action::Index => write!(f, "INDEX"),
644            Action::TxnStart => write!(f, "TXN_START"),
645            Action::TxnCommit => write!(f, "TXN_COMMIT"),
646            Action::TxnRollback => write!(f, "TXN_ROLLBACK"),
647            Action::Put => write!(f, "PUT"),
648            Action::DropCol => write!(f, "DROP_COL"),
649            Action::RenameCol => write!(f, "RENAME_COL"),
650            Action::JsonTable => write!(f, "JSON_TABLE"),
651        }
652    }
653}
654
655
656
657/// A cage (constraint block) in the query.
658#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
659pub struct Cage {
660    /// The type of cage
661    pub kind: CageKind,
662    /// Conditions within this cage
663    pub conditions: Vec<Condition>,
664    /// Logical operator between conditions (AND or OR)
665    pub logical_op: LogicalOp,
666}
667
668/// The type of cage.
669#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
670pub enum CageKind {
671    /// WHERE filter
672    Filter,
673    /// SET payload (for updates)
674    Payload,
675    /// ORDER BY
676    Sort(SortOrder),
677    /// LIMIT
678    Limit(usize),
679    /// OFFSET
680    Offset(usize),
681    /// TABLESAMPLE - percentage of rows
682    Sample(usize),
683    /// QUALIFY - filter on window function results
684    Qualify,
685    /// PARTITION BY - window function partitioning
686    Partition,
687}
688
689/// Sort order direction.
690#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
691pub enum SortOrder {
692    Asc,
693    Desc,
694    /// ASC NULLS FIRST (nulls at top)
695    AscNullsFirst,
696    /// ASC NULLS LAST (nulls at bottom)
697    AscNullsLast,
698    /// DESC NULLS FIRST (nulls at top)
699    DescNullsFirst,
700    /// DESC NULLS LAST (nulls at bottom)
701    DescNullsLast,
702}
703
704/// Logical operator between conditions.
705#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
706pub enum LogicalOp {
707    #[default]
708    And,
709    Or,
710}
711
712/// A single condition within a cage.
713#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
714pub struct Condition {
715    /// Column name
716    pub column: String,
717    /// Comparison operator
718    pub op: Operator,
719    /// Value to compare against
720    pub value: Value,
721    /// Whether this is an array unnest operation (column[*])
722    #[serde(default)]
723    pub is_array_unnest: bool,
724}
725
726/// Comparison operators.
727#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
728pub enum Operator {
729    /// Equal (=)
730    Eq,
731    /// Not equal (!=, <>)
732    Ne,
733    /// Greater than (>)
734    Gt,
735    /// Greater than or equal (>=)
736    Gte,
737    /// Less than (<)
738    Lt,
739    /// Less than or equal (<=)  
740    Lte,
741    /// Fuzzy match (~) -> ILIKE
742    Fuzzy,
743    /// IN array
744    In,
745    /// NOT IN array
746    NotIn,
747    /// IS NULL
748    IsNull,
749    /// IS NOT NULL
750    IsNotNull,
751    /// JSON/Array Contains (@>)
752    Contains,
753    /// JSON Key Exists (?)
754    KeyExists,
755    /// JSON_EXISTS - check if path exists (Postgres 17+)
756    JsonExists,
757    /// JSON_QUERY - extract JSON object/array at path (Postgres 17+)
758    JsonQuery,
759    /// JSON_VALUE - extract scalar value at path (Postgres 17+)
760    JsonValue,
761}
762
763/// A value in a condition.
764#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
765pub enum Value {
766    /// NULL value
767    Null,
768    /// Boolean
769    Bool(bool),
770    /// Integer
771    Int(i64),
772    /// Float
773    Float(f64),
774    /// String
775    String(String),
776    /// Parameter reference ($1, $2, etc.)
777    Param(usize),
778    /// SQL function call (e.g., now())
779    Function(String),
780    /// Array of values
781    Array(Vec<Value>),
782    /// Subquery for IN/EXISTS expressions (e.g., WHERE id IN (SELECT ...))
783    Subquery(Box<QailCmd>),
784    /// Column reference (e.g. JOIN ... ON a.id = b.id)
785    Column(String),
786}
787
788impl From<bool> for Value {
789    fn from(b: bool) -> Self {
790        Value::Bool(b)
791    }
792}
793
794impl From<i32> for Value {
795    fn from(n: i32) -> Self {
796        Value::Int(n as i64)
797    }
798}
799
800impl From<i64> for Value {
801    fn from(n: i64) -> Self {
802        Value::Int(n)
803    }
804}
805
806impl From<f64> for Value {
807    fn from(n: f64) -> Self {
808        Value::Float(n)
809    }
810}
811
812impl From<&str> for Value {
813    fn from(s: &str) -> Self {
814        Value::String(s.to_string())
815    }
816}
817
818impl From<String> for Value {
819    fn from(s: String) -> Self {
820        Value::String(s)
821    }
822}
823
824impl std::fmt::Display for Value {
825    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
826        match self {
827            Value::Null => write!(f, "NULL"),
828            Value::Bool(b) => write!(f, "{}", b),
829            Value::Int(n) => write!(f, "{}", n),
830            Value::Float(n) => write!(f, "{}", n),
831            Value::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
832            Value::Param(n) => write!(f, "${}", n),
833            Value::Function(name) => write!(f, "{}()", name),
834            Value::Array(arr) => {
835                write!(f, "ARRAY[")?;
836                for (i, v) in arr.iter().enumerate() {
837                    if i > 0 {
838                        write!(f, ", ")?;
839                    }
840                    write!(f, "{}", v)?;
841                }
842                write!(f, "]")
843            }
844            Value::Subquery(cmd) => write!(f, "({})", cmd.table), // Placeholder display
845            Value::Column(col) => write!(f, "\"{}\"", col), // Placeholder, generator should quote properly
846        }
847    }
848}
849
850#[cfg(test)]
851mod tests {
852    use super::*;
853
854    #[test]
855    fn test_builder_pattern() {
856        let cmd = QailCmd::get("users")
857            .hook(&["id", "email"])
858            .cage("active", true)
859            .limit(10);
860
861        assert_eq!(cmd.action, Action::Get);
862        assert_eq!(cmd.table, "users");
863        assert_eq!(cmd.columns.len(), 2);
864        assert_eq!(cmd.cages.len(), 2);
865    }
866}