Skip to content

Commit d46398f

Browse files
committed
add support for MERGE statement
Signed-off-by: Maciej Obuchowski <obuchowski.maciej@gmail.com>
1 parent 481551c commit d46398f

File tree

4 files changed

+313
-0
lines changed

4 files changed

+313
-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: 85 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()?)
@@ -3890,6 +3891,90 @@ impl<'a> Parser<'a> {
38903891
comment,
38913892
})
38923893
}
3894+
3895+
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
3896+
let mut clauses: Vec<MergeClause> = vec![];
3897+
loop {
3898+
if self.peek_token() == Token::EOF {
3899+
break;
3900+
}
3901+
self.expect_keyword(Keyword::WHEN)?;
3902+
3903+
let is_not_matched = self.parse_keyword(Keyword::NOT);
3904+
self.expect_keyword(Keyword::MATCHED)?;
3905+
3906+
let predicate = if self.parse_keyword(Keyword::AND) {
3907+
Some(self.parse_expr()?)
3908+
} else {
3909+
None
3910+
};
3911+
3912+
self.expect_keyword(Keyword::THEN)?;
3913+
3914+
clauses.push(
3915+
match self.parse_one_of_keywords(&[
3916+
Keyword::UPDATE,
3917+
Keyword::INSERT,
3918+
Keyword::DELETE,
3919+
]) {
3920+
Some(Keyword::UPDATE) => {
3921+
if is_not_matched {
3922+
parser_err!("UPDATE in NOT MATCHED merge clause")?;
3923+
}
3924+
self.expect_keyword(Keyword::SET)?;
3925+
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
3926+
MergeClause::MatchedUpdate {
3927+
predicate,
3928+
assignments,
3929+
}
3930+
}
3931+
Some(Keyword::DELETE) => {
3932+
if is_not_matched {
3933+
parser_err!("DELETE in NOT MATCHED merge clause")?;
3934+
}
3935+
MergeClause::MatchedDelete(predicate)
3936+
}
3937+
Some(Keyword::INSERT) => {
3938+
if !is_not_matched {
3939+
parser_err!("INSERT in MATCHED merge clause")?;
3940+
}
3941+
let columns = self.parse_parenthesized_column_list(Optional)?;
3942+
self.expect_keyword(Keyword::VALUES)?;
3943+
let values = self.parse_values()?;
3944+
MergeClause::NotMatched {
3945+
predicate,
3946+
columns,
3947+
values,
3948+
}
3949+
}
3950+
Some(_) => parser_err!("expected UPDATE, DELETE or INSERT in merge clause")?,
3951+
None => parser_err!("expected UPDATE, DELETE or INSERT in merge clause")?,
3952+
},
3953+
);
3954+
}
3955+
Ok(clauses)
3956+
}
3957+
3958+
pub fn parse_merge(&mut self) -> Result<Statement, ParserError> {
3959+
self.expect_keyword(Keyword::INTO)?;
3960+
3961+
let table = self.parse_table_factor()?;
3962+
3963+
self.expect_keyword(Keyword::USING)?;
3964+
let source = self.parse_query_body(0)?;
3965+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
3966+
self.expect_keyword(Keyword::ON)?;
3967+
let on = self.parse_expr()?;
3968+
let clauses = self.parse_merge_clauses()?;
3969+
3970+
Ok(Statement::Merge {
3971+
table,
3972+
source: Box::new(source),
3973+
alias,
3974+
on: Box::new(on),
3975+
clauses,
3976+
})
3977+
}
38933978
}
38943979

38953980
impl Word {

tests/sqlparser_common.rs

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

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

0 commit comments

Comments
 (0)