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