{"record":{"id":"b971c8f64ad77b65","repo":"apache/iceberg","slug":"staging-snapshot-s-on-branch-s-contains-a-v2-p-b971c8","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.3/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.3/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertPlanner.java#L566-L602","documentation":"Thrown by EqualityConvertPlanner.retrieveStagingFiles when a delete file in the staging snapshot is neither an equality delete nor a deletion vector, i.e. a V2 positional delete file. The converter requires a V3-format staging branch where Flink writes only DVs for deletes, so it fails fast rather than mishandling the positional delete.","triggerScenarios":"The table is still format-version 2 (or the staging branch snapshot contains files written under V2), so deletes are stored as positional delete files instead of DVs; a scan of the staging snapshot yields a DeleteFile that is not recognized by ContentFileUtil.isDV.","commonSituations":"Forgetting to upgrade the table to format version 3 (ALTER TABLE ... SET TBLPROPERTIES ('format-version'='3')) before enabling equality-delete conversion; older writers or other engines writing V2 positional deletes to the staging branch; mixed-version clusters writing to the same table.","solutions":["Upgrade the table to format version 3 before running the conversion: ALTER TABLE ... SET TBLPROPERTIES ('format-version'='3').","Ensure all writers (all Flink jobs, Spark, Trino, etc.) use V3-enabled Iceberg versions that produce DVs, then run compaction/rewrite to convert existing positional deletes to DVs.","Point the staging branch at a snapshot produced exclusively by the V3-enabled Flink writer."],"exampleFix":"// before\nspark.sql(\"ALTER TABLE db.t SET TBLPROPERTIES ('format-version'='2')\");\nrunEqualityConvert(table);\n// after\nspark.sql(\"ALTER TABLE db.t SET TBLPROPERTIES ('format-version'='3')\");\nspark.sql(\"CALL catalog.system.rewrite_data_files(table => 'db.t')\"); // clear positional deletes\nrunEqualityConvert(table);","handlingStrategy":"validation","validationCode":"if (!\"3\".equals(table.properties().getOrDefault(TableProperties.FORMAT_VERSION, \"2\"))) {\n  throw new IllegalStateException(\"Equality delete conversion requires format-version 3; upgrade the table first\");\n}","typeGuard":null,"tryCatchPattern":"try {\n  runEqualityConvertJob(table, cfg);\n} catch (IllegalStateException e) {\n  if (e.getMessage().contains(\"positional delete file\")) {\n    LOG.error(\"Staging branch is not V3/DV-only: upgrade format version and rewrite deletes\", e);\n  } else throw e;\n}","preventionTips":["Upgrade the table to format-version 3 before enabling DV conversion","Ensure every writer engine (Spark, Flink, Trino) runs a DV-capable Iceberg version","Rewrite existing positional deletes via compaction after upgrading","Point staging at a branch written exclusively by the V3 Flink writer"],"tags":["flink","format-version","deletion-vectors","precondition"],"backgroundTag":"unsupported-operation","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"}