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 /// RELEASE [ SAVEPOINT ] savepoint_name
/// ``` /// ```
ReleaseSavepoint { name: Ident }, ReleaseSavepoint { name: Ident },
/// A `MERGE` statement.
///
/// ```sql /// ```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 { Merge {
// optional INTO keyword /// optional INTO keyword
into: bool, into: bool,
// Specifies the table to merge /// Specifies the table to merge
table: TableFactor, 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, 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>, 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>, clauses: Vec<MergeClause>,
}, },
/// ```sql /// ```sql
@ -5549,72 +5552,193 @@ impl fmt::Display for CopyLegacyCsvOption {
} }
} }
/// `MERGE` Statement /// Variant of `WHEN` clause used within a `MERGE` Statement.
/// ///
/// Example:
/// ```sql /// ```sql
/// MERGE INTO <target_table> USING <source> ON <join_expr> { matchedClause | notMatchedClause } [ ... ] /// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE
/// ``` /// ```
/// /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
/// See [Snowflake documentation](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)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
pub enum MergeClause { pub enum MergeClauseKind {
MatchedUpdate { /// `WHEN MATCHED`
predicate: Option<Expr>, Matched,
assignments: Vec<Assignment>, /// `WHEN NOT MATCHED`
}, NotMatched,
MatchedDelete(Option<Expr>), /// `WHEN MATCHED BY TARGET`
NotMatched { ///
predicate: Option<Expr>, /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
columns: Vec<Ident>, NotMatchedByTarget,
values: Values, /// `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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use MergeClause::*;
write!(f, "WHEN")?;
match self { match self {
MatchedUpdate { 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 {
/// 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, predicate,
assignments, action,
} => { } = self;
write!(f, " MATCHED")?;
write!(f, "WHEN {clause_kind}")?;
if let Some(pred) = predicate { if let Some(pred) = predicate {
write!(f, " AND {pred}")?; write!(f, " AND {pred}")?;
} }
write!( write!(f, " THEN {action}")
f,
" THEN UPDATE SET {}",
display_comma_separated(assignments)
)
}
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
)
}
}
} }
} }

View file

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

View file

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

View file

@ -7475,19 +7475,32 @@ fn parse_merge() {
assert_eq!( assert_eq!(
clauses, clauses,
vec![ vec![
MergeClause::NotMatched { MergeClause {
clause_kind: MergeClauseKind::NotMatched,
predicate: None, predicate: None,
action: MergeAction::Insert(MergeInsertExpr {
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")], columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
values: Values { kind: MergeInsertKind::Values(Values {
explicit_row: false, explicit_row: false,
rows: vec![vec![ rows: vec![vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]), Expr::CompoundIdentifier(vec![
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]), Ident::new("stg"),
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]), Ident::new("A")
]),
Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("B")
]),
Expr::CompoundIdentifier(vec![
Ident::new("stg"),
Ident::new("C")
]),
]] ]]
}),
}),
}, },
}, MergeClause {
MergeClause::MatchedUpdate { clause_kind: MergeClauseKind::Matched,
predicate: Some(Expr::BinaryOp { predicate: Some(Expr::BinaryOp {
left: Box::new(Expr::CompoundIdentifier(vec![ left: Box::new(Expr::CompoundIdentifier(vec![
Ident::new("dest"), Ident::new("dest"),
@ -7498,6 +7511,7 @@ fn parse_merge() {
"a".to_string() "a".to_string()
))), ))),
}), }),
action: MergeAction::Update {
assignments: vec![ assignments: vec![
Assignment { Assignment {
id: vec![Ident::new("dest"), Ident::new("F")], id: vec![Ident::new("dest"), Ident::new("F")],
@ -7515,13 +7529,21 @@ fn parse_merge() {
}, },
], ],
}, },
MergeClause::MatchedDelete(None), },
MergeClause {
clause_kind: MergeClauseKind::Matched,
predicate: None,
action: MergeAction::Delete,
},
] ]
); );
assert_eq!(clauses, clauses_no_into); assert_eq!(clauses, clauses_no_into);
} }
_ => unreachable!(), _ => 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] #[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] #[test]
fn test_lock() { fn test_lock() {
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE"; let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";