Skip to content

Commit 6758868

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

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
@@ -915,6 +915,19 @@ pub enum Statement {
915915
/// A SQL query that specifies what to explain
916916
statement: Box<Statement>,
917917
},
918+
// MERGE INTO statement, based on Snowflake. See <https://docs.snowflake.com/en/sql-reference/sql/merge.html>
919+
Merge {
920+
// Specifies the table to merge
921+
table: TableFactor,
922+
// Specifies the table or subquery to join with the target table
923+
source: Box<SetExpr>,
924+
// Specifies alias to the table that is joined with target table
925+
alias: Option<TableAlias>,
926+
// Specifies the expression on which to join the target table and source
927+
on: Box<Expr>,
928+
// Specifies the actions to perform when values match or do not match.
929+
clauses: Vec<MergeClause>,
930+
},
918931
}
919932

920933
impl fmt::Display for Statement {
@@ -1561,6 +1574,20 @@ impl fmt::Display for Statement {
15611574
write!(f, "NULL")
15621575
}
15631576
}
1577+
Statement::Merge {
1578+
table,
1579+
source,
1580+
alias,
1581+
on,
1582+
clauses,
1583+
} => {
1584+
write!(f, "MERGE INTO {} USING {} ", table, source)?;
1585+
if let Some(a) = alias {
1586+
write!(f, "as {} ", a)?;
1587+
};
1588+
write!(f, "ON {} ", on)?;
1589+
write!(f, "{}", display_separated(clauses, " "))
1590+
}
15641591
}
15651592
}
15661593
}
@@ -2092,6 +2119,68 @@ impl fmt::Display for SqliteOnConflict {
20922119
}
20932120
}
20942121

2122+
///
2123+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2124+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2125+
pub enum MergeClause {
2126+
MatchedUpdate {
2127+
predicate: Option<Expr>,
2128+
assignments: Vec<Assignment>,
2129+
},
2130+
MatchedDelete(Option<Expr>),
2131+
NotMatched {
2132+
predicate: Option<Expr>,
2133+
columns: Vec<Ident>,
2134+
values: Values,
2135+
},
2136+
}
2137+
2138+
impl fmt::Display for MergeClause {
2139+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2140+
use MergeClause::*;
2141+
write!(f, "WHEN")?;
2142+
match self {
2143+
MatchedUpdate {
2144+
predicate,
2145+
assignments,
2146+
} => {
2147+
write!(f, " MATCHED")?;
2148+
if let Some(pred) = predicate {
2149+
write!(f, " AND {}", pred)?;
2150+
}
2151+
write!(
2152+
f,
2153+
" THEN UPDATE SET {}",
2154+
display_comma_separated(assignments)
2155+
)
2156+
}
2157+
MatchedDelete(predicate) => {
2158+
write!(f, " MATCHED")?;
2159+
if let Some(pred) = predicate {
2160+
write!(f, " AND {}", pred)?;
2161+
}
2162+
write!(f, " THEN DELETE")
2163+
}
2164+
NotMatched {
2165+
predicate,
2166+
columns,
2167+
values,
2168+
} => {
2169+
write!(f, " NOT MATCHED")?;
2170+
if let Some(pred) = predicate {
2171+
write!(f, " AND {}", pred)?;
2172+
}
2173+
write!(
2174+
f,
2175+
" THEN INSERT ({}) {}",
2176+
display_comma_separated(columns),
2177+
values
2178+
)
2179+
}
2180+
}
2181+
}
2182+
}
2183+
20952184
#[cfg(test)]
20962185
mod tests {
20972186
use super::*;

src/keywords.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ define_keywords!(
287287
LOWER,
288288
MANAGEDLOCATION,
289289
MATCH,
290+
MATCHED,
290291
MATERIALIZED,
291292
MAX,
292293
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()?)
@@ -3803,6 +3804,90 @@ impl<'a> Parser<'a> {
38033804
comment,
38043805
})
38053806
}
3807+
3808+
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
3809+
let mut clauses: Vec<MergeClause> = vec![];
3810+
loop {
3811+
if self.peek_token() == Token::EOF {
3812+
break;
3813+
}
3814+
self.expect_keyword(Keyword::WHEN)?;
3815+
3816+
let is_not_matched = self.parse_keyword(Keyword::NOT);
3817+
self.expect_keyword(Keyword::MATCHED)?;
3818+
3819+
let predicate = if self.parse_keyword(Keyword::AND) {
3820+
Some(self.parse_expr()?)
3821+
} else {
3822+
None
3823+
};
3824+
3825+
self.expect_keyword(Keyword::THEN)?;
3826+
3827+
clauses.push(
3828+
match self.parse_one_of_keywords(&[
3829+
Keyword::UPDATE,
3830+
Keyword::INSERT,
3831+
Keyword::DELETE,
3832+
]) {
3833+
Some(Keyword::UPDATE) => {
3834+
if is_not_matched {
3835+
parser_err!("UPDATE in NOT MATCHED merge clause")?;
3836+
}
3837+
self.expect_keyword(Keyword::SET)?;
3838+
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
3839+
MergeClause::MatchedUpdate {
3840+
predicate,
3841+
assignments,
3842+
}
3843+
}
3844+
Some(Keyword::DELETE) => {
3845+
if is_not_matched {
3846+
parser_err!("DELETE in NOT MATCHED merge clause")?;
3847+
}
3848+
MergeClause::MatchedDelete(predicate)
3849+
}
3850+
Some(Keyword::INSERT) => {
3851+
if !is_not_matched {
3852+
parser_err!("INSERT in MATCHED merge clause")?;
3853+
}
3854+
let columns = self.parse_parenthesized_column_list(Optional)?;
3855+
self.expect_keyword(Keyword::VALUES)?;
3856+
let values = self.parse_values()?;
3857+
MergeClause::NotMatched {
3858+
predicate,
3859+
columns,
3860+
values,
3861+
}
3862+
}
3863+
Some(_) => parser_err!("expected UPDATE, DELETE or INSERT in merge clause")?,
3864+
None => parser_err!("expected UPDATE, DELETE or INSERT in merge clause")?,
3865+
},
3866+
);
3867+
}
3868+
Ok(clauses)
3869+
}
3870+
3871+
pub fn parse_merge(&mut self) -> Result<Statement, ParserError> {
3872+
self.expect_keyword(Keyword::INTO)?;
3873+
3874+
let table = self.parse_table_factor()?;
3875+
3876+
self.expect_keyword(Keyword::USING)?;
3877+
let source = self.parse_query_body(0)?;
3878+
let alias = self.parse_optional_table_alias(keywords::RESERVED_FOR_TABLE_ALIAS)?;
3879+
self.expect_keyword(Keyword::ON)?;
3880+
let on = self.parse_expr()?;
3881+
let clauses = self.parse_merge_clauses()?;
3882+
3883+
Ok(Statement::Merge {
3884+
table,
3885+
source: Box::new(source),
3886+
alias,
3887+
on: Box::new(on),
3888+
clauses,
3889+
})
3890+
}
38063891
}
38073892

38083893
impl Word {

tests/sqlparser_common.rs

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4162,6 +4162,144 @@ fn test_revoke() {
41624162
}
41634163
}
41644164

4165+
#[test]
4166+
fn parse_merge() {
4167+
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";
4168+
match verified_stmt(sql) {
4169+
Statement::Merge {
4170+
table,
4171+
source,
4172+
alias,
4173+
on,
4174+
clauses,
4175+
} => {
4176+
assert_eq!(
4177+
table,
4178+
TableFactor::Table {
4179+
name: ObjectName(vec![Ident::new("s"), Ident::new("bar")]),
4180+
alias: Some(TableAlias {
4181+
name: Ident::new("dest"),
4182+
columns: vec![]
4183+
}),
4184+
args: vec![],
4185+
with_hints: vec![]
4186+
}
4187+
);
4188+
assert_eq!(
4189+
source,
4190+
Box::new(SetExpr::Query(Box::new(Query {
4191+
with: None,
4192+
body: SetExpr::Select(Box::new(Select {
4193+
distinct: false,
4194+
top: None,
4195+
projection: vec![SelectItem::Wildcard],
4196+
from: vec![TableWithJoins {
4197+
relation: TableFactor::Table {
4198+
name: ObjectName(vec![Ident::new("s"), Ident::new("foo")]),
4199+
alias: None,
4200+
args: vec![],
4201+
with_hints: vec![],
4202+
},
4203+
joins: vec![]
4204+
}],
4205+
lateral_views: vec![],
4206+
selection: None,
4207+
group_by: vec![],
4208+
cluster_by: vec![],
4209+
distribute_by: vec![],
4210+
sort_by: vec![],
4211+
having: None
4212+
})),
4213+
order_by: vec![],
4214+
limit: None,
4215+
offset: None,
4216+
fetch: None,
4217+
lock: None
4218+
})))
4219+
);
4220+
assert_eq!(
4221+
alias,
4222+
Some(TableAlias {
4223+
name: Ident::new("stg"),
4224+
columns: vec![]
4225+
})
4226+
);
4227+
assert_eq!(
4228+
on,
4229+
Box::new(Expr::BinaryOp {
4230+
left: Box::new(Expr::BinaryOp {
4231+
left: Box::new(Expr::CompoundIdentifier(vec![
4232+
Ident::new("dest"),
4233+
Ident::new("D")
4234+
])),
4235+
op: BinaryOperator::Eq,
4236+
right: Box::new(Expr::CompoundIdentifier(vec![
4237+
Ident::new("stg"),
4238+
Ident::new("D")
4239+
]))
4240+
}),
4241+
op: BinaryOperator::And,
4242+
right: Box::new(Expr::BinaryOp {
4243+
left: Box::new(Expr::CompoundIdentifier(vec![
4244+
Ident::new("dest"),
4245+
Ident::new("E")
4246+
])),
4247+
op: BinaryOperator::Eq,
4248+
right: Box::new(Expr::CompoundIdentifier(vec![
4249+
Ident::new("stg"),
4250+
Ident::new("E")
4251+
]))
4252+
})
4253+
})
4254+
);
4255+
assert_eq!(
4256+
clauses,
4257+
vec![
4258+
MergeClause::NotMatched {
4259+
predicate: None,
4260+
columns: vec![Ident::new("A"), Ident::new("B"), Ident::new("C")],
4261+
values: Values(vec![vec![
4262+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("A")]),
4263+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("B")]),
4264+
Expr::CompoundIdentifier(vec![Ident::new("stg"), Ident::new("C")]),
4265+
]])
4266+
},
4267+
MergeClause::MatchedUpdate {
4268+
predicate: Some(Expr::BinaryOp {
4269+
left: Box::new(Expr::CompoundIdentifier(vec![
4270+
Ident::new("dest"),
4271+
Ident::new("A")
4272+
])),
4273+
op: BinaryOperator::Eq,
4274+
right: Box::new(Expr::Value(Value::SingleQuotedString(
4275+
"a".to_string()
4276+
)))
4277+
}),
4278+
assignments: vec![
4279+
Assignment {
4280+
id: vec![Ident::new("dest"), Ident::new("F")],
4281+
value: Expr::CompoundIdentifier(vec![
4282+
Ident::new("stg"),
4283+
Ident::new("F")
4284+
])
4285+
},
4286+
Assignment {
4287+
id: vec![Ident::new("dest"), Ident::new("G")],
4288+
value: Expr::CompoundIdentifier(vec![
4289+
Ident::new("stg"),
4290+
Ident::new("G")
4291+
])
4292+
}
4293+
]
4294+
},
4295+
MergeClause::MatchedDelete(None)
4296+
]
4297+
)
4298+
}
4299+
_ => unreachable!(),
4300+
}
4301+
}
4302+
41654303
#[test]
41664304
fn test_lock() {
41674305
let sql = "SELECT * FROM student WHERE id = '1' FOR UPDATE";

0 commit comments

Comments
 (0)