Guard FieldExistsQuery against null pointers (#11794)

FieldExistsQuery checks if there are points for a certain field, and then retrieves the
corresponding point values. When all documents that had points for a certain field have
been deleted from a certain segments, as well as merged away, field info may report
that there are points yet the corresponding point values are null.

With this change we add a null check in FieldExistsQuery. Long term, we will likely want
to prevent this situation from happening.

Relates #11393
This commit is contained in:
Luca Cavanna 2022-09-20 15:38:38 +02:00 committed by GitHub
parent 6c46662b43
commit 4eaebee686
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 2 deletions

View File

@ -169,6 +169,8 @@ Bug Fixes
* LUCENE-10674: Ensure BitSetConjDISI returns NO_MORE_DOCS when sub-iterator exhausts. (Jack Mazanec)
* GITHUB#11794: Guard FieldExistsQuery against null pointers (Luca Cavanna)
Build
---------------------

View File

@ -230,9 +230,11 @@ public class FieldExistsQuery extends Query {
!= DocValuesType.NONE) { // the field indexes doc values
if (reader.hasDeletions() == false) {
if (fieldInfo.getPointDimensionCount() > 0) {
return reader.getPointValues(field).getDocCount();
PointValues pointValues = reader.getPointValues(field);
return pointValues == null ? 0 : pointValues.getDocCount();
} else if (fieldInfo.getIndexOptions() != IndexOptions.NONE) {
return reader.terms(field).getDocCount();
Terms terms = reader.terms(field);
return terms == null ? 0 : terms.getDocCount();
}
}

View File

@ -702,6 +702,64 @@ public class TestFieldExistsQuery extends LuceneTestCase {
return v;
}
public void testDeleteAllPointDocs() throws Exception {
try (Directory dir = newDirectory();
RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
Document doc = new Document();
doc.add(new StringField("id", "0", Field.Store.NO));
doc.add(new LongPoint("long", 17));
doc.add(new NumericDocValuesField("long", 17));
iw.addDocument(doc);
// add another document before the flush, otherwise the segment only has the document that
// we are going to delete and the merge simply ignores the segment without carrying over its
// field infos
iw.addDocument(new Document());
// make sure there are two segments or force merge will be a no-op
iw.flush();
iw.addDocument(new Document());
iw.commit();
iw.deleteDocuments(new Term("id", "0"));
iw.forceMerge(1);
try (IndexReader reader = iw.getReader()) {
assertTrue(reader.leaves().size() == 1 && reader.hasDeletions() == false);
IndexSearcher searcher = newSearcher(reader);
assertEquals(0, searcher.count(new FieldExistsQuery("long")));
}
}
}
public void testDeleteAllTermDocs() throws Exception {
try (Directory dir = newDirectory();
RandomIndexWriter iw = new RandomIndexWriter(random(), dir)) {
Document doc = new Document();
doc.add(new StringField("id", "0", Field.Store.NO));
doc.add(new StringField("str", "foo", Store.NO));
doc.add(new SortedDocValuesField("str", new BytesRef("foo")));
iw.addDocument(doc);
// add another document before the flush, otherwise the segment only has the document that
// we are going to delete and the merge simply ignores the segment without carrying over its
// field infos
iw.addDocument(new Document());
// make sure there are two segments or force merge will be a no-op
iw.flush();
iw.addDocument(new Document());
iw.commit();
iw.deleteDocuments(new Term("id", "0"));
iw.forceMerge(1);
try (IndexReader reader = iw.getReader()) {
assertTrue(reader.leaves().size() == 1 && reader.hasDeletions() == false);
IndexSearcher searcher = newSearcher(reader);
assertEquals(0, searcher.count(new FieldExistsQuery("str")));
}
}
}
private void assertSameMatches(IndexSearcher searcher, Query q1, Query q2, boolean scores)
throws IOException {
final int maxDoc = searcher.getIndexReader().maxDoc();