Spark 3.5: Add row lineage rule for DELETE to keep plans resolved - #18159
rishi-rana wants to merge 1 commit into
Conversation
Copy-on-write DELETE on a v3 table left the plan unresolved after GroupBasedRowLevelOperationScanPlanning. SparkCopyOnWriteScan drops the metadata column marker from _row_id and _last_updated_sequence_number so the 3.5 row-level rules keep them in the output. ReplaceData.outputResolved then counts both as data columns, while the DELETE target relation supplies only the table columns, so the sizes never match. UPDATE and MERGE already add the lineage attributes to the target relation output through RewriteUpdateTableForRowLineage and RewriteMergeIntoTableForRowLineage. DELETE had no such rule. Add RewriteDeleteFromTableForRowLineage, which needs no assignments because carried over rows keep the lineage values they already have. Also return false for non-Iceberg tables in shouldUpdatePlan, which DELETE now reaches, and enable spark.sql.planChangeValidation in the test harness. Plan validation reads the spark.testing system property rather than the Spark conf the harness sets, so per-rule validation never ran. Generated-by: Claude Code Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@rishi-rana Is this fix Spark 3.5 specific? If not, open a PR against Spark 4.2 first. |
|
@manuzhang Yes, this one is Spark 3.5 specific — there is no 4.2 counterpart to port it to. The row lineage rules for row-level operations only exist under Same for the trigger. I verified this: I enabled
192 passed / 0 failures on 4.2. I reverted that local 4.2 change since it is not part of this PR. Happy to open a separate PR enabling |
Closes #18131
Problem
A copy-on-write
DELETEon a v3 table fails Spark's per-rule plan validation:SparkCopyOnWriteScan.rowLineageAsDataColsremoves the__metadata_colmarker from_row_idand_last_updated_sequence_numberso the 3.5 row-level rules do not drop them.ReplaceData.dataInputfilters out metadata attributes, so both lineage columns stay in it,giving four columns against a target relation that supplies two, and
outputResolvedfails.Only the intermediate plan is affected, which is why this surfaces just under plan validation.
Spark 4.x does not override
readSchema()at all and relies on the native metadata columnsemantics from SPARK-50820, matching the version split in the issue.
Fix
UPDATEandMERGEalready append the lineage attributes to the target relation output inRewriteUpdateTableForRowLineageandRewriteMergeIntoTableForRowLineage.DELETEhad noequivalent, so this adds
RewriteDeleteFromTableForRowLineagealongside them. It needs noassignments because copy-on-write
DELETEcarries surviving rows over unchanged.shouldUpdatePlannow also returns false for non-Iceberg tables. Its inner match had nodefault branch, and
DELETEnewly reaches it, so aDELETEagainst another v2 catalog in asession with these extensions loaded would hit a
MatchError.I also considered dropping the
rowLineageAsDataColsworkaround instead. That is not viable:with it removed the lineage columns are no longer written, and
TestCopyOnWriteWithLineagefails with
Structs do not matchandArrayIndexOutOfBoundsExceptionrather than a planvalidation error. The workaround is still required.
Testing
No new test was needed. The existing
testDeletereproduces this once validation is on, sothis enables
spark.sql.planChangeValidationinExtensionsTestBase. Spark gates per-rulevalidation on the
spark.testingsystem property, but the harness setsspark.testingas a Spark conf, so validation was never actually running.
With validation enabled and without the fix, 30 tests fail across
TestCopyOnWriteDeleteand
TestCopyOnWriteWithLineage. With the fix the fullspark-extensionssuite passes:2501 passed, 180 skipped, 0 failures (
TestRewritePositionDeleteFilesProcedurehit anunrelated flaky REST catalog 404 during initialization in one run and passes on its own).
Notes for reviewers
RewriteUpdateTableForRowLineageandRewriteMergeIntoTableForRowLineagedo not distinguish either, and I verified merge-on-readis unaffected (
TestMergeOnReadWithLineage40/40). Say the word if you would rather it werenarrowed to the group-based path.
MatchError, and guards onresolvedthe way Spark's ownRewriteDeleteFromTableandRewriteMergeIntoTableForRowLineagedo.bug recurring, but it is the part of this change most likely to affect unrelated tests. Happy
to scope it to the row lineage tests instead.
AI Disclosure
validation failure, and implement a fix with regression coverage. The agent reproduced the
failure, traced it to the missing DELETE counterpart of the existing row lineage rules,
empirically ruled out the alternative fix, and ran the full spark-extensions suite.