Skip to content

Commit 29f2fd2

Browse files
committed
Oracle: Support for MERGE predicates
1 parent 5a3b63b commit 29f2fd2

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
@@ -8644,6 +8644,7 @@ impl Display for MergeInsertKind {
86448644
///
86458645
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86468646
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8647+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86478648
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86488649
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86498650
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8662,14 +8663,22 @@ pub struct MergeInsertExpr {
86628663
pub kind_token: AttachedToken,
86638664
/// The insert type used by the statement.
86648665
pub kind: MergeInsertKind,
8666+
/// An optional condition to restrict the insertion (Oracle specific)
8667+
///
8668+
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
8669+
pub insert_predicate: Option<Expr>,
86658670
}
86668671

86678672
impl Display for MergeInsertExpr {
86688673
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
86698674
if !self.columns.is_empty() {
86708675
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
86718676
}
8672-
write!(f, "{}", self.kind)
8677+
write!(f, "{}", self.kind)?;
8678+
if let Some(predicate) = self.insert_predicate.as_ref() {
8679+
write!(f, " WHERE {}", predicate)?;
8680+
}
8681+
Ok(())
86738682
}
86748683
}
86758684

@@ -8682,6 +8691,7 @@ impl Display for MergeInsertExpr {
86828691
///
86838692
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
86848693
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
8694+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
86858695
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
86868696
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
86878697
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
@@ -8702,7 +8712,16 @@ pub enum MergeAction {
87028712
Update {
87038713
/// The `UPDATE` token that starts the sub-expression.
87048714
update_token: AttachedToken,
8715+
/// The update assiment expressions
87058716
assignments: Vec<Assignment>,
8717+
/// `where_clause` for the update (Oralce specific)
8718+
///
8719+
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
8720+
update_predicate: Option<Expr>,
8721+
/// `delete_clause` for the update "delete where" (Oracle specific)
8722+
///
8723+
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
8724+
delete_predicate: Option<Expr>,
87068725
},
87078726
/// A plain `DELETE` clause
87088727
Delete {
@@ -8717,8 +8736,20 @@ impl Display for MergeAction {
87178736
MergeAction::Insert(insert) => {
87188737
write!(f, "INSERT {insert}")
87198738
}
8720-
MergeAction::Update { assignments, .. } => {
8721-
write!(f, "UPDATE SET {}", display_comma_separated(assignments))
8739+
MergeAction::Update {
8740+
update_token: _,
8741+
assignments,
8742+
update_predicate,
8743+
delete_predicate,
8744+
} => {
8745+
write!(f, "UPDATE SET {}", display_comma_separated(assignments))?;
8746+
if let Some(predicate) = update_predicate.as_ref() {
8747+
write!(f, " WHERE {predicate}")?;
8748+
}
8749+
if let Some(predicate) = delete_predicate.as_ref() {
8750+
write!(f, " DELETE WHERE {predicate}")?;
8751+
}
8752+
Ok(())
87228753
}
87238754
MergeAction::Delete { .. } => {
87248755
write!(f, "DELETE")

src/ast/spans.rs

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

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
@@ -603,13 +603,122 @@ pub trait Dialect: Debug + Any {
603603
false
604604
}
605605

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

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

0 commit comments

Comments
 (0)