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

szehon-ho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new f2cbc7803c18 [SPARK-57643][DOCS] Document MERGE INTO schema evolution
f2cbc7803c18 is described below

commit f2cbc7803c18648130db862cd85581a57c1dad98
Author: Szehon Ho <[email protected]>
AuthorDate: Tue Jun 23 20:28:39 2026 -0700

    [SPARK-57643][DOCS] Document MERGE INTO schema evolution
    
    ### What changes were proposed in this pull request?
    
    This PR documents `MERGE WITH SCHEMA EVOLUTION` in the MERGE INTO SQL 
reference (`docs/sql-ref-syntax-dml-merge-into.md`), mirroring the existing 
INSERT INTO schema evolution documentation. Specifically it:
    
    - Adds `[ WITH SCHEMA EVOLUTION ]` to the **Syntax** and a `WITH SCHEMA 
EVOLUTION` entry to the **Parameters**.
    - Adds notes describing what triggers schema evolution: only columns 
referenced by a direct assignment are added/widened; `UPDATE SET *` / `INSERT 
*` count as direct assignments of every target column; and directly assigning a 
struct column counts as a direct assignment of every nested field.
    - Adds a `Schema Evolution` examples subsection (add a top-level column, 
add a nested struct field, widen a column's type).
    - Adds a `Schema Evolution Triggers` examples subsection (an extra column 
used only in an expression is not added; a wider value produced by an 
expression is not widened and overflows; directly assigning a struct adds its 
new nested fields).
    - Adds a `Schema Evolution When the Source Omits Columns` examples 
subsection.
    
    The examples and behavior were cross-checked against the 
`MergeIntoSchemaEvolution*` test suites under `sql/core`.
    
    ### Why are the changes needed?
    
    `MERGE WITH SCHEMA EVOLUTION` is supported but undocumented in the SQL 
reference, while the analogous `INSERT WITH SCHEMA EVOLUTION` is documented. 
This brings the MERGE INTO reference to parity.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No (documentation-only change).
    
    ### How was this patch tested?
    
    Documentation-only change. The example behavior was verified against the 
existing `MergeIntoSchemaEvolution*` test suites.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor (Claude Opus 4.8)
    
    Closes #56710 from szehon-ho/merge-into-schema-evolution-docs.
    
    Authored-by: Szehon Ho <[email protected]>
    Signed-off-by: Szehon Ho <[email protected]>
---
 docs/sql-ref-syntax-dml-merge-into.md | 270 +++++++++++++++++++++++++++++++++-
 1 file changed, 269 insertions(+), 1 deletion(-)

diff --git a/docs/sql-ref-syntax-dml-merge-into.md 
b/docs/sql-ref-syntax-dml-merge-into.md
index c6b74dcad4b6..1ae72ca63b7f 100644
--- a/docs/sql-ref-syntax-dml-merge-into.md
+++ b/docs/sql-ref-syntax-dml-merge-into.md
@@ -32,7 +32,7 @@ apply. All of these row-level changes are performed as a 
single atomic operation
 ### Syntax
 
 ```sql
-MERGE INTO target_table [ [ AS ] target_alias ]
+MERGE [ WITH SCHEMA EVOLUTION ] INTO target_table [ [ AS ] target_alias ]
     USING { source_table | ( source_query ) } [ [ AS ] source_alias ]
     ON merge_condition
     [ WHEN MATCHED [ AND matched_condition ] THEN matched_action ] [ ... ]
@@ -51,6 +51,12 @@ not_matched_by_source_action
 
 ### Parameters
 
+* **WITH SCHEMA EVOLUTION**
+
+    Enables automatic schema evolution for this `MERGE` operation. When 
enabled, the schema
+    of the target table is automatically evolved to add new columns and widen 
data types based
+    on the source table or query, subject to the capabilities of the 
underlying connector.
+
 * **target_table**
 
     Specifies the table to be merged into, which may be optionally qualified 
with a database name.
@@ -111,6 +117,12 @@ not_matched_by_source_action
   once. This check is skipped when there are no `WHEN MATCHED` clauses, or 
when the only `WHEN MATCHED`
   action is an unconditional `DELETE`, because in those cases the outcome does 
not depend on the number
   of matching source rows.
+- When `WITH SCHEMA EVOLUTION` is specified, only the columns that are 
referenced in the merge clauses
+  by a direct assignment are added or have their data type widened.
+- `UPDATE SET *` and `INSERT *` count as direct assignments of every target 
column, so schema
+  evolution is triggered for all of them.
+- An `UPDATE` or `INSERT` that directly assigns a struct column counts as a 
direct assignment of every
+  nested field in that struct, so schema evolution is triggered for all of 
those fields.
 
 ### Examples
 
@@ -217,6 +229,262 @@ SELECT * FROM target;
 +--+------+----------+
 ```
 
+#### Schema Evolution
+
+```sql
+SELECT * FROM students;
++-------------+--------------------------+----------+
+|         name|                   address|student_id|
++-------------+--------------------------+----------+
+|    Amy Smith|    123 Park Ave, San Jose|    111111|
+|    Bob Brown|  456 Taylor St, Cupertino|    222222|
+|  Helen Davis| 469 Mission St, San Diego|    999999|
++-------------+--------------------------+----------+
+
+-- This example uses a source table that has an extra "email" column not 
present in the target.
+SELECT * FROM new_students;
++-------------+--------------------------+----------+-----------------+
+|         name|                   address|student_id|            email|
++-------------+--------------------------+----------+-----------------+
+|    Amy Smith|    123 Park Ave, San Jose|    111111|  [email protected]|
+|Cathy Johnson|   789 Race Ave, Palo Alto|    333333|[email protected]|
++-------------+--------------------------+----------+-----------------+
+
+-- Evolve the students table schema to add the new email column from the 
source.
+MERGE WITH SCHEMA EVOLUTION INTO students t
+    USING new_students s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET *
+    WHEN NOT MATCHED THEN INSERT *;
+
+SELECT * FROM students;
++-------------+--------------------------+----------+-----------------+
+|         name|                   address|student_id|            email|
++-------------+--------------------------+----------+-----------------+
+|    Amy Smith|    123 Park Ave, San Jose|    111111|  [email protected]|
+|    Bob Brown|  456 Taylor St, Cupertino|    222222|             NULL|
+|Cathy Johnson|   789 Race Ave, Palo Alto|    333333|[email protected]|
+|  Helen Davis| 469 Mission St, San Diego|    999999|             NULL|
++-------------+--------------------------+----------+-----------------+
+```
+
+Schema evolution also applies to nested fields. In the following example the 
source's `address`
+struct has an extra `zip` field that is added to the target's `address` struct.
+
+```sql
+SELECT * FROM student_addresses;
++----------+-----------+
+|student_id|    address|
++----------+-----------+
+|    111111| {San Jose}|
+|    222222|{Cupertino}|
++----------+-----------+
+
+SELECT * FROM address_updates;
++----------+------------------+
+|student_id|           address|
++----------+------------------+
+|    111111| {San Jose, 95110}|
+|    333333|{Palo Alto, 94301}|
++----------+------------------+
+
+-- Evolve the address struct to add the new nested zip field.
+MERGE WITH SCHEMA EVOLUTION INTO student_addresses t
+    USING address_updates s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET *
+    WHEN NOT MATCHED THEN INSERT *;
+
+SELECT * FROM student_addresses;
++----------+------------------+
+|student_id|           address|
++----------+------------------+
+|    111111| {San Jose, 95110}|
+|    222222| {Cupertino, NULL}|
+|    333333|{Palo Alto, 94301}|
++----------+------------------+
+```
+
+Schema evolution can also widen the data type of an existing column. In the 
following example the
+source's `credits` column is a `BIGINT` that holds a value too large for the 
target's `INT` column,
+so the target's `credits` column is widened from `INT` to `BIGINT`.
+
+```sql
+SELECT * FROM student_credits;
++----------+-------+
+|student_id|credits|
++----------+-------+
+|    111111|     30|
+|    222222|     45|
++----------+-------+
+
+-- The source's credits column is a BIGINT and contains a value that does not 
fit in an INT.
+SELECT * FROM credit_updates;
++----------+-----------+
+|student_id|    credits|
++----------+-----------+
+|    111111|10000000000|
+|    333333|         60|
++----------+-----------+
+
+-- Widen the target's credits column from INT to BIGINT to accommodate the 
source values.
+MERGE WITH SCHEMA EVOLUTION INTO student_credits t
+    USING credit_updates s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET *
+    WHEN NOT MATCHED THEN INSERT *;
+
+SELECT * FROM student_credits;
++----------+-----------+
+|student_id|    credits|
++----------+-----------+
+|    111111|10000000000|
+|    222222|         45|
+|    333333|         60|
++----------+-----------+
+```
+
+#### Schema Evolution Triggers
+
+Schema evolution adds or widens a column only when a source column is 
referenced by a direct
+assignment to a target column. Referencing a source column only inside an 
expression does not evolve
+the target. Conversely, directly assigning a whole struct counts as 
referencing it, so all of its
+new nested fields are added.
+
+In the following example the source has an extra `bonus` column that is only 
used in an expression,
+so the target table is not evolved and no `bonus` column is added.
+
+```sql
+SELECT * FROM student_credits;
++----------+-------+
+|student_id|credits|
++----------+-------+
+|    111111|     30|
+|    222222|     45|
++----------+-------+
+
+-- The source has an extra bonus column not present in the target.
+SELECT * FROM credit_transfers;
++----------+-------+-----+
+|student_id|credits|bonus|
++----------+-------+-----+
+|    111111|     30|   12|
+|    333333|     60|    5|
++----------+-------+-----+
+
+-- bonus is only referenced inside an expression, so the target schema is left 
unchanged.
+MERGE WITH SCHEMA EVOLUTION INTO student_credits t
+    USING credit_transfers s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET credits = s.credits + s.bonus
+    WHEN NOT MATCHED THEN INSERT (student_id, credits) VALUES (s.student_id, 
s.credits + s.bonus);
+
+-- The target schema is unchanged: no bonus column is added.
+SELECT * FROM student_credits;
++----------+-------+
+|student_id|credits|
++----------+-------+
+|    111111|     42|
+|    222222|     45|
+|    333333|     65|
++----------+-------+
+```
+
+Type widening follows the same rule. If a wider value is produced by an 
expression instead of a
+direct assignment, the target column keeps its type, so a value that does not 
fit can no longer be
+stored and the statement fails. The example below reuses the `credit_updates` 
table, whose `credits`
+column is a `BIGINT` value too large for an `INT`.
+
+```sql
+-- credits is produced by an expression, so the target column is not widened 
from INT to BIGINT.
+MERGE WITH SCHEMA EVOLUTION INTO student_credits t
+    USING credit_updates s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET credits = s.credits + 100;
+Error: CAST_OVERFLOW_IN_TABLE_INSERT
+```
+
+Directly assigning a struct column from the source, on the other hand, counts 
as referencing it, so
+its new nested fields are added even though they are not named individually. 
In the following example
+assigning the whole struct with `UPDATE SET address = s.address` adds the 
source's new `zip` field to
+the target's `address` struct.
+
+```sql
+SELECT * FROM student_addresses;
++----------+-----------+
+|student_id|    address|
++----------+-----------+
+|    111111| {San Jose}|
+|    222222|{Cupertino}|
++----------+-----------+
+
+-- The source's address struct has an extra zip field not present in the 
target.
+SELECT * FROM address_changes;
++----------+------------------+
+|student_id|           address|
++----------+------------------+
+|    222222|{Cupertino, 95014}|
++----------+------------------+
+
+-- Assigning the whole struct directly adds the new zip nested field.
+MERGE WITH SCHEMA EVOLUTION INTO student_addresses t
+    USING address_changes s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET address = s.address;
+
+SELECT * FROM student_addresses;
++----------+------------------+
+|student_id|           address|
++----------+------------------+
+|    111111|  {San Jose, NULL}|
+|    222222|{Cupertino, 95014}|
++----------+------------------+
+```
+
+#### Schema Evolution When the Source Omits Columns
+
+With schema evolution enabled, `UPDATE SET *` and `INSERT *` also tolerate a 
source that is missing
+some of the target's columns. A missing column keeps its existing value on an 
update and is set to
+its default value (or `NULL`) on an insert. Without schema evolution the same 
statement fails,
+because `*` requires the source to provide every target column.
+
+```sql
+SELECT * FROM students;
++-------------+--------------------------+----------+
+|         name|                   address|student_id|
++-------------+--------------------------+----------+
+|    Amy Smith|    123 Park Ave, San Jose|    111111|
+|    Bob Brown|  456 Taylor St, Cupertino|    222222|
+|  Helen Davis| 469 Mission St, San Diego|    999999|
++-------------+--------------------------+----------+
+
+-- The source is missing the address column that exists in the target.
+SELECT * FROM name_updates;
++-------------+----------+
+|         name|student_id|
++-------------+----------+
+|    Amy Smith|    111111|
+|Cathy Johnson|    333333|
++-------------+----------+
+
+MERGE WITH SCHEMA EVOLUTION INTO students t
+    USING name_updates s
+    ON t.student_id = s.student_id
+    WHEN MATCHED THEN UPDATE SET *
+    WHEN NOT MATCHED THEN INSERT *;
+
+-- Student 111111 keeps its address on update; student 333333 is inserted with 
a NULL address.
+SELECT * FROM students;
++-------------+--------------------------+----------+
+|         name|                   address|student_id|
++-------------+--------------------------+----------+
+|    Amy Smith|    123 Park Ave, San Jose|    111111|
+|    Bob Brown|  456 Taylor St, Cupertino|    222222|
+|Cathy Johnson|                      NULL|    333333|
+|  Helen Davis| 469 Mission St, San Diego|    999999|
++-------------+--------------------------+----------+
+```
+
 ### Related Statements
 
 * [INSERT TABLE statement](sql-ref-syntax-dml-insert-table.html)


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to