{"record":{"id":"67313ad83c2fec47","repo":"apache/iceberg","slug":"v2-positional-delete-file-s-attached-to-main-data-67313a","errorCode":null,"errorMessage":"V2 positional delete file %s attached to main data file %s; the converter expects a V3 target with deletion vectors only.","messagePattern":"V2 positional delete file (.+?) attached to main data file (.+?); the converter expects a V3 target with deletion vectors only\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java","lineNumber":230,"sourceCode":"  @Override\n  public void close() throws Exception {\n    super.close();\n    tableLoader.close();\n  }\n\n  private Schema appendRowPosition(Schema schema) {\n    List<Types.NestedField> columns = Lists.newArrayList(schema.columns());\n    columns.add(MetadataColumns.ROW_POSITION);\n    return new Schema(columns);\n  }\n\n  private PositionDeleteIndex loadExistingDVs(FileScanTask task, String dataFilePath) {\n    List<DeleteFile> dvs = Lists.newArrayList();\n    for (DeleteFile deleteFile : task.deletes()) {\n      if (ContentFileUtil.isDV(deleteFile)) {\n        dvs.add(deleteFile);\n      } else if (deleteFile.content() == FileContent.POSITION_DELETES) {\n        throw new IllegalStateException(\n            String.format(\n                \"V2 positional delete file %s attached to main data file %s; \"\n                    + \"the converter expects a V3 target with deletion vectors only.\",\n                deleteFile.location(), dataFilePath));\n      } else if (deleteFile.content() == FileContent.EQUALITY_DELETES && !stagingOnTargetBranch) {\n        // When stagingBranch == targetBranch the target carries unconverted equality deletes; they\n        // are indexed as rows here and converted via the planner's RESOLVE_DELETE commands. On a\n        // separate target branch an attached equality delete means an unconverted delete leaked\n        // onto the target, which the converter cannot reason about.\n        throw new IllegalStateException(\n            String.format(\n                \"Equality delete file %s attached to main data file %s; the converter expects \"\n                    + \"equality deletes only on the staging branch, converted to DVs on the target.\",\n                deleteFile.location(), dataFilePath));\n      }\n    }\n\n    if (dvs.isEmpty()) {","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v2.2/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L212-L248","documentation":"loadExistingDVs collects the deletion vectors attached to each main data file in the scan task. If a V2 positional delete file is attached to a data file on the target, the converter cannot represent those deletes as DVs and throws IllegalStateException naming both files. The target table is expected to be format v3 with positional deletes already expressed as deletion vectors.","triggerScenarios":"existingDeletes -> loadExistingDVs encounters task.deletes() containing a file with content POSITION_DELETES while scanning main data files.","commonSituations":"The table was upgraded toward v3 but older writers (Spark/Flink v2 writers) still commit positional delete files; a merge-on-read v2 table being converted before legacy position deletes were rewritten to DVs; sharing the target branch with a legacy writer.","solutions":["Stop all v2 writers from committing to the target branch; upgrade them to v3 DV writers.","Rewrite/convert existing positional deletes to DVs (e.g., via rewrite delete files / compaction with DV support) before running the converter.","Verify the table's format version is 3 and all engines writing to it support DVs.","Run the converter only after a clean cycle shows no POSITION_DELETES content on the target branch."],"exampleFix":"// before\n// legacy writer on same table\nspark.sql(\"ALTER TABLE t SET TBLPROPERTIES ('format-version'='3')\");\nconvertEqualityDeletes(table); // fails: v2 writers still add position deletes\n// after\n// 1) stop legacy writers, 2) convert existing position deletes to DVs,\n// 3) then convert\nspark.sql(\"CALL catalog.system.rewrite_delete_files(t => 't', options => map('format','v3'))\");\nconvertEqualityDeletes(table);","handlingStrategy":"validation","validationCode":"// confirm target branch has no V2 positional deletes before converting\nfor (FileScanTask t : table.newScan().planFiles()) {\n  for (DeleteFile d : t.deletes()) {\n    if (d.content() == FileContent.POSITION_DELETES) {\n      throw new IllegalStateException(\"Position deletes remain on target: \" + d.location());\n    }\n  }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Upgrade all writers to format v3 DV output before enabling conversion.","Rewrite existing position deletes to DVs first (rewrite delete files action).","Verify table.formatVersion() == 3 and writer compatibility.","Pre-scan target branch deletes as a gate before each conversion cycle."],"tags":["deletion-vectors","format-version","flink","precondition"],"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"}