This is an automated email from the ASF dual-hosted git repository.
szehon-ho pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/main by this push:
new 6955fcd1f3 API: Fix failed scans and wrong results for IN/NOT IN on
timestamp_ns partitions (#18478)
6955fcd1f3 is described below
commit 6955fcd1f350856d96a572e3f53f01bdab748e7f
Author: jiwen624 <[email protected]>
AuthorDate: Thu Oct 29 11:43:15 2026 -0700
API: Fix failed scans and wrong results for IN/NOT IN on timestamp_ns
partitions (#18478)
---
.../org/apache/iceberg/transforms/Identity.java | 13 ++++++++-
.../apache/iceberg/transforms/TestProjection.java | 31 ++++++++++++++++++++++
2 files changed, 43 insertions(+), 1 deletion(-)
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 f52e636335..f0839edbb5 100644
--- a/api/src/main/java/org/apache/iceberg/transforms/Identity.java
+++ b/api/src/main/java/org/apache/iceberg/transforms/Identity.java
@@ -25,6 +25,7 @@ import org.apache.iceberg.expressions.Expressions;
import org.apache.iceberg.expressions.UnboundPredicate;
import org.apache.iceberg.relocated.com.google.common.base.Preconditions;
import org.apache.iceberg.relocated.com.google.common.collect.ImmutableSet;
+import org.apache.iceberg.relocated.com.google.common.collect.Iterables;
import org.apache.iceberg.types.Type;
import org.apache.iceberg.util.SerializableFunction;
@@ -145,13 +146,23 @@ class Identity<T> implements Transform<T, T> {
}
@Override
+ @SuppressWarnings("unchecked")
public UnboundPredicate<T> projectStrict(String name, BoundPredicate<T>
predicate) {
if (predicate.isUnaryPredicate()) {
return Expressions.predicate(predicate.op(), name);
} else if (predicate.isLiteralPredicate()) {
return Expressions.predicate(predicate.op(), name,
predicate.asLiteralPredicate().literal());
} else if (predicate.isSetPredicate()) {
- return Expressions.predicate(predicate.op(), name,
predicate.asSetPredicate().literalSet());
+ Set<T> values = predicate.asSetPredicate().literalSet();
+ if (predicate.term().type().typeId() == Type.TypeID.TIMESTAMP_NANO) {
+ return (UnboundPredicate<T>)
+ Expressions.predicate(
+ predicate.op(),
+ name,
+ Iterables.transform(values, v -> Expressions.nanos((Long) v)));
+ }
+
+ return Expressions.predicate(predicate.op(), name, values);
}
return null;
}
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 5dd1cdab86..cf3dfe4e4c 100644
--- a/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java
+++ b/api/src/test/java/org/apache/iceberg/transforms/TestProjection.java
@@ -39,9 +39,11 @@ import static
org.assertj.core.api.Assertions.assertThatThrownBy;
import java.util.List;
import org.apache.iceberg.PartitionSpec;
import org.apache.iceberg.Schema;
+import org.apache.iceberg.TestHelpers;
import org.apache.iceberg.exceptions.ValidationException;
import org.apache.iceberg.expressions.Binder;
import org.apache.iceberg.expressions.BoundPredicate;
+import org.apache.iceberg.expressions.Evaluator;
import org.apache.iceberg.expressions.Expression;
import org.apache.iceberg.expressions.Expressions;
import org.apache.iceberg.expressions.Or;
@@ -49,7 +51,10 @@ import org.apache.iceberg.expressions.Projections;
import org.apache.iceberg.expressions.UnboundPredicate;
import org.apache.iceberg.relocated.com.google.common.collect.Lists;
import org.apache.iceberg.types.Types;
+import org.apache.iceberg.util.DateTimeUtil;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
public class TestProjection {
private static final Schema SCHEMA = new Schema(optional(16, "id",
Types.LongType.get()));
@@ -394,4 +399,30 @@ public class TestProjection {
Projections.inclusive(partitionSpec).project(equal(truncate("string", 10),
"abc"));
assertThat(predicate.ref().name()).isEqualTo("string_trunc");
}
+
+ @ParameterizedTest
+ @ValueSource(strings = {"2026-01-01T00:00:00.000000001",
"1970-01-02T00:00:00.000000001"})
+ void identityProjectionOfTimestampNanoIn(String ts) {
+ Schema schema = new Schema(optional(1, "ts",
Types.TimestampNanoType.withoutZone()));
+ PartitionSpec spec =
PartitionSpec.builderFor(schema).identity("ts").build();
+ Expression in = Expressions.in("ts", ts, "1970-01-03T00:00:00");
+
+ Expression projected = Projections.inclusive(spec).project(in);
+
+ Evaluator evaluator = new Evaluator(spec.partitionType(), projected);
+
assertThat(evaluator.eval(TestHelpers.Row.of(DateTimeUtil.isoTimestampToNanos(ts)))).isTrue();
+ }
+
+ @Test
+ void identityStrictProjectionOfTimestampNanoNotIn() {
+ String ts = "1970-01-02T00:00:00.000000001";
+ Schema schema = new Schema(optional(1, "ts",
Types.TimestampNanoType.withoutZone()));
+ PartitionSpec spec =
PartitionSpec.builderFor(schema).identity("ts").build();
+ Expression notIn = Expressions.notIn("ts", ts, "1970-01-03T00:00:00");
+
+ Expression projected = Projections.strict(spec).project(notIn);
+
+ Evaluator evaluator = new Evaluator(spec.partitionType(), projected);
+
assertThat(evaluator.eval(TestHelpers.Row.of(DateTimeUtil.isoTimestampToNanos(ts)))).isFalse();
+ }
}