mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-10-09 13:40:22 +00:00
Support BigQuery MERGE
syntax (#1217)
This commit is contained in:
parent
b51f2a0c25
commit
0adf4c675c
5 changed files with 556 additions and 142 deletions
244
src/ast/mod.rs
244
src/ast/mod.rs
|
@ -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))]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 [
|
||||
|
|
|
@ -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";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue