{"record":{"id":"a0ce7927942b5767","repo":"apache/iceberg","slug":"staging-snapshot-s-on-branch-s-contains-a-v2-p-a0ce79","errorCode":null,"errorMessage":"Staging snapshot %s on branch '%s' contains a V2 positional delete file (%s); equality delete conversion expects a V3 staging branch written by Flink, which produces only deletion vectors for deletes.","messagePattern":"Staging snapshot (.+?) on branch '(.+?)' contains a V2 positional delete file \\((.+?)\\); equality delete conversion expects a V3 staging branch written by Flink, which produces only deletion vectors for deletes\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertPlanner.java","lineNumber":584,"sourceCode":"    for (DeleteFile deleteFile : changes.addedDeleteFiles()) {\n      if (deleteFile.content() == FileContent.EQUALITY_DELETES) {\n        Set<Integer> deleteFieldIds = Sets.newHashSet(deleteFile.equalityFieldIds());\n        Preconditions.checkState(\n            deleteFieldIds.equals(eqFieldIds),\n            \"Staging snapshot %s on branch '%s' contains an equality delete file %s with \"\n                + \"equalityFieldIds=%s, which does not match the configured eqFieldIds=%s. \"\n                + \"The writer must use the same equality field IDs as the converter.\",\n            stagingSnapshot.snapshotId(),\n            stagingBranch,\n            deleteFile.location(),\n            deleteFieldIds,\n            eqFieldIds);\n        validateDeleteSpecPartitionColumns(stagingSnapshot, deleteFile);\n        eqDeleteFiles.add(deleteFile);\n      } else if (ContentFileUtil.isDV(deleteFile)) {\n        stagingDVFiles.add(deleteFile);\n      } else {\n        throw new IllegalStateException(\n            String.format(\n                \"Staging snapshot %s on branch '%s' contains a V2 positional delete file (%s); \"\n                    + \"equality delete conversion expects a V3 staging branch written by Flink, \"\n                    + \"which produces only deletion vectors for deletes.\",\n                stagingSnapshot.snapshotId(), stagingBranch, deleteFile.location()));\n      }\n    }\n\n    return new StagingInputs(newDataFiles, stagingDVFiles, eqDeleteFiles);\n  }\n\n  private void validateDeleteSpecPartitionColumns(Snapshot stagingSnapshot, DeleteFile deleteFile) {\n    PartitionSpec spec = table.specs().get(deleteFile.specId());\n    for (PartitionField field : spec.fields()) {\n      Preconditions.checkState(\n          eqFieldIds.contains(field.sourceId()),\n          \"Staging snapshot %s on branch '%s' contains an equality delete file %s under spec %s, \"\n              + \"which partitions by field '%s' (source id %s) that is not an equality field %s. \"","sourceCodeStart":566,"sourceCodeEnd":602,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertPlanner.java#L566-L602","documentation":"EqualityConvertPlanner.retrieveStagingFiles expects every delete file on the staging branch to be either an equality delete file or a deletion vector (V3). Finding a V2 positional delete file means the staging branch content does not match what the converter supports, so it fails fast naming the offending file location.","triggerScenarios":"inputs() -> retrieveStagingFiles iterates the staging snapshot's delete files and hits a ContentFile that is neither an equality delete nor a DV — a positional delete file written by a V2 writer on the staging branch.","commonSituations":"Pointing stagingBranch at a branch that received writes from an older V2-format writer (e.g., legacy Spark/Flink jobs producing position deletes); format-version migration leftovers on the branch.","solutions":["Ensure all writers to the staging branch use format version 3 with DVs (Flink writer for this feature).","Dedicate the staging branch to the converter and stop legacy V2 writers from targeting it.","Repoint stagingBranch to a fresh, empty branch and start a new conversion cycle.","Convert or remove existing positional deletes on the branch (e.g., via rewrite delete files on the main branch) before planning."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// ensure all delete files on the staging branch are eq-deletes or DVs\nfor (DeleteFile f : stagingDeleteFiles) {\n  if (!ContentFileUtil.isDV(f) && f.content() != FileContent.EQUALITY_DELETES) {\n    throw new IllegalStateException(\"Unsupported delete file on staging: \" + f.location());\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Ensure the table format version is 3 and all staging writers emit DVs.","Block legacy v2 writers from the staging branch.","Verify table.formatVersion() before enabling the converter.","Inspect staging-branch files for POSITION_DELETES content before each cycle."],"tags":["flink","planner","format-version","deletion-vectors"],"backgroundTag":"incompatible-source-type","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"}