Skip to content

Commit a6b82f3

Browse files
lovasoaayman-sigma
authored andcommitted
feat: MERGE statements: add RETURNING and OUTPUT without INTO (apache#2011)
1 parent cd76e7d commit a6b82f3

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
@@ -9136,24 +9136,36 @@ impl Display for MergeClause {
91369136
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
91379137
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
91389138
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
9139-
pub struct OutputClause {
9140-
pub select_items: Vec<SelectItem>,
9141-
pub into_table: SelectInto,
9139+
pub enum OutputClause {
9140+
Output {
9141+
select_items: Vec<SelectItem>,
9142+
into_table: Option<SelectInto>,
9143+
},
9144+
Returning {
9145+
select_items: Vec<SelectItem>,
9146+
},
91429147
}
91439148

91449149
impl fmt::Display for OutputClause {
91459150
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
9146-
let OutputClause {
9147-
select_items,
9148-
into_table,
9149-
} = self;
9150-
9151-
write!(
9152-
f,
9153-
"OUTPUT {} {}",
9154-
display_comma_separated(select_items),
9155-
into_table
9156-
)
9151+
match self {
9152+
OutputClause::Output {
9153+
select_items,
9154+
into_table,
9155+
} => {
9156+
f.write_str("OUTPUT ")?;
9157+
display_comma_separated(select_items).fmt(f)?;
9158+
if let Some(into_table) = into_table {
9159+
f.write_str(" ")?;
9160+
into_table.fmt(f)?;
9161+
}
9162+
Ok(())
9163+
}
9164+
OutputClause::Returning { select_items } => {
9165+
f.write_str("RETURNING ")?;
9166+
display_comma_separated(select_items).fmt(f)
9167+
}
9168+
}
91579169
}
91589170
}
91599171

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
@@ -11539,6 +11539,13 @@ impl<'a> Parser<'a> {
1153911539
Ok(Box::new(SetExpr::Delete(self.parse_delete()?)))
1154011540
}
1154111541

11542+
/// Parse a MERGE statement, returning a `Box`ed SetExpr
11543+
///
11544+
/// This is used to reduce the size of the stack frames in debug builds
11545+
fn parse_merge_setexpr_boxed(&mut self) -> Result<Box<SetExpr>, ParserError> {
11546+
Ok(Box::new(SetExpr::Merge(self.parse_merge()?)))
11547+
}
11548+
1154211549
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
1154311550
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
1154411551
// `FROM` keyword is optional in BigQuery SQL.
@@ -11750,6 +11757,20 @@ impl<'a> Parser<'a> {
1175011757
pipe_operators: vec![],
1175111758
}
1175211759
.into())
11760+
} else if self.parse_keyword(Keyword::MERGE) {
11761+
Ok(Query {
11762+
with,
11763+
body: self.parse_merge_setexpr_boxed()?,
11764+
limit_clause: None,
11765+
order_by: None,
11766+
fetch: None,
11767+
locks: vec![],
11768+
for_clause: None,
11769+
settings: None,
11770+
format_clause: None,
11771+
pipe_operators: vec![],
11772+
}
11773+
.into())
1175311774
} else {
1175411775
let body = self.parse_query_body(self.dialect.prec_unknown())?;
1175511776

@@ -16603,15 +16624,22 @@ impl<'a> Parser<'a> {
1660316624
Ok(clauses)
1660416625
}
1660516626

16606-
fn parse_output(&mut self) -> Result<OutputClause, ParserError> {
16607-
self.expect_keyword_is(Keyword::OUTPUT)?;
16627+
fn parse_output(&mut self, start_keyword: Keyword) -> Result<OutputClause, ParserError> {
1660816628
let select_items = self.parse_projection()?;
16609-
self.expect_keyword_is(Keyword::INTO)?;
16610-
let into_table = self.parse_select_into()?;
16629+
let into_table = if start_keyword == Keyword::OUTPUT && self.peek_keyword(Keyword::INTO) {
16630+
self.expect_keyword_is(Keyword::INTO)?;
16631+
Some(self.parse_select_into()?)
16632+
} else {
16633+
None
16634+
};
1661116635

16612-
Ok(OutputClause {
16613-
select_items,
16614-
into_table,
16636+
Ok(if start_keyword == Keyword::OUTPUT {
16637+
OutputClause::Output {
16638+
select_items,
16639+
into_table,
16640+
}
16641+
} else {
16642+
OutputClause::Returning { select_items }
1661516643
})
1661616644
}
1661716645

@@ -16641,10 +16669,9 @@ impl<'a> Parser<'a> {
1664116669
self.expect_keyword_is(Keyword::ON)?;
1664216670
let on = self.parse_expr()?;
1664316671
let clauses = self.parse_merge_clauses()?;
16644-
let output = if self.peek_keyword(Keyword::OUTPUT) {
16645-
Some(self.parse_output()?)
16646-
} else {
16647-
None
16672+
let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
16673+
Some(start_keyword) => Some(self.parse_output(start_keyword)?),
16674+
None => None,
1664816675
};
1664916676

1665016677
Ok(Statement::Merge {

tests/sqlparser_common.rs

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

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

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

0 commit comments

Comments
 (0)