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            Expression::Vector { elements, .. } => {
128                doc!("[", Document::join(elements.iter().map(|it| it.as_document()), doc!(",", soft_space)), "]")
129            }
130        }
131    }
132}
133
134#[cfg(feature = "oak-pretty-print")]
135impl AsDocument for BinaryOperator {
136    fn as_document(&self) -> Document<'_> {
137        match self {
138            BinaryOperator::Plus => Document::text("+"),
139            BinaryOperator::Minus => Document::text("-"),
140            BinaryOperator::Star => Document::text("*"),
141            BinaryOperator::Slash => Document::text("/"),
142            BinaryOperator::Percent => Document::text("%"),
143            BinaryOperator::And => Document::text("AND"),
144            BinaryOperator::Or => Document::text("OR"),
145            BinaryOperator::Equal => Document::text("="),
146            BinaryOperator::NotEqual => Document::text("<>"),
147            BinaryOperator::Less => Document::text("<"),
148            BinaryOperator::Greater => Document::text(">"),
149            BinaryOperator::LessEqual => Document::text("<="),
150            BinaryOperator::GreaterEqual => Document::text(">="),
151            BinaryOperator::Like => Document::text("LIKE"),
152        }
153    }
154}
155
156#[cfg(feature = "oak-pretty-print")]
157impl AsDocument for UnaryOperator {
158    fn as_document(&self) -> Document<'_> {
159        match self {
160            UnaryOperator::Plus => Document::text("+"),
161            UnaryOperator::Minus => Document::text("-"),
162            UnaryOperator::Not => Document::text("NOT"),
163        }
164    }
165}
166
167#[cfg(feature = "oak-pretty-print")]
168impl AsDocument for Literal {
169    fn as_document(&self) -> Document<'_> {
170        match self {
171            Literal::Number(n, _) => Document::text(n.as_ref()),
172            Literal::String(s, _) => doc!("'", s.as_ref(), "'"),
173            Literal::Boolean(b, _) => Document::text(if *b { "TRUE" } else { "FALSE" }),
174            Literal::Null(_) => Document::text("NULL"),
175        }
176    }
177}
178
179#[cfg(feature = "oak-pretty-print")]
180impl AsDocument for Identifier {
181    fn as_document(&self) -> Document<'_> {
182        Document::text(self.name.as_ref())
183    }
184}
185
186#[cfg(feature = "oak-pretty-print")]
187impl AsDocument for TableName {
188    fn as_document(&self) -> Document<'_> {
189        self.name.as_document()
190    }
191}
192
193#[cfg(feature = "oak-pretty-print")]
194impl AsDocument for ColumnName {
195    fn as_document(&self) -> Document<'_> {
196        self.name.as_document()
197    }
198}
199
200#[cfg(feature = "oak-pretty-print")]
201impl AsDocument for JoinClause {
202    fn as_document(&self) -> Document<'_> {
203        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 })
204    }
205}
206
207#[cfg(feature = "oak-pretty-print")]
208impl AsDocument for JoinType {
209    fn as_document(&self) -> Document<'_> {
210        match self {
211            JoinType::Inner => Document::text("INNER"),
212            JoinType::Left => Document::text("LEFT"),
213            JoinType::Right => Document::text("RIGHT"),
214            JoinType::Full => Document::text("FULL"),
215        }
216    }
217}
218
219#[cfg(feature = "oak-pretty-print")]
220impl AsDocument for GroupByClause {
221    fn as_document(&self) -> Document<'_> {
222        doc!("GROUP", soft_space, "BY", soft_space, Document::join(self.columns.iter().map(|it| it.as_document()), doc!(",", soft_space)))
223    }
224}
225
226#[cfg(feature = "oak-pretty-print")]
227impl AsDocument for HavingClause {
228    fn as_document(&self) -> Document<'_> {
229        doc!("HAVING", soft_space, self.condition.as_document())
230    }
231}
232
233#[cfg(feature = "oak-pretty-print")]
234impl AsDocument for OrderByClause {
235    fn as_document(&self) -> Document<'_> {
236        doc!("ORDER", soft_space, "BY", soft_space, Document::join(self.items.iter().map(|it| it.as_document()), doc!(",", soft_space)))
237    }
238}
239
240#[cfg(feature = "oak-pretty-print")]
241impl AsDocument for OrderByItem {
242    fn as_document(&self) -> Document<'_> {
243        doc!(self.expr.as_document(), soft_space, self.direction.as_document())
244    }
245}
246
247#[cfg(feature = "oak-pretty-print")]
248impl AsDocument for OrderDirection {
249    fn as_document(&self) -> Document<'_> {
250        match self {
251            OrderDirection::Asc => Document::text("ASC"),
252            OrderDirection::Desc => Document::text("DESC"),
253        }
254    }
255}
256
257#[cfg(feature = "oak-pretty-print")]
258impl AsDocument for LimitClause {
259    fn as_document(&self) -> Document<'_> {
260        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 })
261    }
262}
263
264#[cfg(feature = "oak-pretty-print")]
265impl AsDocument for CreateStatement {
266    fn as_document(&self) -> Document<'_> {
267        let mut parts = Vec::new();
268        parts.push(Document::text("CREATE"));
269        if let CreateBody::Index { unique: true, .. } = &self.body {
270            parts.push(soft_space);
271            parts.push(Document::text("UNIQUE"));
272        }
273        parts.push(soft_space);
274        parts.push(self.object_type.as_document());
275        if self.if_not_exists {
276            parts.push(soft_space);
277            parts.push(Document::text("IF NOT EXISTS"));
278        }
279        parts.push(soft_space);
280        parts.push(self.name.as_document());
281
282        match &self.body {
283            CreateBody::Table { columns, .. } => {
284                if !columns.is_empty() {
285                    parts.push(soft_space);
286                    parts.push(Document::text("("));
287                    let cols = Document::join(columns.iter().map(|it| it.as_document()), doc!(",", line));
288                    parts.push(indent(doc!(line, cols)));
289                    parts.push(line);
290                    parts.push(Document::text(")"));
291                }
292            }
293            CreateBody::View { query, .. } => {
294                parts.push(line);
295                parts.push(Document::text("AS"));
296                parts.push(soft_space);
297                parts.push(query.as_document());
298            }
299            CreateBody::Index { table_name, columns, unique: _, .. } => {
300                parts.push(soft_space);
301                parts.push(Document::text("ON"));
302                parts.push(soft_space);
303                parts.push(table_name.as_document());
304                parts.push(soft_space);
305                parts.push(Document::text("("));
306                let cols = Document::join(columns.iter().map(|it| it.as_document()), doc!(",", soft_space));
307                parts.push(cols);
308                parts.push(Document::text(")"));
309            }
310            CreateBody::Database { .. } => {}
311        }
312
313        Document::group(Document::Concat(parts))
314    }
315}
316
317#[cfg(feature = "oak-pretty-print")]
318impl AsDocument for ColumnDefinition {
319    fn as_document(&self) -> Document<'_> {
320        let data_type_str = self.data_type.to_string();
321        doc!(self.name.as_document(), soft_space, data_type_str, Document::join(self.constraints.iter().map(|it| doc!(soft_space, it.as_document())), nil))
322    }
323}
324
325#[cfg(feature = "oak-pretty-print")]
326impl AsDocument for ColumnConstraint {
327    fn as_document(&self) -> Document<'_> {
328        match self {
329            ColumnConstraint::PrimaryKey { .. } => Document::text("PRIMARY KEY"),
330            ColumnConstraint::NotNull { .. } => Document::text("NOT NULL"),
331            ColumnConstraint::Nullable { .. } => Document::text("NULL"),
332            ColumnConstraint::Unique { .. } => Document::text("UNIQUE"),
333            ColumnConstraint::Default(expr, _) => doc!("DEFAULT", soft_space, expr.as_document()),
334            ColumnConstraint::Check(expr, _) => doc!("CHECK", soft_space, "(", expr.as_document(), ")"),
335            ColumnConstraint::AutoIncrement { .. } => Document::text("AUTOINCREMENT"),
336        }
337    }
338}
339
340#[cfg(feature = "oak-pretty-print")]
341impl AsDocument for CreateObjectType {
342    fn as_document(&self) -> Document<'_> {
343        match self {
344            CreateObjectType::Table => Document::text("TABLE"),
345            CreateObjectType::View => Document::text("VIEW"),
346            CreateObjectType::Index => Document::text("INDEX"),
347            CreateObjectType::Database => Document::text("DATABASE"),
348        }
349    }
350}
351
352#[cfg(feature = "oak-pretty-print")]
353impl AsDocument for InsertStatement {
354    fn as_document(&self) -> Document<'_> {
355        let mut parts = Vec::new();
356        parts.push(Document::text("INSERT INTO"));
357        parts.push(soft_space);
358        parts.push(self.table_name.as_document());
359
360        if !self.columns.is_empty() {
361            parts.push(soft_space);
362            parts.push(Document::text("("));
363            parts.push(Document::join(self.columns.iter().map(|it| it.as_document()), doc!(",", soft_space)));
364            parts.push(Document::text(")"));
365        }
366
367        parts.push(line);
368        parts.push(Document::text("VALUES"));
369        parts.push(soft_space);
370        parts.push(Document::text("("));
371        parts.push(Document::join(self.values.iter().map(|it| it.as_document()), doc!(",", soft_space)));
372        parts.push(Document::text(")"));
373
374        Document::group(Document::Concat(parts))
375    }
376}
377
378#[cfg(feature = "oak-pretty-print")]
379impl AsDocument for UpdateStatement {
380    fn as_document(&self) -> Document<'_> {
381        let mut parts = Vec::new();
382        parts.push(Document::text("UPDATE"));
383        parts.push(soft_space);
384        parts.push(self.table_name.as_document());
385        parts.push(line);
386        parts.push(Document::text("SET"));
387        parts.push(soft_space);
388        parts.push(Document::join(self.assignments.iter().map(|it| it.as_document()), doc!(",", line)));
389
390        if let Some(selection) = &self.selection {
391            parts.push(line);
392            parts.push(Document::text("WHERE"));
393            parts.push(soft_space);
394            parts.push(selection.as_document());
395        }
396
397        Document::group(Document::Concat(parts))
398    }
399}
400
401#[cfg(feature = "oak-pretty-print")]
402impl AsDocument for Assignment {
403    fn as_document(&self) -> Document<'_> {
404        doc!(self.column.as_document(), soft_space, "=", soft_space, self.value.as_document())
405    }
406}
407
408#[cfg(feature = "oak-pretty-print")]
409impl AsDocument for DeleteStatement {
410    fn as_document(&self) -> Document<'_> {
411        let mut parts = Vec::new();
412        parts.push(Document::text("DELETE FROM"));
413        parts.push(soft_space);
414        parts.push(self.table_name.as_document());
415
416        if let Some(selection) = &self.selection {
417            parts.push(line);
418            parts.push(Document::text("WHERE"));
419            parts.push(soft_space);
420            parts.push(selection.as_document());
421        }
422
423        Document::group(Document::Concat(parts))
424    }
425}
426
427#[cfg(feature = "oak-pretty-print")]
428impl AsDocument for DropStatement {
429    fn as_document(&self) -> Document<'_> {
430        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())
431    }
432}
433
434#[cfg(feature = "oak-pretty-print")]
435impl AsDocument for DropObjectType {
436    fn as_document(&self) -> Document<'_> {
437        match self {
438            DropObjectType::Table => Document::text("TABLE"),
439            DropObjectType::View => Document::text("VIEW"),
440            DropObjectType::Index => Document::text("INDEX"),
441            DropObjectType::Database => Document::text("DATABASE"),
442        }
443    }
444}
445
446#[cfg(feature = "oak-pretty-print")]
447impl AsDocument for AlterStatement {
448    fn as_document(&self) -> Document<'_> {
449        let mut d = doc!("ALTER TABLE", soft_space, self.table_name.as_document());
450        if let Some(action) = &self.action {
451            d = doc!(d, soft_space, action.as_document());
452        }
453        d
454    }
455}
456
457#[cfg(feature = "oak-pretty-print")]
458impl AsDocument for AlterAction {
459    fn as_document(&self) -> Document<'_> {
460        match self {
461            AlterAction::AddColumn { name, data_type, .. } => {
462                let mut d = doc!("ADD COLUMN", soft_space, name.as_document());
463                if let Some(dt) = data_type {
464                    let dt_str = dt.to_string();
465                    d = doc!(d, soft_space, dt_str);
466                }
467                d
468            }
469            AlterAction::DropColumn { name, .. } => {
470                doc!("DROP COLUMN", soft_space, name.as_document())
471            }
472            AlterAction::RenameTo { new_name, .. } => {
473                doc!("RENAME TO", soft_space, new_name.as_document())
474            }
475        }
476    }
477}