mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-08-25 16:34:04 +00:00
feat: MERGE statements: add RETURNING and OUTPUT without INTO
adds better support for parsing MERGE statements including OUTPUT/RETURNING. fixes https://github.com/apache/datafusion-sqlparser-rs/issues/2010
This commit is contained in:
parent
5d5c90c77f
commit
33975dd059
5 changed files with 98 additions and 25 deletions
|
@ -9107,24 +9107,36 @@ impl Display for MergeClause {
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||||
pub struct OutputClause {
|
pub enum OutputClause {
|
||||||
pub select_items: Vec<SelectItem>,
|
Output {
|
||||||
pub into_table: SelectInto,
|
select_items: Vec<SelectItem>,
|
||||||
|
into_table: Option<SelectInto>,
|
||||||
|
},
|
||||||
|
Returning {
|
||||||
|
select_items: Vec<SelectItem>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for OutputClause {
|
impl fmt::Display for OutputClause {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let OutputClause {
|
match self {
|
||||||
select_items,
|
OutputClause::Output {
|
||||||
into_table,
|
select_items,
|
||||||
} = self;
|
into_table,
|
||||||
|
} => {
|
||||||
write!(
|
f.write_str("OUTPUT ")?;
|
||||||
f,
|
display_comma_separated(select_items).fmt(f)?;
|
||||||
"OUTPUT {} {}",
|
if let Some(into_table) = into_table {
|
||||||
display_comma_separated(select_items),
|
f.write_str(" ")?;
|
||||||
into_table
|
into_table.fmt(f)?;
|
||||||
)
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
OutputClause::Returning { select_items } => {
|
||||||
|
f.write_str("RETURNING ")?;
|
||||||
|
display_comma_separated(select_items).fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,6 +161,7 @@ pub enum SetExpr {
|
||||||
Insert(Statement),
|
Insert(Statement),
|
||||||
Update(Statement),
|
Update(Statement),
|
||||||
Delete(Statement),
|
Delete(Statement),
|
||||||
|
Merge(Statement),
|
||||||
Table(Box<Table>),
|
Table(Box<Table>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +189,7 @@ impl fmt::Display for SetExpr {
|
||||||
SetExpr::Insert(v) => v.fmt(f),
|
SetExpr::Insert(v) => v.fmt(f),
|
||||||
SetExpr::Update(v) => v.fmt(f),
|
SetExpr::Update(v) => v.fmt(f),
|
||||||
SetExpr::Delete(v) => v.fmt(f),
|
SetExpr::Delete(v) => v.fmt(f),
|
||||||
|
SetExpr::Merge(v) => v.fmt(f),
|
||||||
SetExpr::Table(t) => t.fmt(f),
|
SetExpr::Table(t) => t.fmt(f),
|
||||||
SetExpr::SetOperation {
|
SetExpr::SetOperation {
|
||||||
left,
|
left,
|
||||||
|
|
|
@ -214,6 +214,7 @@ impl Spanned for SetExpr {
|
||||||
SetExpr::Table(_) => Span::empty(),
|
SetExpr::Table(_) => Span::empty(),
|
||||||
SetExpr::Update(statement) => statement.span(),
|
SetExpr::Update(statement) => statement.span(),
|
||||||
SetExpr::Delete(statement) => statement.span(),
|
SetExpr::Delete(statement) => statement.span(),
|
||||||
|
SetExpr::Merge(statement) => statement.span(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11508,6 +11508,13 @@ impl<'a> Parser<'a> {
|
||||||
Ok(Box::new(SetExpr::Delete(self.parse_delete()?)))
|
Ok(Box::new(SetExpr::Delete(self.parse_delete()?)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse a MERGE statement, returning a `Box`ed SetExpr
|
||||||
|
///
|
||||||
|
/// This is used to reduce the size of the stack frames in debug builds
|
||||||
|
fn parse_merge_setexpr_boxed(&mut self) -> Result<Box<SetExpr>, ParserError> {
|
||||||
|
Ok(Box::new(SetExpr::Merge(self.parse_merge()?)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
|
pub fn parse_delete(&mut self) -> Result<Statement, ParserError> {
|
||||||
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
|
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
|
||||||
// `FROM` keyword is optional in BigQuery SQL.
|
// `FROM` keyword is optional in BigQuery SQL.
|
||||||
|
@ -11719,6 +11726,20 @@ impl<'a> Parser<'a> {
|
||||||
pipe_operators: vec![],
|
pipe_operators: vec![],
|
||||||
}
|
}
|
||||||
.into())
|
.into())
|
||||||
|
} else if self.parse_keyword(Keyword::MERGE) {
|
||||||
|
Ok(Query {
|
||||||
|
with,
|
||||||
|
body: self.parse_merge_setexpr_boxed()?,
|
||||||
|
limit_clause: None,
|
||||||
|
order_by: None,
|
||||||
|
fetch: None,
|
||||||
|
locks: vec![],
|
||||||
|
for_clause: None,
|
||||||
|
settings: None,
|
||||||
|
format_clause: None,
|
||||||
|
pipe_operators: vec![],
|
||||||
|
}
|
||||||
|
.into())
|
||||||
} else {
|
} else {
|
||||||
let body = self.parse_query_body(self.dialect.prec_unknown())?;
|
let body = self.parse_query_body(self.dialect.prec_unknown())?;
|
||||||
|
|
||||||
|
@ -16571,15 +16592,22 @@ impl<'a> Parser<'a> {
|
||||||
Ok(clauses)
|
Ok(clauses)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_output(&mut self) -> Result<OutputClause, ParserError> {
|
fn parse_output(&mut self, start_keyword: Keyword) -> Result<OutputClause, ParserError> {
|
||||||
self.expect_keyword_is(Keyword::OUTPUT)?;
|
|
||||||
let select_items = self.parse_projection()?;
|
let select_items = self.parse_projection()?;
|
||||||
self.expect_keyword_is(Keyword::INTO)?;
|
let into_table = if start_keyword == Keyword::OUTPUT && self.peek_keyword(Keyword::INTO) {
|
||||||
let into_table = self.parse_select_into()?;
|
self.expect_keyword_is(Keyword::INTO)?;
|
||||||
|
Some(self.parse_select_into()?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
Ok(OutputClause {
|
Ok(if start_keyword == Keyword::OUTPUT {
|
||||||
select_items,
|
OutputClause::Output {
|
||||||
into_table,
|
select_items,
|
||||||
|
into_table,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutputClause::Returning { select_items }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16609,10 +16637,9 @@ impl<'a> Parser<'a> {
|
||||||
self.expect_keyword_is(Keyword::ON)?;
|
self.expect_keyword_is(Keyword::ON)?;
|
||||||
let on = self.parse_expr()?;
|
let on = self.parse_expr()?;
|
||||||
let clauses = self.parse_merge_clauses()?;
|
let clauses = self.parse_merge_clauses()?;
|
||||||
let output = if self.peek_keyword(Keyword::OUTPUT) {
|
let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
|
||||||
Some(self.parse_output()?)
|
Some(start_keyword) => Some(self.parse_output(start_keyword)?),
|
||||||
} else {
|
None => None,
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Statement::Merge {
|
Ok(Statement::Merge {
|
||||||
|
|
|
@ -9902,6 +9902,29 @@ fn parse_merge() {
|
||||||
verified_stmt(sql);
|
verified_stmt(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_in_cte() {
|
||||||
|
verified_only_select(
|
||||||
|
"WITH x AS (\
|
||||||
|
MERGE INTO t USING (VALUES (1)) ON 1 = 1 \
|
||||||
|
WHEN MATCHED THEN DELETE \
|
||||||
|
RETURNING *\
|
||||||
|
) SELECT * FROM x",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_with_returning() {
|
||||||
|
let sql = "MERGE INTO wines AS w \
|
||||||
|
USING wine_stock_changes AS s \
|
||||||
|
ON s.winename = w.winename \
|
||||||
|
WHEN NOT MATCHED AND s.stock_delta > 0 THEN INSERT VALUES (s.winename, s.stock_delta) \
|
||||||
|
WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN UPDATE SET stock = w.stock + s.stock_delta \
|
||||||
|
WHEN MATCHED THEN DELETE \
|
||||||
|
RETURNING merge_action(), w.*";
|
||||||
|
verified_stmt(sql);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_with_output() {
|
fn test_merge_with_output() {
|
||||||
let sql = "MERGE INTO target_table USING source_table \
|
let sql = "MERGE INTO target_table USING source_table \
|
||||||
|
@ -9915,6 +9938,14 @@ fn test_merge_with_output() {
|
||||||
verified_stmt(sql);
|
verified_stmt(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_merge_with_output_without_into() {
|
||||||
|
let sql = "MERGE INTO a USING b ON a.id = b.id \
|
||||||
|
WHEN MATCHED THEN DELETE \
|
||||||
|
OUTPUT inserted.*";
|
||||||
|
verified_stmt(sql);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_merge_into_using_table() {
|
fn test_merge_into_using_table() {
|
||||||
let sql = "MERGE INTO target_table USING source_table \
|
let sql = "MERGE INTO target_table USING source_table \
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue