{"record":{"id":"ff06a6baf5fe3d65","repo":"apache/iceberg","slug":"equality-delete-file-s-attached-to-main-data-file","errorCode":null,"errorMessage":"Equality delete file %s attached to main data file %s; the converter expects equality deletes only on the staging branch, converted to DVs on the target.","messagePattern":"Equality delete file (.+?) attached to main data file (.+?); the converter expects equality deletes only on the staging branch, converted to DVs on the target\\.","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java","lineNumber":240,"sourceCode":"  }\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()) {\n      return null;\n    }\n\n    return deleteLoader.loadPositionDeletes(dvs, dataFilePath);\n  }\n}\n","sourceCodeStart":222,"sourceCodeEnd":255,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/flink/v1.20/flink/src/main/java/org/apache/iceberg/flink/maintenance/operator/EqualityConvertReader.java#L222-L255","documentation":"The loader found an equality delete file attached to a main-branch data file while stagingBranch != targetBranch. Equality deletes are only expected on the staging branch (where the converter converts them to DVs); on a separate target branch their presence means an unconverted delete leaked onto the target and the converter cannot reason about it, so it fails fast.","triggerScenarios":"EqualityConvertReader.loadExistingDVs() encounters DeleteFile content EQUALITY_DELETES on the target branch while stagingOnTargetBranch is false (staging and target are different branches).","commonSituations":"A writer configured with equality deletes (e.g. upsert-mode Flink sink or Spark MERGE) committed directly to the target branch instead of the staging branch; misconfigured branch routing of the writing job.","solutions":["Redirect all equality-delete-writing jobs to the staging branch (or the same branch used for conversion).","Re-branch the target from a snapshot without unconverted equality deletes, then run the conversion.","Set stagingBranch == targetBranch if your pipeline intentionally keeps unconverted equality deletes on the target (they will be indexed and resolved instead).","Audit branch configuration of sink and maintenance jobs so deletes never land on the target branch directly."],"exampleFix":"// before\nFlinkSink.forRowData(...).branch(\"main\")... // equality deletes land on target\n// after\nFlinkSink.forRowData(...).branch(\"staging\")... // conversion converts them to DVs on target","handlingStrategy":"validation","validationCode":"// pre-flight: equality deletes must be absent on a distinct target branch\nif (!stagingOnTargetBranch) {\n  table.snapshot(targetBranch).deleteFiles(table.io()).forEach(f ->\n    checkState(f.content() != FileContent.EQUALITY_DELETES,\n        \"eq delete leaked onto target: \" + f.location()));\n}","typeGuard":"boolean targetFreeOfEqualityDeletes(Table table, String branch) {\n  return table.snapshot(branch).deleteFiles(table.io()).stream()\n      .noneMatch(f -> f.content() == FileContent.EQUALITY_DELETES);\n}","tryCatchPattern":"try {\n  convert(table);\n} catch (IllegalStateException e) {\n  if (e.getMessage().startsWith(\"Equality delete file\")) {\n    rerouteWriterToStagingBranch(); rerun();\n  } else throw e;\n}","preventionTips":["Route all equality-delete-producing sinks (upsert mode, MERGE) to the staging branch.","Document branch ownership: target receives only DVs and appends.","Audit the writing job's branch config before enabling conversion.","Run a pre-flight manifest audit for equality deletes on the target branch."],"tags":["flink","branch","equality-delete"],"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"}