Support BigQuery MERGE syntax (#1217)

This commit is contained in:
Ifeanyi Ubah 2024-04-26 20:00:54 +02:00 committed by GitHub
parent b51f2a0c25
commit 0adf4c675c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 556 additions and 142 deletions

View file

@ -2549,20 +2549,23 @@ pub enum Statement {
/// RELEASE [ SAVEPOINT ] savepoint_name
/// ```
ReleaseSavepoint { name: Ident },
/// A `MERGE` statement.
///
/// ```sql
/// MERGE INTO <statement>
/// MERGE INTO <target_table> USING <source> ON <join_expr> { matchedClause | notMatchedClause } [ ... ]
/// ```
/// Based on Snowflake. See <https://docs.snowflake.com/en/sql-reference/sql/merge.html>
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
Merge {
// optional INTO keyword
/// optional INTO keyword
into: bool,
// Specifies the table to merge
/// Specifies the table to merge
table: TableFactor,
// Specifies the table or subquery to join with the target table
/// 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
/// 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.
/// Specifies the actions to perform when values match or do not match.
clauses: Vec<MergeClause>,
},
/// ```sql
@ -5549,75 +5552,196 @@ impl fmt::Display for CopyLegacyCsvOption {
}
}
/// `MERGE` Statement
/// Variant of `WHEN` clause used within a `MERGE` Statement.
///
/// Example:
/// ```sql
/// MERGE INTO <target_table> USING <source> ON <join_expr> { matchedClause | notMatchedClause } [ ... ]
/// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE
/// ```
///
/// See [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// [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 MergeClause {
MatchedUpdate {
predicate: Option<Expr>,
assignments: Vec<Assignment>,
},
MatchedDelete(Option<Expr>),
NotMatched {
predicate: Option<Expr>,
columns: Vec<Ident>,
values: Values,
},
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 fmt::Display for MergeClause {
impl Display for MergeClauseKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use MergeClause::*;
write!(f, "WHEN")?;
match self {
MatchedUpdate {
predicate,
assignments,
} => {
write!(f, " MATCHED")?;
if let Some(pred) = predicate {
write!(f, " AND {pred}")?;
}
write!(
f,
" THEN UPDATE SET {}",
display_comma_separated(assignments)
)
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}")
}
MatchedDelete(predicate) => {
write!(f, " MATCHED")?;
if let Some(pred) = predicate {
write!(f, " AND {pred}")?;
}
write!(f, " THEN DELETE")
}
NotMatched {
predicate,
columns,
values,
} => {
write!(f, " NOT MATCHED")?;
if let Some(pred) = predicate {
write!(f, " AND {pred}")?;
}
write!(
f,
" THEN INSERT ({}) {}",
display_comma_separated(columns),
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 {
/// Columns (if any) specified by the insert.
///
/// Example:
/// ```sql
/// INSERT (product, quantity) VALUES(product, quantity)
/// INSERT (product, quantity) ROW
/// ```
pub columns: Vec<Ident>,
/// 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 { assignments: Vec<Assignment> },
/// A plain `DELETE` clause
Delete,
}
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 {
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 {
clause_kind,
predicate,
action,
} = self;
write!(f, "WHEN {clause_kind}")?;
if let Some(pred) = predicate {
write!(f, " AND {pred}")?;
}
write!(f, " THEN {action}")
}
}
#[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

@ -637,6 +637,7 @@ define_keywords!(
SNAPSHOT,
SOME,
SORT,
SOURCE,
SPATIAL,
SPECIFIC,
SPECIFICTYPE,
@ -676,6 +677,7 @@ define_keywords!(
TABLE,
TABLES,
TABLESAMPLE,
TARGET,
TBLPROPERTIES,
TEMP,
TEMPORARY,

View file

@ -9777,16 +9777,29 @@ impl<'a> Parser<'a> {
}
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
let mut clauses: Vec<MergeClause> = vec![];
let mut clauses = vec![];
loop {
if self.peek_token() == Token::EOF || self.peek_token() == Token::SemiColon {
break;
}
self.expect_keyword(Keyword::WHEN)?;
let is_not_matched = self.parse_keyword(Keyword::NOT);
let mut clause_kind = MergeClauseKind::Matched;
if self.parse_keyword(Keyword::NOT) {
clause_kind = MergeClauseKind::NotMatched;
}
self.expect_keyword(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 {
@ -9795,61 +9808,70 @@ impl<'a> Parser<'a> {
self.expect_keyword(Keyword::THEN)?;
clauses.push(
match self.parse_one_of_keywords(&[
Keyword::UPDATE,
Keyword::INSERT,
Keyword::DELETE,
]) {
Some(Keyword::UPDATE) => {
if is_not_matched {
return Err(ParserError::ParserError(
"UPDATE in NOT MATCHED merge clause".to_string(),
));
}
self.expect_keyword(Keyword::SET)?;
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
MergeClause::MatchedUpdate {
predicate,
assignments,
}
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 Err(ParserError::ParserError(format!(
"UPDATE is not allowed in a {clause_kind} merge clause"
)));
}
Some(Keyword::DELETE) => {
if is_not_matched {
return Err(ParserError::ParserError(
"DELETE in NOT MATCHED merge clause".to_string(),
));
}
MergeClause::MatchedDelete(predicate)
self.expect_keyword(Keyword::SET)?;
MergeAction::Update {
assignments: self.parse_comma_separated(Parser::parse_assignment)?,
}
Some(Keyword::INSERT) => {
if !is_not_matched {
return Err(ParserError::ParserError(
"INSERT in MATCHED merge clause".to_string(),
));
}
let is_mysql = dialect_of!(self is MySqlDialect);
let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?;
}
Some(Keyword::DELETE) => {
if matches!(
clause_kind,
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
) {
return Err(ParserError::ParserError(format!(
"DELETE is not allowed in a {clause_kind} merge clause"
)));
}
MergeAction::Delete
}
Some(Keyword::INSERT) => {
if !matches!(
clause_kind,
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
) {
return Err(ParserError::ParserError(format!(
"INSERT is not allowed in a {clause_kind} merge clause"
)));
}
let is_mysql = dialect_of!(self is MySqlDialect);
let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?;
let kind = if dialect_of!(self is BigQueryDialect | GenericDialect)
&& self.parse_keyword(Keyword::ROW)
{
MergeInsertKind::Row
} else {
self.expect_keyword(Keyword::VALUES)?;
let values = self.parse_values(is_mysql)?;
MergeClause::NotMatched {
predicate,
columns,
values,
}
}
Some(_) => {
return Err(ParserError::ParserError(
"expected UPDATE, DELETE or INSERT in merge clause".to_string(),
));
}
None => {
return Err(ParserError::ParserError(
"expected UPDATE, DELETE or INSERT in merge clause".to_string(),
));
}
},
);
MergeInsertKind::Values(values)
};
MergeAction::Insert(MergeInsertExpr { columns, kind })
}
_ => {
return Err(ParserError::ParserError(
"expected UPDATE, DELETE or INSERT in merge clause".to_string(),
));
}
};
clauses.push(MergeClause {
clause_kind,
predicate,
action: merge_clause,
});
}
Ok(clauses)
}

View file

@ -1074,6 +1074,225 @@ fn parse_join_constraint_unnest_alias() {
);
}
#[test]
fn parse_merge() {
let sql = concat!(
"MERGE inventory AS T USING newArrivals AS S ON false ",
"WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) VALUES (1, 2) ",
"WHEN NOT MATCHED BY TARGET AND 1 THEN INSERT (product, quantity) VALUES (1, 2) ",
"WHEN NOT MATCHED BY TARGET THEN INSERT (product, quantity) VALUES (1, 2) ",
"WHEN NOT MATCHED BY SOURCE AND 2 THEN DELETE ",
"WHEN NOT MATCHED BY SOURCE THEN DELETE ",
"WHEN NOT MATCHED BY SOURCE AND 1 THEN UPDATE SET a = 1, b = 2 ",
"WHEN NOT MATCHED AND 1 THEN INSERT (product, quantity) ROW ",
"WHEN NOT MATCHED THEN INSERT (product, quantity) ROW ",
"WHEN NOT MATCHED AND 1 THEN INSERT ROW ",
"WHEN NOT MATCHED THEN INSERT ROW ",
"WHEN MATCHED AND 1 THEN DELETE ",
"WHEN MATCHED THEN UPDATE SET a = 1, b = 2 ",
"WHEN NOT MATCHED THEN INSERT (a, b) VALUES (1, DEFAULT) ",
"WHEN NOT MATCHED THEN INSERT VALUES (1, DEFAULT)",
);
let insert_action = MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("product"), Ident::new("quantity")],
kind: MergeInsertKind::Values(Values {
explicit_row: false,
rows: vec![vec![Expr::Value(number("1")), Expr::Value(number("2"))]],
}),
});
let update_action = MergeAction::Update {
assignments: vec![
Assignment {
id: vec![Ident::new("a")],
value: Expr::Value(number("1")),
},
Assignment {
id: vec![Ident::new("b")],
value: Expr::Value(number("2")),
},
],
};
match bigquery_and_generic().verified_stmt(sql) {
Statement::Merge {
into,
table,
source,
on,
clauses,
} => {
assert!(!into);
assert_eq!(
TableFactor::Table {
name: ObjectName(vec![Ident::new("inventory")]),
alias: Some(TableAlias {
name: Ident::new("T"),
columns: vec![],
}),
args: Default::default(),
with_hints: Default::default(),
version: Default::default(),
partitions: Default::default(),
},
table
);
assert_eq!(
TableFactor::Table {
name: ObjectName(vec![Ident::new("newArrivals")]),
alias: Some(TableAlias {
name: Ident::new("S"),
columns: vec![],
}),
args: Default::default(),
with_hints: Default::default(),
version: Default::default(),
partitions: Default::default(),
},
source
);
assert_eq!(Expr::Value(Value::Boolean(false)), *on);
assert_eq!(
vec![
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: Some(Expr::Value(number("1"))),
action: insert_action.clone(),
},
MergeClause {
clause_kind: MergeClauseKind::NotMatchedByTarget,
predicate: Some(Expr::Value(number("1"))),
action: insert_action.clone(),
},
MergeClause {
clause_kind: MergeClauseKind::NotMatchedByTarget,
predicate: None,
action: insert_action,
},
MergeClause {
clause_kind: MergeClauseKind::NotMatchedBySource,
predicate: Some(Expr::Value(number("2"))),
action: MergeAction::Delete
},
MergeClause {
clause_kind: MergeClauseKind::NotMatchedBySource,
predicate: None,
action: MergeAction::Delete
},
MergeClause {
clause_kind: MergeClauseKind::NotMatchedBySource,
predicate: Some(Expr::Value(number("1"))),
action: update_action.clone(),
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: Some(Expr::Value(number("1"))),
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("product"), Ident::new("quantity"),],
kind: MergeInsertKind::Row,
})
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("product"), Ident::new("quantity"),],
kind: MergeInsertKind::Row,
})
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: Some(Expr::Value(number("1"))),
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![],
kind: MergeInsertKind::Row
})
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![],
kind: MergeInsertKind::Row
})
},
MergeClause {
clause_kind: MergeClauseKind::Matched,
predicate: Some(Expr::Value(number("1"))),
action: MergeAction::Delete,
},
MergeClause {
clause_kind: MergeClauseKind::Matched,
predicate: None,
action: update_action,
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("a"), Ident::new("b"),],
kind: MergeInsertKind::Values(Values {
explicit_row: false,
rows: vec![vec![
Expr::Value(number("1")),
Expr::Identifier(Ident::new("DEFAULT")),
]]
})
})
},
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![],
kind: MergeInsertKind::Values(Values {
explicit_row: false,
rows: vec![vec![
Expr::Value(number("1")),
Expr::Identifier(Ident::new("DEFAULT")),
]]
})
})
},
],
clauses
);
}
_ => unreachable!(),
}
}
#[test]
fn parse_merge_invalid_statements() {
let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
for (sql, err_msg) in [
(
"MERGE T USING U ON TRUE WHEN MATCHED BY TARGET AND 1 THEN DELETE",
"Expected THEN, found: BY",
),
(
"MERGE T USING U ON TRUE WHEN MATCHED BY SOURCE AND 1 THEN DELETE",
"Expected THEN, found: BY",
),
(
"MERGE T USING U ON TRUE WHEN NOT MATCHED BY SOURCE THEN INSERT(a) VALUES (b)",
"INSERT is not allowed in a NOT MATCHED BY SOURCE merge clause",
),
(
"MERGE INTO T USING U ON TRUE WHEN NOT MATCHED BY TARGET THEN DELETE",
"DELETE is not allowed in a NOT MATCHED BY TARGET merge clause",
),
(
"MERGE INTO T USING U ON TRUE WHEN NOT MATCHED BY TARGET THEN UPDATE SET a = b",
"UPDATE is not allowed in a NOT MATCHED BY TARGET merge clause",
),
] {
let res = dialects.parse_sql_statements(sql);
assert_eq!(
ParserError::ParserError(err_msg.to_string()),
res.unwrap_err()
);
}
}
#[test]
fn parse_trailing_comma() {
for (sql, canonical) in [

View file

@ -7475,19 +7475,32 @@ fn parse_merge() {
assert_eq!(
clauses,
vec![
MergeClause::NotMatched {
MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None,
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
values: Values {
explicit_row: false,
rows: vec![vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]),
]]
},
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
kind: MergeInsertKind::Values(Values {
explicit_row: false,
rows: vec![vec![
Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("A")
]),
Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("B")
]),
Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("C")
]),
]]
}),
}),
},
MergeClause::MatchedUpdate {
MergeClause {
clause_kind: MergeClauseKind::Matched,
predicate: Some(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("dest"),
@ -7498,30 +7511,39 @@ fn parse_merge() {
"a".to_string()
))),
}),
assignments: vec![
Assignment {
id: vec![Ident::new("dest"), Ident::new("F")],
value: Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("F"),
]),
},
Assignment {
id: vec![Ident::new("dest"), Ident::new("G")],
value: Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("G"),
]),
},
],
action: MergeAction::Update {
assignments: vec![
Assignment {
id: vec![Ident::new("dest"), Ident::new("F")],
value: Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("F"),
]),
},
Assignment {
id: vec![Ident::new("dest"), Ident::new("G")],
value: Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("G"),
]),
},
],
},
},
MergeClause {
clause_kind: MergeClauseKind::Matched,
predicate: None,
action: MergeAction::Delete,
},
MergeClause::MatchedDelete(None),
]
);
assert_eq!(clauses, clauses_no_into);
}
_ => unreachable!(),
}
};
let sql = "MERGE INTO s.bar AS dest USING newArrivals AS S ON false WHEN NOT MATCHED THEN INSERT VALUES (stg.A, stg.B, stg.C)";
verified_stmt(sql);
}
#[test]
@ -7551,6 +7573,31 @@ fn test_merge_with_delimiter() {
}
}
#[test]
fn test_merge_invalid_statements() {
let dialects = all_dialects();
for (sql, err_msg) in [
(
"MERGE INTO T USING U ON TRUE WHEN NOT MATCHED THEN UPDATE SET a = b",
"UPDATE is not allowed in a NOT MATCHED merge clause",
),
(
"MERGE INTO T USING U ON TRUE WHEN NOT MATCHED THEN DELETE",
"DELETE is not allowed in a NOT MATCHED merge clause",
),
(
"MERGE INTO T USING U ON TRUE WHEN MATCHED THEN INSERT(a) VALUES(b)",
"INSERT is not allowed in a MATCHED merge clause",
),
] {
let res = dialects.parse_sql_statements(sql);
assert_eq!(
ParserError::ParserError(err_msg.to_string()),
res.unwrap_err()
);
}
}
#[test]
fn test_lock() {
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";