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;