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 8b2844ae24 [globalindex] Fix NPE in SortedFileMetaSelector when index 
contains empty string key (#8312)
8b2844ae24 is described below

commit 8b2844ae2499bd83a1237d90634f998222a740b5
Author: weibangpeng <[email protected]>
AuthorDate: Tue Jun 23 12:25:29 2026 +0800

    [globalindex] Fix NPE in SortedFileMetaSelector when index contains empty 
string key (#8312)
    
    Fix NullPointerException when using btree global index on STRING columns
    with empty string keys.
    
    ## Root cause
    When `SortedIndexFileMeta.firstKey()` or `lastKey()` returns null (from
    null-flagged index file metadata),
    `compareFirstKey`/`compareLastKey`/`overlaps` passes null to
    `deserialize()`, causing NPE.
---
 .../paimon/globalindex/SortedFileMetaSelector.java | 21 ++++++--
 .../globalindex/SortedFileMetaSelectorTest.java    | 59 ++++++++++++++++++++++
 2 files changed, 76 insertions(+), 4 deletions(-)

diff --git 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
index 97d0ada633..047f7b17a5 100644
--- 
a/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
+++ 
b/paimon-common/src/main/java/org/apache/paimon/globalindex/SortedFileMetaSelector.java
@@ -232,16 +232,29 @@ public class SortedFileMetaSelector implements 
FunctionVisitor<Optional<List<Glo
     }
 
     protected boolean overlaps(SortedIndexFileMeta meta, Object from, Object 
to) {
-        return comparator.compare(from, deserialize(meta.lastKey())) <= 0
-                && comparator.compare(to, deserialize(meta.firstKey())) >= 0;
+        if (meta.firstKey() != null && comparator.compare(to, 
deserialize(meta.firstKey())) < 0) {
+            return false;
+        }
+        if (meta.lastKey() != null && comparator.compare(from, 
deserialize(meta.lastKey())) > 0) {
+            return false;
+        }
+        return true;
     }
 
     protected int compareFirstKey(SortedIndexFileMeta meta, Object literal) {
-        return comparator.compare(deserialize(meta.firstKey()), literal);
+        byte[] firstKey = meta.firstKey();
+        if (firstKey == null) {
+            return -1;
+        }
+        return comparator.compare(deserialize(firstKey), literal);
     }
 
     protected int compareLastKey(SortedIndexFileMeta meta, Object literal) {
-        return comparator.compare(deserialize(meta.lastKey()), literal);
+        byte[] lastKey = meta.lastKey();
+        if (lastKey == null) {
+            return 1;
+        }
+        return comparator.compare(deserialize(lastKey), literal);
     }
 
     protected int compareKeys(Object left, Object right) {
diff --git 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedFileMetaSelectorTest.java
 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedFileMetaSelectorTest.java
index 59ab26fcde..77ef2e0d20 100644
--- 
a/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedFileMetaSelectorTest.java
+++ 
b/paimon-common/src/test/java/org/apache/paimon/globalindex/SortedFileMetaSelectorTest.java
@@ -277,6 +277,65 @@ public class SortedFileMetaSelectorTest {
         return BinaryString.fromString(value);
     }
 
+    @Test
+    public void testEmptyStringKeyDoesNotThrowNPE() {
+        KeySerializer serializer = KeySerializer.create(new VarCharType());
+        FieldRef ref = new FieldRef(1, "page_host", new VarCharType());
+
+        byte[] emptyKey = serializer.serialize(BinaryString.EMPTY_UTF8);
+        byte[] normalKey = 
serializer.serialize(BinaryString.fromString("www.example.com"));
+
+        SortedIndexFileMeta metaWithEmptyFirstKey =
+                new SortedIndexFileMeta(emptyKey, normalKey, false);
+        SortedIndexFileMeta metaWithNormalKeys =
+                new SortedIndexFileMeta(
+                        
serializer.serialize(BinaryString.fromString("aaa.com")),
+                        
serializer.serialize(BinaryString.fromString("zzz.com")),
+                        false);
+        SortedIndexFileMeta metaOnlyNulls = new SortedIndexFileMeta(null, 
null, true);
+
+        List<GlobalIndexIOMeta> testFiles =
+                Arrays.asList(
+                        new GlobalIndexIOMeta(
+                                new Path("file_empty"), 1, 
metaWithEmptyFirstKey.serialize()),
+                        new GlobalIndexIOMeta(
+                                new Path("file_normal"), 1, 
metaWithNormalKeys.serialize()),
+                        new GlobalIndexIOMeta(
+                                new Path("file_nulls"), 1, 
metaOnlyNulls.serialize()));
+
+        SortedFileMetaSelector selector = new 
SortedFileMetaSelector(testFiles, serializer);
+
+        // visitEqual should not throw NPE
+        Optional<List<GlobalIndexIOMeta>> result =
+                selector.visitEqual(ref, 
BinaryString.fromString("www.example.com"));
+        Assertions.assertThat(result).isNotEmpty();
+        assertFiles(result.get(), Arrays.asList("file_empty", "file_normal"));
+
+        // visitLessThan should not throw NPE
+        result = selector.visitLessThan(ref, 
BinaryString.fromString("bbb.com"));
+        Assertions.assertThat(result).isNotEmpty();
+        assertFiles(result.get(), Arrays.asList("file_empty", "file_normal"));
+
+        // visitGreaterThan should not throw NPE
+        result = selector.visitGreaterThan(ref, 
BinaryString.fromString("www.example.com"));
+        Assertions.assertThat(result).isNotEmpty();
+
+        // visitIn should not throw NPE
+        result =
+                selector.visitIn(
+                        ref,
+                        Arrays.asList(
+                                BinaryString.fromString("www.example.com"),
+                                BinaryString.fromString("zzz.com")));
+        Assertions.assertThat(result).isNotEmpty();
+
+        // visitBetween should not throw NPE
+        result =
+                selector.visitBetween(
+                        ref, BinaryString.EMPTY_UTF8, 
BinaryString.fromString("zzz.com"));
+        Assertions.assertThat(result).isNotEmpty();
+    }
+
     private static class LengthPrefixedStringSerializer implements 
KeySerializer {
 
         @Override

Reply via email to