diff --git a/api/src/main/java/org/apache/iceberg/transforms/Identity.java b/api/src/main/java/org/apache/iceberg/transforms/Identity.java index f52e63633578..2b4c0ccdfa66 100644 --- a/api/src/main/java/org/apache/iceberg/transforms/Identity.java +++ b/api/src/main/java/org/apache/iceberg/transforms/Identity.java @@ -21,6 +21,8 @@ import java.io.ObjectStreamException; import java.util.Set; import org.apache.iceberg.expressions.BoundPredicate; +import org.apache.iceberg.expressions.BoundReference; +import org.apache.iceberg.expressions.BoundTransform; import org.apache.iceberg.expressions.Expressions; import org.apache.iceberg.expressions.UnboundPredicate; import org.apache.iceberg.relocated.com.google.common.base.Preconditions; @@ -146,6 +148,14 @@ public UnboundPredicate project(String name, BoundPredicate predicate) { @Override public UnboundPredicate projectStrict(String name, BoundPredicate predicate) { + if (predicate.term() instanceof BoundTransform) { + return ProjectionUtil.projectTransformPredicate(this, name, predicate); + } + + if (!(predicate.term() instanceof BoundReference)) { + return null; + } + if (predicate.isUnaryPredicate()) { return Expressions.predicate(predicate.op(), name); } else if (predicate.isLiteralPredicate()) { diff --git a/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java b/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java index 5dd1cdab869b..19989d9dcbbd 100644 --- a/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java +++ b/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java @@ -44,6 +44,7 @@ import org.apache.iceberg.expressions.BoundPredicate; import org.apache.iceberg.expressions.Expression; import org.apache.iceberg.expressions.Expressions; +import org.apache.iceberg.expressions.ManifestEvaluator; import org.apache.iceberg.expressions.Or; import org.apache.iceberg.expressions.Projections; import org.apache.iceberg.expressions.UnboundPredicate; @@ -394,4 +395,26 @@ public void testProjectionNames() { Projections.inclusive(partitionSpec).project(equal(truncate("string", 10), "abc")); assertThat(predicate.ref().name()).isEqualTo("string_trunc"); } + + @Test + void identityProjectionWithTransformPredicate() { + // Regression test for https://github.com/apache/iceberg/issues/15502. + // Identity transform cannot project a transform-based predicate (e.g., hours(ts)), + // so projection must fall back to alwaysTrue (inclusive) or alwaysFalse (strict). + Schema schema = + new Schema( + required(1, "id", Types.LongType.get()), + required(2, "ts", Types.TimestampType.withZone())); + + PartitionSpec spec = PartitionSpec.builderFor(schema).identity("ts").build(); + Expression hourFilter = equal(hour("ts"), 490674); + + Expression projected = Projections.inclusive(spec).project(hourFilter); + assertThat(projected).isEqualTo(Expressions.alwaysTrue()); + + Expression strictProjected = Projections.strict(spec).project(hourFilter); + assertThat(strictProjected).isEqualTo(Expressions.alwaysFalse()); + + assertThat(ManifestEvaluator.forRowFilter(hourFilter, spec, true)).isNotNull(); + } }