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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 8096a82a5a [parquet] Fix that parquet filter pushdown wrong for 
decimal (#8623)
8096a82a5a is described below

commit 8096a82a5a2e41319c4dc701e59f721255f3822b
Author: yuzelin <[email protected]>
AuthorDate: Sat Jul 18 13:34:24 2026 +0800

    [parquet] Fix that parquet filter pushdown wrong for decimal (#8623)
---
 .../paimon/format/parquet/ParquetFileFormat.java   |   4 +-
 .../format/parquet/ParquetReaderFactory.java       |  48 +-
 .../parquet/filter2/predicate/ParquetFilters.java  | 304 ++++++---
 .../apache/parquet/hadoop/ParquetFileReader.java   |   2 +-
 .../parquet/ParquetCaseInsensitiveReadTest.java    |   4 +-
 .../format/parquet/ParquetColumnVectorTest.java    |   3 +-
 .../paimon/format/parquet/ParquetFiltersTest.java  | 718 ++++++++++++++++-----
 .../format/parquet/ParquetReadWriteTest.java       |  35 +-
 .../format/parquet/ParquetSchemaCacheTest.java     |  23 +-
 .../reader/ParquetInt96RowSelectionTest.java       |   3 +-
 10 files changed, 849 insertions(+), 295 deletions(-)

diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetFileFormat.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetFileFormat.java
index 8093e714b0..d78441a840 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetFileFormat.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetFileFormat.java
@@ -38,7 +38,6 @@ import org.apache.paimon.statistics.SimpleColStatsCollector;
 import org.apache.paimon.types.RowType;
 
 import org.apache.parquet.ParquetReadOptions;
-import org.apache.parquet.filter2.predicate.ParquetFilters;
 import org.apache.parquet.hadoop.ParquetFileReader;
 import org.apache.parquet.hadoop.ParquetOutputFormat;
 
@@ -85,8 +84,7 @@ public class ParquetFileFormat extends FileFormat implements 
SupportsFieldMetada
             RowType dataSchemaRowType,
             RowType projectedRowType,
             @Nullable List<Predicate> filters) {
-        return new ParquetReaderFactory(
-                options, projectedRowType, readBatchSize, 
ParquetFilters.convert(filters));
+        return new ParquetReaderFactory(options, projectedRowType, 
readBatchSize, filters);
     }
 
     @Override
diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetReaderFactory.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetReaderFactory.java
index a94e068efd..dd425a23a7 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetReaderFactory.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/parquet/ParquetReaderFactory.java
@@ -33,6 +33,7 @@ import 
org.apache.paimon.format.shredding.ShreddingReadPlanFactories;
 import org.apache.paimon.format.shredding.ShreddingReadPlanFactory;
 import org.apache.paimon.options.CatalogOptions;
 import org.apache.paimon.options.Options;
+import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.reader.FileRecordReader;
 import org.apache.paimon.types.ArrayType;
 import org.apache.paimon.types.DataField;
@@ -45,7 +46,9 @@ import org.apache.paimon.utils.Preconditions;
 
 import org.apache.parquet.ParquetReadOptions;
 import org.apache.parquet.filter2.compat.FilterCompat;
+import org.apache.parquet.filter2.predicate.ParquetFilters;
 import org.apache.parquet.hadoop.ParquetFileReader;
+import org.apache.parquet.hadoop.metadata.ParquetMetadata;
 import org.apache.parquet.io.ColumnIOFactory;
 import org.apache.parquet.io.MessageColumnIO;
 import org.apache.parquet.schema.ConversionPatterns;
@@ -85,7 +88,7 @@ public class ParquetReaderFactory implements 
FormatReaderFactory {
     private final RowType readType;
     private final int batchSize;
     private final boolean caseSensitive;
-    @Nullable private final FilterCompat.Filter filter;
+    @Nullable private final List<Predicate> predicates;
 
     /**
      * Cache: fileSchema -> requestedSchema.
@@ -101,12 +104,12 @@ public class ParquetReaderFactory implements 
FormatReaderFactory {
             new ConcurrentHashMap<>();
 
     public ParquetReaderFactory(
-            Options conf, RowType readType, int batchSize, @Nullable 
FilterCompat.Filter filter) {
+            Options conf, RowType readType, int batchSize, @Nullable 
List<Predicate> predicates) {
         this.conf = conf;
         this.readType = readType;
         this.batchSize = batchSize;
         this.caseSensitive = 
conf.getOptional(CatalogOptions.CASE_SENSITIVE).orElse(true);
-        this.filter = filter;
+        this.predicates = predicates;
     }
 
     @VisibleForTesting
@@ -117,18 +120,33 @@ public class ParquetReaderFactory implements 
FormatReaderFactory {
     @Override
     public FileRecordReader<InternalRow> 
createReader(FormatReaderFactory.Context context)
             throws IOException {
-        ParquetReadOptions.Builder builder =
-                ParquetUtil.getParquetReadOptionsBuilder(conf)
-                        .withRecordFilter(filter)
-                        .withRange(0, context.fileSize());
-
-        ParquetFileReader reader =
-                new ParquetFileReader(
-                        ParquetInputFile.fromPath(
-                                context.fileIO(), context.filePath(), 
context.fileSize()),
-                        builder.build(),
-                        context.selection());
-        MessageType fileSchema = reader.getFileMetaData().getSchema();
+        ParquetInputFile inputFile =
+                ParquetInputFile.fromPath(context.fileIO(), 
context.filePath(), context.fileSize());
+        ParquetReadOptions.Builder readOptionsBuilder =
+                ParquetUtil.getParquetReadOptionsBuilder(conf).withRange(0, 
context.fileSize());
+        ParquetInputStream inputStream = inputFile.newStream();
+        ParquetMetadata footer =
+                ParquetFileReader.readFooter(
+                        inputFile, readOptionsBuilder.build(), inputStream, 
true);
+
+        MessageType fileSchema = footer.getFileMetaData().getSchema();
+        ParquetFileReader reader;
+        try {
+            FilterCompat.Filter filter =
+                    ParquetFilters.convert(predicates, fileSchema, 
caseSensitive);
+            ParquetReadOptions readOptions = 
readOptionsBuilder.withRecordFilter(filter).build();
+            reader =
+                    new ParquetFileReader(
+                            inputFile, footer, readOptions, inputStream, 
context.selection());
+        } catch (Throwable t) {
+            try {
+                inputStream.close();
+            } catch (Throwable closeFailure) {
+                t.addSuppressed(closeFailure);
+            }
+            throw t;
+        }
+
         ShreddingReadPlan readPlan =
                 ShreddingReadPlanFactories.createReadPlan(
                         readType,
diff --git 
a/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
 
b/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
index 29feeb1b5e..f6ffbed1e2 100644
--- 
a/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
+++ 
b/paimon-format/src/main/java/org/apache/parquet/filter2/predicate/ParquetFilters.java
@@ -21,7 +21,6 @@ package org.apache.parquet.filter2.predicate;
 import org.apache.paimon.data.BinaryString;
 import org.apache.paimon.data.Decimal;
 import org.apache.paimon.data.Timestamp;
-import org.apache.paimon.format.parquet.ParquetSchemaConverter;
 import org.apache.paimon.predicate.FieldRef;
 import org.apache.paimon.predicate.FunctionVisitor;
 import org.apache.paimon.predicate.LeafPredicate;
@@ -55,25 +54,34 @@ import org.apache.parquet.filter2.compat.FilterCompat;
 import org.apache.parquet.filter2.predicate.Operators.DoubleColumn;
 import org.apache.parquet.filter2.predicate.Operators.FloatColumn;
 import org.apache.parquet.io.api.Binary;
+import org.apache.parquet.schema.LogicalTypeAnnotation;
+import 
org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation;
+import org.apache.parquet.schema.MessageType;
+import org.apache.parquet.schema.PrimitiveType;
+import org.apache.parquet.schema.Type;
 
 import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 /** Convert {@link Predicate} to {@link FilterCompat.Filter}. */
 public class ParquetFilters {
 
-    private static final ConvertFilterToParquet CONVERTER = new 
ConvertFilterToParquet();
-
     private ParquetFilters() {}
 
-    public static FilterCompat.Filter convert(List<Predicate> predicates) {
+    public static FilterCompat.Filter convert(
+            List<Predicate> predicates, MessageType fileSchema, boolean 
caseSensitive) {
+        ConvertFilterToParquet converter = new 
ConvertFilterToParquet(fileSchema, caseSensitive);
         FilterPredicate result = null;
         if (predicates != null) {
             for (Predicate predicate : predicates) {
                 try {
-                    FilterPredicate parquetFilter = predicate.visit(CONVERTER);
+                    FilterPredicate parquetFilter = predicate.visit(converter);
                     if (result == null) {
                         result = parquetFilter;
                     } else {
@@ -90,6 +98,14 @@ public class ParquetFilters {
     @SuppressWarnings({"unchecked", "rawtypes"})
     private static class ConvertFilterToParquet implements 
FunctionVisitor<FilterPredicate> {
 
+        private final MessageType fileSchema;
+        private final boolean caseSensitive;
+
+        private ConvertFilterToParquet(MessageType fileSchema, boolean 
caseSensitive) {
+            this.fileSchema = Objects.requireNonNull(fileSchema, "fileSchema");
+            this.caseSensitive = caseSensitive;
+        }
+
         @Override
         public FilterPredicate visitIsNotNull(FieldRef fieldRef) {
             return new Operators.NotEq<>(toParquetColumn(fieldRef), null);
@@ -114,38 +130,35 @@ public class ParquetFilters {
 
         @Override
         public FilterPredicate visitLessThan(FieldRef fieldRef, Object 
literal) {
-            return new Operators.Lt(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+            return new Operators.Lt(toParquetColumn(fieldRef), 
toParquetObject(literal, fieldRef));
         }
 
         @Override
         public FilterPredicate visitGreaterOrEqual(FieldRef fieldRef, Object 
literal) {
             return new Operators.GtEq(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef));
         }
 
         @Override
         public FilterPredicate visitNotEqual(FieldRef fieldRef, Object 
literal) {
             return new Operators.NotEq(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef));
         }
 
         @Override
         public FilterPredicate visitLessOrEqual(FieldRef fieldRef, Object 
literal) {
             return new Operators.LtEq(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef));
         }
 
         @Override
         public FilterPredicate visitEqual(FieldRef fieldRef, Object literal) {
-            return new Operators.Eq(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+            return new Operators.Eq(toParquetColumn(fieldRef), 
toParquetObject(literal, fieldRef));
         }
 
         @Override
         public FilterPredicate visitGreaterThan(FieldRef fieldRef, Object 
literal) {
-            return new Operators.Gt(
-                    toParquetColumn(fieldRef), toParquetObject(literal, 
fieldRef.type()));
+            return new Operators.Gt(toParquetColumn(fieldRef), 
toParquetObject(literal, fieldRef));
         }
 
         @Override
@@ -189,22 +202,25 @@ public class ParquetFilters {
         @Override
         public FilterPredicate visitIn(FieldRef fieldRef, List<Object> 
literals) {
             Operators.Column<?> column = toParquetColumn(fieldRef);
-            org.apache.paimon.types.DataType type = fieldRef.type();
             if (column instanceof Operators.LongColumn) {
                 return FilterApi.in(
-                        (Operators.LongColumn) column, convertSets(literals, 
Long.class, type));
+                        (Operators.LongColumn) column, convertSets(literals, 
Long.class, fieldRef));
             } else if (column instanceof Operators.IntColumn) {
                 return FilterApi.in(
-                        (Operators.IntColumn) column, convertSets(literals, 
Integer.class, type));
+                        (Operators.IntColumn) column,
+                        convertSets(literals, Integer.class, fieldRef));
             } else if (column instanceof Operators.DoubleColumn) {
                 return FilterApi.in(
-                        (Operators.DoubleColumn) column, convertSets(literals, 
Double.class, type));
+                        (Operators.DoubleColumn) column,
+                        convertSets(literals, Double.class, fieldRef));
             } else if (column instanceof Operators.FloatColumn) {
                 return FilterApi.in(
-                        (Operators.FloatColumn) column, convertSets(literals, 
Float.class, type));
+                        (Operators.FloatColumn) column,
+                        convertSets(literals, Float.class, fieldRef));
             } else if (column instanceof Operators.BinaryColumn) {
                 return FilterApi.in(
-                        (Operators.BinaryColumn) column, convertSets(literals, 
Binary.class, type));
+                        (Operators.BinaryColumn) column,
+                        convertSets(literals, Binary.class, fieldRef));
             }
 
             throw new UnsupportedOperationException();
@@ -213,22 +229,25 @@ public class ParquetFilters {
         @Override
         public FilterPredicate visitNotIn(FieldRef fieldRef, List<Object> 
literals) {
             Operators.Column<?> column = toParquetColumn(fieldRef);
-            org.apache.paimon.types.DataType type = fieldRef.type();
             if (column instanceof Operators.LongColumn) {
                 return FilterApi.notIn(
-                        (Operators.LongColumn) column, convertSets(literals, 
Long.class, type));
+                        (Operators.LongColumn) column, convertSets(literals, 
Long.class, fieldRef));
             } else if (column instanceof Operators.IntColumn) {
                 return FilterApi.notIn(
-                        (Operators.IntColumn) column, convertSets(literals, 
Integer.class, type));
+                        (Operators.IntColumn) column,
+                        convertSets(literals, Integer.class, fieldRef));
             } else if (column instanceof Operators.DoubleColumn) {
                 return FilterApi.notIn(
-                        (Operators.DoubleColumn) column, convertSets(literals, 
Double.class, type));
+                        (Operators.DoubleColumn) column,
+                        convertSets(literals, Double.class, fieldRef));
             } else if (column instanceof Operators.FloatColumn) {
                 return FilterApi.notIn(
-                        (Operators.FloatColumn) column, convertSets(literals, 
Float.class, type));
+                        (Operators.FloatColumn) column,
+                        convertSets(literals, Float.class, fieldRef));
             } else if (column instanceof Operators.BinaryColumn) {
                 return FilterApi.notIn(
-                        (Operators.BinaryColumn) column, convertSets(literals, 
Binary.class, type));
+                        (Operators.BinaryColumn) column,
+                        convertSets(literals, Binary.class, fieldRef));
             }
 
             throw new UnsupportedOperationException();
@@ -238,88 +257,185 @@ public class ParquetFilters {
         public FilterPredicate visitNonFieldLeaf(LeafPredicate predicate) {
             throw new UnsupportedOperationException();
         }
-    }
 
-    private static <T> Set<T> convertSets(
-            List<Object> values, Class<T> kclass, 
org.apache.paimon.types.DataType type) {
-        Set<T> converted = new HashSet<>();
-        for (Object value : values) {
-            Comparable<?> cmp = toParquetObject(value, type);
-            if (kclass.isInstance(cmp)) {
-                converted.add((T) cmp);
-            } else {
-                throw new UnsupportedOperationException();
+        private <T> Set<T> convertSets(List<Object> values, Class<T> kclass, 
FieldRef fieldRef) {
+            Set<T> converted = new HashSet<>();
+            for (Object value : values) {
+                Comparable<?> cmp = toParquetObject(value, fieldRef);
+                if (kclass.isInstance(cmp)) {
+                    converted.add((T) cmp);
+                } else {
+                    throw new UnsupportedOperationException();
+                }
             }
+            return converted;
         }
-        return converted;
-    }
 
-    private static int getTimestampPrecision(org.apache.paimon.types.DataType 
type) {
-        if (type instanceof TimestampType) {
-            return ((TimestampType) type).getPrecision();
-        } else if (type instanceof LocalZonedTimestampType) {
-            return ((LocalZonedTimestampType) type).getPrecision();
+        private Operators.Column<?> toParquetColumn(FieldRef fieldRef) {
+            return fieldRef.type()
+                    .accept(new ConvertToColumnTypeVisitor(fieldRef, 
fileSchema, caseSensitive));
         }
-        throw new IllegalArgumentException("Not a timestamp type: " + type);
-    }
 
-    private static Operators.Column<?> toParquetColumn(FieldRef fieldRef) {
-        return fieldRef.type().accept(new 
ConvertToColumnTypeVisitor(fieldRef.name()));
-    }
+        private Comparable<?> toParquetObject(Object value, FieldRef fieldRef) 
{
+            if (value == null) {
+                return null;
+            }
+
+            org.apache.paimon.types.DataType type = fieldRef.type();
+            if (type instanceof DecimalType) {
+                DecimalType decimalType = (DecimalType) fieldRef.type();
+                Decimal decimal = normalizeDecimal((Decimal) value, 
decimalType);
+                PrimitiveType primitiveType =
+                        decimalPrimitiveType(fieldRef, fileSchema, 
caseSensitive);
+                switch (primitiveType.getPrimitiveTypeName()) {
+                    case INT32:
+                        long intValue = toUnscaledLong(decimal);
+                        if (intValue < Integer.MIN_VALUE || intValue > 
Integer.MAX_VALUE) {
+                            throw new UnsupportedOperationException();
+                        }
+                        return (int) intValue;
+                    case INT64:
+                        return toUnscaledLong(decimal);
+                    case BINARY:
+                        return 
Binary.fromConstantByteArray(decimal.toUnscaledBytes());
+                    case FIXED_LEN_BYTE_ARRAY:
+                        return decimalToBinary(decimal, 
primitiveType.getTypeLength());
+                    default:
+                        throw new UnsupportedOperationException();
+                }
+            }
 
-    private static Comparable<?> toParquetObject(
-            Object value, org.apache.paimon.types.DataType type) {
-        if (value == null) {
-            return null;
+            if (value instanceof Number) {
+                if (value instanceof Byte) {
+                    return ((Byte) value).intValue();
+                } else if (value instanceof Short) {
+                    return ((Short) value).intValue();
+                }
+                return (Comparable<?>) value;
+            } else if (value instanceof String) {
+                return Binary.fromString((String) value);
+            } else if (value instanceof BinaryString) {
+                return Binary.fromString(value.toString());
+            } else if (value instanceof byte[]) {
+                return Binary.fromReusedByteArray((byte[]) value);
+            } else if (value instanceof Timestamp) {
+                Timestamp timestamp = (Timestamp) value;
+                int precision = getTimestampPrecision(type);
+                if (precision <= 3) {
+                    // milliseconds
+                    return timestamp.getMillisecond();
+                } else if (precision <= 6) {
+                    // microseconds
+                    return timestamp.toMicros();
+                }
+                // precision > 6 uses INT96, not supported
+                throw new UnsupportedOperationException();
+            }
+
+            throw new UnsupportedOperationException();
         }
 
-        if (value instanceof Number) {
-            if (value instanceof Byte) {
-                return ((Byte) value).intValue();
-            } else if (value instanceof Short) {
-                return ((Short) value).intValue();
+        private Decimal normalizeDecimal(Decimal decimal, DecimalType 
fieldType) {
+            try {
+                BigDecimal normalized =
+                        decimal.toBigDecimal()
+                                .setScale(fieldType.getScale(), 
RoundingMode.UNNECESSARY);
+                Decimal result =
+                        Decimal.fromBigDecimal(
+                                normalized, fieldType.getPrecision(), 
fieldType.getScale());
+                if (result == null) {
+                    throw new UnsupportedOperationException();
+                }
+                return result;
+            } catch (ArithmeticException e) {
+                throw new UnsupportedOperationException(e);
             }
-            return (Comparable<?>) value;
-        } else if (value instanceof String) {
-            return Binary.fromString((String) value);
-        } else if (value instanceof BinaryString) {
-            return Binary.fromString(value.toString());
-        } else if (value instanceof byte[]) {
-            return Binary.fromReusedByteArray((byte[]) value);
-        } else if (value instanceof Decimal) {
-            Decimal decimal = (Decimal) value;
-            int precision = decimal.precision();
-            if (ParquetSchemaConverter.is32BitDecimal(precision)) {
-                return (int) decimal.toUnscaledLong();
-            } else if (ParquetSchemaConverter.is64BitDecimal(precision)) {
+        }
+
+        private long toUnscaledLong(Decimal decimal) {
+            try {
                 return decimal.toUnscaledLong();
-            } else {
-                return Binary.fromConstantByteArray(decimal.toUnscaledBytes());
+            } catch (ArithmeticException e) {
+                throw new UnsupportedOperationException(e);
+            }
+        }
+
+        private Binary decimalToBinary(Decimal decimal, int numBytes) {
+            byte[] unscaledBytes = decimal.toUnscaledBytes();
+            if (unscaledBytes.length > numBytes) {
+                throw new UnsupportedOperationException();
             }
-        } else if (value instanceof Timestamp) {
-            Timestamp timestamp = (Timestamp) value;
-            int precision = getTimestampPrecision(type);
-            if (precision <= 3) {
-                // milliseconds
-                return timestamp.getMillisecond();
-            } else if (precision <= 6) {
-                // microseconds
-                return timestamp.toMicros();
+            if (unscaledBytes.length == numBytes) {
+                return Binary.fromConstantByteArray(unscaledBytes);
             }
-            // precision > 6 uses INT96, not supported
+
+            byte[] paddedBytes = new byte[numBytes];
+            Arrays.fill(paddedBytes, unscaledBytes[0] < 0 ? (byte) -1 : (byte) 
0);
+            System.arraycopy(
+                    unscaledBytes,
+                    0,
+                    paddedBytes,
+                    numBytes - unscaledBytes.length,
+                    unscaledBytes.length);
+            return Binary.fromConstantByteArray(paddedBytes);
+        }
+    }
+
+    private static PrimitiveType decimalPrimitiveType(
+            FieldRef fieldRef, MessageType fileSchema, boolean caseSensitive) {
+        Type matched = null;
+        // Paimon predicates currently reference top-level fields only. Nested 
field
+        // predicates are rejected before reaching the format reader.
+        for (Type field : fileSchema.getFields()) {
+            if (caseSensitive
+                    ? field.getName().equals(fieldRef.name())
+                    : field.getName().equalsIgnoreCase(fieldRef.name())) {
+                matched = field;
+                break;
+            }
+        }
+
+        if (matched == null || !matched.isPrimitive()) {
             throw new UnsupportedOperationException();
         }
 
-        throw new UnsupportedOperationException();
+        PrimitiveType primitiveType = matched.asPrimitiveType();
+        LogicalTypeAnnotation logicalType = 
primitiveType.getLogicalTypeAnnotation();
+        if (!(logicalType instanceof DecimalLogicalTypeAnnotation)) {
+            throw new UnsupportedOperationException();
+        }
+
+        DecimalLogicalTypeAnnotation decimalLogicalType =
+                (DecimalLogicalTypeAnnotation) logicalType;
+        if (decimalLogicalType.getScale() != ((DecimalType) 
fieldRef.type()).getScale()) {
+            throw new UnsupportedOperationException();
+        }
+        return primitiveType;
+    }
+
+    private static int getTimestampPrecision(org.apache.paimon.types.DataType 
type) {
+        if (type instanceof TimestampType) {
+            return ((TimestampType) type).getPrecision();
+        } else if (type instanceof LocalZonedTimestampType) {
+            return ((LocalZonedTimestampType) type).getPrecision();
+        }
+        throw new IllegalArgumentException("Not a timestamp type: " + type);
     }
 
     private static class ConvertToColumnTypeVisitor
             implements DataTypeVisitor<Operators.Column<?>> {
 
+        private final FieldRef fieldRef;
         private final String name;
+        private final MessageType fileSchema;
+        private final boolean caseSensitive;
 
-        public ConvertToColumnTypeVisitor(String name) {
-            this.name = name;
+        public ConvertToColumnTypeVisitor(
+                FieldRef fieldRef, MessageType fileSchema, boolean 
caseSensitive) {
+            this.fieldRef = fieldRef;
+            this.name = fieldRef.name();
+            this.fileSchema = fileSchema;
+            this.caseSensitive = caseSensitive;
         }
 
         @Override
@@ -389,13 +505,17 @@ public class ParquetFilters {
 
         @Override
         public Operators.Column<?> visit(DecimalType decimalType) {
-            int precision = decimalType.getPrecision();
-            if (ParquetSchemaConverter.is32BitDecimal(precision)) {
-                return FilterApi.intColumn(name);
-            } else if (ParquetSchemaConverter.is64BitDecimal(precision)) {
-                return FilterApi.longColumn(name);
-            } else {
-                return FilterApi.binaryColumn(name);
+            PrimitiveType primitiveType = decimalPrimitiveType(fieldRef, 
fileSchema, caseSensitive);
+            switch (primitiveType.getPrimitiveTypeName()) {
+                case INT32:
+                    return FilterApi.intColumn(fieldRef.name());
+                case INT64:
+                    return FilterApi.longColumn(fieldRef.name());
+                case BINARY:
+                case FIXED_LEN_BYTE_ARRAY:
+                    return FilterApi.binaryColumn(fieldRef.name());
+                default:
+                    throw new UnsupportedOperationException();
             }
         }
 
diff --git 
a/paimon-format/src/main/java/org/apache/parquet/hadoop/ParquetFileReader.java 
b/paimon-format/src/main/java/org/apache/parquet/hadoop/ParquetFileReader.java
index 6f3a20be59..9f56c21769 100644
--- 
a/paimon-format/src/main/java/org/apache/parquet/hadoop/ParquetFileReader.java
+++ 
b/paimon-format/src/main/java/org/apache/parquet/hadoop/ParquetFileReader.java
@@ -136,7 +136,7 @@ public class ParquetFileReader implements Closeable {
         return readFooter(file, options, f, /*closeStreamOnFailure*/ false);
     }
 
-    private static final ParquetMetadata readFooter(
+    public static final ParquetMetadata readFooter(
             InputFile file,
             ParquetReadOptions options,
             SeekableInputStream f,
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetCaseInsensitiveReadTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetCaseInsensitiveReadTest.java
index f8c22de58b..41933a3d29 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetCaseInsensitiveReadTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetCaseInsensitiveReadTest.java
@@ -33,7 +33,6 @@ import org.apache.hadoop.conf.Configuration;
 import org.apache.parquet.column.ParquetProperties;
 import org.apache.parquet.example.data.Group;
 import org.apache.parquet.example.data.simple.SimpleGroupFactory;
-import org.apache.parquet.filter2.compat.FilterCompat;
 import org.apache.parquet.hadoop.ParquetWriter;
 import org.apache.parquet.hadoop.example.ExampleParquetWriter;
 import org.apache.parquet.hadoop.util.HadoopOutputFile;
@@ -134,8 +133,7 @@ class ParquetCaseInsensitiveReadTest {
             throws Exception {
         Options options = new Options();
         options.set(CatalogOptions.CASE_SENSITIVE, caseSensitive);
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(options, readType, 1024, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(options, 
readType, 1024, null);
         LocalFileIO fileIO = new LocalFileIO();
         List<InternalRow> rows = new ArrayList<>();
         try (RecordReader<InternalRow> reader =
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetColumnVectorTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetColumnVectorTest.java
index ee20945d64..25e5edf392 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetColumnVectorTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetColumnVectorTest.java
@@ -38,7 +38,6 @@ import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.StringUtils;
 
-import org.apache.parquet.filter2.compat.FilterCompat;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -593,7 +592,7 @@ public class ParquetColumnVectorTest {
         writer.close();
 
         ParquetReaderFactory readerFactory =
-                new ParquetReaderFactory(new Options(), rowType, 1024, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), rowType, 1024, null);
 
         RecordReader<InternalRow> reader =
                 readerFactory.createReader(
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
index a10efa2cf2..acb6e6e5bd 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetFiltersTest.java
@@ -32,18 +32,37 @@ import org.apache.paimon.types.RowType;
 import org.apache.paimon.types.TimestampType;
 import org.apache.paimon.types.VarCharType;
 
+import org.apache.parquet.bytes.BytesInput;
+import org.apache.parquet.column.ColumnDescriptor;
+import org.apache.parquet.column.Encoding;
+import org.apache.parquet.column.EncodingStats;
+import org.apache.parquet.column.page.DictionaryPage;
+import org.apache.parquet.column.page.DictionaryPageReadStore;
 import org.apache.parquet.filter2.compat.FilterCompat;
 import org.apache.parquet.filter2.compat.FilterCompat.FilterPredicateCompat;
+import org.apache.parquet.filter2.dictionarylevel.DictionaryFilter;
 import org.apache.parquet.filter2.predicate.FilterApi;
 import org.apache.parquet.filter2.predicate.FilterPredicate;
 import org.apache.parquet.filter2.predicate.ParquetFilters;
+import org.apache.parquet.hadoop.metadata.ColumnChunkMetaData;
+import org.apache.parquet.hadoop.metadata.ColumnPath;
+import org.apache.parquet.hadoop.metadata.CompressionCodecName;
 import org.apache.parquet.io.api.Binary;
+import org.apache.parquet.schema.LogicalTypeAnnotation;
+import org.apache.parquet.schema.MessageType;
+import org.apache.parquet.schema.PrimitiveType;
+import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
+import org.apache.parquet.schema.Types;
 import org.junit.jupiter.api.Test;
 
 import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 import java.util.stream.Collectors;
+import java.util.stream.IntStream;
 import java.util.stream.LongStream;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -52,28 +71,29 @@ class ParquetFiltersTest {
 
     @Test
     public void testLong() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "long1", new 
BigIntType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, 
"long1", new BigIntType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
-        test(builder.isNull(0), "eq(long1, null)", true);
+        test(schema, builder.isNull(0), "eq(long1, null)", true);
 
-        test(builder.isNotNull(0), "noteq(long1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(long1, null)", true);
 
-        test(builder.lessThan(0, 5L), "lt(long1, 5)", true);
+        test(schema, builder.lessThan(0, 5L), "lt(long1, 5)", true);
 
-        test(builder.greaterThan(0, 5L), "gt(long1, 5)", true);
+        test(schema, builder.greaterThan(0, 5L), "gt(long1, 5)", true);
 
         test(
+                schema,
                 builder.in(0, Arrays.asList(1L, 2L, 3L)),
                 "or(eq(long1, 1), or(eq(long1, 2), eq(long1, 3)))",
                 true);
 
-        test(builder.between(0, 1L, 3L), "and(gteq(long1, 1), lteq(long1, 
3))", true);
+        test(schema, builder.between(0, 1L, 3L), "and(gteq(long1, 1), 
lteq(long1, 3))", true);
 
         test(
+                schema,
                 builder.notIn(0, Arrays.asList(1L, 2L, 3L)),
                 "and(and(noteq(long1, 1), noteq(long1, 2)), noteq(long1, 3))",
                 true);
@@ -81,20 +101,22 @@ class ParquetFiltersTest {
 
     @Test
     public void testString() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "string1", new 
VarCharType()))));
-        test(builder.isNull(0), "eq(string1, null)", true);
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(new DataField(0, "string1", 
new VarCharType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
+        test(schema, builder.isNull(0), "eq(string1, null)", true);
 
-        test(builder.isNotNull(0), "noteq(string1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(string1, null)", true);
 
         test(
+                schema,
                 builder.in(0, Arrays.asList("1", "2", "3")),
                 "or(eq(string1, Binary{\"1\"}), or(eq(string1, Binary{\"2\"}), 
eq(string1, Binary{\"3\"})))",
                 true);
         test(
+                schema,
                 builder.notIn(0, Arrays.asList("1", "2", "3")),
                 "and(and(noteq(string1, Binary{\"1\"}), noteq(string1, 
Binary{\"2\"})), noteq(string1, Binary{\"3\"}))",
                 true);
@@ -102,12 +124,12 @@ class ParquetFiltersTest {
 
     @Test
     public void testInFilterLong() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "col1", new 
BigIntType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "col1", 
new BigIntType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
         test(
+                schema,
                 builder.in(0, LongStream.range(1L, 
22L).boxed().collect(Collectors.toList())),
                 FilterApi.in(
                         FilterApi.longColumn("col1"),
@@ -115,6 +137,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(0, LongStream.range(1L, 
22L).boxed().collect(Collectors.toList())),
                 FilterApi.notIn(
                         FilterApi.longColumn("col1"),
@@ -124,12 +147,12 @@ class ParquetFiltersTest {
 
     @Test
     public void testInFilterDouble() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "col1", new 
DoubleType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "col1", 
new DoubleType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
         test(
+                schema,
                 builder.in(
                         0,
                         LongStream.range(1L, 22L)
@@ -145,6 +168,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(
                         0,
                         LongStream.range(1L, 22L)
@@ -162,12 +186,12 @@ class ParquetFiltersTest {
 
     @Test
     public void testInFilterString() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "col1", new 
VarCharType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "col1", 
new VarCharType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
         test(
+                schema,
                 builder.in(
                         0,
                         LongStream.range(1L, 22L)
@@ -183,6 +207,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(
                         0,
                         LongStream.range(1L, 22L)
@@ -200,14 +225,14 @@ class ParquetFiltersTest {
 
     @Test
     public void testIsNaNDouble() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "d1", new 
DoubleType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "d1", 
new DoubleType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
+        Predicate predicate = builder.isNaN(0);
         FilterCompat.Filter filter =
-                
ParquetFilters.convert(Collections.singletonList(builder.isNaN(0)));
+                ParquetFilters.convert(Collections.singletonList(predicate), 
schema, true);
         FilterPredicateCompat compat = (FilterPredicateCompat) filter;
         assertThat(compat.getFilterPredicate().toString())
                 .contains(
@@ -216,14 +241,14 @@ class ParquetFiltersTest {
 
     @Test
     public void testIsNaNFloat() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "f1", new 
FloatType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "f1", 
new FloatType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
+        Predicate predicate = builder.isNaN(0);
         FilterCompat.Filter filter =
-                
ParquetFilters.convert(Collections.singletonList(builder.isNaN(0)));
+                ParquetFilters.convert(Collections.singletonList(predicate), 
schema, true);
         FilterPredicateCompat compat = (FilterPredicateCompat) filter;
         assertThat(compat.getFilterPredicate().toString())
                 .contains(
@@ -232,13 +257,13 @@ class ParquetFiltersTest {
 
     @Test
     public void testInFilterFloat() {
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "col1", new 
FloatType()))));
+        RowType rowType =
+                new RowType(Collections.singletonList(new DataField(0, "col1", 
new FloatType())));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         test(
+                schema,
                 builder.in(
                         0,
                         LongStream.range(1L, 22L)
@@ -254,6 +279,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(
                         0,
                         LongStream.range(1L, 22L)
@@ -274,6 +300,8 @@ class ParquetFiltersTest {
         // precision <= 9 uses INT32
         int precision = 9;
         int scale = 2;
+        MessageType schema =
+                decimalSchema("decimal1", PrimitiveTypeName.INT32, 0, 
precision, scale);
         PredicateBuilder builder =
                 new PredicateBuilder(
                         new RowType(
@@ -286,14 +314,18 @@ class ParquetFiltersTest {
         Decimal value = Decimal.fromBigDecimal(new BigDecimal("123.45"), 
precision, scale);
         int expectedIntVal = (int) value.toUnscaledLong(); // 12345
 
-        test(builder.isNull(0), "eq(decimal1, null)", true);
-        test(builder.isNotNull(0), "noteq(decimal1, null)", true);
-        test(builder.equal(0, value), "eq(decimal1, " + expectedIntVal + ")", 
true);
-        test(builder.notEqual(0, value), "noteq(decimal1, " + expectedIntVal + 
")", true);
-        test(builder.lessThan(0, value), "lt(decimal1, " + expectedIntVal + 
")", true);
-        test(builder.lessOrEqual(0, value), "lteq(decimal1, " + expectedIntVal 
+ ")", true);
-        test(builder.greaterThan(0, value), "gt(decimal1, " + expectedIntVal + 
")", true);
-        test(builder.greaterOrEqual(0, value), "gteq(decimal1, " + 
expectedIntVal + ")", true);
+        test(schema, builder.isNull(0), "eq(decimal1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(decimal1, null)", true);
+        test(schema, builder.equal(0, value), "eq(decimal1, " + expectedIntVal 
+ ")", true);
+        test(schema, builder.notEqual(0, value), "noteq(decimal1, " + 
expectedIntVal + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(decimal1, " + 
expectedIntVal + ")", true);
+        test(schema, builder.lessOrEqual(0, value), "lteq(decimal1, " + 
expectedIntVal + ")", true);
+        test(schema, builder.greaterThan(0, value), "gt(decimal1, " + 
expectedIntVal + ")", true);
+        test(
+                schema,
+                builder.greaterOrEqual(0, value),
+                "gteq(decimal1, " + expectedIntVal + ")",
+                true);
     }
 
     @Test
@@ -301,6 +333,8 @@ class ParquetFiltersTest {
         // 9 < precision <= 18 uses INT64
         int precision = 18;
         int scale = 4;
+        MessageType schema =
+                decimalSchema("decimal1", PrimitiveTypeName.INT64, 0, 
precision, scale);
         PredicateBuilder builder =
                 new PredicateBuilder(
                         new RowType(
@@ -314,21 +348,115 @@ class ParquetFiltersTest {
                 Decimal.fromBigDecimal(new BigDecimal("12345678901234.5678"), 
precision, scale);
         long expectedLongVal = value.toUnscaledLong();
 
-        test(builder.isNull(0), "eq(decimal1, null)", true);
-        test(builder.isNotNull(0), "noteq(decimal1, null)", true);
-        test(builder.equal(0, value), "eq(decimal1, " + expectedLongVal + ")", 
true);
-        test(builder.notEqual(0, value), "noteq(decimal1, " + expectedLongVal 
+ ")", true);
-        test(builder.lessThan(0, value), "lt(decimal1, " + expectedLongVal + 
")", true);
-        test(builder.lessOrEqual(0, value), "lteq(decimal1, " + 
expectedLongVal + ")", true);
-        test(builder.greaterThan(0, value), "gt(decimal1, " + expectedLongVal 
+ ")", true);
-        test(builder.greaterOrEqual(0, value), "gteq(decimal1, " + 
expectedLongVal + ")", true);
+        test(schema, builder.isNull(0), "eq(decimal1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(decimal1, null)", true);
+        test(schema, builder.equal(0, value), "eq(decimal1, " + 
expectedLongVal + ")", true);
+        test(schema, builder.notEqual(0, value), "noteq(decimal1, " + 
expectedLongVal + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(decimal1, " + 
expectedLongVal + ")", true);
+        test(
+                schema,
+                builder.lessOrEqual(0, value),
+                "lteq(decimal1, " + expectedLongVal + ")",
+                true);
+        test(schema, builder.greaterThan(0, value), "gt(decimal1, " + 
expectedLongVal + ")", true);
+        test(
+                schema,
+                builder.greaterOrEqual(0, value),
+                "gteq(decimal1, " + expectedLongVal + ")",
+                true);
     }
 
     @Test
     public void testDecimalBinary() {
         // precision > 18 uses Binary
+        int fieldPrecision = 20;
+        int literalPrecision = 8;
+        int scale = 0;
+        MessageType schema =
+                decimalSchema(
+                        "decimal1",
+                        PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY,
+                        9,
+                        fieldPrecision,
+                        scale);
+        PredicateBuilder builder =
+                new PredicateBuilder(
+                        new RowType(
+                                Collections.singletonList(
+                                        new DataField(
+                                                0,
+                                                "decimal1",
+                                                new 
DecimalType(fieldPrecision, scale)))));
+
+        Decimal positive =
+                Decimal.fromBigDecimal(new BigDecimal("10000939"), 
literalPrecision, scale);
+        Binary expectedPositive =
+                Binary.fromConstantByteArray(
+                        new byte[] {0, 0, 0, 0, 0, 0, (byte) 0x98, (byte) 
0x9A, 0x2B});
+        Decimal negative =
+                Decimal.fromBigDecimal(new BigDecimal("-10000939"), 
literalPrecision, scale);
+        Binary expectedNegative =
+                Binary.fromConstantByteArray(
+                        new byte[] {
+                            (byte) 0xFF,
+                            (byte) 0xFF,
+                            (byte) 0xFF,
+                            (byte) 0xFF,
+                            (byte) 0xFF,
+                            (byte) 0xFF,
+                            0x67,
+                            0x65,
+                            (byte) 0xD5
+                        });
+
+        test(schema, builder.isNull(0), "eq(decimal1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(decimal1, null)", true);
+        test(
+                schema,
+                builder.equal(0, positive),
+                FilterApi.eq(FilterApi.binaryColumn("decimal1"), 
expectedPositive),
+                true);
+        test(
+                schema,
+                builder.notEqual(0, positive),
+                FilterApi.notEq(FilterApi.binaryColumn("decimal1"), 
expectedPositive),
+                true);
+        test(
+                schema,
+                builder.lessThan(0, positive),
+                FilterApi.lt(FilterApi.binaryColumn("decimal1"), 
expectedPositive),
+                true);
+        test(
+                schema,
+                builder.greaterThan(0, positive),
+                FilterApi.gt(FilterApi.binaryColumn("decimal1"), 
expectedPositive),
+                true);
+        test(
+                schema,
+                builder.equal(0, negative),
+                FilterApi.eq(FilterApi.binaryColumn("decimal1"), 
expectedNegative),
+                true);
+
+        Decimal fullWidth =
+                Decimal.fromBigDecimal(
+                        new BigDecimal("99999999999999999999"), 
fieldPrecision, scale);
+        assertThat(fullWidth.toUnscaledBytes()).hasSize(9);
+        test(
+                schema,
+                builder.equal(0, fullWidth),
+                FilterApi.eq(
+                        FilterApi.binaryColumn("decimal1"),
+                        
Binary.fromConstantByteArray(fullWidth.toUnscaledBytes())),
+                true);
+    }
+
+    @Test
+    public void testDecimalBinaryMaxPrecision() {
         int precision = 38;
         int scale = 10;
+        MessageType schema =
+                decimalSchema(
+                        "decimal1", PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY, 
16, precision, scale);
         PredicateBuilder builder =
                 new PredicateBuilder(
                         new RowType(
@@ -337,29 +465,284 @@ class ParquetFiltersTest {
                                                 0,
                                                 "decimal1",
                                                 new DecimalType(precision, 
scale)))));
-
         Decimal value =
                 Decimal.fromBigDecimal(
                         new BigDecimal("12345678901234567890.1234567890"), 
precision, scale);
-        Binary expectedBinary = 
Binary.fromConstantByteArray(value.toUnscaledBytes());
+        Binary expected =
+                Binary.fromConstantByteArray(
+                        new byte[] {
+                            0,
+                            0,
+                            0,
+                            1,
+                            (byte) 0x8E,
+                            (byte) 0xE9,
+                            0x0F,
+                            (byte) 0xF6,
+                            (byte) 0xC3,
+                            0x73,
+                            (byte) 0xE0,
+                            (byte) 0xEE,
+                            0x4E,
+                            0x3F,
+                            0x0A,
+                            (byte) 0xD2
+                        });
+
+        test(
+                schema,
+                builder.equal(0, value),
+                FilterApi.eq(FilterApi.binaryColumn("decimal1"), expected),
+                true);
+    }
+
+    @Test
+    public void testInFilterDecimalBinary() {
+        int fieldPrecision = 20;
+        int scale = 0;
+        MessageType schema =
+                decimalSchema(
+                        "decimal1",
+                        PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY,
+                        9,
+                        fieldPrecision,
+                        scale);
+        PredicateBuilder builder =
+                new PredicateBuilder(
+                        new RowType(
+                                Collections.singletonList(
+                                        new DataField(
+                                                0,
+                                                "decimal1",
+                                                new 
DecimalType(fieldPrecision, scale)))));
+
+        List<Object> literals =
+                IntStream.rangeClosed(1, 21)
+                        .mapToObj(
+                                value ->
+                                        (Object)
+                                                Decimal.fromBigDecimal(
+                                                        
BigDecimal.valueOf(value),
+                                                        fieldPrecision,
+                                                        scale))
+                        .collect(Collectors.toList());
+        Set<Binary> expected =
+                IntStream.rangeClosed(1, 21)
+                        .mapToObj(
+                                value -> {
+                                    byte[] bytes = new byte[9];
+                                    bytes[8] = (byte) value;
+                                    return Binary.fromConstantByteArray(bytes);
+                                })
+                        .collect(Collectors.toSet());
+
+        test(
+                schema,
+                builder.in(0, literals),
+                FilterApi.in(FilterApi.binaryColumn("decimal1"), expected),
+                true);
+        test(
+                schema,
+                builder.notIn(0, literals),
+                FilterApi.notIn(FilterApi.binaryColumn("decimal1"), expected),
+                true);
+    }
+
+    @Test
+    public void testDecimalDictionaryFilter() {
+        int fieldPrecision = 20;
+        int scale = 0;
+        PredicateBuilder builder =
+                new PredicateBuilder(
+                        new RowType(
+                                Collections.singletonList(
+                                        new DataField(
+                                                0,
+                                                "decimal1",
+                                                new 
DecimalType(fieldPrecision, scale)))));
+        Decimal positive =
+                Decimal.fromBigDecimal(new BigDecimal("10000939"), 
fieldPrecision, scale);
+        Decimal negative =
+                Decimal.fromBigDecimal(new BigDecimal("-10000939"), 
fieldPrecision, scale);
+        Decimal missing = Decimal.fromBigDecimal(new BigDecimal("10000940"), 
fieldPrecision, scale);
+
+        byte[] positiveBytes = new byte[] {0, 0, 0, 0, 0, 0, (byte) 0x98, 
(byte) 0x9A, 0x2B};
+        byte[] negativeBytes =
+                new byte[] {
+                    (byte) 0xFF,
+                    (byte) 0xFF,
+                    (byte) 0xFF,
+                    (byte) 0xFF,
+                    (byte) 0xFF,
+                    (byte) 0xFF,
+                    0x67,
+                    0x65,
+                    (byte) 0xD5
+                };
+        DictionaryPage dictionaryPage =
+                new DictionaryPage(
+                        BytesInput.concat(
+                                BytesInput.from(positiveBytes), 
BytesInput.from(negativeBytes)),
+                        2,
+                        Encoding.PLAIN);
+        DictionaryPageReadStore dictionaries =
+                new DictionaryPageReadStore() {
+                    @Override
+                    public DictionaryPage readDictionaryPage(ColumnDescriptor 
descriptor) {
+                        return dictionaryPage;
+                    }
+
+                    @Override
+                    public void close() {}
+                };
+
+        PrimitiveType primitiveType =
+                Types.required(PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY)
+                        .length(9)
+                        .as(LogicalTypeAnnotation.decimalType(scale, 
fieldPrecision))
+                        .named("decimal1");
+        MessageType schema =
+                new MessageType("paimon_schema", 
Collections.singletonList(primitiveType));
+        EncodingStats encodingStats =
+                new EncodingStats.Builder()
+                        .addDictEncoding(Encoding.PLAIN)
+                        .addDataEncoding(Encoding.RLE_DICTIONARY)
+                        .build();
+        Set<Encoding> encodings =
+                new HashSet<>(Arrays.asList(Encoding.PLAIN, 
Encoding.RLE_DICTIONARY, Encoding.RLE));
+        ColumnChunkMetaData metadata =
+                ColumnChunkMetaData.get(
+                        ColumnPath.get("decimal1"),
+                        primitiveType,
+                        CompressionCodecName.UNCOMPRESSED,
+                        encodingStats,
+                        encodings,
+                        null,
+                        0,
+                        0,
+                        2,
+                        0,
+                        0);
+
+        assertThat(
+                        DictionaryFilter.canDrop(
+                                convert(schema, builder.equal(0, positive)),
+                                Collections.singletonList(metadata),
+                                dictionaries))
+                .isFalse();
+        assertThat(
+                        DictionaryFilter.canDrop(
+                                convert(schema, builder.equal(0, negative)),
+                                Collections.singletonList(metadata),
+                                dictionaries))
+                .isFalse();
+        assertThat(
+                        DictionaryFilter.canDrop(
+                                convert(schema, builder.equal(0, missing)),
+                                Collections.singletonList(metadata),
+                                dictionaries))
+                .isTrue();
+    }
+
+    @Test
+    public void testDecimalLiteralOutsideFieldDomain() {
+        int fieldPrecision = 9;
+        int scale = 0;
+        PredicateBuilder builder = decimalPredicateBuilder(fieldPrecision, 
scale);
+        Decimal value = Decimal.fromBigDecimal(new BigDecimal("4294967297"), 
10, scale);
+        MessageType schema =
+                decimalSchema("decimal1", PrimitiveTypeName.INT32, 0, 
fieldPrecision, scale);
+
+        test(schema, builder.equal(0, value), "", false);
+    }
+
+    @Test
+    public void testDecimalLiteralWiderThanPhysicalWidth() {
+        int fieldPrecision = 20;
+        int scale = 0;
+        PredicateBuilder builder = decimalPredicateBuilder(fieldPrecision, 
scale);
+        Decimal value = Decimal.fromBigDecimal(new BigDecimal("3000000000"), 
10, scale);
+        MessageType schema =
+                decimalSchema("decimal1", 
PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY, 4, 9, scale);
+
+        test(schema, builder.equal(0, value), "", false);
+    }
+
+    @Test
+    public void testDecimalLiteralWiderThanFieldDomain() {
+        int fieldPrecision = 20;
+        int scale = 0;
+        PredicateBuilder builder = decimalPredicateBuilder(fieldPrecision, 
scale);
+        Decimal value =
+                Decimal.fromBigDecimal(
+                        new 
BigDecimal("99999999999999999999999999999999999999"), 38, scale);
+        MessageType schema =
+                decimalSchema(
+                        "decimal1",
+                        PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY,
+                        9,
+                        fieldPrecision,
+                        scale);
+
+        test(schema, builder.equal(0, value), "", false);
+    }
+
+    @Test
+    public void testDecimalScaleNormalization() {
+        int fieldPrecision = 20;
+        int fieldScale = 2;
+        PredicateBuilder builder = decimalPredicateBuilder(fieldPrecision, 
fieldScale);
+        MessageType schema =
+                decimalSchema(
+                        "decimal1",
+                        PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY,
+                        9,
+                        fieldPrecision,
+                        fieldScale);
+        Decimal exact = Decimal.fromBigDecimal(new BigDecimal("100"), 3, 0);
+        Decimal inexact = Decimal.fromBigDecimal(new BigDecimal("100.001"), 6, 
3);
+        Binary expected =
+                Binary.fromConstantByteArray(new byte[] {0, 0, 0, 0, 0, 0, 0, 
0x27, 0x10});
 
-        test(builder.isNull(0), "eq(decimal1, null)", true);
-        test(builder.isNotNull(0), "noteq(decimal1, null)", true);
         test(
+                schema,
+                builder.equal(0, exact),
+                FilterApi.eq(FilterApi.binaryColumn("decimal1"), expected),
+                true);
+        test(schema, builder.equal(0, inexact), "", false);
+    }
+
+    @Test
+    public void testDecimalPhysicalTypes() {
+        int precision = 9;
+        int scale = 2;
+        PredicateBuilder builder = decimalPredicateBuilder(precision, scale);
+        Decimal value = Decimal.fromBigDecimal(new BigDecimal("12.34"), 
precision, scale);
+
+        test(
+                decimalSchema("decimal1", PrimitiveTypeName.INT32, 0, 
precision, scale),
                 builder.equal(0, value),
-                FilterApi.eq(FilterApi.binaryColumn("decimal1"), 
expectedBinary),
+                FilterApi.eq(FilterApi.intColumn("decimal1"), 1234),
                 true);
         test(
-                builder.notEqual(0, value),
-                FilterApi.notEq(FilterApi.binaryColumn("decimal1"), 
expectedBinary),
+                decimalSchema("decimal1", PrimitiveTypeName.INT64, 0, 
precision, scale),
+                builder.equal(0, value),
+                FilterApi.eq(FilterApi.longColumn("decimal1"), 1234L),
                 true);
         test(
-                builder.lessThan(0, value),
-                FilterApi.lt(FilterApi.binaryColumn("decimal1"), 
expectedBinary),
+                decimalSchema("decimal1", PrimitiveTypeName.BINARY, 0, 
precision, scale),
+                builder.equal(0, value),
+                FilterApi.eq(
+                        FilterApi.binaryColumn("decimal1"),
+                        Binary.fromConstantByteArray(new byte[] {0x04, (byte) 
0xD2})),
                 true);
         test(
-                builder.greaterThan(0, value),
-                FilterApi.gt(FilterApi.binaryColumn("decimal1"), 
expectedBinary),
+                decimalSchema(
+                        "decimal1", PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY, 4, 
precision, scale),
+                builder.equal(0, value),
+                FilterApi.eq(
+                        FilterApi.binaryColumn("decimal1"),
+                        Binary.fromConstantByteArray(new byte[] {0, 0, 0x04, 
(byte) 0xD2})),
                 true);
     }
 
@@ -367,6 +750,8 @@ class ParquetFiltersTest {
     public void testInFilterDecimal32Bit() {
         int precision = 9;
         int scale = 2;
+        MessageType schema =
+                decimalSchema("decimal1", PrimitiveTypeName.INT32, 0, 
precision, scale);
         PredicateBuilder builder =
                 new PredicateBuilder(
                         new RowType(
@@ -381,6 +766,7 @@ class ParquetFiltersTest {
         Decimal v3 = Decimal.fromBigDecimal(new BigDecimal("300.00"), 
precision, scale);
 
         test(
+                schema,
                 builder.in(0, Arrays.asList(v1, v2, v3)),
                 "or(eq(decimal1, "
                         + (int) v1.toUnscaledLong()
@@ -392,6 +778,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(0, Arrays.asList(v1, v2, v3)),
                 "and(and(noteq(decimal1, "
                         + (int) v1.toUnscaledLong()
@@ -407,6 +794,8 @@ class ParquetFiltersTest {
     public void testInFilterDecimal64Bit() {
         int precision = 18;
         int scale = 4;
+        MessageType schema =
+                decimalSchema("decimal1", PrimitiveTypeName.INT64, 0, 
precision, scale);
         PredicateBuilder builder =
                 new PredicateBuilder(
                         new RowType(
@@ -421,6 +810,7 @@ class ParquetFiltersTest {
         Decimal v3 = Decimal.fromBigDecimal(new 
BigDecimal("30000000000.0000"), precision, scale);
 
         test(
+                schema,
                 builder.in(0, Arrays.asList(v1, v2, v3)),
                 "or(eq(decimal1, "
                         + v1.toUnscaledLong()
@@ -432,6 +822,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(0, Arrays.asList(v1, v2, v3)),
                 "and(and(noteq(decimal1, "
                         + v1.toUnscaledLong()
@@ -447,110 +838,110 @@ class ParquetFiltersTest {
     public void testTimestampMillis() {
         // precision <= 3 uses milliseconds (INT64)
         int precision = 3;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "ts1", new 
TimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
TimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp value = Timestamp.fromEpochMillis(1704067200000L); // 
2024-01-01 00:00:00
         long expectedMillis = value.getMillisecond();
 
-        test(builder.isNull(0), "eq(ts1, null)", true);
-        test(builder.isNotNull(0), "noteq(ts1, null)", true);
-        test(builder.equal(0, value), "eq(ts1, " + expectedMillis + ")", true);
-        test(builder.notEqual(0, value), "noteq(ts1, " + expectedMillis + ")", 
true);
-        test(builder.lessThan(0, value), "lt(ts1, " + expectedMillis + ")", 
true);
-        test(builder.lessOrEqual(0, value), "lteq(ts1, " + expectedMillis + 
")", true);
-        test(builder.greaterThan(0, value), "gt(ts1, " + expectedMillis + ")", 
true);
-        test(builder.greaterOrEqual(0, value), "gteq(ts1, " + expectedMillis + 
")", true);
+        test(schema, builder.isNull(0), "eq(ts1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(ts1, null)", true);
+        test(schema, builder.equal(0, value), "eq(ts1, " + expectedMillis + 
")", true);
+        test(schema, builder.notEqual(0, value), "noteq(ts1, " + 
expectedMillis + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(ts1, " + expectedMillis + 
")", true);
+        test(schema, builder.lessOrEqual(0, value), "lteq(ts1, " + 
expectedMillis + ")", true);
+        test(schema, builder.greaterThan(0, value), "gt(ts1, " + 
expectedMillis + ")", true);
+        test(schema, builder.greaterOrEqual(0, value), "gteq(ts1, " + 
expectedMillis + ")", true);
     }
 
     @Test
     public void testTimestampMicros() {
         // 3 < precision <= 6 uses microseconds (INT64)
         int precision = 6;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "ts1", new 
TimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
TimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp value = Timestamp.fromEpochMillis(1704067200123L, 456000); 
// with nanos
         long expectedMicros = value.getMillisecond() * 1000 + 
value.getNanoOfMillisecond() / 1000;
 
-        test(builder.isNull(0), "eq(ts1, null)", true);
-        test(builder.isNotNull(0), "noteq(ts1, null)", true);
-        test(builder.equal(0, value), "eq(ts1, " + expectedMicros + ")", true);
-        test(builder.notEqual(0, value), "noteq(ts1, " + expectedMicros + ")", 
true);
-        test(builder.lessThan(0, value), "lt(ts1, " + expectedMicros + ")", 
true);
-        test(builder.lessOrEqual(0, value), "lteq(ts1, " + expectedMicros + 
")", true);
-        test(builder.greaterThan(0, value), "gt(ts1, " + expectedMicros + ")", 
true);
-        test(builder.greaterOrEqual(0, value), "gteq(ts1, " + expectedMicros + 
")", true);
+        test(schema, builder.isNull(0), "eq(ts1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(ts1, null)", true);
+        test(schema, builder.equal(0, value), "eq(ts1, " + expectedMicros + 
")", true);
+        test(schema, builder.notEqual(0, value), "noteq(ts1, " + 
expectedMicros + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(ts1, " + expectedMicros + 
")", true);
+        test(schema, builder.lessOrEqual(0, value), "lteq(ts1, " + 
expectedMicros + ")", true);
+        test(schema, builder.greaterThan(0, value), "gt(ts1, " + 
expectedMicros + ")", true);
+        test(schema, builder.greaterOrEqual(0, value), "gteq(ts1, " + 
expectedMicros + ")", true);
     }
 
     @Test
     public void testLocalZonedTimestampMillis() {
         // precision <= 3 uses milliseconds (INT64)
         int precision = 3;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(
-                                                0,
-                                                "ts1",
-                                                new 
LocalZonedTimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
LocalZonedTimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp value = Timestamp.fromEpochMillis(1704067200000L);
         long expectedMillis = value.getMillisecond();
 
-        test(builder.isNull(0), "eq(ts1, null)", true);
-        test(builder.isNotNull(0), "noteq(ts1, null)", true);
-        test(builder.equal(0, value), "eq(ts1, " + expectedMillis + ")", true);
-        test(builder.notEqual(0, value), "noteq(ts1, " + expectedMillis + ")", 
true);
-        test(builder.lessThan(0, value), "lt(ts1, " + expectedMillis + ")", 
true);
-        test(builder.greaterThan(0, value), "gt(ts1, " + expectedMillis + ")", 
true);
+        test(schema, builder.isNull(0), "eq(ts1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(ts1, null)", true);
+        test(schema, builder.equal(0, value), "eq(ts1, " + expectedMillis + 
")", true);
+        test(schema, builder.notEqual(0, value), "noteq(ts1, " + 
expectedMillis + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(ts1, " + expectedMillis + 
")", true);
+        test(schema, builder.greaterThan(0, value), "gt(ts1, " + 
expectedMillis + ")", true);
     }
 
     @Test
     public void testLocalZonedTimestampMicros() {
         // 3 < precision <= 6 uses microseconds (INT64)
         int precision = 6;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(
-                                                0,
-                                                "ts1",
-                                                new 
LocalZonedTimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
LocalZonedTimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp value = Timestamp.fromEpochMillis(1704067200123L, 456000);
         long expectedMicros = value.getMillisecond() * 1000 + 
value.getNanoOfMillisecond() / 1000;
 
-        test(builder.isNull(0), "eq(ts1, null)", true);
-        test(builder.isNotNull(0), "noteq(ts1, null)", true);
-        test(builder.equal(0, value), "eq(ts1, " + expectedMicros + ")", true);
-        test(builder.notEqual(0, value), "noteq(ts1, " + expectedMicros + ")", 
true);
-        test(builder.lessThan(0, value), "lt(ts1, " + expectedMicros + ")", 
true);
-        test(builder.greaterThan(0, value), "gt(ts1, " + expectedMicros + ")", 
true);
+        test(schema, builder.isNull(0), "eq(ts1, null)", true);
+        test(schema, builder.isNotNull(0), "noteq(ts1, null)", true);
+        test(schema, builder.equal(0, value), "eq(ts1, " + expectedMicros + 
")", true);
+        test(schema, builder.notEqual(0, value), "noteq(ts1, " + 
expectedMicros + ")", true);
+        test(schema, builder.lessThan(0, value), "lt(ts1, " + expectedMicros + 
")", true);
+        test(schema, builder.greaterThan(0, value), "gt(ts1, " + 
expectedMicros + ")", true);
     }
 
     @Test
     public void testInFilterTimestampMillis() {
         int precision = 3;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "ts1", new 
TimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
TimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp v1 = Timestamp.fromEpochMillis(1704067200000L);
         Timestamp v2 = Timestamp.fromEpochMillis(1704153600000L);
         Timestamp v3 = Timestamp.fromEpochMillis(1704240000000L);
 
         test(
+                schema,
                 builder.in(0, Arrays.asList(v1, v2, v3)),
                 "or(eq(ts1, "
                         + v1.getMillisecond()
@@ -562,6 +953,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(0, Arrays.asList(v1, v2, v3)),
                 "and(and(noteq(ts1, "
                         + v1.getMillisecond()
@@ -576,11 +968,12 @@ class ParquetFiltersTest {
     @Test
     public void testInFilterTimestampMicros() {
         int precision = 6;
-        PredicateBuilder builder =
-                new PredicateBuilder(
-                        new RowType(
-                                Collections.singletonList(
-                                        new DataField(0, "ts1", new 
TimestampType(precision)))));
+        RowType rowType =
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "ts1", new 
TimestampType(precision))));
+        MessageType schema = 
ParquetSchemaConverter.convertToParquetMessageType(rowType);
+        PredicateBuilder builder = new PredicateBuilder(rowType);
 
         Timestamp v1 = Timestamp.fromEpochMillis(1704067200000L, 123000);
         Timestamp v2 = Timestamp.fromEpochMillis(1704153600000L, 456000);
@@ -591,6 +984,7 @@ class ParquetFiltersTest {
         long micros3 = v3.getMillisecond() * 1000 + v3.getNanoOfMillisecond() 
/ 1000;
 
         test(
+                schema,
                 builder.in(0, Arrays.asList(v1, v2, v3)),
                 "or(eq(ts1, "
                         + micros1
@@ -602,6 +996,7 @@ class ParquetFiltersTest {
                 true);
 
         test(
+                schema,
                 builder.notIn(0, Arrays.asList(v1, v2, v3)),
                 "and(and(noteq(ts1, "
                         + micros1
@@ -613,8 +1008,13 @@ class ParquetFiltersTest {
                 true);
     }
 
-    private void test(Predicate predicate, FilterPredicate parquetPredicate, 
boolean canPushDown) {
-        FilterCompat.Filter filter = 
ParquetFilters.convert(PredicateBuilder.splitAnd(predicate));
+    private void test(
+            MessageType schema,
+            Predicate predicate,
+            FilterPredicate parquetPredicate,
+            boolean canPushDown) {
+        FilterCompat.Filter filter =
+                ParquetFilters.convert(PredicateBuilder.splitAnd(predicate), 
schema, true);
         if (canPushDown) {
             FilterPredicateCompat compat = (FilterPredicateCompat) filter;
             
assertThat(compat.getFilterPredicate()).isEqualTo(parquetPredicate);
@@ -623,8 +1023,16 @@ class ParquetFiltersTest {
         }
     }
 
-    private void test(Predicate predicate, String expected, boolean 
canPushDown) {
-        FilterCompat.Filter filter = 
ParquetFilters.convert(PredicateBuilder.splitAnd(predicate));
+    private FilterPredicate convert(MessageType schema, Predicate predicate) {
+        FilterCompat.Filter filter =
+                ParquetFilters.convert(PredicateBuilder.splitAnd(predicate), 
schema, true);
+        return ((FilterPredicateCompat) filter).getFilterPredicate();
+    }
+
+    private void test(
+            MessageType schema, Predicate predicate, String expected, boolean 
canPushDown) {
+        FilterCompat.Filter filter =
+                ParquetFilters.convert(PredicateBuilder.splitAnd(predicate), 
schema, true);
         if (canPushDown) {
             FilterPredicateCompat compat = (FilterPredicateCompat) filter;
             
assertThat(compat.getFilterPredicate().toString()).isEqualTo(expected);
@@ -632,4 +1040,26 @@ class ParquetFiltersTest {
             assertThat(filter).isEqualTo(FilterCompat.NOOP);
         }
     }
+
+    private static PredicateBuilder decimalPredicateBuilder(int precision, int 
scale) {
+        return new PredicateBuilder(
+                new RowType(
+                        Collections.singletonList(
+                                new DataField(0, "decimal1", new 
DecimalType(precision, scale)))));
+    }
+
+    private static MessageType decimalSchema(
+            String fieldName,
+            PrimitiveTypeName physicalType,
+            int fixedLength,
+            int precision,
+            int scale) {
+        Types.PrimitiveBuilder<PrimitiveType> builder = 
Types.required(physicalType);
+        if (physicalType == PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY) {
+            builder.length(fixedLength);
+        }
+        PrimitiveType type =
+                builder.as(LogicalTypeAnnotation.decimalType(scale, 
precision)).named(fieldName);
+        return new MessageType("paimon_schema", 
Collections.singletonList(type));
+    }
 }
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetReadWriteTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetReadWriteTest.java
index ef18dd0c29..f585a0cfdd 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetReadWriteTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetReadWriteTest.java
@@ -60,8 +60,6 @@ import org.apache.paimon.types.VarCharType;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.parquet.example.data.Group;
 import org.apache.parquet.example.data.simple.SimpleGroupFactory;
-import org.apache.parquet.filter2.compat.FilterCompat;
-import org.apache.parquet.filter2.predicate.ParquetFilters;
 import org.apache.parquet.hadoop.ParquetFileWriter;
 import org.apache.parquet.hadoop.ParquetWriter;
 import org.apache.parquet.hadoop.example.ExampleParquetWriter;
@@ -309,7 +307,7 @@ public class ParquetReadWriteTest {
                                 .fields(fieldTypes, new String[] {"f7", "f2", 
"f4"})
                                 .build(),
                         500,
-                        FilterCompat.NOOP);
+                        null);
 
         AtomicInteger cnt = new AtomicInteger(0);
         RecordReader<InternalRow> reader =
@@ -353,7 +351,7 @@ public class ParquetReadWriteTest {
                                 .fields(fieldTypes, new String[] {"f7", "f2", 
"f4", "f99"})
                                 .build(),
                         500,
-                        FilterCompat.NOOP);
+                        null);
 
         AtomicInteger cnt = new AtomicInteger(0);
         RecordReader<InternalRow> reader =
@@ -392,7 +390,7 @@ public class ParquetReadWriteTest {
                         new Options(),
                         RowType.builder().fields(fieldTypes, new String[] 
{"f7"}).build(),
                         batchSize,
-                        FilterCompat.NOOP);
+                        null);
 
         AtomicInteger cnt = new AtomicInteger(0);
         try (RecordReader<InternalRow> reader =
@@ -434,15 +432,12 @@ public class ParquetReadWriteTest {
                 new PredicateBuilder(
                         new RowType(
                                 Collections.singletonList(new DataField(0, 
"f4", new IntType()))));
-        FilterCompat.Filter filter =
-                ParquetFilters.convert(
-                        PredicateBuilder.splitAnd(builder.greaterThan(0, 
randomStart)));
         ParquetReaderFactory format =
                 new ParquetReaderFactory(
                         new Options(),
                         RowType.builder().fields(fieldTypes, new String[] 
{"f4"}).build(),
                         batchSize,
-                        filter);
+                        PredicateBuilder.splitAnd(builder.greaterThan(0, 
randomStart)));
 
         AtomicBoolean isFirst = new AtomicBoolean(true);
         try (RecordReader<InternalRow> reader =
@@ -480,8 +475,7 @@ public class ParquetReadWriteTest {
             throw new RuntimeException("Unknown writer type.");
         }
         ParquetReaderFactory format =
-                new ParquetReaderFactory(
-                        new Options(), NESTED_ARRAY_MAP_TYPE, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), NESTED_ARRAY_MAP_TYPE, 
500, null);
         RecordReader<InternalRow> reader =
                 format.createReader(
                         new FormatReaderContext(
@@ -498,8 +492,14 @@ public class ParquetReadWriteTest {
         int number = new Random().nextInt(1000) + 100;
         Path path = createDecimalFile(number, folder, 10);
 
+        PredicateBuilder builder = new PredicateBuilder(DECIMAL_TYPE);
+        Decimal filterValue = Decimal.fromBigDecimal(new 
BigDecimal("1234567.67"), 9, 2);
         ParquetReaderFactory format =
-                new ParquetReaderFactory(new Options(), DECIMAL_TYPE, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(
+                        new Options(),
+                        DECIMAL_TYPE,
+                        500,
+                        Collections.singletonList(builder.equal(2, 
filterValue)));
         RecordReader<InternalRow> reader =
                 format.createReader(
                         new FormatReaderContext(
@@ -507,6 +507,7 @@ public class ParquetReadWriteTest {
         List<InternalRow> results = new ArrayList<>(number);
         InternalRowSerializer internalRowSerializer = new 
InternalRowSerializer(DECIMAL_TYPE);
         reader.forEachRemaining(row -> 
results.add(internalRowSerializer.copy(row)));
+        assertThat(results).hasSize(number);
 
         BigDecimal decimalValue0 = new BigDecimal("123.67");
         BigDecimal decimalValue1 = new BigDecimal("12345.67");
@@ -659,7 +660,7 @@ public class ParquetReadWriteTest {
                         .build();
 
         ParquetReaderFactory format =
-                new ParquetReaderFactory(new Options(), paimonRowType, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), paimonRowType, 500, 
null);
 
         RecordReader<InternalRow> reader =
                 format.createReader(
@@ -723,7 +724,7 @@ public class ParquetReadWriteTest {
                         .fields(new TimestampType(9), new ArrayType(new 
TimestampType(9)))
                         .build();
         ParquetReaderFactory format =
-                new ParquetReaderFactory(new Options(), paimonRowType, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), paimonRowType, 500, 
null);
         AtomicInteger count = new AtomicInteger(0);
         try (RecordReader<InternalRow> reader =
                 format.createReader(
@@ -755,8 +756,7 @@ public class ParquetReadWriteTest {
                         GenericRow.of(2, BinaryVector.fromPrimitiveArray(new 
float[] {4, 5, 6})));
 
         Path path = createTempParquetFileByPaimon(folder, rows, 1024, rowType);
-        ParquetReaderFactory format =
-                new ParquetReaderFactory(new Options(), rowType, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory format = new ParquetReaderFactory(new Options(), 
rowType, 500, null);
 
         RecordReader<InternalRow> reader =
                 format.createReader(
@@ -826,8 +826,7 @@ public class ParquetReadWriteTest {
     }
 
     private int testReadingFile(List<Integer> expected, Path path) throws 
IOException {
-        ParquetReaderFactory format =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory format = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         RecordReader<InternalRow> reader =
                 format.createReader(
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetSchemaCacheTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetSchemaCacheTest.java
index 146391021a..5a787cff90 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetSchemaCacheTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/ParquetSchemaCacheTest.java
@@ -30,7 +30,6 @@ import org.apache.paimon.reader.RecordReader;
 import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
 
-import org.apache.parquet.filter2.compat.FilterCompat;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -61,8 +60,7 @@ public class ParquetSchemaCacheTest {
 
     @Test
     void testCacheIsEmptyBeforeFirstRead() throws Exception {
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         assertThat(getSchemaCacheSize(factory)).isEqualTo(0);
     }
@@ -71,8 +69,7 @@ public class ParquetSchemaCacheTest {
     void testCacheIsPopulatedAfterFirstRead() throws Exception {
         Path path = writeSingleFile();
 
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         readAll(factory, path);
 
@@ -89,8 +86,7 @@ public class ParquetSchemaCacheTest {
         Path path2 = writeSingleFile();
         Path path3 = writeSingleFile();
 
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         readAll(factory, path1);
         readAll(factory, path2);
@@ -108,8 +104,7 @@ public class ParquetSchemaCacheTest {
     void testDataReadCorrectlyOnFirstRead() throws Exception {
         Path path = writeSingleFile();
 
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         assertThat(countRows(factory, path)).isEqualTo(3);
     }
@@ -118,8 +113,7 @@ public class ParquetSchemaCacheTest {
     void testDataReadCorrectlyOnSubsequentCachedReads() throws Exception {
         Path path = writeSingleFile();
 
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         assertThat(countRows(factory, path)).isEqualTo(3);
         assertThat(countRows(factory, path)).isEqualTo(3);
@@ -131,8 +125,7 @@ public class ParquetSchemaCacheTest {
         Path path1 = writeSingleFile();
         Path path2 = writeSingleFile();
 
-        ParquetReaderFactory factory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+        ParquetReaderFactory factory = new ParquetReaderFactory(new Options(), 
ROW_TYPE, 500, null);
 
         assertThat(countRows(factory, path1)).isEqualTo(3);
         assertThat(countRows(factory, path2)).isEqualTo(3);
@@ -147,9 +140,9 @@ public class ParquetSchemaCacheTest {
         Path path = writeSingleFile();
 
         ParquetReaderFactory factory1 =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, null);
         ParquetReaderFactory factory2 =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), ROW_TYPE, 500, null);
 
         readAll(factory1, path);
 
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/ParquetInt96RowSelectionTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/ParquetInt96RowSelectionTest.java
index d8fd08adad..2c998dbb25 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/ParquetInt96RowSelectionTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/parquet/reader/ParquetInt96RowSelectionTest.java
@@ -37,7 +37,6 @@ import org.apache.paimon.types.DataTypes;
 import org.apache.paimon.types.RowType;
 import org.apache.paimon.utils.RoaringBitmap32;
 
-import org.apache.parquet.filter2.compat.FilterCompat;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
 
@@ -78,7 +77,7 @@ public class ParquetInt96RowSelectionTest {
                         fileIO.getFileSize(file),
                         RoaringBitmap32.bitmapOf(SELECTED_POSITIONS));
         ParquetReaderFactory readerFactory =
-                new ParquetReaderFactory(new Options(), ROW_TYPE, BATCH_SIZE, 
FilterCompat.NOOP);
+                new ParquetReaderFactory(new Options(), ROW_TYPE, BATCH_SIZE, 
null);
 
         try (RecordReader<InternalRow> reader = 
readerFactory.createReader(context)) {
             int expectedIndex = 0;


Reply via email to