Skip to main content

oak_sql/ast/
pretty_nodes.rs

1#[cfg(feature = "oak-pretty-print")]
2use crate::ast::*;
3#[cfg(feature = "oak-pretty-print")]
4use oak_pretty_print::{AsDocument, Document, LINE as line, NIL as nil, SOFT_LINE_SPACE as soft_space, doc, indent};
5
6#[cfg(feature = "oak-pretty-print")]
7impl AsDocument for SqlRoot {
8    fn as_document(&self) -> Document<'_> {
9        Document::join(self.statements.iter().map(|it| it.as_document()), doc!(";", line))
10    }
11}
12
13#[cfg(feature = "oak-pretty-print")]
14impl AsDocument for SqlStatement {
15    fn as_document(&self) -> Document<'_> {
16        match self {
17            SqlStatement::Select(it) => it.as_document(),
18            SqlStatement::Insert(it) => it.as_document(),
19            SqlStatement::Update(it) => it.as_document(),
20            SqlStatement::Delete(it) => it.as_document(),
21            SqlStatement::Create(it) => it.as_document(),
22            SqlStatement::Drop(it) => it.as_document(),
23            SqlStatement::Alter(it) => it.as_document(),
24            SqlStatement::Error { .. } => nil,
25            SqlStatement::Unknown { .. } => nil,
26        }
27    }
28}
29
30#[cfg(feature = "oak-pretty-print")]
31impl AsDocument for SelectStatement {
32    fn as_document(&self) -> Document<'_> {
33        let mut parts = Vec::new();
34        parts.push(Document::text("SELECT"));
35
36        let items = Document::join(self.items.iter().map(|it| it.as_document()), doc!(",", soft_space));
37        parts.push(indent(doc!(line, items)));
38
39        if let Some(from) = &self.from {
40            parts.push(line);
41            parts.push(Document::text("FROM"));
42            parts.push(soft_space);
43            parts.push(from.as_document());
44        }
45
46        for join in &self.joins {
47            parts.push(line);
48            parts.push(join.as_document());
49        }
50
51        if let Some(expr) = &self.expr {
52            parts.push(line);
53            parts.push(Document::text("WHERE"));
54            parts.push(soft_space);
55            parts.push(expr.as_document());
56        }
57
58        if let Some(group_by) = &self.group_by {
59            parts.push(line);
60            parts.push(group_by.as_document());
61        }
62
63        if let Some(having) = &self.having {
64            parts.push(line);
65            parts.push(having.as_document());
66        }
67
68        if let Some(order_by) = &self.order_by {
69            parts.push(line);
70            parts.push(order_by.as_document());
71        }
72
73        if let Some(limit) = &self.limit {
74            parts.push(line);
75            parts.push(limit.as_document());
76        }
77
78        Document::group(Document::Concat(parts))
79    }
80}
81
82#[cfg(feature = "oak-pretty-print")]
83impl AsDocument for SelectItem {
84    fn as_document(&self) -> Document<'_> {
85        match self {
86            SelectItem::Star { .. } => Document::text("*"),
87            SelectItem::Expression { expr, alias, .. } => {
88                if let Some(alias) = alias {
89                    doc!(expr.as_document(), soft_space, "AS", soft_space, alias.as_document())
90                }
91                else {
92                    expr.as_document()
93                }
94            }
95        }
96    }
97}
98
99#[cfg(feature = "oak-pretty-print")]
100impl AsDocument for Expression {
101    fn as_document(&self) -> Document<'_> {
102        match self {
103            Expression::Identifier(it) => it.as_document(),
104            Expression::Literal(it) => it.as_document(),
105            Expression::Binary { left, op, right, .. } => {
106                doc!(left.as_document(), soft_space, op.as_document(), soft_space, right.as_document())
107            }
108            Expression::Unary { op, expr, .. } => {
109                doc!(op.as_document(), expr.as_document())
110            }
111            Expression::FunctionCall { name, args, .. } => {
112                doc!(name.as_document(), "(", Document::join(args.iter().map(|it| it.as_document()), doc!(",", soft_space)), ")")
113            }
114            Expression::InList { expr, list, negated, .. } => {
115                doc!(expr.as_document(), if *negated { doc!(soft_space, "NOT") } else { nil }, soft_space, "IN", soft_space, "(", Document::join(list.iter().map(|it| it.as_document()), doc!(",", soft_space)), ")")
116            }
117            Expression::Between { expr, low, high, negated, .. } => {
118                doc!(expr.as_document(), if *negated { doc!(soft_space, "NOT") } else { nil }, soft_space, "BETWEEN", soft_space, low.as_document(), soft_space, "AND", soft_space, high.as_document())
119            }
120            Expression::Subquery { query, .. } => {
121                doc!("(", query.as_document(), ")")
122            }
123            Expression::InSubquery { expr, query: subquery, negated, .. } => {
124                doc!(expr.as_document(), if *negated { doc!(soft_space, "NOT") } else { nil }, soft_space, "IN", soft_space, "(", subquery.as_document(), ")")
125            }
126            Expression::Error { .. } => nil,
127        }
128    }
129}
130
131#[cfg(feature = "oak-pretty-print")]
132impl AsDocument for BinaryOperator {
133    fn as_document(&self) -> Document<'_> {
134        match self {
135            BinaryOperator::Plus => Document::text("+"),
136            BinaryOperator::Minus => Document::text("-"),
137            BinaryOperator::Star => Document::text("*"),
138            BinaryOperator::Slash => Document::text("/"),
139            BinaryOperator::Percent => Document::text("%"),
140            BinaryOperator::And => Document::text("AND"),
141            BinaryOperator::Or => Document::text("OR"),
142            BinaryOperator::Equal => Document::text("="),
143            BinaryOperator::NotEqual => Document::text("<>"),
144            BinaryOperator::Less => Document::text("<"),
145            BinaryOperator::Greater => Document::text(">"),
146            BinaryOperator::LessEqual => Document::text("<="),
147            BinaryOperator::GreaterEqual => Document::text(">="),
148            BinaryOperator::Like => Document::text("LIKE"),
149        }
150    }
151}
152
153#[cfg(feature = "oak-pretty-print")]
154impl AsDocument for UnaryOperator {
155    fn as_document(&self) -> Document<'_> {
156        match self {
157            UnaryOperator::Plus => Document::text("+"),
158            UnaryOperator::Minus => Document::text("-"),
159            UnaryOperator::Not => Document::text("NOT"),
160        }
161    }
162}
163
164#[cfg(feature = "oak-pretty-print")]
165impl AsDocument for Literal {
166    fn as_document(&self) -> Document<'_> {
167        match self {
168            Literal::Number(n, _) => Document::text(n.as_ref()),
169            Literal::String(s, _) => doc!("'", s.as_ref(), "'"),
170            Literal::Boolean(b, _) => Document::text(if *b { "TRUE" } else { "FALSE" }),
171            Literal::Null(_) => Document::text("NULL"),
172        }
173    }
174}
175
176#[cfg(feature = "oak-pretty-print")]
177impl AsDocument for Identifier {
178    fn as_document(&self) -> Document<'_> {
179        Document::text(self.name.as_ref())
180    }
181}
182
183#[cfg(feature = "oak-pretty-print")]
184impl AsDocument for TableName {
185    fn as_document(&self) -> Document<'_> {
186        self.name.as_document()
187    }
188}
189
190#[cfg(feature = "oak-pretty-print")]
191impl AsDocument for ColumnName {
192    fn as_document(&self) -> Document<'_> {
193        self.name.as_document()
194    }
195}
196
197#[cfg(feature = "oak-pretty-print")]
198impl AsDocument for JoinClause {
199    fn as_document(&self) -> Document<'_> {
200        doc!(self.join_type.as_document(), soft_space, "JOIN", soft_space, self.table.as_document(), if let Some(on) = &self.on { doc!(soft_space, "ON", soft_space, on.as_document()) } else { nil })
201    }
202}
203
204#[cfg(feature = "oak-pretty-print")]
205impl AsDocument for JoinType {
206    fn as_document(&self) -> Document<'_> {
207        match self {
208            JoinType::Inner => Document::text("INNER"),
209            JoinType::Left => Document::text("LEFT"),
210            JoinType::Right => Document::text("RIGHT"),
211            JoinType::Full => Document::text("FULL"),
212        }
213    }
214}
215
216#[cfg(feature = "oak-pretty-print")]
217impl AsDocument for GroupByClause {
218    fn as_document(&self) -> Document<'_> {
219        doc!("GROUP", soft_space, "BY", soft_space, Document::join(self.columns.iter().map(|it| it.as_document()), doc!(",", soft_space)))
220    }
221}
222
223#[cfg(feature = "oak-pretty-print")]
224impl AsDocument for HavingClause {
225    fn as_document(&self) -> Document<'_> {
226        doc!("HAVING", soft_space, self.condition.as_document())
227    }
228}
229
230#[cfg(feature = "oak-pretty-print")]
231impl AsDocument for OrderByClause {
232    fn as_document(&self) -> Document<'_> {
233        doc!("ORDER", soft_space, "BY", soft_space, Document::join(self.items.iter().map(|it| it.as_document()), doc!(",", soft_space)))
234    }
235}
236
237#[cfg(feature = "oak-pretty-print")]
238impl AsDocument for OrderByItem {
239    fn as_document(&self) -> Document<'_> {
240        doc!(self.expr.as_document(), soft_space, self.direction.as_document())
241    }
242}
243
244#[cfg(feature = "oak-pretty-print")]
245impl AsDocument for OrderDirection {
246    fn as_document(&self) -> Document<'_> {
247        match self {
248            OrderDirection::Asc => Document::text("ASC"),
249            OrderDirection::Desc => Document::text("DESC"),
250        }
251    }
252}
253
254#[cfg(feature = "oak-pretty-print")]
255impl AsDocument for LimitClause {
256    fn as_document(&self) -> Document<'_> {
257        doc!("LIMIT", soft_space, self.limit.as_document(), if let Some(offset) = &self.offset { doc!(soft_space, "OFFSET", soft_space, offset.as_document()) } else { nil })
258    }
259}
260
261#[cfg(feature = "oak-pretty-print")]
262impl AsDocument for CreateStatement {
263    fn as_document(&self) -> Document<'_> {
264        let mut parts = Vec::new();
265        parts.push(Document::text("CREATE"));
266        if let CreateBody::Index { unique: true, .. } = &self.body {
267            parts.push(soft_space);
268            parts.push(Document::text("UNIQUE"));
269        }
270        parts.push(soft_space);
271        parts.push(self.object_type.as_document());
272        if self.if_not_exists {
273            parts.push(soft_space);
274            parts.push(Document::text("IF NOT EXISTS"));
275        }
276        parts.push(soft_space);
277        parts.push(self.name.as_document());
278
279        match &self.body {
280            CreateBody::Table { columns, .. } => {
281                if !columns.is_empty() {
282                    parts.push(soft_space);
283                    parts.push(Document::text("("));
284                    let cols = Document::join(columns.iter().map(|it| it.as_document()), doc!(",", line));
285                    parts.push(indent(doc!(line, cols)));
286                    parts.push(line);
287                    parts.push(Document::text(")"));
288                }
289            }
290            CreateBody::View { query, .. } => {
291                parts.push(line);
292                parts.push(Document::text("AS"));
293                parts.push(soft_space);
294                parts.push(query.as_document());
295            }
296            CreateBody::Index { table_name, columns, unique: _, .. } => {
297                parts.push(soft_space);
298                parts.push(Document::text("ON"));
299                parts.push(soft_space);
300                parts.push(table_name.as_document());
301                parts.push(soft_space);
302                parts.push(Document::text("("));
303                let cols = Document::join(columns.iter().map(|it| it.as_document()), doc!(",", soft_space));
304                parts.push(cols);
305                parts.push(Document::text(")"));
306            }
307            CreateBody::Database { .. } => {}
308        }
309
310        Document::group(Document::Concat(parts))
311    }
312}
313
314#[cfg(feature = "oak-pretty-print")]
315impl AsDocument for ColumnDefinition {
316    fn as_document(&self) -> Document<'_> {
317        doc!(self.name.as_document(), soft_space, self.data_type.clone(), Document::join(self.constraints.iter().map(|it| doc!(soft_space, it.as_document())), nil))
318    }
319}
320
321#[cfg(feature = "oak-pretty-print")]
322impl AsDocument for ColumnConstraint {
323    fn as_document(&self) -> Document<'_> {
324        match self {
325            ColumnConstraint::PrimaryKey { .. } => Document::text("PRIMARY KEY"),
326            ColumnConstraint::NotNull { .. } => Document::text("NOT NULL"),
327            ColumnConstraint::Nullable { .. } => Document::text("NULL"),
328            ColumnConstraint::Unique { .. } => Document::text("UNIQUE"),
329            ColumnConstraint::Default(expr, _) => doc!("DEFAULT", soft_space, expr.as_document()),
330            ColumnConstraint::Check(expr, _) => doc!("CHECK", soft_space, "(", expr.as_document(), ")"),
331            ColumnConstraint::AutoIncrement { .. } => Document::text("AUTOINCREMENT"),
332        }
333    }
334}
335
336#[cfg(feature = "oak-pretty-print")]
337impl AsDocument for CreateObjectType {
338    fn as_document(&self) -> Document<'_> {
339        match self {
340            CreateObjectType::Table => Document::text("TABLE"),
341            CreateObjectType::View => Document::text("VIEW"),
342            CreateObjectType::Index => Document::text("INDEX"),
343            CreateObjectType::Database => Document::text("DATABASE"),
344        }
345    }
346}
347
348#[cfg(feature = "oak-pretty-print")]
349impl AsDocument for InsertStatement {
350    fn as_document(&self) -> Document<'_> {
351        let mut parts = Vec::new();
352        parts.push(Document::text("INSERT INTO"));
353        parts.push(soft_space);
354        parts.push(self.table_name.as_document());
355
356        if !self.columns.is_empty() {
357            parts.push(soft_space);
358            parts.push(Document::text("("));
359            parts.push(Document::join(self.columns.iter().map(|it| it.as_document()), doc!(",", soft_space)));
360            parts.push(Document::text(")"));
361        }
362
363        parts.push(line);
364        parts.push(Document::text("VALUES"));
365        parts.push(soft_space);
366        parts.push(Document::text("("));
367        parts.push(Document::join(self.values.iter().map(|it| it.as_document()), doc!(",", soft_space)));
368        parts.push(Document::text(")"));
369
370        Document::group(Document::Concat(parts))
371    }
372}
373
374#[cfg(feature = "oak-pretty-print")]
375impl AsDocument for UpdateStatement {
376    fn as_document(&self) -> Document<'_> {
377        let mut parts = Vec::new();
378        parts.push(Document::text("UPDATE"));
379        parts.push(soft_space);
380        parts.push(self.table_name.as_document());
381        parts.push(line);
382        parts.push(Document::text("SET"));
383        parts.push(soft_space);
384        parts.push(Document::join(self.assignments.iter().map(|it| it.as_document()), doc!(",", line)));
385
386        if let Some(selection) = &self.selection {
387            parts.push(line);
388            parts.push(Document::text("WHERE"));
389            parts.push(soft_space);
390            parts.push(selection.as_document());
391        }
392
393        Document::group(Document::Concat(parts))
394    }
395}
396
397#[cfg(feature = "oak-pretty-print")]
398impl AsDocument for Assignment {
399    fn as_document(&self) -> Document<'_> {
400        doc!(self.column.as_document(), soft_space, "=", soft_space, self.value.as_document())
401    }
402}
403
404#[cfg(feature = "oak-pretty-print")]
405impl AsDocument for DeleteStatement {
406    fn as_document(&self) -> Document<'_> {
407        let mut parts = Vec::new();
408        parts.push(Document::text("DELETE FROM"));
409        parts.push(soft_space);
410        parts.push(self.table_name.as_document());
411
412        if let Some(selection) = &self.selection {
413            parts.push(line);
414            parts.push(Document::text("WHERE"));
415            parts.push(soft_space);
416            parts.push(selection.as_document());
417        }
418
419        Document::group(Document::Concat(parts))
420    }
421}
422
423#[cfg(feature = "oak-pretty-print")]
424impl AsDocument for DropStatement {
425    fn as_document(&self) -> Document<'_> {
426        doc!("DROP", soft_space, self.object_type.as_document(), if self.if_exists { doc!(soft_space, "IF EXISTS") } else { nil }, soft_space, self.name.as_document())
427    }
428}
429
430#[cfg(feature = "oak-pretty-print")]
431impl AsDocument for DropObjectType {
432    fn as_document(&self) -> Document<'_> {
433        match self {
434            DropObjectType::Table => Document::text("TABLE"),
435            DropObjectType::View => Document::text("VIEW"),
436            DropObjectType::Index => Document::text("INDEX"),
437            DropObjectType::Database => Document::text("DATABASE"),
438        }
439    }
440}
441
442#[cfg(feature = "oak-pretty-print")]
443impl AsDocument for AlterStatement {
444    fn as_document(&self) -> Document<'_> {
445        let mut d = doc!("ALTER TABLE", soft_space, self.table_name.as_document());
446        if let Some(action) = &self.action {
447            d = doc!(d, soft_space, action.as_document());
448        }
449        d
450    }
451}
452
453#[cfg(feature = "oak-pretty-print")]
454impl AsDocument for AlterAction {
455    fn as_document(&self) -> Document<'_> {
456        match self {
457            AlterAction::AddColumn { name, data_type, .. } => {
458                let mut d = doc!("ADD COLUMN", soft_space, name.as_document());
459                if let Some(dt) = data_type {
460                    d = doc!(d, soft_space, dt.clone());
461                }
462                d
463            }
464            AlterAction::DropColumn { name, .. } => {
465                doc!("DROP COLUMN", soft_space, name.as_document())
466            }
467            AlterAction::RenameTo { new_name, .. } => {
468                doc!("RENAME TO", soft_space, new_name.as_document())
469            }
470        }
471    }
472}