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 671786d58e [hive] Skip predicate pushdown for literals not 
representable in the column type (#10403)
671786d58e is described below

commit 671786d58ec61615b10013e69652cbddc4ed8538
Author: Eunbin Son <[email protected]>
AuthorDate: Fri Oct 9 23:01:36 2026 +0900

    [hive] Skip predicate pushdown for literals not representable in the column 
type (#10403)
---
 .../hive/SearchArgumentToPredicateConverter.java   |  31 +++++-
 .../org/apache/paimon/hive/HiveReadITCaseBase.java |  48 ++++++++++
 .../SearchArgumentToPredicateConverterTest.java    | 104 +++++++++++++++++++++
 3 files changed, 182 insertions(+), 1 deletion(-)

diff --git 
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/SearchArgumentToPredicateConverter.java
 
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/SearchArgumentToPredicateConverter.java
index 202cceae29..b33d94b882 100644
--- 
a/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/SearchArgumentToPredicateConverter.java
+++ 
b/paimon-hive/paimon-hive-connector-common/src/main/java/org/apache/paimon/hive/SearchArgumentToPredicateConverter.java
@@ -18,6 +18,7 @@
 
 package org.apache.paimon.hive;
 
+import org.apache.paimon.data.Decimal;
 import org.apache.paimon.predicate.Predicate;
 import org.apache.paimon.predicate.PredicateBuilder;
 import org.apache.paimon.types.DataType;
@@ -33,6 +34,7 @@ import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
 
+import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
@@ -182,6 +184,33 @@ public class SearchArgumentToPredicateConverter {
         if (o instanceof HiveDecimalWritable) {
             o = ((HiveDecimalWritable) o).getHiveDecimal().bigDecimalValue();
         }
-        return convertJavaObject(literalType, o);
+        Object literal = convertJavaObject(literalType, o);
+        // Hive does not cast literals to the column type. A literal that 
cannot be represented
+        // exactly in the column type (e.g. 1000 for DECIMAL(5, 2), 1.005 for 
DECIMAL(5, 2) or 200
+        // for TINYINT) would be altered here and prune data files wrongly, so 
leave this
+        // conjunct to Hive's residual filter instead.
+        if (o != null && !isExact(literalType, o, literal)) {
+            throw new UnsupportedOperationException(
+                    "Literal "
+                            + o
+                            + " cannot be represented exactly in column type "
+                            + literalType
+                            + ".");
+        }
+        return literal;
+    }
+
+    private static boolean isExact(DataType literalType, Object original, 
Object literal) {
+        switch (literalType.getTypeRoot()) {
+            case DECIMAL:
+                return literal != null
+                        && ((Decimal) 
literal).toBigDecimal().compareTo((BigDecimal) original) == 0;
+            case TINYINT:
+            case SMALLINT:
+            case INTEGER:
+                return ((Number) literal).longValue() == ((Number) 
original).longValue();
+            default:
+                return true;
+        }
     }
 }
diff --git 
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveReadITCaseBase.java
 
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveReadITCaseBase.java
index 2a15ffae34..6c225bafe3 100644
--- 
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveReadITCaseBase.java
+++ 
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/HiveReadITCaseBase.java
@@ -706,6 +706,54 @@ public abstract class HiveReadITCaseBase extends 
HiveTestBase {
                 .containsExactly("1", "2", "3", "4", "5", "6");
     }
 
+    @Test
+    public void 
testPredicatePushDownWithLiteralsNotRepresentableInColumnType() throws 
Exception {
+        Options conf = getBasicConf();
+        conf.set(CoreOptions.FILE_FORMAT, CoreOptions.FILE_FORMAT_AVRO);
+        Table table =
+                FileStoreTestUtils.createFileStoreTable(
+                        conf,
+                        RowType.of(
+                                new DataType[] {
+                                    DataTypes.DECIMAL(5, 2), 
DataTypes.TINYINT(), DataTypes.INT()
+                                },
+                                new String[] {"c", "t", "i"}),
+                        Collections.emptyList(),
+                        Collections.emptyList());
+
+        // one data file per row, so that file pruning by statistics is 
observable
+        StreamWriteBuilder streamWriteBuilder = table.newStreamWriteBuilder();
+        StreamTableWrite write = streamWriteBuilder.newWrite();
+        StreamTableCommit commit = streamWriteBuilder.newCommit();
+        write.write(GenericRow.of(decimal("1.00"), (byte) 5, 5));
+        commit.commit(0, write.prepareCommit(true, 0));
+        write.write(GenericRow.of(decimal("1.01"), (byte) 100, 100));
+        commit.commit(1, write.prepareCommit(true, 1));
+        write.write(GenericRow.of(decimal("999.99"), (byte) 120, 2000000000));
+        commit.commit(2, write.prepareCommit(true, 2));
+        write.close();
+        commit.close();
+        createExternalTable();
+
+        String select = "SELECT t FROM " + externalTable + " WHERE ";
+        assertThat(hiveShell.executeQuery(select + "c < 1000"))
+                .containsExactlyInAnyOrder("5", "100", "120");
+        assertThat(hiveShell.executeQuery(select + "c BETWEEN 0 AND 1000"))
+                .containsExactlyInAnyOrder("5", "100", "120");
+        assertThat(hiveShell.executeQuery(select + "c > 1.005"))
+                .containsExactlyInAnyOrder("100", "120");
+        assertThat(hiveShell.executeQuery(select + "c < 
1.004")).containsExactly("5");
+        assertThat(hiveShell.executeQuery(select + "t < 200"))
+                .containsExactlyInAnyOrder("5", "100", "120");
+        assertThat(hiveShell.executeQuery(select + "i < 3000000000"))
+                .containsExactlyInAnyOrder("5", "100", "120");
+        assertThat(hiveShell.executeQuery(select + "c < 
1.01")).containsExactly("5");
+    }
+
+    private static Decimal decimal(String value) {
+        return Decimal.fromBigDecimal(new BigDecimal(value), 5, 2);
+    }
+
     @Test
     public void testDateAndTimestamp() throws Exception {
         ThreadLocalRandom random = ThreadLocalRandom.current();
diff --git 
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/SearchArgumentToPredicateConverterTest.java
 
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/SearchArgumentToPredicateConverterTest.java
index d9a6b7c8f1..ad97ecdab7 100644
--- 
a/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/SearchArgumentToPredicateConverterTest.java
+++ 
b/paimon-hive/paimon-hive-connector-common/src/test/java/org/apache/paimon/hive/SearchArgumentToPredicateConverterTest.java
@@ -421,6 +421,110 @@ public class SearchArgumentToPredicateConverterTest {
         assertExpected(sarg, expected);
     }
 
+    private static final List<String> NARROW_COLUMN_NAMES =
+            Arrays.asList("f_decimal", "f_tinyint", "f_int");
+    private static final List<DataType> NARROW_COLUMN_TYPES =
+            Arrays.asList(DataTypes.DECIMAL(5, 2), DataTypes.TINYINT(), 
DataTypes.INT());
+    private static final PredicateBuilder NARROW_BUILDER =
+            new PredicateBuilder(
+                    RowType.of(
+                            NARROW_COLUMN_TYPES.toArray(new DataType[0]),
+                            NARROW_COLUMN_NAMES.toArray(new String[0])));
+
+    @Test
+    public void testDecimalLiteralExceedingPrecision() {
+        SearchArgument sarg =
+                SearchArgumentFactory.newBuilder()
+                        .lessThan("f_decimal", PredicateLeaf.Type.DECIMAL, 
hiveDecimal("1000"))
+                        .build();
+        assertNarrowExpected(sarg, null);
+
+        sarg =
+                SearchArgumentFactory.newBuilder()
+                        .startAnd()
+                        .between(
+                                "f_decimal",
+                                PredicateLeaf.Type.DECIMAL,
+                                hiveDecimal("0"),
+                                hiveDecimal("1000"))
+                        .in(
+                                "f_decimal",
+                                PredicateLeaf.Type.DECIMAL,
+                                hiveDecimal("1.5"),
+                                hiveDecimal("1000"))
+                        .lessThan("f_tinyint", PredicateLeaf.Type.LONG, 10L)
+                        .end()
+                        .build();
+        assertNarrowExpected(sarg, NARROW_BUILDER.lessThan(1, (byte) 10));
+    }
+
+    @Test
+    public void testDecimalLiteralExceedingScale() {
+        SearchArgument sarg =
+                SearchArgumentFactory.newBuilder()
+                        .startAnd()
+                        .startNot()
+                        .lessThanEquals(
+                                "f_decimal", PredicateLeaf.Type.DECIMAL, 
hiveDecimal("1.005"))
+                        .end()
+                        .lessThan("f_int", PredicateLeaf.Type.LONG, 100L)
+                        .end()
+                        .build();
+        assertNarrowExpected(sarg, NARROW_BUILDER.lessThan(2, 100));
+    }
+
+    @Test
+    public void testIntegerLiteralOutOfRange() {
+        SearchArgument sarg =
+                SearchArgumentFactory.newBuilder()
+                        .lessThan("f_tinyint", PredicateLeaf.Type.LONG, 200L)
+                        .build();
+        assertNarrowExpected(sarg, null);
+
+        sarg =
+                SearchArgumentFactory.newBuilder()
+                        .lessThan("f_int", PredicateLeaf.Type.LONG, 
3000000000L)
+                        .build();
+        assertNarrowExpected(sarg, null);
+    }
+
+    @Test
+    public void testRepresentableNarrowLiterals() {
+        SearchArgument sarg =
+                SearchArgumentFactory.newBuilder()
+                        .startAnd()
+                        .lessThan("f_decimal", PredicateLeaf.Type.DECIMAL, 
hiveDecimal("1.50"))
+                        .lessThanEquals(
+                                "f_decimal", PredicateLeaf.Type.DECIMAL, 
hiveDecimal("999.99"))
+                        .lessThanEquals("f_int", PredicateLeaf.Type.LONG, 
(long) Integer.MAX_VALUE)
+                        .in("f_tinyint", PredicateLeaf.Type.LONG, 0L, null, 
127L)
+                        .end()
+                        .build();
+        Predicate expected =
+                PredicateBuilder.and(
+                        NARROW_BUILDER.lessThan(0, decimal("1.50")),
+                        NARROW_BUILDER.lessOrEqual(0, decimal("999.99")),
+                        NARROW_BUILDER.lessOrEqual(2, Integer.MAX_VALUE),
+                        NARROW_BUILDER.in(1, Arrays.asList((byte) 0, null, 
(byte) 127)));
+        assertNarrowExpected(sarg, expected);
+    }
+
+    private static HiveDecimalWritable hiveDecimal(String value) {
+        return new HiveDecimalWritable(HiveDecimal.create(value));
+    }
+
+    private static Decimal decimal(String value) {
+        return Decimal.fromBigDecimal(new BigDecimal(value), 5, 2);
+    }
+
+    private void assertNarrowExpected(SearchArgument sarg, Predicate expected) 
{
+        SearchArgumentToPredicateConverter converter =
+                new SearchArgumentToPredicateConverter(
+                        sarg, NARROW_COLUMN_NAMES, NARROW_COLUMN_TYPES, null);
+        Predicate actual = converter.convert().orElse(null);
+        assertThat(actual).isEqualTo(expected);
+    }
+
     private void assertExpected(SearchArgument sarg, Predicate expected) {
         SearchArgumentToPredicateConverter converter =
                 new SearchArgumentToPredicateConverter(sarg, COLUMN_NAMES, 
COLUMN_TYPES, null);

Reply via email to