1use 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 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 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 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 for mapping in &v.include_headers.mappings {
132 header_docs.push(self.doc_display_pass(mapping));
133 }
134
135 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 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 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 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 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 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 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 let mut title = "CREATE SUBSOURCE".to_string();
403 if v.if_not_exists {
404 title.push_str(" IF NOT EXISTS");
405 }
406
407 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 if let Some(of_source) = &v.of_source {
420 docs.push(nest_title("OF SOURCE", self.doc_display_pass(of_source)));
421 }
422
423 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 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 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 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 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 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 nest_title("CREATE NETWORK POLICY", self.doc_display_pass(&v.name)),
504 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 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 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 if let Some(cluster) = &v.in_cluster {
548 docs.push(nest_title("IN CLUSTER", self.doc_display_pass(cluster)));
549 }
550
551 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 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 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 AlterRoleOption::Variable(var) => docs.push(self.doc_display_pass(var)),
834 }
835 intersperse_line_nest(docs)
836 }
837
838 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 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 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 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 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 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 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 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}