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 b24751731a [format] Drop ORC float bounds that hide NaN or -0.0 
(#10337)
b24751731a is described below

commit b24751731a22fa9788205897b6554f247a3fe300
Author: Burak Kalaycı <[email protected]>
AuthorDate: Mon Oct 12 16:47:07 2026 +0300

    [format] Drop ORC float bounds that hide NaN or -0.0 (#10337)
---
 .../apache/paimon/table/OrcSignedZeroReadTest.java | 199 +++++++++++++++++++++
 .../orc/filter/OrcPredicateFunctionVisitor.java    |  28 ++-
 .../format/orc/filter/OrcSimpleStatsExtractor.java |  29 +++
 .../format/orc/OrcFloatingPointStatsTest.java      | 160 +++++++++++++++++
 .../format/orc/OrcSimpleStatsExtractorTest.java    |  17 ++
 .../format/orc/filter/OrcFilterConverterTest.java  |  28 +++
 6 files changed, 460 insertions(+), 1 deletion(-)

diff --git 
a/paimon-core/src/test/java/org/apache/paimon/table/OrcSignedZeroReadTest.java 
b/paimon-core/src/test/java/org/apache/paimon/table/OrcSignedZeroReadTest.java
new file mode 100644
index 0000000000..75d157d15a
--- /dev/null
+++ 
b/paimon-core/src/test/java/org/apache/paimon/table/OrcSignedZeroReadTest.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.table;
+
+import org.apache.paimon.append.AppendCompactTask;
+import org.apache.paimon.data.BinaryRow;
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.data.InternalRow;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.io.DataFileMeta;
+import org.apache.paimon.operation.BaseAppendFileStoreWrite;
+import org.apache.paimon.operation.FileStoreWrite;
+import org.apache.paimon.predicate.Predicate;
+import org.apache.paimon.predicate.PredicateBuilder;
+import org.apache.paimon.reader.RecordReader;
+import org.apache.paimon.schema.FileSystemSchemaManager;
+import org.apache.paimon.schema.Schema;
+import org.apache.paimon.schema.SchemaUtils;
+import org.apache.paimon.schema.TableSchema;
+import org.apache.paimon.table.sink.BatchTableCommit;
+import org.apache.paimon.table.sink.BatchTableWrite;
+import org.apache.paimon.table.sink.BatchWriteBuilder;
+import org.apache.paimon.table.sink.CommitMessage;
+import org.apache.paimon.table.sink.TableWriteImpl;
+import org.apache.paimon.table.source.DataSplit;
+import org.apache.paimon.table.source.ReadBuilder;
+import org.apache.paimon.table.source.Split;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Table reads for an ORC append file whose footer max is {@code -0.0} while a 
{@code +0.0} row is
+ * still present. File stats and ORC SearchArgument both have to leave that 
row visible.
+ */
+class OrcSignedZeroReadTest {
+
+    @TempDir java.nio.file.Path tempDir;
+
+    @ParameterizedTest
+    @ValueSource(strings = {"float", "double"})
+    public void testPositiveZeroSurvivesNegativeZeroUpperBound(String 
typeName) throws Exception {
+        boolean isFloat = "float".equals(typeName);
+        DataType type = isFloat ? DataTypes.FLOAT() : DataTypes.DOUBLE();
+        FileStoreTable table = createTable(type);
+        writeFile(table, isFloat);
+        writeFile(table, isFloat);
+
+        assertThat(count(table, null)).isEqualTo(6);
+        assertMatches(table, isFloat);
+
+        compact(table);
+        assertMatches(table, isFloat);
+    }
+
+    @Test
+    public void testFiniteDoubleFilterStillPlans() throws Exception {
+        FileStoreTable table = createTable(DataTypes.DOUBLE());
+        writeFinite(table);
+        Predicate filter = new PredicateBuilder(table.rowType()).equal(0, 
1.0d);
+        assertThat(count(table, filter)).isEqualTo(1);
+    }
+
+    private void assertMatches(FileStoreTable table, boolean isFloat) throws 
Exception {
+        PredicateBuilder builder = new PredicateBuilder(table.rowType());
+        Object positiveZero;
+        Object negativeZero;
+        if (isFloat) {
+            positiveZero = Float.valueOf(0.0f);
+            negativeZero = -0.0f;
+        } else {
+            positiveZero = 0.0d;
+            negativeZero = -0.0d;
+        }
+        assertThat(count(table, builder.equal(0, 
positiveZero))).as("equal(+0.0)").isEqualTo(2);
+        assertThat(count(table, builder.greaterThan(0, negativeZero)))
+                .as("greaterThan(-0.0)")
+                .isEqualTo(2);
+    }
+
+    private FileStoreTable createTable(DataType type) throws Exception {
+        Path path = new Path(tempDir.toString() + "/" + 
type.getTypeRoot().name());
+        Map<String, String> options = new HashMap<>();
+        options.put("file.format", "orc");
+        options.put("bucket", "-1");
+        options.put("async-file-write", "false");
+        RowType rowType = new RowType(Collections.singletonList(new 
DataField(0, "v", type)));
+        TableSchema schema =
+                SchemaUtils.forceCommit(
+                        new FileSystemSchemaManager(LocalFileIO.create(), 
path),
+                        new Schema(
+                                rowType.getFields(),
+                                Collections.emptyList(),
+                                Collections.emptyList(),
+                                options,
+                                ""));
+        return new AppendOnlyFileStoreTable(LocalFileIO.create(), path, 
schema);
+    }
+
+    private void writeFile(FileStoreTable table, boolean isFloat) throws 
Exception {
+        Object negativeOne;
+        Object negativeZero;
+        Object positiveZero;
+        if (isFloat) {
+            negativeOne = -1.0f;
+            negativeZero = -0.0f;
+            positiveZero = 0.0f;
+        } else {
+            negativeOne = -1.0d;
+            negativeZero = -0.0d;
+            positiveZero = 0.0d;
+        }
+        BatchWriteBuilder builder = table.newBatchWriteBuilder();
+        try (BatchTableWrite write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            write.write(GenericRow.of(negativeOne));
+            write.write(GenericRow.of(negativeZero));
+            write.write(GenericRow.of(positiveZero));
+            commit.commit(write.prepareCommit());
+        }
+    }
+
+    private void writeFinite(FileStoreTable table) throws Exception {
+        BatchWriteBuilder builder = table.newBatchWriteBuilder();
+        try (BatchTableWrite write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            write.write(GenericRow.of(1.0d));
+            write.write(GenericRow.of(2.0d));
+            commit.commit(write.prepareCommit());
+        }
+    }
+
+    private int count(FileStoreTable table, Predicate filter) throws Exception 
{
+        ReadBuilder builder = table.newReadBuilder();
+        if (filter != null) {
+            builder.withFilter(filter);
+            assertThat(builder.newScan().plan().splits())
+                    .as("filter planned no splits")
+                    .isNotEmpty();
+        }
+        int[] matched = new int[1];
+        try (RecordReader<InternalRow> reader =
+                
builder.newRead().executeFilter().createReader(builder.newScan().plan())) {
+            reader.forEachRemaining(row -> matched[0]++);
+        }
+        return matched[0];
+    }
+
+    private void compact(FileStoreTable table) throws Exception {
+        List<Split> splits = table.newReadBuilder().newScan().plan().splits();
+        assertThat(splits).isNotEmpty();
+        List<DataFileMeta> files = new ArrayList<>();
+        BinaryRow partition = ((DataSplit) splits.get(0)).partition();
+        for (Split split : splits) {
+            files.addAll(((DataSplit) split).dataFiles());
+        }
+        assertThat(files.size()).isGreaterThan(1);
+        BatchWriteBuilder builder = table.newBatchWriteBuilder();
+        try (BatchTableWrite write = builder.newWrite();
+                BatchTableCommit commit = builder.newCommit()) {
+            FileStoreWrite<?> fileStoreWrite = ((TableWriteImpl<?>) 
write).getWrite();
+            CommitMessage message =
+                    new AppendCompactTask(partition, files)
+                            .doCompact(table, (BaseAppendFileStoreWrite) 
fileStoreWrite);
+            commit.commit(Collections.singletonList(message));
+        }
+    }
+}
diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
index 1e5825f96a..b41282e8b0 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcPredicateFunctionVisitor.java
@@ -185,6 +185,11 @@ public class OrcPredicateFunctionVisitor
         if (colType == null) {
             return Optional.empty();
         }
+        for (Object literal : literals) {
+            if (signedZeroPushdown(colType, literal)) {
+                return Optional.empty();
+            }
+        }
 
         Object[] orcLiterals = new Object[literals.size()];
         for (int i = 0; i < literals.size(); i++) {
@@ -238,7 +243,7 @@ public class OrcPredicateFunctionVisitor
             Object literal,
             TriFunction<String, PredicateLeaf.Type, Serializable, 
OrcFilters.Predicate> func) {
         PredicateLeaf.Type litType = toOrcType(fieldRef.type());
-        if (litType == null) {
+        if (litType == null || signedZeroPushdown(litType, literal)) {
             return Optional.empty();
         }
         // fetch literal and ensure it is serializable
@@ -249,6 +254,27 @@ public class OrcPredicateFunctionVisitor
                 : Optional.empty();
     }
 
+    /**
+     * ORC SearchArgument uses primitive comparisons, so {@code -0.0} and 
{@code +0.0} compare equal
+     * and a row group can be dropped while it still holds the other zero. 
Leave the predicate for
+     * the engine.
+     */
+    private static boolean signedZeroPushdown(PredicateLeaf.Type litType, 
Object literal) {
+        if (litType != PredicateLeaf.Type.FLOAT || literal == null) {
+            return false;
+        }
+        if (literal instanceof Double) {
+            long bits = Double.doubleToRawLongBits((Double) literal);
+            return bits == Double.doubleToRawLongBits(0.0d)
+                    || bits == Double.doubleToRawLongBits(-0.0d);
+        }
+        if (literal instanceof Float) {
+            int bits = Float.floatToRawIntBits((Float) literal);
+            return bits == Float.floatToRawIntBits(0.0f) || bits == 
Float.floatToRawIntBits(-0.0f);
+        }
+        return false;
+    }
+
     @Nullable
     private static Object toOrcObject(PredicateLeaf.Type litType, Object 
literalObj) {
         if (literalObj == null) {
diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcSimpleStatsExtractor.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcSimpleStatsExtractor.java
index f044596708..bb26215e5c 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcSimpleStatsExtractor.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/filter/OrcSimpleStatsExtractor.java
@@ -200,6 +200,9 @@ public class OrcSimpleStatsExtractor implements 
SimpleStatsExtractor {
             case FLOAT:
                 assertStatsClass(field, stats, DoubleColumnStatistics.class);
                 DoubleColumnStatistics floatStats = (DoubleColumnStatistics) 
stats;
+                if (orcFloatingPointBoundsUnusable(floatStats)) {
+                    return new SimpleColStats(null, null, nullCount);
+                }
                 return new SimpleColStats(
                         (float) floatStats.getMinimum(),
                         (float) floatStats.getMaximum(),
@@ -207,6 +210,9 @@ public class OrcSimpleStatsExtractor implements 
SimpleStatsExtractor {
             case DOUBLE:
                 assertStatsClass(field, stats, DoubleColumnStatistics.class);
                 DoubleColumnStatistics doubleStats = (DoubleColumnStatistics) 
stats;
+                if (orcFloatingPointBoundsUnusable(doubleStats)) {
+                    return new SimpleColStats(null, null, nullCount);
+                }
                 return new SimpleColStats(
                         doubleStats.getMinimum(), doubleStats.getMaximum(), 
nullCount);
             case DATE:
@@ -240,6 +246,29 @@ public class OrcSimpleStatsExtractor implements 
SimpleStatsExtractor {
         }
     }
 
+    /**
+     * ORC updates double min/max with primitive {@code <} / {@code >}. NaN 
never replaces a finite
+     * bound, {@code -0.0} never replaces {@code +0.0} as a minimum, and 
{@code +0.0} never replaces
+     * {@code -0.0} as a maximum. Any of those can make a predicate skip a 
file that still contains
+     * a matching row. A missing bound is not used for skipping.
+     */
+    private static boolean 
orcFloatingPointBoundsUnusable(DoubleColumnStatistics stats) {
+        double minimum = stats.getMinimum();
+        double maximum = stats.getMaximum();
+        if (Double.isNaN(minimum) || Double.isNaN(maximum) || 
Double.isNaN(stats.getSum())) {
+            return true;
+        }
+        return isPositiveZero(minimum) || isNegativeZero(maximum);
+    }
+
+    private static boolean isPositiveZero(double value) {
+        return Double.doubleToRawLongBits(value) == 
Double.doubleToRawLongBits(0.0d);
+    }
+
+    private static boolean isNegativeZero(double value) {
+        return Double.doubleToRawLongBits(value) == 
Double.doubleToRawLongBits(-0.0d);
+    }
+
     private void assertStatsClass(
             DataField field,
             ColumnStatistics stats,
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcFloatingPointStatsTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcFloatingPointStatsTest.java
new file mode 100644
index 0000000000..0089566fb7
--- /dev/null
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcFloatingPointStatsTest.java
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.format.orc;
+
+import org.apache.paimon.data.GenericRow;
+import org.apache.paimon.format.FileFormat;
+import org.apache.paimon.format.FormatWriter;
+import org.apache.paimon.format.FormatWriterFactory;
+import org.apache.paimon.format.SimpleColStats;
+import org.apache.paimon.format.SimpleStatsExtractor;
+import org.apache.paimon.fs.FileIO;
+import org.apache.paimon.fs.Path;
+import org.apache.paimon.fs.PositionOutputStream;
+import org.apache.paimon.fs.local.LocalFileIO;
+import org.apache.paimon.options.Options;
+import org.apache.paimon.statistics.SimpleColStatsCollector;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** ORC footer stats use primitive comparisons, which hide NaN and -0.0. */
+class OrcFloatingPointStatsTest {
+
+    @TempDir java.nio.file.Path tempDir;
+
+    private final FileIO fileIO = new LocalFileIO();
+
+    @Test
+    public void testDoubleNaNIsNotExcludedByTheUpperBound() throws Exception {
+        SimpleColStats stats = extract(DataTypes.DOUBLE(), new Double[] {1.0d, 
Double.NaN});
+
+        assertThat(boundKeepsNaN(stats.max()))
+                .as("max %s excludes a NaN that was written", stats.max())
+                .isTrue();
+    }
+
+    @Test
+    public void testFloatNaNIsNotExcludedByTheUpperBound() throws Exception {
+        SimpleColStats stats = extract(DataTypes.FLOAT(), new Float[] {1.0f, 
Float.NaN});
+
+        assertThat(boundKeepsNaN(stats.max()))
+                .as("max %s excludes a NaN that was written", stats.max())
+                .isTrue();
+    }
+
+    @Test
+    public void testDoubleNegativeZeroIsNotExcludedByTheLowerBound() throws 
Exception {
+        SimpleColStats stats = extract(DataTypes.DOUBLE(), new Double[] {0.0d, 
-0.0d});
+
+        assertThat(boundKeepsNegativeZero(stats.min()))
+                .as("min %s excludes a -0.0 that was written", stats.min())
+                .isTrue();
+    }
+
+    @Test
+    public void testFloatNegativeZeroIsNotExcludedByTheLowerBound() throws 
Exception {
+        SimpleColStats stats = extract(DataTypes.FLOAT(), new Float[] {0.0f, 
-0.0f});
+
+        assertThat(boundKeepsNegativeZero(stats.min()))
+                .as("min %s excludes a -0.0 that was written", stats.min())
+                .isTrue();
+    }
+
+    @Test
+    public void testFiniteDoubleBoundsAreKept() throws Exception {
+        SimpleColStats stats = extract(DataTypes.DOUBLE(), new Double[] {1.0d, 
3.0d});
+
+        assertThat(stats.min()).isEqualTo(1.0d);
+        assertThat(stats.max()).isEqualTo(3.0d);
+    }
+
+    @Test
+    public void testFiniteFloatBoundsAreKept() throws Exception {
+        SimpleColStats stats = extract(DataTypes.FLOAT(), new Float[] {1.0f, 
3.0f});
+
+        assertThat(stats.min()).isEqualTo(1.0f);
+        assertThat(stats.max()).isEqualTo(3.0f);
+    }
+
+    @Test
+    public void testDoubleNegativeZeroUpperBoundDoesNotHidePositiveZero() 
throws Exception {
+        SimpleColStats stats = extract(DataTypes.DOUBLE(), new Double[] 
{-1.0d, -0.0d, 0.0d});
+
+        assertThat(stats.max()).as("max %s hides a +0.0 that was written", 
stats.max()).isNull();
+    }
+
+    @Test
+    public void testFloatNegativeZeroUpperBoundDoesNotHidePositiveZero() 
throws Exception {
+        SimpleColStats stats = extract(DataTypes.FLOAT(), new Float[] {-1.0f, 
-0.0f, 0.0f});
+
+        assertThat(stats.max()).as("max %s hides a +0.0 that was written", 
stats.max()).isNull();
+    }
+
+    private SimpleColStats extract(DataType type, Object[] values) throws 
Exception {
+        FileFormat format = FileFormat.fromIdentifier("orc", new Options());
+        RowType rowType = RowType.of(type);
+        FormatWriterFactory writerFactory = 
format.createWriterFactory(rowType);
+        Path path = new Path(tempDir.toString() + "/stats.orc");
+        PositionOutputStream out = fileIO.newOutputStream(path, false);
+        FormatWriter writer = writerFactory.create(out, "LZ4");
+        for (Object value : values) {
+            writer.addElement(GenericRow.of(value));
+        }
+        writer.close();
+
+        SimpleColStatsCollector.Factory[] collectors =
+                new SimpleColStatsCollector.Factory[] 
{SimpleColStatsCollector.from("full")};
+        SimpleStatsExtractor extractor = format.createStatsExtractor(rowType, 
collectors).get();
+        return extractor.extract(fileIO, path, fileIO.getFileSize(path))[0];
+    }
+
+    /** A missing bound cannot skip the file. A NaN bound is the greatest 
value. */
+    private static boolean boundKeepsNaN(Object bound) {
+        if (bound == null) {
+            return true;
+        }
+        if (bound instanceof Double) {
+            return Double.isNaN((Double) bound);
+        }
+        if (bound instanceof Float) {
+            return Float.isNaN((Float) bound);
+        }
+        return false;
+    }
+
+    /** A missing bound cannot skip the file. Otherwise the bound must be 
negative zero. */
+    private static boolean boundKeepsNegativeZero(Object bound) {
+        if (bound == null) {
+            return true;
+        }
+        if (bound instanceof Double) {
+            return Double.doubleToRawLongBits((Double) bound) == 
Double.doubleToRawLongBits(-0.0d);
+        }
+        if (bound instanceof Float) {
+            return Float.floatToRawIntBits((Float) bound) == 
Float.floatToRawIntBits(-0.0f);
+        }
+        return false;
+    }
+}
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcSimpleStatsExtractorTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcSimpleStatsExtractorTest.java
index e2c68ab60d..676ec9b48d 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcSimpleStatsExtractorTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcSimpleStatsExtractorTest.java
@@ -88,10 +88,27 @@ public class OrcSimpleStatsExtractorTest extends 
SimpleColStatsExtractorTest {
             case BINARY:
             case VARBINARY:
                 return new SimpleColStats(null, null, stats.nullCount());
+            case FLOAT:
+            case DOUBLE:
+                // ORC cannot prove a +0.0 minimum is not hiding -0.0.
+                if (isPositiveZero(stats.min())) {
+                    return new SimpleColStats(null, null, stats.nullCount());
+                }
+                return stats;
         }
         return stats;
     }
 
+    private static boolean isPositiveZero(Object value) {
+        if (value instanceof Float) {
+            return Float.floatToRawIntBits((Float) value) == 
Float.floatToRawIntBits(0.0f);
+        }
+        if (value instanceof Double) {
+            return Double.doubleToRawLongBits((Double) value) == 
Double.doubleToRawLongBits(0.0d);
+        }
+        return false;
+    }
+
     @Override
     protected String fileCompression() {
         return "LZ4";
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/filter/OrcFilterConverterTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/filter/OrcFilterConverterTest.java
index ead0762b2a..47f16d4d4d 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/filter/OrcFilterConverterTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/filter/OrcFilterConverterTest.java
@@ -297,6 +297,34 @@ public class OrcFilterConverterTest {
                 .isEqualTo(Optional.empty());
     }
 
+    @Test
+    public void testSignedZeroIsNotPushedDown() {
+        PredicateBuilder builder =
+                new PredicateBuilder(
+                        new RowType(
+                                Arrays.asList(
+                                        new DataField(0, "floatField", new 
FloatType()),
+                                        new DataField(1, "doubleField", new 
DoubleType()))));
+
+        assertThat(builder.equal(0, 
0.0f).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.equal(0, 
-0.0f).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.greaterThan(0, 
-0.0f).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.equal(1, 
0.0d).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.equal(1, 
-0.0d).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.greaterThan(1, 
-0.0d).visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(
+                        builder.in(1, Arrays.asList(-1.0d, 0.0d))
+                                .visit(OrcPredicateFunctionVisitor.VISITOR))
+                .isEqualTo(Optional.empty());
+        assertThat(builder.equal(1, 
1.0d).visit(OrcPredicateFunctionVisitor.VISITOR)).isPresent();
+    }
+
     @Test
     public void testNotLike() {
         PredicateBuilder builder =

Reply via email to