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 549a739fa [lake/iceberg] Fix predicate push-down for TINYINT / 
SMALLINT into Iceberg (#3565)
549a739fa is described below

commit 549a739fa828ac418995488869244d22b3c48699
Author: GuoYu <[email protected]>
AuthorDate: Fri Jul 3 14:14:37 2026 +0800

    [lake/iceberg] Fix predicate push-down for TINYINT / SMALLINT into Iceberg 
(#3565)
---
 .../utils/FlussToIcebergPredicateConverter.java    | 27 ++++++-----
 .../lake/iceberg/utils/IcebergConversions.java     | 55 ++--------------------
 .../lake/iceberg/source/IcebergLakeSourceTest.java | 54 ++++++++++++++++++++-
 3 files changed, 71 insertions(+), 65 deletions(-)

diff --git 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/FlussToIcebergPredicateConverter.java
 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/FlussToIcebergPredicateConverter.java
index 77d50bb96..634ee0f40 100644
--- 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/FlussToIcebergPredicateConverter.java
+++ 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/FlussToIcebergPredicateConverter.java
@@ -107,7 +107,7 @@ public class FlussToIcebergPredicateConverter implements 
PredicateVisitor<Expres
         public Expression visitStartsWith(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
             return Expressions.startsWith(
-                    fieldName, convertToIcebergLiteral(fieldRef.index(), 
literal).toString());
+                    fieldName, convertToIcebergLiteral(fieldRef, 
literal).toString());
         }
 
         @Override
@@ -125,41 +125,39 @@ public class FlussToIcebergPredicateConverter implements 
PredicateVisitor<Expres
         @Override
         public Expression visitLessThan(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
-            return Expressions.lessThan(
-                    fieldName, convertToIcebergLiteral(fieldRef.index(), 
literal));
+            return Expressions.lessThan(fieldName, 
convertToIcebergLiteral(fieldRef, literal));
         }
 
         @Override
         public Expression visitGreaterOrEqual(FieldRef fieldRef, Object 
literal) {
             String fieldName = getField(fieldRef.index()).name();
             return Expressions.greaterThanOrEqual(
-                    fieldName, convertToIcebergLiteral(fieldRef.index(), 
literal));
+                    fieldName, convertToIcebergLiteral(fieldRef, literal));
         }
 
         @Override
         public Expression visitNotEqual(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
-            return Expressions.notEqual(
-                    fieldName, convertToIcebergLiteral(fieldRef.index(), 
literal));
+            return Expressions.notEqual(fieldName, 
convertToIcebergLiteral(fieldRef, literal));
         }
 
         @Override
         public Expression visitLessOrEqual(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
             return Expressions.lessThanOrEqual(
-                    fieldName, convertToIcebergLiteral(fieldRef.index(), 
literal));
+                    fieldName, convertToIcebergLiteral(fieldRef, literal));
         }
 
         @Override
         public Expression visitEqual(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
-            return Expressions.equal(fieldName, 
convertToIcebergLiteral(fieldRef.index(), literal));
+            return Expressions.equal(fieldName, 
convertToIcebergLiteral(fieldRef, literal));
         }
 
         @Override
         public Expression visitGreaterThan(FieldRef fieldRef, Object literal) {
             String fieldName = getField(fieldRef.index()).name();
-            Object icebergLiteral = convertToIcebergLiteral(fieldRef.index(), 
literal);
+            Object icebergLiteral = convertToIcebergLiteral(fieldRef, literal);
             return Expressions.greaterThan(fieldName, icebergLiteral);
         }
 
@@ -168,7 +166,7 @@ public class FlussToIcebergPredicateConverter implements 
PredicateVisitor<Expres
             String fieldName = getField(fieldRef.index()).name();
             List<Object> icebergLiterals =
                     literals.stream()
-                            .map(literal -> 
convertToIcebergLiteral(fieldRef.index(), literal))
+                            .map(literal -> convertToIcebergLiteral(fieldRef, 
literal))
                             .collect(Collectors.toList());
             return Expressions.in(fieldName, icebergLiterals);
         }
@@ -178,7 +176,7 @@ public class FlussToIcebergPredicateConverter implements 
PredicateVisitor<Expres
             String fieldName = getField(fieldRef.index()).name();
             List<Object> icebergLiterals =
                     literals.stream()
-                            .map(literal -> 
convertToIcebergLiteral(fieldRef.index(), literal))
+                            .map(literal -> convertToIcebergLiteral(fieldRef, 
literal))
                             .collect(Collectors.toList());
             return Expressions.notIn(fieldName, icebergLiterals);
         }
@@ -199,8 +197,11 @@ public class FlussToIcebergPredicateConverter implements 
PredicateVisitor<Expres
             return icebergSchema.columns().get(fieldIndex);
         }
 
-        private Object convertToIcebergLiteral(int fieldIndex, Object 
flussLiteral) {
-            return toIcebergLiteral(getField(fieldIndex), flussLiteral);
+        private Object convertToIcebergLiteral(FieldRef fieldRef, Object 
flussLiteral) {
+            // Pass the original Fluss field type through so 
IcebergConversions can reuse
+            // FlussRowAsIcebergRecord's per-Fluss-type converter directly, 
avoiding the lossy
+            // reverse mapping from Iceberg types back to Fluss types.
+            return toIcebergLiteral(getField(fieldRef.index()), 
fieldRef.type(), flussLiteral);
         }
     }
 }
diff --git 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/IcebergConversions.java
 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/IcebergConversions.java
index b75ce8ad4..111df385f 100644
--- 
a/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/IcebergConversions.java
+++ 
b/fluss-lake/fluss-lake-iceberg/src/main/java/org/apache/fluss/lake/iceberg/utils/IcebergConversions.java
@@ -21,9 +21,7 @@ import 
org.apache.fluss.lake.iceberg.source.FlussRowAsIcebergRecord;
 import org.apache.fluss.metadata.TablePath;
 import org.apache.fluss.row.GenericRow;
 import org.apache.fluss.row.InternalRow;
-import org.apache.fluss.types.DataField;
 import org.apache.fluss.types.DataType;
-import org.apache.fluss.types.DataTypes;
 import org.apache.fluss.types.RowType;
 
 import org.apache.iceberg.PartitionField;
@@ -34,12 +32,10 @@ import org.apache.iceberg.Table;
 import org.apache.iceberg.catalog.TableIdentifier;
 import org.apache.iceberg.expressions.Expression;
 import org.apache.iceberg.expressions.Expressions;
-import org.apache.iceberg.types.Type;
 import org.apache.iceberg.types.Types;
 
 import javax.annotation.Nullable;
 
-import java.util.ArrayList;
 import java.util.List;
 
 import static 
org.apache.fluss.metadata.ResolvedPartitionSpec.PARTITION_SPEC_SEPARATOR;
@@ -93,55 +89,12 @@ public class IcebergConversions {
         return expression;
     }
 
-    public static Object toIcebergLiteral(Types.NestedField field, Object 
flussLiteral) {
+    public static Object toIcebergLiteral(
+            Types.NestedField icebergField, DataType flussFieldType, Object 
flussLiteral) {
         InternalRow flussRow = GenericRow.of(flussLiteral);
         FlussRowAsIcebergRecord flussRowAsIcebergRecord =
                 new FlussRowAsIcebergRecord(
-                        Types.StructType.of(field),
-                        
RowType.of(convertIcebergTypeToFlussType(field.type())),
-                        flussRow);
-        return flussRowAsIcebergRecord.get(0, 
field.type().typeId().javaClass());
-    }
-
-    /** Converts Iceberg data types to Fluss data types. */
-    private static DataType convertIcebergTypeToFlussType(Type icebergType) {
-        if (icebergType instanceof Types.BooleanType) {
-            return DataTypes.BOOLEAN();
-        } else if (icebergType instanceof Types.IntegerType) {
-            return DataTypes.INT();
-        } else if (icebergType instanceof Types.LongType) {
-            return DataTypes.BIGINT();
-        } else if (icebergType instanceof Types.DoubleType) {
-            return DataTypes.DOUBLE();
-        } else if (icebergType instanceof Types.TimeType) {
-            return DataTypes.TIME();
-        } else if (icebergType instanceof Types.TimestampType) {
-            Types.TimestampType timestampType = (Types.TimestampType) 
icebergType;
-            if (timestampType.shouldAdjustToUTC()) {
-                return DataTypes.TIMESTAMP_LTZ();
-            } else {
-                return DataTypes.TIMESTAMP();
-            }
-        } else if (icebergType instanceof Types.StringType) {
-            return DataTypes.STRING();
-        } else if (icebergType instanceof Types.DecimalType) {
-            Types.DecimalType decimalType = (Types.DecimalType) icebergType;
-            return DataTypes.DECIMAL(decimalType.precision(), 
decimalType.scale());
-        } else if (icebergType instanceof Types.ListType) {
-            Types.ListType listType = (Types.ListType) icebergType;
-            return 
DataTypes.ARRAY(convertIcebergTypeToFlussType(listType.elementType()));
-        } else if (icebergType.isStructType()) {
-            Types.StructType structType = icebergType.asStructType();
-            List<DataField> fields = new ArrayList<>();
-            for (Types.NestedField nestedField : structType.fields()) {
-                DataType fieldType = 
convertIcebergTypeToFlussType(nestedField.type());
-                fields.add(new DataField(nestedField.name(), fieldType));
-            }
-            return DataTypes.ROW(fields.toArray(new DataField[0]));
-        }
-
-        throw new UnsupportedOperationException(
-                "Unsupported data type conversion for Iceberg type: "
-                        + icebergType.getClass().getName());
+                        Types.StructType.of(icebergField), 
RowType.of(flussFieldType), flussRow);
+        return flussRowAsIcebergRecord.get(0, 
icebergField.type().typeId().javaClass());
     }
 }
diff --git 
a/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergLakeSourceTest.java
 
b/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergLakeSourceTest.java
index d69def535..d136e910b 100644
--- 
a/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergLakeSourceTest.java
+++ 
b/fluss-lake/fluss-lake-iceberg/src/test/java/org/apache/fluss/lake/iceberg/source/IcebergLakeSourceTest.java
@@ -66,7 +66,10 @@ class IcebergLakeSourceTest extends IcebergSourceTestBase {
             PartitionSpec.builderFor(SCHEMA).bucket("id", 
DEFAULT_BUCKET_NUM).build();
 
     private static final PredicateBuilder FLUSS_BUILDER =
-            new PredicateBuilder(RowType.of(DataTypes.BIGINT(), 
DataTypes.STRING()));
+            new PredicateBuilder(RowType.of(DataTypes.INT(), 
DataTypes.STRING()));
+
+    private static final PredicateBuilder FLUSS_BUILDER_TINYINT =
+            new PredicateBuilder(RowType.of(DataTypes.TINYINT(), 
DataTypes.STRING()));
 
     @BeforeAll
     protected static void beforeAll() {
@@ -157,4 +160,53 @@ class IcebergLakeSourceTest extends IcebergSourceTestBase {
         assertThat(filterPushDownResult.remainingPredicates().toString())
                 .isEqualTo(allFilters.toString());
     }
+
+    @Test
+    void testWithTinyIntFilter() throws Exception {
+        TablePath tablePath = TablePath.of("fluss", "test_filters_tinyint");
+        createTable(tablePath, SCHEMA, PARTITION_SPEC);
+
+        Table table = getTable(tablePath);
+        List<Record> rows = new ArrayList<>();
+        for (int i = 1; i <= 4; i++) {
+            rows.add(
+                    createIcebergRecord(
+                            SCHEMA,
+                            i,
+                            "name" + i,
+                            0,
+                            (long) i,
+                            OffsetDateTime.now(ZoneOffset.UTC)));
+        }
+        writeRecord(table, rows, null, 0);
+        table.refresh();
+
+        // pass Byte literal (not Integer) to exercise the Fluss TINYINT -> 
Iceberg int path
+        Predicate filter1 = FLUSS_BUILDER_TINYINT.greaterOrEqual(0, (byte) 2);
+        Predicate filter2 = FLUSS_BUILDER_TINYINT.lessOrEqual(0, (byte) 3);
+        List<Predicate> allFilters = Arrays.asList(filter1, filter2);
+
+        LakeSource<IcebergSplit> lakeSource = 
lakeStorage.createLakeSource(tablePath);
+        LakeSource.FilterPushDownResult filterPushDownResult = 
lakeSource.withFilters(allFilters);
+        
assertThat(filterPushDownResult.acceptedPredicates()).isEqualTo(allFilters);
+        assertThat(filterPushDownResult.remainingPredicates()).isEmpty();
+
+        List<IcebergSplit> icebergSplits =
+                lakeSource.createPlanner(() -> 
table.currentSnapshot().snapshotId()).plan();
+        assertThat(icebergSplits).hasSize(1);
+        IcebergSplit icebergSplit = icebergSplits.get(0);
+
+        List<Row> actual = new ArrayList<>();
+        org.apache.fluss.row.InternalRow.FieldGetter[] fieldGetters =
+                org.apache.fluss.row.InternalRow.createFieldGetters(
+                        RowType.of(new IntType(), new StringType()));
+        RecordReader recordReader = lakeSource.createRecordReader(() -> 
icebergSplit);
+        try (CloseableIterator<LogRecord> iterator = recordReader.read()) {
+            actual.addAll(
+                    convertToFlinkRow(
+                            fieldGetters,
+                            TransformingCloseableIterator.transform(iterator, 
LogRecord::getRow)));
+        }
+        assertThat(actual.toString()).isEqualTo("[+I[2, name2], +I[3, 
name3]]");
+    }
 }

Reply via email to