Skip to content

Commit c556446

Browse files
committed
Oracle: Support for MERGE predicates
1 parent f06c68f commit c556446

File tree

8 files changed

+670
-166
lines changed

8 files changed

+670
-166
lines changed

src/ast/mod.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8618,6 +8618,7 @@ impl Display for MergeInsertKind {
86188618
///
86198619
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86208620
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8621+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86218622
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86228623
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86238624
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8636,14 +8637,22 @@ pub struct MergeInsertExpr {
86368637
pub kind_token: AttachedToken,
86378638
/// The insert type used by the statement.
86388639
pub kind: MergeInsertKind,
8640+
/// An optional condition to restrict the insertion (Oracle specific)
8641+
///
8642+
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
8643+
pub insert_predicate: Option<Expr>,
86398644
}
86408645

86418646
impl Display for MergeInsertExpr {
86428647
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86438648
if !self.columns.is_empty() {
86448649
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
86458650
}
8646-
write!(f, "{}", self.kind)
8651+
write!(f, "{}", self.kind)?;
8652+
if let Some(predicate) = self.insert_predicate.as_ref() {
8653+
write!(f, " WHERE {}", predicate)?;
8654+
}
8655+
Ok(())
86478656
}
86488657
}
86498658

@@ -8656,6 +8665,7 @@ impl Display for MergeInsertExpr {
86568665
///
86578666
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86588667
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8668+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86598669
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86608670
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86618671
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8676,7 +8686,16 @@ pub enum MergeAction {
86768686
Update {
86778687
/// The `UPDATE` token that starts the sub-expression.
86788688
update_token: AttachedToken,
8689+
/// The update assiment expressions
86798690
assignments: Vec<Assignment>,
8691+
/// `where_clause` for the update (Oralce specific)
8692+
///
8693+
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
8694+
update_predicate: Option<Expr>,
8695+
/// `delete_clause` for the update "delete where" (Oracle specific)
8696+
///
8697+
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
8698+
delete_predicate: Option<Expr>,
86808699
},
86818700
/// A plain `DELETE` clause
86828701
Delete {
@@ -8691,8 +8710,20 @@ impl Display for MergeAction {
86918710
MergeAction::Insert(insert) => {
86928711
write!(f, "INSERT {insert}")
86938712
}
8694-
MergeAction::Update { assignments, .. } => {
8695-
write!(f, "UPDATE SET {}", display_comma_separated(assignments))
8713+
MergeAction::Update {
8714+
update_token: _,
8715+
assignments,
8716+
update_predicate,
8717+
delete_predicate,
8718+
} => {
8719+
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
8720+
if let Some(predicate) = update_predicate.as_ref() {
8721+
write!(f, " WHERE {predicate}")?;
8722+
}
8723+
if let Some(predicate) = delete_predicate.as_ref() {
8724+
write!(f, " DELETE WHERE {predicate}")?;
8725+
}
8726+
Ok(())
86968727
}
86978728
MergeAction::Delete { .. } => {
86988729
write!(f, "DELETE")

src/ast/spans.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2407,8 +2407,13 @@ impl Spanned for MergeAction {
24072407
MergeAction::Update {
24082408
update_token,
24092409
assignments,
2410+
update_predicate,
2411+
delete_predicate,
24102412
} => union_spans(
2411-
core::iter::once(update_token.0.span).chain(assignments.iter().map(Spanned::span)),
2413+
core::iter::once(update_token.0.span)
2414+
.chain(assignments.iter().map(Spanned::span))
2415+
.chain(update_predicate.iter().map(Spanned::span))
2416+
.chain(delete_predicate.iter().map(Spanned::span)),
24122417
),
24132418
MergeAction::Delete { delete_token } => delete_token.0.span,
24142419
}
@@ -2427,6 +2432,7 @@ impl Spanned for MergeInsertExpr {
24272432
},
24282433
]
24292434
.into_iter()
2435+
.chain(self.insert_predicate.iter().map(Spanned::span))
24302436
.chain(self.columns.iter().map(|i| i.span)),
24312437
)
24322438
}
@@ -2800,6 +2806,8 @@ WHERE id = 1
28002806
if let MergeAction::Update {
28012807
update_token,
28022808
assignments: _,
2809+
update_predicate: _,
2810+
delete_predicate: _,
28032811
} = &clauses[1].action
28042812
{
28052813
assert_eq!(
@@ -2920,4 +2928,44 @@ WHERE id = 1
29202928
panic!("not a MERGE statement");
29212929
};
29222930
}
2931+
2932+
#[test]
2933+
fn test_merge_statement_spans_with_update_predicates() {
2934+
let sql = r#"
2935+
MERGE INTO a USING b ON a.id = b.id
2936+
WHEN MATCHED THEN
2937+
UPDATE set a.x = a.x + b.x
2938+
WHERE b.x != 2
2939+
DELETE WHERE a.x <> 3"#;
2940+
2941+
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
2942+
assert_eq!(1, r.len());
2943+
2944+
// ~ assert the span of the whole statement
2945+
let stmt_span = r[0].span();
2946+
assert_eq!(
2947+
stmt_span,
2948+
Span::new(Location::new(2, 8), Location::new(6, 36))
2949+
);
2950+
}
2951+
2952+
#[test]
2953+
fn test_merge_statement_spans_with_insert_predicate() {
2954+
let sql = r#"
2955+
MERGE INTO a USING b ON a.id = b.id
2956+
WHEN NOT MATCHED THEN
2957+
INSERT VALUES (b.x, b.y) WHERE b.x != 2
2958+
-- qed
2959+
"#;
2960+
2961+
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
2962+
assert_eq!(1, r.len());
2963+
2964+
// ~ assert the span of the whole statement
2965+
let stmt_span = r[0].span();
2966+
assert_eq!(
2967+
stmt_span,
2968+
Span::new(Location::new(2, 8), Location::new(4, 52))
2969+
);
2970+
}
29232971
}

src/dialect/generic.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,4 +195,20 @@ impl Dialect for GenericDialect {
195195
fn supports_interval_options(&self) -> bool {
196196
true
197197
}
198+
199+
fn supports_merge_insert_qualified_columns(&self) -> bool {
200+
true
201+
}
202+
203+
fn supports_merge_insert_predicate(&self) -> bool {
204+
true
205+
}
206+
207+
fn supports_merge_update_predicate(&self) -> bool {
208+
true
209+
}
210+
211+
fn supports_merge_update_delete_predicate(&self) -> bool {
212+
true
213+
}
198214
}

src/dialect/mod.rs

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,13 +601,122 @@ pub trait Dialect: Debug + Any {
601601
false
602602
}
603603

604-
/// Return true if the dialect supports specifying multiple options
604+
/// Returns true if the dialect supports specifying multiple options
605605
/// in a `CREATE TABLE` statement for the structure of the new table. For example:
606606
/// `CREATE TABLE t (a INT, b INT) AS SELECT 1 AS b, 2 AS a`
607607
fn supports_create_table_multi_schema_info_sources(&self) -> bool {
608608
false
609609
}
610610

611+
/// Returns `true` if the dialect supports qualified column names
612+
/// as part of a MERGE's INSERT's column list. Example:
613+
///
614+
/// ```sql
615+
/// MERGE INTO FOO
616+
/// USING FOO_IMP
617+
/// ON (FOO.ID = FOO_IMP.ID)
618+
/// WHEN NOT MATCHED THEN
619+
/// -- no qualifier
620+
/// INSERT (ID, NAME)
621+
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
622+
/// ```
623+
/// vs.
624+
/// ```sql
625+
/// MERGE INTO FOO
626+
/// USING FOO_IMP
627+
/// ON (FOO.ID = FOO_IMP.ID)
628+
/// WHEN NOT MATCHED THEN
629+
/// -- here: qualified
630+
/// INSERT (FOO.ID, FOO.NAME)
631+
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
632+
/// ```
633+
/// or
634+
/// ```sql
635+
/// MERGE INTO FOO X
636+
/// USING FOO_IMP
637+
/// ON (X.ID = FOO_IMP.ID)
638+
/// WHEN NOT MATCHED THEN
639+
/// -- here: qualified using the alias
640+
/// INSERT (X.ID, X.NAME)
641+
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
642+
/// ```
643+
///
644+
/// Note: in the latter case, the qualifier must match the target table
645+
/// name or its alias if one is present. The parser will enforce this.
646+
///
647+
/// The default implementation always returns `false` not allowing the
648+
/// qualifiers.
649+
fn supports_merge_insert_qualified_columns(&self) -> bool {
650+
false
651+
}
652+
653+
/// Returns `true` if the dialect supports specify an INSERT predicate in
654+
/// MERGE statements. Example:
655+
///
656+
/// ```sql
657+
/// MERGE INTO FOO
658+
/// USING FOO_IMP
659+
/// ON (FOO.ID = FOO_IMP.ID)
660+
/// WHEN NOT MATCHED THEN
661+
/// INSERT (ID, NAME)
662+
/// VALUES (FOO_IMP.ID, UPPER(FOO_IMP.NAME))
663+
/// -- insert predicate
664+
/// WHERE NOT FOO_IMP.NAME like '%.IGNORE'
665+
/// ```
666+
///
667+
/// The default implementation always returns `false` indicating no
668+
/// support for the additional predicate.
669+
///
670+
/// See also [Dialect::supports_merge_update_predicate] and
671+
/// [Dialect::supports_merge_update_delete_predicate].
672+
fn supports_merge_insert_predicate(&self) -> bool {
673+
false
674+
}
675+
676+
/// Indicates the supports of UPDATE predicates in MERGE
677+
/// statements. Example:
678+
///
679+
/// ```sql
680+
/// MERGE INTO FOO
681+
/// USING FOO_IMPORT
682+
/// ON (FOO.ID = FOO_IMPORT.ID)
683+
/// WHEN MATCHED THEN
684+
/// UPDATE SET FOO.NAME = FOO_IMPORT.NAME
685+
/// -- update predicate
686+
/// WHERE FOO.NAME <> 'pete'
687+
/// ```
688+
///
689+
/// The default implementation always returns false indicating no support
690+
/// for the additional predicate.
691+
///
692+
/// See also [Dialect::supports_merge_insert_predicate] and
693+
/// [Dialect::supports_merge_update_delete_predicate].
694+
fn supports_merge_update_predicate(&self) -> bool {
695+
false
696+
}
697+
698+
/// Indicates the supports of UPDATE ... DELETEs and associated predicates
699+
/// in MERGE statements. Example:
700+
///
701+
/// ```sql
702+
/// MERGE INTO FOO
703+
/// USING FOO_IMPORT
704+
/// ON (FOO.ID = FOO_IMPORT.ID)
705+
/// WHEN MATCHED THEN
706+
/// UPDATE SET FOO.NAME = FOO_IMPORT.NAME
707+
/// -- update delete with predicate
708+
/// DELETE WHERE UPPER(FOO.NAME) == FOO.NAME
709+
/// ```
710+
///
711+
/// The default implementation always returns false indicating no support
712+
/// for the `UPDATE ... DELETE` and its associated predicate.
713+
///
714+
/// See also [Dialect::supports_merge_insert_predicate] and
715+
/// [Dialect::supports_merge_update_predicate].
716+
fn supports_merge_update_delete_predicate(&self) -> bool {
717+
false
718+
}
719+
611720
/// Dialect-specific infix parser override
612721
///
613722
/// This method is called to parse the next infix expression.

0 commit comments

Comments
 (0)