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

houston pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new ce64685c7f9 SOLR-9775: Fix NPEs in QueryResultKey
ce64685c7f9 is described below

commit ce64685c7f97c18fe7caadbf6ba39afc295f4806
Author: Houston Putman <[email protected]>
AuthorDate: Thu Apr 6 10:56:28 2023 -0400

    SOLR-9775: Fix NPEs in QueryResultKey
---
 solr/CHANGES.txt                                   |  2 +
 .../org/apache/solr/search/QueryResultKey.java     | 33 ++++++++-----
 .../org/apache/solr/core/QueryResultKeyTest.java   | 57 ++++++++++++++++++++++
 3 files changed, 80 insertions(+), 12 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index e6992a6f038..aaf7d865987 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -68,6 +68,8 @@ Bug Fixes
 * SOLR-16728: Fix Classloading Exception for inter-node requests when using 
SSL and HTTP2.
   All Jetty classes are able to be shared between the Jetty server and webApp 
now. (Houston Putman)
 
+* NPE in QueryResultKey when running clustering search query in some cases.  
(Roman Kagan, Christine Poerschke via Eric Pugh)
+
 Dependency Upgrades
 ---------------------
 * PR#1494: Upgrade forbiddenapis to 3.5 (Uwe Schindler)
diff --git a/solr/core/src/java/org/apache/solr/search/QueryResultKey.java 
b/solr/core/src/java/org/apache/solr/search/QueryResultKey.java
index 6b68a13e581..caeeb337895 100644
--- a/solr/core/src/java/org/apache/solr/search/QueryResultKey.java
+++ b/solr/core/src/java/org/apache/solr/search/QueryResultKey.java
@@ -17,7 +17,10 @@
 package org.apache.solr.search;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.Sort;
 import org.apache.lucene.search.SortField;
@@ -33,7 +36,7 @@ public final class QueryResultKey implements Accountable {
 
   final Query query;
   final Sort sort;
-  final SortField[] sfields;
+  final List<SortField> sfields;
   final List<Query> filters;
   final int nc_flags; // non-comparable flags... ignored by hashCode and equals
   final int minExactCount;
@@ -41,8 +44,6 @@ public final class QueryResultKey implements Accountable {
   private final int hc; // cached hashCode
   private final long ramBytesUsed; // cached
 
-  private static SortField[] defaultSort = new SortField[0];
-
   public QueryResultKey(Query query, List<Query> filters, Sort sort, int 
nc_flags) {
     this(query, filters, sort, nc_flags, Integer.MAX_VALUE);
   }
@@ -51,20 +52,28 @@ public final class QueryResultKey implements Accountable {
       Query query, List<Query> filters, Sort sort, int nc_flags, int 
minExactCount) {
     this.query = query;
     this.sort = sort;
-    this.filters = filters;
     this.nc_flags = nc_flags;
     this.minExactCount = minExactCount;
 
     int h = query.hashCode();
 
-    if (filters != null) {
-      for (Query filt : filters)
+    if (filters == null) {
+      this.filters = null;
+    } else {
+      this.filters = 
filters.stream().filter(Objects::nonNull).collect(Collectors.toList());
+      for (Query filt : this.filters) {
         // NOTE: simple summation used here so keys with the same filters but 
in
         // different orders get the same hashCode
         h += filt.hashCode();
+      }
     }
 
-    sfields = (this.sort != null) ? this.sort.getSort() : defaultSort;
+    if (this.sort == null) {
+      this.sfields = List.of();
+    } else {
+      this.sfields =
+          
Arrays.stream(sort.getSort()).filter(Objects::nonNull).collect(Collectors.toList());
+    }
     long ramSfields = RamUsageEstimator.NUM_BYTES_ARRAY_HEADER;
     for (SortField sf : sfields) {
       h = h * 29 + sf.hashCode();
@@ -79,7 +88,7 @@ public final class QueryResultKey implements Accountable {
             + ramSfields
             + RamUsageEstimator.sizeOfObject(query, 
RamUsageEstimator.QUERY_DEFAULT_RAM_BYTES_USED)
             + RamUsageEstimator.sizeOfObject(
-                filters, RamUsageEstimator.QUERY_DEFAULT_RAM_BYTES_USED);
+                this.filters, RamUsageEstimator.QUERY_DEFAULT_RAM_BYTES_USED);
   }
 
   @Override
@@ -100,14 +109,14 @@ public final class QueryResultKey implements Accountable {
 
     // check for the thing most likely to be different (and the fastest things)
     // first.
-    if (this.sfields.length != other.sfields.length) return false;
+    if (this.sfields.size() != other.sfields.size()) return false;
     if (!this.query.equals(other.query)) return false;
     if (!unorderedCompare(this.filters, other.filters)) return false;
     if (this.minExactCount != other.minExactCount) return false;
 
-    for (int i = 0; i < sfields.length; i++) {
-      SortField sf1 = this.sfields[i];
-      SortField sf2 = other.sfields[i];
+    for (int i = 0; i < sfields.size(); i++) {
+      SortField sf1 = this.sfields.get(i);
+      SortField sf2 = other.sfields.get(i);
       if (!sf1.equals(sf2)) return false;
     }
 
diff --git a/solr/core/src/test/org/apache/solr/core/QueryResultKeyTest.java 
b/solr/core/src/test/org/apache/solr/core/QueryResultKeyTest.java
index e5f53892b24..d663b6db600 100644
--- a/solr/core/src/test/org/apache/solr/core/QueryResultKeyTest.java
+++ b/solr/core/src/test/org/apache/solr/core/QueryResultKeyTest.java
@@ -108,6 +108,63 @@ public class QueryResultKeyTest extends SolrTestCaseJ4 {
     assertKeyNotEquals(key1, key2);
   }
 
+  public void testCreateKeyWithNullFilterList() {
+    Sort sort = new Sort(new SortField("test", SortField.Type.INT));
+    BooleanQuery.Builder query = new BooleanQuery.Builder();
+    query.add(new TermQuery(new Term("test", "field")), Occur.MUST);
+
+    QueryResultKey qrk1 = new QueryResultKey(query.build(), null, sort, 1);
+    assertNotNull(qrk1);
+  }
+
+  public void testCreateKeyWithNullFilterListAndNullSort() {
+    BooleanQuery.Builder query = new BooleanQuery.Builder();
+    query.add(new TermQuery(new Term("test", "field")), Occur.MUST);
+
+    QueryResultKey qrk1 = new QueryResultKey(query.build(), null, null, 1);
+    assertNotNull(qrk1);
+  }
+
+  public void testNullFilterInFiltersList() {
+    // the hashcode should be the same even when the list
+    // of filters has a null filter
+
+    Sort sort = new Sort(new SortField("test", SortField.Type.INT));
+    BooleanQuery.Builder query = new BooleanQuery.Builder();
+    query.add(new TermQuery(new Term("test", "field")), Occur.MUST);
+
+    List<Query> filters =
+        Arrays.asList(
+            new TermQuery(new Term("test", "field")),
+            new TermQuery(new Term("test2", "field2")),
+            null);
+    QueryResultKey qrk1 = new QueryResultKey(query.build(), filters, sort, 1);
+
+    List<Query> filters2 =
+        Arrays.asList(
+            new TermQuery(new Term("test2", "field2")), new TermQuery(new 
Term("test", "field")));
+    QueryResultKey qrk2 = new QueryResultKey(query.build(), filters2, sort, 1);
+    assertKeyEquals(qrk1, qrk2);
+  }
+
+  public void testNullSortFieldInSort() {
+    // the hashcode should be the same even when the list
+    // of SortFields has a null value.
+
+    Sort sort1 = new Sort(new SortField("test", SortField.Type.INT), null);
+    Sort sort2 = new Sort(new SortField("test", SortField.Type.INT), null);
+    BooleanQuery.Builder query = new BooleanQuery.Builder();
+    query.add(new TermQuery(new Term("test", "field")), Occur.MUST);
+
+    List<Query> filters =
+        Arrays.asList(
+            new TermQuery(new Term("test", "field")), new TermQuery(new 
Term("test2", "field2")));
+    QueryResultKey qrk1 = new QueryResultKey(query.build(), filters, sort1, 1);
+
+    QueryResultKey qrk2 = new QueryResultKey(query.build(), filters, sort2, 1);
+    assertKeyEquals(qrk1, qrk2);
+  }
+
   public void testRandomQueryKeyEquality() {
 
     final int minIters = atLeast(100 * 1000);

Reply via email to