Skip to content

Commit 6b55e51

Browse files
authored
add support for MERGE statement (#430)
* add support for MERGE statement Signed-off-by: Maciej Obuchowski <obuchowski.maciej@gmail.com> * fix lint errors Signed-off-by: Maciej Obuchowski <obuchowski.maciej@gmail.com>
1 parent c688bbb commit 6b55e51

File tree

4 files changed

+327
-0
lines changed

4 files changed

+327
-0
lines changed

src/ast/mod.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,19 @@ pub enum Statement {
956956
/// A SQL query that specifies what to explain
957957
statement: Box<Statement>,
958958
},
959+
// MERGE INTO statement, based on Snowflake. See <https://docs.snowflake.com/en/sql-reference/sql/merge.html>
960+
Merge {
961+
// Specifies the table to merge
962+
table: TableFactor,
963+
// Specifies the table or subquery to join with the target table
964+
source: Box<SetExpr>,
965+
// Specifies alias to the table that is joined with target table
966+
alias: Option<TableAlias>,
967+
// Specifies the expression on which to join the target table and source
968+
on: Box<Expr>,
969+
// Specifies the actions to perform when values match or do not match.
970+
clauses: Vec<MergeClause>,
971+
},
959972
}
960973

961974
impl fmt::Display for Statement {
@@ -1606,6 +1619,20 @@ impl fmt::Display for Statement {
16061619
write!(f, "NULL")
16071620
}
16081621
}
1622+
Statement::Merge {
1623+
table,
1624+
source,
1625+
alias,
1626+
on,
1627+
clauses,
1628+
} => {
1629+
write!(f, "MERGE INTO {} USING {} ", table, source)?;
1630+
if let Some(a) = alias {
1631+
write!(f, "as {} ", a)?;
1632+
};
1633+
write!(f, "ON {} ", on)?;
1634+
write!(f, "{}", display_separated(clauses, " "))
1635+
}
16091636
}
16101637
}
16111638
}
@@ -2137,6 +2164,68 @@ impl fmt::Display for SqliteOnConflict {
21372164
}
21382165
}
21392166

2167+
///
2168+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2169+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2170+
pub enum MergeClause {
2171+
MatchedUpdate {
2172+
predicate: Option<Expr>,
2173+
assignments: Vec<Assignment>,
2174+
},
2175+
MatchedDelete(Option<Expr>),
2176+
NotMatched {
2177+
predicate: Option<Expr>,
2178+
columns: Vec<Ident>,
2179+
values: Values,
2180+
},
2181+
}
2182+
2183+
impl fmt::Display for MergeClause {
2184+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2185+
use MergeClause::*;
2186+
write!(f, "WHEN")?;
2187+
match self {
2188+
MatchedUpdate {
2189+
predicate,
2190+
assignments,
2191+
} => {
2192+
write!(f, " MATCHED")?;
2193+
if let Some(pred) = predicate {
2194+
write!(f, " AND {}", pred)?;
2195+
}
2196+
write!(
2197+
f,
2198+
" THEN UPDATE SET {}",
2199+
display_comma_separated(assignments)
2200+
)
2201+
}
2202+
MatchedDelete(predicate) => {
2203+
write!(f, " MATCHED")?;
2204+
if let Some(pred) = predicate {
2205+
write!(f, " AND {}", pred)?;
2206+
}
2207+
write!(f, " THEN DELETE")
2208+
}
2209+
NotMatched {
2210+
predicate,
2211+
columns,
2212+
values,
2213+
} => {
2214+
write!(f, " NOT MATCHED")?;
2215+
if let Some(pred) = predicate {
2216+
write!(f, " AND {}", pred)?;
2217+
}
2218+
write!(
2219+
f,
2220+
" THEN INSERT ({}) {}",
2221+
display_comma_separated(columns),
2222+
values
2223+
)
2224+
}
2225+
}
2226+
}
2227+
}
2228+
21402229
#[cfg(test)]
21412230
mod tests {
21422231
use super::*;

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ define_keywords!(
295295
LOWER,
296296
MANAGEDLOCATION,
297297
MATCH,
298+
MATCHED,
298299
MATERIALIZED,
299300
MAX,
300301
MEMBER,

src/parser.rs

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ impl<'a> Parser<'a> {
187187
Keyword::DEALLOCATE => Ok(self.parse_deallocate()?),
188188
Keyword::EXECUTE => Ok(self.parse_execute()?),
189189
Keyword::PREPARE => Ok(self.parse_prepare()?),
190+
Keyword::MERGE => Ok(self.parse_merge()?),
190191
Keyword::REPLACE if dialect_of!(self is SQLiteDialect ) => {
191192
self.prev_token();
192193
Ok(self.parse_insert()?)
@@ -3892,6 +3893,104 @@ impl<'a> Parser<'a> {
38923893
comment,
38933894
})
38943895
}
3896+
3897+
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
3898+
let mut clauses: Vec<MergeClause> = vec![];
3899+
loop {
3900+
if self.peek_token() == Token::EOF {
3901+
break;
3902+
}
3903+
self.expect_keyword(Keyword::WHEN)?;
3904+
3905+
let is_not_matched = self.parse_keyword(Keyword::NOT);
3906+
self.expect_keyword(Keyword::MATCHED)?;
3907+
3908+
let predicate = if self.parse_keyword(Keyword::AND) {
3909+
Some(self.parse_expr()?)
3910+
} else {
3911+
None
3912+
};
3913+
3914+
self.expect_keyword(Keyword::THEN)?;
3915+
3916+
clauses.push(
3917+
match self.parse_one_of_keywords(&[
3918+
Keyword::UPDATE,
3919+
Keyword::INSERT,
3920+
Keyword::DELETE,
3921+
]) {
3922+
Some(Keyword::UPDATE) => {
3923+
if is_not_matched {
3924+
return Err(ParserError::ParserError(
3925+
"UPDATE in NOT MATCHED merge clause".to_string(),
3926+
));
3927+
}
3928+
self.expect_keyword(Keyword::SET)?;
3929+
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
3930+
MergeClause::MatchedUpdate {
3931+
predicate,
3932+
assignments,
3933+
}
3934+
}
3935+
Some(Keyword::DELETE) => {
3936+
if is_not_matched {
3937+
return Err(ParserError::ParserError(
3938+
"DELETE in NOT MATCHED merge clause".to_string(),
3939+
));
3940+
}
3941+
MergeClause::MatchedDelete(predicate)
3942+
}
3943+
Some(Keyword::INSERT) => {
3944+
if !is_not_matched {
3945+
return Err(ParserError::ParserError(
3946+
"INSERT in MATCHED merge clause".to_string(),
3947+
));
3948+
}
3949+
let columns = self.parse_parenthesized_column_list(Optional)?;
3950+
self.expect_keyword(Keyword::VALUES)?;
3951+
let values = self.parse_values()?;
3952+
MergeClause::NotMatched {
3953+
predicate,
3954+
columns,
3955+
values,
3956+
}
3957+
}
3958+
Some(_) => {
3959+
return Err(ParserError::ParserError(
3960+
"expected UPDATE, DELETE or INSERT in merge clause".to_string(),
3961+
))
3962+
}
3963+
None => {
3964+
return Err(ParserError::ParserError(
3965+
"expected UPDATE, DELETE or INSERT in merge clause".to_string(),
3966+
))
3967+
}
3968+
},
3969+
);
3970+
}
3971+
Ok(clauses)
3972+
}
3973+
3974+
pub fn parse_merge(&mut self) -> Result<Statement, ParserError> {
3975+
self.expect_keyword(Keyword::INTO)?;
3976+
3977+
let table = self.parse_table_factor()?;
3978+
3979+
self.expect_keyword(Keyword::USING)?;
3980+
let source = self.parse_query_body(0)?;
3981+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
3982+
self.expect_keyword(Keyword::ON)?;
3983+
let on = self.parse_expr()?;
3984+
let clauses = self.parse_merge_clauses()?;
3985+
3986+
Ok(Statement::Merge {
3987+
table,
3988+
source: Box::new(source),
3989+
alias,
3990+
on: Box::new(on),
3991+
clauses,
3992+
})
3993+
}
38953994
}
38963995

38973996
impl Word {

tests/sqlparser_common.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4204,6 +4204,144 @@ fn test_revoke() {
42044204
}
42054205
}
42064206

4207+
#[test]
4208+
fn parse_merge() {
4209+
let sql = "MERGE INTO 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";
4210+
match verified_stmt(sql) {
4211+
Statement::Merge {
4212+
table,
4213+
source,
4214+
alias,
4215+
on,
4216+
clauses,
4217+
} => {
4218+
assert_eq!(
4219+
table,
4220+
TableFactor::Table {
4221+
name: ObjectName(vec![Ident::new("s"), Ident::new("bar")]),
4222+
alias: Some(TableAlias {
4223+
name: Ident::new("dest"),
4224+
columns: vec![]
4225+
}),
4226+
args: vec![],
4227+
with_hints: vec![]
4228+
}
4229+
);
4230+
assert_eq!(
4231+
source,
4232+
Box::new(SetExpr::Query(Box::new(Query {
4233+
with: None,
4234+
body: SetExpr::Select(Box::new(Select {
4235+
distinct: false,
4236+
top: None,
4237+
projection: vec![SelectItem::Wildcard],
4238+
from: vec![TableWithJoins {
4239+
relation: TableFactor::Table {
4240+
name: ObjectName(vec![Ident::new("s"), Ident::new("foo")]),
4241+
alias: None,
4242+
args: vec![],
4243+
with_hints: vec![],
4244+
},
4245+
joins: vec![]
4246+
}],
4247+
lateral_views: vec![],
4248+
selection: None,
4249+
group_by: vec![],
4250+
cluster_by: vec![],
4251+
distribute_by: vec![],
4252+
sort_by: vec![],
4253+
having: None
4254+
})),
4255+
order_by: vec![],
4256+
limit: None,
4257+
offset: None,
4258+
fetch: None,
4259+
lock: None
4260+
})))
4261+
);
4262+
assert_eq!(
4263+
alias,
4264+
Some(TableAlias {
4265+
name: Ident::new("stg"),
4266+
columns: vec![]
4267+
})
4268+
);
4269+
assert_eq!(
4270+
on,
4271+
Box::new(Expr::BinaryOp {
4272+
left: Box::new(Expr::BinaryOp {
4273+
left: Box::new(Expr::CompoundIdentifier(vec![
4274+
Ident::new("dest"),
4275+
Ident::new("D")
4276+
])),
4277+
op: BinaryOperator::Eq,
4278+
right: Box::new(Expr::CompoundIdentifier(vec![
4279+
Ident::new("stg"),
4280+
Ident::new("D")
4281+
]))
4282+
}),
4283+
op: BinaryOperator::And,
4284+
right: Box::new(Expr::BinaryOp {
4285+
left: Box::new(Expr::CompoundIdentifier(vec![
4286+
Ident::new("dest"),
4287+
Ident::new("E")
4288+
])),
4289+
op: BinaryOperator::Eq,
4290+
right: Box::new(Expr::CompoundIdentifier(vec![
4291+
Ident::new("stg"),
4292+
Ident::new("E")
4293+
]))
4294+
})
4295+
})
4296+
);
4297+
assert_eq!(
4298+
clauses,
4299+
vec![
4300+
MergeClause::NotMatched {
4301+
predicate: None,
4302+
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
4303+
values: Values(vec![vec![
4304+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]),
4305+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]),
4306+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]),
4307+
]])
4308+
},
4309+
MergeClause::MatchedUpdate {
4310+
predicate: Some(Expr::BinaryOp {
4311+
left: Box::new(Expr::CompoundIdentifier(vec![
4312+
Ident::new("dest"),
4313+
Ident::new("A")
4314+
])),
4315+
op: BinaryOperator::Eq,
4316+
right: Box::new(Expr::Value(Value::SingleQuotedString(
4317+
"a".to_string()
4318+
)))
4319+
}),
4320+
assignments: vec![
4321+
Assignment {
4322+
id: vec![Ident::new("dest"), Ident::new("F")],
4323+
value: Expr::CompoundIdentifier(vec![
4324+
Ident::new("stg"),
4325+
Ident::new("F")
4326+
])
4327+
},
4328+
Assignment {
4329+
id: vec![Ident::new("dest"), Ident::new("G")],
4330+
value: Expr::CompoundIdentifier(vec![
4331+
Ident::new("stg"),
4332+
Ident::new("G")
4333+
])
4334+
}
4335+
]
4336+
},
4337+
MergeClause::MatchedDelete(None)
4338+
]
4339+
)
4340+
}
4341+
_ => unreachable!(),
4342+
}
4343+
}
4344+
42074345
#[test]
42084346
fn test_lock() {
42094347
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";

0 commit comments

Comments
 (0)