mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-11-25 00:19:37 +00:00
Oracle: Support for MERGE predicates
This commit is contained in:
parent
2b8e99c665
commit
f406386e51
8 changed files with 672 additions and 168 deletions
|
|
@ -8618,6 +8618,7 @@ impl Display for MergeInsertKind {
|
|||
///
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
|
||||
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
|
@ -8636,6 +8637,10 @@ pub struct MergeInsertExpr {
|
|||
pub kind_token: AttachedToken,
|
||||
/// The insert type used by the statement.
|
||||
pub kind: MergeInsertKind,
|
||||
/// An optional condition to restrict the insertion (Oracle specific)
|
||||
///
|
||||
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
|
||||
pub insert_predicate: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Display for MergeInsertExpr {
|
||||
|
|
@ -8643,7 +8648,11 @@ impl Display for MergeInsertExpr {
|
|||
if !self.columns.is_empty() {
|
||||
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
|
||||
}
|
||||
write!(f, "{}", self.kind)
|
||||
write!(f, "{}", self.kind)?;
|
||||
if let Some(predicate) = self.insert_predicate.as_ref() {
|
||||
write!(f, " WHERE {}", predicate)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8656,6 +8665,7 @@ impl Display for MergeInsertExpr {
|
|||
///
|
||||
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
|
||||
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
|
||||
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
|
||||
|
|
@ -8676,7 +8686,16 @@ pub enum MergeAction {
|
|||
Update {
|
||||
/// The `UPDATE` token that starts the sub-expression.
|
||||
update_token: AttachedToken,
|
||||
/// The update assiment expressions
|
||||
assignments: Vec<Assignment>,
|
||||
/// `where_clause` for the update (Oralce specific)
|
||||
///
|
||||
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
|
||||
update_predicate: Option<Expr>,
|
||||
/// `delete_clause` for the update "delete where" (Oracle specific)
|
||||
///
|
||||
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
|
||||
delete_predicate: Option<Expr>,
|
||||
},
|
||||
/// A plain `DELETE` clause
|
||||
Delete {
|
||||
|
|
@ -8691,8 +8710,20 @@ impl Display for MergeAction {
|
|||
MergeAction::Insert(insert) => {
|
||||
write!(f, "INSERT {insert}")
|
||||
}
|
||||
MergeAction::Update { assignments, .. } => {
|
||||
write!(f, "UPDATE SET {}", display_comma_separated(assignments))
|
||||
MergeAction::Update {
|
||||
update_token: _,
|
||||
assignments,
|
||||
update_predicate,
|
||||
delete_predicate,
|
||||
} => {
|
||||
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
|
||||
if let Some(predicate) = update_predicate.as_ref() {
|
||||
write!(f, " WHERE {predicate}")?;
|
||||
}
|
||||
if let Some(predicate) = delete_predicate.as_ref() {
|
||||
write!(f, " DELETE WHERE {predicate}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
MergeAction::Delete { .. } => {
|
||||
write!(f, "DELETE")
|
||||
|
|
|
|||
|
|
@ -2407,8 +2407,13 @@ impl Spanned for MergeAction {
|
|||
MergeAction::Update {
|
||||
update_token,
|
||||
assignments,
|
||||
update_predicate,
|
||||
delete_predicate,
|
||||
} => union_spans(
|
||||
core::iter::once(update_token.0.span).chain(assignments.iter().map(Spanned::span)),
|
||||
core::iter::once(update_token.0.span)
|
||||
.chain(assignments.iter().map(Spanned::span))
|
||||
.chain(update_predicate.iter().map(Spanned::span))
|
||||
.chain(delete_predicate.iter().map(Spanned::span)),
|
||||
),
|
||||
MergeAction::Delete { delete_token } => delete_token.0.span,
|
||||
}
|
||||
|
|
@ -2427,6 +2432,7 @@ impl Spanned for MergeInsertExpr {
|
|||
},
|
||||
]
|
||||
.into_iter()
|
||||
.chain(self.insert_predicate.iter().map(Spanned::span))
|
||||
.chain(self.columns.iter().map(|i| i.span)),
|
||||
)
|
||||
}
|
||||
|
|
@ -2800,6 +2806,8 @@ WHERE id = 1
|
|||
if let MergeAction::Update {
|
||||
update_token,
|
||||
assignments: _,
|
||||
update_predicate: _,
|
||||
delete_predicate: _,
|
||||
} = &clauses[1].action
|
||||
{
|
||||
assert_eq!(
|
||||
|
|
@ -2920,4 +2928,44 @@ WHERE id = 1
|
|||
panic!("not a MERGE statement");
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_statement_spans_with_update_predicates() {
|
||||
let sql = r#"
|
||||
MERGE INTO a USING b ON a.id = b.id
|
||||
WHEN MATCHED THEN
|
||||
UPDATE set a.x = a.x + b.x
|
||||
WHERE b.x != 2
|
||||
DELETE WHERE a.x <> 3"#;
|
||||
|
||||
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
|
||||
assert_eq!(1, r.len());
|
||||
|
||||
// ~ assert the span of the whole statement
|
||||
let stmt_span = r[0].span();
|
||||
assert_eq!(
|
||||
stmt_span,
|
||||
Span::new(Location::new(2, 8), Location::new(6, 36))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_statement_spans_with_insert_predicate() {
|
||||
let sql = r#"
|
||||
MERGE INTO a USING b ON a.id = b.id
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT VALUES (b.x, b.y) WHERE b.x != 2
|
||||
-- qed
|
||||
"#;
|
||||
|
||||
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
|
||||
assert_eq!(1, r.len());
|
||||
|
||||
// ~ assert the span of the whole statement
|
||||
let stmt_span = r[0].span();
|
||||
assert_eq!(
|
||||
stmt_span,
|
||||
Span::new(Location::new(2, 8), Location::new(4, 52))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,4 +195,20 @@ impl Dialect for GenericDialect {
|
|||
fn supports_interval_options(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_merge_insert_qualified_columns(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_merge_insert_predicate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_merge_update_predicate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn supports_merge_update_delete_predicate(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -601,13 +601,122 @@ pub trait Dialect: Debug + Any {
|
|||
false
|
||||
}
|
||||
|
||||
/// Return true if the dialect supports specifying multiple options
|
||||
/// Returns true if the dialect supports specifying multiple options
|
||||
/// in a `CREATE TABLE` statement for the structure of the new table. For example:
|
||||
/// `CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a`
|
||||
fn supports_create_table_multi_schema_info_sources(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if the dialect supports qualified column names
|
||||
/// as part of a MERGE's INSERT's column list. Example:
|
||||
///
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO
|
||||
/// USING FOO_IMP
|
||||
/// ON (FOO.ID = FOO_IMP.ID)
|
||||
/// WHEN NOT MATCHED THEN
|
||||
/// -- no qualifier
|
||||
/// INSERT (ID, NAME)
|
||||
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
|
||||
/// ```
|
||||
/// vs.
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO
|
||||
/// USING FOO_IMP
|
||||
/// ON (FOO.ID = FOO_IMP.ID)
|
||||
/// WHEN NOT MATCHED THEN
|
||||
/// -- here: qualified
|
||||
/// INSERT (FOO.ID, FOO.NAME)
|
||||
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
|
||||
/// ```
|
||||
/// or
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO X
|
||||
/// USING FOO_IMP
|
||||
/// ON (X.ID = FOO_IMP.ID)
|
||||
/// WHEN NOT MATCHED THEN
|
||||
/// -- here: qualified using the alias
|
||||
/// INSERT (X.ID, X.NAME)
|
||||
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
|
||||
/// ```
|
||||
///
|
||||
/// Note: in the latter case, the qualifier must match the target table
|
||||
/// name or its alias if one is present. The parser will enforce this.
|
||||
///
|
||||
/// The default implementation always returns `false` not allowing the
|
||||
/// qualifiers.
|
||||
fn supports_merge_insert_qualified_columns(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns `true` if the dialect supports specify an INSERT predicate in
|
||||
/// MERGE statements. Example:
|
||||
///
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO
|
||||
/// USING FOO_IMP
|
||||
/// ON (FOO.ID = FOO_IMP.ID)
|
||||
/// WHEN NOT MATCHED THEN
|
||||
/// INSERT (ID, NAME)
|
||||
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
|
||||
/// -- insert predicate
|
||||
/// WHERE NOT FOO_IMP.NAME like '%.IGNORE'
|
||||
/// ```
|
||||
///
|
||||
/// The default implementation always returns `false` indicating no
|
||||
/// support for the additional predicate.
|
||||
///
|
||||
/// See also [Dialect::supports_merge_update_predicate] and
|
||||
/// [Dialect::supports_merge_update_delete_predicate].
|
||||
fn supports_merge_insert_predicate(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Indicates the supports of UPDATE predicates in MERGE
|
||||
/// statements. Example:
|
||||
///
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO
|
||||
/// USING FOO_IMPORT
|
||||
/// ON (FOO.ID = FOO_IMPORT.ID)
|
||||
/// WHEN MATCHED THEN
|
||||
/// UPDATE SET FOO.NAME = FOO_IMPORT.NAME
|
||||
/// -- update predicate
|
||||
/// WHERE FOO.NAME <> 'pete'
|
||||
/// ```
|
||||
///
|
||||
/// The default implementation always returns false indicating no support
|
||||
/// for the additional predicate.
|
||||
///
|
||||
/// See also [Dialect::supports_merge_insert_predicate] and
|
||||
/// [Dialect::supports_merge_update_delete_predicate].
|
||||
fn supports_merge_update_predicate(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Indicates the supports of UPDATE ... DELETEs and associated predicates
|
||||
/// in MERGE statements. Example:
|
||||
///
|
||||
/// ```sql
|
||||
/// MERGE INTO FOO
|
||||
/// USING FOO_IMPORT
|
||||
/// ON (FOO.ID = FOO_IMPORT.ID)
|
||||
/// WHEN MATCHED THEN
|
||||
/// UPDATE SET FOO.NAME = FOO_IMPORT.NAME
|
||||
/// -- update delete with predicate
|
||||
/// DELETE WHERE UPPER(FOO.NAME) == FOO.NAME
|
||||
/// ```
|
||||
///
|
||||
/// The default implementation always returns false indicating no support
|
||||
/// for the `UPDATE ... DELETE` and its associated predicate.
|
||||
///
|
||||
/// See also [Dialect::supports_merge_insert_predicate] and
|
||||
/// [Dialect::supports_merge_update_predicate].
|
||||
fn supports_merge_update_delete_predicate(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Dialect-specific infix parser override
|
||||
///
|
||||
/// This method is called to parse the next infix expression.
|
||||
|
|
|
|||
369
src/parser/merge.rs
Normal file
369
src/parser/merge.rs
Normal file
|
|
@ -0,0 +1,369 @@
|
|||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! SQL Parser for MERGE
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
|
||||
|
||||
use crate::{
|
||||
ast::{
|
||||
Ident, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
|
||||
ObjectName, ObjectNamePart, SetExpr, Statement, TableFactor,
|
||||
},
|
||||
dialect::{BigQueryDialect, GenericDialect, MySqlDialect},
|
||||
keywords::Keyword,
|
||||
parser::IsOptional,
|
||||
tokenizer::TokenWithSpan,
|
||||
};
|
||||
|
||||
use super::{Parser, ParserError};
|
||||
|
||||
impl Parser<'_> {
|
||||
/// Parse a MERGE statement, returning a `Box`ed SetExpr
|
||||
///
|
||||
/// This is used to reduce the size of the stack frames in debug builds
|
||||
pub(super) fn parse_merge_setexpr_boxed(
|
||||
&mut self,
|
||||
merge_token: TokenWithSpan,
|
||||
) -> Result<Box<SetExpr>, ParserError> {
|
||||
Ok(Box::new(SetExpr::Merge(self.parse_merge(merge_token)?)))
|
||||
}
|
||||
|
||||
pub fn parse_merge(&mut self, merge_token: TokenWithSpan) -> Result<Statement, ParserError> {
|
||||
let into = self.parse_keyword(Keyword::INTO);
|
||||
|
||||
let table = self.parse_table_factor()?;
|
||||
|
||||
self.expect_keyword_is(Keyword::USING)?;
|
||||
let source = self.parse_table_factor()?;
|
||||
self.expect_keyword_is(Keyword::ON)?;
|
||||
let on = self.parse_expr()?;
|
||||
let clauses = self.parse_merge_clauses(&table)?;
|
||||
let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
|
||||
Some(keyword) => Some(self.parse_output(keyword, self.get_current_token().clone())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Statement::Merge {
|
||||
merge_token: merge_token.into(),
|
||||
into,
|
||||
table,
|
||||
source,
|
||||
on: Box::new(on),
|
||||
clauses,
|
||||
output,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_merge_clauses(
|
||||
&mut self,
|
||||
target_table: &TableFactor,
|
||||
) -> Result<Vec<MergeClause>, ParserError> {
|
||||
let mut clauses = vec![];
|
||||
loop {
|
||||
if !(self.parse_keyword(Keyword::WHEN)) {
|
||||
break;
|
||||
}
|
||||
let when_token = self.get_current_token().clone();
|
||||
|
||||
let mut clause_kind = MergeClauseKind::Matched;
|
||||
if self.parse_keyword(Keyword::NOT) {
|
||||
clause_kind = MergeClauseKind::NotMatched;
|
||||
}
|
||||
self.expect_keyword_is(Keyword::MATCHED)?;
|
||||
|
||||
if matches!(clause_kind, MergeClauseKind::NotMatched)
|
||||
&& self.parse_keywords(&[Keyword::BY, Keyword::SOURCE])
|
||||
{
|
||||
clause_kind = MergeClauseKind::NotMatchedBySource;
|
||||
} else if matches!(clause_kind, MergeClauseKind::NotMatched)
|
||||
&& self.parse_keywords(&[Keyword::BY, Keyword::TARGET])
|
||||
{
|
||||
clause_kind = MergeClauseKind::NotMatchedByTarget;
|
||||
}
|
||||
|
||||
let predicate = if self.parse_keyword(Keyword::AND) {
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect_keyword_is(Keyword::THEN)?;
|
||||
|
||||
let merge_clause = match self.parse_one_of_keywords(&[
|
||||
Keyword::UPDATE,
|
||||
Keyword::INSERT,
|
||||
Keyword::DELETE,
|
||||
]) {
|
||||
Some(Keyword::UPDATE) => {
|
||||
if matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("UPDATE is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
}
|
||||
|
||||
let update_token = self.get_current_token().clone();
|
||||
self.expect_keyword_is(Keyword::SET)?;
|
||||
let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
|
||||
let update_predicate = if self.dialect.supports_merge_update_predicate()
|
||||
&& self.parse_keyword(Keyword::WHERE)
|
||||
{
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let delete_predicate = if self.dialect.supports_merge_update_delete_predicate()
|
||||
&& self.parse_keyword(Keyword::DELETE)
|
||||
{
|
||||
let _ = self.expect_keyword(Keyword::WHERE)?;
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
MergeAction::Update {
|
||||
update_token: update_token.into(),
|
||||
assignments,
|
||||
update_predicate,
|
||||
delete_predicate,
|
||||
}
|
||||
}
|
||||
Some(Keyword::DELETE) => {
|
||||
if matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("DELETE is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
};
|
||||
|
||||
let delete_token = self.get_current_token().clone();
|
||||
MergeAction::Delete {
|
||||
delete_token: delete_token.into(),
|
||||
}
|
||||
}
|
||||
Some(Keyword::INSERT) => {
|
||||
if !matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("INSERT is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
};
|
||||
|
||||
let insert_token = self.get_current_token().clone();
|
||||
let is_mysql = dialect_of!(self is MySqlDialect);
|
||||
|
||||
let columns = self.parse_merge_clause_insert_columns(
|
||||
target_table,
|
||||
&clause_kind,
|
||||
is_mysql,
|
||||
)?;
|
||||
let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::ROW)
|
||||
{
|
||||
(MergeInsertKind::Row, self.get_current_token().clone())
|
||||
} else {
|
||||
self.expect_keyword_is(Keyword::VALUES)?;
|
||||
let values_token = self.get_current_token().clone();
|
||||
let values = self.parse_values(is_mysql, false)?;
|
||||
(MergeInsertKind::Values(values), values_token)
|
||||
};
|
||||
let insert_predicate = if self.dialect.supports_merge_insert_predicate()
|
||||
&& self.parse_keyword(Keyword::WHERE)
|
||||
{
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
MergeAction::Insert(MergeInsertExpr {
|
||||
insert_token: insert_token.into(),
|
||||
columns,
|
||||
kind_token: kind_token.into(),
|
||||
kind,
|
||||
insert_predicate,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return parser_err!(
|
||||
"expected UPDATE, DELETE or INSERT in merge clause",
|
||||
self.peek_token_ref().span.start
|
||||
);
|
||||
}
|
||||
};
|
||||
clauses.push(MergeClause {
|
||||
when_token: when_token.into(),
|
||||
clause_kind,
|
||||
predicate,
|
||||
action: merge_clause,
|
||||
});
|
||||
}
|
||||
Ok(clauses)
|
||||
}
|
||||
|
||||
fn parse_merge_clause_insert_columns(
|
||||
&mut self,
|
||||
target_table: &TableFactor,
|
||||
clause_kind: &MergeClauseKind,
|
||||
allow_empty: bool,
|
||||
) -> Result<Vec<Ident>, ParserError> {
|
||||
if self.dialect.supports_merge_insert_qualified_columns() {
|
||||
let cols =
|
||||
self.parse_parenthesized_qualified_column_list(IsOptional::Optional, allow_empty)?;
|
||||
if let TableFactor::Table { name, alias, .. } = target_table {
|
||||
if let Some(alias) = alias {
|
||||
if alias.columns.is_empty() {
|
||||
// ~ only the alias is supported at this point
|
||||
unqualify_columns(cols, None, Some(&alias.name)).map_err(|e| {
|
||||
ParserError::ParserError(format!(
|
||||
"Invalid column for INSERT in a {clause_kind} merge clause: {e}"
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Err(ParserError::ParserError(format!(
|
||||
"Invalid target ALIAS for INSERT in a {clause_kind} merge clause; must be an identifier"
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
// ~ allow the full qualifier, but also just the table name
|
||||
if name.0.len() == 1 {
|
||||
unqualify_columns(cols, Some(name), None).map_err(|e| {
|
||||
ParserError::ParserError(format!(
|
||||
"Invalid column for INSERT in a {clause_kind} merge clause: {e}"
|
||||
))
|
||||
})
|
||||
} else if let Some(table_name) =
|
||||
name.0.last().and_then(ObjectNamePart::as_ident)
|
||||
{
|
||||
unqualify_columns(cols, Some(name), Some(table_name)).map_err(|e| {
|
||||
ParserError::ParserError(format!(
|
||||
"Invalid column for INSERT in a {clause_kind} merge clause: {e}"
|
||||
))
|
||||
})
|
||||
} else {
|
||||
Err(ParserError::ParserError(format!(
|
||||
"Invalid target table NAME for INSERT in a {clause_kind} merge clause; must be an identifier"
|
||||
)))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(ParserError::ParserError(format!(
|
||||
"Invalid target for INSERT in a {clause_kind} merge clause; must be a TABLE identifier"
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
self.parse_parenthesized_column_list(IsOptional::Optional, allow_empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to unqualify a list of columns with either a qualified prefix or a
|
||||
/// qualifier identifier
|
||||
///
|
||||
/// Oracle allows `INSERT ([qualifier.]column_name, ...)` in MERGE statements
|
||||
/// with `qualifier` referring to the alias of the target table (if one is
|
||||
/// present) or, if no alias is present, to the target table name itself -
|
||||
/// either qualified or unqualified.
|
||||
fn unqualify_columns(
|
||||
columns: Vec<ObjectName>,
|
||||
allowed_qualifier_1: Option<&ObjectName>,
|
||||
allowed_qualifier_2: Option<&Ident>,
|
||||
) -> Result<Vec<Ident>, &'static str> {
|
||||
// ~ helper to turn a column name (part) into a plain `ident`
|
||||
// possibly bailing with error
|
||||
fn to_ident(name: ObjectNamePart) -> Result<Ident, &'static str> {
|
||||
match name {
|
||||
ObjectNamePart::Identifier(ident) => Ok(ident),
|
||||
ObjectNamePart::Function(_) => Err("not an identifier"),
|
||||
}
|
||||
}
|
||||
|
||||
// ~ helper to return the last part of `name` if it is
|
||||
// preceded by `prefix`
|
||||
fn unqualify_column(
|
||||
mut name: ObjectName,
|
||||
prefix: &ObjectName,
|
||||
) -> Result<ObjectNamePart, ObjectName> {
|
||||
let mut name_iter = name.0.iter();
|
||||
let mut prefix_iter = prefix.0.iter();
|
||||
loop {
|
||||
match (name_iter.next(), prefix_iter.next()) {
|
||||
(Some(_), None) => {
|
||||
if name_iter.next().is_none() {
|
||||
return Ok(name.0.pop().expect("missing name part"));
|
||||
} else {
|
||||
return Err(name);
|
||||
}
|
||||
}
|
||||
(Some(c), Some(q)) if c == q => {
|
||||
// ~ continue matching next part
|
||||
}
|
||||
_ => {
|
||||
return Err(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut unqualified = Vec::<Ident>::with_capacity(columns.len());
|
||||
for mut name in columns {
|
||||
if name.0.is_empty() {
|
||||
return Err("empty column name");
|
||||
}
|
||||
|
||||
if name.0.len() == 1 {
|
||||
unqualified.push(to_ident(name.0.pop().expect("missing name part"))?);
|
||||
continue;
|
||||
}
|
||||
|
||||
// ~ try matching by the primary prefix
|
||||
if let Some(allowed_qualifier) = allowed_qualifier_1 {
|
||||
match unqualify_column(name, allowed_qualifier) {
|
||||
Ok(ident) => {
|
||||
unqualified.push(to_ident(ident)?);
|
||||
continue;
|
||||
}
|
||||
Err(n) => {
|
||||
// ~ continue trying with the alternate prefix below
|
||||
name = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ~ try matching by the alternate prefix
|
||||
if let Some(allowed_qualifier) = allowed_qualifier_2 {
|
||||
if name.0.len() == 2
|
||||
&& name
|
||||
.0
|
||||
.first()
|
||||
.and_then(ObjectNamePart::as_ident)
|
||||
.map(|i| i == allowed_qualifier)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
unqualified.push(to_ident(name.0.pop().expect("missing name part"))?);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Err("not matching target table");
|
||||
}
|
||||
Ok(unqualified)
|
||||
}
|
||||
|
|
@ -45,8 +45,6 @@ use crate::keywords::{Keyword, ALL_KEYWORDS};
|
|||
use crate::tokenizer::*;
|
||||
use sqlparser::parser::ParserState::ColumnDefinition;
|
||||
|
||||
mod alter;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ParserError {
|
||||
TokenizerError(String),
|
||||
|
|
@ -61,6 +59,9 @@ macro_rules! parser_err {
|
|||
};
|
||||
}
|
||||
|
||||
mod alter;
|
||||
mod merge;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Implementation [`RecursionCounter`] if std is available
|
||||
mod recursion {
|
||||
|
|
@ -11618,7 +11619,7 @@ impl<'a> Parser<'a> {
|
|||
token => {
|
||||
return Err(ParserError::ParserError(format!(
|
||||
"Unexpected token in identifier: {token}"
|
||||
)))?
|
||||
)))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12122,16 +12123,6 @@ impl<'a> Parser<'a> {
|
|||
Ok(Box::new(SetExpr::Delete(self.parse_delete(delete_token)?)))
|
||||
}
|
||||
|
||||
/// 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,
|
||||
merge_token: TokenWithSpan,
|
||||
) -> Result<Box<SetExpr>, ParserError> {
|
||||
Ok(Box::new(SetExpr::Merge(self.parse_merge(merge_token)?)))
|
||||
}
|
||||
|
||||
pub fn parse_delete(&mut self, delete_token: TokenWithSpan) -> Result<Statement, ParserError> {
|
||||
let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) {
|
||||
// `FROM` keyword is optional in BigQuery SQL.
|
||||
|
|
@ -17197,126 +17188,6 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_merge_clauses(&mut self) -> Result<Vec<MergeClause>, ParserError> {
|
||||
let mut clauses = vec![];
|
||||
loop {
|
||||
if !(self.parse_keyword(Keyword::WHEN)) {
|
||||
break;
|
||||
}
|
||||
let when_token = self.get_current_token().clone();
|
||||
|
||||
let mut clause_kind = MergeClauseKind::Matched;
|
||||
if self.parse_keyword(Keyword::NOT) {
|
||||
clause_kind = MergeClauseKind::NotMatched;
|
||||
}
|
||||
self.expect_keyword_is(Keyword::MATCHED)?;
|
||||
|
||||
if matches!(clause_kind, MergeClauseKind::NotMatched)
|
||||
&& self.parse_keywords(&[Keyword::BY, Keyword::SOURCE])
|
||||
{
|
||||
clause_kind = MergeClauseKind::NotMatchedBySource;
|
||||
} else if matches!(clause_kind, MergeClauseKind::NotMatched)
|
||||
&& self.parse_keywords(&[Keyword::BY, Keyword::TARGET])
|
||||
{
|
||||
clause_kind = MergeClauseKind::NotMatchedByTarget;
|
||||
}
|
||||
|
||||
let predicate = if self.parse_keyword(Keyword::AND) {
|
||||
Some(self.parse_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.expect_keyword_is(Keyword::THEN)?;
|
||||
|
||||
let merge_clause = match self.parse_one_of_keywords(&[
|
||||
Keyword::UPDATE,
|
||||
Keyword::INSERT,
|
||||
Keyword::DELETE,
|
||||
]) {
|
||||
Some(Keyword::UPDATE) => {
|
||||
if matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("UPDATE is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
}
|
||||
|
||||
let update_token = self.get_current_token().clone();
|
||||
self.expect_keyword_is(Keyword::SET)?;
|
||||
MergeAction::Update {
|
||||
update_token: update_token.into(),
|
||||
assignments: self.parse_comma_separated(Parser::parse_assignment)?,
|
||||
}
|
||||
}
|
||||
Some(Keyword::DELETE) => {
|
||||
if matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("DELETE is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
};
|
||||
|
||||
let delete_token = self.get_current_token().clone();
|
||||
MergeAction::Delete {
|
||||
delete_token: delete_token.into(),
|
||||
}
|
||||
}
|
||||
Some(Keyword::INSERT) => {
|
||||
if !matches!(
|
||||
clause_kind,
|
||||
MergeClauseKind::NotMatched | MergeClauseKind::NotMatchedByTarget
|
||||
) {
|
||||
return parser_err!(
|
||||
format_args!("INSERT is not allowed in a {clause_kind} merge clause"),
|
||||
self.get_current_token().span.start
|
||||
);
|
||||
};
|
||||
|
||||
let insert_token = self.get_current_token().clone();
|
||||
let is_mysql = dialect_of!(self is MySqlDialect);
|
||||
|
||||
let columns = self.parse_parenthesized_column_list(Optional, is_mysql)?;
|
||||
let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
|
||||
&& self.parse_keyword(Keyword::ROW)
|
||||
{
|
||||
(MergeInsertKind::Row, self.get_current_token().clone())
|
||||
} else {
|
||||
self.expect_keyword_is(Keyword::VALUES)?;
|
||||
let values_token = self.get_current_token().clone();
|
||||
let values = self.parse_values(is_mysql, false)?;
|
||||
(MergeInsertKind::Values(values), values_token)
|
||||
};
|
||||
MergeAction::Insert(MergeInsertExpr {
|
||||
insert_token: insert_token.into(),
|
||||
columns,
|
||||
kind_token: kind_token.into(),
|
||||
kind,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
return parser_err!(
|
||||
"expected UPDATE, DELETE or INSERT in merge clause",
|
||||
self.peek_token_ref().span.start
|
||||
);
|
||||
}
|
||||
};
|
||||
clauses.push(MergeClause {
|
||||
when_token: when_token.into(),
|
||||
clause_kind,
|
||||
predicate,
|
||||
action: merge_clause,
|
||||
});
|
||||
}
|
||||
Ok(clauses)
|
||||
}
|
||||
|
||||
fn parse_output(
|
||||
&mut self,
|
||||
start_keyword: Keyword,
|
||||
|
|
@ -17360,32 +17231,6 @@ impl<'a> Parser<'a> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn parse_merge(&mut self, merge_token: TokenWithSpan) -> Result<Statement, ParserError> {
|
||||
let into = self.parse_keyword(Keyword::INTO);
|
||||
|
||||
let table = self.parse_table_factor()?;
|
||||
|
||||
self.expect_keyword_is(Keyword::USING)?;
|
||||
let source = self.parse_table_factor()?;
|
||||
self.expect_keyword_is(Keyword::ON)?;
|
||||
let on = self.parse_expr()?;
|
||||
let clauses = self.parse_merge_clauses()?;
|
||||
let output = match self.parse_one_of_keywords(&[Keyword::OUTPUT, Keyword::RETURNING]) {
|
||||
Some(keyword) => Some(self.parse_output(keyword, self.get_current_token().clone())?),
|
||||
None => None,
|
||||
};
|
||||
|
||||
Ok(Statement::Merge {
|
||||
merge_token: merge_token.into(),
|
||||
into,
|
||||
table,
|
||||
source,
|
||||
on: Box::new(on),
|
||||
clauses,
|
||||
output,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_pragma_value(&mut self) -> Result<Value, ParserError> {
|
||||
match self.parse_value()?.value {
|
||||
v @ Value::SingleQuotedString(_) => Ok(v),
|
||||
|
|
|
|||
|
|
@ -1813,6 +1813,7 @@ fn parse_merge() {
|
|||
explicit_row: false,
|
||||
rows: vec![vec![Expr::value(number("1")), Expr::value(number("2"))]],
|
||||
}),
|
||||
insert_predicate: None,
|
||||
});
|
||||
let update_action = MergeAction::Update {
|
||||
update_token: AttachedToken::empty(),
|
||||
|
|
@ -1826,6 +1827,8 @@ fn parse_merge() {
|
|||
value: Expr::value(number("2")),
|
||||
},
|
||||
],
|
||||
update_predicate: None,
|
||||
delete_predicate: None,
|
||||
};
|
||||
|
||||
match bigquery_and_generic().verified_stmt(sql) {
|
||||
|
|
@ -1926,6 +1929,7 @@ fn parse_merge() {
|
|||
columns: vec![Ident::new("product"), Ident::new("quantity"),],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1937,6 +1941,7 @@ fn parse_merge() {
|
|||
columns: vec![Ident::new("product"), Ident::new("quantity"),],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1947,7 +1952,8 @@ fn parse_merge() {
|
|||
insert_token: AttachedToken::empty(),
|
||||
columns: vec![],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1958,7 +1964,8 @@ fn parse_merge() {
|
|||
insert_token: AttachedToken::empty(),
|
||||
columns: vec![],
|
||||
kind_token: AttachedToken::empty(),
|
||||
kind: MergeInsertKind::Row
|
||||
kind: MergeInsertKind::Row,
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -1990,7 +1997,8 @@ fn parse_merge() {
|
|||
Expr::value(number("1")),
|
||||
Expr::Identifier(Ident::new("DEFAULT")),
|
||||
]]
|
||||
})
|
||||
}),
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -2008,7 +2016,8 @@ fn parse_merge() {
|
|||
Expr::value(number("1")),
|
||||
Expr::Identifier(Ident::new("DEFAULT")),
|
||||
]]
|
||||
})
|
||||
}),
|
||||
insert_predicate: None,
|
||||
})
|
||||
},
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1644,6 +1644,10 @@ fn ms_and_generic() -> TestedDialects {
|
|||
TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})])
|
||||
}
|
||||
|
||||
fn only_generic() -> TestedDialects {
|
||||
TestedDialects::new(vec![Box::new(GenericDialect {})])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_json_ops_without_colon() {
|
||||
use self::BinaryOperator::*;
|
||||
|
|
@ -9946,6 +9950,7 @@ fn parse_merge() {
|
|||
]),
|
||||
]]
|
||||
}),
|
||||
insert_predicate: None,
|
||||
}),
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -9985,6 +9990,8 @@ fn parse_merge() {
|
|||
]),
|
||||
},
|
||||
],
|
||||
update_predicate: None,
|
||||
delete_predicate: None,
|
||||
},
|
||||
},
|
||||
MergeClause {
|
||||
|
|
@ -10077,6 +10084,76 @@ fn test_merge_with_delimiter() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_with_predicates() {
|
||||
let sql = "\
|
||||
MERGE INTO FOO \
|
||||
USING FOO_IMPORT \
|
||||
ON (FOO.ID = FOO_IMPORT.ID) \
|
||||
WHEN MATCHED THEN \
|
||||
UPDATE SET FOO.NAME = FOO_IMPORT.NAME \
|
||||
WHERE 1 = 1 \
|
||||
DELETE WHERE FOO.NAME LIKE '%.DELETE' \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (ID, NAME) \
|
||||
VALUES (FOO_IMPORT.ID, UPPER(FOO_IMPORT.NAME)) \
|
||||
WHERE NOT FOO_IMPORT.NAME LIKE '%.DO_NOT_INSERT'";
|
||||
only_generic().verified_stmt(sql);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_with_insert_qualified_columns() {
|
||||
let sql = "\
|
||||
MERGE INTO FOO USING FOO_IMPORT ON (FOO.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (FOO.ID, FOO.NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
let expected = "\
|
||||
MERGE INTO FOO USING FOO_IMPORT ON (FOO.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (ID, NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
only_generic().one_statement_parses_to(sql, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_with_insert_qualified_columns_via_alias() {
|
||||
let sql = "\
|
||||
MERGE INTO FOO F USING FOO_IMPORT ON (F.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (F.ID, F.NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
// note: this serialized form will break execution on an Oracle database
|
||||
// as it doesn't allow the "AS" keyword; Issue #1784
|
||||
let expected = "\
|
||||
MERGE INTO FOO AS F USING FOO_IMPORT ON (F.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (ID, NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
only_generic().one_statement_parses_to(sql, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_with_insert_qualified_columns_with_schema() {
|
||||
let sql = "\
|
||||
MERGE INTO PLAYGROUND.FOO USING FOO_IMPORT ON (PLAYGROUND.FOO.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (PLAYGROUND.FOO.ID, PLAYGROUND.FOO.NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
let expected = "\
|
||||
MERGE INTO PLAYGROUND.FOO USING FOO_IMPORT ON (PLAYGROUND.FOO.ID = FOO_IMPORT.ID) \
|
||||
WHEN NOT MATCHED THEN \
|
||||
INSERT (ID, NAME) \
|
||||
VALUES (1, 2)";
|
||||
|
||||
only_generic().one_statement_parses_to(sql, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_invalid_statements() {
|
||||
let dialects = all_dialects();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue