sqltk_parser/ast/
ddl.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! AST types specific to CREATE/ALTER variants of [`Statement`](crate::ast::Statement)
19//! (commonly referred to as Data Definition Language, or DDL)
20
21#[cfg(not(feature = "std"))]
22use alloc::{boxed::Box, string::String, vec::Vec};
23use core::fmt::{self, Write};
24
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28#[cfg(feature = "visitor")]
29use sqltk_parser_derive::{Visit, VisitMut};
30
31use crate::ast::value::escape_single_quote_string;
32use crate::ast::{
33    display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition,
34    ObjectName, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value,
35};
36use crate::keywords::Keyword;
37use crate::tokenizer::Token;
38
39/// An `ALTER TABLE` (`Statement::AlterTable`) operation
40#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
41#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
42#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
43pub enum AlterTableOperation {
44    /// `ADD <table_constraint>`
45    AddConstraint(TableConstraint),
46    /// `ADD [COLUMN] [IF NOT EXISTS] <column_def>`
47    AddColumn {
48        /// `[COLUMN]`.
49        column_keyword: bool,
50        /// `[IF NOT EXISTS]`
51        if_not_exists: bool,
52        /// <column_def>.
53        column_def: ColumnDef,
54        /// MySQL `ALTER TABLE` only  [FIRST | AFTER column_name]
55        column_position: Option<MySQLColumnPosition>,
56    },
57    /// `ADD PROJECTION [IF NOT EXISTS] name ( SELECT <COLUMN LIST EXPR> [GROUP BY] [ORDER BY])`
58    ///
59    /// Note: this is a ClickHouse-specific operation.
60    /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#add-projection)
61    AddProjection {
62        if_not_exists: bool,
63        name: Ident,
64        select: ProjectionSelect,
65    },
66
67    /// `DROP PROJECTION [IF EXISTS] name`
68    ///
69    /// Note: this is a ClickHouse-specific operation.
70    /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#drop-projection)
71    DropProjection { if_exists: bool, name: Ident },
72
73    /// `MATERIALIZE PROJECTION [IF EXISTS] name [IN PARTITION partition_name]`
74    ///
75    ///  Note: this is a ClickHouse-specific operation.
76    /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#materialize-projection)
77    MaterializeProjection {
78        if_exists: bool,
79        name: Ident,
80        partition: Option<Ident>,
81    },
82
83    /// `CLEAR PROJECTION [IF EXISTS] name [IN PARTITION partition_name]`
84    ///
85    /// Note: this is a ClickHouse-specific operation.
86    /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#clear-projection)
87    ClearProjection {
88        if_exists: bool,
89        name: Ident,
90        partition: Option<Ident>,
91    },
92
93    /// `DISABLE ROW LEVEL SECURITY`
94    ///
95    /// Note: this is a PostgreSQL-specific operation.
96    DisableRowLevelSecurity,
97    /// `DISABLE RULE rewrite_rule_name`
98    ///
99    /// Note: this is a PostgreSQL-specific operation.
100    DisableRule { name: Ident },
101    /// `DISABLE TRIGGER [ trigger_name | ALL | USER ]`
102    ///
103    /// Note: this is a PostgreSQL-specific operation.
104    DisableTrigger { name: Ident },
105    /// `DROP CONSTRAINT [ IF EXISTS ] <name>`
106    DropConstraint {
107        if_exists: bool,
108        name: Ident,
109        cascade: bool,
110    },
111    /// `DROP [ COLUMN ] [ IF EXISTS ] <column_name> [ CASCADE ]`
112    DropColumn {
113        column_name: Ident,
114        if_exists: bool,
115        cascade: bool,
116    },
117    /// `ATTACH PART|PARTITION <partition_expr>`
118    /// Note: this is a ClickHouse-specific operation, please refer to
119    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/pakrtition#attach-partitionpart)
120    AttachPartition {
121        // PART is not a short form of PARTITION, it's a separate keyword
122        // which represents a physical file on disk and partition is a logical entity.
123        partition: Partition,
124    },
125    /// `DETACH PART|PARTITION <partition_expr>`
126    /// Note: this is a ClickHouse-specific operation, please refer to
127    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#detach-partitionpart)
128    DetachPartition {
129        // See `AttachPartition` for more details
130        partition: Partition,
131    },
132    /// `FREEZE PARTITION <partition_expr>`
133    /// Note: this is a ClickHouse-specific operation, please refer to
134    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#freeze-partition)
135    FreezePartition {
136        partition: Partition,
137        with_name: Option<Ident>,
138    },
139    /// `UNFREEZE PARTITION <partition_expr>`
140    /// Note: this is a ClickHouse-specific operation, please refer to
141    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#unfreeze-partition)
142    UnfreezePartition {
143        partition: Partition,
144        with_name: Option<Ident>,
145    },
146    /// `DROP PRIMARY KEY`
147    ///
148    /// Note: this is a MySQL-specific operation.
149    DropPrimaryKey,
150    /// `ENABLE ALWAYS RULE rewrite_rule_name`
151    ///
152    /// Note: this is a PostgreSQL-specific operation.
153    EnableAlwaysRule { name: Ident },
154    /// `ENABLE ALWAYS TRIGGER trigger_name`
155    ///
156    /// Note: this is a PostgreSQL-specific operation.
157    EnableAlwaysTrigger { name: Ident },
158    /// `ENABLE REPLICA RULE rewrite_rule_name`
159    ///
160    /// Note: this is a PostgreSQL-specific operation.
161    EnableReplicaRule { name: Ident },
162    /// `ENABLE REPLICA TRIGGER trigger_name`
163    ///
164    /// Note: this is a PostgreSQL-specific operation.
165    EnableReplicaTrigger { name: Ident },
166    /// `ENABLE ROW LEVEL SECURITY`
167    ///
168    /// Note: this is a PostgreSQL-specific operation.
169    EnableRowLevelSecurity,
170    /// `ENABLE RULE rewrite_rule_name`
171    ///
172    /// Note: this is a PostgreSQL-specific operation.
173    EnableRule { name: Ident },
174    /// `ENABLE TRIGGER [ trigger_name | ALL | USER ]`
175    ///
176    /// Note: this is a PostgreSQL-specific operation.
177    EnableTrigger { name: Ident },
178    /// `RENAME TO PARTITION (partition=val)`
179    RenamePartitions {
180        old_partitions: Vec<Expr>,
181        new_partitions: Vec<Expr>,
182    },
183    /// Add Partitions
184    AddPartitions {
185        if_not_exists: bool,
186        new_partitions: Vec<Partition>,
187    },
188    DropPartitions {
189        partitions: Vec<Expr>,
190        if_exists: bool,
191    },
192    /// `RENAME [ COLUMN ] <old_column_name> TO <new_column_name>`
193    RenameColumn {
194        old_column_name: Ident,
195        new_column_name: Ident,
196    },
197    /// `RENAME TO <table_name>`
198    RenameTable { table_name: ObjectName },
199    // CHANGE [ COLUMN ] <old_name> <new_name> <data_type> [ <options> ]
200    ChangeColumn {
201        old_name: Ident,
202        new_name: Ident,
203        data_type: DataType,
204        options: Vec<ColumnOption>,
205        /// MySQL `ALTER TABLE` only  [FIRST | AFTER column_name]
206        column_position: Option<MySQLColumnPosition>,
207    },
208    // CHANGE [ COLUMN ] <col_name> <data_type> [ <options> ]
209    ModifyColumn {
210        col_name: Ident,
211        data_type: DataType,
212        options: Vec<ColumnOption>,
213        /// MySQL `ALTER TABLE` only  [FIRST | AFTER column_name]
214        column_position: Option<MySQLColumnPosition>,
215    },
216    /// `RENAME CONSTRAINT <old_constraint_name> TO <new_constraint_name>`
217    ///
218    /// Note: this is a PostgreSQL-specific operation.
219    RenameConstraint { old_name: Ident, new_name: Ident },
220    /// `ALTER [ COLUMN ]`
221    AlterColumn {
222        column_name: Ident,
223        op: AlterColumnOperation,
224    },
225    /// 'SWAP WITH <table_name>'
226    ///
227    /// Note: this is Snowflake specific <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
228    SwapWith { table_name: ObjectName },
229    /// 'SET TBLPROPERTIES ( { property_key [ = ] property_val } [, ...] )'
230    SetTblProperties { table_properties: Vec<SqlOption> },
231
232    /// `OWNER TO { <new_owner> | CURRENT_ROLE | CURRENT_USER | SESSION_USER }`
233    ///
234    /// Note: this is PostgreSQL-specific <https://www.postgresql.org/docs/current/sql-altertable.html>
235    OwnerTo { new_owner: Owner },
236}
237
238/// An `ALTER Policy` (`Statement::AlterPolicy`) operation
239///
240/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-altertable.html)
241#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
242#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
243#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
244pub enum AlterPolicyOperation {
245    Rename {
246        new_name: Ident,
247    },
248    Apply {
249        to: Option<Vec<Owner>>,
250        using: Option<Expr>,
251        with_check: Option<Expr>,
252    },
253}
254
255impl fmt::Display for AlterPolicyOperation {
256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
257        match self {
258            AlterPolicyOperation::Rename { new_name } => {
259                write!(f, " RENAME TO {new_name}")
260            }
261            AlterPolicyOperation::Apply {
262                to,
263                using,
264                with_check,
265            } => {
266                if let Some(to) = to {
267                    write!(f, " TO {}", display_comma_separated(to))?;
268                }
269                if let Some(using) = using {
270                    write!(f, " USING ({using})")?;
271                }
272                if let Some(with_check) = with_check {
273                    write!(f, " WITH CHECK ({with_check})")?;
274                }
275                Ok(())
276            }
277        }
278    }
279}
280
281#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
282#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
283#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
284pub enum Owner {
285    Ident(Ident),
286    CurrentRole,
287    CurrentUser,
288    SessionUser,
289}
290
291impl fmt::Display for Owner {
292    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
293        match self {
294            Owner::Ident(ident) => write!(f, "{}", ident),
295            Owner::CurrentRole => write!(f, "CURRENT_ROLE"),
296            Owner::CurrentUser => write!(f, "CURRENT_USER"),
297            Owner::SessionUser => write!(f, "SESSION_USER"),
298        }
299    }
300}
301
302#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
303#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
304#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
305pub enum AlterIndexOperation {
306    RenameIndex { index_name: ObjectName },
307}
308
309impl fmt::Display for AlterTableOperation {
310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311        match self {
312            AlterTableOperation::AddPartitions {
313                if_not_exists,
314                new_partitions,
315            } => write!(
316                f,
317                "ADD{ine} {}",
318                display_separated(new_partitions, " "),
319                ine = if *if_not_exists { " IF NOT EXISTS" } else { "" }
320            ),
321            AlterTableOperation::AddConstraint(c) => write!(f, "ADD {c}"),
322            AlterTableOperation::AddColumn {
323                column_keyword,
324                if_not_exists,
325                column_def,
326                column_position,
327            } => {
328                write!(f, "ADD")?;
329                if *column_keyword {
330                    write!(f, " COLUMN")?;
331                }
332                if *if_not_exists {
333                    write!(f, " IF NOT EXISTS")?;
334                }
335                write!(f, " {column_def}")?;
336
337                if let Some(position) = column_position {
338                    write!(f, " {position}")?;
339                }
340
341                Ok(())
342            }
343            AlterTableOperation::AddProjection {
344                if_not_exists,
345                name,
346                select: query,
347            } => {
348                write!(f, "ADD PROJECTION")?;
349                if *if_not_exists {
350                    write!(f, " IF NOT EXISTS")?;
351                }
352                write!(f, " {} ({})", name, query)
353            }
354            AlterTableOperation::DropProjection { if_exists, name } => {
355                write!(f, "DROP PROJECTION")?;
356                if *if_exists {
357                    write!(f, " IF EXISTS")?;
358                }
359                write!(f, " {}", name)
360            }
361            AlterTableOperation::MaterializeProjection {
362                if_exists,
363                name,
364                partition,
365            } => {
366                write!(f, "MATERIALIZE PROJECTION")?;
367                if *if_exists {
368                    write!(f, " IF EXISTS")?;
369                }
370                write!(f, " {}", name)?;
371                if let Some(partition) = partition {
372                    write!(f, " IN PARTITION {}", partition)?;
373                }
374                Ok(())
375            }
376            AlterTableOperation::ClearProjection {
377                if_exists,
378                name,
379                partition,
380            } => {
381                write!(f, "CLEAR PROJECTION")?;
382                if *if_exists {
383                    write!(f, " IF EXISTS")?;
384                }
385                write!(f, " {}", name)?;
386                if let Some(partition) = partition {
387                    write!(f, " IN PARTITION {}", partition)?;
388                }
389                Ok(())
390            }
391            AlterTableOperation::AlterColumn { column_name, op } => {
392                write!(f, "ALTER COLUMN {column_name} {op}")
393            }
394            AlterTableOperation::DisableRowLevelSecurity => {
395                write!(f, "DISABLE ROW LEVEL SECURITY")
396            }
397            AlterTableOperation::DisableRule { name } => {
398                write!(f, "DISABLE RULE {name}")
399            }
400            AlterTableOperation::DisableTrigger { name } => {
401                write!(f, "DISABLE TRIGGER {name}")
402            }
403            AlterTableOperation::DropPartitions {
404                partitions,
405                if_exists,
406            } => write!(
407                f,
408                "DROP{ie} PARTITION ({})",
409                display_comma_separated(partitions),
410                ie = if *if_exists { " IF EXISTS" } else { "" }
411            ),
412            AlterTableOperation::DropConstraint {
413                if_exists,
414                name,
415                cascade,
416            } => {
417                write!(
418                    f,
419                    "DROP CONSTRAINT {}{}{}",
420                    if *if_exists { "IF EXISTS " } else { "" },
421                    name,
422                    if *cascade { " CASCADE" } else { "" },
423                )
424            }
425            AlterTableOperation::DropPrimaryKey => write!(f, "DROP PRIMARY KEY"),
426            AlterTableOperation::DropColumn {
427                column_name,
428                if_exists,
429                cascade,
430            } => write!(
431                f,
432                "DROP COLUMN {}{}{}",
433                if *if_exists { "IF EXISTS " } else { "" },
434                column_name,
435                if *cascade { " CASCADE" } else { "" }
436            ),
437            AlterTableOperation::AttachPartition { partition } => {
438                write!(f, "ATTACH {partition}")
439            }
440            AlterTableOperation::DetachPartition { partition } => {
441                write!(f, "DETACH {partition}")
442            }
443            AlterTableOperation::EnableAlwaysRule { name } => {
444                write!(f, "ENABLE ALWAYS RULE {name}")
445            }
446            AlterTableOperation::EnableAlwaysTrigger { name } => {
447                write!(f, "ENABLE ALWAYS TRIGGER {name}")
448            }
449            AlterTableOperation::EnableReplicaRule { name } => {
450                write!(f, "ENABLE REPLICA RULE {name}")
451            }
452            AlterTableOperation::EnableReplicaTrigger { name } => {
453                write!(f, "ENABLE REPLICA TRIGGER {name}")
454            }
455            AlterTableOperation::EnableRowLevelSecurity => {
456                write!(f, "ENABLE ROW LEVEL SECURITY")
457            }
458            AlterTableOperation::EnableRule { name } => {
459                write!(f, "ENABLE RULE {name}")
460            }
461            AlterTableOperation::EnableTrigger { name } => {
462                write!(f, "ENABLE TRIGGER {name}")
463            }
464            AlterTableOperation::RenamePartitions {
465                old_partitions,
466                new_partitions,
467            } => write!(
468                f,
469                "PARTITION ({}) RENAME TO PARTITION ({})",
470                display_comma_separated(old_partitions),
471                display_comma_separated(new_partitions)
472            ),
473            AlterTableOperation::RenameColumn {
474                old_column_name,
475                new_column_name,
476            } => write!(f, "RENAME COLUMN {old_column_name} TO {new_column_name}"),
477            AlterTableOperation::RenameTable { table_name } => {
478                write!(f, "RENAME TO {table_name}")
479            }
480            AlterTableOperation::ChangeColumn {
481                old_name,
482                new_name,
483                data_type,
484                options,
485                column_position,
486            } => {
487                write!(f, "CHANGE COLUMN {old_name} {new_name} {data_type}")?;
488                if !options.is_empty() {
489                    write!(f, " {}", display_separated(options, " "))?;
490                }
491                if let Some(position) = column_position {
492                    write!(f, " {position}")?;
493                }
494
495                Ok(())
496            }
497            AlterTableOperation::ModifyColumn {
498                col_name,
499                data_type,
500                options,
501                column_position,
502            } => {
503                write!(f, "MODIFY COLUMN {col_name} {data_type}")?;
504                if !options.is_empty() {
505                    write!(f, " {}", display_separated(options, " "))?;
506                }
507                if let Some(position) = column_position {
508                    write!(f, " {position}")?;
509                }
510
511                Ok(())
512            }
513            AlterTableOperation::RenameConstraint { old_name, new_name } => {
514                write!(f, "RENAME CONSTRAINT {old_name} TO {new_name}")
515            }
516            AlterTableOperation::SwapWith { table_name } => {
517                write!(f, "SWAP WITH {table_name}")
518            }
519            AlterTableOperation::OwnerTo { new_owner } => {
520                write!(f, "OWNER TO {new_owner}")
521            }
522            AlterTableOperation::SetTblProperties { table_properties } => {
523                write!(
524                    f,
525                    "SET TBLPROPERTIES({})",
526                    display_comma_separated(table_properties)
527                )
528            }
529            AlterTableOperation::FreezePartition {
530                partition,
531                with_name,
532            } => {
533                write!(f, "FREEZE {partition}")?;
534                if let Some(name) = with_name {
535                    write!(f, " WITH NAME {name}")?;
536                }
537                Ok(())
538            }
539            AlterTableOperation::UnfreezePartition {
540                partition,
541                with_name,
542            } => {
543                write!(f, "UNFREEZE {partition}")?;
544                if let Some(name) = with_name {
545                    write!(f, " WITH NAME {name}")?;
546                }
547                Ok(())
548            }
549        }
550    }
551}
552
553impl fmt::Display for AlterIndexOperation {
554    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555        match self {
556            AlterIndexOperation::RenameIndex { index_name } => {
557                write!(f, "RENAME TO {index_name}")
558            }
559        }
560    }
561}
562
563/// An `ALTER COLUMN` (`Statement::AlterTable`) operation
564#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
565#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
566#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
567pub enum AlterColumnOperation {
568    /// `SET NOT NULL`
569    SetNotNull,
570    /// `DROP NOT NULL`
571    DropNotNull,
572    /// `SET DEFAULT <expr>`
573    SetDefault { value: Expr },
574    /// `DROP DEFAULT`
575    DropDefault,
576    /// `[SET DATA] TYPE <data_type> [USING <expr>]`
577    SetDataType {
578        data_type: DataType,
579        /// PostgreSQL specific
580        using: Option<Expr>,
581    },
582    /// `ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]`
583    ///
584    /// Note: this is a PostgreSQL-specific operation.
585    AddGenerated {
586        generated_as: Option<GeneratedAs>,
587        sequence_options: Option<Vec<SequenceOptions>>,
588    },
589}
590
591impl fmt::Display for AlterColumnOperation {
592    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
593        match self {
594            AlterColumnOperation::SetNotNull => write!(f, "SET NOT NULL",),
595            AlterColumnOperation::DropNotNull => write!(f, "DROP NOT NULL",),
596            AlterColumnOperation::SetDefault { value } => {
597                write!(f, "SET DEFAULT {value}")
598            }
599            AlterColumnOperation::DropDefault {} => {
600                write!(f, "DROP DEFAULT")
601            }
602            AlterColumnOperation::SetDataType { data_type, using } => {
603                if let Some(expr) = using {
604                    write!(f, "SET DATA TYPE {data_type} USING {expr}")
605                } else {
606                    write!(f, "SET DATA TYPE {data_type}")
607                }
608            }
609            AlterColumnOperation::AddGenerated {
610                generated_as,
611                sequence_options,
612            } => {
613                let generated_as = match generated_as {
614                    Some(GeneratedAs::Always) => " ALWAYS",
615                    Some(GeneratedAs::ByDefault) => " BY DEFAULT",
616                    _ => "",
617                };
618
619                write!(f, "ADD GENERATED{generated_as} AS IDENTITY",)?;
620                if let Some(options) = sequence_options {
621                    write!(f, " (")?;
622
623                    for sequence_option in options {
624                        write!(f, "{sequence_option}")?;
625                    }
626
627                    write!(f, " )")?;
628                }
629                Ok(())
630            }
631        }
632    }
633}
634
635/// A table-level constraint, specified in a `CREATE TABLE` or an
636/// `ALTER TABLE ADD <constraint>` statement.
637#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
638#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
639#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
640pub enum TableConstraint {
641    /// MySQL [definition][1] for `UNIQUE` constraints statements:\
642    /// * `[CONSTRAINT [<name>]] UNIQUE <index_type_display> [<index_name>] [index_type] (<columns>) <index_options>`
643    ///
644    /// where:
645    /// * [index_type][2] is `USING {BTREE | HASH}`
646    /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
647    /// * [index_type_display][4] is `[INDEX | KEY]`
648    ///
649    /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
650    /// [2]: IndexType
651    /// [3]: IndexOption
652    /// [4]: KeyOrIndexDisplay
653    Unique {
654        /// Constraint name.
655        ///
656        /// Can be not the same as `index_name`
657        name: Option<Ident>,
658        /// Index name
659        index_name: Option<Ident>,
660        /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
661        index_type_display: KeyOrIndexDisplay,
662        /// Optional `USING` of [index type][1] statement before columns.
663        ///
664        /// [1]: IndexType
665        index_type: Option<IndexType>,
666        /// Identifiers of the columns that are unique.
667        columns: Vec<Ident>,
668        index_options: Vec<IndexOption>,
669        characteristics: Option<ConstraintCharacteristics>,
670    },
671    /// MySQL [definition][1] for `PRIMARY KEY` constraints statements:\
672    /// * `[CONSTRAINT [<name>]] PRIMARY KEY [index_name] [index_type] (<columns>) <index_options>`
673    ///
674    /// Actually the specification have no `[index_name]` but the next query will complete successfully:
675    /// ```sql
676    /// CREATE TABLE unspec_table (
677    ///   xid INT NOT NULL,
678    ///   CONSTRAINT p_name PRIMARY KEY index_name USING BTREE (xid)
679    /// );
680    /// ```
681    ///
682    /// where:
683    /// * [index_type][2] is `USING {BTREE | HASH}`
684    /// * [index_options][3] is `{index_type | COMMENT 'string' | ... %currently unsupported stmts% } ...`
685    ///
686    /// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
687    /// [2]: IndexType
688    /// [3]: IndexOption
689    PrimaryKey {
690        /// Constraint name.
691        ///
692        /// Can be not the same as `index_name`
693        name: Option<Ident>,
694        /// Index name
695        index_name: Option<Ident>,
696        /// Optional `USING` of [index type][1] statement before columns.
697        ///
698        /// [1]: IndexType
699        index_type: Option<IndexType>,
700        /// Identifiers of the columns that form the primary key.
701        columns: Vec<Ident>,
702        index_options: Vec<IndexOption>,
703        characteristics: Option<ConstraintCharacteristics>,
704    },
705    /// A referential integrity constraint (`[ CONSTRAINT <name> ] FOREIGN KEY (<columns>)
706    /// REFERENCES <foreign_table> (<referred_columns>)
707    /// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
708    ///   [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
709    /// }`).
710    ForeignKey {
711        name: Option<Ident>,
712        columns: Vec<Ident>,
713        foreign_table: ObjectName,
714        referred_columns: Vec<Ident>,
715        on_delete: Option<ReferentialAction>,
716        on_update: Option<ReferentialAction>,
717        characteristics: Option<ConstraintCharacteristics>,
718    },
719    /// `[ CONSTRAINT <name> ] CHECK (<expr>)`
720    Check {
721        name: Option<Ident>,
722        expr: Box<Expr>,
723    },
724    /// MySQLs [index definition][1] for index creation. Not present on ANSI so, for now, the usage
725    /// is restricted to MySQL, as no other dialects that support this syntax were found.
726    ///
727    /// `{INDEX | KEY} [index_name] [index_type] (key_part,...) [index_option]...`
728    ///
729    /// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
730    Index {
731        /// Whether this index starts with KEY (true) or INDEX (false), to maintain the same syntax.
732        display_as_key: bool,
733        /// Index name.
734        name: Option<Ident>,
735        /// Optional [index type][1].
736        ///
737        /// [1]: IndexType
738        index_type: Option<IndexType>,
739        /// Referred column identifier list.
740        columns: Vec<Ident>,
741    },
742    /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same,
743    /// and MySQL displays both the same way, it is part of this definition as well.
744    ///
745    /// Supported syntax:
746    ///
747    /// ```markdown
748    /// {FULLTEXT | SPATIAL} [INDEX | KEY] [index_name] (key_part,...)
749    ///
750    /// key_part: col_name
751    /// ```
752    ///
753    /// [1]: https://dev.mysql.com/doc/refman/8.0/en/fulltext-natural-language.html
754    /// [2]: https://dev.mysql.com/doc/refman/8.0/en/spatial-types.html
755    FulltextOrSpatial {
756        /// Whether this is a `FULLTEXT` (true) or `SPATIAL` (false) definition.
757        fulltext: bool,
758        /// Whether the type is followed by the keyword `KEY`, `INDEX`, or no keyword at all.
759        index_type_display: KeyOrIndexDisplay,
760        /// Optional index name.
761        opt_index_name: Option<Ident>,
762        /// Referred column identifier list.
763        columns: Vec<Ident>,
764    },
765}
766
767impl fmt::Display for TableConstraint {
768    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
769        match self {
770            TableConstraint::Unique {
771                name,
772                index_name,
773                index_type_display,
774                index_type,
775                columns,
776                index_options,
777                characteristics,
778            } => {
779                write!(
780                    f,
781                    "{}UNIQUE{index_type_display:>}{}{} ({})",
782                    display_constraint_name(name),
783                    display_option_spaced(index_name),
784                    display_option(" USING ", "", index_type),
785                    display_comma_separated(columns),
786                )?;
787
788                if !index_options.is_empty() {
789                    write!(f, " {}", display_separated(index_options, " "))?;
790                }
791
792                write!(f, "{}", display_option_spaced(characteristics))?;
793                Ok(())
794            }
795            TableConstraint::PrimaryKey {
796                name,
797                index_name,
798                index_type,
799                columns,
800                index_options,
801                characteristics,
802            } => {
803                write!(
804                    f,
805                    "{}PRIMARY KEY{}{} ({})",
806                    display_constraint_name(name),
807                    display_option_spaced(index_name),
808                    display_option(" USING ", "", index_type),
809                    display_comma_separated(columns),
810                )?;
811
812                if !index_options.is_empty() {
813                    write!(f, " {}", display_separated(index_options, " "))?;
814                }
815
816                write!(f, "{}", display_option_spaced(characteristics))?;
817                Ok(())
818            }
819            TableConstraint::ForeignKey {
820                name,
821                columns,
822                foreign_table,
823                referred_columns,
824                on_delete,
825                on_update,
826                characteristics,
827            } => {
828                write!(
829                    f,
830                    "{}FOREIGN KEY ({}) REFERENCES {}({})",
831                    display_constraint_name(name),
832                    display_comma_separated(columns),
833                    foreign_table,
834                    display_comma_separated(referred_columns),
835                )?;
836                if let Some(action) = on_delete {
837                    write!(f, " ON DELETE {action}")?;
838                }
839                if let Some(action) = on_update {
840                    write!(f, " ON UPDATE {action}")?;
841                }
842                if let Some(characteristics) = characteristics {
843                    write!(f, " {}", characteristics)?;
844                }
845                Ok(())
846            }
847            TableConstraint::Check { name, expr } => {
848                write!(f, "{}CHECK ({})", display_constraint_name(name), expr)
849            }
850            TableConstraint::Index {
851                display_as_key,
852                name,
853                index_type,
854                columns,
855            } => {
856                write!(f, "{}", if *display_as_key { "KEY" } else { "INDEX" })?;
857                if let Some(name) = name {
858                    write!(f, " {name}")?;
859                }
860                if let Some(index_type) = index_type {
861                    write!(f, " USING {index_type}")?;
862                }
863                write!(f, " ({})", display_comma_separated(columns))?;
864
865                Ok(())
866            }
867            Self::FulltextOrSpatial {
868                fulltext,
869                index_type_display,
870                opt_index_name,
871                columns,
872            } => {
873                if *fulltext {
874                    write!(f, "FULLTEXT")?;
875                } else {
876                    write!(f, "SPATIAL")?;
877                }
878
879                write!(f, "{index_type_display:>}")?;
880
881                if let Some(name) = opt_index_name {
882                    write!(f, " {name}")?;
883                }
884
885                write!(f, " ({})", display_comma_separated(columns))?;
886
887                Ok(())
888            }
889        }
890    }
891}
892
893/// Representation whether a definition can can contains the KEY or INDEX keywords with the same
894/// meaning.
895///
896/// This enum initially is directed to `FULLTEXT`,`SPATIAL`, and `UNIQUE` indexes on create table
897/// statements of `MySQL` [(1)].
898///
899/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
900#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
901#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
902#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
903pub enum KeyOrIndexDisplay {
904    /// Nothing to display
905    None,
906    /// Display the KEY keyword
907    Key,
908    /// Display the INDEX keyword
909    Index,
910}
911
912impl KeyOrIndexDisplay {
913    pub fn is_none(self) -> bool {
914        matches!(self, Self::None)
915    }
916}
917
918impl fmt::Display for KeyOrIndexDisplay {
919    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
920        let left_space = matches!(f.align(), Some(fmt::Alignment::Right));
921
922        if left_space && !self.is_none() {
923            f.write_char(' ')?
924        }
925
926        match self {
927            KeyOrIndexDisplay::None => {
928                write!(f, "")
929            }
930            KeyOrIndexDisplay::Key => {
931                write!(f, "KEY")
932            }
933            KeyOrIndexDisplay::Index => {
934                write!(f, "INDEX")
935            }
936        }
937    }
938}
939
940/// Indexing method used by that index.
941///
942/// This structure isn't present on ANSI, but is found at least in [`MySQL` CREATE TABLE][1],
943/// [`MySQL` CREATE INDEX][2], and [Postgresql CREATE INDEX][3] statements.
944///
945/// [1]: https://dev.mysql.com/doc/refman/8.0/en/create-table.html
946/// [2]: https://dev.mysql.com/doc/refman/8.0/en/create-index.html
947/// [3]: https://www.postgresql.org/docs/14/sql-createindex.html
948#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
949#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
950#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
951pub enum IndexType {
952    BTree,
953    Hash,
954    // TODO add Postgresql's possible indexes
955}
956
957impl fmt::Display for IndexType {
958    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
959        match self {
960            Self::BTree => write!(f, "BTREE"),
961            Self::Hash => write!(f, "HASH"),
962        }
963    }
964}
965
966/// MySQLs index option.
967///
968/// This structure used here [`MySQL` CREATE TABLE][1], [`MySQL` CREATE INDEX][2].
969///
970/// [1]: https://dev.mysql.com/doc/refman/8.3/en/create-table.html
971/// [2]: https://dev.mysql.com/doc/refman/8.3/en/create-index.html
972#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
973#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
974#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
975pub enum IndexOption {
976    Using(IndexType),
977    Comment(String),
978}
979
980impl fmt::Display for IndexOption {
981    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
982        match self {
983            Self::Using(index_type) => write!(f, "USING {index_type}"),
984            Self::Comment(s) => write!(f, "COMMENT '{s}'"),
985        }
986    }
987}
988
989#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
990#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
991#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
992pub struct ProcedureParam {
993    pub name: Ident,
994    pub data_type: DataType,
995}
996
997impl fmt::Display for ProcedureParam {
998    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
999        write!(f, "{} {}", self.name, self.data_type)
1000    }
1001}
1002
1003/// SQL column definition
1004#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1005#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1006#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1007pub struct ColumnDef {
1008    pub name: Ident,
1009    pub data_type: DataType,
1010    pub collation: Option<ObjectName>,
1011    pub options: Vec<ColumnOptionDef>,
1012}
1013
1014impl fmt::Display for ColumnDef {
1015    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1016        if self.data_type == DataType::Unspecified {
1017            write!(f, "{}", self.name)?;
1018        } else {
1019            write!(f, "{} {}", self.name, self.data_type)?;
1020        }
1021        if let Some(collation) = &self.collation {
1022            write!(f, " COLLATE {collation}")?;
1023        }
1024        for option in &self.options {
1025            write!(f, " {option}")?;
1026        }
1027        Ok(())
1028    }
1029}
1030
1031/// Column definition specified in a `CREATE VIEW` statement.
1032///
1033/// Syntax
1034/// ```markdown
1035/// <name> [data_type][OPTIONS(option, ...)]
1036///
1037/// option: <name> = <value>
1038/// ```
1039///
1040/// Examples:
1041/// ```sql
1042/// name
1043/// age OPTIONS(description = "age column", tag = "prod")
1044/// amount COMMENT 'The total amount for the order line'
1045/// created_at DateTime64
1046/// ```
1047#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1048#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1049#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1050pub struct ViewColumnDef {
1051    pub name: Ident,
1052    pub data_type: Option<DataType>,
1053    pub options: Option<Vec<ColumnOption>>,
1054}
1055
1056impl fmt::Display for ViewColumnDef {
1057    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1058        write!(f, "{}", self.name)?;
1059        if let Some(data_type) = self.data_type.as_ref() {
1060            write!(f, " {}", data_type)?;
1061        }
1062        if let Some(options) = self.options.as_ref() {
1063            write!(f, " {}", display_comma_separated(options.as_slice()))?;
1064        }
1065        Ok(())
1066    }
1067}
1068
1069/// An optionally-named `ColumnOption`: `[ CONSTRAINT <name> ] <column-option>`.
1070///
1071/// Note that implementations are substantially more permissive than the ANSI
1072/// specification on what order column options can be presented in, and whether
1073/// they are allowed to be named. The specification distinguishes between
1074/// constraints (NOT NULL, UNIQUE, PRIMARY KEY, and CHECK), which can be named
1075/// and can appear in any order, and other options (DEFAULT, GENERATED), which
1076/// cannot be named and must appear in a fixed order. `PostgreSQL`, however,
1077/// allows preceding any option with `CONSTRAINT <name>`, even those that are
1078/// not really constraints, like NULL and DEFAULT. MSSQL is less permissive,
1079/// allowing DEFAULT, UNIQUE, PRIMARY KEY and CHECK to be named, but not NULL or
1080/// NOT NULL constraints (the last of which is in violation of the spec).
1081///
1082/// For maximum flexibility, we don't distinguish between constraint and
1083/// non-constraint options, lumping them all together under the umbrella of
1084/// "column options," and we allow any column option to be named.
1085#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1086#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1087#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1088pub struct ColumnOptionDef {
1089    pub name: Option<Ident>,
1090    pub option: ColumnOption,
1091}
1092
1093impl fmt::Display for ColumnOptionDef {
1094    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1095        write!(f, "{}{}", display_constraint_name(&self.name), self.option)
1096    }
1097}
1098
1099/// Identity is a column option for defining an identity or autoincrement column in a `CREATE TABLE` statement.
1100/// Syntax
1101/// ```sql
1102/// { IDENTITY | AUTOINCREMENT } [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ]
1103/// ```
1104/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
1105/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1106#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1107#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1108#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1109pub enum IdentityPropertyKind {
1110    /// An identity property declared via the `AUTOINCREMENT` key word
1111    /// Example:
1112    /// ```sql
1113    ///  AUTOINCREMENT(100, 1) NOORDER
1114    ///  AUTOINCREMENT START 100 INCREMENT 1 ORDER
1115    /// ```
1116    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1117    Autoincrement(IdentityProperty),
1118    /// An identity property declared via the `IDENTITY` key word
1119    /// Example, [MS SQL Server] or [Snowflake]:
1120    /// ```sql
1121    ///  IDENTITY(100, 1)
1122    /// ```
1123    /// [Snowflake]
1124    /// ```sql
1125    ///  IDENTITY(100, 1) ORDER
1126    ///  IDENTITY START 100 INCREMENT 1 NOORDER
1127    /// ```
1128    /// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
1129    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1130    Identity(IdentityProperty),
1131}
1132
1133impl fmt::Display for IdentityPropertyKind {
1134    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1135        let (command, property) = match self {
1136            IdentityPropertyKind::Identity(property) => ("IDENTITY", property),
1137            IdentityPropertyKind::Autoincrement(property) => ("AUTOINCREMENT", property),
1138        };
1139        write!(f, "{command}")?;
1140        if let Some(parameters) = &property.parameters {
1141            write!(f, "{parameters}")?;
1142        }
1143        if let Some(order) = &property.order {
1144            write!(f, "{order}")?;
1145        }
1146        Ok(())
1147    }
1148}
1149
1150#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1151#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1152#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1153pub struct IdentityProperty {
1154    pub parameters: Option<IdentityPropertyFormatKind>,
1155    pub order: Option<IdentityPropertyOrder>,
1156}
1157
1158/// A format of parameters of identity column.
1159///
1160/// It is [Snowflake] specific.
1161/// Syntax
1162/// ```sql
1163/// (seed , increment) | START num INCREMENT num
1164/// ```
1165/// [MS SQL Server] uses one way of representing these parameters.
1166/// Syntax
1167/// ```sql
1168/// (seed , increment)
1169/// ```
1170/// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
1171/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1172#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1173#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1174#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1175pub enum IdentityPropertyFormatKind {
1176    /// A parameters of identity column declared like parameters of function call
1177    /// Example:
1178    /// ```sql
1179    ///  (100, 1)
1180    /// ```
1181    /// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
1182    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1183    FunctionCall(IdentityParameters),
1184    /// A parameters of identity column declared with keywords `START` and `INCREMENT`
1185    /// Example:
1186    /// ```sql
1187    ///  START 100 INCREMENT 1
1188    /// ```
1189    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1190    StartAndIncrement(IdentityParameters),
1191}
1192
1193impl fmt::Display for IdentityPropertyFormatKind {
1194    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1195        match self {
1196            IdentityPropertyFormatKind::FunctionCall(parameters) => {
1197                write!(f, "({}, {})", parameters.seed, parameters.increment)
1198            }
1199            IdentityPropertyFormatKind::StartAndIncrement(parameters) => {
1200                write!(
1201                    f,
1202                    " START {} INCREMENT {}",
1203                    parameters.seed, parameters.increment
1204                )
1205            }
1206        }
1207    }
1208}
1209#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1210#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1211#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1212pub struct IdentityParameters {
1213    pub seed: Expr,
1214    pub increment: Expr,
1215}
1216
1217/// The identity column option specifies how values are generated for the auto-incremented column, either in increasing or decreasing order.
1218/// Syntax
1219/// ```sql
1220/// ORDER | NOORDER
1221/// ```
1222/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1223#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1224#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1225#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1226pub enum IdentityPropertyOrder {
1227    Order,
1228    NoOrder,
1229}
1230
1231impl fmt::Display for IdentityPropertyOrder {
1232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1233        match self {
1234            IdentityPropertyOrder::Order => write!(f, " ORDER"),
1235            IdentityPropertyOrder::NoOrder => write!(f, " NOORDER"),
1236        }
1237    }
1238}
1239
1240/// Column policy that identify a security policy of access to a column.
1241/// Syntax
1242/// ```sql
1243/// [ WITH ] MASKING POLICY <policy_name> [ USING ( <col_name> , <cond_col1> , ... ) ]
1244/// [ WITH ] PROJECTION POLICY <policy_name>
1245/// ```
1246/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1247#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1248#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1249#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1250pub enum ColumnPolicy {
1251    MaskingPolicy(ColumnPolicyProperty),
1252    ProjectionPolicy(ColumnPolicyProperty),
1253}
1254
1255impl fmt::Display for ColumnPolicy {
1256    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1257        let (command, property) = match self {
1258            ColumnPolicy::MaskingPolicy(property) => ("MASKING POLICY", property),
1259            ColumnPolicy::ProjectionPolicy(property) => ("PROJECTION POLICY", property),
1260        };
1261        if property.with {
1262            write!(f, "WITH ")?;
1263        }
1264        write!(f, "{command} {}", property.policy_name)?;
1265        if let Some(using_columns) = &property.using_columns {
1266            write!(f, " USING ({})", display_comma_separated(using_columns))?;
1267        }
1268        Ok(())
1269    }
1270}
1271
1272#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1273#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1274#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1275pub struct ColumnPolicyProperty {
1276    /// This flag indicates that the column policy option is declared using the `WITH` prefix.
1277    /// Example
1278    /// ```sql
1279    /// WITH PROJECTION POLICY sample_policy
1280    /// ```
1281    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1282    pub with: bool,
1283    pub policy_name: Ident,
1284    pub using_columns: Option<Vec<Ident>>,
1285}
1286
1287/// Tags option of column
1288/// Syntax
1289/// ```sql
1290/// [ WITH ] TAG ( <tag_name> = '<tag_value>' [ , <tag_name> = '<tag_value>' , ... ] )
1291/// ```
1292/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1293#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1294#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1295#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1296pub struct TagsColumnOption {
1297    /// This flag indicates that the tags option is declared using the `WITH` prefix.
1298    /// Example:
1299    /// ```sql
1300    /// WITH TAG (A = 'Tag A')
1301    /// ```
1302    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1303    pub with: bool,
1304    pub tags: Vec<Tag>,
1305}
1306
1307impl fmt::Display for TagsColumnOption {
1308    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1309        if self.with {
1310            write!(f, "WITH ")?;
1311        }
1312        write!(f, "TAG ({})", display_comma_separated(&self.tags))?;
1313        Ok(())
1314    }
1315}
1316
1317/// `ColumnOption`s are modifiers that follow a column definition in a `CREATE
1318/// TABLE` statement.
1319#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1320#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1321#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1322pub enum ColumnOption {
1323    /// `NULL`
1324    Null,
1325    /// `NOT NULL`
1326    NotNull,
1327    /// `DEFAULT <restricted-expr>`
1328    Default(Expr),
1329
1330    /// ClickHouse supports `MATERIALIZE`, `EPHEMERAL` and `ALIAS` expr to generate default values.
1331    /// Syntax: `b INT MATERIALIZE (a + 1)`
1332    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values)
1333
1334    /// `MATERIALIZE <expr>`
1335    Materialized(Expr),
1336    /// `EPHEMERAL [<expr>]`
1337    Ephemeral(Option<Expr>),
1338    /// `ALIAS <expr>`
1339    Alias(Expr),
1340
1341    /// `{ PRIMARY KEY | UNIQUE } [<constraint_characteristics>]`
1342    Unique {
1343        is_primary: bool,
1344        characteristics: Option<ConstraintCharacteristics>,
1345    },
1346    /// A referential integrity constraint (`[FOREIGN KEY REFERENCES
1347    /// <foreign_table> (<referred_columns>)
1348    /// { [ON DELETE <referential_action>] [ON UPDATE <referential_action>] |
1349    ///   [ON UPDATE <referential_action>] [ON DELETE <referential_action>]
1350    /// }
1351    /// [<constraint_characteristics>]
1352    /// `).
1353    ForeignKey {
1354        foreign_table: ObjectName,
1355        referred_columns: Vec<Ident>,
1356        on_delete: Option<ReferentialAction>,
1357        on_update: Option<ReferentialAction>,
1358        characteristics: Option<ConstraintCharacteristics>,
1359    },
1360    /// `CHECK (<expr>)`
1361    Check(Expr),
1362    /// Dialect-specific options, such as:
1363    /// - MySQL's `AUTO_INCREMENT` or SQLite's `AUTOINCREMENT`
1364    /// - ...
1365    DialectSpecific(Vec<Token>),
1366    CharacterSet(ObjectName),
1367    Comment(String),
1368    OnUpdate(Expr),
1369    /// `Generated`s are modifiers that follow a column definition in a `CREATE
1370    /// TABLE` statement.
1371    Generated {
1372        generated_as: GeneratedAs,
1373        sequence_options: Option<Vec<SequenceOptions>>,
1374        generation_expr: Option<Expr>,
1375        generation_expr_mode: Option<GeneratedExpressionMode>,
1376        /// false if 'GENERATED ALWAYS' is skipped (option starts with AS)
1377        generated_keyword: bool,
1378    },
1379    /// BigQuery specific: Explicit column options in a view [1] or table [2]
1380    /// Syntax
1381    /// ```sql
1382    /// OPTIONS(description="field desc")
1383    /// ```
1384    /// [1]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#view_column_option_list
1385    /// [2]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_option_list
1386    Options(Vec<SqlOption>),
1387    /// Creates an identity or an autoincrement column in a table.
1388    /// Syntax
1389    /// ```sql
1390    /// { IDENTITY | AUTOINCREMENT } [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ]
1391    /// ```
1392    /// [MS SQL Server]: https://learn.microsoft.com/en-us/sql/t-sql/statements/create-table-transact-sql-identity-property
1393    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1394    Identity(IdentityPropertyKind),
1395    /// SQLite specific: ON CONFLICT option on column definition
1396    /// <https://www.sqlite.org/lang_conflict.html>
1397    OnConflict(Keyword),
1398    /// Snowflake specific: an option of specifying security masking or projection policy to set on a column.
1399    /// Syntax:
1400    /// ```sql
1401    /// [ WITH ] MASKING POLICY <policy_name> [ USING ( <col_name> , <cond_col1> , ... ) ]
1402    /// [ WITH ] PROJECTION POLICY <policy_name>
1403    /// ```
1404    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1405    Policy(ColumnPolicy),
1406    /// Snowflake specific: Specifies the tag name and the tag string value.
1407    /// Syntax:
1408    /// ```sql
1409    /// [ WITH ] TAG ( <tag_name> = '<tag_value>' [ , <tag_name> = '<tag_value>' , ... ] )
1410    /// ```
1411    /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1412    Tags(TagsColumnOption),
1413}
1414
1415impl fmt::Display for ColumnOption {
1416    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1417        use ColumnOption::*;
1418        match self {
1419            Null => write!(f, "NULL"),
1420            NotNull => write!(f, "NOT NULL"),
1421            Default(expr) => write!(f, "DEFAULT {expr}"),
1422            Materialized(expr) => write!(f, "MATERIALIZED {expr}"),
1423            Ephemeral(expr) => {
1424                if let Some(e) = expr {
1425                    write!(f, "EPHEMERAL {e}")
1426                } else {
1427                    write!(f, "EPHEMERAL")
1428                }
1429            }
1430            Alias(expr) => write!(f, "ALIAS {expr}"),
1431            Unique {
1432                is_primary,
1433                characteristics,
1434            } => {
1435                write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?;
1436                if let Some(characteristics) = characteristics {
1437                    write!(f, " {}", characteristics)?;
1438                }
1439                Ok(())
1440            }
1441            ForeignKey {
1442                foreign_table,
1443                referred_columns,
1444                on_delete,
1445                on_update,
1446                characteristics,
1447            } => {
1448                write!(f, "REFERENCES {foreign_table}")?;
1449                if !referred_columns.is_empty() {
1450                    write!(f, " ({})", display_comma_separated(referred_columns))?;
1451                }
1452                if let Some(action) = on_delete {
1453                    write!(f, " ON DELETE {action}")?;
1454                }
1455                if let Some(action) = on_update {
1456                    write!(f, " ON UPDATE {action}")?;
1457                }
1458                if let Some(characteristics) = characteristics {
1459                    write!(f, " {}", characteristics)?;
1460                }
1461                Ok(())
1462            }
1463            Check(expr) => write!(f, "CHECK ({expr})"),
1464            DialectSpecific(val) => write!(f, "{}", display_separated(val, " ")),
1465            CharacterSet(n) => write!(f, "CHARACTER SET {n}"),
1466            Comment(v) => write!(f, "COMMENT '{}'", escape_single_quote_string(v)),
1467            OnUpdate(expr) => write!(f, "ON UPDATE {expr}"),
1468            Generated {
1469                generated_as,
1470                sequence_options,
1471                generation_expr,
1472                generation_expr_mode,
1473                generated_keyword,
1474            } => {
1475                if let Some(expr) = generation_expr {
1476                    let modifier = match generation_expr_mode {
1477                        None => "",
1478                        Some(GeneratedExpressionMode::Virtual) => " VIRTUAL",
1479                        Some(GeneratedExpressionMode::Stored) => " STORED",
1480                    };
1481                    if *generated_keyword {
1482                        write!(f, "GENERATED ALWAYS AS ({expr}){modifier}")?;
1483                    } else {
1484                        write!(f, "AS ({expr}){modifier}")?;
1485                    }
1486                    Ok(())
1487                } else {
1488                    // Like Postgres - generated from sequence
1489                    let when = match generated_as {
1490                        GeneratedAs::Always => "ALWAYS",
1491                        GeneratedAs::ByDefault => "BY DEFAULT",
1492                        // ExpStored goes with an expression, handled above
1493                        GeneratedAs::ExpStored => unreachable!(),
1494                    };
1495                    write!(f, "GENERATED {when} AS IDENTITY")?;
1496                    if sequence_options.is_some() {
1497                        let so = sequence_options.as_ref().unwrap();
1498                        if !so.is_empty() {
1499                            write!(f, " (")?;
1500                        }
1501                        for sequence_option in so {
1502                            write!(f, "{sequence_option}")?;
1503                        }
1504                        if !so.is_empty() {
1505                            write!(f, " )")?;
1506                        }
1507                    }
1508                    Ok(())
1509                }
1510            }
1511            Options(options) => {
1512                write!(f, "OPTIONS({})", display_comma_separated(options))
1513            }
1514            Identity(parameters) => {
1515                write!(f, "{parameters}")
1516            }
1517            OnConflict(keyword) => {
1518                write!(f, "ON CONFLICT {:?}", keyword)?;
1519                Ok(())
1520            }
1521            Policy(parameters) => {
1522                write!(f, "{parameters}")
1523            }
1524            Tags(tags) => {
1525                write!(f, "{tags}")
1526            }
1527        }
1528    }
1529}
1530
1531/// `GeneratedAs`s are modifiers that follow a column option in a `generated`.
1532/// 'ExpStored' is used for a column generated from an expression and stored.
1533#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1534#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1535#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1536pub enum GeneratedAs {
1537    Always,
1538    ByDefault,
1539    ExpStored,
1540}
1541
1542/// `GeneratedExpressionMode`s are modifiers that follow an expression in a `generated`.
1543/// No modifier is typically the same as Virtual.
1544#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1545#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1546#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1547pub enum GeneratedExpressionMode {
1548    Virtual,
1549    Stored,
1550}
1551
1552#[must_use]
1553fn display_constraint_name(name: &'_ Option<Ident>) -> impl fmt::Display + '_ {
1554    struct ConstraintName<'a>(&'a Option<Ident>);
1555    impl<'a> fmt::Display for ConstraintName<'a> {
1556        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1557            if let Some(name) = self.0 {
1558                write!(f, "CONSTRAINT {name} ")?;
1559            }
1560            Ok(())
1561        }
1562    }
1563    ConstraintName(name)
1564}
1565
1566/// If `option` is
1567/// * `Some(inner)` => create display struct for `"{prefix}{inner}{postfix}"`
1568/// * `_` => do nothing
1569#[must_use]
1570fn display_option<'a, T: fmt::Display>(
1571    prefix: &'a str,
1572    postfix: &'a str,
1573    option: &'a Option<T>,
1574) -> impl fmt::Display + 'a {
1575    struct OptionDisplay<'a, T>(&'a str, &'a str, &'a Option<T>);
1576    impl<'a, T: fmt::Display> fmt::Display for OptionDisplay<'a, T> {
1577        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1578            if let Some(inner) = self.2 {
1579                let (prefix, postfix) = (self.0, self.1);
1580                write!(f, "{prefix}{inner}{postfix}")?;
1581            }
1582            Ok(())
1583        }
1584    }
1585    OptionDisplay(prefix, postfix, option)
1586}
1587
1588/// If `option` is
1589/// * `Some(inner)` => create display struct for `" {inner}"`
1590/// * `_` => do nothing
1591#[must_use]
1592fn display_option_spaced<T: fmt::Display>(option: &Option<T>) -> impl fmt::Display + '_ {
1593    display_option(" ", "", option)
1594}
1595
1596/// `<constraint_characteristics> = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]`
1597///
1598/// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order.
1599#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash)]
1600#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1601#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1602pub struct ConstraintCharacteristics {
1603    /// `[ DEFERRABLE | NOT DEFERRABLE ]`
1604    pub deferrable: Option<bool>,
1605    /// `[ INITIALLY DEFERRED | INITIALLY IMMEDIATE ]`
1606    pub initially: Option<DeferrableInitial>,
1607    /// `[ ENFORCED | NOT ENFORCED ]`
1608    pub enforced: Option<bool>,
1609}
1610
1611#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1612#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1613#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1614pub enum DeferrableInitial {
1615    /// `INITIALLY IMMEDIATE`
1616    Immediate,
1617    /// `INITIALLY DEFERRED`
1618    Deferred,
1619}
1620
1621impl ConstraintCharacteristics {
1622    fn deferrable_text(&self) -> Option<&'static str> {
1623        self.deferrable.map(|deferrable| {
1624            if deferrable {
1625                "DEFERRABLE"
1626            } else {
1627                "NOT DEFERRABLE"
1628            }
1629        })
1630    }
1631
1632    fn initially_immediate_text(&self) -> Option<&'static str> {
1633        self.initially
1634            .map(|initially_immediate| match initially_immediate {
1635                DeferrableInitial::Immediate => "INITIALLY IMMEDIATE",
1636                DeferrableInitial::Deferred => "INITIALLY DEFERRED",
1637            })
1638    }
1639
1640    fn enforced_text(&self) -> Option<&'static str> {
1641        self.enforced.map(
1642            |enforced| {
1643                if enforced {
1644                    "ENFORCED"
1645                } else {
1646                    "NOT ENFORCED"
1647                }
1648            },
1649        )
1650    }
1651}
1652
1653impl fmt::Display for ConstraintCharacteristics {
1654    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1655        let deferrable = self.deferrable_text();
1656        let initially_immediate = self.initially_immediate_text();
1657        let enforced = self.enforced_text();
1658
1659        match (deferrable, initially_immediate, enforced) {
1660            (None, None, None) => Ok(()),
1661            (None, None, Some(enforced)) => write!(f, "{enforced}"),
1662            (None, Some(initial), None) => write!(f, "{initial}"),
1663            (None, Some(initial), Some(enforced)) => write!(f, "{initial} {enforced}"),
1664            (Some(deferrable), None, None) => write!(f, "{deferrable}"),
1665            (Some(deferrable), None, Some(enforced)) => write!(f, "{deferrable} {enforced}"),
1666            (Some(deferrable), Some(initial), None) => write!(f, "{deferrable} {initial}"),
1667            (Some(deferrable), Some(initial), Some(enforced)) => {
1668                write!(f, "{deferrable} {initial} {enforced}")
1669            }
1670        }
1671    }
1672}
1673
1674/// `<referential_action> =
1675/// { RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT }`
1676///
1677/// Used in foreign key constraints in `ON UPDATE` and `ON DELETE` options.
1678#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1679#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1680#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1681pub enum ReferentialAction {
1682    Restrict,
1683    Cascade,
1684    SetNull,
1685    NoAction,
1686    SetDefault,
1687}
1688
1689impl fmt::Display for ReferentialAction {
1690    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1691        f.write_str(match self {
1692            ReferentialAction::Restrict => "RESTRICT",
1693            ReferentialAction::Cascade => "CASCADE",
1694            ReferentialAction::SetNull => "SET NULL",
1695            ReferentialAction::NoAction => "NO ACTION",
1696            ReferentialAction::SetDefault => "SET DEFAULT",
1697        })
1698    }
1699}
1700
1701/// SQL user defined type definition
1702#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1703#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1704#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1705pub enum UserDefinedTypeRepresentation {
1706    Composite {
1707        attributes: Vec<UserDefinedTypeCompositeAttributeDef>,
1708    },
1709    /// Note: this is PostgreSQL-specific. See <https://www.postgresql.org/docs/current/sql-createtype.html>
1710    Enum { labels: Vec<Ident> },
1711}
1712
1713impl fmt::Display for UserDefinedTypeRepresentation {
1714    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1715        match self {
1716            UserDefinedTypeRepresentation::Composite { attributes } => {
1717                write!(f, "({})", display_comma_separated(attributes))
1718            }
1719            UserDefinedTypeRepresentation::Enum { labels } => {
1720                write!(f, "ENUM ({})", display_comma_separated(labels))
1721            }
1722        }
1723    }
1724}
1725
1726/// SQL user defined type attribute definition
1727#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1728#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1729#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1730pub struct UserDefinedTypeCompositeAttributeDef {
1731    pub name: Ident,
1732    pub data_type: DataType,
1733    pub collation: Option<ObjectName>,
1734}
1735
1736impl fmt::Display for UserDefinedTypeCompositeAttributeDef {
1737    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1738        write!(f, "{} {}", self.name, self.data_type)?;
1739        if let Some(collation) = &self.collation {
1740            write!(f, " COLLATE {collation}")?;
1741        }
1742        Ok(())
1743    }
1744}
1745
1746/// PARTITION statement used in ALTER TABLE et al. such as in Hive and ClickHouse SQL.
1747/// For example, ClickHouse's OPTIMIZE TABLE supports syntax like PARTITION ID 'partition_id' and PARTITION expr.
1748/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize)
1749#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
1750#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1751#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1752pub enum Partition {
1753    Identifier(Ident),
1754    Expr(Expr),
1755    /// ClickHouse supports PART expr which represents physical partition in disk.
1756    /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#attach-partitionpart)
1757    Part(Expr),
1758    Partitions(Vec<Expr>),
1759}
1760
1761impl fmt::Display for Partition {
1762    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1763        match self {
1764            Partition::Identifier(id) => write!(f, "PARTITION ID {id}"),
1765            Partition::Expr(expr) => write!(f, "PARTITION {expr}"),
1766            Partition::Part(expr) => write!(f, "PART {expr}"),
1767            Partition::Partitions(partitions) => {
1768                write!(f, "PARTITION ({})", display_comma_separated(partitions))
1769            }
1770        }
1771    }
1772}
1773
1774/// DEDUPLICATE statement used in OPTIMIZE TABLE et al. such as in ClickHouse SQL
1775/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize)
1776#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1777#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1778#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1779pub enum Deduplicate {
1780    All,
1781    ByExpression(Expr),
1782}
1783
1784impl fmt::Display for Deduplicate {
1785    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1786        match self {
1787            Deduplicate::All => write!(f, "DEDUPLICATE"),
1788            Deduplicate::ByExpression(expr) => write!(f, "DEDUPLICATE BY {expr}"),
1789        }
1790    }
1791}
1792
1793/// Hive supports `CLUSTERED BY` statement in `CREATE TABLE`.
1794/// Syntax: `CLUSTERED BY (col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS`
1795///
1796/// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
1797#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1798#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1799#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
1800pub struct ClusteredBy {
1801    pub columns: Vec<Ident>,
1802    pub sorted_by: Option<Vec<OrderByExpr>>,
1803    pub num_buckets: Value,
1804}
1805
1806impl fmt::Display for ClusteredBy {
1807    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1808        write!(
1809            f,
1810            "CLUSTERED BY ({})",
1811            display_comma_separated(&self.columns)
1812        )?;
1813        if let Some(ref sorted_by) = self.sorted_by {
1814            write!(f, " SORTED BY ({})", display_comma_separated(sorted_by))?;
1815        }
1816        write!(f, " INTO {} BUCKETS", self.num_buckets)
1817    }
1818}