| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| #[cfg(not(feature = "std"))] | |
| use alloc::{boxed::Box, format, string::ToString, vec::Vec}; | |
| use core::fmt::{self, Display}; | |
| #[cfg(feature = "serde")] | |
| use serde::{Deserialize, Serialize}; | |
| #[cfg(feature = "visitor")] | |
| use sqlparser_derive::{Visit, VisitMut}; | |
| use crate::{ | |
| ast::display_separated, | |
| display_utils::{indented_list, Indent, SpaceOrNewline}, | |
| }; | |
| use super::{ | |
| display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause, | |
| Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert, | |
| OptimizerHint, OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, | |
| TableAliasWithoutColumns, TableFactor, TableObject, TableWithJoins, UpdateTableFromKind, | |
| Values, | |
| }; | |
| /// The `OVERRIDING` clause of a PostgreSQL `INSERT`: whether values supplied for an identity | |
| /// column are used instead of the values its sequence would generate. | |
| /// | |
| /// See <https://www.postgresql.org/docs/current/sql-insert.html> | |
| #[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum InsertOverriding { | |
| /// `OVERRIDING SYSTEM VALUE` | |
| System, | |
| /// `OVERRIDING USER VALUE` | |
| User, | |
| } | |
| impl Display for InsertOverriding { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| InsertOverriding::System => write!(f, "OVERRIDING SYSTEM VALUE"), | |
| InsertOverriding::User => write!(f, "OVERRIDING USER VALUE"), | |
| } | |
| } | |
| } | |
| /// INSERT statement. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct Insert { | |
| /// Token for the `INSERT` keyword (or its substitutes) | |
| pub insert_token: AttachedToken, | |
| /// Query optimizer hints | |
| /// | |
| /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E) | |
| pub optimizer_hints: Vec<OptimizerHint>, | |
| /// Only for Sqlite | |
| pub or: Option<SqliteOnConflict>, | |
| /// Only for mysql | |
| pub ignore: bool, | |
| /// INTO - optional keyword | |
| pub into: bool, | |
| /// TABLE | |
| pub table: TableObject, | |
| /// `table_name as foo` (for PostgreSQL) | |
| /// `table_name foo` (for Oracle) | |
| pub table_alias: Option<TableAliasWithoutColumns>, | |
| /// COLUMNS | |
| pub columns: Vec<ObjectName>, | |
| /// `BY NAME` clause used by Databricks SQL. | |
| /// | |
| /// When present, columns from the source query are matched to columns in | |
| /// the target table by name instead of by position. The syntax is: | |
| /// | |
| /// ```sql | |
| /// INSERT INTO [TABLE] table_name | |
| /// [PARTITION (...)] | |
| /// [(column_name [, ...]) | BY NAME] | |
| /// query | |
| /// ``` | |
| /// | |
| /// See <https://docs.databricks.com/gcp/en/sql/language-manual/sql-ref-syntax-dml-insert-into>. | |
| pub by_name: bool, | |
| /// Overwrite (Hive) | |
| pub overwrite: bool, | |
| /// A SQL query that specifies what to insert | |
| pub source: Option<Box<Query>>, | |
| /// MySQL `INSERT INTO ... SET` | |
| /// See: <https://dev.mysql.com/doc/refman/8.4/en/insert.html> | |
| pub assignments: Vec<Assignment>, | |
| /// partitioned insert (Hive) | |
| pub partitioned: Option<Vec<Expr>>, | |
| /// Columns defined after PARTITION | |
| pub after_columns: Vec<Ident>, | |
| /// PostgreSQL `OVERRIDING { SYSTEM | USER } VALUE`, between the column list and the source. | |
| /// | |
| /// See <https://www.postgresql.org/docs/current/sql-insert.html> | |
| pub overriding: Option<InsertOverriding>, | |
| /// whether the insert has the table keyword (Hive) | |
| pub has_table_keyword: bool, | |
| /// ON INSERT | |
| pub on: Option<OnInsert>, | |
| /// RETURNING | |
| pub returning: Option<Vec<SelectItem>>, | |
| /// OUTPUT (MSSQL) | |
| /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql> | |
| pub output: Option<OutputClause>, | |
| /// Only for mysql | |
| pub replace_into: bool, | |
| /// Only for mysql | |
| pub priority: Option<MysqlInsertPriority>, | |
| /// Only for mysql | |
| pub insert_alias: Option<InsertAliases>, | |
| /// Settings used for ClickHouse. | |
| /// | |
| /// ClickHouse syntax: `INSERT INTO tbl SETTINGS format_template_resultset = '/some/path/resultset.format'` | |
| /// | |
| /// [ClickHouse `INSERT INTO`](https://clickhouse.com/docs/en/sql-reference/statements/insert-into) | |
| pub settings: Option<Vec<Setting>>, | |
| /// Format for `INSERT` statement when not using standard SQL format. Can be e.g. `CSV`, | |
| /// `JSON`, `JSONAsString`, `LineAsString` and more. | |
| /// | |
| /// ClickHouse syntax: `INSERT INTO tbl FORMAT JSONEachRow {"foo": 1, "bar": 2}, {"foo": 3}` | |
| /// | |
| /// [ClickHouse formats JSON insert](https://clickhouse.com/docs/en/interfaces/formats#json-inserting-data) | |
| pub format_clause: Option<InputFormatClause>, | |
| /// For Snowflake multi-table insert: specifies the type (`ALL` or `FIRST`) | |
| /// | |
| /// - `None` means this is a regular single-table INSERT | |
| /// - `Some(All)` means `INSERT ALL` (all matching WHEN clauses are executed) | |
| /// - `Some(First)` means `INSERT FIRST` (only the first matching WHEN clause is executed) | |
| /// | |
| /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table> | |
| pub multi_table_insert_type: Option<MultiTableInsertType>, | |
| /// For multi-table insert: additional INTO clauses (unconditional) | |
| /// | |
| /// Used for `INSERT ALL INTO t1 INTO t2 ... SELECT ...` | |
| /// | |
| /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table> | |
| pub multi_table_into_clauses: Vec<MultiTableInsertIntoClause>, | |
| /// For conditional multi-table insert: WHEN clauses | |
| /// | |
| /// Used for `INSERT ALL/FIRST WHEN cond THEN INTO t1 ... SELECT ...` | |
| /// | |
| /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table> | |
| pub multi_table_when_clauses: Vec<MultiTableInsertWhenClause>, | |
| /// For conditional multi-table insert: ELSE clause | |
| /// | |
| /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table> | |
| pub multi_table_else_clause: Option<Vec<MultiTableInsertIntoClause>>, | |
| } | |
| impl Display for Insert { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| // SQLite OR conflict has a special format: INSERT OR ... INTO table_name | |
| let table_name = if let Some(table_alias) = &self.table_alias { | |
| format!( | |
| "{table} {as_keyword}{alias}", | |
| table = self.table, | |
| as_keyword = if table_alias.explicit { "AS " } else { "" }, | |
| alias = table_alias.alias | |
| ) | |
| } else { | |
| self.table.to_string() | |
| }; | |
| if let Some(on_conflict) = self.or { | |
| f.write_str("INSERT")?; | |
| for hint in &self.optimizer_hints { | |
| write!(f, " {hint}")?; | |
| } | |
| write!(f, " {on_conflict} INTO {table_name} ")?; | |
| } else { | |
| write!( | |
| f, | |
| "{start}", | |
| start = if self.replace_into { | |
| "REPLACE" | |
| } else { | |
| "INSERT" | |
| } | |
| )?; | |
| for hint in &self.optimizer_hints { | |
| write!(f, " {hint}")?; | |
| } | |
| if let Some(priority) = self.priority { | |
| write!(f, " {priority}")?; | |
| } | |
| if self.ignore { | |
| write!(f, " IGNORE")?; | |
| } | |
| if self.overwrite { | |
| write!(f, " OVERWRITE")?; | |
| } | |
| if let Some(insert_type) = &self.multi_table_insert_type { | |
| write!(f, " {}", insert_type)?; | |
| } | |
| if self.into { | |
| write!(f, " INTO")?; | |
| } | |
| if self.has_table_keyword { | |
| write!(f, " TABLE")?; | |
| } | |
| if !table_name.is_empty() { | |
| write!(f, " {table_name} ")?; | |
| } | |
| } | |
| if !self.columns.is_empty() { | |
| write!(f, "({})", display_comma_separated(&self.columns))?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| if let Some(ref parts) = self.partitioned { | |
| if !parts.is_empty() { | |
| write!(f, "PARTITION ({})", display_comma_separated(parts))?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| } | |
| if self.by_name { | |
| write!(f, "BY NAME")?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| if !self.after_columns.is_empty() { | |
| write!(f, "({})", display_comma_separated(&self.after_columns))?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| if let Some(overriding) = &self.overriding { | |
| write!(f, "{overriding}")?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| if let Some(output) = &self.output { | |
| write!(f, "{output}")?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| if let Some(settings) = &self.settings { | |
| write!(f, "SETTINGS {}", display_comma_separated(settings))?; | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| for into_clause in &self.multi_table_into_clauses { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{}", into_clause)?; | |
| } | |
| for when_clause in &self.multi_table_when_clauses { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{}", when_clause)?; | |
| } | |
| if let Some(else_clauses) = &self.multi_table_else_clause { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "ELSE")?; | |
| for into_clause in else_clauses { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{}", into_clause)?; | |
| } | |
| } | |
| if let Some(source) = &self.source { | |
| if !self.multi_table_into_clauses.is_empty() | |
| || !self.multi_table_when_clauses.is_empty() | |
| { | |
| SpaceOrNewline.fmt(f)?; | |
| } | |
| source.fmt(f)?; | |
| } else if !self.assignments.is_empty() { | |
| write!(f, "SET")?; | |
| indented_list(f, &self.assignments)?; | |
| } else if let Some(format_clause) = &self.format_clause { | |
| format_clause.fmt(f)?; | |
| } else if self.columns.is_empty() { | |
| write!(f, "DEFAULT VALUES")?; | |
| } | |
| if let Some(insert_alias) = &self.insert_alias { | |
| write!(f, " AS {0}", insert_alias.row_alias)?; | |
| if let Some(col_aliases) = &insert_alias.col_aliases { | |
| if !col_aliases.is_empty() { | |
| write!(f, " ({})", display_comma_separated(col_aliases))?; | |
| } | |
| } | |
| } | |
| if let Some(on) = &self.on { | |
| write!(f, "{on}")?; | |
| } | |
| if let Some(returning) = &self.returning { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("RETURNING")?; | |
| indented_list(f, returning)?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// DELETE statement. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct Delete { | |
| /// Token for the `DELETE` keyword | |
| pub delete_token: AttachedToken, | |
| /// Query optimizer hints | |
| /// | |
| /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E) | |
| pub optimizer_hints: Vec<OptimizerHint>, | |
| /// Multi tables delete are supported in mysql | |
| pub tables: Vec<ObjectName>, | |
| /// FROM | |
| pub from: FromTable, | |
| /// ClickHouse `ON CLUSTER` | |
| pub on_cluster: Option<Ident>, | |
| /// USING (Snowflake, Postgres, MySQL) | |
| pub using: Option<Vec<TableWithJoins>>, | |
| /// WHERE | |
| pub selection: Option<Expr>, | |
| /// RETURNING | |
| pub returning: Option<Vec<SelectItem>>, | |
| /// OUTPUT (MSSQL) | |
| /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql> | |
| pub output: Option<OutputClause>, | |
| /// ORDER BY (MySQL) | |
| pub order_by: Vec<OrderByExpr>, | |
| /// LIMIT (MySQL) | |
| pub limit: Option<Expr>, | |
| /// ClickHouse `SETTINGS` | |
| pub settings: Option<Vec<Setting>>, | |
| } | |
| impl Display for Delete { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.write_str("DELETE")?; | |
| for hint in &self.optimizer_hints { | |
| f.write_str(" ")?; | |
| hint.fmt(f)?; | |
| } | |
| if !self.tables.is_empty() { | |
| indented_list(f, &self.tables)?; | |
| } | |
| match &self.from { | |
| FromTable::WithFromKeyword(from) => { | |
| f.write_str(" FROM")?; | |
| indented_list(f, from)?; | |
| } | |
| FromTable::WithoutKeyword(from) => { | |
| indented_list(f, from)?; | |
| } | |
| } | |
| if let Some(on_cluster) = &self.on_cluster { | |
| write!(f, " ON CLUSTER {on_cluster}")?; | |
| } | |
| if let Some(output) = &self.output { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{output}")?; | |
| } | |
| if let Some(using) = &self.using { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("USING")?; | |
| indented_list(f, using)?; | |
| } | |
| if let Some(selection) = &self.selection { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("WHERE")?; | |
| SpaceOrNewline.fmt(f)?; | |
| Indent(selection).fmt(f)?; | |
| } | |
| if let Some(returning) = &self.returning { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("RETURNING")?; | |
| indented_list(f, returning)?; | |
| } | |
| if !self.order_by.is_empty() { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("ORDER BY")?; | |
| indented_list(f, &self.order_by)?; | |
| } | |
| if let Some(limit) = &self.limit { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("LIMIT")?; | |
| SpaceOrNewline.fmt(f)?; | |
| Indent(limit).fmt(f)?; | |
| } | |
| if let Some(settings) = &self.settings { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("SETTINGS")?; | |
| indented_list(f, settings)?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// UPDATE statement. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct Update { | |
| /// Token for the `UPDATE` keyword | |
| pub update_token: AttachedToken, | |
| /// Query optimizer hints | |
| /// | |
| /// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/optimizer-hints.html) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E) | |
| pub optimizer_hints: Vec<OptimizerHint>, | |
| /// TABLE | |
| pub table: TableWithJoins, | |
| /// ClickHouse `ON CLUSTER` | |
| pub on_cluster: Option<Ident>, | |
| /// Column assignments | |
| pub assignments: Vec<Assignment>, | |
| /// Table which provide value to be set | |
| pub from: Option<UpdateTableFromKind>, | |
| /// WHERE | |
| pub selection: Option<Expr>, | |
| /// RETURNING | |
| pub returning: Option<Vec<SelectItem>>, | |
| /// OUTPUT (MSSQL) | |
| /// See <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql> | |
| pub output: Option<OutputClause>, | |
| /// SQLite-specific conflict resolution clause | |
| pub or: Option<SqliteOnConflict>, | |
| /// ORDER BY (MySQL extension for single-table UPDATE) | |
| /// See <https://dev.mysql.com/doc/refman/8.4/en/update.html> | |
| pub order_by: Vec<OrderByExpr>, | |
| /// LIMIT | |
| pub limit: Option<Expr>, | |
| /// ClickHouse `SETTINGS` | |
| pub settings: Option<Vec<Setting>>, | |
| } | |
| impl Display for Update { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| f.write_str("UPDATE")?; | |
| for hint in &self.optimizer_hints { | |
| f.write_str(" ")?; | |
| hint.fmt(f)?; | |
| } | |
| f.write_str(" ")?; | |
| if let Some(or) = &self.or { | |
| or.fmt(f)?; | |
| f.write_str(" ")?; | |
| } | |
| self.table.fmt(f)?; | |
| if let Some(on_cluster) = &self.on_cluster { | |
| write!(f, " ON CLUSTER {on_cluster}")?; | |
| } | |
| if let Some(UpdateTableFromKind::BeforeSet(from)) = &self.from { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("FROM")?; | |
| indented_list(f, from)?; | |
| } | |
| if !self.assignments.is_empty() { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("SET")?; | |
| indented_list(f, &self.assignments)?; | |
| } | |
| if let Some(output) = &self.output { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{output}")?; | |
| } | |
| if let Some(UpdateTableFromKind::AfterSet(from)) = &self.from { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("FROM")?; | |
| indented_list(f, from)?; | |
| } | |
| if let Some(selection) = &self.selection { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("WHERE")?; | |
| SpaceOrNewline.fmt(f)?; | |
| Indent(selection).fmt(f)?; | |
| } | |
| if let Some(returning) = &self.returning { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("RETURNING")?; | |
| indented_list(f, returning)?; | |
| } | |
| if !self.order_by.is_empty() { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("ORDER BY")?; | |
| indented_list(f, &self.order_by)?; | |
| } | |
| if let Some(limit) = &self.limit { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "LIMIT {limit}")?; | |
| } | |
| if let Some(settings) = &self.settings { | |
| SpaceOrNewline.fmt(f)?; | |
| f.write_str("SETTINGS")?; | |
| indented_list(f, settings)?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// A `MERGE` statement. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct Merge { | |
| /// The `MERGE` token that starts the statement. | |
| pub merge_token: AttachedToken, | |
| /// Query optimizer hints | |
| /// | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Comments.html#GUID-D316D545-89E2-4D54-977F-FC97815CD62E) | |
| pub optimizer_hints: Vec<OptimizerHint>, | |
| /// optional INTO keyword | |
| pub into: bool, | |
| /// Specifies the table to merge | |
| pub table: TableFactor, | |
| /// Specifies the table or subquery to join with the target table | |
| pub source: TableFactor, | |
| /// Specifies the expression on which to join the target table and source | |
| pub on: Box<Expr>, | |
| /// Specifies the actions to perform when values match or do not match. | |
| pub clauses: Vec<MergeClause>, | |
| /// Specifies the output to save changes in MSSQL | |
| pub output: Option<OutputClause>, | |
| } | |
| impl Display for Merge { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| f.write_str("MERGE")?; | |
| for hint in &self.optimizer_hints { | |
| write!(f, " {hint}")?; | |
| } | |
| if self.into { | |
| write!(f, " INTO")?; | |
| } | |
| write!( | |
| f, | |
| " {table} USING {source} ", | |
| table = self.table, | |
| source = self.source | |
| )?; | |
| write!(f, "ON {on} ", on = self.on)?; | |
| write!(f, "{}", display_separated(&self.clauses, " "))?; | |
| if let Some(ref output) = self.output { | |
| write!(f, " {output}")?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// A `WHEN` clause within a `MERGE` Statement | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// WHEN NOT MATCHED BY SOURCE AND product LIKE '%washer%' THEN DELETE | |
| /// ``` | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MergeClause { | |
| /// The `WHEN` token that starts the sub-expression. | |
| pub when_token: AttachedToken, | |
| /// The type of `WHEN` clause. | |
| pub clause_kind: MergeClauseKind, | |
| /// An optional predicate to further restrict the clause. | |
| pub predicate: Option<Expr>, | |
| /// The action to perform when the clause is matched. | |
| pub action: MergeAction, | |
| } | |
| impl Display for MergeClause { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| let MergeClause { | |
| when_token: _, | |
| clause_kind, | |
| predicate, | |
| action, | |
| } = self; | |
| write!(f, "WHEN {clause_kind}")?; | |
| if let Some(pred) = predicate { | |
| write!(f, " AND {pred}")?; | |
| } | |
| write!(f, " THEN {action}") | |
| } | |
| } | |
| /// Variant of `WHEN` clause used within a `MERGE` Statement. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE | |
| /// ``` | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MergeClauseKind { | |
| /// `WHEN MATCHED` | |
| Matched, | |
| /// `WHEN NOT MATCHED` | |
| NotMatched, | |
| /// `WHEN MATCHED BY TARGET` | |
| /// | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| NotMatchedByTarget, | |
| /// `WHEN MATCHED BY SOURCE` | |
| /// | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| NotMatchedBySource, | |
| } | |
| impl Display for MergeClauseKind { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| MergeClauseKind::Matched => write!(f, "MATCHED"), | |
| MergeClauseKind::NotMatched => write!(f, "NOT MATCHED"), | |
| MergeClauseKind::NotMatchedByTarget => write!(f, "NOT MATCHED BY TARGET"), | |
| MergeClauseKind::NotMatchedBySource => write!(f, "NOT MATCHED BY SOURCE"), | |
| } | |
| } | |
| } | |
| /// Underlying statement of a `WHEN` clause within a `MERGE` Statement | |
| /// | |
| /// Example | |
| /// ```sql | |
| /// INSERT (product, quantity) VALUES(product, quantity) | |
| /// ``` | |
| /// | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html) | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MergeAction { | |
| /// An `INSERT` clause | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// INSERT (product, quantity) VALUES(product, quantity) | |
| /// ``` | |
| Insert(MergeInsertExpr), | |
| /// An `UPDATE` clause | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// UPDATE SET quantity = T.quantity + S.quantity | |
| /// ``` | |
| Update(MergeUpdateExpr), | |
| /// A plain `DELETE` clause | |
| Delete { | |
| /// The `DELETE` token that starts the sub-expression. | |
| delete_token: AttachedToken, | |
| }, | |
| /// A `DO NOTHING` clause. | |
| DoNothing { | |
| /// The `DO` token that starts the sub-expression. | |
| do_token: AttachedToken, | |
| /// The `NOTHING` token that ends the sub-expression. | |
| nothing_token: AttachedToken, | |
| }, | |
| } | |
| impl Display for MergeAction { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| MergeAction::Insert(insert) => { | |
| write!(f, "INSERT {insert}") | |
| } | |
| MergeAction::Update(update) => { | |
| write!(f, "UPDATE {update}") | |
| } | |
| MergeAction::Delete { .. } => { | |
| write!(f, "DELETE") | |
| } | |
| MergeAction::DoNothing { .. } => { | |
| write!(f, "DO NOTHING") | |
| } | |
| } | |
| } | |
| } | |
| /// The type of expression used to insert rows within a `MERGE` statement. | |
| /// | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MergeInsertKind { | |
| /// The insert expression is defined from an explicit `VALUES` clause | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// INSERT VALUES(product, quantity) | |
| /// ``` | |
| Values(Values), | |
| /// The insert expression is defined using only the `ROW` keyword. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// INSERT ROW | |
| /// ``` | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| Row, | |
| /// The insert expression uses the `*` wildcard to insert all columns. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// INSERT * | |
| /// ``` | |
| /// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html) | |
| Wildcard, | |
| } | |
| impl Display for MergeInsertKind { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| MergeInsertKind::Values(values) => { | |
| write!(f, "{values}") | |
| } | |
| MergeInsertKind::Row => { | |
| write!(f, "ROW") | |
| } | |
| MergeInsertKind::Wildcard => { | |
| write!(f, "*") | |
| } | |
| } | |
| } | |
| } | |
| /// The expression used to insert rows within a `MERGE` statement. | |
| /// | |
| /// Examples | |
| /// ```sql | |
| /// INSERT (product, quantity) VALUES(product, quantity) | |
| /// INSERT ROW | |
| /// ``` | |
| /// | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html) | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MergeInsertExpr { | |
| /// The `INSERT` token that starts the sub-expression. | |
| pub insert_token: AttachedToken, | |
| /// Columns (if any) specified by the insert. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// INSERT (product, quantity) VALUES(product, quantity) | |
| /// INSERT (product, quantity) ROW | |
| /// ``` | |
| pub columns: Vec<ObjectName>, | |
| /// The token, `[VALUES | ROW]` starting `kind`. | |
| pub kind_token: AttachedToken, | |
| /// The insert type used by the statement. | |
| pub kind: MergeInsertKind, | |
| /// An optional condition to restrict the insertion (Oracle specific) | |
| pub insert_predicate: Option<Expr>, | |
| } | |
| impl Display for MergeInsertExpr { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| if !self.columns.is_empty() { | |
| write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?; | |
| } | |
| write!(f, "{}", self.kind)?; | |
| if let Some(predicate) = self.insert_predicate.as_ref() { | |
| write!(f, " WHERE {}", predicate)?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// The kind of update used within a `MERGE` statement. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MergeUpdateKind { | |
| /// Standard update with explicit assignments. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// UPDATE SET quantity = source.quantity, name = source.name | |
| /// ``` | |
| Set(Vec<Assignment>), | |
| /// The `*` wildcard to update all columns from the source. | |
| /// | |
| /// Example: | |
| /// ```sql | |
| /// UPDATE SET * | |
| /// ``` | |
| /// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html) | |
| Wildcard, | |
| } | |
| impl Display for MergeUpdateKind { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| MergeUpdateKind::Set(assignments) => { | |
| write!(f, "SET {}", display_comma_separated(assignments)) | |
| } | |
| MergeUpdateKind::Wildcard => { | |
| write!(f, "SET *") | |
| } | |
| } | |
| } | |
| } | |
| /// The expression used to update rows within a `MERGE` statement. | |
| /// | |
| /// Examples | |
| /// ```sql | |
| /// UPDATE SET quantity = T.quantity + S.quantity | |
| /// UPDATE SET * | |
| /// ``` | |
| /// | |
| /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge) | |
| /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement) | |
| /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html) | |
| /// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html) | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MergeUpdateExpr { | |
| /// The `UPDATE` token that starts the sub-expression. | |
| pub update_token: AttachedToken, | |
| /// The kind of update: explicit assignments or `*` shorthand. | |
| pub kind: MergeUpdateKind, | |
| /// `where_clause` for the update (Oracle specific) | |
| pub update_predicate: Option<Expr>, | |
| /// `delete_clause` for the update "delete where" (Oracle specific) | |
| pub delete_predicate: Option<Expr>, | |
| } | |
| impl Display for MergeUpdateExpr { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "{}", self.kind)?; | |
| if let Some(predicate) = self.update_predicate.as_ref() { | |
| write!(f, " WHERE {predicate}")?; | |
| } | |
| if let Some(predicate) = self.delete_predicate.as_ref() { | |
| write!(f, " DELETE WHERE {predicate}")?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// An `OUTPUT` clause on `MERGE`, `INSERT`, `UPDATE`, or `DELETE` (MSSQL). | |
| /// | |
| /// Example: | |
| /// OUTPUT $action, deleted.* INTO dbo.temp_products; | |
| /// <https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql> | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum OutputClause { | |
| /// `OUTPUT` clause | |
| Output { | |
| /// The `OUTPUT` token that starts the sub-expression. | |
| output_token: AttachedToken, | |
| /// The select items to output | |
| select_items: Vec<SelectItem>, | |
| /// Optional `INTO` table to direct the output | |
| into_table: Option<SelectInto>, | |
| }, | |
| /// `RETURNING` clause | |
| Returning { | |
| /// The `RETURNING` token that starts the sub-expression. | |
| returning_token: AttachedToken, | |
| /// The select items to return | |
| select_items: Vec<SelectItem>, | |
| }, | |
| } | |
| impl fmt::Display for OutputClause { | |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
| match self { | |
| OutputClause::Output { | |
| output_token: _, | |
| select_items, | |
| into_table, | |
| } => { | |
| f.write_str("OUTPUT ")?; | |
| display_comma_separated(select_items).fmt(f)?; | |
| if let Some(into_table) = into_table { | |
| f.write_str(" ")?; | |
| into_table.fmt(f)?; | |
| } | |
| Ok(()) | |
| } | |
| OutputClause::Returning { | |
| returning_token: _, | |
| select_items, | |
| } => { | |
| f.write_str("RETURNING ")?; | |
| display_comma_separated(select_items).fmt(f) | |
| } | |
| } | |
| } | |
| } | |
| /// A WHEN clause in a conditional multi-table INSERT. | |
| /// | |
| /// Syntax: | |
| /// ```sql | |
| /// WHEN n1 > 100 THEN | |
| /// INTO t1 | |
| /// INTO t2 (c1, c2) VALUES (n1, n2) | |
| /// ``` | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MultiTableInsertWhenClause { | |
| /// The condition for this WHEN clause | |
| pub condition: Expr, | |
| /// The INTO clauses to execute when the condition is true | |
| pub into_clauses: Vec<MultiTableInsertIntoClause>, | |
| } | |
| impl Display for MultiTableInsertWhenClause { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "WHEN {} THEN", self.condition)?; | |
| for into_clause in &self.into_clauses { | |
| SpaceOrNewline.fmt(f)?; | |
| write!(f, "{}", into_clause)?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// An INTO clause in a multi-table INSERT. | |
| /// | |
| /// Syntax: | |
| /// ```sql | |
| /// INTO <target_table> [ ( <target_col_name> [ , ... ] ) ] [ VALUES ( { <source_col_name> | DEFAULT | NULL } [ , ... ] ) ] | |
| /// ``` | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MultiTableInsertIntoClause { | |
| /// The target table | |
| pub table_name: ObjectName, | |
| /// The target columns (optional) | |
| pub columns: Vec<Ident>, | |
| /// The VALUES clause (optional) | |
| pub values: Option<MultiTableInsertValues>, | |
| } | |
| impl Display for MultiTableInsertIntoClause { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| write!(f, "INTO {}", self.table_name)?; | |
| if !self.columns.is_empty() { | |
| write!(f, " ({})", display_comma_separated(&self.columns))?; | |
| } | |
| if let Some(values) = &self.values { | |
| write!(f, " VALUES ({})", display_comma_separated(&values.values))?; | |
| } | |
| Ok(()) | |
| } | |
| } | |
| /// The VALUES clause in a multi-table INSERT INTO clause. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub struct MultiTableInsertValues { | |
| /// The values to insert (can be column references, DEFAULT, or NULL) | |
| pub values: Vec<MultiTableInsertValue>, | |
| } | |
| /// A value in a multi-table INSERT VALUES clause. | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MultiTableInsertValue { | |
| /// A column reference or expression from the source | |
| Expr(Expr), | |
| /// The DEFAULT keyword | |
| Default, | |
| } | |
| impl Display for MultiTableInsertValue { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| MultiTableInsertValue::Expr(expr) => write!(f, "{}", expr), | |
| MultiTableInsertValue::Default => write!(f, "DEFAULT"), | |
| } | |
| } | |
| } | |
| /// The type of multi-table INSERT statement(Snowflake). | |
| /// | |
| /// See: <https://docs.snowflake.com/en/sql-reference/sql/insert-multi-table> | |
| #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] | |
| #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] | |
| #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] | |
| pub enum MultiTableInsertType { | |
| /// `INSERT ALL` - all matching WHEN clauses are executed | |
| All, | |
| /// `INSERT FIRST` - only the first matching WHEN clause is executed | |
| First, | |
| } | |
| impl Display for MultiTableInsertType { | |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | |
| match self { | |
| MultiTableInsertType::All => write!(f, "ALL"), | |
| MultiTableInsertType::First => write!(f, "FIRST"), | |
| } | |
| } | |
| } |