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

   ### What changes were proposed in this pull request?
   
   This PR makes `AddMetadataColumns` skip the target table of `DELETE`, 
`UPDATE` and `MERGE` when Spark rewrites the command into a row-level 
operation, that is, when the table supports row-level operations 
(`SupportsRowLevelOperations`). `UPDATE` and `MERGE` on a table that accepts 
any schema keep the current behavior, because Spark does not align their 
assignments and leaves them to the rules injected for that table.
   
   `RewriteDeleteFromTable`, `RewriteUpdateTable` and `RewriteMergeIntoTable` 
read the metadata columns that the operation requires on their own 
(`RewriteRowLevelCommand.buildRelationWithAttrs`). They take the output of the 
target table as the data columns to write, and 
`ResolveRowLevelCommandAssignments` aligns assignments over the same output.
   
   That output has only data columns when the command is resolved in the first 
iteration of the `Resolution` batch, because `AddMetadataColumns` runs before 
`ResolveReferences`. When the command is resolved only in a later iteration, 
for example because its condition needs type coercion, `AddMetadataColumns` 
sees a resolved command that references a metadata column. It then calls 
`DataSourceV2Relation.withMetadataColumns()` on the target table, which appends 
every metadata column to its output. As a result:
   
   - `ResolveRowLevelCommandAssignments` adds identity assignments such as 
`index = index` for the metadata columns.
   - `RewriteUpdateTable` handles them as data columns. A metadata column with 
`PRESERVE_ON_UPDATE = false` keeps its old value instead of `null`.
   - Group-based `DELETE` and `UPDATE`, and delta-based `UPDATE`, pass the 
metadata columns in every row and report them in `LogicalWriteInfo.schema()`.
   - An `UPDATE` that is represented as a delete and an insert fails in 
`ExpandExec`, because the delete and reinsert projections have different widths.
   - A `MERGE` with a `NOT MATCHED` clause fails with `No assignment for 
'index'`, and a `MERGE` with only `MATCHED` clauses fails with an assertion 
error.
   
   For example, take an in-memory table `pk INT NOT NULL, id INT, dep STRING` 
partitioned by `dep`, with the metadata columns `index` (`PRESERVE_ON_UPDATE = 
false`) and `_partition`, and run:
   
   ```sql
   UPDATE t SET id = -1 WHERE id IN (1, 100) AND _partition = 'hr' AND id > 0.5
   ```
   
   **BEFORE**
   
   The target table gains `index` and `_partition`, and `index` is copied:
   
   ```
   ReplaceData RelationV2[pk#165, id#166, dep#167, index#168, _partition#169] 
cat.ns1.test_table
   +- Project [..., if (...) index#168 else index#168 AS index#174, if (...) 
_partition#169 else _partition#169 AS _partition#175]
      +- RelationV2[pk#165, id#166, dep#167, index#168, _partition#169] 
cat.ns1.test_table
   ```
   
   The connector receives the updated row with the metadata `(hr, 0)` and the 
write schema `struct<pk:int,id:int,dep:string,index:int,_partition:string>`.
   
   **AFTER**
   
   The plan is the same as for the statement without `AND id > 0.5`. The 
connector receives the metadata `(hr, null)` and the write schema 
`struct<pk:int,id:int,dep:string>`.
   
   ### Why are the changes needed?
   
   `MetadataColumn.PRESERVE_ON_UPDATE` says that the metadata value is replaced 
with `null` if it is false. This contract is broken silently for `UPDATE` and 
`MERGE` statements that are resolved in a later analyzer iteration. Row lineage 
in Iceberg and Delta Lake relies on this contract to reset the row version on 
update (SPARK-50820). Such statements also pass metadata columns to connectors 
as data columns, or fail.
   
   ### Does this PR introduce _any_ user-facing change?
   
   Yes. This is a bug fix for DSv2 `DELETE`, `UPDATE` and `MERGE` statements 
that reference a metadata column and are resolved in a later analyzer iteration:
   
   - `UPDATE` and `MERGE` replace metadata columns with `PRESERVE_ON_UPDATE = 
false` with `null` on update, as documented.
   - Connectors no longer receive metadata columns as data columns, in rows or 
in `LogicalWriteInfo.schema()`.
   - An `UPDATE` represented as a delete and an insert, and `MERGE`, no longer 
fail.
   
   These statements are now analyzed in the same way as statements that are 
resolved in the first iteration. So a reference to a metadata column that the 
row-level operation does not request in `requiredMetadataAttributes()` now 
fails analysis, as it already does without type coercion (for example, without 
`AND id > 0.5` above). Before, such a statement could succeed only because 
every metadata column of the table was written as a data column.
   
   ### How was this patch tested?
   
   Pass the CIs with the newly added test cases. Each runs a statement that 
references a metadata column and needs type coercion, and checks the result, 
the write schema and the metadata that the connector receives.
   
   - `GroupBasedUpdateTableSuite`, `DeltaBasedUpdateTableSuite`, 
`DeltaBasedUpdateAsDeleteAndInsertTableSuite`: `SPARK-60667: update with type 
coercion handles metadata columns correctly`
   - `GroupBasedDeleteFromTableSuite`: `SPARK-60667: delete with type coercion 
preserves metadata columns for carried-over records`
   - `GroupBasedMergeIntoTableSuite`, `DeltaBasedMergeIntoTableSuite`, 
`DeltaBasedMergeIntoTableUpdateAsDeleteAndInsertSuite`: `SPARK-60667: merge 
with type coercion handles metadata columns correctly`
   
   All of them fail without this fix.
   
   ### 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