Skip to content

Commit 4756c9a

Browse files
authored
Fix nested query highlighting (#26305)
This commit extracts the inner query in the ESToParentBlockJoinQuery for highlighting. This query has been added in 5.4 and breaks plain highlighting on nested queries. Highlighters that use postings or term vectors are not affected because they can't highlight nested documents correctly. Fixes #26230
1 parent 3d8feff commit 4756c9a

File tree

4 files changed

+88
-1
lines changed

4 files changed

+88
-1
lines changed

core/src/main/java/org/apache/lucene/search/uhighlight/CustomUnifiedHighlighter.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.elasticsearch.common.lucene.all.AllTermQuery;
3939
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
4040
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
41+
import org.elasticsearch.index.search.ESToParentBlockJoinQuery;
4142

4243
import java.io.IOException;
4344
import java.text.BreakIterator;
@@ -210,6 +211,8 @@ private Collection<Query> rewriteCustomQuery(Query query) {
210211
return Collections.singletonList(new TermQuery(atq.getTerm()));
211212
} else if (query instanceof FunctionScoreQuery) {
212213
return Collections.singletonList(((FunctionScoreQuery) query).getSubQuery());
214+
} else if (query instanceof ESToParentBlockJoinQuery) {
215+
return Collections.singletonList(((ESToParentBlockJoinQuery) query).getChildQuery());
213216
} else {
214217
return null;
215218
}

core/src/main/java/org/apache/lucene/search/vectorhighlight/CustomFieldQuery.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.apache.lucene.search.Query;
3131
import org.apache.lucene.search.SynonymQuery;
3232
import org.apache.lucene.search.TermQuery;
33+
import org.apache.lucene.search.join.ToParentBlockJoinQuery;
3334
import org.apache.lucene.search.spans.SpanTermQuery;
3435
import org.elasticsearch.common.lucene.search.MultiPhrasePrefixQuery;
3536
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
@@ -90,6 +91,11 @@ void flatten(Query sourceQuery, IndexReader reader, Collection<Query> flatQuerie
9091
for (Term term : synQuery.getTerms()) {
9192
flatten(new TermQuery(term), reader, flatQueries, boost);
9293
}
94+
} else if (sourceQuery instanceof ESToParentBlockJoinQuery) {
95+
Query childQuery = ((ESToParentBlockJoinQuery) sourceQuery).getChildQuery();
96+
if (childQuery != null) {
97+
flatten(childQuery, reader, flatQueries, boost);
98+
}
9399
} else {
94100
super.flatten(sourceQuery, reader, flatQueries, boost);
95101
}

core/src/main/java/org/elasticsearch/search/fetch/subphase/highlight/CustomQueryScorer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.lucene.search.highlight.WeightedSpanTerm;
2626
import org.apache.lucene.search.highlight.WeightedSpanTermExtractor;
2727
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
28+
import org.elasticsearch.index.search.ESToParentBlockJoinQuery;
2829

2930
import java.io.IOException;
3031
import java.util.Map;
@@ -86,6 +87,8 @@ protected void extract(Query query, float boost, Map<String, WeightedSpanTerm> t
8687
return;
8788
} else if (query instanceof FunctionScoreQuery) {
8889
super.extract(((FunctionScoreQuery) query).getSubQuery(), boost, terms);
90+
} else if (query instanceof ESToParentBlockJoinQuery) {
91+
super.extract(((ESToParentBlockJoinQuery) query).getChildQuery(), boost, terms);
8992
} else {
9093
super.extract(query, boost, terms);
9194
}

core/src/test/java/org/elasticsearch/search/fetch/subphase/highlight/HighlighterSearchIT.java

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020

2121
import com.carrotsearch.randomizedtesting.generators.RandomPicks;
2222
import org.apache.lucene.search.join.ScoreMode;
23-
import org.elasticsearch.Version;
2423
import org.elasticsearch.action.index.IndexRequestBuilder;
2524
import org.elasticsearch.action.search.SearchRequestBuilder;
2625
import org.elasticsearch.action.search.SearchResponse;
@@ -2841,4 +2840,80 @@ public void testHighlightQueryRewriteDatesWithNow() throws Exception {
28412840
equalTo("<x>hello</x> world"));
28422841
}
28432842
}
2843+
2844+
public void testWithNestedQuery() throws Exception {
2845+
String mapping = jsonBuilder().startObject().startObject("type").startObject("properties")
2846+
.startObject("text")
2847+
.field("type", "text")
2848+
.field("index_options", "offsets")
2849+
.field("term_vector", "with_positions_offsets")
2850+
.endObject()
2851+
.startObject("foo")
2852+
.field("type", "nested")
2853+
.startObject("properties")
2854+
.startObject("text")
2855+
.field("type", "text")
2856+
.endObject()
2857+
.endObject()
2858+
.endObject()
2859+
.endObject().endObject().endObject().string();
2860+
prepareCreate("test").addMapping("type", mapping, XContentType.JSON).get();
2861+
2862+
client().prepareIndex("test", "type", "1").setSource(jsonBuilder().startObject()
2863+
.startArray("foo")
2864+
.startObject().field("text", "brown").endObject()
2865+
.startObject().field("text", "cow").endObject()
2866+
.endArray()
2867+
.field("text", "brown")
2868+
.endObject()).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE)
2869+
.get();
2870+
2871+
for (String type : new String[] {"unified", "plain"}) {
2872+
SearchResponse searchResponse = client().prepareSearch()
2873+
.setQuery(nestedQuery("foo", matchQuery("foo.text", "brown cow"), ScoreMode.None))
2874+
.highlighter(new HighlightBuilder()
2875+
.field(new Field("foo.text").highlighterType(type)))
2876+
.get();
2877+
assertHitCount(searchResponse, 1);
2878+
HighlightField field = searchResponse.getHits().getAt(0).getHighlightFields().get("foo.text");
2879+
assertThat(field.getFragments().length, equalTo(2));
2880+
assertThat(field.getFragments()[0].string(), equalTo("<em>brown</em>"));
2881+
assertThat(field.getFragments()[1].string(), equalTo("<em>cow</em>"));
2882+
2883+
searchResponse = client().prepareSearch()
2884+
.setQuery(nestedQuery("foo", prefixQuery("foo.text", "bro"), ScoreMode.None))
2885+
.highlighter(new HighlightBuilder()
2886+
.field(new Field("foo.text").highlighterType(type)))
2887+
.get();
2888+
assertHitCount(searchResponse, 1);
2889+
field = searchResponse.getHits().getAt(0).getHighlightFields().get("foo.text");
2890+
assertThat(field.getFragments().length, equalTo(1));
2891+
assertThat(field.getFragments()[0].string(), equalTo("<em>brown</em>"));
2892+
2893+
searchResponse = client().prepareSearch()
2894+
.setQuery(nestedQuery("foo", prefixQuery("foo.text", "bro"), ScoreMode.None))
2895+
.highlighter(new HighlightBuilder()
2896+
.field(new Field("foo.text").highlighterType("plain")))
2897+
.get();
2898+
assertHitCount(searchResponse, 1);
2899+
field = searchResponse.getHits().getAt(0).getHighlightFields().get("foo.text");
2900+
assertThat(field.getFragments().length, equalTo(1));
2901+
assertThat(field.getFragments()[0].string(), equalTo("<em>brown</em>"));
2902+
}
2903+
2904+
// For unified and fvh highlighters we just check that the nested query is correctly extracted
2905+
// but we highlight the root text field since nested documents cannot be highlighted with postings nor term vectors
2906+
// directly.
2907+
for (String type : ALL_TYPES) {
2908+
SearchResponse searchResponse = client().prepareSearch()
2909+
.setQuery(nestedQuery("foo", prefixQuery("foo.text", "bro"), ScoreMode.None))
2910+
.highlighter(new HighlightBuilder()
2911+
.field(new Field("text").highlighterType(type).requireFieldMatch(false)))
2912+
.get();
2913+
assertHitCount(searchResponse, 1);
2914+
HighlightField field = searchResponse.getHits().getAt(0).getHighlightFields().get("text");
2915+
assertThat(field.getFragments().length, equalTo(1));
2916+
assertThat(field.getFragments()[0].string(), equalTo("<em>brown</em>"));
2917+
}
2918+
}
28442919
}

0 commit comments

Comments
 (0)