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, FormatMode, escape_single_quote_string};
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 [IF NOT EXISTS] name
442        let mut title = "CREATE CLUSTER".to_string();
443        if v.if_not_exists {
444            title.push_str(" IF NOT EXISTS");
445        }
446        docs.push(nest_title(&title, self.doc_display_pass(&v.name)));
447
448        // OPTIONS (...)
449        if !v.options.is_empty() {
450            docs.push(bracket(
451                "(",
452                comma_separate(|o| self.doc_display_pass(o), &v.options),
453                ")",
454            ));
455        }
456
457        // FEATURES (...)
458        if !v.features.is_empty() {
459            docs.push(bracket(
460                "FEATURES (",
461                comma_separate(|f| self.doc_display_pass(f), &v.features),
462                ")",
463            ));
464        }
465
466        RcDoc::intersperse(docs, Doc::line()).group()
467    }
468
469    pub(crate) fn doc_create_cluster_replica<'a, T: AstInfo>(
470        &'a self,
471        v: &'a CreateClusterReplicaStatement<T>,
472    ) -> RcDoc<'a> {
473        let mut docs = Vec::new();
474
475        // CREATE CLUSTER REPLICA [IF NOT EXISTS] cluster.replica
476        let mut title = "CREATE CLUSTER REPLICA".to_string();
477        if v.if_not_exists {
478            title.push_str(" IF NOT EXISTS");
479        }
480        let replica_name = RcDoc::concat([
481            self.doc_display_pass(&v.of_cluster),
482            RcDoc::text("."),
483            self.doc_display_pass(&v.definition.name),
484        ]);
485        docs.push(nest_title(&title, replica_name));
486
487        // OPTIONS (...)
488        docs.push(bracket(
489            "(",
490            comma_separate(|o| self.doc_display_pass(o), &v.definition.options),
491            ")",
492        ));
493
494        RcDoc::intersperse(docs, Doc::line()).group()
495    }
496
497    pub(crate) fn doc_create_network_policy<'a, T: AstInfo>(
498        &'a self,
499        v: &'a CreateNetworkPolicyStatement<T>,
500    ) -> RcDoc<'a> {
501        let docs = vec![
502            // CREATE NETWORK POLICY name
503            nest_title("CREATE NETWORK POLICY", self.doc_display_pass(&v.name)),
504            // OPTIONS (...)
505            bracket(
506                "(",
507                comma_separate(|o| self.doc_display_pass(o), &v.options),
508                ")",
509            ),
510        ];
511
512        RcDoc::intersperse(docs, Doc::line()).group()
513    }
514
515    pub(crate) fn doc_create_index<'a, T: AstInfo>(
516        &'a self,
517        v: &'a CreateIndexStatement<T>,
518    ) -> RcDoc<'a> {
519        let mut docs = Vec::new();
520
521        // CREATE [DEFAULT] INDEX [IF NOT EXISTS] [name]
522        let mut title = "CREATE".to_string();
523        if v.key_parts.is_none() {
524            title.push_str(" DEFAULT");
525        }
526        title.push_str(" INDEX");
527        if v.if_not_exists {
528            title.push_str(" IF NOT EXISTS");
529        }
530
531        if let Some(name) = &v.name {
532            // A bare `in` index name re-lexes as the start of the optional
533            // `IN CLUSTER` clause, so force it quoted (matching the
534            // `CreateIndexStatement` AstDisplay impl). `in` is fine bare in a
535            // required-name position, so this isn't a `can_be_printed_bare` case.
536            let name_doc = if name.as_str().eq_ignore_ascii_case("in") {
537                RcDoc::text(format!("\"{}\"", name.as_str()))
538            } else {
539                self.doc_display_pass(name)
540            };
541            docs.push(nest_title(title, name_doc));
542        } else {
543            docs.push(RcDoc::text(title));
544        }
545
546        // IN CLUSTER
547        if let Some(cluster) = &v.in_cluster {
548            docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
549        }
550
551        // ON table_name [(key_parts)]
552        let on_clause = if let Some(key_parts) = &v.key_parts {
553            nest(
554                self.doc_display_pass(&v.on_name),
555                bracket(
556                    "(",
557                    comma_separate(|k| self.doc_display_pass(k), key_parts),
558                    ")",
559                ),
560            )
561        } else {
562            self.doc_display_pass(&v.on_name)
563        };
564        docs.push(nest_title("ON", on_clause));
565
566        // WITH options
567        if !v.with_options.is_empty() {
568            docs.push(bracket(
569                "WITH (",
570                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
571                ")",
572            ));
573        }
574
575        RcDoc::intersperse(docs, Doc::line()).group()
576    }
577
578    fn doc_format_specifier<T: AstInfo>(&self, v: &FormatSpecifier<T>) -> RcDoc<'_> {
579        match v {
580            FormatSpecifier::Bare(format) => nest_title("FORMAT", self.doc_display_pass(format)),
581            FormatSpecifier::KeyValue { key, value } => {
582                let docs = vec![
583                    nest_title("KEY FORMAT", self.doc_display_pass(key)),
584                    nest_title("VALUE FORMAT", self.doc_display_pass(value)),
585                ];
586                RcDoc::intersperse(docs, Doc::line()).group()
587            }
588        }
589    }
590
591    fn doc_external_references<'a>(&'a self, v: &'a ExternalReferences) -> RcDoc<'a> {
592        match v {
593            ExternalReferences::SubsetTables(subsources) => bracket(
594                "FOR TABLES (",
595                comma_separate(|s| self.doc_display_pass(s), subsources),
596                ")",
597            ),
598            ExternalReferences::SubsetSchemas(schemas) => bracket(
599                "FOR SCHEMAS (",
600                comma_separate(|s| self.doc_display_pass(s), schemas),
601                ")",
602            ),
603            ExternalReferences::All => RcDoc::text("FOR ALL TABLES"),
604        }
605    }
606
607    pub(crate) fn doc_copy<'a, T: AstInfo>(&'a self, v: &'a CopyStatement<T>) -> RcDoc<'a> {
608        let relation = match &v.relation {
609            CopyRelation::Named { name, columns } => {
610                let mut relation = self.doc_display_pass(name);
611                if !columns.is_empty() {
612                    relation = bracket_doc(
613                        nest(relation, RcDoc::text("(")),
614                        comma_separate(|c| self.doc_display_pass(c), columns),
615                        RcDoc::text(")"),
616                        RcDoc::line_(),
617                    );
618                }
619                RcDoc::concat([RcDoc::text("COPY "), relation])
620            }
621            CopyRelation::Select(query) => bracket("COPY (", self.doc_select_statement(query), ")"),
622            CopyRelation::Subscribe(query) => bracket("COPY (", self.doc_subscribe(query), ")"),
623        };
624        let mut docs = vec![
625            relation,
626            RcDoc::concat([
627                self.doc_display_pass(&v.direction),
628                RcDoc::text(" "),
629                self.doc_display_pass(&v.target),
630            ]),
631        ];
632        if !v.options.is_empty() {
633            docs.push(bracket(
634                "WITH (",
635                comma_separate(|o| self.doc_display_pass(o), &v.options),
636                ")",
637            ));
638        }
639        RcDoc::intersperse(docs, Doc::line()).group()
640    }
641
642    pub(crate) fn doc_subscribe<'a, T: AstInfo>(
643        &'a self,
644        v: &'a SubscribeStatement<T>,
645    ) -> RcDoc<'a> {
646        let doc = match &v.relation {
647            SubscribeRelation::Name(name) => {
648                let title = if v.relation.needs_explicit_to(matches!(
649                    self.config.format_mode,
650                    FormatMode::Simple | FormatMode::SimpleRedacted
651                )) {
652                    "SUBSCRIBE TO"
653                } else {
654                    "SUBSCRIBE"
655                };
656                nest_title(title, self.doc_display_pass(name))
657            }
658            SubscribeRelation::Query(query) => bracket("SUBSCRIBE (", self.doc_query(query), ")"),
659        };
660        let mut docs = vec![doc];
661        if !v.options.is_empty() {
662            docs.push(bracket(
663                "WITH (",
664                comma_separate(|o| self.doc_display_pass(o), &v.options),
665                ")",
666            ));
667        }
668        if let Some(as_of) = &v.as_of {
669            docs.push(self.doc_as_of(as_of));
670        }
671        if let Some(up_to) = &v.up_to {
672            docs.push(nest_title("UP TO", self.doc_expr(up_to)));
673        }
674        match &v.output {
675            SubscribeOutput::Diffs => {}
676            SubscribeOutput::WithinTimestampOrderBy { order_by } => {
677                docs.push(nest_title(
678                    "WITHIN TIMESTAMP ORDER BY ",
679                    comma_separate(|o| self.doc_order_by_expr(o), order_by),
680                ));
681            }
682            SubscribeOutput::EnvelopeUpsert { key_columns } => {
683                docs.push(bracket(
684                    "ENVELOPE UPSERT (KEY (",
685                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
686                    "))",
687                ));
688            }
689            SubscribeOutput::EnvelopeDebezium { key_columns } => {
690                docs.push(bracket(
691                    "ENVELOPE DEBEZIUM (KEY (",
692                    comma_separate(|kc| self.doc_display_pass(kc), key_columns),
693                    "))",
694                ));
695            }
696        }
697        RcDoc::intersperse(docs, Doc::line()).group()
698    }
699
700    fn doc_as_of<'a, T: AstInfo>(&'a self, v: &'a AsOf<T>) -> RcDoc<'a> {
701        let (title, expr) = match v {
702            AsOf::At(expr) => ("AS OF", expr),
703            AsOf::AtLeast(expr) => ("AS OF AT LEAST", expr),
704        };
705        nest_title(title, self.doc_expr(expr))
706    }
707
708    pub(crate) fn doc_create_view<'a, T: AstInfo>(
709        &'a self,
710        v: &'a CreateViewStatement<T>,
711    ) -> RcDoc<'a> {
712        let mut docs = vec![];
713        docs.push(RcDoc::text(format!(
714            "CREATE{}{} VIEW{}",
715            if v.if_exists == IfExistsBehavior::Replace {
716                " OR REPLACE"
717            } else {
718                ""
719            },
720            if v.temporary { " TEMPORARY" } else { "" },
721            if v.if_exists == IfExistsBehavior::Skip {
722                " IF NOT EXISTS"
723            } else {
724                ""
725            },
726        )));
727        docs.push(self.doc_view_definition(&v.definition));
728        intersperse_line_nest(docs)
729    }
730
731    pub(crate) fn doc_create_materialized_view<'a, T: AstInfo>(
732        &'a self,
733        v: &'a CreateMaterializedViewStatement<T>,
734    ) -> RcDoc<'a> {
735        let mut docs = vec![];
736        docs.push(RcDoc::text(format!(
737            "CREATE{}{} MATERIALIZED VIEW{} {}",
738            if v.if_exists == IfExistsBehavior::Replace {
739                " OR REPLACE"
740            } else {
741                ""
742            },
743            if v.replacement_for.is_some() {
744                " REPLACEMENT"
745            } else {
746                ""
747            },
748            if v.if_exists == IfExistsBehavior::Skip {
749                " IF NOT EXISTS"
750            } else {
751                ""
752            },
753            v.name,
754        )));
755        if !v.columns.is_empty() {
756            docs.push(bracket(
757                "(",
758                comma_separate(|c| self.doc_display_pass(c), &v.columns),
759                ")",
760            ));
761        }
762        if let Some(target) = &v.replacement_for {
763            docs.push(RcDoc::text(format!(
764                "FOR {}",
765                target.to_ast_string_simple()
766            )));
767        }
768        match (&v.in_cluster, &v.in_cluster_replica) {
769            (Some(cluster), Some(replica)) => {
770                docs.push(RcDoc::text(format!(
771                    "IN CLUSTER {} REPLICA {}",
772                    cluster.to_ast_string_simple(),
773                    replica.to_ast_string_simple(),
774                )));
775            }
776            (Some(cluster), None) => {
777                docs.push(RcDoc::text(format!(
778                    "IN CLUSTER {}",
779                    cluster.to_ast_string_simple(),
780                )));
781            }
782            (None, Some(replica)) => {
783                docs.push(RcDoc::text(format!(
784                    "IN REPLICA {}",
785                    replica.to_ast_string_simple(),
786                )));
787            }
788            (None, None) => {}
789        }
790        if !v.with_options.is_empty() {
791            docs.push(bracket(
792                "WITH (",
793                comma_separate(|wo| self.doc_display_pass(wo), &v.with_options),
794                ")",
795            ));
796        }
797        docs.push(nest_title("AS", self.doc_query(&v.query)));
798        // `AS OF` is internal syntax that follows the query; the generic AstDisplay
799        // emits it, so we must too, otherwise it is silently dropped.
800        if let Some(time) = &v.as_of {
801            docs.push(RcDoc::text(format!("AS OF {time}")));
802        }
803        intersperse_line_nest(docs)
804    }
805
806    pub(crate) fn doc_create_role<'a>(&'a self, v: &'a CreateRoleStatement) -> RcDoc<'a> {
807        let mut docs = vec![RcDoc::text(format!(
808            "CREATE ROLE {}",
809            v.name.to_ast_string_simple()
810        ))];
811        for option in &v.options {
812            docs.push(self.doc_role_attribute(option));
813        }
814        intersperse_line_nest(docs)
815    }
816
817    pub(crate) fn doc_alter_role<'a, T: AstInfo>(
818        &'a self,
819        v: &'a AlterRoleStatement<T>,
820    ) -> RcDoc<'a> {
821        let mut docs = vec![RcDoc::text(format!(
822            "ALTER ROLE {}",
823            v.name.to_ast_string_simple()
824        ))];
825        match &v.option {
826            AlterRoleOption::Attributes(attrs) => {
827                for attr in attrs {
828                    docs.push(self.doc_role_attribute(attr));
829                }
830            }
831            // `SET`/`RESET` variables carry no password-like data, so the generic
832            // AstDisplay is already lossless here.
833            AlterRoleOption::Variable(var) => docs.push(self.doc_display_pass(var)),
834        }
835        intersperse_line_nest(docs)
836    }
837
838    /// Like the generic AstDisplay for `RoleAttribute`, but preserves the `PASSWORD`
839    /// value instead of dropping it (the AstDisplay redaction is a global safety net
840    /// for logs/catalog; a pretty-printer that round-trips user SQL must keep it).
841    fn doc_role_attribute<'a>(&'a self, attr: &'a RoleAttribute) -> RcDoc<'a> {
842        match attr {
843            RoleAttribute::Password(Some(password)) => RcDoc::text(format!(
844                "PASSWORD '{}'",
845                escape_single_quote_string(password)
846            )),
847            RoleAttribute::Password(None) => RcDoc::text("PASSWORD NULL"),
848            other => self.doc_display_pass(other),
849        }
850    }
851
852    /// `DECLARE <name> CURSOR FOR <stmt>`. The inner statement is printed via the
853    /// recursive doc printer (not the redacting AstDisplay fallback) so a secret
854    /// it carries — e.g. `CURSOR FOR ALTER ROLE r PASSWORD '…'` — survives the
855    /// round trip instead of becoming `'<REDACTED>'`.
856    pub(crate) fn doc_declare<'a, T: AstInfo<NestedStatement = Statement<Raw>>>(
857        &'a self,
858        v: &'a DeclareStatement<T>,
859    ) -> RcDoc<'a> {
860        RcDoc::text(format!(
861            "DECLARE {} CURSOR FOR ",
862            v.name.to_ast_string_simple()
863        ))
864        .append(self.to_doc(&v.stmt))
865    }
866
867    /// `PREPARE <name> AS <stmt>`. Recurses into the inner statement for the same
868    /// reason as [`Self::doc_declare`].
869    pub(crate) fn doc_prepare<'a, T: AstInfo<NestedStatement = Statement<Raw>>>(
870        &'a self,
871        v: &'a PrepareStatement<T>,
872    ) -> RcDoc<'a> {
873        RcDoc::text(format!("PREPARE {} AS ", v.name.to_ast_string_simple()))
874            .append(self.to_doc(&v.stmt))
875    }
876
877    fn doc_view_definition<'a, T: AstInfo>(&'a self, v: &'a ViewDefinition<T>) -> RcDoc<'a> {
878        let mut docs = vec![RcDoc::text(v.name.to_string())];
879        if !v.columns.is_empty() {
880            docs.push(bracket(
881                "(",
882                comma_separate(|c| self.doc_display_pass(c), &v.columns),
883                ")",
884            ));
885        }
886        docs.push(nest_title("AS", self.doc_query(&v.query)));
887        RcDoc::intersperse(docs, Doc::line()).group()
888    }
889
890    pub(crate) fn doc_insert<'a, T: AstInfo>(&'a self, v: &'a InsertStatement<T>) -> RcDoc<'a> {
891        let mut first = vec![RcDoc::text(format!(
892            "INSERT INTO {}",
893            v.table_name.to_ast_string_simple()
894        ))];
895        if !v.columns.is_empty() {
896            first.push(bracket(
897                "(",
898                comma_separate(|c| self.doc_display_pass(c), &v.columns),
899                ")",
900            ));
901        }
902        let sources = match &v.source {
903            InsertSource::Query(query) => self.doc_query(query),
904            InsertSource::DefaultValues => self.doc_display(&v.source, "insert source"),
905        };
906        let mut doc = intersperse_line_nest([intersperse_line_nest(first), sources]);
907        if !v.returning.is_empty() {
908            doc = nest(
909                doc,
910                nest_title(
911                    "RETURNING",
912                    comma_separate(|r| self.doc_display_pass(r), &v.returning),
913                ),
914            )
915        }
916        doc
917    }
918
919    pub(crate) fn doc_select_statement<'a, T: AstInfo>(
920        &'a self,
921        v: &'a SelectStatement<T>,
922    ) -> RcDoc<'a> {
923        let query = self.doc_query(&v.query);
924        // A query whose rendering begins with `SHOW` (a bare `SHOW` body or a
925        // set operation whose leftmost operand is one) only reparses when
926        // parenthesized — a top-level leading `SHOW` is dispatched directly and
927        // terminates the statement. Mirror `AstDisplay for SelectStatement`.
928        let mut doc = if v.query.body.starts_with_show() {
929            bracket("(", query, ")")
930        } else {
931            query
932        };
933        if let Some(as_of) = &v.as_of {
934            doc = intersperse_line_nest([doc, self.doc_as_of(as_of)]);
935        }
936        doc.group()
937    }
938
939    fn doc_order_by<'a, T: AstInfo>(&'a self, v: &'a [OrderByExpr<T>]) -> RcDoc<'a> {
940        title_comma_separate("ORDER BY", |o| self.doc_order_by_expr(o), v)
941    }
942
943    fn doc_order_by_expr<'a, T: AstInfo>(&'a self, v: &'a OrderByExpr<T>) -> RcDoc<'a> {
944        let doc = self.doc_expr(&v.expr);
945        let doc = match v.asc {
946            Some(true) => nest(doc, RcDoc::text("ASC")),
947            Some(false) => nest(doc, RcDoc::text("DESC")),
948            None => doc,
949        };
950        match v.nulls_last {
951            Some(true) => nest(doc, RcDoc::text("NULLS LAST")),
952            Some(false) => nest(doc, RcDoc::text("NULLS FIRST")),
953            None => doc,
954        }
955    }
956
957    fn doc_query<'a, T: AstInfo>(&'a self, v: &'a Query<T>) -> RcDoc<'a> {
958        let mut docs = vec![];
959        if !v.ctes.is_empty() {
960            match &v.ctes {
961                CteBlock::Simple(ctes) => {
962                    docs.push(title_comma_separate("WITH", |cte| self.doc_cte(cte), ctes))
963                }
964                CteBlock::MutuallyRecursive(mutrec) => {
965                    let mut doc = RcDoc::text("WITH MUTUALLY RECURSIVE");
966                    if !mutrec.options.is_empty() {
967                        doc = nest(
968                            doc,
969                            bracket(
970                                "(",
971                                comma_separate(|o| self.doc_display_pass(o), &mutrec.options),
972                                ")",
973                            ),
974                        );
975                    }
976                    docs.push(nest(
977                        doc,
978                        comma_separate(|c| self.doc_mutually_recursive(c), &mutrec.ctes),
979                    ));
980                }
981            }
982        }
983        docs.push(self.doc_set_expr(&v.body));
984        if !v.order_by.is_empty() {
985            docs.push(self.doc_order_by(&v.order_by));
986        }
987
988        let offset = if let Some(offset) = &v.offset {
989            vec![RcDoc::concat([nest_title("OFFSET", self.doc_expr(offset))])]
990        } else {
991            vec![]
992        };
993
994        if let Some(limit) = &v.limit {
995            if limit.with_ties {
996                docs.extend(offset);
997                docs.push(RcDoc::concat([
998                    RcDoc::text("FETCH FIRST "),
999                    self.doc_expr(&limit.quantity),
1000                    RcDoc::text(" ROWS WITH TIES"),
1001                ]));
1002            } else {
1003                docs.push(nest_title("LIMIT", self.doc_expr(&limit.quantity)));
1004                docs.extend(offset);
1005            }
1006        } else {
1007            docs.extend(offset);
1008        }
1009
1010        RcDoc::intersperse(docs, Doc::line()).group()
1011    }
1012
1013    fn doc_cte<'a, T: AstInfo>(&'a self, v: &'a Cte<T>) -> RcDoc<'a> {
1014        RcDoc::concat([
1015            RcDoc::text(format!("{} AS", v.alias)),
1016            RcDoc::line(),
1017            bracket("(", self.doc_query(&v.query), ")"),
1018        ])
1019    }
1020
1021    fn doc_mutually_recursive<'a, T: AstInfo>(&'a self, v: &'a CteMutRec<T>) -> RcDoc<'a> {
1022        let mut docs = Vec::new();
1023        if !v.columns.is_empty() {
1024            docs.push(bracket(
1025                "(",
1026                comma_separate(|c| self.doc_display_pass(c), &v.columns),
1027                ")",
1028            ));
1029        }
1030        docs.push(bracket("AS (", self.doc_query(&v.query), ")"));
1031        nest(
1032            self.doc_display_pass(&v.name),
1033            RcDoc::intersperse(docs, Doc::line()).group(),
1034        )
1035    }
1036
1037    fn doc_set_expr<'a, T: AstInfo>(&'a self, v: &'a SetExpr<T>) -> RcDoc<'a> {
1038        match v {
1039            SetExpr::Select(v) => self.doc_select(v),
1040            SetExpr::Query(v) => bracket("(", self.doc_query(v), ")"),
1041            SetExpr::SetOperation {
1042                op,
1043                all,
1044                left,
1045                right,
1046            } => {
1047                let all_str = if *all { " ALL" } else { "" };
1048                RcDoc::concat([
1049                    self.doc_set_expr(left),
1050                    RcDoc::line(),
1051                    RcDoc::concat([
1052                        RcDoc::text(format!("{}{}", op, all_str)),
1053                        RcDoc::line(),
1054                        self.doc_set_expr(right),
1055                    ])
1056                    .nest(TAB)
1057                    .group(),
1058                ])
1059            }
1060            SetExpr::Values(v) => self.doc_values(v),
1061            SetExpr::Show(v) => self.doc_display(v, "SHOW"),
1062            SetExpr::Table(v) => nest(RcDoc::text("TABLE"), self.doc_display_pass(v)),
1063        }
1064        .group()
1065    }
1066
1067    fn doc_values<'a, T: AstInfo>(&'a self, v: &'a Values<T>) -> RcDoc<'a> {
1068        let rows =
1069            v.0.iter()
1070                .map(|row| bracket("(", comma_separate(|v| self.doc_expr(v), row), ")"));
1071        RcDoc::concat([RcDoc::text("VALUES"), RcDoc::line(), comma_separated(rows)])
1072            .nest(TAB)
1073            .group()
1074    }
1075
1076    fn doc_table_with_joins<'a, T: AstInfo>(&'a self, v: &'a TableWithJoins<T>) -> RcDoc<'a> {
1077        let mut docs = vec![self.doc_table_factor(&v.relation)];
1078        for j in &v.joins {
1079            docs.push(self.doc_join(j));
1080        }
1081        intersperse_line_nest(docs)
1082    }
1083
1084    fn doc_join<'a, T: AstInfo>(&'a self, v: &'a Join<T>) -> RcDoc<'a> {
1085        let (constraint, name) = match &v.join_operator {
1086            JoinOperator::Inner(constraint) => (constraint, "JOIN"),
1087            JoinOperator::FullOuter(constraint) => (constraint, "FULL JOIN"),
1088            JoinOperator::LeftOuter(constraint) => (constraint, "LEFT JOIN"),
1089            JoinOperator::RightOuter(constraint) => (constraint, "RIGHT JOIN"),
1090            JoinOperator::CrossJoin => return self.doc_display(v, "join operator"),
1091        };
1092        let constraint = match constraint {
1093            JoinConstraint::On(expr) => nest_title("ON", self.doc_expr(expr)),
1094            JoinConstraint::Using { columns, alias } => {
1095                let mut doc = bracket(
1096                    "USING(",
1097                    comma_separate(|c| self.doc_display_pass(c), columns),
1098                    ")",
1099                );
1100                if let Some(alias) = alias {
1101                    doc = nest(doc, nest_title("AS", self.doc_display_pass(alias)));
1102                }
1103                doc
1104            }
1105            JoinConstraint::Natural => return self.doc_display(v, "join constraint"),
1106        };
1107        intersperse_line_nest([
1108            RcDoc::text(name),
1109            self.doc_table_factor(&v.relation),
1110            constraint,
1111        ])
1112    }
1113
1114    fn doc_table_factor<'a, T: AstInfo>(&'a self, v: &'a TableFactor<T>) -> RcDoc<'a> {
1115        match v {
1116            TableFactor::Derived {
1117                lateral,
1118                subquery,
1119                alias,
1120            } => {
1121                let prefix = if *lateral { "LATERAL (" } else { "(" };
1122                let mut docs = vec![bracket(prefix, self.doc_query(subquery), ")")];
1123                if let Some(alias) = alias {
1124                    docs.push(RcDoc::text(format!("AS {}", alias)));
1125                }
1126                intersperse_line_nest(docs)
1127            }
1128            TableFactor::NestedJoin { join, alias } => {
1129                let mut doc = bracket("(", self.doc_table_with_joins(join), ")");
1130                if let Some(alias) = alias {
1131                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1132                }
1133                doc
1134            }
1135            TableFactor::Table { name, alias } => {
1136                let mut doc = self.doc_display_pass(name);
1137                if let Some(alias) = alias {
1138                    doc = nest(doc, RcDoc::text(format!("AS {}", alias)));
1139                }
1140                doc
1141            }
1142            _ => self.doc_display(v, "table factor variant"),
1143        }
1144    }
1145
1146    fn doc_distinct<'a, T: AstInfo>(&'a self, v: &'a Distinct<T>) -> RcDoc<'a> {
1147        match v {
1148            Distinct::EntireRow => RcDoc::text("DISTINCT"),
1149            Distinct::On(cols) => bracket(
1150                "DISTINCT ON (",
1151                comma_separate(|c| self.doc_expr(c), cols),
1152                ")",
1153            ),
1154        }
1155    }
1156
1157    fn doc_select<'a, T: AstInfo>(&'a self, v: &'a Select<T>) -> RcDoc<'a> {
1158        let mut docs = vec![];
1159        let mut select = RcDoc::text("SELECT");
1160        if let Some(distinct) = &v.distinct {
1161            select = nest(select, self.doc_distinct(distinct));
1162        }
1163        docs.push(nest_comma_separate(
1164            select,
1165            |s| self.doc_select_item(s),
1166            &v.projection,
1167        ));
1168        if !v.from.is_empty() {
1169            docs.push(title_comma_separate(
1170                "FROM",
1171                |t| self.doc_table_with_joins(t),
1172                &v.from,
1173            ));
1174        }
1175        if let Some(selection) = &v.selection {
1176            docs.push(nest_title("WHERE", self.doc_expr(selection)));
1177        }
1178        if !v.group_by.is_empty() {
1179            docs.push(title_comma_separate(
1180                "GROUP BY",
1181                |e| self.doc_expr(e),
1182                &v.group_by,
1183            ));
1184        }
1185        if let Some(having) = &v.having {
1186            docs.push(nest_title("HAVING", self.doc_expr(having)));
1187        }
1188        if let Some(qualify) = &v.qualify {
1189            docs.push(nest_title("QUALIFY", self.doc_expr(qualify)));
1190        }
1191        if !v.options.is_empty() {
1192            docs.push(bracket(
1193                "OPTIONS (",
1194                comma_separate(|o| self.doc_display_pass(o), &v.options),
1195                ")",
1196            ));
1197        }
1198        RcDoc::intersperse(docs, Doc::line()).group()
1199    }
1200
1201    fn doc_select_item<'a, T: AstInfo>(&'a self, v: &'a SelectItem<T>) -> RcDoc<'a> {
1202        match v {
1203            SelectItem::Expr { expr, alias } => {
1204                let mut doc = self.doc_expr(expr);
1205                if let Some(alias) = alias {
1206                    doc = nest(
1207                        doc,
1208                        RcDoc::concat([RcDoc::text("AS "), self.doc_display_pass(alias)]),
1209                    );
1210                }
1211                doc
1212            }
1213            SelectItem::Wildcard => self.doc_display_pass(v),
1214        }
1215    }
1216
1217    pub fn doc_expr<'a, T: AstInfo>(&'a self, v: &'a Expr<T>) -> RcDoc<'a> {
1218        match v {
1219            Expr::Op { op, expr1, expr2 } => {
1220                if let Some(expr2) = expr2 {
1221                    RcDoc::concat([
1222                        self.doc_expr(expr1),
1223                        RcDoc::line(),
1224                        RcDoc::text(format!("{} ", op)),
1225                        self.doc_expr(expr2).nest(TAB),
1226                    ])
1227                } else {
1228                    // See the AstDisplay `Expr::Op` comment (`prefix_operand_needs_parens`):
1229                    // a prefix op binds tighter than `COLLATE`/the binary ops but
1230                    // looser than the postfix `::`/`[…]`, and `- <number>` folds, so
1231                    // peel the tight postfixes and parenthesize when the chain
1232                    // bottoms out at a numeric literal or a non-self-delimiting /
1233                    // `COLLATE` operand.
1234                    let needs_parens = {
1235                        let mut e = expr1.as_ref();
1236                        let mut saw_postfix = false;
1237                        loop {
1238                            match e {
1239                                Expr::Cast { expr, .. } | Expr::Subscript { expr, .. } => {
1240                                    saw_postfix = true;
1241                                    e = expr.as_ref();
1242                                }
1243                                Expr::Value(Value::Number(_)) => break saw_postfix,
1244                                // Another prefix operator stacks directly (no
1245                                // re-association, no `- <number>` fold) — safe,
1246                                // and avoids exploding deep unary chains.
1247                                Expr::Op { expr2: None, .. } | Expr::Not { .. } => break false,
1248                                Expr::Value(_)
1249                                | Expr::Identifier(_)
1250                                | Expr::QualifiedWildcard(_)
1251                                | Expr::Parameter(_)
1252                                | Expr::Function(_)
1253                                | Expr::HomogenizingFunction { .. }
1254                                | Expr::NullIf { .. }
1255                                | Expr::Subquery(_)
1256                                | Expr::Exists(_)
1257                                | Expr::Nested(_)
1258                                | Expr::Array(_)
1259                                | Expr::ArraySubquery(_)
1260                                | Expr::List(_)
1261                                | Expr::ListSubquery(_)
1262                                | Expr::Map(_)
1263                                | Expr::MapSubquery(_)
1264                                | Expr::Case { .. }
1265                                | Expr::Row { .. } => break false,
1266                                _ => break true,
1267                            }
1268                        }
1269                    };
1270                    let operand = if needs_parens {
1271                        bracket("(", self.doc_expr(expr1), ")")
1272                    } else {
1273                        self.doc_expr(expr1)
1274                    };
1275                    RcDoc::concat([RcDoc::text(format!("{} ", op)), operand])
1276                }
1277            }
1278            Expr::Case {
1279                operand,
1280                conditions,
1281                results,
1282                else_result,
1283            } => {
1284                let mut docs = Vec::new();
1285                if let Some(operand) = operand {
1286                    docs.push(self.doc_expr(operand));
1287                }
1288                for (c, r) in conditions.iter().zip_eq(results) {
1289                    let when = nest_title("WHEN", self.doc_expr(c));
1290                    let then = nest_title("THEN", self.doc_expr(r));
1291                    docs.push(nest(when, then));
1292                }
1293                if let Some(else_result) = else_result {
1294                    docs.push(nest_title("ELSE", self.doc_expr(else_result)));
1295                }
1296                let doc = intersperse_line_nest(docs);
1297                bracket_doc(RcDoc::text("CASE"), doc, RcDoc::text("END"), RcDoc::line())
1298            }
1299            Expr::Cast { expr, data_type } => {
1300                let doc = self.doc_expr(expr);
1301                RcDoc::concat([
1302                    doc,
1303                    RcDoc::text(format!("::{}", data_type.to_ast_string_simple())),
1304                ])
1305            }
1306            Expr::Nested(ast) => bracket("(", self.doc_expr(ast), ")"),
1307            Expr::Function(fun) => self.doc_function(fun),
1308            Expr::Subquery(ast) => bracket("(", self.doc_query(ast), ")"),
1309            Expr::Identifier(_)
1310            | Expr::Value(_)
1311            | Expr::QualifiedWildcard(_)
1312            | Expr::WildcardAccess(_)
1313            | Expr::FieldAccess { .. } => self.doc_display_pass(v),
1314            Expr::And { left, right } => bracket_doc(
1315                self.doc_expr(left),
1316                RcDoc::text("AND"),
1317                self.doc_expr(right),
1318                RcDoc::line(),
1319            ),
1320            Expr::Or { left, right } => bracket_doc(
1321                self.doc_expr(left),
1322                RcDoc::text("OR"),
1323                self.doc_expr(right),
1324                RcDoc::line(),
1325            ),
1326            Expr::Exists(s) => bracket("EXISTS (", self.doc_query(s), ")"),
1327            Expr::IsExpr {
1328                expr,
1329                negated,
1330                construct,
1331            } => bracket_doc(
1332                self.doc_expr(expr),
1333                RcDoc::text(if *negated { "IS NOT" } else { "IS" }),
1334                self.doc_display_pass(construct),
1335                RcDoc::line(),
1336            ),
1337            Expr::Not { expr } => {
1338                RcDoc::concat([RcDoc::text("NOT"), RcDoc::line(), self.doc_expr(expr)])
1339            }
1340            Expr::Between {
1341                expr,
1342                negated,
1343                low,
1344                high,
1345            } => RcDoc::intersperse(
1346                [
1347                    self.doc_expr(expr),
1348                    RcDoc::text(if *negated { "NOT BETWEEN" } else { "BETWEEN" }),
1349                    RcDoc::intersperse(
1350                        [self.doc_expr(low), RcDoc::text("AND"), self.doc_expr(high)],
1351                        RcDoc::line(),
1352                    )
1353                    .group(),
1354                ],
1355                RcDoc::line(),
1356            ),
1357            Expr::InSubquery {
1358                expr,
1359                subquery,
1360                negated,
1361            } => RcDoc::intersperse(
1362                [
1363                    self.doc_expr(expr),
1364                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1365                    self.doc_query(subquery),
1366                    RcDoc::text(")"),
1367                ],
1368                RcDoc::line(),
1369            ),
1370            Expr::InList {
1371                expr,
1372                list,
1373                negated,
1374            } => RcDoc::intersperse(
1375                [
1376                    self.doc_expr(expr),
1377                    RcDoc::text(if *negated { "NOT IN (" } else { "IN (" }),
1378                    comma_separate(|e| self.doc_expr(e), list),
1379                    RcDoc::text(")"),
1380                ],
1381                RcDoc::line(),
1382            ),
1383            Expr::Row { exprs } => {
1384                bracket("ROW(", comma_separate(|e| self.doc_expr(e), exprs), ")")
1385            }
1386            Expr::NullIf { l_expr, r_expr } => bracket(
1387                "NULLIF (",
1388                comma_separate(|e| self.doc_expr(e), [&**l_expr, &**r_expr]),
1389                ")",
1390            ),
1391            Expr::HomogenizingFunction { function, exprs } => bracket(
1392                format!("{function}("),
1393                comma_separate(|e| self.doc_expr(e), exprs),
1394                ")",
1395            ),
1396            Expr::ArraySubquery(s) => bracket("ARRAY(", self.doc_query(s), ")"),
1397            Expr::ListSubquery(s) => bracket("LIST(", self.doc_query(s), ")"),
1398            Expr::Array(exprs) => {
1399                bracket("ARRAY[", comma_separate(|e| self.doc_expr(e), exprs), "]")
1400            }
1401            Expr::List(exprs) => bracket("LIST[", comma_separate(|e| self.doc_expr(e), exprs), "]"),
1402            _ => self.doc_display(v, "expr variant"),
1403        }
1404        .group()
1405    }
1406
1407    fn doc_function<'a, T: AstInfo>(&'a self, v: &'a Function<T>) -> RcDoc<'a> {
1408        match &v.args {
1409            FunctionArgs::Star => self.doc_display_pass(v),
1410            FunctionArgs::Args { args, order_by } => {
1411                if args.is_empty() {
1412                    // Nullary, don't allow newline between parens, so just delegate.
1413                    return self.doc_display_pass(v);
1414                }
1415                if v.filter.is_some() || v.over.is_some() || !order_by.is_empty() {
1416                    return self.doc_display(v, "function filter or over or order by");
1417                }
1418                let name_stable = v.name.to_ast_string_stable();
1419                let special = match name_stable.as_str() {
1420                    r#""extract""# if v.args.len() == Some(2) => true,
1421                    r#""position""# if v.args.len() == Some(2) => true,
1422                    _ => false,
1423                };
1424                if special {
1425                    return self.doc_display(v, "special function");
1426                }
1427                // Mirror the same carve-out as the `AstDisplay for Function`
1428                // impl: function names that clash with a keyword having its
1429                // own special-grammar parser form (`(Kw, LParen)` dispatch in
1430                // parse_prefix) must be quoted on emit, or the reparse goes
1431                // through the special grammar instead of a regular call. (The
1432                // `ANY`/`ALL`/`SOME` quantifier keywords are handled more
1433                // generally by `can_be_printed_bare`, since they're also unsafe
1434                // as bare identifiers.)
1435                let needs_quote = matches!(
1436                    name_stable.as_str(),
1437                    r#""array""#
1438                        | r#""coalesce""#
1439                        | r#""exists""#
1440                        | r#""extract""#
1441                        | r#""greatest""#
1442                        | r#""least""#
1443                        | r#""list""#
1444                        | r#""map""#
1445                        | r#""normalize""#
1446                        | r#""nullif""#
1447                        | r#""position""#
1448                        | r#""row""#
1449                        | r#""substring""#
1450                        | r#""trim""#
1451                );
1452                let printed_name = if needs_quote {
1453                    name_stable
1454                } else {
1455                    v.name.to_ast_string_simple()
1456                };
1457                let name = format!(
1458                    "{}({}",
1459                    printed_name,
1460                    if v.distinct { "DISTINCT " } else { "" }
1461                );
1462                bracket(name, comma_separate(|e| self.doc_expr(e), args), ")")
1463            }
1464        }
1465    }
1466}