qail_core/fmt/
mod.rs

1use crate::ast::{
2    Action, Cage, CageKind, Condition, Expr, Join, LogicalOp, Operator, Qail, SortOrder, Value,
3};
4use std::fmt::{Result, Write};
5
6#[cfg(test)]
7mod tests;
8
9pub struct Formatter {
10    indent_level: usize,
11    buffer: String,
12}
13
14impl Default for Formatter {
15    fn default() -> Self {
16        Self::new()
17    }
18}
19
20impl Formatter {
21    pub fn new() -> Self {
22        Self {
23            indent_level: 0,
24            buffer: String::new(),
25        }
26    }
27
28    pub fn format(mut self, cmd: &Qail) -> std::result::Result<String, std::fmt::Error> {
29        self.visit_cmd(cmd)?;
30        Ok(self.buffer)
31    }
32
33    fn indent(&mut self) -> Result {
34        for _ in 0..self.indent_level {
35            write!(self.buffer, "  ")?;
36        }
37        Ok(())
38    }
39
40    fn visit_cmd(&mut self, cmd: &Qail) -> Result {
41        for cte in &cmd.ctes {
42            write!(self.buffer, "with {} = ", cte.name)?;
43            self.indent_level += 1;
44            writeln!(self.buffer)?;
45            self.indent()?;
46            self.visit_cmd(&cte.base_query)?;
47
48            if cte.recursive
49                && let Some(ref recursive_query) = cte.recursive_query
50            {
51                writeln!(self.buffer)?;
52                self.indent()?;
53                writeln!(self.buffer, "union all")?;
54                self.indent()?;
55                self.visit_cmd(recursive_query)?;
56            }
57
58            self.indent_level -= 1;
59            writeln!(self.buffer)?;
60        }
61
62        // Action and Table
63        match cmd.action {
64            Action::Get => write!(self.buffer, "get {}", cmd.table)?,
65            Action::Set => write!(self.buffer, "set {}", cmd.table)?,
66            Action::Del => write!(self.buffer, "del {}", cmd.table)?,
67            Action::Add => write!(self.buffer, "add {}", cmd.table)?,
68            _ => write!(self.buffer, "{} {}", cmd.action, cmd.table)?, // Fallback for others
69        }
70        writeln!(self.buffer)?;
71
72        // self.indent_level += 1; // Removed: Clauses should act at same level as command
73
74        // Cages: Group By (if any "by" equivalent exists? No, "by" is usually implicit in AST or explicit in group_by_mode?)
75        // The proposal example shows "by phone_number".
76        // In AST `cmd.rs`, there isn't a direct "Group By" list, usually inferred or group_by_mode.
77        // Wait, where is `by phone_number` stored in AST?
78        // Checking `ast/cmd.rs`: `group_by_mode: GroupByMode`.
79        // Usually group by is inferred from aggregates or explicit.
80        // If the AST doesn't have explicit group by columns, we might need to derive it or it's in `cages`?
81        // Let's check `cages.rs` again. `CageKind` has `Filter`, `Sort`, `Limit`... no `GroupBy`.
82        // Maybe it's implied by non-aggregated columns in a `Get` with aggregates?
83        // For now, I will skip "by" unless I find it in AST.
84
85        if !cmd.columns.is_empty() {
86            // But proposal says "Canonical".
87            // "get table" implies "get table fields *" usually?
88            // If manual explicit columns:
89            if !(cmd.columns.len() == 1 && matches!(cmd.columns[0], Expr::Star)) {
90                self.indent()?;
91                writeln!(self.buffer, "fields")?;
92                self.indent_level += 1;
93                for (i, col) in cmd.columns.iter().enumerate() {
94                    self.indent()?;
95                    self.format_column(col)?;
96                    if i < cmd.columns.len() - 1 {
97                        writeln!(self.buffer, ",")?;
98                    } else {
99                        writeln!(self.buffer)?;
100                    }
101                }
102                self.indent_level -= 1;
103            }
104        }
105
106        // Joins
107        for join in &cmd.joins {
108            self.indent()?;
109            self.format_join(join)?;
110            writeln!(self.buffer)?;
111        }
112
113        // Where (Filter Cages)
114        let filters: Vec<&Cage> = cmd
115            .cages
116            .iter()
117            .filter(|c| matches!(c.kind, CageKind::Filter))
118            .collect();
119        if !filters.is_empty() {
120            // We need to merge them or print them?
121            // Proposal says: "where rn = 1"
122            self.indent()?;
123            write!(self.buffer, "where ")?;
124            for (i, cage) in filters.iter().enumerate() {
125                if i > 0 {
126                    write!(self.buffer, " and ")?; // Assuming AND between cages for now
127                }
128                self.format_conditions(&cage.conditions, cage.logical_op)?;
129            }
130            writeln!(self.buffer)?;
131        }
132
133        // Order By (Sort Cages)
134        let sorts: Vec<&Cage> = cmd
135            .cages
136            .iter()
137            .filter(|c| matches!(c.kind, CageKind::Sort(_)))
138            .collect();
139        if !sorts.is_empty() {
140            self.indent()?;
141            writeln!(self.buffer, "order by")?;
142            self.indent_level += 1;
143            for (i, cage) in sorts.iter().enumerate() {
144                if let CageKind::Sort(order) = cage.kind {
145                    for (j, cond) in cage.conditions.iter().enumerate() {
146                        self.indent()?;
147                        write!(self.buffer, "{}", cond.left)?;
148                        self.format_sort_order(order)?;
149                        if i < sorts.len() - 1 || j < cage.conditions.len() - 1 {
150                            writeln!(self.buffer, ",")?;
151                        } else {
152                            writeln!(self.buffer)?;
153                        }
154                    }
155                }
156            }
157            self.indent_level -= 1;
158        }
159
160        for cage in &cmd.cages {
161            match cage.kind {
162                CageKind::Limit(n) => {
163                    self.indent()?;
164                    writeln!(self.buffer, "limit {}", n)?;
165                }
166                CageKind::Offset(n) => {
167                    self.indent()?;
168                    writeln!(self.buffer, "offset {}", n)?;
169                }
170                _ => {}
171            }
172        }
173
174        // self.indent_level -= 1; // Removed matching decrement
175        Ok(())
176    }
177
178    fn format_column(&mut self, col: &Expr) -> Result {
179        match col {
180            Expr::Star => write!(self.buffer, "*")?,
181            Expr::Named(name) => write!(self.buffer, "{}", name)?,
182            Expr::Aliased { name, alias } => write!(self.buffer, "{} as {}", name, alias)?,
183            Expr::Aggregate {
184                col,
185                func,
186                distinct,
187                filter,
188                alias,
189            } => {
190                let func_name = match func {
191                    crate::ast::AggregateFunc::Count => "count",
192                    crate::ast::AggregateFunc::Sum => "sum",
193                    crate::ast::AggregateFunc::Avg => "avg",
194                    crate::ast::AggregateFunc::Min => "min",
195                    crate::ast::AggregateFunc::Max => "max",
196                    crate::ast::AggregateFunc::ArrayAgg => "array_agg",
197                    crate::ast::AggregateFunc::StringAgg => "string_agg",
198                    crate::ast::AggregateFunc::JsonAgg => "json_agg",
199                    crate::ast::AggregateFunc::JsonbAgg => "jsonb_agg",
200                    crate::ast::AggregateFunc::BoolAnd => "bool_and",
201                    crate::ast::AggregateFunc::BoolOr => "bool_or",
202                };
203                if *distinct {
204                    write!(self.buffer, "{}(distinct {})", func_name, col)?;
205                } else {
206                    write!(self.buffer, "{}({})", func_name, col)?;
207                }
208                if let Some(conditions) = filter {
209                    write!(
210                        self.buffer,
211                        " filter (where {})",
212                        conditions
213                            .iter()
214                            .map(|c| c.to_string())
215                            .collect::<Vec<_>>()
216                            .join(" and ")
217                    )?;
218                }
219                if let Some(a) = alias {
220                    write!(self.buffer, " as {}", a)?;
221                }
222            }
223            Expr::FunctionCall { name, args, alias } => {
224                let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
225                write!(self.buffer, "{}({})", name, args_str.join(", "))?;
226                if let Some(a) = alias {
227                    write!(self.buffer, " as {}", a)?;
228                }
229            }
230            Expr::Window { name, func, params, partition, .. } => {
231                // Use Window function format: func(params) OVER (PARTITION BY ...)
232                let params_str: Vec<String> = params.iter().map(|p| p.to_string()).collect();
233                write!(self.buffer, "{}({})", func, params_str.join(", "))?;
234                if !partition.is_empty() {
235                    write!(self.buffer, " over (partition by {})", partition.join(", "))?;
236                }
237                write!(self.buffer, " as {}", name)?;
238            }
239            Expr::Case { when_clauses, else_value, alias } => {
240                write!(self.buffer, "case")?;
241                for (cond, val) in when_clauses {
242                    write!(self.buffer, " when {} then {}", cond.left, val)?;
243                }
244                if let Some(e) = else_value {
245                    write!(self.buffer, " else {}", e)?;
246                }
247                write!(self.buffer, " end")?;
248                if let Some(a) = alias {
249                    write!(self.buffer, " as {}", a)?;
250                }
251            }
252            Expr::JsonAccess { column, path_segments, alias } => {
253                write!(self.buffer, "{}", column)?;
254                for (path, as_text) in path_segments {
255                    let op = if *as_text { "->>" } else { "->" };
256                    if path.parse::<i64>().is_ok() {
257                        write!(self.buffer, "{}{}", op, path)?;
258                    } else {
259                        write!(self.buffer, "{}'{}'", op, path)?;
260                    }
261                }
262                if let Some(a) = alias {
263                    write!(self.buffer, " as {}", a)?;
264                }
265            }
266            Expr::Cast { expr, target_type, alias } => {
267                write!(self.buffer, "{}::{}", expr, target_type)?;
268                if let Some(a) = alias {
269                    write!(self.buffer, " as {}", a)?;
270                }
271            }
272            Expr::Binary { left, op, right, alias } => {
273                write!(self.buffer, "({} {} {})", left, op, right)?;
274                if let Some(a) = alias {
275                    write!(self.buffer, " as {}", a)?;
276                }
277            }
278            Expr::SpecialFunction { name, args, alias } => {
279                write!(self.buffer, "{}(", name)?;
280                for (i, (keyword, expr)) in args.iter().enumerate() {
281                    if i > 0 { write!(self.buffer, " ")?; }
282                    if let Some(kw) = keyword {
283                        write!(self.buffer, "{} ", kw)?;
284                    }
285                    write!(self.buffer, "{}", expr)?;
286                }
287                write!(self.buffer, ")")?;
288                if let Some(a) = alias {
289                    write!(self.buffer, " as {}", a)?;
290                }
291            }
292            Expr::Literal(val) => self.format_value(val)?,
293            Expr::Def { name, data_type, constraints } => {
294                write!(self.buffer, "{}:{}", name, data_type)?;
295                for c in constraints {
296                    write!(self.buffer, "^{}", c)?;
297                }
298            }
299            Expr::Mod { kind, col } => {
300                let prefix = match kind { crate::ast::ModKind::Add => "+", crate::ast::ModKind::Drop => "-" };
301                write!(self.buffer, "{}{}", prefix, col)?;
302            }
303            Expr::ArrayConstructor { elements, alias } => {
304                write!(self.buffer, "ARRAY[")?;
305                for (i, elem) in elements.iter().enumerate() {
306                    if i > 0 { write!(self.buffer, ", ")?; }
307                    self.format_column(elem)?;
308                }
309                write!(self.buffer, "]")?;
310                if let Some(a) = alias { write!(self.buffer, " as {}", a)?; }
311            }
312            Expr::RowConstructor { elements, alias } => {
313                write!(self.buffer, "ROW(")?;
314                for (i, elem) in elements.iter().enumerate() {
315                    if i > 0 { write!(self.buffer, ", ")?; }
316                    self.format_column(elem)?;
317                }
318                write!(self.buffer, ")")?;
319                if let Some(a) = alias { write!(self.buffer, " as {}", a)?; }
320            }
321            Expr::Subscript { expr, index, alias } => {
322                self.format_column(expr)?;
323                write!(self.buffer, "[")?;
324                self.format_column(index)?;
325                write!(self.buffer, "]")?;
326                if let Some(a) = alias { write!(self.buffer, " as {}", a)?; }
327            }
328            Expr::Collate { expr, collation, alias } => {
329                self.format_column(expr)?;
330                write!(self.buffer, " COLLATE \"{}\"", collation)?;
331                if let Some(a) = alias { write!(self.buffer, " as {}", a)?; }
332            }
333            Expr::FieldAccess { expr, field, alias } => {
334                write!(self.buffer, "(")?;
335                self.format_column(expr)?;
336                write!(self.buffer, ").{}", field)?;
337                if let Some(a) = alias { write!(self.buffer, " as {}", a)?; }
338            }
339        }
340        Ok(())
341    }
342
343    fn format_join(&mut self, join: &Join) -> Result {
344        match join.kind {
345            crate::ast::JoinKind::Inner => write!(self.buffer, "join {}", join.table)?,
346            crate::ast::JoinKind::Left => write!(self.buffer, "left join {}", join.table)?,
347            crate::ast::JoinKind::Right => write!(self.buffer, "right join {}", join.table)?,
348            crate::ast::JoinKind::Full => write!(self.buffer, "full join {}", join.table)?,
349            crate::ast::JoinKind::Cross => write!(self.buffer, "cross join {}", join.table)?,
350            crate::ast::JoinKind::Lateral => write!(self.buffer, "lateral join {}", join.table)?,
351        }
352
353        if let Some(conditions) = &join.on
354            && !conditions.is_empty()
355        {
356            writeln!(self.buffer)?;
357            self.indent_level += 1;
358            self.indent()?;
359            write!(self.buffer, "on ")?;
360            self.format_conditions(conditions, LogicalOp::And)?;
361            self.indent_level -= 1;
362        }
363        Ok(())
364    }
365
366    fn format_conditions(&mut self, conditions: &[Condition], logical_op: LogicalOp) -> Result {
367        for (i, cond) in conditions.iter().enumerate() {
368            if i > 0 {
369                match logical_op {
370                    LogicalOp::And => write!(self.buffer, " and ")?,
371                    LogicalOp::Or => write!(self.buffer, " or ")?,
372                }
373            }
374
375            write!(self.buffer, "{}", cond.left)?;
376
377            match cond.op {
378                Operator::Eq => write!(self.buffer, " = ")?,
379                Operator::Ne => write!(self.buffer, " != ")?,
380                Operator::Gt => write!(self.buffer, " > ")?,
381                Operator::Gte => write!(self.buffer, " >= ")?,
382                Operator::Lt => write!(self.buffer, " < ")?,
383                Operator::Lte => write!(self.buffer, " <= ")?,
384                Operator::Fuzzy => write!(self.buffer, " ~ ")?, // ILIKE
385                Operator::In => write!(self.buffer, " in ")?,
386                Operator::NotIn => write!(self.buffer, " not in ")?,
387                Operator::IsNull => write!(self.buffer, " is null")?,
388                Operator::IsNotNull => write!(self.buffer, " is not null")?,
389                Operator::Contains => write!(self.buffer, " @> ")?,
390                Operator::KeyExists => write!(self.buffer, " ? ")?,
391                _ => write!(self.buffer, " {:?} ", cond.op)?,
392            }
393
394            // Some operators like IsNull don't need a value printed
395            if !matches!(cond.op, Operator::IsNull | Operator::IsNotNull) {
396                self.format_value(&cond.value)?;
397            }
398        }
399        Ok(())
400    }
401
402    fn format_value(&mut self, val: &Value) -> Result {
403        match val {
404            Value::Null => write!(self.buffer, "null")?,
405            Value::Bool(b) => write!(self.buffer, "{}", b)?,
406            Value::Int(n) => write!(self.buffer, "{}", n)?,
407            Value::Float(n) => write!(self.buffer, "{}", n)?,
408            Value::Param(n) => write!(self.buffer, "${}", n)?,
409            Value::Function(f) => write!(self.buffer, "{}", f)?,
410            Value::Column(c) => write!(self.buffer, "{}", c)?,
411            Value::String(s) => write!(self.buffer, "'{}'", s)?, // Simple quoting, might need escaping
412            // Value::Date and Value::Interval are not in AST, likely Strings
413            // Value::Date(d) => write!(self.buffer, "'{}'", d)?,
414            // Value::Interval(i) => write!(self.buffer, "interval '{}'", i)?,
415            Value::Array(arr) => {
416                write!(self.buffer, "[")?;
417                for (i, v) in arr.iter().enumerate() {
418                    if i > 0 {
419                        write!(self.buffer, ", ")?;
420                    }
421                    self.format_value(v)?;
422                }
423                write!(self.buffer, "]")?;
424            }
425            Value::NamedParam(name) => write!(self.buffer, ":{}", name)?,
426            Value::Uuid(u) => write!(self.buffer, "'{}'", u)?,
427            Value::NullUuid => write!(self.buffer, "null")?,
428            Value::Interval { amount, unit } => write!(self.buffer, "interval '{} {}'", amount, unit)?,
429            Value::Timestamp(ts) => write!(self.buffer, "'{}'", ts)?,
430            Value::Bytes(bytes) => {
431                write!(self.buffer, "'\\x")?;
432                for byte in bytes { write!(self.buffer, "{:02x}", byte)?; }
433                write!(self.buffer, "'")?;
434            }
435            Value::Subquery(cmd) => {
436                write!(self.buffer, "(")?;
437                self.visit_cmd(cmd)?;
438                write!(self.buffer, ")")?;
439            }
440            Value::Expr(expr) => write!(self.buffer, "{}", expr)?,
441        }
442        Ok(())
443    }
444
445    fn format_sort_order(&mut self, order: SortOrder) -> Result {
446        match order {
447            SortOrder::Asc => {}
448            SortOrder::Desc => write!(self.buffer, " desc")?,
449            SortOrder::AscNullsFirst => write!(self.buffer, " nulls first")?,
450            SortOrder::AscNullsLast => write!(self.buffer, " nulls last")?,
451            SortOrder::DescNullsFirst => write!(self.buffer, " desc nulls first")?,
452            SortOrder::DescNullsLast => write!(self.buffer, " desc nulls last")?,
453        }
454        Ok(())
455    }
456}