Skip to main content

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 let Some(mode) = &v.mode {
381            docs.push(nest_title("MODE", self.doc_display_pass(mode)));
382        }
383
384        if !v.with_options.is_empty() {
385            docs.push(bracket(
386                "WITH (",
387                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
388                ")",
389            ));
390        }
391
392        RcDoc::intersperse(docs, Doc::line()).group()
393    }
394
395    pub(crate) fn doc_create_subsource<'a, T: AstInfo>(
396        &'a self,
397        v: &'a CreateSubsourceStatement<T>,
398    ) -> RcDoc<'a> {
399        let mut docs = Vec::new();
400
401        // CREATE SUBSOURCE [IF NOT EXISTS] name
402        let mut title = "CREATE SUBSOURCE".to_string();
403        if v.if_not_exists {
404            title.push_str(" IF NOT EXISTS");
405        }
406
407        // Table name with columns/constraints
408        let mut col_items = Vec::new();
409        col_items.extend(v.columns.iter().map(|c| self.doc_display_pass(c)));
410        col_items.extend(v.constraints.iter().map(|c| self.doc_display_pass(c)));
411
412        let table_def = nest(
413            self.doc_display_pass(&v.name),
414            bracket("(", comma_separated(col_items), ")"),
415        );
416        docs.push(nest_title(title, table_def));
417
418        // OF SOURCE
419        if let Some(of_source) = &v.of_source {
420            docs.push(nest_title("OF SOURCE", self.doc_display_pass(of_source)));
421        }
422
423        // WITH options
424        if !v.with_options.is_empty() {
425            docs.push(bracket(
426                "WITH (",
427                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
428                ")",
429            ));
430        }
431
432        RcDoc::intersperse(docs, Doc::line()).group()
433    }
434
435    pub(crate) fn doc_create_cluster<'a, T: AstInfo>(
436        &'a self,
437        v: &'a CreateClusterStatement<T>,
438    ) -> RcDoc<'a> {
439        let mut docs = Vec::new();
440
441        // CREATE CLUSTER name
442        docs.push(nest_title("CREATE CLUSTER", self.doc_display_pass(&v.name)));
443
444        // OPTIONS (...)
445        if !v.options.is_empty() {
446            docs.push(bracket(
447                "(",
448                comma_separate(|o| self.doc_display_pass(o), &v.options),
449                ")",
450            ));
451        }
452
453        // FEATURES (...)
454        if !v.features.is_empty() {
455            docs.push(bracket(
456                "FEATURES (",
457                comma_separate(|f| self.doc_display_pass(f), &v.features),
458                ")",
459            ));
460        }
461
462        RcDoc::intersperse(docs, Doc::line()).group()
463    }
464
465    pub(crate) fn doc_create_cluster_replica<'a, T: AstInfo>(
466        &'a self,
467        v: &'a CreateClusterReplicaStatement<T>,
468    ) -> RcDoc<'a> {
469        let mut docs = Vec::new();
470
471        // CREATE CLUSTER REPLICA cluster.replica
472        let replica_name = RcDoc::concat([
473            self.doc_display_pass(&v.of_cluster),
474            RcDoc::text("."),
475            self.doc_display_pass(&v.definition.name),
476        ]);
477        docs.push(nest_title("CREATE CLUSTER REPLICA", replica_name));
478
479        // OPTIONS (...)
480        docs.push(bracket(
481            "(",
482            comma_separate(|o| self.doc_display_pass(o), &v.definition.options),
483            ")",
484        ));
485
486        RcDoc::intersperse(docs, Doc::line()).group()
487    }
488
489    pub(crate) fn doc_create_network_policy<'a, T: AstInfo>(
490        &'a self,
491        v: &'a CreateNetworkPolicyStatement<T>,
492    ) -> RcDoc<'a> {
493        let docs = vec![
494            // CREATE NETWORK POLICY name
495            nest_title("CREATE NETWORK POLICY", self.doc_display_pass(&v.name)),
496            // OPTIONS (...)
497            bracket(
498                "(",
499                comma_separate(|o| self.doc_display_pass(o), &v.options),
500                ")",
501            ),
502        ];
503
504        RcDoc::intersperse(docs, Doc::line()).group()
505    }
506
507    pub(crate) fn doc_create_index<'a, T: AstInfo>(
508        &'a self,
509        v: &'a CreateIndexStatement<T>,
510    ) -> RcDoc<'a> {
511        let mut docs = Vec::new();
512
513        // CREATE [DEFAULT] INDEX [IF NOT EXISTS] [name]
514        let mut title = "CREATE".to_string();
515        if v.key_parts.is_none() {
516            title.push_str(" DEFAULT");
517        }
518        title.push_str(" INDEX");
519        if v.if_not_exists {
520            title.push_str(" IF NOT EXISTS");
521        }
522
523        if let Some(name) = &v.name {
524            docs.push(nest_title(title, self.doc_display_pass(name)));
525        } else {
526            docs.push(RcDoc::text(title));
527        }
528
529        // IN CLUSTER
530        if let Some(cluster) = &v.in_cluster {
531            docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
532        }
533
534        // ON table_name [(key_parts)]
535        let on_clause = if let Some(key_parts) = &v.key_parts {
536            nest(
537                self.doc_display_pass(&v.on_name),
538                bracket(
539                    "(",
540                    comma_separate(|k| self.doc_display_pass(k), key_parts),
541                    ")",
542                ),
543            )
544        } else {
545            self.doc_display_pass(&v.on_name)
546        };
547        docs.push(nest_title("ON", on_clause));
548
549        // WITH options
550        if !v.with_options.is_empty() {
551            docs.push(bracket(
552                "WITH (",
553                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
554                ")",
555            ));
556        }
557
558        RcDoc::intersperse(docs, Doc::line()).group()
559    }
560
561    fn doc_format_specifier<T: AstInfo>(&self, v: &FormatSpecifier<T>) -> RcDoc<'_> {
562        match v {
563            FormatSpecifier::Bare(format) => nest_title("FORMAT", self.doc_display_pass(format)),
564            FormatSpecifier::KeyValue { key, value } => {
565                let docs = vec![
566                    nest_title("KEY FORMAT", self.doc_display_pass(key)),
567                    nest_title("VALUE FORMAT", self.doc_display_pass(value)),
568                ];
569                RcDoc::intersperse(docs, Doc::line()).group()
570            }
571        }
572    }
573
574    fn doc_external_references<'a>(&'a self, v: &'a ExternalReferences) -> RcDoc<'a> {
575        match v {
576            ExternalReferences::SubsetTables(subsources) => bracket(
577                "FOR TABLES (",
578                comma_separate(|s| self.doc_display_pass(s), subsources),
579                ")",
580            ),
581            ExternalReferences::SubsetSchemas(schemas) => bracket(
582                "FOR SCHEMAS (",
583                comma_separate(|s| self.doc_display_pass(s), schemas),
584                ")",
585            ),
586            ExternalReferences::All => RcDoc::text("FOR ALL TABLES"),
587        }
588    }
589
590    pub(crate) fn doc_copy<'a, T: AstInfo>(&'a self, v: &'a CopyStatement<T>) -> RcDoc<'a> {
591        let relation = match &v.relation {
592            CopyRelation::Named { name, columns } => {
593                let mut relation = self.doc_display_pass(name);
594                if !columns.is_empty() {
595                    relation = bracket_doc(
596                        nest(relation, RcDoc::text("(")),
597                        comma_separate(|c| self.doc_display_pass(c), columns),
598                        RcDoc::text(")"),
599                        RcDoc::line_(),
600                    );
601                }
602                RcDoc::concat([RcDoc::text("COPY "), relation])
603            }
604            CopyRelation::Select(query) => bracket("COPY (", self.doc_select_statement(query), ")"),
605            CopyRelation::Subscribe(query) => bracket("COPY (", self.doc_subscribe(query), ")"),
606        };
607        let mut docs = vec![
608            relation,
609            RcDoc::concat([
610                self.doc_display_pass(&v.direction),
611                RcDoc::text(" "),
612                self.doc_display_pass(&v.target),
613            ]),
614        ];
615        if !v.options.is_empty() {
616            docs.push(bracket(
617                "WITH (",
618                comma_separate(|o| self.doc_display_pass(o), &v.options),
619                ")",
620            ));
621        }
622        RcDoc::intersperse(docs, Doc::line()).group()
623    }
624
625    pub(crate) fn doc_subscribe<'a, T: AstInfo>(
626        &'a self,
627        v: &'a SubscribeStatement<T>,
628    ) -> RcDoc<'a> {
629        let doc = match &v.relation {
630            SubscribeRelation::Name(name) => nest_title("SUBSCRIBE", self.doc_display_pass(name)),
631            SubscribeRelation::Query(query) => bracket("SUBSCRIBE (", self.doc_query(query), ")"),
632        };
633        let mut docs = vec![doc];
634        if !v.options.is_empty() {
635            docs.push(bracket(
636                "WITH (",
637                comma_separate(|o| self.doc_display_pass(o), &v.options),
638                ")",
639            ));
640        }
641        if let Some(as_of) = &v.as_of {
642            docs.push(self.doc_as_of(as_of));
643        }
644        if let Some(up_to) = &v.up_to {
645            docs.push(nest_title("UP TO", self.doc_expr(up_to)));
646        }
647        match &v.output {
648            SubscribeOutput::Diffs => {}
649            SubscribeOutput::WithinTimestampOrderBy { order_by } => {
650                docs.push(nest_title(
651                    "WITHIN TIMESTAMP ORDER BY ",
652                    comma_separate(|o| self.doc_order_by_expr(o), order_by),
653                ));
654            }
655            SubscribeOutput::EnvelopeUpsert { key_columns } => {
656                docs.push(bracket(
657                    "ENVELOPE UPSERT (KEY (",
658                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
659                    "))",
660                ));
661            }
662            SubscribeOutput::EnvelopeDebezium { key_columns } => {
663                docs.push(bracket(
664                    "ENVELOPE DEBEZIUM (KEY (",
665                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
666                    "))",
667                ));
668            }
669        }
670        RcDoc::intersperse(docs, Doc::line()).group()
671    }
672
673    fn doc_as_of<'a, T: AstInfo>(&'a self, v: &'a AsOf<T>) -> RcDoc<'a> {
674        let (title, expr) = match v {
675            AsOf::At(expr) => ("AS OF", expr),
676            AsOf::AtLeast(expr) => ("AS OF AT LEAST", expr),
677        };
678        nest_title(title, self.doc_expr(expr))
679    }
680
681    pub(crate) fn doc_create_view<'a, T: AstInfo>(
682        &'a self,
683        v: &'a CreateViewStatement<T>,
684    ) -> RcDoc<'a> {
685        let mut docs = vec![];
686        docs.push(RcDoc::text(format!(
687            "CREATE{}{} VIEW{}",
688            if v.if_exists == IfExistsBehavior::Replace {
689                " OR REPLACE"
690            } else {
691                ""
692            },
693            if v.temporary { " TEMPORARY" } else { "" },
694            if v.if_exists == IfExistsBehavior::Skip {
695                " IF NOT EXISTS"
696            } else {
697                ""
698            },
699        )));
700        docs.push(self.doc_view_definition(&v.definition));
701        intersperse_line_nest(docs)
702    }
703
704    pub(crate) fn doc_create_materialized_view<'a, T: AstInfo>(
705        &'a self,
706        v: &'a CreateMaterializedViewStatement<T>,
707    ) -> RcDoc<'a> {
708        let mut docs = vec![];
709        docs.push(RcDoc::text(format!(
710            "CREATE{}{} MATERIALIZED VIEW{} {}",
711            if v.if_exists == IfExistsBehavior::Replace {
712                " OR REPLACE"
713            } else {
714                ""
715            },
716            if v.replacement_for.is_some() {
717                " REPLACEMENT"
718            } else {
719                ""
720            },
721            if v.if_exists == IfExistsBehavior::Skip {
722                " IF NOT EXISTS"
723            } else {
724                ""
725            },
726            v.name,
727        )));
728        if !v.columns.is_empty() {
729            docs.push(bracket(
730                "(",
731                comma_separate(|c| self.doc_display_pass(c), &v.columns),
732                ")",
733            ));
734        }
735        if let Some(target) = &v.replacement_for {
736            docs.push(RcDoc::text(format!(
737                "FOR {}",
738                target.to_ast_string_simple()
739            )));
740        }
741        match (&v.in_cluster, &v.in_cluster_replica) {
742            (Some(cluster), Some(replica)) => {
743                docs.push(RcDoc::text(format!(
744                    "IN CLUSTER {} REPLICA {}",
745                    cluster.to_ast_string_simple(),
746                    replica.to_ast_string_simple(),
747                )));
748            }
749            (Some(cluster), None) => {
750                docs.push(RcDoc::text(format!(
751                    "IN CLUSTER {}",
752                    cluster.to_ast_string_simple(),
753                )));
754            }
755            (None, Some(replica)) => {
756                docs.push(RcDoc::text(format!(
757                    "IN REPLICA {}",
758                    replica.to_ast_string_simple(),
759                )));
760            }
761            (None, None) => {}
762        }
763        if !v.with_options.is_empty() {
764            docs.push(bracket(
765                "WITH (",
766                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
767                ")",
768            ));
769        }
770        docs.push(nest_title("AS", self.doc_query(&v.query)));
771        intersperse_line_nest(docs)
772    }
773
774    fn doc_view_definition<'a, T: AstInfo>(&'a self, v: &'a ViewDefinition<T>) -> RcDoc<'a> {
775        let mut docs = vec![RcDoc::text(v.name.to_string())];
776        if !v.columns.is_empty() {
777            docs.push(bracket(
778                "(",
779                comma_separate(|c| self.doc_display_pass(c), &v.columns),
780                ")",
781            ));
782        }
783        docs.push(nest_title("AS", self.doc_query(&v.query)));
784        RcDoc::intersperse(docs, Doc::line()).group()
785    }
786
787    pub(crate) fn doc_insert<'a, T: AstInfo>(&'a self, v: &'a InsertStatement<T>) -> RcDoc<'a> {
788        let mut first = vec![RcDoc::text(format!(
789            "INSERT INTO {}",
790            v.table_name.to_ast_string_simple()
791        ))];
792        if !v.columns.is_empty() {
793            first.push(bracket(
794                "(",
795                comma_separate(|c| self.doc_display_pass(c), &v.columns),
796                ")",
797            ));
798        }
799        let sources = match &v.source {
800            InsertSource::Query(query) => self.doc_query(query),
801            _ => self.doc_display(&v.source, "insert source"),
802        };
803        let mut doc = intersperse_line_nest([intersperse_line_nest(first), sources]);
804        if !v.returning.is_empty() {
805            doc = nest(
806                doc,
807                nest_title(
808                    "RETURNING",
809                    comma_separate(|r| self.doc_display_pass(r), &v.returning),
810                ),
811            )
812        }
813        doc
814    }
815
816    pub(crate) fn doc_select_statement<'a, T: AstInfo>(
817        &'a self,
818        v: &'a SelectStatement<T>,
819    ) -> RcDoc<'a> {
820        let mut doc = self.doc_query(&v.query);
821        if let Some(as_of) = &v.as_of {
822            doc = intersperse_line_nest([doc, self.doc_as_of(as_of)]);
823        }
824        doc.group()
825    }
826
827    fn doc_order_by<'a, T: AstInfo>(&'a self, v: &'a [OrderByExpr<T>]) -> RcDoc<'a> {
828        title_comma_separate("ORDER BY", |o| self.doc_order_by_expr(o), v)
829    }
830
831    fn doc_order_by_expr<'a, T: AstInfo>(&'a self, v: &'a OrderByExpr<T>) -> RcDoc<'a> {
832        let doc = self.doc_expr(&v.expr);
833        let doc = match v.asc {
834            Some(true) => nest(doc, RcDoc::text("ASC")),
835            Some(false) => nest(doc, RcDoc::text("DESC")),
836            None => doc,
837        };
838        match v.nulls_last {
839            Some(true) => nest(doc, RcDoc::text("NULLS LAST")),
840            Some(false) => nest(doc, RcDoc::text("NULLS FIRST")),
841            None => doc,
842        }
843    }
844
845    fn doc_query<'a, T: AstInfo>(&'a self, v: &'a Query<T>) -> RcDoc<'a> {
846        let mut docs = vec![];
847        if !v.ctes.is_empty() {
848            match &v.ctes {
849                CteBlock::Simple(ctes) => {
850                    docs.push(title_comma_separate("WITH", |cte| self.doc_cte(cte), ctes))
851                }
852                CteBlock::MutuallyRecursive(mutrec) => {
853                    let mut doc = RcDoc::text("WITH MUTUALLY RECURSIVE");
854                    if !mutrec.options.is_empty() {
855                        doc = nest(
856                            doc,
857                            bracket(
858                                "(",
859                                comma_separate(|o| self.doc_display_pass(o), &mutrec.options),
860                                ")",
861                            ),
862                        );
863                    }
864                    docs.push(nest(
865                        doc,
866                        comma_separate(|c| self.doc_mutually_recursive(c), &mutrec.ctes),
867                    ));
868                }
869            }
870        }
871        docs.push(self.doc_set_expr(&v.body));
872        if !v.order_by.is_empty() {
873            docs.push(self.doc_order_by(&v.order_by));
874        }
875
876        let offset = if let Some(offset) = &v.offset {
877            vec![RcDoc::concat([nest_title("OFFSET", self.doc_expr(offset))])]
878        } else {
879            vec![]
880        };
881
882        if let Some(limit) = &v.limit {
883            if limit.with_ties {
884                docs.extend(offset);
885                docs.push(RcDoc::concat([
886                    RcDoc::text("FETCH FIRST "),
887                    self.doc_expr(&limit.quantity),
888                    RcDoc::text(" ROWS WITH TIES"),
889                ]));
890            } else {
891                docs.push(nest_title("LIMIT", self.doc_expr(&limit.quantity)));
892                docs.extend(offset);
893            }
894        } else {
895            docs.extend(offset);
896        }
897
898        RcDoc::intersperse(docs, Doc::line()).group()
899    }
900
901    fn doc_cte<'a, T: AstInfo>(&'a self, v: &'a Cte<T>) -> RcDoc<'a> {
902        RcDoc::concat([
903            RcDoc::text(format!("{} AS", v.alias)),
904            RcDoc::line(),
905            bracket("(", self.doc_query(&v.query), ")"),
906        ])
907    }
908
909    fn doc_mutually_recursive<'a, T: AstInfo>(&'a self, v: &'a CteMutRec<T>) -> RcDoc<'a> {
910        let mut docs = Vec::new();
911        if !v.columns.is_empty() {
912            docs.push(bracket(
913                "(",
914                comma_separate(|c| self.doc_display_pass(c), &v.columns),
915                ")",
916            ));
917        }
918        docs.push(bracket("AS (", self.doc_query(&v.query), ")"));
919        nest(
920            self.doc_display_pass(&v.name),
921            RcDoc::intersperse(docs, Doc::line()).group(),
922        )
923    }
924
925    fn doc_set_expr<'a, T: AstInfo>(&'a self, v: &'a SetExpr<T>) -> RcDoc<'a> {
926        match v {
927            SetExpr::Select(v) => self.doc_select(v),
928            SetExpr::Query(v) => bracket("(", self.doc_query(v), ")"),
929            SetExpr::SetOperation {
930                op,
931                all,
932                left,
933                right,
934            } => {
935                let all_str = if *all { " ALL" } else { "" };
936                RcDoc::concat([
937                    self.doc_set_expr(left),
938                    RcDoc::line(),
939                    RcDoc::concat([
940                        RcDoc::text(format!("{}{}", op, all_str)),
941                        RcDoc::line(),
942                        self.doc_set_expr(right),
943                    ])
944                    .nest(TAB)
945                    .group(),
946                ])
947            }
948            SetExpr::Values(v) => self.doc_values(v),
949            SetExpr::Show(v) => self.doc_display(v, "SHOW"),
950            SetExpr::Table(v) => nest(RcDoc::text("TABLE"), self.doc_display_pass(v)),
951        }
952        .group()
953    }
954
955    fn doc_values<'a, T: AstInfo>(&'a self, v: &'a Values<T>) -> RcDoc<'a> {
956        let rows =
957            v.0.iter()
958                .map(|row| bracket("(", comma_separate(|v| self.doc_expr(v), row), ")"));
959        RcDoc::concat([RcDoc::text("VALUES"), RcDoc::line(), comma_separated(rows)])
960            .nest(TAB)
961            .group()
962    }
963
964    fn doc_table_with_joins<'a, T: AstInfo>(&'a self, v: &'a TableWithJoins<T>) -> RcDoc<'a> {
965        let mut docs = vec![self.doc_table_factor(&v.relation)];
966        for j in &v.joins {
967            docs.push(self.doc_join(j));
968        }
969        intersperse_line_nest(docs)
970    }
971
972    fn doc_join<'a, T: AstInfo>(&'a self, v: &'a Join<T>) -> RcDoc<'a> {
973        let (constraint, name) = match &v.join_operator {
974            JoinOperator::Inner(constraint) => (constraint, "JOIN"),
975            JoinOperator::FullOuter(constraint) => (constraint, "FULL JOIN"),
976            JoinOperator::LeftOuter(constraint) => (constraint, "LEFT JOIN"),
977            JoinOperator::RightOuter(constraint) => (constraint, "RIGHT JOIN"),
978            _ => return self.doc_display(v, "join operator"),
979        };
980        let constraint = match constraint {
981            JoinConstraint::On(expr) => nest_title("ON", self.doc_expr(expr)),
982            JoinConstraint::Using { columns, alias } => {
983                let mut doc = bracket(
984                    "USING(",
985                    comma_separate(|c| self.doc_display_pass(c), columns),
986                    ")",
987                );
988                if let Some(alias) = alias {
989                    doc = nest(doc, nest_title("AS", self.doc_display_pass(alias)));
990                }
991                doc
992            }
993            _ => return self.doc_display(v, "join constraint"),
994        };
995        intersperse_line_nest([
996            RcDoc::text(name),
997            self.doc_table_factor(&v.relation),
998            constraint,
999        ])
1000    }
1001
1002    fn doc_table_factor<'a, T: AstInfo>(&'a self, v: &'a TableFactor<T>) -> RcDoc<'a> {
1003        match v {
1004            TableFactor::Derived {
1005                lateral,
1006                subquery,
1007                alias,
1008            } => {
1009                let prefix = if *lateral { "LATERAL (" } else { "(" };
1010                let mut docs = vec![bracket(prefix, self.doc_query(subquery), ")")];
1011                if let Some(alias) = alias {
1012                    docs.push(RcDoc::text(format!("AS {}", alias)));
1013                }
1014                intersperse_line_nest(docs)
1015            }
1016            TableFactor::NestedJoin { join, alias } => {
1017                let mut doc = bracket("(", self.doc_table_with_joins(join), ")");
1018                if let Some(alias) = alias {
1019                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1020                }
1021                doc
1022            }
1023            TableFactor::Table { name, alias } => {
1024                let mut doc = self.doc_display_pass(name);
1025                if let Some(alias) = alias {
1026                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1027                }
1028                doc
1029            }
1030            _ => self.doc_display(v, "table factor variant"),
1031        }
1032    }
1033
1034    fn doc_distinct<'a, T: AstInfo>(&'a self, v: &'a Distinct<T>) -> RcDoc<'a> {
1035        match v {
1036            Distinct::EntireRow => RcDoc::text("DISTINCT"),
1037            Distinct::On(cols) => bracket(
1038                "DISTINCT ON (",
1039                comma_separate(|c| self.doc_expr(c), cols),
1040                ")",
1041            ),
1042        }
1043    }
1044
1045    fn doc_select<'a, T: AstInfo>(&'a self, v: &'a Select<T>) -> RcDoc<'a> {
1046        let mut docs = vec![];
1047        let mut select = RcDoc::text("SELECT");
1048        if let Some(distinct) = &v.distinct {
1049            select = nest(select, self.doc_distinct(distinct));
1050        }
1051        docs.push(nest_comma_separate(
1052            select,
1053            |s| self.doc_select_item(s),
1054            &v.projection,
1055        ));
1056        if !v.from.is_empty() {
1057            docs.push(title_comma_separate(
1058                "FROM",
1059                |t| self.doc_table_with_joins(t),
1060                &v.from,
1061            ));
1062        }
1063        if let Some(selection) = &v.selection {
1064            docs.push(nest_title("WHERE", self.doc_expr(selection)));
1065        }
1066        if !v.group_by.is_empty() {
1067            docs.push(title_comma_separate(
1068                "GROUP BY",
1069                |e| self.doc_expr(e),
1070                &v.group_by,
1071            ));
1072        }
1073        if let Some(having) = &v.having {
1074            docs.push(nest_title("HAVING", self.doc_expr(having)));
1075        }
1076        if let Some(qualify) = &v.qualify {
1077            docs.push(nest_title("QUALIFY", self.doc_expr(qualify)));
1078        }
1079        if !v.options.is_empty() {
1080            docs.push(bracket(
1081                "OPTIONS (",
1082                comma_separate(|o| self.doc_display_pass(o), &v.options),
1083                ")",
1084            ));
1085        }
1086        RcDoc::intersperse(docs, Doc::line()).group()
1087    }
1088
1089    fn doc_select_item<'a, T: AstInfo>(&'a self, v: &'a SelectItem<T>) -> RcDoc<'a> {
1090        match v {
1091            SelectItem::Expr { expr, alias } => {
1092                let mut doc = self.doc_expr(expr);
1093                if let Some(alias) = alias {
1094                    doc = nest(
1095                        doc,
1096                        RcDoc::concat([RcDoc::text("AS "), self.doc_display_pass(alias)]),
1097                    );
1098                }
1099                doc
1100            }
1101            SelectItem::Wildcard => self.doc_display_pass(v),
1102        }
1103    }
1104
1105    pub fn doc_expr<'a, T: AstInfo>(&'a self, v: &'a Expr<T>) -> RcDoc<'a> {
1106        match v {
1107            Expr::Op { op, expr1, expr2 } => {
1108                if let Some(expr2) = expr2 {
1109                    RcDoc::concat([
1110                        self.doc_expr(expr1),
1111                        RcDoc::line(),
1112                        RcDoc::text(format!("{} ", op)),
1113                        self.doc_expr(expr2).nest(TAB),
1114                    ])
1115                } else {
1116                    RcDoc::concat([RcDoc::text(format!("{} ", op)), self.doc_expr(expr1)])
1117                }
1118            }
1119            Expr::Case {
1120                operand,
1121                conditions,
1122                results,
1123                else_result,
1124            } => {
1125                let mut docs = Vec::new();
1126                if let Some(operand) = operand {
1127                    docs.push(self.doc_expr(operand));
1128                }
1129                for (c, r) in conditions.iter().zip_eq(results) {
1130                    let when = nest_title("WHEN", self.doc_expr(c));
1131                    let then = nest_title("THEN", self.doc_expr(r));
1132                    docs.push(nest(when, then));
1133                }
1134                if let Some(else_result) = else_result {
1135                    docs.push(nest_title("ELSE", self.doc_expr(else_result)));
1136                }
1137                let doc = intersperse_line_nest(docs);
1138                bracket_doc(RcDoc::text("CASE"), doc, RcDoc::text("END"), RcDoc::line())
1139            }
1140            Expr::Cast { expr, data_type } => {
1141                let doc = self.doc_expr(expr);
1142                RcDoc::concat([
1143                    doc,
1144                    RcDoc::text(format!("::{}", data_type.to_ast_string_simple())),
1145                ])
1146            }
1147            Expr::Nested(ast) => bracket("(", self.doc_expr(ast), ")"),
1148            Expr::Function(fun) => self.doc_function(fun),
1149            Expr::Subquery(ast) => bracket("(", self.doc_query(ast), ")"),
1150            Expr::Identifier(_)
1151            | Expr::Value(_)
1152            | Expr::QualifiedWildcard(_)
1153            | Expr::WildcardAccess(_)
1154            | Expr::FieldAccess { .. } => self.doc_display_pass(v),
1155            Expr::And { left, right } => bracket_doc(
1156                self.doc_expr(left),
1157                RcDoc::text("AND"),
1158                self.doc_expr(right),
1159                RcDoc::line(),
1160            ),
1161            Expr::Or { left, right } => bracket_doc(
1162                self.doc_expr(left),
1163                RcDoc::text("OR"),
1164                self.doc_expr(right),
1165                RcDoc::line(),
1166            ),
1167            Expr::Exists(s) => bracket("EXISTS (", self.doc_query(s), ")"),
1168            Expr::IsExpr {
1169                expr,
1170                negated,
1171                construct,
1172            } => bracket_doc(
1173                self.doc_expr(expr),
1174                RcDoc::text(if *negated { "IS NOT" } else { "IS" }),
1175                self.doc_display_pass(construct),
1176                RcDoc::line(),
1177            ),
1178            Expr::Not { expr } => {
1179                RcDoc::concat([RcDoc::text("NOT"), RcDoc::line(), self.doc_expr(expr)])
1180            }
1181            Expr::Between {
1182                expr,
1183                negated,
1184                low,
1185                high,
1186            } => RcDoc::intersperse(
1187                [
1188                    self.doc_expr(expr),
1189                    RcDoc::text(if *negated { "NOT BETWEEN" } else { "BETWEEN" }),
1190                    RcDoc::intersperse(
1191                        [self.doc_expr(low), RcDoc::text("AND"), self.doc_expr(high)],
1192                        RcDoc::line(),
1193                    )
1194                    .group(),
1195                ],
1196                RcDoc::line(),
1197            ),
1198            Expr::InSubquery {
1199                expr,
1200                subquery,
1201                negated,
1202            } => RcDoc::intersperse(
1203                [
1204                    self.doc_expr(expr),
1205                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1206                    self.doc_query(subquery),
1207                    RcDoc::text(")"),
1208                ],
1209                RcDoc::line(),
1210            ),
1211            Expr::InList {
1212                expr,
1213                list,
1214                negated,
1215            } => RcDoc::intersperse(
1216                [
1217                    self.doc_expr(expr),
1218                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1219                    comma_separate(|e| self.doc_expr(e), list),
1220                    RcDoc::text(")"),
1221                ],
1222                RcDoc::line(),
1223            ),
1224            Expr::Row { exprs } => {
1225                bracket("ROW(", comma_separate(|e| self.doc_expr(e), exprs), ")")
1226            }
1227            Expr::NullIf { l_expr, r_expr } => bracket(
1228                "NULLIF (",
1229                comma_separate(|e| self.doc_expr(e), [&**l_expr, &**r_expr]),
1230                ")",
1231            ),
1232            Expr::HomogenizingFunction { function, exprs } => bracket(
1233                format!("{function}("),
1234                comma_separate(|e| self.doc_expr(e), exprs),
1235                ")",
1236            ),
1237            Expr::ArraySubquery(s) => bracket("ARRAY(", self.doc_query(s), ")"),
1238            Expr::ListSubquery(s) => bracket("LIST(", self.doc_query(s), ")"),
1239            Expr::Array(exprs) => {
1240                bracket("ARRAY[", comma_separate(|e| self.doc_expr(e), exprs), "]")
1241            }
1242            Expr::List(exprs) => bracket("LIST[", comma_separate(|e| self.doc_expr(e), exprs), "]"),
1243            _ => self.doc_display(v, "expr variant"),
1244        }
1245        .group()
1246    }
1247
1248    fn doc_function<'a, T: AstInfo>(&'a self, v: &'a Function<T>) -> RcDoc<'a> {
1249        match &v.args {
1250            FunctionArgs::Star => self.doc_display_pass(v),
1251            FunctionArgs::Args { args, order_by } => {
1252                if args.is_empty() {
1253                    // Nullary, don't allow newline between parens, so just delegate.
1254                    return self.doc_display_pass(v);
1255                }
1256                if v.filter.is_some() || v.over.is_some() || !order_by.is_empty() {
1257                    return self.doc_display(v, "function filter or over or order by");
1258                }
1259                let special = match v.name.to_ast_string_stable().as_str() {
1260                    r#""extract""# if v.args.len() == Some(2) => true,
1261                    r#""position""# if v.args.len() == Some(2) => true,
1262                    _ => false,
1263                };
1264                if special {
1265                    return self.doc_display(v, "special function");
1266                }
1267                let name = format!(
1268                    "{}({}",
1269                    v.name.to_ast_string_simple(),
1270                    if v.distinct { "DISTINCT " } else { "" }
1271                );
1272                bracket(name, comma_separate(|e| self.doc_expr(e), args), ")")
1273            }
1274        }
1275    }
1276}