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