Skip to content

Commit 376f47e

Browse files
authored
feat: MERGE statements: add RETURNING and OUTPUT without INTO (#2011)
1 parent 5d5c90c commit 376f47e

File tree

5 files changed

+98
-25
lines changed

5 files changed

+98
-25
lines changed

src/ast/mod.rs

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9107,24 +9107,36 @@ impl Display for MergeClause {
91079107
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
91089108
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
91099109
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9110-
pub struct OutputClause {
9111-
pub select_items: Vec<SelectItem>,
9112-
pub into_table: SelectInto,
9110+
pub enum OutputClause {
9111+
Output {
9112+
select_items: Vec<SelectItem>,
9113+
into_table: Option<SelectInto>,
9114+
},
9115+
Returning {
9116+
select_items: Vec<SelectItem>,
9117+
},
91139118
}
91149119

91159120
impl fmt::Display for OutputClause {
91169121
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9117-
let OutputClause {
9118-
select_items,
9119-
into_table,
9120-
} = self;
9121-
9122-
write!(
9123-
f,
9124-
"OUTPUT {} {}",
9125-
display_comma_separated(select_items),
9126-
into_table
9127-
)
9122+
match self {
9123+
OutputClause::Output {
9124+
select_items,
9125+
into_table,
9126+
} => {
9127+
f.write_str("OUTPUT ")?;
9128+
display_comma_separated(select_items).fmt(f)?;
9129+
if let Some(into_table) = into_table {
9130+
f.write_str(" ")?;
9131+
into_table.fmt(f)?;
9132+
}
9133+
Ok(())
9134+
}
9135+
OutputClause::Returning { select_items } => {
9136+
f.write_str("RETURNING ")?;
9137+
display_comma_separated(select_items).fmt(f)
9138+
}
9139+
}
91289140
}
91299141
}
91309142

src/ast/query.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ pub enum SetExpr {
161161
Insert(Statement),
162162
Update(Statement),
163163
Delete(Statement),
164+
Merge(Statement),
164165
Table(Box<Table>),
165166
}
166167

@@ -188,6 +189,7 @@ impl fmt::Display for SetExpr {
188189
SetExpr::Insert(v) => v.fmt(f),
189190
SetExpr::Update(v) => v.fmt(f),
190191
SetExpr::Delete(v) => v.fmt(f),
192+
SetExpr::Merge(v) => v.fmt(f),
191193
SetExpr::Table(t) => t.fmt(f),
192194
SetExpr::SetOperation {
193195
left,

src/ast/spans.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ impl Spanned for SetExpr {
214214
SetExpr::Table(_) => Span::empty(),
215215
SetExpr::Update(statement) => statement.span(),
216216
SetExpr::Delete(statement) => statement.span(),
217+
SetExpr::Merge(statement) => statement.span(),
217218
}
218219
}
219220
}

src/parser/mod.rs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11508,6 +11508,13 @@ impl<'a> Parser<'a> {
1150811508
Ok(Box::new(SetExpr::Delete(self.parse_delete()?)))
1150911509
}
1151011510

11511+
/// Parse a MERGE statement, returning a `Box`ed SetExpr
11512+
///
11513+
/// This is used to reduce the size of the stack frames in debug builds
11514+
fn parse_merge_setexpr_boxed(&mut self) -> Result<Box<SetExpr>, ParserError> {
11515+
Ok(Box::new(SetExpr::Merge(self.parse_merge()?)))
11516+
}
11517+
1151111518
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
1151211519
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
1151311520
// `FROM` keyword is optional in BigQuery SQL.
@@ -11719,6 +11726,20 @@ impl<'a> Parser<'a> {
1171911726
pipe_operators: vec![],
1172011727
}
1172111728
.into())
11729+
} else if self.parse_keyword(Keyword::MERGE) {
11730+
Ok(Query {
11731+
with,
11732+
body: self.parse_merge_setexpr_boxed()?,
11733+
limit_clause: None,
11734+
order_by: None,
11735+
fetch: None,
11736+
locks: vec![],
11737+
for_clause: None,
11738+
settings: None,
11739+
format_clause: None,
11740+
pipe_operators: vec![],
11741+
}
11742+
.into())
1172211743
} else {
1172311744
let body = self.parse_query_body(self.dialect.prec_unknown())?;
1172411745

@@ -16571,15 +16592,22 @@ impl<'a> Parser<'a> {
1657116592
Ok(clauses)
1657216593
}
1657316594

16574-
fn parse_output(&mut self) -> Result<OutputClause, ParserError> {
16575-
self.expect_keyword_is(Keyword::OUTPUT)?;
16595+
fn parse_output(&mut self, start_keyword: Keyword) -> Result<OutputClause, ParserError> {
1657616596
let select_items = self.parse_projection()?;
16577-
self.expect_keyword_is(Keyword::INTO)?;
16578-
let into_table = self.parse_select_into()?;
16597+
let into_table = if start_keyword == Keyword::OUTPUT && self.peek_keyword(Keyword::INTO) {
16598+
self.expect_keyword_is(Keyword::INTO)?;
16599+
Some(self.parse_select_into()?)
16600+
} else {
16601+
None
16602+
};
1657916603

16580-
Ok(OutputClause {
16581-
select_items,
16582-
into_table,
16604+
Ok(if start_keyword == Keyword::OUTPUT {
16605+
OutputClause::Output {
16606+
select_items,
16607+
into_table,
16608+
}
16609+
} else {
16610+
OutputClause::Returning { select_items }
1658316611
})
1658416612
}
1658516613

@@ -16609,10 +16637,9 @@ impl<'a> Parser<'a> {
1660916637
self.expect_keyword_is(Keyword::ON)?;
1661016638
let on = self.parse_expr()?;
1661116639
let clauses = self.parse_merge_clauses()?;
16612-
let output = if self.peek_keyword(Keyword::OUTPUT) {
16613-
Some(self.parse_output()?)
16614-
} else {
16615-
None
16640+
let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
16641+
Some(start_keyword) => Some(self.parse_output(start_keyword)?),
16642+
None => None,
1661616643
};
1661716644

1661816645
Ok(Statement::Merge {

tests/sqlparser_common.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9902,6 +9902,29 @@ fn parse_merge() {
99029902
verified_stmt(sql);
99039903
}
99049904

9905+
#[test]
9906+
fn test_merge_in_cte() {
9907+
verified_only_select(
9908+
"WITH x AS (\
9909+
MERGE INTO t USING (VALUES (1)) ON 1 = 1 \
9910+
WHEN MATCHED THEN DELETE \
9911+
RETURNING *\
9912+
) SELECT * FROM x",
9913+
);
9914+
}
9915+
9916+
#[test]
9917+
fn test_merge_with_returning() {
9918+
let sql = "MERGE INTO wines AS w \
9919+
USING wine_stock_changes AS s \
9920+
ON s.winename = w.winename \
9921+
WHEN NOT MATCHED AND s.stock_delta > 0 THEN INSERT VALUES (s.winename, s.stock_delta) \
9922+
WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN UPDATE SET stock = w.stock + s.stock_delta \
9923+
WHEN MATCHED THEN DELETE \
9924+
RETURNING merge_action(), w.*";
9925+
verified_stmt(sql);
9926+
}
9927+
99059928
#[test]
99069929
fn test_merge_with_output() {
99079930
let sql = "MERGE INTO target_table USING source_table \
@@ -9915,6 +9938,14 @@ fn test_merge_with_output() {
99159938
verified_stmt(sql);
99169939
}
99179940

9941+
#[test]
9942+
fn test_merge_with_output_without_into() {
9943+
let sql = "MERGE INTO a USING b ON a.id = b.id \
9944+
WHEN MATCHED THEN DELETE \
9945+
OUTPUT inserted.*";
9946+
verified_stmt(sql);
9947+
}
9948+
99189949
#[test]
99199950
fn test_merge_into_using_table() {
99209951
let sql = "MERGE INTO target_table USING source_table \

0 commit comments

Comments
 (0)