{"record":{"id":"e19cf33af7f2c004","repo":"apache/iceberg","slug":"cannot-replace-transform-in-unknown-predicate-p","errorCode":null,"errorMessage":"Cannot replace transform in unknown predicate: + pred","messagePattern":"Cannot replace transform in unknown predicate: \\+ pred","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/src/main/java/org/apache/iceberg/transforms/ProjectionUtil.java","lineNumber":252,"sourceCode":"        && transform\n            .toString()\n            .equals(((BoundTransform<?, ?>) pred.term()).transform().toString())) {\n      // the bound value must be a T because the transform matches\n      return (UnboundPredicate<T>) removeTransform(partitionName, pred);\n    }\n    return null;\n  }\n\n  private static <T> UnboundPredicate<T> removeTransform(\n      String partitionName, BoundPredicate<T> pred) {\n    if (pred.isUnaryPredicate()) {\n      return Expressions.predicate(pred.op(), partitionName);\n    } else if (pred.isLiteralPredicate()) {\n      return Expressions.predicate(pred.op(), partitionName, pred.asLiteralPredicate().literal());\n    } else if (pred.isSetPredicate()) {\n      return Expressions.predicate(pred.op(), partitionName, pred.asSetPredicate().literalSet());\n    }\n    throw new UnsupportedOperationException(\n        \"Cannot replace transform in unknown predicate: \" + pred);\n  }\n\n  static <S, T> UnboundPredicate<T> transformSet(\n      String fieldName, BoundSetPredicate<S> predicate, Function<S, T> transform) {\n    return predicate(\n        predicate.op(),\n        fieldName,\n        Iterables.transform(predicate.asSetPredicate().literalSet(), transform::apply));\n  }\n\n  /**\n   * Fixes an inclusive projection to account for incorrectly transformed values.\n   *\n   * <p>A bug in 0.10.0 and earlier caused negative values to be incorrectly transformed by date and\n   * timestamp transforms to 1 larger than the correct value. For example, day(1969-12-31 10:00:00)\n   * produced 0 instead of -1. To read data written by versions with this bug, this method adjusts\n   * the inclusive projection. The current inclusive projection is correct, so this modifies the","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/api/src/main/java/org/apache/iceberg/transforms/ProjectionUtil.java#L234-L270","documentation":"ProjectionUtil.removeTransform converts a BoundPredicate whose term is a BoundTransform back into an unbound predicate over the partition column. It handles unary, literal, and set predicates; if the bound predicate is none of these, the transform cannot be stripped and this UnsupportedOperationException is thrown. In practice this is an internal invariant guard: reaching it means the predicate shape wasn't covered by the three supported categories.","triggerScenarios":"projectTransformPredicate matches a BoundTransform term whose transform equals the partition transform, then removeTransform encounters a BoundPredicate that is simultaneously not unary, not literal, and not set — i.e. an unrecognized/extended predicate kind reaching the projection path during expression projection to partition predicates.","commonSituations":"Custom expressions or third-party predicate extensions bound against a transformed term; engine integrations feeding non-standard BoundPredicate implementations into inclusive/strict projection; internal bug when a new predicate operation is added without updating removeTransform.","solutions":["Check the predicate's op() and term() — restrict usage to standard unary (isNull/notNull), literal (==, <, >=, startsWith), and set (in/notIn) predicates","Upgrade Iceberg — a fix may have extended removeTransform to cover the new predicate kind","If you bind custom predicate types, project them yourself before handing the expression to partition projection (Bindings via Expressions over partition columns directly)"],"exampleFix":"// before\n// projection given an unsupported bound predicate kind\nUnsupportedOperationException: Cannot replace transform in unknown predicate: ...\n// after\n// project only standard predicate forms, e.g. rewrite to EQ/IN/LT-style bound predicates\nExpression projected = Projections.inclusive(expr, spec); // uses supported predicate kinds","handlingStrategy":"try-catch","validationCode":"// Confirm the bound predicate is one of the supported shapes before projection:\nboolean projectable(BoundPredicate<?> pred) {\n  return pred.isUnaryPredicate() || pred.isLiteralPredicate() || pred.isSetPredicate();\n}","typeGuard":null,"tryCatchPattern":"try {\n  UnboundPredicate<T> p = Projections.inclusive(expr, spec); // uses projectTransformPredicate\n} catch (UnsupportedOperationException e) {\n  if (e.getMessage().startsWith(\"Cannot replace transform\")) {\n    // fall back to unprojected expression / full manifest scan\n  } else {\n    throw e;\n  }\n}","preventionTips":["Only bind standard Iceberg predicates (EQ/IN/LT/GT/IS NULL/NOT) over transformed terms","Upgrade Iceberg if you use newer predicate kinds that removeTransform may not cover","When writing custom engines, project unsupported predicate shapes yourself before handing them to Projections"],"tags":["java","expressions","predicate-projection","internal"],"backgroundTag":"internal-invariant-violation","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}