This is an automated email from the ASF dual-hosted git repository.

aokolnychyi 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 865b60d473 Core: Prevent exceptions in ExpressionUtil for 
unpartitioned tables (#15243)
865b60d473 is described below

commit 865b60d473d4a43c5dc623fb84fd542fc556fdd9
Author: Anton Okolnychyi <[email protected]>
AuthorDate: Mon Feb 9 07:31:04 2026 -0800

    Core: Prevent exceptions in ExpressionUtil for unpartitioned tables (#15243)
---
 .../apache/iceberg/expressions/ExpressionUtil.java    |  4 ++++
 .../iceberg/expressions/TestExpressionUtil.java       | 19 +++++++++++++++++++
 .../apache/iceberg/spark/source/SparkScanBuilder.java |  9 +--------
 3 files changed, 24 insertions(+), 8 deletions(-)

diff --git 
a/api/src/main/java/org/apache/iceberg/expressions/ExpressionUtil.java 
b/api/src/main/java/org/apache/iceberg/expressions/ExpressionUtil.java
index f6fb082d86..9502e1d5bd 100644
--- a/api/src/main/java/org/apache/iceberg/expressions/ExpressionUtil.java
+++ b/api/src/main/java/org/apache/iceberg/expressions/ExpressionUtil.java
@@ -209,6 +209,10 @@ public class ExpressionUtil {
    */
   public static boolean selectsPartitions(
       Expression expr, PartitionSpec spec, boolean caseSensitive) {
+    if (spec.isUnpartitioned()) {
+      return false;
+    }
+
     return equivalent(
         Projections.inclusive(spec, caseSensitive).project(expr),
         Projections.strict(spec, caseSensitive).project(expr),
diff --git 
a/api/src/test/java/org/apache/iceberg/expressions/TestExpressionUtil.java 
b/api/src/test/java/org/apache/iceberg/expressions/TestExpressionUtil.java
index d9fe26eacc..a528142188 100644
--- a/api/src/test/java/org/apache/iceberg/expressions/TestExpressionUtil.java
+++ b/api/src/test/java/org/apache/iceberg/expressions/TestExpressionUtil.java
@@ -19,6 +19,8 @@
 package org.apache.iceberg.expressions;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import java.nio.ByteBuffer;
 import java.time.LocalDate;
@@ -33,6 +35,7 @@ import java.util.regex.Pattern;
 import java.util.stream.IntStream;
 import org.apache.iceberg.PartitionSpec;
 import org.apache.iceberg.Schema;
+import org.apache.iceberg.Table;
 import org.apache.iceberg.relocated.com.google.common.collect.ImmutableMap;
 import org.apache.iceberg.relocated.com.google.common.collect.Lists;
 import org.apache.iceberg.types.Types;
@@ -1072,6 +1075,22 @@ public class TestExpressionUtil {
         .isFalse();
   }
 
+  @Test
+  public void testSelectsPartitionsWithUnpartitionedTable() {
+    Table table = mock(Table.class);
+    Map<Integer, PartitionSpec> specs =
+        ImmutableMap.of(
+            0,
+            PartitionSpec.unpartitioned(),
+            1,
+            PartitionSpec.builderFor(SCHEMA).identity("val").build());
+    when(table.specs()).thenReturn(specs);
+
+    assertThat(ExpressionUtil.selectsPartitions(Expressions.lessThan("id", 
1L), table, true))
+        .as("Should return false for unpartitioned table (no partition 
boundaries to select)")
+        .isFalse();
+  }
+
   @Test
   public void testSanitizeVariantArray() {
     Expression bound =
diff --git 
a/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/SparkScanBuilder.java
 
b/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/SparkScanBuilder.java
index dd914f1617..3878759f74 100644
--- 
a/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/SparkScanBuilder.java
+++ 
b/spark/v4.1/spark/src/main/java/org/apache/iceberg/spark/source/SparkScanBuilder.java
@@ -33,7 +33,6 @@ import org.apache.iceberg.IncrementalChangelogScan;
 import org.apache.iceberg.MetadataColumns;
 import org.apache.iceberg.MetricsConfig;
 import org.apache.iceberg.MetricsModes;
-import org.apache.iceberg.PartitionSpec;
 import org.apache.iceberg.RequiresRemoteScanPlanning;
 import org.apache.iceberg.Schema;
 import org.apache.iceberg.Snapshot;
@@ -175,9 +174,7 @@ public class SparkScanBuilder
           pushableFilters.add(predicate);
         }
 
-        if (expr == null
-            || unpartitioned()
-            || !ExpressionUtil.selectsPartitions(expr, table, caseSensitive)) {
+        if (expr == null || !ExpressionUtil.selectsPartitions(expr, table, 
caseSensitive)) {
           postScanFilters.add(predicate);
         } else {
           LOG.info("Evaluating completely on Iceberg side: {}", predicate);
@@ -195,10 +192,6 @@ public class SparkScanBuilder
     return postScanFilters.toArray(new Predicate[0]);
   }
 
-  private boolean unpartitioned() {
-    return 
table.specs().values().stream().noneMatch(PartitionSpec::isPartitioned);
-  }
-
   @Override
   public Predicate[] pushedPredicates() {
     return pushedPredicates;

Reply via email to