dongjoon-hyun opened a new pull request, #59930:
URL: https://github.com/apache/spark/pull/59930

   ### What changes were proposed in this pull request?
   
   This PR makes DSv2 UPDATE and MERGE fail analysis when an assignment targets 
a metadata column of the target table, instead of silently ignoring the 
assignment.
   
   The assignment keys of UPDATE and MERGE are resolved against the target 
table including its metadata columns, so a key such as `_partition` can resolve 
to a metadata column. `ResolveRowLevelCommandAssignments` then aligns the 
assignments with the table attributes using 
`AssignmentUtils.alignUpdateAssignments` and 
`AssignmentUtils.alignInsertAssignments`, which produce one value per table 
attribute, so an assignment to a metadata column is dropped.
   
   Both alignment methods now report assignments whose key refers to a metadata 
column with the existing 
`DATATYPE_MISMATCH.INVALID_ROW_LEVEL_OPERATION_ASSIGNMENTS` error, which the 
alignment already uses for other invalid assignments such as multiple 
assignments to the same column. A key refers to a metadata column if its 
attribute carries the metadata column marker (`MetadataAttribute.isValid`), so 
the check does not depend on expression IDs and also applies when 
`AddMetadataColumns` has already added the metadata columns to the target 
output.
   
   ### Why are the changes needed?
   
   The values of metadata columns are produced by the data source. DSv2 
row-level operations pass the metadata values they read (or nulls, see 
`MetadataColumn.PRESERVE_ON_UPDATE`) back to the writer and have no way to 
write a value that a statement assigns to a metadata column. However, DSv2 
UPDATE and MERGE statements that assign to a metadata column succeed, apply the 
other assignments and silently drop the assigned value. For instance, with the 
in-memory row-level table of `RowLevelOperationSuiteBase` (`pk INT NOT NULL, id 
INT, dep STRING`, partitioned by `dep`, with the metadata columns `_partition` 
and `index`):
   
   ```sql
   UPDATE cat.ns1.test_table SET id = -1, index = 5 WHERE pk = 1
   ```
   
   sets `id` to `-1` and ignores `index = 5`. The same happens with:
   - `WHEN MATCHED` and `WHEN NOT MATCHED BY SOURCE` `UPDATE SET` clauses and 
`WHEN NOT MATCHED` `INSERT` clauses of MERGE, including insert-only MERGE.
   - The DataFrame merge API, e.g. `whenNotMatched().insert(...)` with 
`"_partition" -> lit("x")`.
   - `MERGE WITH SCHEMA EVOLUTION` with `UPDATE SET *` or `INSERT *` when the 
source has a column named like a metadata column of the target, e.g. `index`. 
The expanded assignment resolves to the metadata column, so the schema is not 
evolved and the values of the source column are dropped.
   
   When the command is resolved in a later analyzer iteration, e.g. because of 
an implicit cast in a condition such as `WHERE id < 1.5`, `AddMetadataColumns` 
has already added the metadata columns to the target output (see SPARK-60667). 
Then the assigned value reaches the writer as an extra data column and, if the 
operation requires metadata columns, as the metadata value of the row, or the 
query fails at runtime with an internal error (`ArrayIndexOutOfBoundsException` 
for an UPDATE that splits updates into deletes and inserts, `AssertionError` 
for MERGE).
   
   All of these happen with group-based and delta-based operations, including 
operations that split updates and operations that require no metadata columns. 
For comparison, `INSERT INTO cat.ns1.test_table (pk, id, dep, index) VALUES 
(...)` already fails with `INSERT_COLUMN_ARITY_MISMATCH.TOO_MANY_DATA_COLUMNS`.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. DSv2 UPDATE and MERGE statements that assign to a metadata column of 
the target table now fail instead of ignoring the assignment. For the example 
above:
   
   ```
   [DATATYPE_MISMATCH.INVALID_ROW_LEVEL_OPERATION_ASSIGNMENTS] Cannot resolve 
"id = -1", "index = 5" due to data type mismatch:
   - Cannot assign to metadata column 'index' SQLSTATE: 42K09
   ```
   
   `MERGE WITH SCHEMA EVOLUTION` with `UPDATE SET *` or `INSERT *` and a source 
column named like a metadata column of the target fails the same way, so the 
source column has to be renamed or dropped. Without the evolution clause, 
`UPDATE SET *` and `INSERT *` still ignore such a source column as before.
   
   By code reading, the assignments have been dropped since the alignment of 
UPDATE and MERGE assignments was added in Apache Spark 
[3.5.0](https://github.com/apache/spark/releases/tag/v3.5.0) (2023-09-09), and 
the `UPDATE SET *` and `INSERT *` case since MERGE schema evolution was added 
in Apache Spark [4.1.0](https://github.com/apache/spark/releases/tag/v4.1.0) 
(2025-12-11).
   
   ### How was this patch tested?
   
   Added tests:
   - `UpdateTableSuiteBase`: an unqualified and a qualified assignment to a 
metadata column, and an UPDATE resolved in a later analyzer iteration.
   - `MergeIntoTableSuiteBase`: `WHEN MATCHED` and `WHEN NOT MATCHED BY SOURCE` 
updates, a `WHEN NOT MATCHED` insert in a MERGE with an update clause and in an 
insert-only MERGE, and a MERGE resolved in a later analyzer iteration.
   - `MergeIntoDataFrameSuite`: the same through the DataFrame API `update` and 
`insert`.
   - `MergeIntoSchemaEvolutionExtraSourceColumnTests`: `UPDATE SET *` with 
`INSERT *`, and insert-only `INSERT *`, with a source column named `index` or 
`_partition`. Without the evolution clause, the source column is ignored as 
before.
   
   They run in the group-based and delta-based suites, including the split 
update, NoMetadata and NoCodegen variants and the SQL and DataFrame schema 
evolution suites. Without the fix, all 21 tests that expect the error fail 
because the statements succeed; the 8 schema evolution runs without the 
evolution clause pass with and without the fix.
   
   ```
   build/sbt "sql/testOnly *UpdateTableSuite *UpdateAsDeleteAndInsertTableSuite 
*MergeIntoTableSuite *MergeIntoTableNoCodegenSuite 
*MergeIntoTableUpdateAsDeleteAndInsertSuite *MergeIntoDataFrameSuite 
*MergeIntoSchemaEvolution*Suite -- -z SPARK-60721"
   ```
   
   Also ran the DSv2 DELETE, UPDATE and MERGE suites (including the NoMetadata, 
split update and NoCodegen variants), the MERGE schema evolution suites, 
`MergeIntoDataFrameSuite`, the row-level runtime filter suites, 
`MetadataColumnSuite`, `PlanResolutionSuite`, `AlignUpdateAssignmentsSuite` and 
`AlignMergeAssignmentsSuite` locally.
   
   ### Was this patch authored or co-authored using generative AI tooling?
   
   Generated-by: Claude Opus 5.5


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


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

Reply via email to