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

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


The following commit(s) were added to refs/heads/master by this push:
     new d9c08e5f159b [SPARK-57639][CORE] Fix NPE in `InMemoryStore.count` for 
a type with no instances
d9c08e5f159b is described below

commit d9c08e5f159be8a8391250e83fdf5e01ed9ef3ca
Author: YangJie <[email protected]>
AuthorDate: Wed Jun 24 11:14:49 2026 +0800

    [SPARK-57639][CORE] Fix NPE in `InMemoryStore.count` for a type with no 
instances
    
    ### What changes were proposed in this pull request?
    
    This PR fixes a `NullPointerException` in `InMemoryStore.count(Class, 
String, Object)` and makes its behavior consistent with the other `KVStore` 
implementations.
    
    `inMemoryLists.get(type)` returns `null` for a type that has never been 
written, and the method then dereferenced it via 
`list.getIndexAccessor(index)`, throwing an NPE. The fix adds an early branch 
for the never-written type that returns `0`. It still validates the index name 
first (via a `KVTypeInfo` built from the type, mirroring how 
`LevelDB`/`RocksDB` lazily build their type info), so an unknown index name 
throws `IllegalArgumentException` exactly as it does for a populated type  [...]
    
    ### Why are the changes needed?
    
    It is a bug: counting by an indexed value for a type with no instances 
should return `0`, not throw. The sibling one-arg `count(Class)` already 
returns `0` in this situation, and both `LevelDB.count` and `RocksDB.count` 
return `0` for an absent type, so `InMemoryStore` was inconsistent with its 
siblings. The 3-arg `count` was also previously uncovered by 
`InMemoryStoreSuite`.
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    Added `InMemoryStoreSuite.testCountByIndexValue`, which covers an absent 
type returning `0`, an unknown index name throwing `IllegalArgumentException` 
for both the absent-type and populated-type paths, and the match-counting cases 
for a populated type. Ran `build/sbt 'kvstore/testOnly *InMemoryStoreSuite'` 
and all tests pass.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    No.
    
    Closes #56701 from LuciferYang/SPARK-kvstore-inmemory-count-npe.
    
    Authored-by: YangJie <[email protected]>
    Signed-off-by: yangjie01 <[email protected]>
---
 .../apache/spark/util/kvstore/InMemoryStore.java   |  8 +++++
 .../spark/util/kvstore/InMemoryStoreSuite.java     | 35 ++++++++++++++++++++++
 2 files changed, 43 insertions(+)

diff --git 
a/common/kvstore/src/main/java/org/apache/spark/util/kvstore/InMemoryStore.java 
b/common/kvstore/src/main/java/org/apache/spark/util/kvstore/InMemoryStore.java
index 9a45a10532de..bb9c68bc8a96 100644
--- 
a/common/kvstore/src/main/java/org/apache/spark/util/kvstore/InMemoryStore.java
+++ 
b/common/kvstore/src/main/java/org/apache/spark/util/kvstore/InMemoryStore.java
@@ -65,6 +65,14 @@ public class InMemoryStore implements KVStore {
   @Override
   public long count(Class<?> type, String index, Object indexedValue) throws 
Exception {
     InstanceList<?> list = inMemoryLists.get(type);
+    if (list == null) {
+      // The type has never been written, so there are no matching entities. 
Still validate
+      // the index name (via reflection on the type) before returning, so that 
an unknown
+      // index fails the same way it does for a populated type and for the 
LevelDB/RocksDB
+      // stores, instead of being silently swallowed.
+      new KVTypeInfo(type).getAccessor(index);
+      return 0;
+    }
     int count = 0;
     Object comparable = asKey(indexedValue);
     KVTypeInfo.Accessor accessor = list.getIndexAccessor(index);
diff --git 
a/common/kvstore/src/test/java/org/apache/spark/util/kvstore/InMemoryStoreSuite.java
 
b/common/kvstore/src/test/java/org/apache/spark/util/kvstore/InMemoryStoreSuite.java
index c7ae03f07829..ed4e58c974af 100644
--- 
a/common/kvstore/src/test/java/org/apache/spark/util/kvstore/InMemoryStoreSuite.java
+++ 
b/common/kvstore/src/test/java/org/apache/spark/util/kvstore/InMemoryStoreSuite.java
@@ -237,4 +237,39 @@ public class InMemoryStoreSuite {
     store.delete(t4.getClass(), t4.key);
     assertEquals(0, store.count(CustomType2.class));
   }
+
+  @Test
+  public void testCountByIndexValue() throws Exception {
+    KVStore store = new InMemoryStore();
+
+    // Counting by an indexed value for a type that was never written must 
return 0 instead
+    // of throwing, consistent with count(Class) and the LevelDB/RocksDB 
implementations.
+    assertEquals(0, store.count(CustomType1.class, "id", "id"));
+
+    // An unknown index name must be rejected even when the type has no rows, 
matching both
+    // the populated-type path and the LevelDB/RocksDB stores 
(IllegalArgumentException),
+    // rather than being silently swallowed into a 0.
+    assertThrows(IllegalArgumentException.class,
+      () -> store.count(CustomType1.class, "nonexistentIndex", "id"));
+
+    CustomType1 t1 = new CustomType1();
+    t1.key = "key1";
+    t1.id = "id";
+    t1.name = "name1";
+    store.write(t1);
+
+    CustomType1 t2 = new CustomType1();
+    t2.key = "key2";
+    t2.id = "id";
+    t2.name = "name2";
+    store.write(t2);
+
+    assertEquals(2, store.count(CustomType1.class, "id", "id"));
+    assertEquals(1, store.count(CustomType1.class, "name", "name1"));
+    assertEquals(0, store.count(CustomType1.class, "name", "nonexistent"));
+
+    // Same rejection for a populated type, confirming the absent/present 
paths agree.
+    assertThrows(IllegalArgumentException.class,
+      () -> store.count(CustomType1.class, "nonexistentIndex", "id"));
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to