You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datafusion.apache.org by gi...@apache.org on 2026/05/02 04:56:44 UTC

(datafusion-sqlparser-rs) branch main updated: Databricks: Add support for `UPDATE SET *` and `INSERT *` in MERGE statements (#2325)

This is an automated email from the ASF dual-hosted git repository.

github-merge-queue[bot] pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new a2811711 Databricks: Add support for `UPDATE SET *` and `INSERT *` in MERGE statements (#2325)
a2811711 is described below

commit a281171106b1cd28744eb0ec3e642182bf5d49a3
Author: finchxxia <13...@163.com>
AuthorDate: Sat May 2 12:52:09 2026 +0800

    Databricks: Add support for `UPDATE SET *` and `INSERT *` in MERGE statements (#2325)
    
    Co-authored-by: Ifeanyi Ubah <if...@yahoo.com>
---
 src/ast/dml.rs              | 56 ++++++++++++++++++++++++++++++++++---
 src/ast/mod.rs              |  6 ++--
 src/ast/spans.rs            | 28 +++++++++++--------
 src/parser/merge.rs         | 68 ++++++++++++++++++++++++++++-----------------
 tests/sqlparser_bigquery.rs |  4 +--
 tests/sqlparser_common.rs   | 43 ++++++++++++++++++++++++++--
 6 files changed, 156 insertions(+), 49 deletions(-)

diff --git a/src/ast/dml.rs b/src/ast/dml.rs
index 3e2f7ee0..c2fe2ffd 100644
--- a/src/ast/dml.rs
+++ b/src/ast/dml.rs
@@ -649,6 +649,14 @@ pub enum MergeInsertKind {
     /// ```
     /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
     Row,
+    /// The insert expression uses the `*` wildcard to insert all columns.
+    ///
+    /// Example:
+    /// ```sql
+    /// INSERT *
+    /// ```
+    /// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
+    Wildcard,
 }
 
 impl Display for MergeInsertKind {
@@ -660,6 +668,9 @@ impl Display for MergeInsertKind {
             MergeInsertKind::Row => {
                 write!(f, "ROW")
             }
+            MergeInsertKind::Wildcard => {
+                write!(f, "*")
+            }
         }
     }
 }
@@ -710,25 +721,62 @@ impl Display for MergeInsertExpr {
     }
 }
 
+/// The kind of update used within a `MERGE` statement.
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub enum MergeUpdateKind {
+    /// Standard update with explicit assignments.
+    ///
+    /// Example:
+    /// ```sql
+    /// UPDATE SET quantity = source.quantity, name = source.name
+    /// ```
+    Set(Vec<Assignment>),
+    /// The `*` wildcard to update all columns from the source.
+    ///
+    /// Example:
+    /// ```sql
+    /// UPDATE SET *
+    /// ```
+    /// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
+    Wildcard,
+}
+
+impl Display for MergeUpdateKind {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            MergeUpdateKind::Set(assignments) => {
+                write!(f, "SET {}", display_comma_separated(assignments))
+            }
+            MergeUpdateKind::Wildcard => {
+                write!(f, "SET *")
+            }
+        }
+    }
+}
+
 /// The expression used to update rows within a `MERGE` statement.
 ///
 /// Examples
 /// ```sql
 /// UPDATE SET quantity = T.quantity + S.quantity
+/// UPDATE SET *
 /// ```
 ///
 /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/merge)
 /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#merge_statement)
 /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/MERGE.html)
+/// [Databricks](https://docs.databricks.com/en/sql/language-manual/delta-merge-into.html)
 #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
 pub struct MergeUpdateExpr {
     /// The `UPDATE` token that starts the sub-expression.
     pub update_token: AttachedToken,
-    /// The update assiment expressions
-    pub assignments: Vec<Assignment>,
-    /// `where_clause` for the update (Oralce specific)
+    /// The kind of update: explicit assignments or `*` shorthand.
+    pub kind: MergeUpdateKind,
+    /// `where_clause` for the update (Oracle specific)
     pub update_predicate: Option<Expr>,
     /// `delete_clause` for the update "delete where" (Oracle specific)
     pub delete_predicate: Option<Expr>,
@@ -736,7 +784,7 @@ pub struct MergeUpdateExpr {
 
 impl Display for MergeUpdateExpr {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "SET {}", display_comma_separated(&self.assignments))?;
+        write!(f, "{}", self.kind)?;
         if let Some(predicate) = self.update_predicate.as_ref() {
             write!(f, " WHERE {predicate}")?;
         }
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 886bea26..3cb5f804 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -85,9 +85,9 @@ pub use self::ddl::{
 };
 pub use self::dml::{
     Delete, Insert, Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr,
-    MergeInsertKind, MergeUpdateExpr, MultiTableInsertIntoClause, MultiTableInsertType,
-    MultiTableInsertValue, MultiTableInsertValues, MultiTableInsertWhenClause, OutputClause,
-    Update,
+    MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, MultiTableInsertIntoClause,
+    MultiTableInsertType, MultiTableInsertValue, MultiTableInsertValues,
+    MultiTableInsertWhenClause, OutputClause, Update,
 };
 pub use self::operator::{BinaryOperator, UnaryOperator};
 pub use self::query::{
diff --git a/src/ast/spans.rs b/src/ast/spans.rs
index 0dc834ba..f0e3e252 100644
--- a/src/ast/spans.rs
+++ b/src/ast/spans.rs
@@ -39,15 +39,15 @@ use super::{
     IfStatement, IlikeSelectItem, IndexColumn, Insert, Interpolate, InterpolateExpr, Join,
     JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, LimitClause,
     MatchRecognizePattern, Measure, Merge, MergeAction, MergeClause, MergeInsertExpr,
-    MergeInsertKind, MergeUpdateExpr, NamedParenthesizedList, NamedWindowDefinition, ObjectName,
-    ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, OrderBy,
-    OrderByExpr, OrderByKind, OutputClause, Parens, Partition, PartitionBoundValue,
-    PivotValueSource, ProjectionSelect, Query, RaiseStatement, RaiseStatementValue,
-    ReferentialAction, RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select,
-    SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias,
-    TableAliasColumnDef, TableConstraint, TableFactor, TableObject, TableOptionsClustered,
-    TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef, WhileStatement,
-    WildcardAdditionalOptions, With, WithFill,
+    MergeInsertKind, MergeUpdateExpr, MergeUpdateKind, NamedParenthesizedList,
+    NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction,
+    OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, OutputClause, Parens, Partition,
+    PartitionBoundValue, PivotValueSource, ProjectionSelect, Query, RaiseStatement,
+    RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement,
+    ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript,
+    SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject,
+    TableOptionsClustered, TableWithJoins, Update, UpdateTableFromKind, Use, Values, ViewColumnDef,
+    WhileStatement, WildcardAdditionalOptions, With, WithFill,
 };
 
 /// Given an iterator of spans, return the [Span::union] of all spans.
@@ -2534,7 +2534,7 @@ impl Spanned for MergeInsertExpr {
                 self.kind_token.0.span,
                 match self.kind {
                     MergeInsertKind::Values(ref values) => values.span(),
-                    MergeInsertKind::Row => Span::empty(), // ~ covered by `kind_token`
+                    MergeInsertKind::Row | MergeInsertKind::Wildcard => Span::empty(),
                 },
             ]
             .into_iter()
@@ -2546,9 +2546,13 @@ impl Spanned for MergeInsertExpr {
 
 impl Spanned for MergeUpdateExpr {
     fn span(&self) -> Span {
+        let kind_span = match &self.kind {
+            MergeUpdateKind::Set(assignments) => union_spans(assignments.iter().map(Spanned::span)),
+            MergeUpdateKind::Wildcard => Span::empty(),
+        };
         union_spans(
             core::iter::once(self.update_token.0.span)
-                .chain(self.assignments.iter().map(Spanned::span))
+                .chain(core::iter::once(kind_span))
                 .chain(self.update_predicate.iter().map(Spanned::span))
                 .chain(self.delete_predicate.iter().map(Spanned::span)),
         )
@@ -2930,7 +2934,7 @@ WHERE id = 1
         );
         if let MergeAction::Update(MergeUpdateExpr {
             update_token,
-            assignments: _,
+            kind: _,
             update_predicate: _,
             delete_predicate: _,
         }) = &clauses[1].action
diff --git a/src/parser/merge.rs b/src/parser/merge.rs
index 619be612..21e9d608 100644
--- a/src/parser/merge.rs
+++ b/src/parser/merge.rs
@@ -18,11 +18,12 @@ use alloc::{boxed::Box, format, vec, vec::Vec};
 use crate::{
     ast::{
         Merge, MergeAction, MergeClause, MergeClauseKind, MergeInsertExpr, MergeInsertKind,
-        MergeUpdateExpr, ObjectName, OutputClause, SetExpr,
+        MergeUpdateExpr, MergeUpdateKind, ObjectName, OutputClause, SetExpr,
     },
     dialect::{BigQueryDialect, GenericDialect, MySqlDialect},
     keywords::Keyword,
     parser::IsOptional,
+    tokenizer::Token,
     tokenizer::TokenWithSpan,
 };
 
@@ -120,7 +121,11 @@ impl Parser<'_> {
 
                     let update_token = self.get_current_token().clone();
                     self.expect_keyword_is(Keyword::SET)?;
-                    let assignments = self.parse_comma_separated(Parser::parse_assignment)?;
+                    let kind = if self.consume_token(&Token::Mul) {
+                        MergeUpdateKind::Wildcard
+                    } else {
+                        MergeUpdateKind::Set(self.parse_comma_separated(Parser::parse_assignment)?)
+                    };
                     let update_predicate = if self.parse_keyword(Keyword::WHERE) {
                         Some(self.parse_expr()?)
                     } else {
@@ -134,7 +139,7 @@ impl Parser<'_> {
                     };
                     MergeAction::Update(MergeUpdateExpr {
                         update_token: update_token.into(),
-                        assignments,
+                        kind,
                         update_predicate,
                         delete_predicate,
                     })
@@ -167,32 +172,43 @@ impl Parser<'_> {
                     };
 
                     let insert_token = self.get_current_token().clone();
-                    let is_mysql = dialect_of!(self is MySqlDialect);
 
-                    let columns = self.parse_merge_clause_insert_columns(is_mysql)?;
-                    let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
-                        && self.parse_keyword(Keyword::ROW)
-                    {
-                        (MergeInsertKind::Row, self.get_current_token().clone())
-                    } else {
-                        self.expect_keyword_is(Keyword::VALUES)?;
-                        let values_token = self.get_current_token().clone();
-                        let values = self.parse_values(is_mysql, false)?;
-                        (MergeInsertKind::Values(values), values_token)
-                    };
-                    let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
-                        Some(self.parse_expr()?)
+                    if self.consume_token(&Token::Mul) {
+                        let star_token = self.get_current_token().clone();
+                        MergeAction::Insert(MergeInsertExpr {
+                            insert_token: insert_token.into(),
+                            columns: vec![],
+                            kind_token: star_token.into(),
+                            kind: MergeInsertKind::Wildcard,
+                            insert_predicate: None,
+                        })
                     } else {
-                        None
-                    };
+                        let is_mysql = dialect_of!(self is MySqlDialect);
+                        let columns = self.parse_merge_clause_insert_columns(is_mysql)?;
+                        let (kind, kind_token) = if dialect_of!(self is BigQueryDialect | GenericDialect)
+                            && self.parse_keyword(Keyword::ROW)
+                        {
+                            (MergeInsertKind::Row, self.get_current_token().clone())
+                        } else {
+                            self.expect_keyword_is(Keyword::VALUES)?;
+                            let values_token = self.get_current_token().clone();
+                            let values = self.parse_values(is_mysql, false)?;
+                            (MergeInsertKind::Values(values), values_token)
+                        };
+                        let insert_predicate = if self.parse_keyword(Keyword::WHERE) {
+                            Some(self.parse_expr()?)
+                        } else {
+                            None
+                        };
 
-                    MergeAction::Insert(MergeInsertExpr {
-                        insert_token: insert_token.into(),
-                        columns,
-                        kind_token: kind_token.into(),
-                        kind,
-                        insert_predicate,
-                    })
+                        MergeAction::Insert(MergeInsertExpr {
+                            insert_token: insert_token.into(),
+                            columns,
+                            kind_token: kind_token.into(),
+                            kind,
+                            insert_predicate,
+                        })
+                    }
                 }
                 _ => {
                     return parser_err!(
diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs
index 4bdb54f7..afb61908 100644
--- a/tests/sqlparser_bigquery.rs
+++ b/tests/sqlparser_bigquery.rs
@@ -1830,7 +1830,7 @@ fn parse_merge() {
     });
     let update_action = MergeAction::Update(MergeUpdateExpr {
         update_token: AttachedToken::empty(),
-        assignments: vec![
+        kind: MergeUpdateKind::Set(vec![
             Assignment {
                 target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::new("a")])),
                 value: Expr::value(number("1")),
@@ -1839,7 +1839,7 @@ fn parse_merge() {
                 target: AssignmentTarget::ColumnName(ObjectName::from(vec![Ident::new("b")])),
                 value: Expr::value(number("2")),
             },
-        ],
+        ]),
         update_predicate: None,
         delete_predicate: None,
     });
diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs
index 221c8897..739238c8 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -10157,7 +10157,7 @@ fn parse_merge() {
                         }),
                         action: MergeAction::Update(MergeUpdateExpr {
                             update_token: AttachedToken::empty(),
-                            assignments: vec![
+                            kind: MergeUpdateKind::Set(vec![
                                 Assignment {
                                     target: AssignmentTarget::ColumnName(ObjectName::from(vec![
                                         Ident::new("dest"),
@@ -10178,7 +10178,7 @@ fn parse_merge() {
                                         Ident::new("G"),
                                     ]),
                                 },
-                            ],
+                            ]),
                             update_predicate: None,
                             delete_predicate: None,
                         }),
@@ -10239,6 +10239,45 @@ WHEN NOT MATCHED THEN \
 INSERT (PLAYGROUND.FOO.ID, PLAYGROUND.FOO.NAME) \
 VALUES (1, 'abc')";
     all_dialects().verified_stmt(sql);
+
+    // MERGE with wildcard (UPDATE SET * and INSERT *)
+    let sql = "MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN UPDATE SET * WHEN NOT MATCHED THEN INSERT *";
+    match verified_stmt(sql) {
+        Statement::Merge(merge) => {
+            assert_eq!(merge.clauses.len(), 2);
+
+            match &merge.clauses[0].action {
+                MergeAction::Update(update_expr) => {
+                    assert!(matches!(update_expr.kind, MergeUpdateKind::Wildcard));
+                }
+                _ => panic!("Expected UPDATE action"),
+            }
+
+            match &merge.clauses[1].action {
+                MergeAction::Insert(insert_expr) => {
+                    assert!(matches!(insert_expr.kind, MergeInsertKind::Wildcard));
+                    assert!(insert_expr.columns.is_empty());
+                }
+                _ => panic!("Expected INSERT action"),
+            }
+        }
+        _ => panic!("Expected MERGE statement"),
+    }
+
+    verified_stmt("MERGE INTO target USING source ON target.id = source.id WHEN MATCHED AND source.active = 1 THEN UPDATE SET *");
+
+    verified_stmt("MERGE INTO target USING source ON target.id = source.id WHEN NOT MATCHED BY TARGET THEN INSERT *");
+
+    verified_stmt("MERGE INTO target USING source ON target.id = source.id WHEN MATCHED THEN UPDATE SET * WHEN NOT MATCHED THEN INSERT (a, b) VALUES (source.a, source.b)");
+
+    let sql = concat!(
+        "MERGE INTO t1 AS target ",
+        "USING (SELECT * FROM t2) AS source ",
+        "ON target.id = source.id ",
+        "WHEN MATCHED THEN UPDATE SET * ",
+        "WHEN NOT MATCHED THEN INSERT *"
+    );
+    verified_stmt(sql);
 }
 
 #[test]


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@datafusion.apache.org
For additional commands, e-mail: commits-help@datafusion.apache.org