{"record":{"id":"7275e5be3d0aafab","repo":"apache/iceberg","slug":"deleted-rows-scan-task-is-not-supported-yet-7275e5","errorCode":null,"errorMessage":"Deleted rows scan task is not supported yet","messagePattern":"Deleted rows scan task is not supported yet","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/ChangelogRowReader.java","lineNumber":104,"sourceCode":"    return cdcRows.iterator();\n  }\n\n  private static InternalRow changelogMetadata(ChangelogScanTask task) {\n    InternalRow metadataRow = new GenericInternalRow(3);\n\n    metadataRow.update(0, UTF8String.fromString(task.operation().name()));\n    metadataRow.update(1, task.changeOrdinal());\n    metadataRow.update(2, task.commitSnapshotId());\n\n    return metadataRow;\n  }\n\n  private CloseableIterable<InternalRow> openChangelogScanTask(ChangelogScanTask task) {\n    if (task instanceof AddedRowsScanTask) {\n      return openAddedRowsScanTask((AddedRowsScanTask) task);\n\n    } else if (task instanceof DeletedRowsScanTask) {\n      throw new UnsupportedOperationException(\"Deleted rows scan task is not supported yet\");\n\n    } else if (task instanceof DeletedDataFileScanTask) {\n      return openDeletedDataFileScanTask((DeletedDataFileScanTask) task);\n\n    } else {\n      throw new IllegalArgumentException(\n          \"Unsupported changelog scan task type: \" + task.getClass().getName());\n    }\n  }\n\n  CloseableIterable<InternalRow> openAddedRowsScanTask(AddedRowsScanTask task) {\n    String filePath = task.file().location();\n    SparkDeleteFilter deletes = new SparkDeleteFilter(filePath, task.deletes(), counter(), true);\n    return deletes.filter(rows(task, deletes.requiredSchema()));\n  }\n\n  private CloseableIterable<InternalRow> openDeletedDataFileScanTask(DeletedDataFileScanTask task) {\n    String filePath = task.file().location();","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/ChangelogRowReader.java#L86-L122","documentation":"ChangelogRowReader.openChangelogScanTask dispatches incremental changelog scan tasks by type. AddedRowsScanTask and DeletedDataFileScanTask are supported, but DeletedRowsScanTask (position-delete-based row deletions carrying prior-row values) has no reader implementation yet, so it throws UnsupportedOperationException. The changelog mode ('incremental' with deletes) cannot process such tasks.","triggerScenarios":"Streaming/incremental read with read.changelog or scan.mode=incremental-append... when the computed task set contains a DeletedRowsScanTask — i.e. rows removed via v2 position deletes (merge-on-read delete files) between the start and end snapshot.","commonSituations":"Enabling changelog reads on tables where writers emit position deletes (Spark MERGE/DELETE with copy-on-write off, Flink upsert writes); forgetting that only equality/position delete handling for DeletedDataFile tasks is implemented; running an older Iceberg version where support is narrower.","solutions":["Avoid position deletes on the table: enable copy-on-write (write.delete.mode=copy-on-write / write.merge.mode=copy-on-write) so deletions produce deleted data files instead.","Use a scan mode that does not need to emit prior row values for position deletes, or periodic full re-reads instead of incremental changelog.","Upgrade Iceberg — newer releases may have implemented DeletedRowsScanTask handling.","Run frequent rewrites/compaction to remove delete files before the changelog window."],"exampleFix":"// before (table properties)\n\"write.delete.mode\": \"merge-on-read\"\n// after\n\"write.delete.mode\": \"copy-on-write\"","handlingStrategy":"validation","validationCode":"// detect position-delete usage before enabling changelog reads\nboolean usesPositionDeletes = spark.read.format(\"iceberg\")\n    .load(table + \".files\").filter($\"content\" === 2).count() > 0;\nif (usesPositionDeletes) { /* reconfigure writers or avoid changelog mode */ }","typeGuard":null,"tryCatchPattern":"try {\n    spark.readStream.format(\"iceberg\").option(\"stream-from-timestamp\", ts).load(table);\n} catch (UnsupportedOperationException e) {\n    if (e.getMessage().contains(\"Deleted rows scan task\")) { /* switch to copy-on-write or full re-read */ }\n}","preventionTips":["Set write.delete.mode/write.merge.mode to copy-on-write on tables consumed via changelog streaming","Compact regularly to clear residual delete files before the incremental window","Check Iceberg release notes for DeletedRowsScanTask support before upgrading readers","Prefer equality-delete-aware Flink/Spark changelog paths where supported"],"tags":["spark","streaming","changelog","position-deletes"],"backgroundTag":"method-not-implemented","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}