Plain highlighter should ignore parent/child queries.

The plain highligher fails when it tries to select the fragments based on a query containing either a `has_child` or `has_parent` query.

The plain highligher should just ignore parent/child queries as it makes no sense to highligh a parent match with a has_child as the child documents are not available at highlight time. Instead if child document should be highlighed inner hits should be used.

Parent/child queries already have no effect when the `fvh` or `postings` highligher is used. The test added in this commit verifies that.

Closes #14999
This commit is contained in:
Martijn van Groningen 2016-07-27 11:23:15 +02:00
parent 502217f035
commit 72e0d422e9
3 changed files with 66 additions and 5 deletions

View File

@ -352,7 +352,17 @@ public class HasChildQueryBuilder extends AbstractQueryBuilder<HasChildQueryBuil
parentType, scoreMode, parentChildIndexFieldData, context.getSearchSimilarity()); parentType, scoreMode, parentChildIndexFieldData, context.getSearchSimilarity());
} }
static final class LateParsingQuery extends Query { /**
* A query that rewrites into another query using
* {@link JoinUtil#createJoinQuery(String, Query, Query, IndexSearcher, ScoreMode, MultiDocValues.OrdinalMap, int, int)}
* that executes the actual join.
*
* This query is exclusively used by the {@link HasChildQueryBuilder} and {@link HasParentQueryBuilder} to get access
* to the {@link DirectoryReader} used by the current search in order to retrieve the {@link MultiDocValues.OrdinalMap}.
* The {@link MultiDocValues.OrdinalMap} is required by {@link JoinUtil} to execute the join.
*/
// TODO: Find a way to remove this query and let doToQuery(...) just return the query from JoinUtil.createJoinQuery(...)
public static final class LateParsingQuery extends Query {
private final Query toQuery; private final Query toQuery;
private final Query innerQuery; private final Query innerQuery;

View File

@ -27,6 +27,7 @@ import org.apache.lucene.search.highlight.WeightedSpanTermExtractor;
import org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery; import org.apache.lucene.spatial.geopoint.search.GeoPointInBBoxQuery;
import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery; import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery; import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.HasChildQueryBuilder;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
@ -89,11 +90,16 @@ public final class CustomQueryScorer extends QueryScorer {
} }
protected void extract(Query query, float boost, Map<String, WeightedSpanTerm> terms) throws IOException { protected void extract(Query query, float boost, Map<String, WeightedSpanTerm> terms) throws IOException {
// skip all geo queries, see https://issues.apache.org/jira/browse/LUCENE-7293 and if (query instanceof GeoPointInBBoxQuery) {
// https://github.com/elastic/elasticsearch/issues/17537 // skip all geo queries, see https://issues.apache.org/jira/browse/LUCENE-7293 and
if (query instanceof GeoPointInBBoxQuery == false) { // https://github.com/elastic/elasticsearch/issues/17537
super.extract(query, boost, terms); return;
} else if (query instanceof HasChildQueryBuilder.LateParsingQuery) {
// skip has_child or has_parent queries, see: https://github.com/elastic/elasticsearch/issues/14999
return;
} }
super.extract(query, boost, terms);
} }
} }
} }

View File

@ -32,8 +32,12 @@ import org.elasticsearch.common.lucene.search.function.FiltersFunctionScoreQuery
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.IndexModule; import org.elasticsearch.index.IndexModule;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.HasChildQueryBuilder; import org.elasticsearch.index.query.HasChildQueryBuilder;
import org.elasticsearch.index.query.HasParentQueryBuilder;
import org.elasticsearch.index.query.IdsQueryBuilder; import org.elasticsearch.index.query.IdsQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder; import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
@ -43,6 +47,8 @@ import org.elasticsearch.search.aggregations.bucket.filter.Filter;
import org.elasticsearch.search.aggregations.bucket.global.Global; import org.elasticsearch.search.aggregations.bucket.global.Global;
import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.highlight.HighlightBuilder;
import org.elasticsearch.search.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.ESIntegTestCase;
@ -1889,4 +1895,43 @@ public class ChildQuerySearchIT extends ESIntegTestCase {
QueryBuilders.hasChildQuery("child-type", new IdsQueryBuilder().addIds("child-id"), ScoreMode.None)).get(); QueryBuilders.hasChildQuery("child-type", new IdsQueryBuilder().addIds("child-id"), ScoreMode.None)).get();
assertSearchHits(searchResponse, "parent-id"); assertSearchHits(searchResponse, "parent-id");
} }
public void testHighlighersIgnoreParentChild() {
assertAcked(prepareCreate("test")
.addMapping("parent-type", "searchText", "type=text,term_vector=with_positions_offsets,index_options=offsets")
.addMapping("child-type", "_parent", "type=parent-type", "searchText",
"type=text,term_vector=with_positions_offsets,index_options=offsets"));
client().prepareIndex("test", "parent-type", "parent-id").setSource("searchText", "quick brown fox").get();
client().prepareIndex("test", "child-type", "child-id").setParent("parent-id").setSource("searchText", "quick brown fox").get();
refresh();
String[] highlightTypes = new String[] {"plain", "fvh", "postings"};
for (String highlightType : highlightTypes) {
logger.info("Testing with highlight type [{}]", highlightType);
SearchResponse searchResponse = client().prepareSearch("test")
.setQuery(new BoolQueryBuilder()
.must(new MatchQueryBuilder("searchText", "fox"))
.must(new HasChildQueryBuilder("child-type", new MatchAllQueryBuilder(), ScoreMode.None))
)
.highlighter(new HighlightBuilder().field(new HighlightBuilder.Field("searchText").highlighterType(highlightType)))
.get();
assertHitCount(searchResponse, 1);
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("parent-id"));
HighlightField highlightField = searchResponse.getHits().getAt(0).getHighlightFields().get("searchText");
assertThat(highlightField.getFragments()[0].string(), equalTo("quick brown <em>fox</em>"));
searchResponse = client().prepareSearch("test")
.setQuery(new BoolQueryBuilder()
.must(new MatchQueryBuilder("searchText", "fox"))
.must(new HasParentQueryBuilder("parent-type", new MatchAllQueryBuilder(), false))
)
.highlighter(new HighlightBuilder().field(new HighlightBuilder.Field("searchText").highlighterType(highlightType)))
.get();
assertHitCount(searchResponse, 1);
assertThat(searchResponse.getHits().getAt(0).id(), equalTo("child-id"));
highlightField = searchResponse.getHits().getAt(0).getHighlightFields().get("searchText");
assertThat(highlightField.getFragments()[0].string(), equalTo("quick brown <em>fox</em>"));
}
}
} }