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();
+  }
 }

Reply via email to