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}
23
24impl QailCmd {
25    /// Create a new GET command for the given table.
26    pub fn get(table: impl Into<String>) -> Self {
27        Self {
28            action: Action::Get,
29            table: table.into(),
30            joins: vec![],
31            columns: vec![],
32            cages: vec![],
33        }
34    }
35
36    /// Create a new SET (update) command for the given table.
37    pub fn set(table: impl Into<String>) -> Self {
38        Self {
39            action: Action::Set,
40            table: table.into(),
41            joins: vec![],
42            columns: vec![],
43            cages: vec![],
44        }
45    }
46
47    /// Create a new DEL (delete) command for the given table.
48    pub fn del(table: impl Into<String>) -> Self {
49        Self {
50            action: Action::Del,
51            table: table.into(),
52            joins: vec![],
53            columns: vec![],
54            cages: vec![],
55        }
56    }
57
58    /// Create a new ADD (insert) command for the given table.
59    pub fn add(table: impl Into<String>) -> Self {
60        Self {
61            action: Action::Add,
62            table: table.into(),
63            joins: vec![],
64            columns: vec![],
65            cages: vec![],
66        }
67    }
68    /// Add columns to hook (select).
69    pub fn hook(mut self, cols: &[&str]) -> Self {
70        self.columns = cols.iter().map(|c| Column::Named(c.to_string())).collect();
71        self
72    }
73
74    /// Add a filter cage.
75    pub fn cage(mut self, column: &str, value: impl Into<Value>) -> Self {
76        self.cages.push(Cage {
77            kind: CageKind::Filter,
78            conditions: vec![Condition {
79                column: column.to_string(),
80                op: Operator::Eq,
81                value: value.into(),
82                is_array_unnest: false,
83            }],
84            logical_op: LogicalOp::And,
85        });
86        self
87    }
88
89    /// Add a limit cage.
90    pub fn limit(mut self, n: i64) -> Self {
91        self.cages.push(Cage {
92            kind: CageKind::Limit(n as usize),
93            conditions: vec![],
94            logical_op: LogicalOp::And,
95        });
96        self
97    }
98
99    /// Add a sort cage (ascending).
100    pub fn sort_asc(mut self, column: &str) -> Self {
101        self.cages.push(Cage {
102            kind: CageKind::Sort(SortOrder::Asc),
103            conditions: vec![Condition {
104                column: column.to_string(),
105                op: Operator::Eq,
106                value: Value::Null,
107                is_array_unnest: false,
108            }],
109            logical_op: LogicalOp::And,
110        });
111        self
112    }
113
114    /// Add a sort cage (descending).
115    pub fn sort_desc(mut self, column: &str) -> Self {
116        self.cages.push(Cage {
117            kind: CageKind::Sort(SortOrder::Desc),
118            conditions: vec![Condition {
119                column: column.to_string(),
120                op: Operator::Eq,
121                value: Value::Null,
122                is_array_unnest: false,
123            }],
124            logical_op: LogicalOp::And,
125        });
126        self
127    }
128}
129
130/// A join definition.
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct Join {
133    pub table: String,
134    pub kind: JoinKind,
135}
136
137#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
138pub enum JoinKind {
139    Inner,
140    Left,
141    Right,
142}
143
144/// A column reference.
145#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
146pub enum Column {
147    /// All columns (*)
148    Star,
149    /// A named column
150    Named(String),
151    /// An aliased column (col AS alias)
152    Aliased { name: String, alias: String },
153    /// An aggregate function (COUNT(col))
154    Aggregate { col: String, func: AggregateFunc },
155    /// Column Definition (for Make keys)
156    Def {
157        name: String,
158        data_type: String,
159        constraints: Vec<Constraint>,
160    },
161    /// Column Modification (for Mod keys)
162    Mod {
163        kind: ModKind,
164        col: Box<Column>,
165    },
166    /// Window Function Definition
167    Window {
168        name: String,
169        func: String,
170        params: Vec<Value>,
171        partition: Vec<String>,
172        order: Vec<Cage>,
173    },
174}
175
176impl std::fmt::Display for Column {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        match self {
179            Column::Star => write!(f, "*"),
180            Column::Named(name) => write!(f, "{}", name),
181            Column::Aliased { name, alias } => write!(f, "{} AS {}", name, alias),
182            Column::Aggregate { col, func } => write!(f, "{}({})", func, col),
183            Column::Def {
184                name,
185                data_type,
186                constraints,
187            } => {
188                write!(f, "{}:{}", name, data_type)?;
189                for c in constraints {
190                    write!(f, "^{}", c)?;
191                }
192                Ok(())
193            }
194            Column::Mod { kind, col } => match kind {
195                ModKind::Add => write!(f, "+{}", col),
196                ModKind::Drop => write!(f, "-{}", col),
197            },
198            Column::Window { name, func, params, partition, order } => {
199                write!(f, "{}:{}(", name, func)?;
200                for (i, p) in params.iter().enumerate() {
201                    if i > 0 { write!(f, ", ")?; }
202                    write!(f, "{}", p)?;
203                }
204                write!(f, ")")?;
205                
206                // Print partitions if any (custom syntax for display?)
207                if !partition.is_empty() {
208                    write!(f, "{{Part=")?;
209                    for (i, p) in partition.iter().enumerate() {
210                        if i > 0 { write!(f, ",")?; }
211                        write!(f, "{}", p)?;
212                    }
213                    write!(f, "}}")?;
214                }
215
216                // Print order cages
217                for cage in order {
218                   // We need a helper to print cages without brackets?? 
219                   // Actually cages print format is not defined here yet.
220                   // Spec implies order is just sort cages suffixed like ^uniq
221                   // But order cages are `[^!col]`.
222                   // Let's just output them as cages for now or skip until transpiler.
223                   // For display purposes, we might need to serialize cages or skip.
224                }
225                Ok(())
226            }
227        }
228    }
229}
230
231/// Column modification type
232#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub enum ModKind {
234    Add,
235    Drop,
236}
237
238/// Column definition constraints
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub enum Constraint {
241    PrimaryKey,
242    Unique,
243    Nullable,
244}
245
246impl std::fmt::Display for Constraint {
247    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
248        match self {
249            Constraint::PrimaryKey => write!(f, "pk"),
250            Constraint::Unique => write!(f, "uniq"),
251            Constraint::Nullable => write!(f, "?"),
252        }
253    }
254}
255
256/// Aggregate functions.
257#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
258pub enum AggregateFunc {
259    Count,
260    Sum,
261    Avg,
262    Min,
263    Max,
264}
265
266impl std::fmt::Display for AggregateFunc {
267    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
268        match self {
269            AggregateFunc::Count => write!(f, "COUNT"),
270            AggregateFunc::Sum => write!(f, "SUM"),
271            AggregateFunc::Avg => write!(f, "AVG"),
272            AggregateFunc::Min => write!(f, "MIN"),
273            AggregateFunc::Max => write!(f, "MAX"),
274        }
275    }
276}
277
278
279
280/// The action type (SQL operation).
281#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
282pub enum Action {
283    /// SELECT query
284    Get,
285    /// UPDATE query  
286    Set,
287    /// DELETE query
288    Del,
289    /// INSERT query
290    Add,
291    /// Generate Rust struct from table schema
292    Gen,
293    /// Create Table (Make)
294    Make,
295    /// Modify Table (Mod)
296    Mod,
297    /// Window Function (Over)
298    Over,
299    /// CTE (With)
300    With,
301}
302
303impl std::fmt::Display for Action {
304    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
305        match self {
306            Action::Get => write!(f, "GET"),
307            Action::Set => write!(f, "SET"),
308            Action::Del => write!(f, "DEL"),
309            Action::Add => write!(f, "ADD"),
310            Action::Gen => write!(f, "GEN"),
311            Action::Make => write!(f, "MAKE"),
312            Action::Mod => write!(f, "MOD"),
313            Action::Over => write!(f, "OVER"),
314            Action::With => write!(f, "WITH"),
315        }
316    }
317}
318
319
320
321/// A cage (constraint block) in the query.
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct Cage {
324    /// The type of cage
325    pub kind: CageKind,
326    /// Conditions within this cage
327    pub conditions: Vec<Condition>,
328    /// Logical operator between conditions (AND or OR)
329    pub logical_op: LogicalOp,
330}
331
332/// The type of cage.
333#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
334pub enum CageKind {
335    /// WHERE filter
336    Filter,
337    /// SET payload (for updates)
338    Payload,
339    /// ORDER BY
340    Sort(SortOrder),
341    /// LIMIT
342    Limit(usize),
343    /// OFFSET
344    Offset(usize),
345}
346
347/// Sort order direction.
348#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
349pub enum SortOrder {
350    Asc,
351    Desc,
352}
353
354/// Logical operator between conditions.
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
356pub enum LogicalOp {
357    #[default]
358    And,
359    Or,
360}
361
362/// A single condition within a cage.
363#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
364pub struct Condition {
365    /// Column name
366    pub column: String,
367    /// Comparison operator
368    pub op: Operator,
369    /// Value to compare against
370    pub value: Value,
371    /// Whether this is an array unnest operation (column[*])
372    #[serde(default)]
373    pub is_array_unnest: bool,
374}
375
376/// Comparison operators.
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
378pub enum Operator {
379    /// Equal (=)
380    Eq,
381    /// Not equal (!=, <>)
382    Ne,
383    /// Greater than (>)
384    Gt,
385    /// Greater than or equal (>=)
386    Gte,
387    /// Less than (<)
388    Lt,
389    /// Less than or equal (<=)  
390    Lte,
391    /// Fuzzy match (~) -> ILIKE
392    Fuzzy,
393    /// IN array
394    In,
395    /// NOT IN array
396    NotIn,
397    /// IS NULL
398    IsNull,
399    /// IS NOT NULL
400    IsNotNull,
401}
402
403/// A value in a condition.
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub enum Value {
406    /// NULL value
407    Null,
408    /// Boolean
409    Bool(bool),
410    /// Integer
411    Int(i64),
412    /// Float
413    Float(f64),
414    /// String
415    String(String),
416    /// Parameter reference ($1, $2, etc.)
417    Param(usize),
418    /// SQL function call (e.g., now())
419    Function(String),
420    /// Array of values
421    Array(Vec<Value>),
422}
423
424impl From<bool> for Value {
425    fn from(b: bool) -> Self {
426        Value::Bool(b)
427    }
428}
429
430impl From<i32> for Value {
431    fn from(n: i32) -> Self {
432        Value::Int(n as i64)
433    }
434}
435
436impl From<i64> for Value {
437    fn from(n: i64) -> Self {
438        Value::Int(n)
439    }
440}
441
442impl From<f64> for Value {
443    fn from(n: f64) -> Self {
444        Value::Float(n)
445    }
446}
447
448impl From<&str> for Value {
449    fn from(s: &str) -> Self {
450        Value::String(s.to_string())
451    }
452}
453
454impl From<String> for Value {
455    fn from(s: String) -> Self {
456        Value::String(s)
457    }
458}
459
460impl std::fmt::Display for Value {
461    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
462        match self {
463            Value::Null => write!(f, "NULL"),
464            Value::Bool(b) => write!(f, "{}", b),
465            Value::Int(n) => write!(f, "{}", n),
466            Value::Float(n) => write!(f, "{}", n),
467            Value::String(s) => write!(f, "'{}'", s.replace('\'', "''")),
468            Value::Param(n) => write!(f, "${}", n),
469            Value::Function(name) => write!(f, "{}()", name),
470            Value::Array(arr) => {
471                write!(f, "ARRAY[")?;
472                for (i, v) in arr.iter().enumerate() {
473                    if i > 0 {
474                        write!(f, ", ")?;
475                    }
476                    write!(f, "{}", v)?;
477                }
478                write!(f, "]")
479            }
480        }
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::*;
487
488    #[test]
489    fn test_builder_pattern() {
490        let cmd = QailCmd::get("users")
491            .hook(&["id", "email"])
492            .cage("active", true)
493            .limit(10);
494
495        assert_eq!(cmd.action, Action::Get);
496        assert_eq!(cmd.table, "users");
497        assert_eq!(cmd.columns.len(), 2);
498        assert_eq!(cmd.cages.len(), 2);
499    }
500}