Skip to content

Commit ca2d333

Browse files
xitepiffyio
andauthored
Oracle: Support for MERGE predicates (#2101)
Co-authored-by: Ifeanyi Ubah <ify1992@yahoo.com>
1 parent 326f111 commit ca2d333

File tree

8 files changed

+759
-534
lines changed

8 files changed

+759
-534
lines changed

src/ast/dml.rs

Lines changed: 337 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,16 @@ use serde::{Deserialize, Serialize};
2424
#[cfg(feature = "visitor")]
2525
use sqlparser_derive::{Visit, VisitMut};
2626

27-
use crate::display_utils::{indented_list, Indent, SpaceOrNewline};
27+
use crate::{
28+
ast::display_separated,
29+
display_utils::{indented_list, Indent, SpaceOrNewline},
30+
};
2831

2932
use super::{
3033
display_comma_separated, helpers::attached_token::AttachedToken, query::InputFormatClause,
3134
Assignment, Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert,
32-
OrderByExpr, Query, SelectItem, Setting, SqliteOnConflict, TableObject, TableWithJoins,
33-
UpdateTableFromKind,
35+
OrderByExpr, Query, SelectInto, SelectItem, Setting, SqliteOnConflict, TableFactor,
36+
TableObject, TableWithJoins, UpdateTableFromKind, Values,
3437
};
3538

3639
/// INSERT statement.
@@ -310,3 +313,334 @@ impl Display for Update {
310313
Ok(())
311314
}
312315
}
316+
317+
/// A `MERGE` statement.
318+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
319+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
320+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
321+
pub struct Merge {
322+
/// The `MERGE` token that starts the statement.
323+
pub merge_token: AttachedToken,
324+
/// optional INTO keyword
325+
pub into: bool,
326+
/// Specifies the table to merge
327+
pub table: TableFactor,
328+
/// Specifies the table or subquery to join with the target table
329+
pub source: TableFactor,
330+
/// Specifies the expression on which to join the target table and source
331+
pub on: Box<Expr>,
332+
/// Specifies the actions to perform when values match or do not match.
333+
pub clauses: Vec<MergeClause>,
334+
// Specifies the output to save changes in MSSQL
335+
pub output: Option<OutputClause>,
336+
}
337+
338+
impl Display for Merge {
339+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340+
write!(
341+
f,
342+
"MERGE{int} {table} USING {source} ",
343+
int = if self.into { " INTO" } else { "" },
344+
table = self.table,
345+
source = self.source,
346+
)?;
347+
write!(f, "ON {on} ", on = self.on)?;
348+
write!(f, "{}", display_separated(&self.clauses, " "))?;
349+
if let Some(ref output) = self.output {
350+
write!(f, " {output}")?;
351+
}
352+
Ok(())
353+
}
354+
}
355+
356+
/// A `WHEN` clause within a `MERGE` Statement
357+
///
358+
/// Example:
359+
/// ```sql
360+
/// WHEN NOT MATCHED BY SOURCE AND product LIKE '%washer%' THEN DELETE
361+
/// ```
362+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
363+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
364+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
365+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
366+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
367+
pub struct MergeClause {
368+
/// The `WHEN` token that starts the sub-expression.
369+
pub when_token: AttachedToken,
370+
pub clause_kind: MergeClauseKind,
371+
pub predicate: Option<Expr>,
372+
pub action: MergeAction,
373+
}
374+
375+
impl Display for MergeClause {
376+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
377+
let MergeClause {
378+
when_token: _,
379+
clause_kind,
380+
predicate,
381+
action,
382+
} = self;
383+
384+
write!(f, "WHEN {clause_kind}")?;
385+
if let Some(pred) = predicate {
386+
write!(f, " AND {pred}")?;
387+
}
388+
write!(f, " THEN {action}")
389+
}
390+
}
391+
392+
/// Variant of `WHEN` clause used within a `MERGE` Statement.
393+
///
394+
/// Example:
395+
/// ```sql
396+
/// MERGE INTO T USING U ON FALSE WHEN MATCHED THEN DELETE
397+
/// ```
398+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
399+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
400+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
401+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
402+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
403+
pub enum MergeClauseKind {
404+
/// `WHEN MATCHED`
405+
Matched,
406+
/// `WHEN NOT MATCHED`
407+
NotMatched,
408+
/// `WHEN MATCHED BY TARGET`
409+
///
410+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
411+
NotMatchedByTarget,
412+
/// `WHEN MATCHED BY SOURCE`
413+
///
414+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
415+
NotMatchedBySource,
416+
}
417+
418+
impl Display for MergeClauseKind {
419+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420+
match self {
421+
MergeClauseKind::Matched => write!(f, "MATCHED"),
422+
MergeClauseKind::NotMatched => write!(f, "NOT MATCHED"),
423+
MergeClauseKind::NotMatchedByTarget => write!(f, "NOT MATCHED BY TARGET"),
424+
MergeClauseKind::NotMatchedBySource => write!(f, "NOT MATCHED BY SOURCE"),
425+
}
426+
}
427+
}
428+
429+
/// Underlying statement of a `WHEN` clause within a `MERGE` Statement
430+
///
431+
/// Example
432+
/// ```sql
433+
/// INSERT (product, quantity) VALUES(product, quantity)
434+
/// ```
435+
///
436+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
437+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
438+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
439+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
440+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
441+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
442+
pub enum MergeAction {
443+
/// An `INSERT` clause
444+
///
445+
/// Example:
446+
/// ```sql
447+
/// INSERT (product, quantity) VALUES(product, quantity)
448+
/// ```
449+
Insert(MergeInsertExpr),
450+
/// An `UPDATE` clause
451+
///
452+
/// Example:
453+
/// ```sql
454+
/// UPDATE SET quantity = T.quantity + S.quantity
455+
/// ```
456+
Update(MergeUpdateExpr),
457+
/// A plain `DELETE` clause
458+
Delete {
459+
/// The `DELETE` token that starts the sub-expression.
460+
delete_token: AttachedToken,
461+
},
462+
}
463+
464+
impl Display for MergeAction {
465+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
466+
match self {
467+
MergeAction::Insert(insert) => {
468+
write!(f, "INSERT {insert}")
469+
}
470+
MergeAction::Update(update) => {
471+
write!(f, "UPDATE {update}")
472+
}
473+
MergeAction::Delete { .. } => {
474+
write!(f, "DELETE")
475+
}
476+
}
477+
}
478+
}
479+
480+
/// The type of expression used to insert rows within a `MERGE` statement.
481+
///
482+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
483+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
484+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
485+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
486+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
487+
pub enum MergeInsertKind {
488+
/// The insert expression is defined from an explicit `VALUES` clause
489+
///
490+
/// Example:
491+
/// ```sql
492+
/// INSERT VALUES(product, quantity)
493+
/// ```
494+
Values(Values),
495+
/// The insert expression is defined using only the `ROW` keyword.
496+
///
497+
/// Example:
498+
/// ```sql
499+
/// INSERT ROW
500+
/// ```
501+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
502+
Row,
503+
}
504+
505+
impl Display for MergeInsertKind {
506+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
507+
match self {
508+
MergeInsertKind::Values(values) => {
509+
write!(f, "{values}")
510+
}
511+
MergeInsertKind::Row => {
512+
write!(f, "ROW")
513+
}
514+
}
515+
}
516+
}
517+
518+
/// The expression used to insert rows within a `MERGE` statement.
519+
///
520+
/// Examples
521+
/// ```sql
522+
/// INSERT (product, quantity) VALUES(product, quantity)
523+
/// INSERT ROW
524+
/// ```
525+
///
526+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
527+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
528+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
529+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
530+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
531+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
532+
pub struct MergeInsertExpr {
533+
/// The `INSERT` token that starts the sub-expression.
534+
pub insert_token: AttachedToken,
535+
/// Columns (if any) specified by the insert.
536+
///
537+
/// Example:
538+
/// ```sql
539+
/// INSERT (product, quantity) VALUES(product, quantity)
540+
/// INSERT (product, quantity) ROW
541+
/// ```
542+
pub columns: Vec<ObjectName>,
543+
/// The token, `[VALUES | ROW]` starting `kind`.
544+
pub kind_token: AttachedToken,
545+
/// The insert type used by the statement.
546+
pub kind: MergeInsertKind,
547+
/// An optional condition to restrict the insertion (Oracle specific)
548+
pub insert_predicate: Option<Expr>,
549+
}
550+
551+
impl Display for MergeInsertExpr {
552+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
553+
if !self.columns.is_empty() {
554+
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
555+
}
556+
write!(f, "{}", self.kind)?;
557+
if let Some(predicate) = self.insert_predicate.as_ref() {
558+
write!(f, " WHERE {}", predicate)?;
559+
}
560+
Ok(())
561+
}
562+
}
563+
564+
/// The expression used to update rows within a `MERGE` statement.
565+
///
566+
/// Examples
567+
/// ```sql
568+
/// UPDATE SET quantity = T.quantity + S.quantity
569+
/// ```
570+
///
571+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
572+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
573+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
574+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
575+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
576+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
577+
pub struct MergeUpdateExpr {
578+
/// The `UPDATE` token that starts the sub-expression.
579+
pub update_token: AttachedToken,
580+
/// The update assiment expressions
581+
pub assignments: Vec<Assignment>,
582+
/// `where_clause` for the update (Oralce specific)
583+
pub update_predicate: Option<Expr>,
584+
/// `delete_clause` for the update "delete where" (Oracle specific)
585+
pub delete_predicate: Option<Expr>,
586+
}
587+
588+
impl Display for MergeUpdateExpr {
589+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590+
write!(f, "SET {}", display_comma_separated(&self.assignments))?;
591+
if let Some(predicate) = self.update_predicate.as_ref() {
592+
write!(f, " WHERE {predicate}")?;
593+
}
594+
if let Some(predicate) = self.delete_predicate.as_ref() {
595+
write!(f, " DELETE WHERE {predicate}")?;
596+
}
597+
Ok(())
598+
}
599+
}
600+
601+
/// A `OUTPUT` Clause in the end of a `MERGE` Statement
602+
///
603+
/// Example:
604+
/// OUTPUT $action, deleted.* INTO dbo.temp_products;
605+
/// [mssql](https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql)
606+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
607+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
608+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
609+
pub enum OutputClause {
610+
Output {
611+
output_token: AttachedToken,
612+
select_items: Vec<SelectItem>,
613+
into_table: Option<SelectInto>,
614+
},
615+
Returning {
616+
returning_token: AttachedToken,
617+
select_items: Vec<SelectItem>,
618+
},
619+
}
620+
621+
impl fmt::Display for OutputClause {
622+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
623+
match self {
624+
OutputClause::Output {
625+
output_token: _,
626+
select_items,
627+
into_table,
628+
} => {
629+
f.write_str("OUTPUT ")?;
630+
display_comma_separated(select_items).fmt(f)?;
631+
if let Some(into_table) = into_table {
632+
f.write_str(" ")?;
633+
into_table.fmt(f)?;
634+
}
635+
Ok(())
636+
}
637+
OutputClause::Returning {
638+
returning_token: _,
639+
select_items,
640+
} => {
641+
f.write_str("RETURNING ")?;
642+
display_comma_separated(select_items).fmt(f)
643+
}
644+
}
645+
}
646+
}

0 commit comments

Comments
 (0)