Upgrade to Lucene r1657571.

Closes #9587

Squashed commit of the following:

commit 23ac91dca4b949638ca1d3842fd6db2e00ee1d36
Author: Adrien Grand <jpountz@gmail.com>
Date:   Thu Feb 5 18:42:28 2015 +0100

    Do not compute scores if aggregations do not need it (like top_hits) or use a script (which might compute scores).

commit 51262fe2681c067337ca41ab88096ef80a2e8ebb
Author: Adrien Grand <jpountz@gmail.com>
Date:   Thu Feb 5 15:58:38 2015 +0100

    Fix more compile errors.

commit a074895d55b8b3c898d23f7f5334e564d5271a56
Author: Robert Muir <rmuir@apache.org>
Date:   Thu Feb 5 09:31:22 2015 -0500

    fix a few more obvious ones

commit 399c41186cb3c9be70107f6c25b51fc4844f8fde
Author: Robert Muir <rmuir@apache.org>
Date:   Thu Feb 5 09:28:32 2015 -0500

    fix some collectors and queries

commit 5f46c2f846c5020d5749233b71cbe66ae534ba51
Author: Robert Muir <rmuir@apache.org>
Date:   Thu Feb 5 09:24:24 2015 -0500

    upgrade to lucene r1657571
This commit is contained in:
Robert Muir 2015-02-06 08:53:20 -05:00
parent 487ef80c35
commit 9c9b5c27d3
31 changed files with 198 additions and 37 deletions

View File

@ -32,7 +32,7 @@
<properties>
<lucene.version>5.1.0</lucene.version>
<lucene.maven.version>5.1.0-snapshot-1656366</lucene.maven.version>
<lucene.maven.version>5.1.0-snapshot-1657571</lucene.maven.version>
<tests.jvms>auto</tests.jvms>
<tests.shuffle>true</tests.shuffle>
<tests.output>onerror</tests.output>
@ -54,7 +54,7 @@
</repository>
<repository>
<id>Lucene snapshots</id>
<url>https://download.elasticsearch.org/lucenesnapshots/1656366</url>
<url>https://download.elasticsearch.org/lucenesnapshots/1657571</url>
</repository>
</repositories>

View File

@ -536,6 +536,11 @@ public class Lucene {
public void doSetNextReader(LeafReaderContext atomicReaderContext) throws IOException {
leafCollector = delegate.getLeafCollector(atomicReaderContext);
}
@Override
public boolean needsScores() {
return delegate.needsScores();
}
}
private Lucene() {

View File

@ -60,4 +60,9 @@ public class MinimumScoreCollector extends SimpleCollector {
public void doSetNextReader(LeafReaderContext context) throws IOException {
leafCollector = collector.getLeafCollector(context);
}
@Override
public boolean needsScores() {
return true;
}
}

View File

@ -62,7 +62,7 @@ public class AllTermQuery extends SpanTermQuery {
}
@Override
public AllTermSpanScorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public AllTermSpanScorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
if (this.stats == null) {
return null;
}
@ -146,7 +146,7 @@ public class AllTermQuery extends SpanTermQuery {
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException{
AllTermSpanScorer scorer = scorer(context, context.reader().getLiveDocs());
AllTermSpanScorer scorer = scorer(context, context.reader().getLiveDocs(), true);
if (scorer != null) {
int newDoc = scorer.advance(doc);
if (newDoc == doc) {

View File

@ -97,4 +97,8 @@ public class FilteredCollector implements XCollector {
};
}
@Override
public boolean needsScores() {
return collector.needsScores();
}
}

View File

@ -57,7 +57,7 @@ public final class MatchNoDocsQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
return null;
}

View File

@ -43,4 +43,9 @@ public class NoopCollector extends SimpleCollector {
@Override
protected void doSetNextReader(LeafReaderContext context) throws IOException {
}
@Override
public boolean needsScores() {
return false;
}
}

View File

@ -152,11 +152,11 @@ public class FiltersFunctionScoreQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
// we ignore scoreDocsInOrder parameter, because we need to score in
// order if documents are scored with a script. The
// ShardLookup depends on in order scoring.
Scorer subQueryScorer = subQueryWeight.scorer(context, acceptDocs);
Scorer subQueryScorer = subQueryWeight.scorer(context, acceptDocs, needsScores);
if (subQueryScorer == null) {
return null;
}

View File

@ -120,11 +120,11 @@ public class FunctionScoreQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
// we ignore scoreDocsInOrder parameter, because we need to score in
// order if documents are scored with a script. The
// ShardLookup depends on in order scoring.
Scorer subQueryScorer = subQueryWeight.scorer(context, acceptDocs);
Scorer subQueryScorer = subQueryWeight.scorer(context, acceptDocs, needsScores);
if (subQueryScorer == null) {
return null;
}

View File

@ -96,4 +96,9 @@ final class QueriesLoaderCollector extends SimpleCollector {
@Override
public void setScorer(Scorer scorer) throws IOException {
}
@Override
public boolean needsScores() {
return false;
}
}

View File

@ -75,14 +75,14 @@ public class FilteredQueryParser implements QueryParser {
}
@Override
public Scorer filteredScorer(LeafReaderContext context, Weight weight, DocIdSet docIdSet) throws IOException {
public Scorer filteredScorer(LeafReaderContext context, Weight weight, DocIdSet docIdSet, boolean needsScores) throws IOException {
// CHANGE: If threshold is 0, always pass down the accept docs, don't pay the price of calling nextDoc even...
final Bits filterAcceptDocs = docIdSet.bits();
if (threshold == 0) {
if (filterAcceptDocs != null) {
return weight.scorer(context, filterAcceptDocs);
return weight.scorer(context, filterAcceptDocs, needsScores);
} else {
return FilteredQuery.LEAP_FROG_QUERY_FIRST_STRATEGY.filteredScorer(context, weight, docIdSet);
return FilteredQuery.LEAP_FROG_QUERY_FIRST_STRATEGY.filteredScorer(context, weight, docIdSet, needsScores);
}
}
@ -91,11 +91,11 @@ public class FilteredQueryParser implements QueryParser {
// default value, don't iterate on only apply filter after query if its not a "fast" docIdSet
// TODO: is there a way we could avoid creating an iterator here?
if (filterAcceptDocs != null && DocIdSets.isBroken(docIdSet.iterator())) {
return FilteredQuery.QUERY_FIRST_FILTER_STRATEGY.filteredScorer(context, weight, docIdSet);
return FilteredQuery.QUERY_FIRST_FILTER_STRATEGY.filteredScorer(context, weight, docIdSet, needsScores);
}
}
return super.filteredScorer(context, weight, docIdSet);
return super.filteredScorer(context, weight, docIdSet, needsScores);
}
@Override

View File

@ -51,6 +51,7 @@ import java.util.Set;
/**
*
*/
// TODO: Remove me and move the logic to ChildrenQuery when needsScore=false
public class ChildrenConstantScoreQuery extends Query {
private final IndexParentChildFieldData parentChildIndexFieldData;
@ -221,7 +222,7 @@ public class ChildrenConstantScoreQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
if (remaining == 0) {
return null;
}

View File

@ -288,7 +288,7 @@ public class ChildrenQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
DocIdSet parentsSet = parentFilter.getDocIdSet(context, acceptDocs);
if (DocIdSets.isEmpty(parentsSet) || remaining == 0) {
return null;

View File

@ -73,7 +73,7 @@ public class CustomQueryWrappingFilter extends NoCacheFilter implements Releasab
final DocIdSet set = new DocIdSet() {
@Override
public DocIdSetIterator iterator() throws IOException {
return weight.scorer(leaf, null);
return weight.scorer(leaf, null, false);
}
@Override
public boolean isCacheable() { return false; }

View File

@ -187,7 +187,7 @@ public class ParentConstantScoreQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
DocIdSet childrenDocIdSet = childrenFilter.getDocIdSet(context, acceptDocs);
if (DocIdSets.isEmpty(childrenDocIdSet)) {
return null;

View File

@ -251,7 +251,7 @@ public class ParentQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
DocIdSet childrenDocSet = childrenFilter.getDocIdSet(context, acceptDocs);
if (DocIdSets.isEmpty(childrenDocSet)) {
return null;

View File

@ -323,8 +323,11 @@ public class TopChildrenQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
ParentDoc[] readerParentDocs = parentDocs.get(context.reader().getCoreCacheKey());
// We ignore the needsScores parameter here because there isn't really anything that we
// can improve by ignoring scores. Actually this query does not really make sense
// with needsScores=false...
if (readerParentDocs != null) {
if (scoreType == ScoreType.MIN) {
return new ParentScorer(this, readerParentDocs) {

View File

@ -105,8 +105,8 @@ public class IncludeNestedDocsQuery extends Query {
}
@Override
public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOException {
final Scorer parentScorer = parentWeight.scorer(context, acceptDocs);
public Scorer scorer(LeafReaderContext context, Bits acceptDocs, boolean needsScores) throws IOException {
final Scorer parentScorer = parentWeight.scorer(context, acceptDocs, needsScores);
// no matches
if (parentScorer == null) {

View File

@ -247,8 +247,9 @@ public class IndicesTTLService extends AbstractLifecycleComponent<IndicesTTLServ
public void setScorer(Scorer scorer) {
}
public boolean acceptsDocsOutOfOrder() {
return true;
@Override
public boolean needsScores() {
return false;
}
public void collect(int doc) {

View File

@ -110,6 +110,16 @@ abstract class QueryCollector extends SimpleCollector {
}
}
@Override
public boolean needsScores() {
for (Collector collector : aggregatorCollector) {
if (collector.needsScores()) {
return true;
}
}
return false;
}
@Override
public void doSetNextReader(LeafReaderContext context) throws IOException {
// we use the UID because id might not be indexed

View File

@ -94,7 +94,7 @@ public class AggregationPhase implements SearchPhase {
}
context.aggregations().aggregators(aggregators);
if (!collectors.isEmpty()) {
context.searcher().addMainQueryCollector(new AggregationsCollector(collectors, aggregationContext));
context.searcher().queryCollectors().put(AggregationPhase.class, new AggregationsCollector(collectors, aggregationContext));
}
aggregationContext.setNextReader(context.searcher().getIndexReader().getContext());
}
@ -148,6 +148,7 @@ public class AggregationPhase implements SearchPhase {
// disable aggregations so that they don't run on next pages in case of scrolling
context.aggregations(null);
context.searcher().queryCollectors().remove(AggregationPhase.class);
}
@ -166,6 +167,11 @@ public class AggregationPhase implements SearchPhase {
aggregationContext.setScorer(scorer);
}
@Override
public boolean needsScores() {
return aggregationContext.needsScores();
}
@Override
public void collect(int doc) throws IOException {
for (Aggregator collector : collectors) {

View File

@ -99,6 +99,15 @@ public class AggregatorFactories {
}
}
public boolean needsScores() {
for (AggregatorFactory factory : factories) {
if (factory.needsScores()) {
return true;
}
}
return false;
}
private final static class Empty extends AggregatorFactories {
private static final AggregatorFactory[] EMPTY_FACTORIES = new AggregatorFactory[0];

View File

@ -101,6 +101,10 @@ public abstract class AggregatorFactory {
this.metaData = metaData;
}
public boolean needsScores() {
return factories.needsScores();
}
/**
* Utility method. Given an {@link AggregatorFactory} that creates {@link Aggregator}s that only know how
* to collect bucket <tt>0</tt>, this returns an aggregator that can collect any bucket.

View File

@ -176,5 +176,15 @@ public class TopHitsAggregator extends MetricsAggregator implements ScorerAware
throw new AggregationInitializationException("Aggregator [" + name + "] of type [" + type + "] cannot accept sub-aggregations");
}
@Override
public boolean needsScores() {
Sort sort = subSearchContext.sort();
if (sort != null) {
return sort.needsScores() || subSearchContext.trackScores();
} else {
// sort by score
return true;
}
}
}
}

View File

@ -63,6 +63,13 @@ public class AggregationContext implements ReaderContextAware, ScorerAware {
this.searchContext = searchContext;
}
/**
* Whether aggregators which are attached to this context need scores.
*/
public boolean needsScores() {
return searchContext.aggregations().factories().needsScores();
}
public SearchContext searchContext() {
return searchContext;
}

View File

@ -64,6 +64,15 @@ public abstract class ValuesSourceAggregatorFactory<VS extends ValuesSource> ext
}
}
@Override
public boolean needsScores() {
// TODO: we have no way to know whether scripts use the score so
// for now we assume that they do but in the future it would be
// nice to be able to know if they need scores so that the query
// would only provuce scores if required.
return config.script != null || super.needsScores();
}
protected abstract Aggregator createUnmapped(AggregationContext aggregationContext, Aggregator parent, Map<String, Object> metaData) throws IOException;
protected abstract Aggregator doCreateInternal(VS valuesSource, AggregationContext aggregationContext, Aggregator parent, boolean collectsFromSingleBucket, Map<String, Object> metaData) throws IOException;

View File

@ -39,7 +39,9 @@ import org.elasticsearch.search.internal.SearchContext.Lifetime;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Context-aware extension of {@link IndexSearcher}.
@ -60,7 +62,7 @@ public class ContextIndexSearcher extends IndexSearcher implements Releasable {
private CachedDfSource dfSource;
private List<Collector> queryCollectors;
private Map<Class<?>, Collector> queryCollectors;
private Stage currentState = Stage.NA;
@ -84,11 +86,11 @@ public class ContextIndexSearcher extends IndexSearcher implements Releasable {
* {@link org.elasticsearch.common.lucene.search.XCollector} allowing for a callback
* when collection is done.
*/
public void addMainQueryCollector(Collector collector) {
public Map<Class<?>, Collector> queryCollectors() {
if (queryCollectors == null) {
queryCollectors = new ArrayList<>();
queryCollectors = new HashMap<>();
}
queryCollectors.add(collector);
return queryCollectors;
}
public void inStage(Stage stage) {
@ -151,7 +153,7 @@ public class ContextIndexSearcher extends IndexSearcher implements Releasable {
collector = new FilteredCollector(collector, searchContext.parsedPostFilter().filter());
}
if (queryCollectors != null && !queryCollectors.isEmpty()) {
ArrayList<Collector> allCollectors = new ArrayList<>(queryCollectors);
ArrayList<Collector> allCollectors = new ArrayList<>(queryCollectors.values());
allCollectors.add(collector);
collector = MultiCollector.wrap(allCollectors);
}
@ -183,7 +185,7 @@ public class ContextIndexSearcher extends IndexSearcher implements Releasable {
if (currentState == Stage.MAIN_QUERY) {
if (queryCollectors != null && !queryCollectors.isEmpty()) {
for (Collector queryCollector : queryCollectors) {
for (Collector queryCollector : queryCollectors.values()) {
if (queryCollector instanceof XCollector) {
((XCollector) queryCollector).postCollection();
}

View File

@ -100,6 +100,11 @@ public class ScanContext {
return new TopDocs(docs.size(), docs.toArray(new ScoreDoc[docs.size()]), 0f);
}
@Override
public boolean needsScores() {
return trackScores;
}
@Override
public void setScorer(Scorer scorer) throws IOException {
this.scorer = scorer;

View File

@ -0,0 +1,64 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.search.aggregations;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.index.IndexService;
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
import java.io.IOException;
public class AggregationCollectorTests extends ElasticsearchSingleNodeTest {
public void testNeedsScores() throws Exception {
IndexService index = createIndex("idx");
client().prepareIndex("idx", "type", "1").setSource("f", 5).execute().get();
client().admin().indices().prepareRefresh("idx").get();
// simple field aggregation, no scores needed
String fieldAgg = "{ \"my_terms\": {\"terms\": {\"field\": \"f\"}}}";
assertFalse(needsScores(index, fieldAgg));
// agg on a script => scores are needed
String scriptAgg = "{ \"my_terms\": {\"terms\": {\"script\": \"doc['f'].value\"}}}";
assertTrue(needsScores(index, scriptAgg));
// make sure the information is propagated to sub aggregations
String subFieldAgg = "{ \"my_outer_terms\": { \"terms\": { \"field\": \"f\" }, \"aggs\": " + fieldAgg + "}}";
assertFalse(needsScores(index, subFieldAgg));
String subScriptAgg = "{ \"my_outer_terms\": { \"terms\": { \"field\": \"f\" }, \"aggs\": " + scriptAgg + "}}";
assertTrue(needsScores(index, subScriptAgg));
// top_hits is a particular example of an aggregation that needs scores
String topHitsAgg = "{ \"my_hits\": {\"top_hits\": {}}}";
assertTrue(needsScores(index, topHitsAgg));
}
private boolean needsScores(IndexService index, String agg) throws IOException {
AggregatorParsers parser = getInstanceFromNode(AggregatorParsers.class);
XContentParser aggParser = JsonXContent.jsonXContent.createParser(agg);
aggParser.nextToken();
final AggregatorFactories factories = parser.parseAggregators(aggParser, createSearchContext(index));
return factories.needsScores();
}
}

View File

@ -38,7 +38,9 @@ import org.elasticsearch.index.search.nested.NonNestedDocsFilter;
import org.elasticsearch.search.aggregations.AggregationPhase;
import org.elasticsearch.search.aggregations.Aggregator;
import org.elasticsearch.search.aggregations.AggregatorFactories;
import org.elasticsearch.search.aggregations.SearchContextAggregations;
import org.elasticsearch.search.aggregations.support.AggregationContext;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.test.ElasticsearchSingleNodeLuceneTestCase;
import org.junit.Test;
@ -113,11 +115,13 @@ public class NestedAggregatorTest extends ElasticsearchSingleNodeLuceneTestCase
IndexService indexService = createIndex("test");
indexService.mapperService().merge("test", new CompressedString(PutMappingRequest.buildFromSimplifiedDef("test", "nested_field", "type=nested").string()), true);
AggregationContext context = new AggregationContext(createSearchContext(indexService));
SearchContext searchContext = createSearchContext(indexService);
AggregationContext context = new AggregationContext(searchContext);
AggregatorFactories.Builder builder = AggregatorFactories.builder();
builder.add(new NestedAggregator.Factory("test", "nested_field", FilterCachingPolicy.ALWAYS_CACHE));
AggregatorFactories factories = builder.build();
searchContext.aggregations(new SearchContextAggregations(factories));
Aggregator[] aggs = factories.createTopLevelAggregators(context);
AggregationPhase.AggregationsCollector collector = new AggregationPhase.AggregationsCollector(Arrays.asList(aggs), context);
// A regular search always exclude nested docs, so we use NonNestedDocsFilter.INSTANCE here (otherwise MatchAllDocsQuery would be sufficient)

View File

@ -77,6 +77,7 @@ public class TestSearchContext extends SearchContext {
int size;
private int terminateAfter = DEFAULT_TERMINATE_AFTER;
private String[] types;
private SearchContextAggregations aggregations;
public TestSearchContext(ThreadPool threadPool,PageCacheRecycler pageCacheRecycler, BigArrays bigArrays, IndexService indexService, FilterCache filterCache, IndexFieldDataService indexFieldDataService) {
this.pageCacheRecycler = pageCacheRecycler;
@ -143,7 +144,7 @@ public class TestSearchContext extends SearchContext {
@Override
public int numberOfShards() {
return 0;
return 1;
}
@Override
@ -183,12 +184,13 @@ public class TestSearchContext extends SearchContext {
@Override
public SearchContextAggregations aggregations() {
return null;
return aggregations;
}
@Override
public SearchContext aggregations(SearchContextAggregations aggregations) {
return null;
this.aggregations = aggregations;
return this;
}
@Override
@ -297,7 +299,7 @@ public class TestSearchContext extends SearchContext {
@Override
public ScriptService scriptService() {
return null;
return indexService.injector().getInstance(ScriptService.class);
}
@Override
@ -529,7 +531,7 @@ public class TestSearchContext extends SearchContext {
@Override
public SearchLookup lookup() {
return null;
return new SearchLookup(mapperService(), fieldData(), null);
}
@Override