sqltk_parser/ast/
dml.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#[cfg(not(feature = "std"))]
19use alloc::{
20    boxed::Box,
21    format,
22    string::{String, ToString},
23    vec::Vec,
24};
25
26use core::fmt::{self, Display};
27#[cfg(feature = "serde")]
28use serde::{Deserialize, Serialize};
29#[cfg(feature = "visitor")]
30use sqltk_parser_derive::{Visit, VisitMut};
31
32pub use super::ddl::{ColumnDef, TableConstraint};
33
34use super::{
35    display_comma_separated, display_separated, query::InputFormatClause, Assignment, ClusteredBy,
36    CommentDef, CreateTableOptions, Expr, FileFormat, FromTable, HiveDistributionStyle, HiveFormat,
37    HiveIOFormat, HiveRowFormat, Ident, IndexType, InsertAliases, MysqlInsertPriority, ObjectName,
38    OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, RowAccessPolicy, SelectItem,
39    Setting, SqliteOnConflict, StorageSerializationPolicy, TableObject, TableWithJoins, Tag,
40    WrappedCollection,
41};
42
43/// Index column type.
44#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
45#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
46#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
47pub struct IndexColumn {
48    pub column: OrderByExpr,
49    pub operator_class: Option<Ident>,
50}
51
52impl Display for IndexColumn {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        write!(f, "{}", self.column)?;
55        if let Some(operator_class) = &self.operator_class {
56            write!(f, " {}", operator_class)?;
57        }
58        Ok(())
59    }
60}
61
62/// CREATE INDEX statement.
63#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
66pub struct CreateIndex {
67    /// index name
68    pub name: Option<ObjectName>,
69    #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
70    pub table_name: ObjectName,
71    pub using: Option<IndexType>,
72    pub columns: Vec<IndexColumn>,
73    pub unique: bool,
74    pub concurrently: bool,
75    pub if_not_exists: bool,
76    pub include: Vec<Ident>,
77    pub nulls_distinct: Option<bool>,
78    /// WITH clause: <https://www.postgresql.org/docs/current/sql-createindex.html>
79    pub with: Vec<Expr>,
80    pub predicate: Option<Expr>,
81}
82
83impl Display for CreateIndex {
84    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
85        write!(
86            f,
87            "CREATE {unique}INDEX {concurrently}{if_not_exists}",
88            unique = if self.unique { "UNIQUE " } else { "" },
89            concurrently = if self.concurrently {
90                "CONCURRENTLY "
91            } else {
92                ""
93            },
94            if_not_exists = if self.if_not_exists {
95                "IF NOT EXISTS "
96            } else {
97                ""
98            },
99        )?;
100        if let Some(value) = &self.name {
101            write!(f, "{value} ")?;
102        }
103        write!(f, "ON {}", self.table_name)?;
104        if let Some(value) = &self.using {
105            write!(f, " USING {value} ")?;
106        }
107        write!(f, "({})", display_separated(&self.columns, ","))?;
108        if !self.include.is_empty() {
109            write!(f, " INCLUDE ({})", display_separated(&self.include, ","))?;
110        }
111        if let Some(value) = self.nulls_distinct {
112            if value {
113                write!(f, " NULLS DISTINCT")?;
114            } else {
115                write!(f, " NULLS NOT DISTINCT")?;
116            }
117        }
118        if !self.with.is_empty() {
119            write!(f, " WITH ({})", display_comma_separated(&self.with))?;
120        }
121        if let Some(predicate) = &self.predicate {
122            write!(f, " WHERE {predicate}")?;
123        }
124        Ok(())
125    }
126}
127
128/// CREATE TABLE statement.
129#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
130#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
131#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
132pub struct CreateTable {
133    pub or_replace: bool,
134    pub temporary: bool,
135    pub external: bool,
136    pub global: Option<bool>,
137    pub if_not_exists: bool,
138    pub transient: bool,
139    pub volatile: bool,
140    pub iceberg: bool,
141    /// Table name
142    #[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
143    pub name: ObjectName,
144    /// Optional schema
145    pub columns: Vec<ColumnDef>,
146    pub constraints: Vec<TableConstraint>,
147    pub hive_distribution: HiveDistributionStyle,
148    pub hive_formats: Option<HiveFormat>,
149    pub table_options: CreateTableOptions,
150    pub file_format: Option<FileFormat>,
151    pub location: Option<String>,
152    pub query: Option<Box<Query>>,
153    pub without_rowid: bool,
154    pub like: Option<ObjectName>,
155    pub clone: Option<ObjectName>,
156    // For Hive dialect, the table comment is after the column definitions without `=`,
157    // so the `comment` field is optional and different than the comment field in the general options list.
158    // [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
159    pub comment: Option<CommentDef>,
160    pub on_commit: Option<OnCommit>,
161    /// ClickHouse "ON CLUSTER" clause:
162    /// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
163    pub on_cluster: Option<Ident>,
164    /// ClickHouse "PRIMARY KEY " clause.
165    /// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
166    pub primary_key: Option<Box<Expr>>,
167    /// ClickHouse "ORDER BY " clause. Note that omitted ORDER BY is different
168    /// than empty (represented as ()), the latter meaning "no sorting".
169    /// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
170    pub order_by: Option<OneOrManyWithParens<Expr>>,
171    /// BigQuery: A partition expression for the table.
172    /// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
173    pub partition_by: Option<Box<Expr>>,
174    /// BigQuery: Table clustering column list.
175    /// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
176    pub cluster_by: Option<WrappedCollection<Vec<Ident>>>,
177    /// Hive: Table clustering column list.
178    /// <https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable>
179    pub clustered_by: Option<ClusteredBy>,
180    /// Postgres `INHERITs` clause, which contains the list of tables from which
181    /// the new table inherits.
182    /// <https://www.postgresql.org/docs/current/ddl-inherit.html>
183    /// <https://www.postgresql.org/docs/current/sql-createtable.html#SQL-CREATETABLE-PARMS-INHERITS>
184    pub inherits: Option<Vec<ObjectName>>,
185    /// SQLite "STRICT" clause.
186    /// if the "STRICT" table-option keyword is added to the end, after the closing ")",
187    /// then strict typing rules apply to that table.
188    pub strict: bool,
189    /// Snowflake "COPY GRANTS" clause
190    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
191    pub copy_grants: bool,
192    /// Snowflake "ENABLE_SCHEMA_EVOLUTION" clause
193    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
194    pub enable_schema_evolution: Option<bool>,
195    /// Snowflake "CHANGE_TRACKING" clause
196    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
197    pub change_tracking: Option<bool>,
198    /// Snowflake "DATA_RETENTION_TIME_IN_DAYS" clause
199    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
200    pub data_retention_time_in_days: Option<u64>,
201    /// Snowflake "MAX_DATA_EXTENSION_TIME_IN_DAYS" clause
202    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
203    pub max_data_extension_time_in_days: Option<u64>,
204    /// Snowflake "DEFAULT_DDL_COLLATION" clause
205    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
206    pub default_ddl_collation: Option<String>,
207    /// Snowflake "WITH AGGREGATION POLICY" clause
208    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
209    pub with_aggregation_policy: Option<ObjectName>,
210    /// Snowflake "WITH ROW ACCESS POLICY" clause
211    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
212    pub with_row_access_policy: Option<RowAccessPolicy>,
213    /// Snowflake "WITH TAG" clause
214    /// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
215    pub with_tags: Option<Vec<Tag>>,
216    /// Snowflake "EXTERNAL_VOLUME" clause for Iceberg tables
217    /// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
218    pub external_volume: Option<String>,
219    /// Snowflake "BASE_LOCATION" clause for Iceberg tables
220    /// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
221    pub base_location: Option<String>,
222    /// Snowflake "CATALOG" clause for Iceberg tables
223    /// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
224    pub catalog: Option<String>,
225    /// Snowflake "CATALOG_SYNC" clause for Iceberg tables
226    /// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
227    pub catalog_sync: Option<String>,
228    /// Snowflake "STORAGE_SERIALIZATION_POLICY" clause for Iceberg tables
229    /// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
230    pub storage_serialization_policy: Option<StorageSerializationPolicy>,
231}
232
233impl Display for CreateTable {
234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235        // We want to allow the following options
236        // Empty column list, allowed by PostgreSQL:
237        //   `CREATE TABLE t ()`
238        // No columns provided for CREATE TABLE AS:
239        //   `CREATE TABLE t AS SELECT a from t2`
240        // Columns provided for CREATE TABLE AS:
241        //   `CREATE TABLE t (a INT) AS SELECT a from t2`
242        write!(
243            f,
244            "CREATE {or_replace}{external}{global}{temporary}{transient}{volatile}{iceberg}TABLE {if_not_exists}{name}",
245            or_replace = if self.or_replace { "OR REPLACE " } else { "" },
246            external = if self.external { "EXTERNAL " } else { "" },
247            global = self.global
248                .map(|global| {
249                    if global {
250                        "GLOBAL "
251                    } else {
252                        "LOCAL "
253                    }
254                })
255                .unwrap_or(""),
256            if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
257            temporary = if self.temporary { "TEMPORARY " } else { "" },
258            transient = if self.transient { "TRANSIENT " } else { "" },
259            volatile = if self.volatile { "VOLATILE " } else { "" },
260            // Only for Snowflake
261            iceberg = if self.iceberg { "ICEBERG " } else { "" },
262            name = self.name,
263        )?;
264        if let Some(on_cluster) = &self.on_cluster {
265            write!(f, " ON CLUSTER {}", on_cluster)?;
266        }
267        if !self.columns.is_empty() || !self.constraints.is_empty() {
268            write!(f, " ({}", display_comma_separated(&self.columns))?;
269            if !self.columns.is_empty() && !self.constraints.is_empty() {
270                write!(f, ", ")?;
271            }
272            write!(f, "{})", display_comma_separated(&self.constraints))?;
273        } else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
274            // PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
275            write!(f, " ()")?;
276        }
277
278        // Hive table comment should be after column definitions, please refer to:
279        // [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable)
280        if let Some(comment) = &self.comment {
281            write!(f, " COMMENT '{comment}'")?;
282        }
283
284        // Only for SQLite
285        if self.without_rowid {
286            write!(f, " WITHOUT ROWID")?;
287        }
288
289        // Only for Hive
290        if let Some(l) = &self.like {
291            write!(f, " LIKE {l}")?;
292        }
293
294        if let Some(c) = &self.clone {
295            write!(f, " CLONE {c}")?;
296        }
297
298        match &self.hive_distribution {
299            HiveDistributionStyle::PARTITIONED { columns } => {
300                write!(f, " PARTITIONED BY ({})", display_comma_separated(columns))?;
301            }
302            HiveDistributionStyle::SKEWED {
303                columns,
304                on,
305                stored_as_directories,
306            } => {
307                write!(
308                    f,
309                    " SKEWED BY ({})) ON ({})",
310                    display_comma_separated(columns),
311                    display_comma_separated(on)
312                )?;
313                if *stored_as_directories {
314                    write!(f, " STORED AS DIRECTORIES")?;
315                }
316            }
317            _ => (),
318        }
319
320        if let Some(clustered_by) = &self.clustered_by {
321            write!(f, " {clustered_by}")?;
322        }
323
324        if let Some(HiveFormat {
325            row_format,
326            serde_properties,
327            storage,
328            location,
329        }) = &self.hive_formats
330        {
331            match row_format {
332                Some(HiveRowFormat::SERDE { class }) => write!(f, " ROW FORMAT SERDE '{class}'")?,
333                Some(HiveRowFormat::DELIMITED { delimiters }) => {
334                    write!(f, " ROW FORMAT DELIMITED")?;
335                    if !delimiters.is_empty() {
336                        write!(f, " {}", display_separated(delimiters, " "))?;
337                    }
338                }
339                None => (),
340            }
341            match storage {
342                Some(HiveIOFormat::IOF {
343                    input_format,
344                    output_format,
345                }) => write!(
346                    f,
347                    " STORED AS INPUTFORMAT {input_format} OUTPUTFORMAT {output_format}"
348                )?,
349                Some(HiveIOFormat::FileFormat { format }) if !self.external => {
350                    write!(f, " STORED AS {format}")?
351                }
352                _ => (),
353            }
354            if let Some(serde_properties) = serde_properties.as_ref() {
355                write!(
356                    f,
357                    " WITH SERDEPROPERTIES ({})",
358                    display_comma_separated(serde_properties)
359                )?;
360            }
361            if !self.external {
362                if let Some(loc) = location {
363                    write!(f, " LOCATION '{loc}'")?;
364                }
365            }
366        }
367        if self.external {
368            if let Some(file_format) = self.file_format {
369                write!(f, " STORED AS {file_format}")?;
370            }
371            write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?;
372        }
373
374        match &self.table_options {
375            options @ CreateTableOptions::With(_)
376            | options @ CreateTableOptions::Plain(_)
377            | options @ CreateTableOptions::TableProperties(_) => write!(f, " {}", options)?,
378            _ => (),
379        }
380
381        if let Some(primary_key) = &self.primary_key {
382            write!(f, " PRIMARY KEY {}", primary_key)?;
383        }
384        if let Some(order_by) = &self.order_by {
385            write!(f, " ORDER BY {}", order_by)?;
386        }
387        if let Some(inherits) = &self.inherits {
388            write!(f, " INHERITS ({})", display_comma_separated(inherits))?;
389        }
390        if let Some(partition_by) = self.partition_by.as_ref() {
391            write!(f, " PARTITION BY {partition_by}")?;
392        }
393        if let Some(cluster_by) = self.cluster_by.as_ref() {
394            write!(f, " CLUSTER BY {cluster_by}")?;
395        }
396        if let options @ CreateTableOptions::Options(_) = &self.table_options {
397            write!(f, " {}", options)?;
398        }
399        if let Some(external_volume) = self.external_volume.as_ref() {
400            write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?;
401        }
402
403        if let Some(catalog) = self.catalog.as_ref() {
404            write!(f, " CATALOG = '{catalog}'")?;
405        }
406
407        if self.iceberg {
408            if let Some(base_location) = self.base_location.as_ref() {
409                write!(f, " BASE_LOCATION = '{base_location}'")?;
410            }
411        }
412
413        if let Some(catalog_sync) = self.catalog_sync.as_ref() {
414            write!(f, " CATALOG_SYNC = '{catalog_sync}'")?;
415        }
416
417        if let Some(storage_serialization_policy) = self.storage_serialization_policy.as_ref() {
418            write!(
419                f,
420                " STORAGE_SERIALIZATION_POLICY = {storage_serialization_policy}"
421            )?;
422        }
423
424        if self.copy_grants {
425            write!(f, " COPY GRANTS")?;
426        }
427
428        if let Some(is_enabled) = self.enable_schema_evolution {
429            write!(
430                f,
431                " ENABLE_SCHEMA_EVOLUTION={}",
432                if is_enabled { "TRUE" } else { "FALSE" }
433            )?;
434        }
435
436        if let Some(is_enabled) = self.change_tracking {
437            write!(
438                f,
439                " CHANGE_TRACKING={}",
440                if is_enabled { "TRUE" } else { "FALSE" }
441            )?;
442        }
443
444        if let Some(data_retention_time_in_days) = self.data_retention_time_in_days {
445            write!(
446                f,
447                " DATA_RETENTION_TIME_IN_DAYS={data_retention_time_in_days}",
448            )?;
449        }
450
451        if let Some(max_data_extension_time_in_days) = self.max_data_extension_time_in_days {
452            write!(
453                f,
454                " MAX_DATA_EXTENSION_TIME_IN_DAYS={max_data_extension_time_in_days}",
455            )?;
456        }
457
458        if let Some(default_ddl_collation) = &self.default_ddl_collation {
459            write!(f, " DEFAULT_DDL_COLLATION='{default_ddl_collation}'",)?;
460        }
461
462        if let Some(with_aggregation_policy) = &self.with_aggregation_policy {
463            write!(f, " WITH AGGREGATION POLICY {with_aggregation_policy}",)?;
464        }
465
466        if let Some(row_access_policy) = &self.with_row_access_policy {
467            write!(f, " {row_access_policy}",)?;
468        }
469
470        if let Some(tag) = &self.with_tags {
471            write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?;
472        }
473
474        if self.on_commit.is_some() {
475            let on_commit = match self.on_commit {
476                Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS",
477                Some(OnCommit::PreserveRows) => "ON COMMIT PRESERVE ROWS",
478                Some(OnCommit::Drop) => "ON COMMIT DROP",
479                None => "",
480            };
481            write!(f, " {on_commit}")?;
482        }
483        if self.strict {
484            write!(f, " STRICT")?;
485        }
486        if let Some(query) = &self.query {
487            write!(f, " AS {query}")?;
488        }
489        Ok(())
490    }
491}
492
493/// INSERT statement.
494#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
495#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
496#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
497pub struct Insert {
498    /// Only for Sqlite
499    pub or: Option<SqliteOnConflict>,
500    /// Only for mysql
501    pub ignore: bool,
502    /// INTO - optional keyword
503    pub into: bool,
504    /// TABLE
505    pub table: TableObject,
506    /// table_name as foo (for PostgreSQL)
507    pub table_alias: Option<Ident>,
508    /// COLUMNS
509    pub columns: Vec<Ident>,
510    /// Overwrite (Hive)
511    pub overwrite: bool,
512    /// A SQL query that specifies what to insert
513    pub source: Option<Box<Query>>,
514    /// MySQL `INSERT INTO ... SET`
515    /// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html>
516    pub assignments: Vec<Assignment>,
517    /// partitioned insert (Hive)
518    pub partitioned: Option<Vec<Expr>>,
519    /// Columns defined after PARTITION
520    pub after_columns: Vec<Ident>,
521    /// whether the insert has the table keyword (Hive)
522    pub has_table_keyword: bool,
523    pub on: Option<OnInsert>,
524    /// RETURNING
525    pub returning: Option<Vec<SelectItem>>,
526    /// Only for mysql
527    pub replace_into: bool,
528    /// Only for mysql
529    pub priority: Option<MysqlInsertPriority>,
530    /// Only for mysql
531    pub insert_alias: Option<InsertAliases>,
532    /// Settings used for ClickHouse.
533    ///
534    /// ClickHouse syntax: `INSERT INTO tbl SETTINGS format_template_resultset = '/some/path/resultset.format'`
535    ///
536    /// [ClickHouse `INSERT INTO`](https://clickhouse.com/docs/en/sql-reference/statements/insert-into)
537    pub settings: Option<Vec<Setting>>,
538    /// Format for `INSERT` statement when not using standard SQL format. Can be e.g. `CSV`,
539    /// `JSON`, `JSONAsString`, `LineAsString` and more.
540    ///
541    /// ClickHouse syntax: `INSERT INTO tbl FORMAT JSONEachRow {"foo": 1, "bar": 2}, {"foo": 3}`
542    ///
543    /// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data)
544    pub format_clause: Option<InputFormatClause>,
545}
546
547impl Display for Insert {
548    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
549        let table_name = if let Some(alias) = &self.table_alias {
550            format!("{0} AS {alias}", self.table)
551        } else {
552            self.table.to_string()
553        };
554
555        if let Some(on_conflict) = self.or {
556            write!(f, "INSERT {on_conflict} INTO {table_name} ")?;
557        } else {
558            write!(
559                f,
560                "{start}",
561                start = if self.replace_into {
562                    "REPLACE"
563                } else {
564                    "INSERT"
565                },
566            )?;
567            if let Some(priority) = self.priority {
568                write!(f, " {priority}",)?;
569            }
570
571            write!(
572                f,
573                "{ignore}{over}{int}{tbl} {table_name} ",
574                table_name = table_name,
575                ignore = if self.ignore { " IGNORE" } else { "" },
576                over = if self.overwrite { " OVERWRITE" } else { "" },
577                int = if self.into { " INTO" } else { "" },
578                tbl = if self.has_table_keyword { " TABLE" } else { "" },
579            )?;
580        }
581        if !self.columns.is_empty() {
582            write!(f, "({}) ", display_comma_separated(&self.columns))?;
583        }
584        if let Some(ref parts) = self.partitioned {
585            if !parts.is_empty() {
586                write!(f, "PARTITION ({}) ", display_comma_separated(parts))?;
587            }
588        }
589        if !self.after_columns.is_empty() {
590            write!(f, "({}) ", display_comma_separated(&self.after_columns))?;
591        }
592
593        if let Some(settings) = &self.settings {
594            write!(f, "SETTINGS {} ", display_comma_separated(settings))?;
595        }
596
597        if let Some(source) = &self.source {
598            write!(f, "{source}")?;
599        } else if !self.assignments.is_empty() {
600            write!(f, "SET ")?;
601            write!(f, "{}", display_comma_separated(&self.assignments))?;
602        } else if let Some(format_clause) = &self.format_clause {
603            write!(f, "{format_clause}")?;
604        } else if self.columns.is_empty() {
605            write!(f, "DEFAULT VALUES")?;
606        }
607
608        if let Some(insert_alias) = &self.insert_alias {
609            write!(f, " AS {0}", insert_alias.row_alias)?;
610
611            if let Some(col_aliases) = &insert_alias.col_aliases {
612                if !col_aliases.is_empty() {
613                    write!(f, " ({})", display_comma_separated(col_aliases))?;
614                }
615            }
616        }
617
618        if let Some(on) = &self.on {
619            write!(f, "{on}")?;
620        }
621
622        if let Some(returning) = &self.returning {
623            write!(f, " RETURNING {}", display_comma_separated(returning))?;
624        }
625        Ok(())
626    }
627}
628
629/// DELETE statement.
630#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
631#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
632#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
633pub struct Delete {
634    /// Multi tables delete are supported in mysql
635    pub tables: Vec<ObjectName>,
636    /// FROM
637    pub from: FromTable,
638    /// USING (Snowflake, Postgres, MySQL)
639    pub using: Option<Vec<TableWithJoins>>,
640    /// WHERE
641    pub selection: Option<Expr>,
642    /// RETURNING
643    pub returning: Option<Vec<SelectItem>>,
644    /// ORDER BY (MySQL)
645    pub order_by: Vec<OrderByExpr>,
646    /// LIMIT (MySQL)
647    pub limit: Option<Expr>,
648}
649
650impl Display for Delete {
651    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652        write!(f, "DELETE ")?;
653        if !self.tables.is_empty() {
654            write!(f, "{} ", display_comma_separated(&self.tables))?;
655        }
656        match &self.from {
657            FromTable::WithFromKeyword(from) => {
658                write!(f, "FROM {}", display_comma_separated(from))?;
659            }
660            FromTable::WithoutKeyword(from) => {
661                write!(f, "{}", display_comma_separated(from))?;
662            }
663        }
664        if let Some(using) = &self.using {
665            write!(f, " USING {}", display_comma_separated(using))?;
666        }
667        if let Some(selection) = &self.selection {
668            write!(f, " WHERE {selection}")?;
669        }
670        if let Some(returning) = &self.returning {
671            write!(f, " RETURNING {}", display_comma_separated(returning))?;
672        }
673        if !self.order_by.is_empty() {
674            write!(f, " ORDER BY {}", display_comma_separated(&self.order_by))?;
675        }
676        if let Some(limit) = &self.limit {
677            write!(f, " LIMIT {limit}")?;
678        }
679        Ok(())
680    }
681}