Skip to content

Commit 0f9e1b1

Browse files
authored
Check positions on MultiPhraseQueries as well as phrase queries (#129326) (#129352)
Backport #129326 to 8.19
1 parent 9ff05a1 commit 0f9e1b1

File tree

6 files changed

+71
-9
lines changed

6 files changed

+71
-9
lines changed

docs/changelog/129326.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 129326
2+
summary: Check positions on `MultiPhraseQueries` as well as phrase queries
3+
area: Search
4+
type: bug
5+
issues:
6+
- 123871

modules/analysis-common/src/yamlRestTest/resources/rest-api-spec/test/search.query/50_queries_with_synonyms.yml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,50 @@
200200
query: quick brown
201201
operator: and
202202
- match: { hits.total: 2 }
203+
---
204+
# Tests that this returns 400, and not 500 (#123871)
205+
"Test multi_match phrase with no positions":
206+
- requires:
207+
cluster_features: [ "search.multi.match.checks.positions" ]
208+
reason: "This previously resulted in a 5xx error code"
209+
- do:
210+
indices.create:
211+
index: test
212+
body:
213+
settings:
214+
analysis:
215+
filter:
216+
syns:
217+
type: synonym
218+
synonyms: [ "quick,fast" ]
219+
analyzer:
220+
syns:
221+
tokenizer: standard
222+
filter: [ "syns" ]
223+
mappings:
224+
properties:
225+
field1:
226+
type: text
227+
index_options: freqs
228+
analyzer: syns
229+
230+
- do:
231+
index:
232+
index: test
233+
id: "1"
234+
body:
235+
field1: the quick brown fox
236+
237+
- do:
238+
catch: bad_request
239+
search:
240+
body:
241+
query:
242+
multi_match:
243+
query: the fast brown
244+
type: phrase
245+
fields:
246+
- field1
247+
- match: { status: 400 }
248+
- match: { error.root_cause.0.type: query_shard_exception }
249+
- match: { error.root_cause.0.reason: "failed to create query: field:[field1] was indexed without position data; cannot run MultiPhraseQuery" }

modules/mapper-extras/src/main/java/org/elasticsearch/index/mapper/extras/SearchAsYouTypeFieldMapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ public Query prefixQuery(
362362

363363
private void checkForPositions() {
364364
if (getTextSearchInfo().hasPositions() == false) {
365-
throw new IllegalStateException("field:[" + name() + "] was indexed without position data; cannot run PhraseQuery");
365+
throw new IllegalArgumentException("field:[" + name() + "] was indexed without position data; cannot run PhraseQuery");
366366
}
367367
}
368368

server/src/internalClusterTest/java/org/elasticsearch/search/query/QueryStringIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ public void testPhraseQueryOnFieldWithNoPositions() throws Exception {
189189
Exception.class,
190190
prepareSearch("test").setQuery(queryStringQuery("f4:\"eggplant parmesan\"").lenient(false))
191191
);
192-
IllegalStateException ise = (IllegalStateException) ExceptionsHelper.unwrap(exc, IllegalStateException.class);
193-
assertNotNull(ise);
194-
assertThat(ise.getMessage(), containsString("field:[f4] was indexed without position data; cannot run PhraseQuery"));
192+
IllegalArgumentException iae = (IllegalArgumentException) ExceptionsHelper.unwrap(exc, IllegalArgumentException.class);
193+
assertNotNull(iae);
194+
assertThat(iae.getMessage(), containsString("field:[f4] was indexed without position data; cannot run PhraseQuery"));
195195
}
196196

197197
public void testBooleanStrictQuery() throws Exception {

server/src/main/java/org/elasticsearch/index/mapper/TextFieldMapper.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -897,17 +897,19 @@ public IntervalsSource rangeIntervals(
897897
return Intervals.range(lowerTerm, upperTerm, includeLower, includeUpper, IndexSearcher.getMaxClauseCount());
898898
}
899899

900-
private void checkForPositions() {
900+
private void checkForPositions(boolean multi) {
901901
if (getTextSearchInfo().hasPositions() == false) {
902-
throw new IllegalStateException("field:[" + name() + "] was indexed without position data; cannot run PhraseQuery");
902+
throw new IllegalArgumentException(
903+
"field:[" + name() + "] was indexed without position data; cannot run " + (multi ? "MultiPhraseQuery" : "PhraseQuery")
904+
);
903905
}
904906
}
905907

906908
@Override
907909
public Query phraseQuery(TokenStream stream, int slop, boolean enablePosIncrements, SearchExecutionContext context)
908910
throws IOException {
909911
String field = name();
910-
checkForPositions();
912+
checkForPositions(false);
911913
// we can't use the index_phrases shortcut with slop, if there are gaps in the stream,
912914
// or if the incoming token stream is the output of a token graph due to
913915
// https://issues.apache.org/jira/browse/LUCENE-8916
@@ -942,6 +944,7 @@ public Query phraseQuery(TokenStream stream, int slop, boolean enablePosIncremen
942944
public Query multiPhraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements, SearchExecutionContext context)
943945
throws IOException {
944946
String field = name();
947+
checkForPositions(true);
945948
if (indexPhrases && slop == 0 && hasGaps(stream) == false) {
946949
stream = new FixedShingleFilter(stream, 2);
947950
field = field + FAST_PHRASE_SUFFIX;
@@ -962,7 +965,7 @@ private static int countTokens(TokenStream ts) throws IOException {
962965
@Override
963966
public Query phrasePrefixQuery(TokenStream stream, int slop, int maxExpansions, SearchExecutionContext context) throws IOException {
964967
if (countTokens(stream) > 1) {
965-
checkForPositions();
968+
checkForPositions(false);
966969
}
967970
return analyzePhrasePrefix(stream, slop, maxExpansions);
968971
}

server/src/main/java/org/elasticsearch/search/SearchFeatures.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,15 @@ public Set<NodeFeature> getFeatures() {
2626
"search.completion_field.duplicate.support"
2727
);
2828
public static final NodeFeature INT_SORT_FOR_INT_SHORT_BYTE_FIELDS = new NodeFeature("search.sort.int_sort_for_int_short_byte_fields");
29+
static final NodeFeature MULTI_MATCH_CHECKS_POSITIONS = new NodeFeature("search.multi.match.checks.positions");
2930

3031
@Override
3132
public Set<NodeFeature> getTestFeatures() {
32-
return Set.of(RETRIEVER_RESCORER_ENABLED, COMPLETION_FIELD_SUPPORTS_DUPLICATE_SUGGESTIONS, INT_SORT_FOR_INT_SHORT_BYTE_FIELDS);
33+
return Set.of(
34+
RETRIEVER_RESCORER_ENABLED,
35+
COMPLETION_FIELD_SUPPORTS_DUPLICATE_SUGGESTIONS,
36+
INT_SORT_FOR_INT_SHORT_BYTE_FIELDS,
37+
MULTI_MATCH_CHECKS_POSITIONS
38+
);
3339
}
3440
}

0 commit comments

Comments
 (0)