`TopDocsCollectorContext` can already shortcut hit counts on `match_all` and `term` queries when there are no deletions. This change adds this ability for `exists` queries if the index doesn't have deletions and fields are indexed. Closes #37475
This commit is contained in:
parent
af4918ebff
commit
2fe1fa8972
|
@ -19,12 +19,18 @@
|
||||||
|
|
||||||
package org.elasticsearch.search.query;
|
package org.elasticsearch.search.query;
|
||||||
|
|
||||||
|
import org.apache.lucene.index.FieldInfo;
|
||||||
|
import org.apache.lucene.index.FieldInfos;
|
||||||
|
import org.apache.lucene.index.IndexOptions;
|
||||||
import org.apache.lucene.index.IndexReader;
|
import org.apache.lucene.index.IndexReader;
|
||||||
import org.apache.lucene.index.LeafReaderContext;
|
import org.apache.lucene.index.LeafReaderContext;
|
||||||
|
import org.apache.lucene.index.PointValues;
|
||||||
import org.apache.lucene.index.Term;
|
import org.apache.lucene.index.Term;
|
||||||
|
import org.apache.lucene.index.Terms;
|
||||||
import org.apache.lucene.search.BoostQuery;
|
import org.apache.lucene.search.BoostQuery;
|
||||||
import org.apache.lucene.search.Collector;
|
import org.apache.lucene.search.Collector;
|
||||||
import org.apache.lucene.search.ConstantScoreQuery;
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
|
import org.apache.lucene.search.DocValuesFieldExistsQuery;
|
||||||
import org.apache.lucene.search.FieldDoc;
|
import org.apache.lucene.search.FieldDoc;
|
||||||
import org.apache.lucene.search.MatchAllDocsQuery;
|
import org.apache.lucene.search.MatchAllDocsQuery;
|
||||||
import org.apache.lucene.search.MultiCollector;
|
import org.apache.lucene.search.MultiCollector;
|
||||||
|
@ -125,6 +131,7 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
Collector create(Collector in) {
|
Collector create(Collector in) {
|
||||||
assert in == null;
|
assert in == null;
|
||||||
return collector;
|
return collector;
|
||||||
|
@ -357,6 +364,29 @@ abstract class TopDocsCollectorContext extends QueryCollectorContext {
|
||||||
count += context.reader().docFreq(term);
|
count += context.reader().docFreq(term);
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
} else if (query.getClass() == DocValuesFieldExistsQuery.class && reader.hasDeletions() == false) {
|
||||||
|
final String field = ((DocValuesFieldExistsQuery) query).getField();
|
||||||
|
int count = 0;
|
||||||
|
for (LeafReaderContext context : reader.leaves()) {
|
||||||
|
FieldInfos fieldInfos = context.reader().getFieldInfos();
|
||||||
|
FieldInfo fieldInfo = fieldInfos.fieldInfo(field);
|
||||||
|
if (fieldInfo != null) {
|
||||||
|
if (fieldInfo.getPointIndexDimensionCount() > 0) {
|
||||||
|
PointValues points = context.reader().getPointValues(field);
|
||||||
|
if (points != null) {
|
||||||
|
count += points.getDocCount();
|
||||||
|
}
|
||||||
|
} else if (fieldInfo.getIndexOptions() != IndexOptions.NONE) {
|
||||||
|
Terms terms = context.reader().terms(field);
|
||||||
|
if (terms != null) {
|
||||||
|
count += terms.getDocCount();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return -1; // no shortcut possible for fields that are not indexed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,10 @@ package org.elasticsearch.search.query;
|
||||||
|
|
||||||
import org.apache.lucene.document.Document;
|
import org.apache.lucene.document.Document;
|
||||||
import org.apache.lucene.document.Field.Store;
|
import org.apache.lucene.document.Field.Store;
|
||||||
|
import org.apache.lucene.document.LatLonDocValuesField;
|
||||||
|
import org.apache.lucene.document.LatLonPoint;
|
||||||
import org.apache.lucene.document.NumericDocValuesField;
|
import org.apache.lucene.document.NumericDocValuesField;
|
||||||
|
import org.apache.lucene.document.SortedSetDocValuesField;
|
||||||
import org.apache.lucene.document.StringField;
|
import org.apache.lucene.document.StringField;
|
||||||
import org.apache.lucene.index.DirectoryReader;
|
import org.apache.lucene.index.DirectoryReader;
|
||||||
import org.apache.lucene.index.IndexReader;
|
import org.apache.lucene.index.IndexReader;
|
||||||
|
@ -35,6 +38,7 @@ import org.apache.lucene.search.BooleanClause.Occur;
|
||||||
import org.apache.lucene.search.BooleanQuery;
|
import org.apache.lucene.search.BooleanQuery;
|
||||||
import org.apache.lucene.search.Collector;
|
import org.apache.lucene.search.Collector;
|
||||||
import org.apache.lucene.search.ConstantScoreQuery;
|
import org.apache.lucene.search.ConstantScoreQuery;
|
||||||
|
import org.apache.lucene.search.DocValuesFieldExistsQuery;
|
||||||
import org.apache.lucene.search.FieldComparator;
|
import org.apache.lucene.search.FieldComparator;
|
||||||
import org.apache.lucene.search.FieldDoc;
|
import org.apache.lucene.search.FieldDoc;
|
||||||
import org.apache.lucene.search.FilterCollector;
|
import org.apache.lucene.search.FilterCollector;
|
||||||
|
@ -50,6 +54,7 @@ import org.apache.lucene.search.TermQuery;
|
||||||
import org.apache.lucene.search.TotalHitCountCollector;
|
import org.apache.lucene.search.TotalHitCountCollector;
|
||||||
import org.apache.lucene.search.Weight;
|
import org.apache.lucene.search.Weight;
|
||||||
import org.apache.lucene.store.Directory;
|
import org.apache.lucene.store.Directory;
|
||||||
|
import org.apache.lucene.util.BytesRef;
|
||||||
import org.elasticsearch.action.search.SearchTask;
|
import org.elasticsearch.action.search.SearchTask;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.index.query.ParsedQuery;
|
import org.elasticsearch.index.query.ParsedQuery;
|
||||||
|
@ -92,18 +97,20 @@ public class QueryPhaseTests extends IndexShardTestCase {
|
||||||
closeShards(indexShard);
|
closeShards(indexShard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void countTestCase(Query query, IndexReader reader, boolean shouldCollect) throws Exception {
|
private void countTestCase(Query query, IndexReader reader, boolean shouldCollectSearch, boolean shouldCollectCount) throws Exception {
|
||||||
TestSearchContext context = new TestSearchContext(null, indexShard);
|
TestSearchContext context = new TestSearchContext(null, indexShard);
|
||||||
context.parsedQuery(new ParsedQuery(query));
|
context.parsedQuery(new ParsedQuery(query));
|
||||||
context.setSize(0);
|
context.setSize(0);
|
||||||
context.setTask(new SearchTask(123L, "", "", "", null, Collections.emptyMap()));
|
context.setTask(new SearchTask(123L, "", "", "", null, Collections.emptyMap()));
|
||||||
|
|
||||||
final IndexSearcher searcher = shouldCollect ? new IndexSearcher(reader) :
|
final IndexSearcher searcher = shouldCollectSearch ? new IndexSearcher(reader) :
|
||||||
getAssertingEarlyTerminationSearcher(reader, 0);
|
getAssertingEarlyTerminationSearcher(reader, 0);
|
||||||
|
|
||||||
final boolean rescore = QueryPhase.execute(context, searcher, checkCancelled -> {});
|
final boolean rescore = QueryPhase.execute(context, searcher, checkCancelled -> {});
|
||||||
assertFalse(rescore);
|
assertFalse(rescore);
|
||||||
assertEquals(searcher.count(query), context.queryResult().topDocs().topDocs.totalHits.value);
|
IndexSearcher countSearcher = shouldCollectCount ? new IndexSearcher(reader) :
|
||||||
|
getAssertingEarlyTerminationSearcher(reader, 0);
|
||||||
|
assertEquals(countSearcher.count(query), context.queryResult().topDocs().topDocs.totalHits.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void countTestCase(boolean withDeletions) throws Exception {
|
private void countTestCase(boolean withDeletions) throws Exception {
|
||||||
|
@ -115,9 +122,14 @@ public class QueryPhaseTests extends IndexShardTestCase {
|
||||||
Document doc = new Document();
|
Document doc = new Document();
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
doc.add(new StringField("foo", "bar", Store.NO));
|
doc.add(new StringField("foo", "bar", Store.NO));
|
||||||
|
doc.add(new SortedSetDocValuesField("foo", new BytesRef("bar")));
|
||||||
|
doc.add(new SortedSetDocValuesField("docValuesOnlyField", new BytesRef("bar")));
|
||||||
|
doc.add(new LatLonDocValuesField("latLonDVField", 1.0, 1.0));
|
||||||
|
doc.add(new LatLonPoint("latLonDVField", 1.0, 1.0));
|
||||||
}
|
}
|
||||||
if (randomBoolean()) {
|
if (randomBoolean()) {
|
||||||
doc.add(new StringField("foo", "baz", Store.NO));
|
doc.add(new StringField("foo", "baz", Store.NO));
|
||||||
|
doc.add(new SortedSetDocValuesField("foo", new BytesRef("baz")));
|
||||||
}
|
}
|
||||||
if (withDeletions && (rarely() || i == 0)) {
|
if (withDeletions && (rarely() || i == 0)) {
|
||||||
doc.add(new StringField("delete", "yes", Store.NO));
|
doc.add(new StringField("delete", "yes", Store.NO));
|
||||||
|
@ -132,16 +144,25 @@ public class QueryPhaseTests extends IndexShardTestCase {
|
||||||
Query matchAllCsq = new ConstantScoreQuery(matchAll);
|
Query matchAllCsq = new ConstantScoreQuery(matchAll);
|
||||||
Query tq = new TermQuery(new Term("foo", "bar"));
|
Query tq = new TermQuery(new Term("foo", "bar"));
|
||||||
Query tCsq = new ConstantScoreQuery(tq);
|
Query tCsq = new ConstantScoreQuery(tq);
|
||||||
|
Query dvfeq = new DocValuesFieldExistsQuery("foo");
|
||||||
|
Query dvfeq_points = new DocValuesFieldExistsQuery("latLonDVField");
|
||||||
|
Query dvfeqCsq = new ConstantScoreQuery(dvfeq);
|
||||||
|
// field with doc-values but not indexed will need to collect
|
||||||
|
Query dvOnlyfeq = new DocValuesFieldExistsQuery("docValuesOnlyField");
|
||||||
BooleanQuery bq = new BooleanQuery.Builder()
|
BooleanQuery bq = new BooleanQuery.Builder()
|
||||||
.add(matchAll, Occur.SHOULD)
|
.add(matchAll, Occur.SHOULD)
|
||||||
.add(tq, Occur.MUST)
|
.add(tq, Occur.MUST)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
countTestCase(matchAll, reader, false);
|
countTestCase(matchAll, reader, false, false);
|
||||||
countTestCase(matchAllCsq, reader, false);
|
countTestCase(matchAllCsq, reader, false, false);
|
||||||
countTestCase(tq, reader, withDeletions);
|
countTestCase(tq, reader, withDeletions, withDeletions);
|
||||||
countTestCase(tCsq, reader, withDeletions);
|
countTestCase(tCsq, reader, withDeletions, withDeletions);
|
||||||
countTestCase(bq, reader, true);
|
countTestCase(dvfeq, reader, withDeletions, true);
|
||||||
|
countTestCase(dvfeq_points, reader, withDeletions, true);
|
||||||
|
countTestCase(dvfeqCsq, reader, withDeletions, true);
|
||||||
|
countTestCase(dvOnlyfeq, reader, true, true);
|
||||||
|
countTestCase(bq, reader, true, true);
|
||||||
reader.close();
|
reader.close();
|
||||||
w.close();
|
w.close();
|
||||||
dir.close();
|
dir.close();
|
||||||
|
@ -541,6 +562,7 @@ public class QueryPhaseTests extends IndexShardTestCase {
|
||||||
|
|
||||||
private static IndexSearcher getAssertingEarlyTerminationSearcher(IndexReader reader, int size) {
|
private static IndexSearcher getAssertingEarlyTerminationSearcher(IndexReader reader, int size) {
|
||||||
return new IndexSearcher(reader) {
|
return new IndexSearcher(reader) {
|
||||||
|
@Override
|
||||||
protected void search(List<LeafReaderContext> leaves, Weight weight, Collector collector) throws IOException {
|
protected void search(List<LeafReaderContext> leaves, Weight weight, Collector collector) throws IOException {
|
||||||
final Collector in = new AssertingEarlyTerminationFilterCollector(collector, size);
|
final Collector in = new AssertingEarlyTerminationFilterCollector(collector, size);
|
||||||
super.search(leaves, weight, in);
|
super.search(leaves, weight, in);
|
||||||
|
|
Loading…
Reference in New Issue