qail_core/fmt/
mod.rs

1use std::fmt::{Result, Write};
2use crate::ast::{QailCmd, Column, Join, Cage, CageKind, Condition, Operator, Value, LogicalOp, SortOrder, Action};
3
4#[cfg(test)]
5mod tests;
6
7pub struct Formatter {
8    indent_level: usize,
9    buffer: String,
10}
11
12impl Formatter {
13    pub fn new() -> Self {
14        Self {
15            indent_level: 0,
16            buffer: String::new(),
17        }
18    }
19
20    pub fn format(mut self, cmd: &QailCmd) -> std::result::Result<String, std::fmt::Error> {
21        self.visit_cmd(cmd)?;
22        Ok(self.buffer)
23    }
24
25    fn indent(&mut self) -> Result {
26        for _ in 0..self.indent_level {
27            write!(self.buffer, "  ")?;
28        }
29        Ok(())
30    }
31
32    fn visit_cmd(&mut self, cmd: &QailCmd) -> Result {
33        // Handle CTEs first
34        for cte in &cmd.ctes {
35            write!(self.buffer, "with {} = ", cte.name)?;
36            self.indent_level += 1;
37            writeln!(self.buffer)?;
38            self.indent()?;
39            self.visit_cmd(&cte.base_query)?;
40            
41            // Handle recursive part if present
42            if cte.recursive {
43                if let Some(ref recursive_query) = cte.recursive_query {
44                    writeln!(self.buffer)?;
45                    self.indent()?;
46                    writeln!(self.buffer, "union all")?;
47                    self.indent()?;
48                    self.visit_cmd(recursive_query)?;
49                }
50            }
51            
52            self.indent_level -= 1;
53            writeln!(self.buffer)?;
54        }
55
56        // Action and Table
57        match cmd.action {
58            Action::Get => write!(self.buffer, "get {}", cmd.table)?,
59            Action::Set => write!(self.buffer, "set {}", cmd.table)?,
60            Action::Del => write!(self.buffer, "del {}", cmd.table)?,
61            Action::Add => write!(self.buffer, "add {}", cmd.table)?,
62            _ => write!(self.buffer, "{} {}", cmd.action, cmd.table)?, // Fallback for others
63        }
64        writeln!(self.buffer)?;
65        
66        // self.indent_level += 1; // Removed: Clauses should act at same level as command
67        
68        // Cages: Group By (if any "by" equivalent exists? No, "by" is usually implicit in AST or explicit in group_by_mode?)
69        // The proposal example shows "by phone_number".
70        // In AST `cmd.rs`, there isn't a direct "Group By" list, usually inferred or group_by_mode.
71        // Wait, where is `by phone_number` stored in AST? 
72        // Checking `ast/cmd.rs`: `group_by_mode: GroupByMode`.
73        // Usually group by is inferred from aggregates or explicit. 
74        // If the AST doesn't have explicit group by columns, we might need to derive it or it's in `cages`?
75        // Let's check `cages.rs` again. `CageKind` has `Filter`, `Sort`, `Limit`... no `GroupBy`.
76        // Maybe it's implied by non-aggregated columns in a `Get` with aggregates? 
77        // For now, I will skip "by" unless I find it in AST.
78        
79        // Columns (Fields)
80        if !cmd.columns.is_empty() {
81             // Check if all are Star, then maybe skip fields block? 
82             // But proposal says "Canonical". 
83             // "get table" implies "get table fields *" usually?
84             // If manual explicit columns:
85            if !(cmd.columns.len() == 1 && matches!(cmd.columns[0], Column::Star)) {
86                self.indent()?;
87                writeln!(self.buffer, "fields")?;
88                self.indent_level += 1;
89                for (i, col) in cmd.columns.iter().enumerate() {
90                    self.indent()?;
91                    self.format_column(col)?;
92                    if i < cmd.columns.len() - 1 {
93                        writeln!(self.buffer, ",")?;
94                    } else {
95                        writeln!(self.buffer)?;
96                    }
97                }
98                self.indent_level -= 1;
99            }
100        }
101
102        // Joins
103        for join in &cmd.joins {
104            self.indent()?;
105            self.format_join(join)?;
106            writeln!(self.buffer)?;
107        }
108
109        // Where (Filter Cages)
110        let filters: Vec<&Cage> = cmd.cages.iter().filter(|c| matches!(c.kind, CageKind::Filter)).collect();
111        if !filters.is_empty() {
112            // We need to merge them or print them?
113            // Proposal says: "where rn = 1"
114            self.indent()?;
115            write!(self.buffer, "where ")?;
116            for (i, cage) in filters.iter().enumerate() {
117                if i > 0 {
118                    write!(self.buffer, " and ")?; // Assuming AND between cages for now
119                }
120                self.format_conditions(&cage.conditions, cage.logical_op)?;
121            }
122            writeln!(self.buffer)?;
123        }
124
125        // Order By (Sort Cages)
126        let sorts: Vec<&Cage> = cmd.cages.iter().filter(|c| matches!(c.kind, CageKind::Sort(_))).collect();
127        if !sorts.is_empty() {
128            self.indent()?;
129            writeln!(self.buffer, "order by")?;
130            self.indent_level += 1;
131            for (i, cage) in sorts.iter().enumerate() {
132                if let CageKind::Sort(order) = cage.kind {
133                     for (j, cond) in cage.conditions.iter().enumerate() {
134                        self.indent()?;
135                        write!(self.buffer, "{}", cond.column)?;
136                        self.format_sort_order(order)?;
137                        if i < sorts.len() - 1 || j < cage.conditions.len() - 1 {
138                             writeln!(self.buffer, ",")?;
139                        } else {
140                             writeln!(self.buffer)?;
141                        }
142                     }
143                }
144            }
145            self.indent_level -= 1;
146        }
147
148        // Limit / Offset
149        for cage in &cmd.cages {
150             match cage.kind {
151                 CageKind::Limit(n) => {
152                     self.indent()?;
153                     writeln!(self.buffer, "limit {}", n)?;
154                 },
155                 CageKind::Offset(n) => {
156                     self.indent()?;
157                     writeln!(self.buffer, "offset {}", n)?;
158                 },
159                 _ => {}
160             }
161        }
162        
163        
164        // self.indent_level -= 1; // Removed matching decrement
165        Ok(())
166    }
167
168    fn format_column(&mut self, col: &Column) -> Result {
169        match col {
170            Column::Star => write!(self.buffer, "*")?,
171            Column::Named(name) => write!(self.buffer, "{}", name)?,
172            Column::Aliased { name, alias } => write!(self.buffer, "{} as {}", name, alias)?,
173            Column::Aggregate { col, func } => {
174                 let func_name = match func {
175                     crate::ast::AggregateFunc::Count => "count",
176                     crate::ast::AggregateFunc::Sum => "sum",
177                     crate::ast::AggregateFunc::Avg => "avg",
178                     crate::ast::AggregateFunc::Min => "min",
179                     crate::ast::AggregateFunc::Max => "max",
180                 };
181                 write!(self.buffer, "{}({})", func_name, col)?
182            },
183            Column::FunctionCall { name, args, alias } => {
184                write!(self.buffer, "{}({})", name, args.join(", "))?;
185                if let Some(a) = alias {
186                    write!(self.buffer, " as {}", a)?;
187                }
188            }
189            // TODO: Handle Window, Case, JsonAccess
190            _ => write!(self.buffer, "/* TODO: {:?} */", col)?, 
191        }
192        Ok(())
193    }
194
195    fn format_join(&mut self, join: &Join) -> Result {
196        match join.kind {
197            crate::ast::JoinKind::Inner => write!(self.buffer, "join {}", join.table)?,
198            crate::ast::JoinKind::Left => write!(self.buffer, "left join {}", join.table)?,
199            crate::ast::JoinKind::Right => write!(self.buffer, "right join {}", join.table)?,
200            crate::ast::JoinKind::Full => write!(self.buffer, "full join {}", join.table)?,
201            crate::ast::JoinKind::Cross => write!(self.buffer, "cross join {}", join.table)?,
202            crate::ast::JoinKind::Lateral => write!(self.buffer, "lateral join {}", join.table)?,
203        }
204
205        if let Some(conditions) = &join.on {
206            if !conditions.is_empty() {
207                write!(self.buffer, "\n")?;
208                self.indent_level += 1;
209                self.indent()?;
210                write!(self.buffer, "on ")?;
211                self.format_conditions(conditions, LogicalOp::And)?;
212                self.indent_level -= 1;
213            }
214        }
215        Ok(())
216    }
217
218    fn format_conditions(&mut self, conditions: &[Condition], logical_op: LogicalOp) -> Result {
219        for (i, cond) in conditions.iter().enumerate() {
220             if i > 0 {
221                 match logical_op {
222                     LogicalOp::And => write!(self.buffer, " and ")?,
223                     LogicalOp::Or => write!(self.buffer, " or ")?,
224                 }
225             }
226             
227             write!(self.buffer, "{}", cond.column)?;
228             
229             match cond.op {
230                 Operator::Eq => write!(self.buffer, " = ")?,
231                 Operator::Ne => write!(self.buffer, " != ")?,
232                 Operator::Gt => write!(self.buffer, " > ")?,
233                 Operator::Gte => write!(self.buffer, " >= ")?,
234                 Operator::Lt => write!(self.buffer, " < ")?,
235                 Operator::Lte => write!(self.buffer, " <= ")?,
236                 Operator::Fuzzy => write!(self.buffer, " ~ ")?, // ILIKE
237                 Operator::In => write!(self.buffer, " in ")?,
238                 Operator::NotIn => write!(self.buffer, " not in ")?,
239                 Operator::IsNull => write!(self.buffer, " is null")?,
240                 Operator::IsNotNull => write!(self.buffer, " is not null")?,
241                 Operator::Contains => write!(self.buffer, " @> ")?,
242                 Operator::KeyExists => write!(self.buffer, " ? ")?,
243                 _ => write!(self.buffer, " {:?} ", cond.op)?,
244             }
245
246             // Some operators like IsNull don't need a value printed
247             if !matches!(cond.op, Operator::IsNull | Operator::IsNotNull) {
248                 self.format_value(&cond.value)?;
249             }
250        }
251        Ok(())
252    }
253    
254    fn format_value(&mut self, val: &Value) -> Result {
255        match val {
256            Value::Null => write!(self.buffer, "null")?,
257            Value::Bool(b) => write!(self.buffer, "{}", b)?,
258            Value::Int(n) => write!(self.buffer, "{}", n)?,
259            Value::Float(n) => write!(self.buffer, "{}", n)?,
260            Value::Param(n) => write!(self.buffer, "${}", n)?,
261            Value::Function(f) => write!(self.buffer, "{}", f)?,
262            Value::Column(c) => write!(self.buffer, "{}", c)?,
263            Value::String(s) => write!(self.buffer, "'{}'", s)?, // Simple quoting, might need escaping
264            // Value::Date and Value::Interval are not in AST, likely Strings
265            // Value::Date(d) => write!(self.buffer, "'{}'", d)?,
266            // Value::Interval(i) => write!(self.buffer, "interval '{}'", i)?,
267            Value::Array(arr) => {
268                write!(self.buffer, "[")?;
269                for (i, v) in arr.iter().enumerate() {
270                    if i > 0 { write!(self.buffer, ", ")?; }
271                    self.format_value(v)?;
272                }
273                write!(self.buffer, "]")?;
274            }
275            // TODO: Handle others
276             _ => write!(self.buffer, "{:?}", val)?,
277        }
278        Ok(())
279    }
280
281    fn format_sort_order(&mut self, order: SortOrder) -> Result {
282        match order {
283            SortOrder::Asc => {},
284            SortOrder::Desc => write!(self.buffer, " desc")?,
285            SortOrder::AscNullsFirst => write!(self.buffer, " nulls first")?,
286            SortOrder::AscNullsLast => write!(self.buffer, " nulls last")?,
287            SortOrder::DescNullsFirst => write!(self.buffer, " desc nulls first")?,
288            SortOrder::DescNullsLast => write!(self.buffer, " desc nulls last")?,
289        }
290        Ok(())
291    }
292}