mz_sql_pretty/
doc.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Functions that convert SQL AST nodes to pretty Docs.
11
12use itertools::Itertools;
13use mz_sql_parser::ast::display::AstDisplay;
14use mz_sql_parser::ast::*;
15use pretty::{Doc, RcDoc};
16
17use crate::util::{
18    bracket, bracket_doc, comma_separate, comma_separated, intersperse_line_nest, nest,
19    nest_comma_separate, nest_title, title_comma_separate,
20};
21use crate::{Pretty, TAB};
22
23impl Pretty {
24    // Use when we don't know what to do.
25    pub(crate) fn doc_display<'a, T: AstDisplay>(&self, v: &T, _debug: &str) -> RcDoc<'a, ()> {
26        #[cfg(test)]
27        eprintln!(
28            "UNKNOWN PRETTY TYPE in {}: {}, {}",
29            _debug,
30            std::any::type_name::<T>(),
31            v.to_ast_string_simple()
32        );
33        self.doc_display_pass(v)
34    }
35
36    // Use when the AstDisplay trait is what we want.
37    fn doc_display_pass<'a, T: AstDisplay>(&self, v: &T) -> RcDoc<'a, ()> {
38        RcDoc::text(v.to_ast_string(self.config.format_mode))
39    }
40
41    pub(crate) fn doc_create_source<'a, T: AstInfo>(
42        &'a self,
43        v: &'a CreateSourceStatement<T>,
44    ) -> RcDoc<'a> {
45        let mut docs = Vec::new();
46        let title = format!(
47            "CREATE SOURCE{}",
48            if v.if_not_exists {
49                " IF NOT EXISTS"
50            } else {
51                ""
52            }
53        );
54        let mut doc = self.doc_display_pass(&v.name);
55        let mut names = Vec::new();
56        names.extend(v.col_names.iter().map(|name| self.doc_display_pass(name)));
57        names.extend(v.key_constraint.iter().map(|kc| self.doc_display_pass(kc)));
58        if !names.is_empty() {
59            doc = nest(doc, bracket("(", comma_separated(names), ")"));
60        }
61        docs.push(nest_title(title, doc));
62        if let Some(cluster) = &v.in_cluster {
63            docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
64        }
65        docs.push(nest_title("FROM", self.doc_display_pass(&v.connection)));
66        if let Some(format) = &v.format {
67            docs.push(self.doc_format_specifier(format));
68        }
69        if !v.include_metadata.is_empty() {
70            docs.push(nest_title(
71                "INCLUDE",
72                comma_separate(|im| self.doc_display_pass(im), &v.include_metadata),
73            ));
74        }
75        if let Some(envelope) = &v.envelope {
76            docs.push(nest_title("ENVELOPE", self.doc_display_pass(envelope)));
77        }
78        if let Some(references) = &v.external_references {
79            docs.push(self.doc_external_references(references));
80        }
81        if let Some(progress) = &v.progress_subsource {
82            docs.push(nest_title(
83                "EXPOSE PROGRESS AS",
84                self.doc_display_pass(progress),
85            ));
86        }
87        if !v.with_options.is_empty() {
88            docs.push(bracket(
89                "WITH (",
90                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
91                ")",
92            ));
93        }
94        RcDoc::intersperse(docs, Doc::line()).group()
95    }
96
97    pub(crate) fn doc_create_webhook_source<'a, T: AstInfo>(
98        &'a self,
99        v: &'a CreateWebhookSourceStatement<T>,
100    ) -> RcDoc<'a> {
101        let mut docs = Vec::new();
102
103        let mut title = "CREATE ".to_string();
104        if v.is_table {
105            title.push_str("TABLE");
106        } else {
107            title.push_str("SOURCE");
108        }
109        if v.if_not_exists {
110            title.push_str(" IF NOT EXISTS");
111        }
112        docs.push(nest_title(title, self.doc_display_pass(&v.name)));
113
114        // IN CLUSTER (only for sources, not tables)
115        if !v.is_table {
116            if let Some(cluster) = &v.in_cluster {
117                docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
118            }
119        }
120
121        docs.push(RcDoc::text("FROM WEBHOOK"));
122        docs.push(nest_title(
123            "BODY FORMAT",
124            self.doc_display_pass(&v.body_format),
125        ));
126
127        if !v.include_headers.mappings.is_empty() || v.include_headers.column.is_some() {
128            let mut header_docs = Vec::new();
129
130            // Individual header mappings
131            for mapping in &v.include_headers.mappings {
132                header_docs.push(self.doc_display_pass(mapping));
133            }
134
135            // INCLUDE HEADERS column
136            if let Some(filters) = &v.include_headers.column {
137                if filters.is_empty() {
138                    header_docs.push(RcDoc::text("INCLUDE HEADERS"));
139                } else {
140                    header_docs.push(bracket(
141                        "INCLUDE HEADERS (",
142                        comma_separate(|f| self.doc_display_pass(f), filters),
143                        ")",
144                    ));
145                }
146            }
147
148            if !header_docs.is_empty() {
149                docs.extend(header_docs);
150            }
151        }
152
153        if let Some(check) = &v.validate_using {
154            docs.push(self.doc_webhook_check(check));
155        }
156
157        RcDoc::intersperse(docs, Doc::line()).group()
158    }
159
160    fn doc_webhook_check<'a, T: AstInfo>(
161        &'a self,
162        v: &'a CreateWebhookSourceCheck<T>,
163    ) -> RcDoc<'a> {
164        let mut inner = Vec::new();
165
166        if let Some(options) = &v.options {
167            let mut with_items = Vec::new();
168
169            for header in &options.headers {
170                with_items.push(self.doc_display_pass(header));
171            }
172            for body in &options.bodies {
173                with_items.push(self.doc_display_pass(body));
174            }
175            for secret in &options.secrets {
176                with_items.push(self.doc_display_pass(secret));
177            }
178
179            if !with_items.is_empty() {
180                inner.push(bracket("WITH (", comma_separated(with_items), ")"));
181                inner.push(RcDoc::line());
182            }
183        }
184
185        inner.push(self.doc_display_pass(&v.using));
186
187        bracket_doc(
188            RcDoc::text("CHECK ("),
189            RcDoc::concat(inner),
190            RcDoc::text(")"),
191            RcDoc::line(),
192        )
193    }
194
195    pub(crate) fn doc_create_table<'a, T: AstInfo>(
196        &'a self,
197        v: &'a CreateTableStatement<T>,
198    ) -> RcDoc<'a> {
199        let mut docs = Vec::new();
200
201        // CREATE [TEMPORARY] TABLE [IF NOT EXISTS] name
202        let mut title = "CREATE ".to_string();
203        if v.temporary {
204            title.push_str("TEMPORARY ");
205        }
206        title.push_str("TABLE");
207        if v.if_not_exists {
208            title.push_str(" IF NOT EXISTS");
209        }
210
211        // Table name and columns/constraints
212        let mut col_items = Vec::new();
213        col_items.extend(v.columns.iter().map(|c| self.doc_display_pass(c)));
214        col_items.extend(v.constraints.iter().map(|c| self.doc_display_pass(c)));
215
216        let table_def = nest(
217            self.doc_display_pass(&v.name),
218            bracket("(", comma_separated(col_items), ")"),
219        );
220        docs.push(nest_title(title, table_def));
221
222        // WITH options
223        if !v.with_options.is_empty() {
224            docs.push(bracket(
225                "WITH (",
226                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
227                ")",
228            ));
229        }
230
231        RcDoc::intersperse(docs, Doc::line()).group()
232    }
233
234    pub(crate) fn doc_create_table_from_source<'a, T: AstInfo>(
235        &'a self,
236        v: &'a CreateTableFromSourceStatement<T>,
237    ) -> RcDoc<'a> {
238        let mut docs = Vec::new();
239
240        // CREATE TABLE [IF NOT EXISTS] name
241        let mut title = "CREATE TABLE".to_string();
242        if v.if_not_exists {
243            title.push_str(" IF NOT EXISTS");
244        }
245
246        let mut table_def = self.doc_display_pass(&v.name);
247
248        let has_columns_or_constraints = match &v.columns {
249            TableFromSourceColumns::NotSpecified => false,
250            _ => true,
251        } || !v.constraints.is_empty();
252
253        if has_columns_or_constraints {
254            let mut items = Vec::new();
255
256            match &v.columns {
257                TableFromSourceColumns::NotSpecified => {}
258                TableFromSourceColumns::Named(cols) => {
259                    items.extend(cols.iter().map(|c| self.doc_display_pass(c)));
260                }
261                TableFromSourceColumns::Defined(cols) => {
262                    items.extend(cols.iter().map(|c| self.doc_display_pass(c)));
263                }
264            }
265
266            items.extend(v.constraints.iter().map(|c| self.doc_display_pass(c)));
267
268            if !items.is_empty() {
269                table_def = nest(table_def, bracket("(", comma_separated(items), ")"));
270            }
271        }
272
273        docs.push(nest_title(title, table_def));
274
275        // FROM SOURCE
276        let mut from_source = nest_title("FROM SOURCE", self.doc_display_pass(&v.source));
277        if let Some(reference) = &v.external_reference {
278            from_source = nest(
279                from_source,
280                bracket("(REFERENCE = ", self.doc_display_pass(reference), ")"),
281            );
282        }
283        docs.push(from_source);
284
285        if let Some(format) = &v.format {
286            docs.push(self.doc_format_specifier(format));
287        }
288
289        if !v.include_metadata.is_empty() {
290            docs.push(nest_title(
291                "INCLUDE",
292                comma_separate(|im| self.doc_display_pass(im), &v.include_metadata),
293            ));
294        }
295
296        if let Some(envelope) = &v.envelope {
297            docs.push(nest_title("ENVELOPE", self.doc_display_pass(envelope)));
298        }
299
300        if !v.with_options.is_empty() {
301            docs.push(bracket(
302                "WITH (",
303                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
304                ")",
305            ));
306        }
307
308        RcDoc::intersperse(docs, Doc::line()).group()
309    }
310
311    pub(crate) fn doc_create_connection<'a, T: AstInfo>(
312        &'a self,
313        v: &'a CreateConnectionStatement<T>,
314    ) -> RcDoc<'a> {
315        let mut docs = Vec::new();
316
317        let mut title = "CREATE CONNECTION".to_string();
318        if v.if_not_exists {
319            title.push_str(" IF NOT EXISTS");
320        }
321        docs.push(nest_title(title, self.doc_display_pass(&v.name)));
322
323        let connection_with_values = nest(
324            RcDoc::concat([
325                RcDoc::text("TO "),
326                self.doc_display_pass(&v.connection_type),
327            ]),
328            bracket(
329                "(",
330                comma_separate(|val| self.doc_display_pass(val), &v.values),
331                ")",
332            ),
333        );
334        docs.push(connection_with_values);
335
336        if !v.with_options.is_empty() {
337            docs.push(bracket(
338                "WITH (",
339                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
340                ")",
341            ));
342        }
343
344        RcDoc::intersperse(docs, Doc::line()).group()
345    }
346
347    pub(crate) fn doc_create_sink<'a, T: AstInfo>(
348        &'a self,
349        v: &'a CreateSinkStatement<T>,
350    ) -> RcDoc<'a> {
351        let mut docs = Vec::new();
352
353        // CREATE SINK [IF NOT EXISTS] [name]
354        let mut title = "CREATE SINK".to_string();
355        if v.if_not_exists {
356            title.push_str(" IF NOT EXISTS");
357        }
358
359        if let Some(name) = &v.name {
360            docs.push(nest_title(title, self.doc_display_pass(name)));
361        } else {
362            docs.push(RcDoc::text(title));
363        }
364
365        if let Some(cluster) = &v.in_cluster {
366            docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
367        }
368
369        docs.push(nest_title("FROM", self.doc_display_pass(&v.from)));
370        docs.push(nest_title("INTO", self.doc_display_pass(&v.connection)));
371
372        if let Some(format) = &v.format {
373            docs.push(self.doc_format_specifier(format));
374        }
375
376        if let Some(envelope) = &v.envelope {
377            docs.push(nest_title("ENVELOPE", self.doc_display_pass(envelope)));
378        }
379
380        if !v.with_options.is_empty() {
381            docs.push(bracket(
382                "WITH (",
383                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
384                ")",
385            ));
386        }
387
388        RcDoc::intersperse(docs, Doc::line()).group()
389    }
390
391    pub(crate) fn doc_create_subsource<'a, T: AstInfo>(
392        &'a self,
393        v: &'a CreateSubsourceStatement<T>,
394    ) -> RcDoc<'a> {
395        let mut docs = Vec::new();
396
397        // CREATE SUBSOURCE [IF NOT EXISTS] name
398        let mut title = "CREATE SUBSOURCE".to_string();
399        if v.if_not_exists {
400            title.push_str(" IF NOT EXISTS");
401        }
402
403        // Table name with columns/constraints
404        let mut col_items = Vec::new();
405        col_items.extend(v.columns.iter().map(|c| self.doc_display_pass(c)));
406        col_items.extend(v.constraints.iter().map(|c| self.doc_display_pass(c)));
407
408        let table_def = nest(
409            self.doc_display_pass(&v.name),
410            bracket("(", comma_separated(col_items), ")"),
411        );
412        docs.push(nest_title(title, table_def));
413
414        // OF SOURCE
415        if let Some(of_source) = &v.of_source {
416            docs.push(nest_title("OF SOURCE", self.doc_display_pass(of_source)));
417        }
418
419        // WITH options
420        if !v.with_options.is_empty() {
421            docs.push(bracket(
422                "WITH (",
423                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
424                ")",
425            ));
426        }
427
428        RcDoc::intersperse(docs, Doc::line()).group()
429    }
430
431    pub(crate) fn doc_create_cluster<'a, T: AstInfo>(
432        &'a self,
433        v: &'a CreateClusterStatement<T>,
434    ) -> RcDoc<'a> {
435        let mut docs = Vec::new();
436
437        // CREATE CLUSTER name
438        docs.push(nest_title("CREATE CLUSTER", self.doc_display_pass(&v.name)));
439
440        // OPTIONS (...)
441        if !v.options.is_empty() {
442            docs.push(bracket(
443                "(",
444                comma_separate(|o| self.doc_display_pass(o), &v.options),
445                ")",
446            ));
447        }
448
449        // FEATURES (...)
450        if !v.features.is_empty() {
451            docs.push(bracket(
452                "FEATURES (",
453                comma_separate(|f| self.doc_display_pass(f), &v.features),
454                ")",
455            ));
456        }
457
458        RcDoc::intersperse(docs, Doc::line()).group()
459    }
460
461    pub(crate) fn doc_create_cluster_replica<'a, T: AstInfo>(
462        &'a self,
463        v: &'a CreateClusterReplicaStatement<T>,
464    ) -> RcDoc<'a> {
465        let mut docs = Vec::new();
466
467        // CREATE CLUSTER REPLICA cluster.replica
468        let replica_name = RcDoc::concat([
469            self.doc_display_pass(&v.of_cluster),
470            RcDoc::text("."),
471            self.doc_display_pass(&v.definition.name),
472        ]);
473        docs.push(nest_title("CREATE CLUSTER REPLICA", replica_name));
474
475        // OPTIONS (...)
476        docs.push(bracket(
477            "(",
478            comma_separate(|o| self.doc_display_pass(o), &v.definition.options),
479            ")",
480        ));
481
482        RcDoc::intersperse(docs, Doc::line()).group()
483    }
484
485    pub(crate) fn doc_create_network_policy<'a, T: AstInfo>(
486        &'a self,
487        v: &'a CreateNetworkPolicyStatement<T>,
488    ) -> RcDoc<'a> {
489        let docs = vec![
490            // CREATE NETWORK POLICY name
491            nest_title("CREATE NETWORK POLICY", self.doc_display_pass(&v.name)),
492            // OPTIONS (...)
493            bracket(
494                "(",
495                comma_separate(|o| self.doc_display_pass(o), &v.options),
496                ")",
497            ),
498        ];
499
500        RcDoc::intersperse(docs, Doc::line()).group()
501    }
502
503    pub(crate) fn doc_create_index<'a, T: AstInfo>(
504        &'a self,
505        v: &'a CreateIndexStatement<T>,
506    ) -> RcDoc<'a> {
507        let mut docs = Vec::new();
508
509        // CREATE [DEFAULT] INDEX [IF NOT EXISTS] [name]
510        let mut title = "CREATE".to_string();
511        if v.key_parts.is_none() {
512            title.push_str(" DEFAULT");
513        }
514        title.push_str(" INDEX");
515        if v.if_not_exists {
516            title.push_str(" IF NOT EXISTS");
517        }
518
519        if let Some(name) = &v.name {
520            docs.push(nest_title(title, self.doc_display_pass(name)));
521        } else {
522            docs.push(RcDoc::text(title));
523        }
524
525        // IN CLUSTER
526        if let Some(cluster) = &v.in_cluster {
527            docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
528        }
529
530        // ON table_name [(key_parts)]
531        let on_clause = if let Some(key_parts) = &v.key_parts {
532            nest(
533                self.doc_display_pass(&v.on_name),
534                bracket(
535                    "(",
536                    comma_separate(|k| self.doc_display_pass(k), key_parts),
537                    ")",
538                ),
539            )
540        } else {
541            self.doc_display_pass(&v.on_name)
542        };
543        docs.push(nest_title("ON", on_clause));
544
545        // WITH options
546        if !v.with_options.is_empty() {
547            docs.push(bracket(
548                "WITH (",
549                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
550                ")",
551            ));
552        }
553
554        RcDoc::intersperse(docs, Doc::line()).group()
555    }
556
557    fn doc_format_specifier<T: AstInfo>(&self, v: &FormatSpecifier<T>) -> RcDoc<'_> {
558        match v {
559            FormatSpecifier::Bare(format) => nest_title("FORMAT", self.doc_display_pass(format)),
560            FormatSpecifier::KeyValue { key, value } => {
561                let docs = vec![
562                    nest_title("KEY FORMAT", self.doc_display_pass(key)),
563                    nest_title("VALUE FORMAT", self.doc_display_pass(value)),
564                ];
565                RcDoc::intersperse(docs, Doc::line()).group()
566            }
567        }
568    }
569
570    fn doc_external_references<'a>(&'a self, v: &'a ExternalReferences) -> RcDoc<'a> {
571        match v {
572            ExternalReferences::SubsetTables(subsources) => bracket(
573                "FOR TABLES (",
574                comma_separate(|s| self.doc_display_pass(s), subsources),
575                ")",
576            ),
577            ExternalReferences::SubsetSchemas(schemas) => bracket(
578                "FOR SCHEMAS (",
579                comma_separate(|s| self.doc_display_pass(s), schemas),
580                ")",
581            ),
582            ExternalReferences::All => RcDoc::text("FOR ALL TABLES"),
583        }
584    }
585
586    pub(crate) fn doc_copy<'a, T: AstInfo>(&'a self, v: &'a CopyStatement<T>) -> RcDoc<'a> {
587        let relation = match &v.relation {
588            CopyRelation::Named { name, columns } => {
589                let mut relation = self.doc_display_pass(name);
590                if !columns.is_empty() {
591                    relation = bracket_doc(
592                        nest(relation, RcDoc::text("(")),
593                        comma_separate(|c| self.doc_display_pass(c), columns),
594                        RcDoc::text(")"),
595                        RcDoc::line_(),
596                    );
597                }
598                RcDoc::concat([RcDoc::text("COPY "), relation])
599            }
600            CopyRelation::Select(query) => bracket("COPY (", self.doc_select_statement(query), ")"),
601            CopyRelation::Subscribe(query) => bracket("COPY (", self.doc_subscribe(query), ")"),
602        };
603        let mut docs = vec![
604            relation,
605            RcDoc::concat([
606                self.doc_display_pass(&v.direction),
607                RcDoc::text(" "),
608                self.doc_display_pass(&v.target),
609            ]),
610        ];
611        if !v.options.is_empty() {
612            docs.push(bracket(
613                "WITH (",
614                comma_separate(|o| self.doc_display_pass(o), &v.options),
615                ")",
616            ));
617        }
618        RcDoc::intersperse(docs, Doc::line()).group()
619    }
620
621    pub(crate) fn doc_subscribe<'a, T: AstInfo>(
622        &'a self,
623        v: &'a SubscribeStatement<T>,
624    ) -> RcDoc<'a> {
625        let doc = match &v.relation {
626            SubscribeRelation::Name(name) => nest_title("SUBSCRIBE", self.doc_display_pass(name)),
627            SubscribeRelation::Query(query) => bracket("SUBSCRIBE (", self.doc_query(query), ")"),
628        };
629        let mut docs = vec![doc];
630        if !v.options.is_empty() {
631            docs.push(bracket(
632                "WITH (",
633                comma_separate(|o| self.doc_display_pass(o), &v.options),
634                ")",
635            ));
636        }
637        if let Some(as_of) = &v.as_of {
638            docs.push(self.doc_as_of(as_of));
639        }
640        if let Some(up_to) = &v.up_to {
641            docs.push(nest_title("UP TO", self.doc_expr(up_to)));
642        }
643        match &v.output {
644            SubscribeOutput::Diffs => {}
645            SubscribeOutput::WithinTimestampOrderBy { order_by } => {
646                docs.push(nest_title(
647                    "WITHIN TIMESTAMP ORDER BY ",
648                    comma_separate(|o| self.doc_order_by_expr(o), order_by),
649                ));
650            }
651            SubscribeOutput::EnvelopeUpsert { key_columns } => {
652                docs.push(bracket(
653                    "ENVELOPE UPSERT (KEY (",
654                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
655                    "))",
656                ));
657            }
658            SubscribeOutput::EnvelopeDebezium { key_columns } => {
659                docs.push(bracket(
660                    "ENVELOPE DEBEZIUM (KEY (",
661                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
662                    "))",
663                ));
664            }
665        }
666        RcDoc::intersperse(docs, Doc::line()).group()
667    }
668
669    fn doc_as_of<'a, T: AstInfo>(&'a self, v: &'a AsOf<T>) -> RcDoc<'a> {
670        let (title, expr) = match v {
671            AsOf::At(expr) => ("AS OF", expr),
672            AsOf::AtLeast(expr) => ("AS OF AT LEAST", expr),
673        };
674        nest_title(title, self.doc_expr(expr))
675    }
676
677    pub(crate) fn doc_create_view<'a, T: AstInfo>(
678        &'a self,
679        v: &'a CreateViewStatement<T>,
680    ) -> RcDoc<'a> {
681        let mut docs = vec![];
682        docs.push(RcDoc::text(format!(
683            "CREATE{}{} VIEW{}",
684            if v.if_exists == IfExistsBehavior::Replace {
685                " OR REPLACE"
686            } else {
687                ""
688            },
689            if v.temporary { " TEMPORARY" } else { "" },
690            if v.if_exists == IfExistsBehavior::Skip {
691                " IF NOT EXISTS"
692            } else {
693                ""
694            },
695        )));
696        docs.push(self.doc_view_definition(&v.definition));
697        intersperse_line_nest(docs)
698    }
699
700    pub(crate) fn doc_create_materialized_view<'a, T: AstInfo>(
701        &'a self,
702        v: &'a CreateMaterializedViewStatement<T>,
703    ) -> RcDoc<'a> {
704        let mut docs = vec![];
705        docs.push(RcDoc::text(format!(
706            "CREATE{}{} MATERIALIZED VIEW{} {}",
707            if v.if_exists == IfExistsBehavior::Replace {
708                " OR REPLACE"
709            } else {
710                ""
711            },
712            if v.replacement_for.is_some() {
713                " REPLACEMENT"
714            } else {
715                ""
716            },
717            if v.if_exists == IfExistsBehavior::Skip {
718                " IF NOT EXISTS"
719            } else {
720                ""
721            },
722            v.name,
723        )));
724        if !v.columns.is_empty() {
725            docs.push(bracket(
726                "(",
727                comma_separate(|c| self.doc_display_pass(c), &v.columns),
728                ")",
729            ));
730        }
731        if let Some(target) = &v.replacement_for {
732            docs.push(RcDoc::text(format!(
733                "FOR {}",
734                target.to_ast_string_simple()
735            )));
736        }
737        if let Some(cluster) = &v.in_cluster {
738            docs.push(RcDoc::text(format!(
739                "IN CLUSTER {}",
740                cluster.to_ast_string_simple()
741            )));
742        }
743        if !v.with_options.is_empty() {
744            docs.push(bracket(
745                "WITH (",
746                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
747                ")",
748            ));
749        }
750        docs.push(nest_title("AS", self.doc_query(&v.query)));
751        intersperse_line_nest(docs)
752    }
753
754    fn doc_view_definition<'a, T: AstInfo>(&'a self, v: &'a ViewDefinition<T>) -> RcDoc<'a> {
755        let mut docs = vec![RcDoc::text(v.name.to_string())];
756        if !v.columns.is_empty() {
757            docs.push(bracket(
758                "(",
759                comma_separate(|c| self.doc_display_pass(c), &v.columns),
760                ")",
761            ));
762        }
763        docs.push(nest_title("AS", self.doc_query(&v.query)));
764        RcDoc::intersperse(docs, Doc::line()).group()
765    }
766
767    pub(crate) fn doc_insert<'a, T: AstInfo>(&'a self, v: &'a InsertStatement<T>) -> RcDoc<'a> {
768        let mut first = vec![RcDoc::text(format!(
769            "INSERT INTO {}",
770            v.table_name.to_ast_string_simple()
771        ))];
772        if !v.columns.is_empty() {
773            first.push(bracket(
774                "(",
775                comma_separate(|c| self.doc_display_pass(c), &v.columns),
776                ")",
777            ));
778        }
779        let sources = match &v.source {
780            InsertSource::Query(query) => self.doc_query(query),
781            _ => self.doc_display(&v.source, "insert source"),
782        };
783        let mut doc = intersperse_line_nest([intersperse_line_nest(first), sources]);
784        if !v.returning.is_empty() {
785            doc = nest(
786                doc,
787                nest_title(
788                    "RETURNING",
789                    comma_separate(|r| self.doc_display_pass(r), &v.returning),
790                ),
791            )
792        }
793        doc
794    }
795
796    pub(crate) fn doc_select_statement<'a, T: AstInfo>(
797        &'a self,
798        v: &'a SelectStatement<T>,
799    ) -> RcDoc<'a> {
800        let mut doc = self.doc_query(&v.query);
801        if let Some(as_of) = &v.as_of {
802            doc = intersperse_line_nest([doc, self.doc_as_of(as_of)]);
803        }
804        doc.group()
805    }
806
807    fn doc_order_by<'a, T: AstInfo>(&'a self, v: &'a [OrderByExpr<T>]) -> RcDoc<'a> {
808        title_comma_separate("ORDER BY", |o| self.doc_order_by_expr(o), v)
809    }
810
811    fn doc_order_by_expr<'a, T: AstInfo>(&'a self, v: &'a OrderByExpr<T>) -> RcDoc<'a> {
812        let doc = self.doc_expr(&v.expr);
813        let doc = match v.asc {
814            Some(true) => nest(doc, RcDoc::text("ASC")),
815            Some(false) => nest(doc, RcDoc::text("DESC")),
816            None => doc,
817        };
818        match v.nulls_last {
819            Some(true) => nest(doc, RcDoc::text("NULLS LAST")),
820            Some(false) => nest(doc, RcDoc::text("NULLS FIRST")),
821            None => doc,
822        }
823    }
824
825    fn doc_query<'a, T: AstInfo>(&'a self, v: &'a Query<T>) -> RcDoc<'a> {
826        let mut docs = vec![];
827        if !v.ctes.is_empty() {
828            match &v.ctes {
829                CteBlock::Simple(ctes) => {
830                    docs.push(title_comma_separate("WITH", |cte| self.doc_cte(cte), ctes))
831                }
832                CteBlock::MutuallyRecursive(mutrec) => {
833                    let mut doc = RcDoc::text("WITH MUTUALLY RECURSIVE");
834                    if !mutrec.options.is_empty() {
835                        doc = nest(
836                            doc,
837                            bracket(
838                                "(",
839                                comma_separate(|o| self.doc_display_pass(o), &mutrec.options),
840                                ")",
841                            ),
842                        );
843                    }
844                    docs.push(nest(
845                        doc,
846                        comma_separate(|c| self.doc_mutually_recursive(c), &mutrec.ctes),
847                    ));
848                }
849            }
850        }
851        docs.push(self.doc_set_expr(&v.body));
852        if !v.order_by.is_empty() {
853            docs.push(self.doc_order_by(&v.order_by));
854        }
855
856        let offset = if let Some(offset) = &v.offset {
857            vec![RcDoc::concat([nest_title("OFFSET", self.doc_expr(offset))])]
858        } else {
859            vec![]
860        };
861
862        if let Some(limit) = &v.limit {
863            if limit.with_ties {
864                docs.extend(offset);
865                docs.push(RcDoc::concat([
866                    RcDoc::text("FETCH FIRST "),
867                    self.doc_expr(&limit.quantity),
868                    RcDoc::text(" ROWS WITH TIES"),
869                ]));
870            } else {
871                docs.push(nest_title("LIMIT", self.doc_expr(&limit.quantity)));
872                docs.extend(offset);
873            }
874        } else {
875            docs.extend(offset);
876        }
877
878        RcDoc::intersperse(docs, Doc::line()).group()
879    }
880
881    fn doc_cte<'a, T: AstInfo>(&'a self, v: &'a Cte<T>) -> RcDoc<'a> {
882        RcDoc::concat([
883            RcDoc::text(format!("{} AS", v.alias)),
884            RcDoc::line(),
885            bracket("(", self.doc_query(&v.query), ")"),
886        ])
887    }
888
889    fn doc_mutually_recursive<'a, T: AstInfo>(&'a self, v: &'a CteMutRec<T>) -> RcDoc<'a> {
890        let mut docs = Vec::new();
891        if !v.columns.is_empty() {
892            docs.push(bracket(
893                "(",
894                comma_separate(|c| self.doc_display_pass(c), &v.columns),
895                ")",
896            ));
897        }
898        docs.push(bracket("AS (", self.doc_query(&v.query), ")"));
899        nest(
900            self.doc_display_pass(&v.name),
901            RcDoc::intersperse(docs, Doc::line()).group(),
902        )
903    }
904
905    fn doc_set_expr<'a, T: AstInfo>(&'a self, v: &'a SetExpr<T>) -> RcDoc<'a> {
906        match v {
907            SetExpr::Select(v) => self.doc_select(v),
908            SetExpr::Query(v) => bracket("(", self.doc_query(v), ")"),
909            SetExpr::SetOperation {
910                op,
911                all,
912                left,
913                right,
914            } => {
915                let all_str = if *all { " ALL" } else { "" };
916                RcDoc::concat([
917                    self.doc_set_expr(left),
918                    RcDoc::line(),
919                    RcDoc::concat([
920                        RcDoc::text(format!("{}{}", op, all_str)),
921                        RcDoc::line(),
922                        self.doc_set_expr(right),
923                    ])
924                    .nest(TAB)
925                    .group(),
926                ])
927            }
928            SetExpr::Values(v) => self.doc_values(v),
929            SetExpr::Show(v) => self.doc_display(v, "SHOW"),
930            SetExpr::Table(v) => nest(RcDoc::text("TABLE"), self.doc_display_pass(v)),
931        }
932        .group()
933    }
934
935    fn doc_values<'a, T: AstInfo>(&'a self, v: &'a Values<T>) -> RcDoc<'a> {
936        let rows =
937            v.0.iter()
938                .map(|row| bracket("(", comma_separate(|v| self.doc_expr(v), row), ")"));
939        RcDoc::concat([RcDoc::text("VALUES"), RcDoc::line(), comma_separated(rows)])
940            .nest(TAB)
941            .group()
942    }
943
944    fn doc_table_with_joins<'a, T: AstInfo>(&'a self, v: &'a TableWithJoins<T>) -> RcDoc<'a> {
945        let mut docs = vec![self.doc_table_factor(&v.relation)];
946        for j in &v.joins {
947            docs.push(self.doc_join(j));
948        }
949        intersperse_line_nest(docs)
950    }
951
952    fn doc_join<'a, T: AstInfo>(&'a self, v: &'a Join<T>) -> RcDoc<'a> {
953        let (constraint, name) = match &v.join_operator {
954            JoinOperator::Inner(constraint) => (constraint, "JOIN"),
955            JoinOperator::FullOuter(constraint) => (constraint, "FULL JOIN"),
956            JoinOperator::LeftOuter(constraint) => (constraint, "LEFT JOIN"),
957            JoinOperator::RightOuter(constraint) => (constraint, "RIGHT JOIN"),
958            _ => return self.doc_display(v, "join operator"),
959        };
960        let constraint = match constraint {
961            JoinConstraint::On(expr) => nest_title("ON", self.doc_expr(expr)),
962            JoinConstraint::Using { columns, alias } => {
963                let mut doc = bracket(
964                    "USING(",
965                    comma_separate(|c| self.doc_display_pass(c), columns),
966                    ")",
967                );
968                if let Some(alias) = alias {
969                    doc = nest(doc, nest_title("AS", self.doc_display_pass(alias)));
970                }
971                doc
972            }
973            _ => return self.doc_display(v, "join constraint"),
974        };
975        intersperse_line_nest([
976            RcDoc::text(name),
977            self.doc_table_factor(&v.relation),
978            constraint,
979        ])
980    }
981
982    fn doc_table_factor<'a, T: AstInfo>(&'a self, v: &'a TableFactor<T>) -> RcDoc<'a> {
983        match v {
984            TableFactor::Derived {
985                lateral,
986                subquery,
987                alias,
988            } => {
989                let prefix = if *lateral { "LATERAL (" } else { "(" };
990                let mut docs = vec![bracket(prefix, self.doc_query(subquery), ")")];
991                if let Some(alias) = alias {
992                    docs.push(RcDoc::text(format!("AS {}", alias)));
993                }
994                intersperse_line_nest(docs)
995            }
996            TableFactor::NestedJoin { join, alias } => {
997                let mut doc = bracket("(", self.doc_table_with_joins(join), ")");
998                if let Some(alias) = alias {
999                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1000                }
1001                doc
1002            }
1003            TableFactor::Table { name, alias } => {
1004                let mut doc = self.doc_display_pass(name);
1005                if let Some(alias) = alias {
1006                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1007                }
1008                doc
1009            }
1010            _ => self.doc_display(v, "table factor variant"),
1011        }
1012    }
1013
1014    fn doc_distinct<'a, T: AstInfo>(&'a self, v: &'a Distinct<T>) -> RcDoc<'a> {
1015        match v {
1016            Distinct::EntireRow => RcDoc::text("DISTINCT"),
1017            Distinct::On(cols) => bracket(
1018                "DISTINCT ON (",
1019                comma_separate(|c| self.doc_expr(c), cols),
1020                ")",
1021            ),
1022        }
1023    }
1024
1025    fn doc_select<'a, T: AstInfo>(&'a self, v: &'a Select<T>) -> RcDoc<'a> {
1026        let mut docs = vec![];
1027        let mut select = RcDoc::text("SELECT");
1028        if let Some(distinct) = &v.distinct {
1029            select = nest(select, self.doc_distinct(distinct));
1030        }
1031        docs.push(nest_comma_separate(
1032            select,
1033            |s| self.doc_select_item(s),
1034            &v.projection,
1035        ));
1036        if !v.from.is_empty() {
1037            docs.push(title_comma_separate(
1038                "FROM",
1039                |t| self.doc_table_with_joins(t),
1040                &v.from,
1041            ));
1042        }
1043        if let Some(selection) = &v.selection {
1044            docs.push(nest_title("WHERE", self.doc_expr(selection)));
1045        }
1046        if !v.group_by.is_empty() {
1047            docs.push(title_comma_separate(
1048                "GROUP BY",
1049                |e| self.doc_expr(e),
1050                &v.group_by,
1051            ));
1052        }
1053        if let Some(having) = &v.having {
1054            docs.push(nest_title("HAVING", self.doc_expr(having)));
1055        }
1056        if let Some(qualify) = &v.qualify {
1057            docs.push(nest_title("QUALIFY", self.doc_expr(qualify)));
1058        }
1059        if !v.options.is_empty() {
1060            docs.push(bracket(
1061                "OPTIONS (",
1062                comma_separate(|o| self.doc_display_pass(o), &v.options),
1063                ")",
1064            ));
1065        }
1066        RcDoc::intersperse(docs, Doc::line()).group()
1067    }
1068
1069    fn doc_select_item<'a, T: AstInfo>(&'a self, v: &'a SelectItem<T>) -> RcDoc<'a> {
1070        match v {
1071            SelectItem::Expr { expr, alias } => {
1072                let mut doc = self.doc_expr(expr);
1073                if let Some(alias) = alias {
1074                    doc = nest(
1075                        doc,
1076                        RcDoc::concat([RcDoc::text("AS "), self.doc_display_pass(alias)]),
1077                    );
1078                }
1079                doc
1080            }
1081            SelectItem::Wildcard => self.doc_display_pass(v),
1082        }
1083    }
1084
1085    pub fn doc_expr<'a, T: AstInfo>(&'a self, v: &'a Expr<T>) -> RcDoc<'a> {
1086        match v {
1087            Expr::Op { op, expr1, expr2 } => {
1088                if let Some(expr2) = expr2 {
1089                    RcDoc::concat([
1090                        self.doc_expr(expr1),
1091                        RcDoc::line(),
1092                        RcDoc::text(format!("{} ", op)),
1093                        self.doc_expr(expr2).nest(TAB),
1094                    ])
1095                } else {
1096                    RcDoc::concat([RcDoc::text(format!("{} ", op)), self.doc_expr(expr1)])
1097                }
1098            }
1099            Expr::Case {
1100                operand,
1101                conditions,
1102                results,
1103                else_result,
1104            } => {
1105                let mut docs = Vec::new();
1106                if let Some(operand) = operand {
1107                    docs.push(self.doc_expr(operand));
1108                }
1109                for (c, r) in conditions.iter().zip_eq(results) {
1110                    let when = nest_title("WHEN", self.doc_expr(c));
1111                    let then = nest_title("THEN", self.doc_expr(r));
1112                    docs.push(nest(when, then));
1113                }
1114                if let Some(else_result) = else_result {
1115                    docs.push(nest_title("ELSE", self.doc_expr(else_result)));
1116                }
1117                let doc = intersperse_line_nest(docs);
1118                bracket_doc(RcDoc::text("CASE"), doc, RcDoc::text("END"), RcDoc::line())
1119            }
1120            Expr::Cast { expr, data_type } => {
1121                let doc = self.doc_expr(expr);
1122                RcDoc::concat([
1123                    doc,
1124                    RcDoc::text(format!("::{}", data_type.to_ast_string_simple())),
1125                ])
1126            }
1127            Expr::Nested(ast) => bracket("(", self.doc_expr(ast), ")"),
1128            Expr::Function(fun) => self.doc_function(fun),
1129            Expr::Subquery(ast) => bracket("(", self.doc_query(ast), ")"),
1130            Expr::Identifier(_)
1131            | Expr::Value(_)
1132            | Expr::QualifiedWildcard(_)
1133            | Expr::WildcardAccess(_)
1134            | Expr::FieldAccess { .. } => self.doc_display_pass(v),
1135            Expr::And { left, right } => bracket_doc(
1136                self.doc_expr(left),
1137                RcDoc::text("AND"),
1138                self.doc_expr(right),
1139                RcDoc::line(),
1140            ),
1141            Expr::Or { left, right } => bracket_doc(
1142                self.doc_expr(left),
1143                RcDoc::text("OR"),
1144                self.doc_expr(right),
1145                RcDoc::line(),
1146            ),
1147            Expr::Exists(s) => bracket("EXISTS (", self.doc_query(s), ")"),
1148            Expr::IsExpr {
1149                expr,
1150                negated,
1151                construct,
1152            } => bracket_doc(
1153                self.doc_expr(expr),
1154                RcDoc::text(if *negated { "IS NOT" } else { "IS" }),
1155                self.doc_display_pass(construct),
1156                RcDoc::line(),
1157            ),
1158            Expr::Not { expr } => {
1159                RcDoc::concat([RcDoc::text("NOT"), RcDoc::line(), self.doc_expr(expr)])
1160            }
1161            Expr::Between {
1162                expr,
1163                negated,
1164                low,
1165                high,
1166            } => RcDoc::intersperse(
1167                [
1168                    self.doc_expr(expr),
1169                    RcDoc::text(if *negated { "NOT BETWEEN" } else { "BETWEEN" }),
1170                    RcDoc::intersperse(
1171                        [self.doc_expr(low), RcDoc::text("AND"), self.doc_expr(high)],
1172                        RcDoc::line(),
1173                    )
1174                    .group(),
1175                ],
1176                RcDoc::line(),
1177            ),
1178            Expr::InSubquery {
1179                expr,
1180                subquery,
1181                negated,
1182            } => RcDoc::intersperse(
1183                [
1184                    self.doc_expr(expr),
1185                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1186                    self.doc_query(subquery),
1187                    RcDoc::text(")"),
1188                ],
1189                RcDoc::line(),
1190            ),
1191            Expr::InList {
1192                expr,
1193                list,
1194                negated,
1195            } => RcDoc::intersperse(
1196                [
1197                    self.doc_expr(expr),
1198                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1199                    comma_separate(|e| self.doc_expr(e), list),
1200                    RcDoc::text(")"),
1201                ],
1202                RcDoc::line(),
1203            ),
1204            Expr::Row { exprs } => {
1205                bracket("ROW(", comma_separate(|e| self.doc_expr(e), exprs), ")")
1206            }
1207            Expr::NullIf { l_expr, r_expr } => bracket(
1208                "NULLIF (",
1209                comma_separate(|e| self.doc_expr(e), [&**l_expr, &**r_expr]),
1210                ")",
1211            ),
1212            Expr::HomogenizingFunction { function, exprs } => bracket(
1213                format!("{function}("),
1214                comma_separate(|e| self.doc_expr(e), exprs),
1215                ")",
1216            ),
1217            Expr::ArraySubquery(s) => bracket("ARRAY(", self.doc_query(s), ")"),
1218            Expr::ListSubquery(s) => bracket("LIST(", self.doc_query(s), ")"),
1219            Expr::Array(exprs) => {
1220                bracket("ARRAY[", comma_separate(|e| self.doc_expr(e), exprs), "]")
1221            }
1222            Expr::List(exprs) => bracket("LIST[", comma_separate(|e| self.doc_expr(e), exprs), "]"),
1223            _ => self.doc_display(v, "expr variant"),
1224        }
1225        .group()
1226    }
1227
1228    fn doc_function<'a, T: AstInfo>(&'a self, v: &'a Function<T>) -> RcDoc<'a> {
1229        match &v.args {
1230            FunctionArgs::Star => self.doc_display_pass(v),
1231            FunctionArgs::Args { args, order_by } => {
1232                if args.is_empty() {
1233                    // Nullary, don't allow newline between parens, so just delegate.
1234                    return self.doc_display_pass(v);
1235                }
1236                if v.filter.is_some() || v.over.is_some() || !order_by.is_empty() {
1237                    return self.doc_display(v, "function filter or over or order by");
1238                }
1239                let special = match v.name.to_ast_string_stable().as_str() {
1240                    r#""extract""# if v.args.len() == Some(2) => true,
1241                    r#""position""# if v.args.len() == Some(2) => true,
1242                    _ => false,
1243                };
1244                if special {
1245                    return self.doc_display(v, "special function");
1246                }
1247                let name = format!(
1248                    "{}({}",
1249                    v.name.to_ast_string_simple(),
1250                    if v.distinct { "DISTINCT " } else { "" }
1251                );
1252                bracket(name, comma_separate(|e| self.doc_expr(e), args), ")")
1253            }
1254        }
1255    }
1256}