Skip to content

Commit 6dc6a01

Browse files
committed
Merge as top-level struct
1 parent f406386 commit 6dc6a01

File tree

7 files changed

+431
-406
lines changed

7 files changed

+431
-406
lines changed

src/ast/dml.rs

Lines changed: 343 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,340 @@ impl Display for Update {
310313
Ok(())
311314
}
312315
}
316+
317+
/// 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<Ident>,
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+
///
549+
/// Enabled via [`Dialect::supports_merge_insert_predicate`](crate::dialect::Dialect::supports_merge_insert_predicate).
550+
pub insert_predicate: Option<Expr>,
551+
}
552+
553+
impl Display for MergeInsertExpr {
554+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
555+
if !self.columns.is_empty() {
556+
write!(f, "({}) ", display_comma_separated(self.columns.as_slice()))?;
557+
}
558+
write!(f, "{}", self.kind)?;
559+
if let Some(predicate) = self.insert_predicate.as_ref() {
560+
write!(f, " WHERE {}", predicate)?;
561+
}
562+
Ok(())
563+
}
564+
}
565+
566+
/// The expression used to update rows within a `MERGE` statement.
567+
///
568+
/// Examples
569+
/// ```sql
570+
/// UPDATE SET quantity = T.quantity + S.quantity
571+
/// ```
572+
///
573+
/// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
574+
/// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
575+
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
576+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
577+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
578+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
579+
pub struct MergeUpdateExpr {
580+
/// The `UPDATE` token that starts the sub-expression.
581+
pub update_token: AttachedToken,
582+
/// The update assiment expressions
583+
pub assignments: Vec<Assignment>,
584+
/// `where_clause` for the update (Oralce specific)
585+
///
586+
/// Enabled via [`Dialect::supports_merge_update_predicate`](crate::dialect::Dialect::supports_merge_update_predicate).
587+
pub update_predicate: Option<Expr>,
588+
/// `delete_clause` for the update "delete where" (Oracle specific)
589+
///
590+
/// Enabled via [`Dialect::supports_merge_update_delete_predicate`](crate::dialect::Dialect::supports_merge_update_delete_predicate).
591+
pub delete_predicate: Option<Expr>,
592+
}
593+
594+
impl Display for MergeUpdateExpr {
595+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596+
write!(f, "SET {}", display_comma_separated(&self.assignments))?;
597+
if let Some(predicate) = self.update_predicate.as_ref() {
598+
write!(f, " WHERE {predicate}")?;
599+
}
600+
if let Some(predicate) = self.delete_predicate.as_ref() {
601+
write!(f, " DELETE WHERE {predicate}")?;
602+
}
603+
Ok(())
604+
}
605+
}
606+
607+
/// A `OUTPUT` Clause in the end of a `MERGE` Statement
608+
///
609+
/// Example:
610+
/// OUTPUT $action, deleted.* INTO dbo.temp_products;
611+
/// [mssql](https://learn.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql)
612+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
613+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
614+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
615+
pub enum OutputClause {
616+
Output {
617+
output_token: AttachedToken,
618+
select_items: Vec<SelectItem>,
619+
into_table: Option<SelectInto>,
620+
},
621+
Returning {
622+
returning_token: AttachedToken,
623+
select_items: Vec<SelectItem>,
624+
},
625+
}
626+
627+
impl fmt::Display for OutputClause {
628+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
629+
match self {
630+
OutputClause::Output {
631+
output_token: _,
632+
select_items,
633+
into_table,
634+
} => {
635+
f.write_str("OUTPUT ")?;
636+
display_comma_separated(select_items).fmt(f)?;
637+
if let Some(into_table) = into_table {
638+
f.write_str(" ")?;
639+
into_table.fmt(f)?;
640+
}
641+
Ok(())
642+
}
643+
OutputClause::Returning {
644+
returning_token: _,
645+
select_items,
646+
} => {
647+
f.write_str("RETURNING ")?;
648+
display_comma_separated(select_items).fmt(f)
649+
}
650+
}
651+
}
652+
}

0 commit comments

Comments
 (0)