Oracle: Support for MERGE predicates (#2101)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index d6009ce..d740b14 100644
--- a/src/ast/dml.rs
+++ b/src/ast/dml.rs
@@ -24,13 +24,16 @@
#[cfg(feature = "visitor")]
use sqlparser_derive::{Visit, VisitMut};
-use crate::display_utils::{indented_list, Indent, SpaceOrNewline};
+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,
- OrderByExpr, Query, SelectItem, Setting, SqliteOnConflict, TableObject, TableWithJoins,
- UpdateTableFromKind,
+ OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, TableFactor,
+ TableObject, TableWithJoins, UpdateTableFromKind, Values,
};
/// INSERT statement.
@@ -310,3 +313,334 @@
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,
+ /// 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 {
+ write!(
+ f,
+ "MERGE{int} {table} USING {source} ",
+ int = if self.into { " INTO" } else { "" },
+ 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,
+ pub clause_kind: MergeClauseKind,
+ pub predicate: Option<Expr>,
+ 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, 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,
+ },
+}
+
+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")
+ }
+ }
+ }
+}
+
+/// 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,
+}
+
+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")
+ }
+ }
+ }
+}
+
+/// 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 expression used to update rows within a `MERGE` statement.
+///
+/// Examples
+/// ```sql
+/// UPDATE SET quantity = T.quantity + S.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 struct MergeUpdateExpr {
+ /// The `UPDATE` token that starts the sub-expression.
+ pub update_token: AttachedToken,
+ /// The update assiment expressions
+ pub assignments: Vec<Assignment>,
+ /// `where_clause` for the update (Oralce 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, "SET {}", display_comma_separated(&self.assignments))?;
+ 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(())
+ }
+}
+
+/// A `OUTPUT` Clause in the end of a `MERGE` Statement
+///
+/// Example:
+/// OUTPUT $action, deleted.* INTO dbo.temp_products;
+/// [mssql](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 {
+ output_token: AttachedToken,
+ select_items: Vec<SelectItem>,
+ into_table: Option<SelectInto>,
+ },
+ Returning {
+ returning_token: AttachedToken,
+ 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)
+ }
+ }
+ }
+}
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index b9d947e..6cb4c33 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -77,7 +77,10 @@
UserDefinedTypeInternalLength, UserDefinedTypeRangeOption, UserDefinedTypeRepresentation,
UserDefinedTypeSqlDefinitionOption, UserDefinedTypeStorage, ViewColumnDef,
};
-pub use self::dml::{Delete, Insert, Update};
+pub use self::dml::{
+ Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
+ MergeInsertKind, MergeUpdateExpr, OutputClause, Update,
+};
pub use self::operator::{BinaryOperator, UnaryOperator};
pub use self::query::{
AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode,
@@ -344,6 +347,12 @@
}
}
+impl From<Ident> for ObjectName {
+ fn from(ident: Ident) -> Self {
+ ObjectName(vec![ObjectNamePart::Identifier(ident)])
+ }
+}
+
impl fmt::Display for ObjectName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", display_separated(&self.0, "."))
@@ -4087,22 +4096,7 @@
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
/// [MSSQL](https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql?view=sql-server-ver16)
- Merge {
- /// The `MERGE` token that starts the statement.
- merge_token: AttachedToken,
- /// optional INTO keyword
- into: bool,
- /// Specifies the table to merge
- table: TableFactor,
- /// Specifies the table or subquery to join with the target table
- source: TableFactor,
- /// Specifies the expression on which to join the target table and source
- on: Box<Expr>,
- /// Specifies the actions to perform when values match or do not match.
- clauses: Vec<MergeClause>,
- // Specifies the output to save changes in MSSQL
- output: Option<OutputClause>,
- },
+ Merge(Merge),
/// ```sql
/// CACHE [ FLAG ] TABLE <table_name> [ OPTIONS('K1' = 'V1', 'K2' = V2) ] [ AS ] [ <query> ]
/// ```
@@ -5520,27 +5514,7 @@
Statement::ReleaseSavepoint { name } => {
write!(f, "RELEASE SAVEPOINT {name}")
}
- Statement::Merge {
- merge_token: _,
- into,
- table,
- source,
- on,
- clauses,
- output,
- } => {
- write!(
- f,
- "MERGE{int} {table} USING {source} ",
- int = if *into { " INTO" } else { "" }
- )?;
- write!(f, "ON {on} ")?;
- write!(f, "{}", display_separated(clauses, " "))?;
- if let Some(output) = output {
- write!(f, " {output}")?;
- }
- Ok(())
- }
+ Statement::Merge(merge) => merge.fmt(f),
Statement::Cache {
table_name,
table_flag,
@@ -8565,257 +8539,6 @@
}
}
-/// 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, 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"),
- }
- }
-}
-
-/// 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,
-}
-
-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")
- }
- }
- }
-}
-
-/// 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)
-#[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<Ident>,
- /// The token, `[VALUES | ROW]` starting `kind`.
- pub kind_token: AttachedToken,
- /// The insert type used by the statement.
- pub kind: MergeInsertKind,
-}
-
-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)
- }
-}
-
-/// 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)
-#[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 {
- /// The `UPDATE` token that starts the sub-expression.
- update_token: AttachedToken,
- assignments: Vec<Assignment>,
- },
- /// A plain `DELETE` clause
- Delete {
- /// The `DELETE` token that starts the sub-expression.
- delete_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 { assignments, .. } => {
- write!(f, "UPDATE SET {}", display_comma_separated(assignments))
- }
- MergeAction::Delete { .. } => {
- write!(f, "DELETE")
- }
- }
- }
-}
-
-/// 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,
- pub clause_kind: MergeClauseKind,
- pub predicate: Option<Expr>,
- 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}")
- }
-}
-
-/// A Output Clause in the end of a 'MERGE' Statement
-///
-/// Example:
-/// OUTPUT $action, deleted.* INTO dbo.temp_products;
-/// [mssql](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 {
- output_token: AttachedToken,
- select_items: Vec<SelectItem>,
- into_table: Option<SelectInto>,
- },
- Returning {
- returning_token: AttachedToken,
- 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)
- }
- }
- }
-}
-
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 20a0525..d63ed62 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -38,15 +38,15 @@
FunctionArgumentClause, FunctionArgumentList, FunctionArguments, GroupByExpr, HavingBound,
IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
- MatchRecognizePattern, Measure, MergeAction, MergeClause, MergeInsertExpr, MergeInsertKind,
- NamedParenthesizedList, NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict,
- OnConflictAction, OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause,
- Partition, PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue,
- ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
- SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
- TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
- TableWithJoins, Update, UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement,
- WildcardAdditionalOptions, With, WithFill,
+ MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
+ MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
+ ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
+ OrderByExpr, OrderByKind, OutputClause, Partition, PivotValueSource, ProjectionSelect, Query,
+ RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
+ ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
+ SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
+ TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Value, Values,
+ ViewColumnDef, WhileStatement, WildcardAdditionalOptions, With, WithFill,
};
/// Given an iterator of spans, return the [Span::union] of all spans.
@@ -453,20 +453,7 @@
Statement::Explain { .. } => Span::empty(),
Statement::Savepoint { .. } => Span::empty(),
Statement::ReleaseSavepoint { .. } => Span::empty(),
- Statement::Merge {
- merge_token,
- into: _,
- table: _,
- source: _,
- on,
- clauses,
- output,
- } => union_spans(
- [merge_token.0.span, on.span()]
- .into_iter()
- .chain(clauses.iter().map(Spanned::span))
- .chain(output.iter().map(Spanned::span)),
- ),
+ Statement::Merge(merge) => merge.span(),
Statement::Cache { .. } => Span::empty(),
Statement::UNCache { .. } => Span::empty(),
Statement::CreateSequence { .. } => Span::empty(),
@@ -927,6 +914,17 @@
}
}
+impl Spanned for Merge {
+ fn span(&self) -> Span {
+ union_spans(
+ [self.merge_token.0.span, self.on.span()]
+ .into_iter()
+ .chain(self.clauses.iter().map(Spanned::span))
+ .chain(self.output.iter().map(Spanned::span)),
+ )
+ }
+}
+
impl Spanned for FromTable {
fn span(&self) -> Span {
match self {
@@ -2421,12 +2419,7 @@
fn span(&self) -> Span {
match self {
MergeAction::Insert(expr) => expr.span(),
- MergeAction::Update {
- update_token,
- assignments,
- } => union_spans(
- core::iter::once(update_token.0.span).chain(assignments.iter().map(Spanned::span)),
- ),
+ MergeAction::Update(expr) => expr.span(),
MergeAction::Delete { delete_token } => delete_token.0.span,
}
}
@@ -2444,7 +2437,19 @@
},
]
.into_iter()
- .chain(self.columns.iter().map(|i| i.span)),
+ .chain(self.insert_predicate.iter().map(Spanned::span))
+ .chain(self.columns.iter().map(|i| i.span())),
+ )
+ }
+}
+
+impl Spanned for MergeUpdateExpr {
+ fn span(&self) -> Span {
+ union_spans(
+ core::iter::once(self.update_token.0.span)
+ .chain(self.assignments.iter().map(Spanned::span))
+ .chain(self.update_predicate.iter().map(Spanned::span))
+ .chain(self.delete_predicate.iter().map(Spanned::span)),
)
}
}
@@ -2768,7 +2773,7 @@
assert_eq!(stmt_span.end, (16, 67).into());
// ~ individual tokens within the statement
- let Statement::Merge {
+ let Statement::Merge(Merge {
merge_token,
into: _,
table: _,
@@ -2776,7 +2781,7 @@
on: _,
clauses,
output,
- } = &r[0]
+ }) = &r[0]
else {
panic!("not a MERGE statement");
};
@@ -2814,10 +2819,12 @@
clauses[1].when_token.0.span,
Span::new(Location::new(12, 17), Location::new(12, 21))
);
- if let MergeAction::Update {
+ if let MergeAction::Update(MergeUpdateExpr {
update_token,
assignments: _,
- } = &clauses[1].action
+ update_predicate: _,
+ delete_predicate: _,
+ }) = &clauses[1].action
{
assert_eq!(
update_token.0.span,
@@ -2890,7 +2897,7 @@
);
// ~ individual tokens within the statement
- if let Statement::Merge { output, .. } = &r[0] {
+ if let Statement::Merge(Merge { output, .. }) = &r[0] {
if let Some(OutputClause::Returning {
returning_token, ..
}) = output
@@ -2924,7 +2931,7 @@
);
// ~ individual tokens within the statement
- if let Statement::Merge { output, .. } = &r[0] {
+ if let Statement::Merge(Merge { output, .. }) = &r[0] {
if let Some(OutputClause::Output { output_token, .. }) = output {
assert_eq!(
output_token.0.span,
@@ -2937,4 +2944,44 @@
panic!("not a MERGE statement");
};
}
+
+ #[test]
+ fn test_merge_statement_spans_with_update_predicates() {
+ let sql = r#"
+ MERGE INTO a USING b ON a.id = b.id
+ WHEN MATCHED THEN
+ UPDATE set a.x = a.x + b.x
+ WHERE b.x != 2
+ DELETE WHERE a.x <> 3"#;
+
+ let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
+ assert_eq!(1, r.len());
+
+ // ~ assert the span of the whole statement
+ let stmt_span = r[0].span();
+ assert_eq!(
+ stmt_span,
+ Span::new(Location::new(2, 8), Location::new(6, 36))
+ );
+ }
+
+ #[test]
+ fn test_merge_statement_spans_with_insert_predicate() {
+ let sql = r#"
+ MERGE INTO a USING b ON a.id = b.id
+ WHEN NOT MATCHED THEN
+ INSERT VALUES (b.x, b.y) WHERE b.x != 2
+-- qed
+"#;
+
+ let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
+ assert_eq!(1, r.len());
+
+ // ~ assert the span of the whole statement
+ let stmt_span = r[0].span();
+ assert_eq!(
+ stmt_span,
+ Span::new(Location::new(2, 8), Location::new(4, 52))
+ );
+ }
}
diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs
index 83c6da4..1d99d86 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -603,7 +603,7 @@
false
}
- /// Return true if the dialect supports specifying multiple options
+ /// Returns true if the dialect supports specifying multiple options
/// in a `CREATE TABLE` statement for the structure of the new table. For example:
/// `CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a`
fn supports_create_table_multi_schema_info_sources(&self) -> bool {
diff --git a/src/parser/merge.rs b/src/parser/merge.rs
new file mode 100644
index 0000000..b2283b6
--- /dev/null
+++ b/src/parser/merge.rs
@@ -0,0 +1,242 @@
+// Licensed 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.
+
+//! SQL Parser for a `MERGE` statement
+
+#[cfg(not(feature = "std"))]
+use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
+
+use crate::{
+ ast::{
+ Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
+ MergeUpdateExpr, ObjectName, OutputClause, SetExpr, Statement,
+ },
+ dialect::{BigQueryDialect, GenericDialect, MySqlDialect},
+ keywords::Keyword,
+ parser::IsOptional,
+ tokenizer::TokenWithSpan,
+};
+
+use super::{Parser, ParserError};
+
+impl Parser<'_> {
+ /// Parse a `MERGE` statement, returning a `Box`ed SetExpr
+ ///
+ /// This is used to reduce the size of the stack frames in debug builds
+ pub(super) fn parse_merge_setexpr_boxed(
+ &mut self,
+ merge_token: TokenWithSpan,
+ ) -> Result<Box<SetExpr>, ParserError> {
+ Ok(Box::new(SetExpr::Merge(self.parse_merge(merge_token)?)))
+ }
+
+ pub fn parse_merge(&mut self, merge_token: TokenWithSpan) -> Result<Statement, ParserError> {
+ let into = self.parse_keyword(Keyword::INTO);
+
+ let table = self.parse_table_factor()?;
+
+ self.expect_keyword_is(Keyword::USING)?;
+ let source = self.parse_table_factor()?;
+ self.expect_keyword_is(Keyword::ON)?;
+ let on = self.parse_expr()?;
+ let clauses = self.parse_merge_clauses()?;
+ let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
+ Some(keyword) => Some(self.parse_output(keyword, self.get_current_token().clone())?),
+ None => None,
+ };
+
+ Ok(Statement::Merge(Merge {
+ merge_token: merge_token.into(),
+ into,
+ table,
+ source,
+ on: Box::new(on),
+ clauses,
+ output,
+ }))
+ }
+
+ fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
+ let mut clauses = vec![];
+ loop {
+ if !(self.parse_keyword(Keyword::WHEN)) {
+ break;
+ }
+ let when_token = self.get_current_token().clone();
+
+ let mut clause_kind = MergeClauseKind::Matched;
+ if self.parse_keyword(Keyword::NOT) {
+ clause_kind = MergeClauseKind::NotMatched;
+ }
+ self.expect_keyword_is(Keyword::MATCHED)?;
+
+ if matches!(clause_kind, MergeClauseKind::NotMatched)
+ && self.parse_keywords(&[Keyword::BY, Keyword::SOURCE])
+ {
+ clause_kind = MergeClauseKind::NotMatchedBySource;
+ } else if matches!(clause_kind, MergeClauseKind::NotMatched)
+ && self.parse_keywords(&[Keyword::BY, Keyword::TARGET])
+ {
+ clause_kind = MergeClauseKind::NotMatchedByTarget;
+ }
+
+ let predicate = if self.parse_keyword(Keyword::AND) {
+ Some(self.parse_expr()?)
+ } else {
+ None
+ };
+
+ self.expect_keyword_is(Keyword::THEN)?;
+
+ let merge_clause = match self.parse_one_of_keywords(&[
+ Keyword::UPDATE,
+ Keyword::INSERT,
+ Keyword::DELETE,
+ ]) {
+ Some(Keyword::UPDATE) => {
+ if matches!(
+ clause_kind,
+ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
+ ) {
+ return parser_err!(
+ format_args!("UPDATE is not allowed in a {clause_kind} merge clause"),
+ self.get_current_token().span.start
+ );
+ }
+
+ let update_token = self.get_current_token().clone();
+ self.expect_keyword_is(Keyword::SET)?;
+ let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
+ let update_predicate = if self.parse_keyword(Keyword::WHERE) {
+ Some(self.parse_expr()?)
+ } else {
+ None
+ };
+ let delete_predicate = if self.parse_keyword(Keyword::DELETE) {
+ let _ = self.expect_keyword(Keyword::WHERE)?;
+ Some(self.parse_expr()?)
+ } else {
+ None
+ };
+ MergeAction::Update(MergeUpdateExpr {
+ update_token: update_token.into(),
+ assignments,
+ update_predicate,
+ delete_predicate,
+ })
+ }
+ Some(Keyword::DELETE) => {
+ if matches!(
+ clause_kind,
+ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
+ ) {
+ return parser_err!(
+ format_args!("DELETE is not allowed in a {clause_kind} merge clause"),
+ self.get_current_token().span.start
+ );
+ };
+
+ let delete_token = self.get_current_token().clone();
+ MergeAction::Delete {
+ delete_token: delete_token.into(),
+ }
+ }
+ Some(Keyword::INSERT) => {
+ if !matches!(
+ clause_kind,
+ MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
+ ) {
+ return parser_err!(
+ format_args!("INSERT is not allowed in a {clause_kind} merge clause"),
+ self.get_current_token().span.start
+ );
+ };
+
+ let insert_token = self.get_current_token().clone();
+ let is_mysql = dialect_of!(self is MySqlDialect);
+
+ let columns = self.parse_merge_clause_insert_columns(is_mysql)?;
+ let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
+ && self.parse_keyword(Keyword::ROW)
+ {
+ (MergeInsertKind::Row, self.get_current_token().clone())
+ } else {
+ self.expect_keyword_is(Keyword::VALUES)?;
+ let values_token = self.get_current_token().clone();
+ let values = self.parse_values(is_mysql, false)?;
+ (MergeInsertKind::Values(values), values_token)
+ };
+ let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
+ Some(self.parse_expr()?)
+ } else {
+ None
+ };
+
+ MergeAction::Insert(MergeInsertExpr {
+ insert_token: insert_token.into(),
+ columns,
+ kind_token: kind_token.into(),
+ kind,
+ insert_predicate,
+ })
+ }
+ _ => {
+ return parser_err!(
+ "expected UPDATE, DELETE or INSERT in merge clause",
+ self.peek_token_ref().span.start
+ );
+ }
+ };
+ clauses.push(MergeClause {
+ when_token: when_token.into(),
+ clause_kind,
+ predicate,
+ action: merge_clause,
+ });
+ }
+ Ok(clauses)
+ }
+
+ fn parse_merge_clause_insert_columns(
+ &mut self,
+ allow_empty: bool,
+ ) -> Result<Vec<ObjectName>, ParserError> {
+ self.parse_parenthesized_qualified_column_list(IsOptional::Optional, allow_empty)
+ }
+
+ fn parse_output(
+ &mut self,
+ start_keyword: Keyword,
+ start_token: TokenWithSpan,
+ ) -> Result<OutputClause, ParserError> {
+ let select_items = self.parse_projection()?;
+ let into_table = if start_keyword == Keyword::OUTPUT && self.peek_keyword(Keyword::INTO) {
+ self.expect_keyword_is(Keyword::INTO)?;
+ Some(self.parse_select_into()?)
+ } else {
+ None
+ };
+
+ Ok(if start_keyword == Keyword::OUTPUT {
+ OutputClause::Output {
+ output_token: start_token.into(),
+ select_items,
+ into_table,
+ }
+ } else {
+ OutputClause::Returning {
+ returning_token: start_token.into(),
+ select_items,
+ }
+ })
+ }
+}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index ce83ada..c499829 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -45,8 +45,6 @@
use crate::tokenizer::*;
use sqlparser::parser::ParserState::ColumnDefinition;
-mod alter;
-
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParserError {
TokenizerError(String),
@@ -61,6 +59,9 @@
};
}
+mod alter;
+mod merge;
+
#[cfg(feature = "std")]
/// Implementation [`RecursionCounter`] if std is available
mod recursion {
@@ -11882,7 +11883,7 @@
token => {
return Err(ParserError::ParserError(format!(
"Unexpected token in identifier: {token}"
- )))?
+ )))?;
}
}
}
@@ -12386,16 +12387,6 @@
Ok(Box::new(SetExpr::Delete(self.parse_delete(delete_token)?)))
}
- /// Parse a MERGE statement, returning a `Box`ed SetExpr
- ///
- /// This is used to reduce the size of the stack frames in debug builds
- fn parse_merge_setexpr_boxed(
- &mut self,
- merge_token: TokenWithSpan,
- ) -> Result<Box<SetExpr>, ParserError> {
- Ok(Box::new(SetExpr::Merge(self.parse_merge(merge_token)?)))
- }
-
pub fn parse_delete(&mut self, delete_token: TokenWithSpan) -> Result<Statement, ParserError> {
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
// `FROM` keyword is optional in BigQuery SQL.
@@ -17476,153 +17467,6 @@
})
}
- pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
- let mut clauses = vec![];
- loop {
- if !(self.parse_keyword(Keyword::WHEN)) {
- break;
- }
- let when_token = self.get_current_token().clone();
-
- let mut clause_kind = MergeClauseKind::Matched;
- if self.parse_keyword(Keyword::NOT) {
- clause_kind = MergeClauseKind::NotMatched;
- }
- self.expect_keyword_is(Keyword::MATCHED)?;
-
- if matches!(clause_kind, MergeClauseKind::NotMatched)
- && self.parse_keywords(&[Keyword::BY, Keyword::SOURCE])
- {
- clause_kind = MergeClauseKind::NotMatchedBySource;
- } else if matches!(clause_kind, MergeClauseKind::NotMatched)
- && self.parse_keywords(&[Keyword::BY, Keyword::TARGET])
- {
- clause_kind = MergeClauseKind::NotMatchedByTarget;
- }
-
- let predicate = if self.parse_keyword(Keyword::AND) {
- Some(self.parse_expr()?)
- } else {
- None
- };
-
- self.expect_keyword_is(Keyword::THEN)?;
-
- let merge_clause = match self.parse_one_of_keywords(&[
- Keyword::UPDATE,
- Keyword::INSERT,
- Keyword::DELETE,
- ]) {
- Some(Keyword::UPDATE) => {
- if matches!(
- clause_kind,
- MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
- ) {
- return parser_err!(
- format_args!("UPDATE is not allowed in a {clause_kind} merge clause"),
- self.get_current_token().span.start
- );
- }
-
- let update_token = self.get_current_token().clone();
- self.expect_keyword_is(Keyword::SET)?;
- MergeAction::Update {
- update_token: update_token.into(),
- assignments: self.parse_comma_separated(Parser::parse_assignment)?,
- }
- }
- Some(Keyword::DELETE) => {
- if matches!(
- clause_kind,
- MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
- ) {
- return parser_err!(
- format_args!("DELETE is not allowed in a {clause_kind} merge clause"),
- self.get_current_token().span.start
- );
- };
-
- let delete_token = self.get_current_token().clone();
- MergeAction::Delete {
- delete_token: delete_token.into(),
- }
- }
- Some(Keyword::INSERT) => {
- if !matches!(
- clause_kind,
- MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
- ) {
- return parser_err!(
- format_args!("INSERT is not allowed in a {clause_kind} merge clause"),
- self.get_current_token().span.start
- );
- };
-
- let insert_token = self.get_current_token().clone();
- let is_mysql = dialect_of!(self is MySqlDialect);
-
- let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?;
- let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
- && self.parse_keyword(Keyword::ROW)
- {
- (MergeInsertKind::Row, self.get_current_token().clone())
- } else {
- self.expect_keyword_is(Keyword::VALUES)?;
- let values_token = self.get_current_token().clone();
- let values = self.parse_values(is_mysql, false)?;
- (MergeInsertKind::Values(values), values_token)
- };
- MergeAction::Insert(MergeInsertExpr {
- insert_token: insert_token.into(),
- columns,
- kind_token: kind_token.into(),
- kind,
- })
- }
- _ => {
- return parser_err!(
- "expected UPDATE, DELETE or INSERT in merge clause",
- self.peek_token_ref().span.start
- );
- }
- };
- clauses.push(MergeClause {
- when_token: when_token.into(),
- clause_kind,
- predicate,
- action: merge_clause,
- });
- }
- Ok(clauses)
- }
-
- fn parse_output(
- &mut self,
- start_keyword: Keyword,
- start_token: TokenWithSpan,
- ) -> Result<OutputClause, ParserError> {
- let select_items = self.parse_projection()?;
- let into_table = if start_keyword == Keyword::OUTPUT && self.peek_keyword(Keyword::INTO) {
- self.expect_keyword_is(Keyword::INTO)?;
- Some(self.parse_select_into()?)
- } else {
- None
- };
-
- Ok(if start_keyword == Keyword::OUTPUT {
- OutputClause::Output {
- output_token: start_token.into(),
- select_items,
- into_table,
- }
- } else {
- OutputClause::Returning {
- returning_token: start_token.into(),
- select_items,
- }
- })
- }
-
fn parse_select_into(&mut self) -> Result<SelectInto, ParserError> {
let temporary = self
.parse_one_of_keywords(&[Keyword::TEMP, Keyword::TEMPORARY])
@@ -17639,32 +17483,6 @@
})
}
- pub fn parse_merge(&mut self, merge_token: TokenWithSpan) -> Result<Statement, ParserError> {
- let into = self.parse_keyword(Keyword::INTO);
-
- let table = self.parse_table_factor()?;
-
- self.expect_keyword_is(Keyword::USING)?;
- let source = self.parse_table_factor()?;
- self.expect_keyword_is(Keyword::ON)?;
- let on = self.parse_expr()?;
- let clauses = self.parse_merge_clauses()?;
- let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
- Some(keyword) => Some(self.parse_output(keyword, self.get_current_token().clone())?),
- None => None,
- };
-
- Ok(Statement::Merge {
- merge_token: merge_token.into(),
- into,
- table,
- source,
- on: Box::new(on),
- clauses,
- output,
- })
- }
-
fn parse_pragma_value(&mut self) -> Result<Value, ParserError> {
match self.parse_value()?.value {
v @ Value::SingleQuotedString(_) => Ok(v),
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs
index f2b9f2a..24b9efc 100644
--- a/tests/sqlparser_bigquery.rs
+++ b/tests/sqlparser_bigquery.rs
@@ -1806,15 +1806,16 @@
);
let insert_action = MergeAction::Insert(MergeInsertExpr {
insert_token: AttachedToken::empty(),
- columns: vec![Ident::new("product"), Ident::new("quantity")],
+ columns: vec![Ident::new("product").into(), Ident::new("quantity").into()],
kind_token: AttachedToken::empty(),
kind: MergeInsertKind::Values(Values {
value_keyword: false,
explicit_row: false,
rows: vec![vec![Expr::value(number("1")), Expr::value(number("2"))]],
}),
+ insert_predicate: None,
});
- let update_action = MergeAction::Update {
+ let update_action = MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
Assignment {
@@ -1826,17 +1827,19 @@
value: Expr::value(number("2")),
},
],
- };
+ update_predicate: None,
+ delete_predicate: None,
+ });
match bigquery_and_generic().verified_stmt(sql) {
- Statement::Merge {
+ Statement::Merge(Merge {
into,
table,
source,
on,
clauses,
..
- } => {
+ }) => {
assert!(!into);
assert_eq!(
TableFactor::Table {
@@ -1917,9 +1920,13 @@
predicate: Some(Expr::value(number("1"))),
action: MergeAction::Insert(MergeInsertExpr {
insert_token: AttachedToken::empty(),
- columns: vec![Ident::new("product"), Ident::new("quantity"),],
+ columns: vec![
+ Ident::new("product").into(),
+ Ident::new("quantity").into(),
+ ],
kind_token: AttachedToken::empty(),
kind: MergeInsertKind::Row,
+ insert_predicate: None,
})
},
MergeClause {
@@ -1928,9 +1935,13 @@
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
insert_token: AttachedToken::empty(),
- columns: vec![Ident::new("product"), Ident::new("quantity"),],
+ columns: vec![
+ Ident::new("product").into(),
+ Ident::new("quantity").into(),
+ ],
kind_token: AttachedToken::empty(),
kind: MergeInsertKind::Row,
+ insert_predicate: None,
})
},
MergeClause {
@@ -1941,7 +1952,8 @@
insert_token: AttachedToken::empty(),
columns: vec![],
kind_token: AttachedToken::empty(),
- kind: MergeInsertKind::Row
+ kind: MergeInsertKind::Row,
+ insert_predicate: None,
})
},
MergeClause {
@@ -1952,7 +1964,8 @@
insert_token: AttachedToken::empty(),
columns: vec![],
kind_token: AttachedToken::empty(),
- kind: MergeInsertKind::Row
+ kind: MergeInsertKind::Row,
+ insert_predicate: None,
})
},
MergeClause {
@@ -1975,7 +1988,7 @@
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
insert_token: AttachedToken::empty(),
- columns: vec![Ident::new("a"), Ident::new("b"),],
+ columns: vec![Ident::new("a").into(), Ident::new("b").into(),],
kind_token: AttachedToken::empty(),
kind: MergeInsertKind::Values(Values {
value_keyword: false,
@@ -1984,7 +1997,8 @@
Expr::value(number("1")),
Expr::Identifier(Ident::new("DEFAULT")),
]]
- })
+ }),
+ insert_predicate: None,
})
},
MergeClause {
@@ -2002,7 +2016,8 @@
Expr::value(number("1")),
Expr::Identifier(Ident::new("DEFAULT")),
]]
- })
+ }),
+ insert_predicate: None,
})
},
],
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index ccad67e..cfee262 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -9793,22 +9793,22 @@
let sql_no_into = "MERGE s.bar AS dest USING (SELECT * FROM s.foo) AS stg ON dest.D = stg.D AND dest.E = stg.E WHEN NOT MATCHED THEN INSERT (A, B, C) VALUES (stg.A, stg.B, stg.C) WHEN MATCHED AND dest.A = 'a' THEN UPDATE SET dest.F = stg.F, dest.G = stg.G WHEN MATCHED THEN DELETE";
match (verified_stmt(sql), verified_stmt(sql_no_into)) {
(
- Statement::Merge {
+ Statement::Merge(Merge {
into,
table,
source,
on,
clauses,
..
- },
- Statement::Merge {
+ }),
+ Statement::Merge(Merge {
into: no_into,
table: table_no_into,
source: source_no_into,
on: on_no_into,
clauses: clauses_no_into,
..
- },
+ }),
) => {
assert!(into);
assert!(!no_into);
@@ -9921,7 +9921,11 @@
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
insert_token: AttachedToken::empty(),
- columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
+ columns: vec![
+ Ident::new("A").into(),
+ Ident::new("B").into(),
+ Ident::new("C").into()
+ ],
kind_token: AttachedToken::empty(),
kind: MergeInsertKind::Values(Values {
value_keyword: false,
@@ -9941,6 +9945,7 @@
]),
]]
}),
+ insert_predicate: None,
}),
},
MergeClause {
@@ -9956,7 +9961,7 @@
(Value::SingleQuotedString("a".to_string())).with_empty_span()
)),
}),
- action: MergeAction::Update {
+ action: MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
Assignment {
@@ -9980,7 +9985,9 @@
]),
},
],
- },
+ update_predicate: None,
+ delete_predicate: None,
+ }),
},
MergeClause {
when_token: AttachedToken::empty(),
@@ -9999,6 +10006,45 @@
let sql = "MERGE INTO s.bar AS dest USING newArrivals AS S ON (1 > 1) WHEN NOT MATCHED THEN INSERT VALUES (stg.A, stg.B, stg.C)";
verified_stmt(sql);
+
+ // MERGE with predicates
+ let sql = "\
+MERGE INTO FOO \
+USING FOO_IMPORT \
+ON (FOO.ID = FOO_IMPORT.ID) \
+WHEN MATCHED THEN \
+UPDATE SET FOO.NAME = FOO_IMPORT.NAME \
+WHERE 1 = 1 \
+DELETE WHERE FOO.NAME LIKE '%.DELETE' \
+WHEN NOT MATCHED THEN \
+INSERT (ID, NAME) \
+VALUES (FOO_IMPORT.ID, UPPER(FOO_IMPORT.NAME)) \
+WHERE NOT FOO_IMPORT.NAME LIKE '%.DO_NOT_INSERT'";
+ all_dialects().verified_stmt(sql);
+
+ // MERGE with simple insert columns
+ let sql = "\
+MERGE INTO FOO USING FOO_IMPORT ON (FOO.ID = FOO_IMPORT.ID) \
+WHEN NOT MATCHED THEN \
+INSERT (ID, NAME) \
+VALUES (1, 'abc')";
+ all_dialects().verified_stmt(sql);
+
+ // MERGE with qualified insert columns
+ let sql = "\
+MERGE INTO FOO USING FOO_IMPORT ON (FOO.ID = FOO_IMPORT.ID) \
+WHEN NOT MATCHED THEN \
+INSERT (FOO.ID, FOO.NAME) \
+VALUES (1, 'abc')";
+ all_dialects().verified_stmt(sql);
+
+ // MERGE with schema qualified insert columns
+ let sql = "\
+MERGE INTO PLAYGROUND.FOO USING FOO_IMPORT ON (PLAYGROUND.FOO.ID = FOO_IMPORT.ID) \
+WHEN NOT MATCHED THEN \
+INSERT (PLAYGROUND.FOO.ID, PLAYGROUND.FOO.NAME) \
+VALUES (1, 'abc')";
+ all_dialects().verified_stmt(sql);
}
#[test]