Skip to content

Commit 142d3e6

Browse files
committed
Oracle: Support for MERGE predicates
1 parent 4beea9a commit 142d3e6

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
@@ -8626,6 +8626,7 @@ impl Display for MergeInsertKind {
86268626
///
86278627
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86288628
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8629+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86298630
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86308631
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86318632
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8644,14 +8645,22 @@ pub struct MergeInsertExpr {
86448645
pub kind_token: AttachedToken,
86458646
/// The insert type used by the statement.
86468647
pub kind: MergeInsertKind,
8648+
/// An optional condition to restrict the insertion (Oracle specific)
8649+
///
8650+
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
8651+
pub insert_predicate: Option<Expr>,
86478652
}
86488653

86498654
impl Display for MergeInsertExpr {
86508655
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86518656
if !self.columns.is_empty() {
86528657
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
86538658
}
8654-
write!(f, "{}", self.kind)
8659+
write!(f, "{}", self.kind)?;
8660+
if let Some(predicate) = self.insert_predicate.as_ref() {
8661+
write!(f, " WHERE {}", predicate)?;
8662+
}
8663+
Ok(())
86558664
}
86568665
}
86578666

@@ -8664,6 +8673,7 @@ impl Display for MergeInsertExpr {
86648673
///
86658674
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86668675
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8676+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86678677
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86688678
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86698679
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8684,7 +8694,16 @@ pub enum MergeAction {
86848694
Update {
86858695
/// The `UPDATE` token that starts the sub-expression.
86868696
update_token: AttachedToken,
8697+
/// The update assiment expressions
86878698
assignments: Vec<Assignment>,
8699+
/// `where_clause` for the update (Oralce specific)
8700+
///
8701+
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
8702+
update_predicate: Option<Expr>,
8703+
/// `delete_clause` for the update "delete where" (Oracle specific)
8704+
///
8705+
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
8706+
delete_predicate: Option<Expr>,
86888707
},
86898708
/// A plain `DELETE` clause
86908709
Delete {
@@ -8699,8 +8718,20 @@ impl Display for MergeAction {
86998718
MergeAction::Insert(insert) => {
87008719
write!(f, "INSERT {insert}")
87018720
}
8702-
MergeAction::Update { assignments, .. } => {
8703-
write!(f, "UPDATE SET {}", display_comma_separated(assignments))
8721+
MergeAction::Update {
8722+
update_token: _,
8723+
assignments,
8724+
update_predicate,
8725+
delete_predicate,
8726+
} => {
8727+
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
8728+
if let Some(predicate) = update_predicate.as_ref() {
8729+
write!(f, " WHERE {predicate}")?;
8730+
}
8731+
if let Some(predicate) = delete_predicate.as_ref() {
8732+
write!(f, " DELETE WHERE {predicate}")?;
8733+
}
8734+
Ok(())
87048735
}
87058736
MergeAction::Delete { .. } => {
87068737
write!(f, "DELETE")

src/ast/spans.rs

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

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)