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

luoyuxia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fluss.git


The following commit(s) were added to refs/heads/main by this push:
     new 71e6bb6f0 [lake/iceberg] Enhance IcebergSplitPlanner to only include 
filter column stats (#3237)
71e6bb6f0 is described below

commit 71e6bb6f0f7120a2b6092a5aac2c947086f94857
Author: Junfan Zhang <[email protected]>
AuthorDate: Sat May 9 15:00:19 2026 +0800

    [lake/iceberg] Enhance IcebergSplitPlanner to only include filter column 
stats (#3237)
---
 .../lake/iceberg/source/IcebergSplitPlanner.java   | 60 ++++++++++++++++++-
 .../iceberg/source/IcebergSplitPlannerTest.java    | 70 ++++++++++++++++++++++
 2 files changed, 129 insertions(+), 1 deletion(-)

diff --git 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlanner.java
 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlanner.java
index 2c308088c..a397a5af0 100644
--- 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlanner.java
+++ 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlanner.java
@@ -18,6 +18,7 @@
 
 package org.apache.fluss.lake.iceberg.source;
 
+import org.apache.fluss.annotation.VisibleForTesting;
 import org.apache.fluss.config.Configuration;
 import org.apache.fluss.lake.iceberg.utils.IcebergCatalogUtils;
 import org.apache.fluss.lake.source.Planner;
@@ -29,7 +30,10 @@ import org.apache.iceberg.PartitionSpec;
 import org.apache.iceberg.Table;
 import org.apache.iceberg.TableScan;
 import org.apache.iceberg.catalog.Catalog;
+import org.apache.iceberg.expressions.BoundPredicate;
 import org.apache.iceberg.expressions.Expression;
+import org.apache.iceberg.expressions.ExpressionVisitors;
+import org.apache.iceberg.expressions.UnboundPredicate;
 import org.apache.iceberg.io.CloseableIterable;
 
 import javax.annotation.Nullable;
@@ -37,7 +41,9 @@ import javax.annotation.Nullable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
@@ -70,7 +76,11 @@ public class IcebergSplitPlanner implements 
Planner<IcebergSplit> {
         Function<FileScanTask, Integer> bucketExtractor = 
createBucketExtractor(table);
         TableScan tableScan = table.newScan().useSnapshot(snapshotId);
         if (filter != null) {
-            tableScan = tableScan.includeColumnStats().filter(filter);
+            Set<String> filterColumns = referencedColumns(filter);
+            if (!filterColumns.isEmpty()) {
+                tableScan = tableScan.includeColumnStats(filterColumns);
+            }
+            tableScan = tableScan.filter(filter);
         }
         try (CloseableIterable<FileScanTask> tasks = tableScan.planFiles()) {
             tasks.forEach(
@@ -84,6 +94,54 @@ public class IcebergSplitPlanner implements 
Planner<IcebergSplit> {
         return splits;
     }
 
+    @VisibleForTesting
+    Set<String> referencedColumns(Expression expression) {
+        return ExpressionVisitors.visit(
+                expression,
+                new ExpressionVisitors.ExpressionVisitor<Set<String>>() {
+                    @Override
+                    public Set<String> alwaysTrue() {
+                        return Collections.emptySet();
+                    }
+
+                    @Override
+                    public Set<String> alwaysFalse() {
+                        return Collections.emptySet();
+                    }
+
+                    @Override
+                    public Set<String> not(Set<String> result) {
+                        return result;
+                    }
+
+                    @Override
+                    public Set<String> and(Set<String> leftResult, Set<String> 
rightResult) {
+                        return union(leftResult, rightResult);
+                    }
+
+                    @Override
+                    public Set<String> or(Set<String> leftResult, Set<String> 
rightResult) {
+                        return union(leftResult, rightResult);
+                    }
+
+                    @Override
+                    public <T> Set<String> predicate(BoundPredicate<T> pred) {
+                        return Collections.singleton(pred.ref().name());
+                    }
+
+                    @Override
+                    public <T> Set<String> predicate(UnboundPredicate<T> pred) 
{
+                        return Collections.singleton(pred.ref().name());
+                    }
+                });
+    }
+
+    private Set<String> union(Set<String> left, Set<String> right) {
+        Set<String> result = new LinkedHashSet<>(left);
+        result.addAll(right);
+        return result;
+    }
+
     private Function<FileScanTask, Integer> createBucketExtractor(Table table) 
{
         PartitionSpec partitionSpec = table.spec();
         List<PartitionField> partitionFields = partitionSpec.fields();
diff --git 
a/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlannerTest.java
 
b/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlannerTest.java
index 2697339e6..a083b63f2 100644
--- 
a/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlannerTest.java
+++ 
b/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergSplitPlannerTest.java
@@ -18,15 +18,23 @@
 
 package org.apache.fluss.lake.iceberg.source;
 
+import org.apache.fluss.config.Configuration;
 import org.apache.fluss.lake.source.LakeSource;
 import org.apache.fluss.metadata.TablePath;
+import org.apache.fluss.predicate.PredicateBuilder;
+import org.apache.fluss.types.IntType;
+import org.apache.fluss.types.RowType;
+import org.apache.fluss.types.StringType;
 
 import org.apache.iceberg.PartitionSpec;
 import org.apache.iceberg.Schema;
 import org.apache.iceberg.Snapshot;
 import org.apache.iceberg.Table;
 import org.apache.iceberg.data.GenericRecord;
+import org.apache.iceberg.expressions.Expression;
+import org.apache.iceberg.expressions.Expressions;
 import org.apache.iceberg.types.Types;
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
 
@@ -34,6 +42,7 @@ import java.time.OffsetDateTime;
 import java.time.ZoneOffset;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 import static org.apache.fluss.metadata.TableDescriptor.BUCKET_COLUMN_NAME;
 import static org.apache.fluss.metadata.TableDescriptor.OFFSET_COLUMN_NAME;
@@ -144,4 +153,65 @@ class IcebergSplitPlannerTest extends 
IcebergSourceTestBase {
                             Collections.singletonList("a"), 
Collections.singletonList("b"));
         }
     }
+
+    @Test
+    void testOnlyIncludesFilterColumnStats() throws Exception {
+        TablePath tablePath = TablePath.of(DEFAULT_DB, 
"test_filter_column_stats");
+        Schema schema =
+                new Schema(
+                        optional(1, "c1", Types.IntegerType.get()),
+                        optional(2, "c2", Types.StringType.get()),
+                        optional(3, "c3", Types.StringType.get()));
+        PartitionSpec partitionSpec = 
PartitionSpec.builderFor(schema).bucket("c1", 2).build();
+        createTable(tablePath, schema, partitionSpec);
+
+        Table table = getTable(tablePath);
+        GenericRecord record = createIcebergRecord(schema, 12, "a", "A");
+        writeRecord(table, Collections.singletonList(record), null, 0);
+        table.refresh();
+        Snapshot snapshot = table.currentSnapshot();
+
+        PredicateBuilder predicateBuilder =
+                new PredicateBuilder(RowType.of(new IntType(), new 
StringType(), new StringType()));
+        LakeSource<IcebergSplit> lakeSource = 
lakeStorage.createLakeSource(tablePath);
+        
lakeSource.withFilters(Collections.singletonList(predicateBuilder.greaterOrEqual(0,
 10)));
+        List<IcebergSplit> icebergSplits = 
lakeSource.createPlanner(snapshot::snapshotId).plan();
+
+        assertThat(icebergSplits).hasSize(1);
+        
assertThat(icebergSplits.get(0).fileScanTask().file().lowerBounds().keySet())
+                .containsOnly(1);
+    }
+
+    @Test
+    void testReferencedColumns() {
+        // Unbound predicates expose the referenced column through their named 
reference.
+        assertThat(referencedColumns(Expressions.greaterThanOrEqual("c1", 
10))).containsOnly("c1");
+
+        // Compound expressions merge referenced columns and deduplicate 
repeated columns.
+        Expression compoundExpression =
+                Expressions.and(
+                        Expressions.or(
+                                Expressions.greaterThanOrEqual("c1", 10),
+                                Expressions.equal("c2", "a")),
+                        Expressions.not(Expressions.lessThan("c1", 20)));
+        assertThat(referencedColumns(compoundExpression)).containsOnly("c1", 
"c2");
+
+        // Bound predicates expose the resolved field through their bound 
reference.
+        Schema schema =
+                new Schema(
+                        optional(1, "c1", Types.IntegerType.get()),
+                        optional(2, "c2", Types.StringType.get()));
+        Expression boundExpression =
+                Expressions.greaterThanOrEqual("c2", 
"a").bind(schema.asStruct(), true);
+        assertThat(referencedColumns(boundExpression)).containsOnly("c2");
+    }
+
+    private Set<String> referencedColumns(Expression expression) {
+        return new IcebergSplitPlanner(
+                        new Configuration(),
+                        TablePath.of(DEFAULT_DB, "test_referenced_columns"),
+                        0,
+                        null)
+                .referencedColumns(expression);
+    }
 }

Reply via email to