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

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


The following commit(s) were added to refs/heads/master by this push:
     new cc75051fc4ef fix(hive-sync): fix partition-value parsing on '=' and 
pushdown comparator overflow (#19336)
cc75051fc4ef is described below

commit cc75051fc4efa5449f1e0d9e6f6bffe29e2b14f6
Author: Vova Kolmakov <[email protected]>
AuthorDate: Tue Jul 21 23:45:52 2026 +0700

    fix(hive-sync): fix partition-value parsing on '=' and pushdown comparator 
overflow (#19336)
    
    Two edge-case defects in the partition path of hudi-hive-sync:
    
    - PartitionFilterGenerator.ValueComparator sorted int/bigint partition 
values with subtraction (i1 - i2, Long.signum(l1 - l2)) to derive the pushdown 
min/max bounds. The subtraction overflows when the difference exceeds the type 
range (e.g. Integer.MAX_VALUE and a negative value), giving a wrong ordering 
and wrong bounds that can exclude valid partitions or trip the Comparator 
contract check. Use Integer.compare/Long.compare.
    
    - MultiPartKeysValueExtractor and HiveStylePartitionValueExtractor split a 
hive-style key=value segment with split("=") (no limit), so a value that itself 
contains '=' (base64 padding or an embedded '=') was rejected (aborting the 
sync) or silently truncated (trailing '=' dropped), causing drift from the 
metastore. Split on the first '=' only with split("=", 2).
    
    Extends TestPartitionFilterGenerator (extreme int/bigint min/max bounds), 
TestMultiPartKeysValueExtractor and TestPartitionValueExtractor (values 
containing '=').
    
    Co-authored-by: Vova Kolmakov <[email protected]>
---
 .../hive/HiveStylePartitionValueExtractor.java     |  4 +++-
 .../hudi/hive/MultiPartKeysValueExtractor.java     |  4 +++-
 .../hudi/hive/util/PartitionFilterGenerator.java   | 11 +++++-----
 .../hudi/hive/TestMultiPartKeysValueExtractor.java | 15 ++++++++++++++
 .../hudi/hive/TestPartitionValueExtractor.java     |  8 ++++++++
 .../hive/util/TestPartitionFilterGenerator.java    | 24 ++++++++++++++++++++++
 6 files changed, 58 insertions(+), 8 deletions(-)

diff --git 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveStylePartitionValueExtractor.java
 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveStylePartitionValueExtractor.java
index 11098698e8ae..41ccaa59d486 100644
--- 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveStylePartitionValueExtractor.java
+++ 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/HiveStylePartitionValueExtractor.java
@@ -34,7 +34,9 @@ public class HiveStylePartitionValueExtractor implements 
PartitionValueExtractor
   @Override
   public List<String> extractPartitionValuesInPath(String partitionPath) {
     // partition path is expected to be in this format 
partition_key=partition_value.
-    String[] splits = partitionPath.split("=");
+    // Split on the first '=' only so a value that itself contains '=' (e.g. 
base64 padding)
+    // is kept intact rather than making the split produce more than two parts.
+    String[] splits = partitionPath.split("=", 2);
     if (splits.length != 2) {
       throw new IllegalArgumentException(
               "Partition path " + partitionPath + " is not in the form 
partition_key=partition_value.");
diff --git 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/MultiPartKeysValueExtractor.java
 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/MultiPartKeysValueExtractor.java
index dd356638a47e..5404649a5fd6 100644
--- 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/MultiPartKeysValueExtractor.java
+++ 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/MultiPartKeysValueExtractor.java
@@ -42,7 +42,9 @@ public class MultiPartKeysValueExtractor implements 
PartitionValueExtractor {
     String[] splits = partitionPath.split("/");
     return Arrays.stream(splits).map(s -> {
       if (s.contains("=")) {
-        String[] moreSplit = s.split("=");
+        // Split on the first '=' only so partition values that themselves 
contain '='
+        // (e.g. base64 padding like "col=YWJj==") are preserved instead of 
being rejected or truncated.
+        String[] moreSplit = s.split("=", 2);
         ValidationUtils.checkArgument(moreSplit.length == 2, "Partition Field 
(" + s + ") not in expected format");
         return moreSplit[1];
       }
diff --git 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/util/PartitionFilterGenerator.java
 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/util/PartitionFilterGenerator.java
index ea64c111d42a..75602bc57708 100644
--- 
a/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/util/PartitionFilterGenerator.java
+++ 
b/hudi-sync/hudi-hive-sync/src/main/java/org/apache/hudi/hive/util/PartitionFilterGenerator.java
@@ -140,13 +140,12 @@ public class PartitionFilterGenerator {
     public int compare(String s1, String s2) {
       switch (valueType.toLowerCase(Locale.ROOT)) {
         case HiveSchemaUtil.INT_TYPE_NAME:
-          int i1 = Integer.parseInt(s1);
-          int i2 = Integer.parseInt(s2);
-          return i1 - i2;
+          // Use Integer.compare rather than subtraction, which overflows for 
values whose
+          // difference exceeds the int range and yields a wrong ordering.
+          return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2));
         case HiveSchemaUtil.BIGINT_TYPE_NAME:
-          long l1 = Long.parseLong(s1);
-          long l2 = Long.parseLong(s2);
-          return Long.signum(l1 - l2);
+          // Use Long.compare rather than subtraction, which overflows the 
long arithmetic.
+          return Long.compare(Long.parseLong(s1), Long.parseLong(s2));
         case HiveSchemaUtil.DATE_TYPE_NAME:
         case HiveSchemaUtil.STRING_TYPE_NAME:
           return s1.compareTo(s2);
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestMultiPartKeysValueExtractor.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestMultiPartKeysValueExtractor.java
index d8b9100309e5..74610a0ea8e6 100644
--- 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestMultiPartKeysValueExtractor.java
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestMultiPartKeysValueExtractor.java
@@ -21,6 +21,8 @@ package org.apache.hudi.hive;
 import org.junit.jupiter.api.Test;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -40,4 +42,17 @@ public class TestMultiPartKeysValueExtractor {
     // Test extract hive style partition path
     assertEquals(expected, 
valueExtractor.extractPartitionValuesInPath("ds=2021-04-25/hh=04"));
   }
+
+  @Test
+  public void testValuesContainingEquals() {
+    MultiPartKeysValueExtractor valueExtractor = new 
MultiPartKeysValueExtractor();
+    // Only the first '=' separates key from value, so a value containing '=' 
is kept intact.
+    assertEquals(Collections.singletonList("a=b"), 
valueExtractor.extractPartitionValuesInPath("k=a=b"));
+    // base64-encoded value with '=' padding must not be truncated
+    assertEquals(Collections.singletonList("YWJjZA=="), 
valueExtractor.extractPartitionValuesInPath("col=YWJjZA=="));
+    // empty value
+    assertEquals(Collections.singletonList(""), 
valueExtractor.extractPartitionValuesInPath("dt="));
+    // multiple hive-style parts whose values contain '='
+    assertEquals(Arrays.asList("a=b", "YWJj=="), 
valueExtractor.extractPartitionValuesInPath("k1=a=b/k2=YWJj=="));
+  }
 }
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestPartitionValueExtractor.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestPartitionValueExtractor.java
index 075542d59671..0cce6ee79423 100644
--- 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestPartitionValueExtractor.java
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/TestPartitionValueExtractor.java
@@ -49,6 +49,14 @@ public class TestPartitionValueExtractor {
     assertThrows(
         IllegalArgumentException.class,
         () -> hiveStylePartition.extractPartitionValuesInPath("2021/04/02"));
+    // Only the first '=' is the separator, so a value containing '=' is 
preserved.
+    assertEquals(
+        Collections.singletonList("a=b=c"),
+        hiveStylePartition.extractPartitionValuesInPath("k=a=b=c"));
+    // base64-encoded value with '=' padding must not be truncated
+    assertEquals(
+        Collections.singletonList("YWJjZA=="),
+        hiveStylePartition.extractPartitionValuesInPath("col=YWJjZA=="));
   }
 
   @Test
diff --git 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/util/TestPartitionFilterGenerator.java
 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/util/TestPartitionFilterGenerator.java
index b607e7f6948c..a010261a21bd 100644
--- 
a/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/util/TestPartitionFilterGenerator.java
+++ 
b/hudi-sync/hudi-hive-sync/src/test/java/org/apache/hudi/hive/util/TestPartitionFilterGenerator.java
@@ -79,6 +79,30 @@ public class TestPartitionFilterGenerator {
         partitionFilterGenerator.generatePushDownFilter(writtenPartitions, 
partitionFieldSchemas, config));
   }
 
+  @Test
+  public void testMinMaxFilterNoNumericOverflow() {
+    Properties props = new Properties();
+    // force the min/max branch so the values are sorted by ValueComparator
+    props.put(HIVE_SYNC_FILTER_PUSHDOWN_MAX_SIZE.key(), "0");
+    HiveSyncConfig config = new HiveSyncConfig(props);
+    List<FieldSchema> partitionFieldSchemas = new ArrayList<>(2);
+    partitionFieldSchemas.add(new FieldSchema("intcol", "int"));
+    partitionFieldSchemas.add(new FieldSchema("bigcol", "bigint"));
+
+    List<String> writtenPartitions = new ArrayList<>();
+    // extreme values whose pairwise difference overflows int/long subtraction
+    writtenPartitions.add("2147483647/9223372036854775807");
+    writtenPartitions.add("-2147483648/-9223372036854775808");
+    writtenPartitions.add("0/0");
+
+    // bounds must reflect the true numeric order; a subtraction-based 
comparator overflows and
+    // would pick wrong min/max (or throw a comparator-contract violation).
+    assertEquals(
+        "((intcol >= -2147483648 AND intcol <= 2147483647) "
+            + "AND (bigcol >= -9223372036854775808 AND bigcol <= 
9223372036854775807))",
+        partitionFilterGenerator.generatePushDownFilter(writtenPartitions, 
partitionFieldSchemas, config));
+  }
+
   @Test
   public void testPushDownFilterIfExceedLimit() {
     Properties props = new Properties();

Reply via email to