Skip to content

Commit adf2ebd

Browse files
committed
Oracle: Support for MERGE predicates
1 parent 982f766 commit adf2ebd

8 files changed

Lines changed: 672 additions & 168 deletions

File tree

src/ast/mod.rs

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8635,6 +8635,7 @@ impl Display for MergeInsertKind {
86358635
///
86368636
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86378637
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8638+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86388639
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86398640
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86408641
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8653,14 +8654,22 @@ pub struct MergeInsertExpr {
86538654
pub kind_token: AttachedToken,
86548655
/// The insert type used by the statement.
86558656
pub kind: MergeInsertKind,
8657+
/// An optional condition to restrict the insertion (Oracle specific)
8658+
///
8659+
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
8660+
pub insert_predicate: Option<Expr>,
86568661
}
86578662

86588663
impl Display for MergeInsertExpr {
86598664
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86608665
if !self.columns.is_empty() {
86618666
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
86628667
}
8663-
write!(f, "{}", self.kind)
8668+
write!(f, "{}", self.kind)?;
8669+
if let Some(predicate) = self.insert_predicate.as_ref() {
8670+
write!(f, " WHERE {}", predicate)?;
8671+
}
8672+
Ok(())
86648673
}
86658674
}
86668675

@@ -8673,6 +8682,7 @@ impl Display for MergeInsertExpr {
86738682
///
86748683
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86758684
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8685+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86768686
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86778687
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86788688
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8693,7 +8703,16 @@ pub enum MergeAction {
86938703
Update {
86948704
/// The `UPDATE` token that starts the sub-expression.
86958705
update_token: AttachedToken,
8706+
/// The update assiment expressions
86968707
assignments: Vec<Assignment>,
8708+
/// `where_clause` for the update (Oralce specific)
8709+
///
8710+
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
8711+
update_predicate: Option<Expr>,
8712+
/// `delete_clause` for the update "delete where" (Oracle specific)
8713+
///
8714+
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
8715+
delete_predicate: Option<Expr>,
86978716
},
86988717
/// A plain `DELETE` clause
86998718
Delete {
@@ -8708,8 +8727,20 @@ impl Display for MergeAction {
87088727
MergeAction::Insert(insert) => {
87098728
write!(f, "INSERT {insert}")
87108729
}
8711-
MergeAction::Update { assignments, .. } => {
8712-
write!(f, "UPDATE SET {}", display_comma_separated(assignments))
8730+
MergeAction::Update {
8731+
update_token: _,
8732+
assignments,
8733+
update_predicate,
8734+
delete_predicate,
8735+
} => {
8736+
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
8737+
if let Some(predicate) = update_predicate.as_ref() {
8738+
write!(f, " WHERE {predicate}")?;
8739+
}
8740+
if let Some(predicate) = delete_predicate.as_ref() {
8741+
write!(f, " DELETE WHERE {predicate}")?;
8742+
}
8743+
Ok(())
87138744
}
87148745
MergeAction::Delete { .. } => {
87158746
write!(f, "DELETE")

src/ast/spans.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2421,8 +2421,13 @@ impl Spanned for MergeAction {
24212421
MergeAction::Update {
24222422
update_token,
24232423
assignments,
2424+
update_predicate,
2425+
delete_predicate,
24242426
} => union_spans(
2425-
core::iter::once(update_token.0.span).chain(assignments.iter().map(Spanned::span)),
2427+
core::iter::once(update_token.0.span)
2428+
.chain(assignments.iter().map(Spanned::span))
2429+
.chain(update_predicate.iter().map(Spanned::span))
2430+
.chain(delete_predicate.iter().map(Spanned::span)),
24262431
),
24272432
MergeAction::Delete { delete_token } => delete_token.0.span,
24282433
}
@@ -2441,6 +2446,7 @@ impl Spanned for MergeInsertExpr {
24412446
},
24422447
]
24432448
.into_iter()
2449+
.chain(self.insert_predicate.iter().map(Spanned::span))
24442450
.chain(self.columns.iter().map(|i| i.span)),
24452451
)
24462452
}
@@ -2814,6 +2820,8 @@ WHERE id = 1
28142820
if let MergeAction::Update {
28152821
update_token,
28162822
assignments: _,
2823+
update_predicate: _,
2824+
delete_predicate: _,
28172825
} = &clauses[1].action
28182826
{
28192827
assert_eq!(
@@ -2934,4 +2942,44 @@ WHERE id = 1
29342942
panic!("not a MERGE statement");
29352943
};
29362944
}
2945+
2946+
#[test]
2947+
fn test_merge_statement_spans_with_update_predicates() {
2948+
let sql = r#"
2949+
MERGE INTO a USING b ON a.id = b.id
2950+
WHEN MATCHED THEN
2951+
UPDATE set a.x = a.x + b.x
2952+
WHERE b.x != 2
2953+
DELETE WHERE a.x <> 3"#;
2954+
2955+
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
2956+
assert_eq!(1, r.len());
2957+
2958+
// ~ assert the span of the whole statement
2959+
let stmt_span = r[0].span();
2960+
assert_eq!(
2961+
stmt_span,
2962+
Span::new(Location::new(2, 8), Location::new(6, 36))
2963+
);
2964+
}
2965+
2966+
#[test]
2967+
fn test_merge_statement_spans_with_insert_predicate() {
2968+
let sql = r#"
2969+
MERGE INTO a USING b ON a.id = b.id
2970+
WHEN NOT MATCHED THEN
2971+
INSERT VALUES (b.x, b.y) WHERE b.x != 2
2972+
-- qed
2973+
"#;
2974+
2975+
let r = Parser::parse_sql(&crate::dialect::GenericDialect, sql).unwrap();
2976+
assert_eq!(1, r.len());
2977+
2978+
// ~ assert the span of the whole statement
2979+
let stmt_span = r[0].span();
2980+
assert_eq!(
2981+
stmt_span,
2982+
Span::new(Location::new(2, 8), Location::new(4, 52))
2983+
);
2984+
}
29372985
}

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)