{"record":{"id":"d7c0f09bd2cdab74","repo":"apache/iceberg","slug":"v2-positional-delete-file-s-attached-to-main-data-d7c0f0","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.1/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.1/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L212-L248","documentation":"loadExistingDVs collects the deletion vectors attached to a main data file and hard-fails if it finds a V2 positional delete file instead. The equality-delete converter assumes the target table is format V3 with deletion vectors only; a positional delete attached to a main data file breaks that assumption and is thrown as IllegalStateException naming both files.","triggerScenarios":"A data file scan task on the target/main branch has task.deletes() containing a DeleteFile with content == FileContent.POSITION_DELETES, i.e. the table still has V2-style .pos deletes when the converter runs.","commonSituations":"Table was upgraded to format version 3 but legacy positional deletes from V2 writes are still attached; another writer (older engine version) keeps producing positional deletes on the same branch while maintenance runs.","solutions":["Run a rewrite/delete-file compaction that removes legacy positional deletes (converts them to DVs) before enabling the equality converter.","Verify table format version is 3 and that no writer with format-version 2 is appending to the target branch during maintenance.","Point the converter at a branch that only receives V3/DV writes, or first fully rewrite affected data files."],"exampleFix":"// before\n// table.formatVersion()==2, positional deletes attached\nTableScan scan = table.newScan();\n\n// after\nTable upgraded = ...; // migrate to format version 3 first\nPreconditions.checkArgument(upgraded.ops().current().formatVersion() >= 3,\n    \"Equality converter requires format version >= 3\");","handlingStrategy":"validation","validationCode":"Preconditions.checkArgument(\n    table.ops().current().formatVersion() >= 3,\n    \"Equality converter requires format version 3+ with deletion vectors\");\n// optionally scan once and assert no POSITION_DELETES deletes are attached","typeGuard":null,"tryCatchPattern":"try {\n  loadExistingDVs(task, dataFilePath);\n} catch (IllegalStateException e) {\n  LOG.error(\"Legacy V2 positional deletes present: {}\", e.getMessage());\n  // run compaction that converts positional deletes to DVs first\n}","preventionTips":["Migrate the table to format version 3 and rewrite away legacy positional deletes before enabling the converter","Ensure no format-version-2 writers commit to the target branch during maintenance","Monitor table deletes() composition in tests before scheduling conversion"],"tags":["iceberg","deletion-vectors","format-version","table-format"],"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"}