1use std::fmt::{self, Debug};
22use std::hash::Hash;
23use std::mem;
24
25use crate::ast::display::{self, AstDisplay, AstFormatter, WithOptionName};
26use crate::ast::{AstInfo, Expr, Function, Ident, ShowStatement, WithOptionValue};
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
31pub struct Query<T: AstInfo> {
32 pub ctes: CteBlock<T>,
34 pub body: SetExpr<T>,
36 pub order_by: Vec<OrderByExpr<T>>,
38 pub limit: Option<Limit<T>>,
41 pub offset: Option<Expr<T>>,
43}
44
45impl<T: AstInfo> AstDisplay for Query<T> {
46 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
47 f.write_node(&self.ctes);
48 f.write_node(&self.body);
49 if !self.order_by.is_empty() {
50 f.write_str(" ORDER BY ");
51 f.write_node(&display::comma_separated(&self.order_by));
52 }
53
54 let write_offset = |f: &mut AstFormatter<W>| {
55 if let Some(offset) = &self.offset {
56 f.write_str(" OFFSET ");
57 f.write_node(offset);
58 }
59 };
60
61 if let Some(limit) = &self.limit {
62 if limit.with_ties {
63 write_offset(f);
64 f.write_str(" FETCH FIRST ");
65 f.write_node(&limit.quantity);
66 f.write_str(" ROWS WITH TIES");
67 } else {
68 f.write_str(" LIMIT ");
69 f.write_node(&limit.quantity);
70 write_offset(f);
71 }
72 } else {
73 write_offset(f);
74 }
75 }
76}
77impl_display_t!(Query);
78
79impl<T: AstInfo> Query<T> {
80 pub fn select(select: Select<T>) -> Query<T> {
81 Query {
82 ctes: CteBlock::empty(),
83 body: SetExpr::Select(Box::new(select)),
84 order_by: vec![],
85 limit: None,
86 offset: None,
87 }
88 }
89
90 pub fn query(query: Query<T>) -> Query<T> {
91 Query {
92 ctes: CteBlock::empty(),
93 body: SetExpr::Query(Box::new(query)),
94 order_by: vec![],
95 limit: None,
96 offset: None,
97 }
98 }
99
100 pub fn take(&mut self) -> Query<T> {
101 mem::replace(
102 self,
103 Query::<T> {
104 ctes: CteBlock::empty(),
105 order_by: vec![],
106 body: SetExpr::Values(Values(vec![])),
107 limit: None,
108 offset: None,
109 },
110 )
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
117pub enum SetExpr<T: AstInfo> {
118 Select(Box<Select<T>>),
120 Query(Box<Query<T>>),
123 SetOperation {
125 op: SetOperator,
126 all: bool,
127 left: Box<SetExpr<T>>,
128 right: Box<SetExpr<T>>,
129 },
130 Values(Values<T>),
131 Show(ShowStatement<T>),
132 Table(T::ItemName),
133}
134
135impl<T: AstInfo> AstDisplay for SetExpr<T> {
136 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
137 match self {
138 SetExpr::Select(s) => f.write_node(s),
139 SetExpr::Query(q) => {
140 f.write_str("(");
141 f.write_node(q);
142 f.write_str(")")
143 }
144 SetExpr::Values(v) => f.write_node(v),
145 SetExpr::Show(v) => f.write_node(v),
146 SetExpr::Table(t) => {
147 f.write_str("TABLE ");
148 f.write_node(t)
149 }
150 SetExpr::SetOperation {
151 left,
152 right,
153 op,
154 all,
155 } => {
156 f.write_node(left);
157 f.write_str(" ");
158 f.write_node(op);
159 f.write_str(" ");
160 if *all {
161 f.write_str("ALL ");
162 }
163 f.write_node(right);
164 }
165 }
166 }
167}
168impl_display_t!(SetExpr);
169
170#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
171pub enum SetOperator {
172 Union,
173 Except,
174 Intersect,
175}
176
177impl AstDisplay for SetOperator {
178 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
179 f.write_str(match self {
180 SetOperator::Union => "UNION",
181 SetOperator::Except => "EXCEPT",
182 SetOperator::Intersect => "INTERSECT",
183 })
184 }
185}
186impl_display!(SetOperator);
187
188#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
189pub enum SelectOptionName {
190 ExpectedGroupSize,
191 AggregateInputGroupSize,
192 DistinctOnInputGroupSize,
193 LimitInputGroupSize,
194}
195
196impl AstDisplay for SelectOptionName {
197 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
198 f.write_str(match self {
199 SelectOptionName::ExpectedGroupSize => "EXPECTED GROUP SIZE",
200 SelectOptionName::AggregateInputGroupSize => "AGGREGATE INPUT GROUP SIZE",
201 SelectOptionName::DistinctOnInputGroupSize => "DISTINCT ON INPUT GROUP SIZE",
202 SelectOptionName::LimitInputGroupSize => "LIMIT INPUT GROUP SIZE",
203 })
204 }
205}
206impl_display!(SelectOptionName);
207
208impl WithOptionName for SelectOptionName {
209 fn redact_value(&self) -> bool {
215 match self {
216 SelectOptionName::ExpectedGroupSize
217 | SelectOptionName::AggregateInputGroupSize
218 | SelectOptionName::DistinctOnInputGroupSize
219 | SelectOptionName::LimitInputGroupSize => false,
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
225pub struct SelectOption<T: AstInfo> {
226 pub name: SelectOptionName,
227 pub value: Option<WithOptionValue<T>>,
228}
229impl_display_for_with_option!(SelectOption);
230
231#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
235pub struct Select<T: AstInfo> {
236 pub distinct: Option<Distinct<T>>,
237 pub projection: Vec<SelectItem<T>>,
239 pub from: Vec<TableWithJoins<T>>,
241 pub selection: Option<Expr<T>>,
243 pub group_by: Vec<Expr<T>>,
245 pub having: Option<Expr<T>>,
247 pub qualify: Option<Expr<T>>,
249 pub options: Vec<SelectOption<T>>,
251}
252
253impl<T: AstInfo> AstDisplay for Select<T> {
254 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
255 f.write_str("SELECT");
256 if let Some(distinct) = &self.distinct {
257 f.write_str(" ");
258 f.write_node(distinct);
259 }
260 if !self.projection.is_empty() {
261 f.write_str(" ");
262 f.write_node(&display::comma_separated(&self.projection));
263 }
264 if !self.from.is_empty() {
265 f.write_str(" FROM ");
266 f.write_node(&display::comma_separated(&self.from));
267 }
268 if let Some(ref selection) = self.selection {
269 f.write_str(" WHERE ");
270 f.write_node(selection);
271 }
272 if !self.group_by.is_empty() {
273 f.write_str(" GROUP BY ");
274 f.write_node(&display::comma_separated(&self.group_by));
275 }
276 if let Some(ref having) = self.having {
277 f.write_str(" HAVING ");
278 f.write_node(having);
279 }
280 if let Some(ref qualify) = self.qualify {
281 f.write_str(" QUALIFY ");
282 f.write_node(qualify);
283 }
284 if !self.options.is_empty() {
285 f.write_str(" OPTIONS (");
286 f.write_node(&display::comma_separated(&self.options));
287 f.write_str(")");
288 }
289 }
290}
291impl_display_t!(Select);
292
293impl<T: AstInfo> Select<T> {
294 pub fn from(mut self, twj: TableWithJoins<T>) -> Select<T> {
295 self.from.push(twj);
296 self
297 }
298
299 pub fn project(mut self, select_item: SelectItem<T>) -> Select<T> {
300 self.projection.push(select_item);
301 self
302 }
303
304 pub fn selection(mut self, selection: Option<Expr<T>>) -> Select<T> {
305 self.selection = selection;
306 self
307 }
308}
309
310#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
311pub enum Distinct<T: AstInfo> {
312 EntireRow,
313 On(Vec<Expr<T>>),
314}
315impl_display_t!(Distinct);
316
317impl<T: AstInfo> AstDisplay for Distinct<T> {
318 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
319 match self {
320 Distinct::EntireRow => f.write_str("DISTINCT"),
321 Distinct::On(cols) => {
322 f.write_str("DISTINCT ON (");
323 f.write_node(&display::comma_separated(cols));
324 f.write_str(")");
325 }
326 }
327 }
328}
329
330#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
336pub enum CteBlock<T: AstInfo> {
337 Simple(Vec<Cte<T>>),
338 MutuallyRecursive(MutRecBlock<T>),
339}
340
341#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
342pub struct MutRecBlock<T: AstInfo> {
343 pub options: Vec<MutRecBlockOption<T>>,
344 pub ctes: Vec<CteMutRec<T>>,
345}
346
347impl<T: AstInfo> CteBlock<T> {
348 pub fn empty() -> Self {
350 CteBlock::Simple(Vec::new())
351 }
352 pub fn is_empty(&self) -> bool {
354 match self {
355 CteBlock::Simple(list) => list.is_empty(),
356 CteBlock::MutuallyRecursive(list) => list.ctes.is_empty(),
357 }
358 }
359 pub fn bound_identifiers(&self) -> impl Iterator<Item = &Ident> {
361 let mut names = Vec::new();
362 match self {
363 CteBlock::Simple(list) => {
364 for cte in list.iter() {
365 names.push(&cte.alias.name);
366 }
367 }
368 CteBlock::MutuallyRecursive(MutRecBlock { options: _, ctes }) => {
369 for cte in ctes.iter() {
370 names.push(&cte.name);
371 }
372 }
373 }
374 names.into_iter()
375 }
376}
377
378impl<T: AstInfo> AstDisplay for CteBlock<T> {
379 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
380 if !self.is_empty() {
381 match self {
382 CteBlock::Simple(list) => {
383 f.write_str("WITH ");
384 f.write_node(&display::comma_separated(list));
385 }
386 CteBlock::MutuallyRecursive(MutRecBlock { options, ctes }) => {
387 f.write_str("WITH MUTUALLY RECURSIVE ");
388 if !options.is_empty() {
389 f.write_str("(");
390 f.write_node(&display::comma_separated(options));
391 f.write_str(") ");
392 }
393 f.write_node(&display::comma_separated(ctes));
394 }
395 }
396 f.write_str(" ");
397 }
398 }
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
406pub struct Cte<T: AstInfo> {
407 pub alias: TableAlias,
408 pub id: T::CteId,
409 pub query: Query<T>,
410}
411
412impl<T: AstInfo> AstDisplay for Cte<T> {
413 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
414 f.write_node(&self.alias);
415 f.write_str(" AS (");
416 f.write_node(&self.query);
417 f.write_str(")");
418 }
419}
420impl_display_t!(Cte);
421
422#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
423pub struct CteMutRec<T: AstInfo> {
424 pub name: Ident,
425 pub columns: Vec<CteMutRecColumnDef<T>>,
426 pub id: T::CteId,
427 pub query: Query<T>,
428}
429
430impl<T: AstInfo> AstDisplay for CteMutRec<T> {
431 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
432 f.write_node(&self.name);
433 if !self.columns.is_empty() {
434 f.write_str(" (");
435 f.write_node(&display::comma_separated(&self.columns));
436 f.write_str(")");
437 }
438 f.write_str(" AS (");
439 f.write_node(&self.query);
440 f.write_str(")");
441 }
442}
443impl_display_t!(CteMutRec);
444
445#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
447pub struct CteMutRecColumnDef<T: AstInfo> {
448 pub name: Ident,
449 pub data_type: T::DataType,
450}
451
452impl<T: AstInfo> AstDisplay for CteMutRecColumnDef<T> {
453 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
454 f.write_node(&self.name);
455 f.write_str(" ");
456 f.write_node(&self.data_type);
457 }
458}
459impl_display_t!(CteMutRecColumnDef);
460
461#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
462pub enum MutRecBlockOptionName {
463 RecursionLimit,
464 ErrorAtRecursionLimit,
465 ReturnAtRecursionLimit,
466}
467
468impl AstDisplay for MutRecBlockOptionName {
469 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
470 f.write_str(match self {
471 MutRecBlockOptionName::RecursionLimit => "RECURSION LIMIT",
472 MutRecBlockOptionName::ErrorAtRecursionLimit => "ERROR AT RECURSION LIMIT",
473 MutRecBlockOptionName::ReturnAtRecursionLimit => "RETURN AT RECURSION LIMIT",
474 })
475 }
476}
477impl_display!(MutRecBlockOptionName);
478
479impl WithOptionName for MutRecBlockOptionName {
480 fn redact_value(&self) -> bool {
486 match self {
487 MutRecBlockOptionName::RecursionLimit
488 | MutRecBlockOptionName::ErrorAtRecursionLimit
489 | MutRecBlockOptionName::ReturnAtRecursionLimit => false,
490 }
491 }
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
495pub struct MutRecBlockOption<T: AstInfo> {
496 pub name: MutRecBlockOptionName,
497 pub value: Option<WithOptionValue<T>>,
498}
499impl_display_for_with_option!(MutRecBlockOption);
500
501#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
503pub enum SelectItem<T: AstInfo> {
504 Expr { expr: Expr<T>, alias: Option<Ident> },
506 Wildcard,
508}
509
510impl<T: AstInfo> AstDisplay for SelectItem<T> {
511 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
512 match &self {
513 SelectItem::Expr { expr, alias } => {
514 f.write_node(expr);
515 if let Some(alias) = alias {
516 f.write_str(" AS ");
517 f.write_node(alias);
518 }
519 }
520 SelectItem::Wildcard => f.write_str("*"),
521 }
522 }
523}
524impl_display_t!(SelectItem);
525
526#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
527pub struct TableWithJoins<T: AstInfo> {
528 pub relation: TableFactor<T>,
529 pub joins: Vec<Join<T>>,
530}
531
532impl<T: AstInfo> AstDisplay for TableWithJoins<T> {
533 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
534 f.write_node(&self.relation);
535 for join in &self.joins {
536 f.write_node(join)
537 }
538 }
539}
540impl_display_t!(TableWithJoins);
541
542impl<T: AstInfo> TableWithJoins<T> {
543 pub fn subquery(query: Query<T>, alias: TableAlias) -> TableWithJoins<T> {
544 TableWithJoins {
545 relation: TableFactor::Derived {
546 lateral: false,
547 subquery: Box::new(query),
548 alias: Some(alias),
549 },
550 joins: vec![],
551 }
552 }
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
557pub enum TableFactor<T: AstInfo> {
558 Table {
559 name: T::ItemName,
560 alias: Option<TableAlias>,
561 },
562 Function {
563 function: Function<T>,
564 alias: Option<TableAlias>,
565 with_ordinality: bool,
566 },
567 RowsFrom {
568 functions: Vec<Function<T>>,
569 alias: Option<TableAlias>,
570 with_ordinality: bool,
571 },
572 Derived {
573 lateral: bool,
574 subquery: Box<Query<T>>,
575 alias: Option<TableAlias>,
576 },
577 NestedJoin {
582 join: Box<TableWithJoins<T>>,
583 alias: Option<TableAlias>,
584 },
585}
586
587impl<T: AstInfo> AstDisplay for TableFactor<T> {
588 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
589 match self {
590 TableFactor::Table { name, alias } => {
591 f.write_node(name);
592 if let Some(alias) = alias {
593 f.write_str(" AS ");
594 f.write_node(alias);
595 }
596 }
597 TableFactor::Function {
598 function,
599 alias,
600 with_ordinality,
601 } => {
602 f.write_node(function);
603 if let Some(alias) = &alias {
604 f.write_str(" AS ");
605 f.write_node(alias);
606 }
607 if *with_ordinality {
608 f.write_str(" WITH ORDINALITY");
609 }
610 }
611 TableFactor::RowsFrom {
612 functions,
613 alias,
614 with_ordinality,
615 } => {
616 f.write_str("ROWS FROM (");
617 f.write_node(&display::comma_separated(functions));
618 f.write_str(")");
619 if let Some(alias) = alias {
620 f.write_str(" AS ");
621 f.write_node(alias);
622 }
623 if *with_ordinality {
624 f.write_str(" WITH ORDINALITY");
625 }
626 }
627 TableFactor::Derived {
628 lateral,
629 subquery,
630 alias,
631 } => {
632 if *lateral {
633 f.write_str("LATERAL ");
634 }
635 f.write_str("(");
636 f.write_node(subquery);
637 f.write_str(")");
638 if let Some(alias) = alias {
639 f.write_str(" AS ");
640 f.write_node(alias);
641 }
642 }
643 TableFactor::NestedJoin { join, alias } => {
644 f.write_str("(");
645 f.write_node(join);
646 f.write_str(")");
647 if let Some(alias) = alias {
648 f.write_str(" AS ");
649 f.write_node(alias);
650 }
651 }
652 }
653 }
654}
655impl_display_t!(TableFactor);
656
657#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
658pub struct TableAlias {
659 pub name: Ident,
660 pub columns: Vec<Ident>,
661 pub strict: bool,
667}
668
669impl AstDisplay for TableAlias {
670 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
671 f.write_node(&self.name);
672 if !self.columns.is_empty() {
673 f.write_str(" (");
674 f.write_node(&display::comma_separated(&self.columns));
675 f.write_str(")");
676 }
677 }
678}
679impl_display!(TableAlias);
680
681#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
682pub struct Join<T: AstInfo> {
683 pub relation: TableFactor<T>,
684 pub join_operator: JoinOperator<T>,
685}
686
687impl<T: AstInfo> AstDisplay for Join<T> {
688 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
689 fn prefix<T: AstInfo>(constraint: &JoinConstraint<T>) -> &'static str {
690 match constraint {
691 JoinConstraint::Natural => "NATURAL ",
692 _ => "",
693 }
694 }
695 fn suffix<'a, T: AstInfo>(constraint: &'a JoinConstraint<T>) -> impl AstDisplay + 'a {
696 struct Suffix<'a, T: AstInfo>(&'a JoinConstraint<T>);
697 impl<'a, T: AstInfo> AstDisplay for Suffix<'a, T> {
698 fn fmt<W>(&self, f: &mut AstFormatter<W>)
699 where
700 W: fmt::Write,
701 {
702 match self.0 {
703 JoinConstraint::On(expr) => {
704 f.write_str(" ON ");
705 f.write_node(expr);
706 }
707 JoinConstraint::Using { columns, alias } => {
708 f.write_str(" USING (");
709 f.write_node(&display::comma_separated(columns));
710 f.write_str(")");
711
712 if let Some(join_using_alias) = alias {
713 f.write_str(" AS ");
714 f.write_node(join_using_alias);
715 }
716 }
717 _ => {}
718 }
719 }
720 }
721 Suffix(constraint)
722 }
723 match &self.join_operator {
724 JoinOperator::Inner(constraint) => {
725 f.write_str(" ");
726 f.write_str(prefix(constraint));
727 f.write_str("JOIN ");
728 f.write_node(&self.relation);
729 f.write_node(&suffix(constraint));
730 }
731 JoinOperator::LeftOuter(constraint) => {
732 f.write_str(" ");
733 f.write_str(prefix(constraint));
734 f.write_str("LEFT JOIN ");
735 f.write_node(&self.relation);
736 f.write_node(&suffix(constraint));
737 }
738 JoinOperator::RightOuter(constraint) => {
739 f.write_str(" ");
740 f.write_str(prefix(constraint));
741 f.write_str("RIGHT JOIN ");
742 f.write_node(&self.relation);
743 f.write_node(&suffix(constraint));
744 }
745 JoinOperator::FullOuter(constraint) => {
746 f.write_str(" ");
747 f.write_str(prefix(constraint));
748 f.write_str("FULL JOIN ");
749 f.write_node(&self.relation);
750 f.write_node(&suffix(constraint));
751 }
752 JoinOperator::CrossJoin => {
753 f.write_str(" CROSS JOIN ");
754 f.write_node(&self.relation);
755 }
756 }
757 }
758}
759impl_display_t!(Join);
760
761#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
762pub enum JoinOperator<T: AstInfo> {
763 Inner(JoinConstraint<T>),
764 LeftOuter(JoinConstraint<T>),
765 RightOuter(JoinConstraint<T>),
766 FullOuter(JoinConstraint<T>),
767 CrossJoin,
768}
769
770#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
771pub enum JoinConstraint<T: AstInfo> {
772 On(Expr<T>),
773 Using {
774 columns: Vec<Ident>,
775 alias: Option<Ident>,
776 },
777 Natural,
778}
779
780#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
782pub struct OrderByExpr<T: AstInfo> {
783 pub expr: Expr<T>,
784 pub asc: Option<bool>,
785 pub nulls_last: Option<bool>,
786}
787
788impl<T: AstInfo> AstDisplay for OrderByExpr<T> {
789 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
790 f.write_node(&self.expr);
791 match self.asc {
792 Some(true) => f.write_str(" ASC"),
793 Some(false) => f.write_str(" DESC"),
794 None => {}
795 }
796 match self.nulls_last {
797 Some(true) => f.write_str(" NULLS LAST"),
798 Some(false) => f.write_str(" NULLS FIRST"),
799 None => {}
800 }
801 }
802}
803impl_display_t!(OrderByExpr);
804
805#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
806pub struct Limit<T: AstInfo> {
807 pub with_ties: bool,
808 pub quantity: Expr<T>,
809}
810
811#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
812pub struct Values<T: AstInfo>(pub Vec<Vec<Expr<T>>>);
813
814impl<T: AstInfo> AstDisplay for Values<T> {
815 fn fmt<W: fmt::Write>(&self, f: &mut AstFormatter<W>) {
816 f.write_str("VALUES ");
817 let mut delim = "";
818
819 for (i, row) in self.0.iter().enumerate() {
820 if f.redacted() && i == 20 {
821 f.write_str("/* ");
822 f.write_str(&(self.0.len().saturating_sub(20)).to_string());
823 f.write_str(" more rows */");
824 break;
825 }
826
827 f.write_str(delim);
828 delim = ", ";
829 f.write_str("(");
830 f.write_node(&display::comma_separated(row));
831 f.write_str(")");
832 }
833 }
834}
835impl_display_t!(Values);