Merge as top-level struct

This commit is contained in:
Petr Novotnik 2025-11-23 09:11:43 +01:00 committed by xitep
parent 29f2fd2251
commit 202801f323
7 changed files with 431 additions and 406 deletions

View file

@ -24,13 +24,16 @@ use serde::{Deserialize, Serialize};
#[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,340 @@ impl Display for Update {
Ok(())
}
}
/// 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<Ident>,
/// 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)
///
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
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)
///
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
pub update_predicate: Option<Expr>,
/// `delete_clause` for the update "delete where" (Oracle specific)
///
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
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)
}
}
}
}

View file

@ -77,7 +77,10 @@ pub use self::ddl::{
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,
@ -4082,22 +4085,7 @@ pub enum 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)
/// [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> ]
/// ```
@ -5514,27 +5502,7 @@ impl fmt::Display for Statement {
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,
@ -8559,288 +8527,6 @@ impl fmt::Display for CopyLegacyCsvOption {
}
}
/// 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)
/// [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<Ident>,
/// 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)
///
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
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(())
}
}
/// 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 {
/// The `UPDATE` token that starts the sub-expression.
update_token: AttachedToken,
/// The update assiment expressions
assignments: Vec<Assignment>,
/// `where_clause` for the update (Oralce specific)
///
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
update_predicate: Option<Expr>,
/// `delete_clause` for the update "delete where" (Oracle specific)
///
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
delete_predicate: Option<Expr>,
},
/// 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_token: _,
assignments,
update_predicate,
delete_predicate,
} => {
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
if let Some(predicate) = update_predicate.as_ref() {
write!(f, " WHERE {predicate}")?;
}
if let Some(predicate) = delete_predicate.as_ref() {
write!(f, " DELETE WHERE {predicate}")?;
}
Ok(())
}
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))]

View file

@ -38,15 +38,15 @@ use super::{
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.
@ -451,20 +451,7 @@ impl Spanned for Statement {
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(),
@ -925,6 +912,17 @@ impl Spanned for Update {
}
}
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 {
@ -2419,17 +2417,7 @@ impl Spanned for MergeAction {
fn span(&self) -> Span {
match self {
MergeAction::Insert(expr) => expr.span(),
MergeAction::Update {
update_token,
assignments,
update_predicate,
delete_predicate,
} => union_spans(
core::iter::once(update_token.0.span)
.chain(assignments.iter().map(Spanned::span))
.chain(update_predicate.iter().map(Spanned::span))
.chain(delete_predicate.iter().map(Spanned::span)),
),
MergeAction::Update(expr) => expr.span(),
MergeAction::Delete { delete_token } => delete_token.0.span,
}
}
@ -2453,6 +2441,17 @@ impl Spanned for MergeInsertExpr {
}
}
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)),
)
}
}
impl Spanned for OutputClause {
fn span(&self) -> Span {
match self {
@ -2772,7 +2771,7 @@ WHERE id = 1
assert_eq!(stmt_span.end, (16, 67).into());
// ~ individual tokens within the statement
let Statement::Merge {
let Statement::Merge(Merge {
merge_token,
into: _,
table: _,
@ -2780,7 +2779,7 @@ WHERE id = 1
on: _,
clauses,
output,
} = &r[0]
}) = &r[0]
else {
panic!("not a MERGE statement");
};
@ -2818,12 +2817,12 @@ WHERE id = 1
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: _,
update_predicate: _,
delete_predicate: _,
} = &clauses[1].action
}) = &clauses[1].action
{
assert_eq!(
update_token.0.span,
@ -2896,7 +2895,7 @@ WHERE id = 1
);
// ~ 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
@ -2930,7 +2929,7 @@ WHERE id = 1
);
// ~ 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,

View file

@ -17,8 +17,8 @@ use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
use crate::{
ast::{
Ident, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
ObjectName, ObjectNamePart, SetExpr, Statement, TableFactor,
Ident, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
MergeUpdateExpr, ObjectName, ObjectNamePart, OutputClause, SetExpr, Statement, TableFactor,
},
dialect::{BigQueryDialect, GenericDialect, MySqlDialect},
keywords::Keyword,
@ -54,7 +54,7 @@ impl Parser<'_> {
None => None,
};
Ok(Statement::Merge {
Ok(Statement::Merge(Merge {
merge_token: merge_token.into(),
into,
table,
@ -62,7 +62,7 @@ impl Parser<'_> {
on: Box::new(on),
clauses,
output,
})
}))
}
fn parse_merge_clauses(
@ -134,12 +134,12 @@ impl Parser<'_> {
} else {
None
};
MergeAction::Update {
MergeAction::Update(MergeUpdateExpr {
update_token: update_token.into(),
assignments,
update_predicate,
delete_predicate,
}
})
}
Some(Keyword::DELETE) => {
if matches!(
@ -273,6 +273,33 @@ impl Parser<'_> {
self.parse_parenthesized_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,
}
})
}
}
/// Helper to unqualify a list of columns with either a qualified prefix or a

View file

@ -17304,33 +17304,6 @@ impl<'a> Parser<'a> {
})
}
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])

View file

@ -1815,7 +1815,7 @@ fn parse_merge() {
}),
insert_predicate: None,
});
let update_action = MergeAction::Update {
let update_action = MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
Assignment {
@ -1829,17 +1829,17 @@ fn parse_merge() {
],
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 {

View file

@ -9797,22 +9797,22 @@ fn parse_merge() {
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);
@ -9961,7 +9961,7 @@ fn parse_merge() {
(Value::SingleQuotedString("a".to_string())).with_empty_span()
)),
}),
action: MergeAction::Update {
action: MergeAction::Update(MergeUpdateExpr {
update_token: AttachedToken::empty(),
assignments: vec![
Assignment {
@ -9987,7 +9987,7 @@ fn parse_merge() {
],
update_predicate: None,
delete_predicate: None,
},
}),
},
MergeClause {
when_token: AttachedToken::empty(),