mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-12-23 11:12:51 +00:00
Oracle: Support for MERGE predicates (#2101)
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Some checks failed
license / Release Audit Tool (RAT) (push) Has been cancelled
Rust / lint (push) Has been cancelled
Rust / benchmark-lint (push) Has been cancelled
Rust / codestyle (push) Has been cancelled
Rust / compile (push) Has been cancelled
Rust / docs (push) Has been cancelled
Rust / compile-no-std (push) Has been cancelled
Rust / test (beta) (push) Has been cancelled
Rust / test (nightly) (push) Has been cancelled
Rust / test (stable) (push) Has been cancelled
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
This commit is contained in:
parent
326f111808
commit
ca2d333dff
8 changed files with 759 additions and 534 deletions
340
src/ast/dml.rs
340
src/ast/dml.rs
|
|
@ -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,334 @@ impl Display for Update {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
301
src/ast/mod.rs
301
src/ast/mod.rs
|
|
@ -77,7 +77,10 @@ pub use self::ddl::{
|
|||
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<Vec<Ident>> for ObjectName {
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ 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> ]
|
||||
/// ```
|
||||
|
|
@ -5520,27 +5514,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,
|
||||
|
|
@ -8565,257 +8539,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)
|
||||
#[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))]
|
||||
|
|
|
|||
119
src/ast/spans.rs
119
src/ast/spans.rs
|
|
@ -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.
|
||||
|
|
@ -453,20 +453,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(),
|
||||
|
|
@ -927,6 +914,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 {
|
||||
|
|
@ -2421,12 +2419,7 @@ impl Spanned for MergeAction {
|
|||
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 @@ impl Spanned for MergeInsertExpr {
|
|||
},
|
||||
]
|
||||
.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 @@ 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: _,
|
||||
|
|
@ -2776,7 +2781,7 @@ WHERE id = 1
|
|||
on: _,
|
||||
clauses,
|
||||
output,
|
||||
} = &r[0]
|
||||
}) = &r[0]
|
||||
else {
|
||||
panic!("not a MERGE statement");
|
||||
};
|
||||
|
|
@ -2814,10 +2819,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: _,
|
||||
} = &clauses[1].action
|
||||
update_predicate: _,
|
||||
delete_predicate: _,
|
||||
}) = &clauses[1].action
|
||||
{
|
||||
assert_eq!(
|
||||
update_token.0.span,
|
||||
|
|
@ -2890,7 +2897,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
|
||||
|
|
@ -2924,7 +2931,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,
|
||||
|
|
@ -2937,4 +2944,44 @@ WHERE id = 1
|
|||
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))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -603,7 +603,7 @@ pub trait Dialect: Debug + Any {
|
|||
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 {
|
||||
|
|
|
|||
242
src/parser/merge.rs
Normal file
242
src/parser/merge.rs
Normal file
|
|
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -45,8 +45,6 @@ use crate::keywords::{Keyword, ALL_KEYWORDS};
|
|||
use crate::tokenizer::*;
|
||||
use sqlparser::parser::ParserState::ColumnDefinition;
|
||||
|
||||
mod alter;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParserError {
|
||||
TokenizerError(String),
|
||||
|
|
@ -61,6 +59,9 @@ macro_rules! parser_err {
|
|||
};
|
||||
}
|
||||
|
||||
mod alter;
|
||||
mod merge;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Implementation [`RecursionCounter`] if std is available
|
||||
mod recursion {
|
||||
|
|
@ -11882,7 +11883,7 @@ impl<'a> Parser<'a> {
|
|||
token => {
|
||||
return Err(ParserError::ParserError(format!(
|
||||
"Unexpected token in identifier: {token}"
|
||||
)))?
|
||||
)))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12386,16 +12387,6 @@ impl<'a> Parser<'a> {
|
|||
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 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
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 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
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),
|
||||
|
|
|
|||
|
|
@ -1806,15 +1806,16 @@ fn parse_merge() {
|
|||
);
|
||||
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 @@ fn parse_merge() {
|
|||
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 @@ fn parse_merge() {
|
|||
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 @@ fn parse_merge() {
|
|||
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 @@ fn parse_merge() {
|
|||
insert_token: AttachedToken::empty(),
|
||||
columns: vec![],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1952,7 +1964,8 @@ fn parse_merge() {
|
|||
insert_token: AttachedToken::empty(),
|
||||
columns: vec![],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1975,7 +1988,7 @@ fn parse_merge() {
|
|||
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 @@ fn parse_merge() {
|
|||
Expr::value(number("1")),
|
||||
Expr::Identifier(Ident::new("DEFAULT")),
|
||||
]]
|
||||
})
|
||||
}),
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -2002,7 +2016,8 @@ fn parse_merge() {
|
|||
Expr::value(number("1")),
|
||||
Expr::Identifier(Ident::new("DEFAULT")),
|
||||
]]
|
||||
})
|
||||
}),
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -9793,22 +9793,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);
|
||||
|
|
@ -9921,7 +9921,11 @@ fn parse_merge() {
|
|||
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 @@ fn parse_merge() {
|
|||
]),
|
||||
]]
|
||||
}),
|
||||
insert_predicate: None,
|
||||
}),
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -9956,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 {
|
||||
|
|
@ -9980,7 +9985,9 @@ fn parse_merge() {
|
|||
]),
|
||||
},
|
||||
],
|
||||
},
|
||||
update_predicate: None,
|
||||
delete_predicate: None,
|
||||
}),
|
||||
},
|
||||
MergeClause {
|
||||
when_token: AttachedToken::empty(),
|
||||
|
|
@ -9999,6 +10006,45 @@ fn parse_merge() {
|
|||
|
||||
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]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue