Skip to content

Commit 3988ee1

Browse files
authored
Check positions on MultiPhraseQueries as well as phrase queries (elastic#129326)
1 parent 87ed699 commit 3988ee1

File tree

6 files changed

+67
-9
lines changed

6 files changed

+67
-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
@@ -178,9 +178,9 @@ public void testPhraseQueryOnFieldWithNoPositions() throws Exception {
178178
Exception.class,
179179
prepareSearch("test").setQuery(queryStringQuery("f4:\"eggplant parmesan\"").lenient(false))
180180
);
181-
IllegalStateException ise = (IllegalStateException) ExceptionsHelper.unwrap(exc, IllegalStateException.class);
182-
assertNotNull(ise);
183-
assertThat(ise.getMessage(), containsString("field:[f4] was indexed without position data; cannot run PhraseQuery"));
181+
IllegalArgumentException iae = (IllegalArgumentException) ExceptionsHelper.unwrap(exc, IllegalArgumentException.class);
182+
assertNotNull(iae);
183+
assertThat(iae.getMessage(), containsString("field:[f4] was indexed without position data; cannot run PhraseQuery"));
184184
}
185185

186186
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
@@ -894,17 +894,19 @@ public IntervalsSource rangeIntervals(
894894
return Intervals.range(lowerTerm, upperTerm, includeLower, includeUpper, IndexSearcher.getMaxClauseCount());
895895
}
896896

897-
private void checkForPositions() {
897+
private void checkForPositions(boolean multi) {
898898
if (getTextSearchInfo().hasPositions() == false) {
899-
throw new IllegalStateException("field:[" + name() + "] was indexed without position data; cannot run PhraseQuery");
899+
throw new IllegalArgumentException(
900+
"field:[" + name() + "] was indexed without position data; cannot run " + (multi ? "MultiPhraseQuery" : "PhraseQuery")
901+
);
900902
}
901903
}
902904

903905
@Override
904906
public Query phraseQuery(TokenStream stream, int slop, boolean enablePosIncrements, SearchExecutionContext context)
905907
throws IOException {
906908
String field = name();
907-
checkForPositions();
909+
checkForPositions(false);
908910
// we can't use the index_phrases shortcut with slop, if there are gaps in the stream,
909911
// or if the incoming token stream is the output of a token graph due to
910912
// https://issues.apache.org/jira/browse/LUCENE-8916
@@ -939,6 +941,7 @@ public Query phraseQuery(TokenStream stream, int slop, boolean enablePosIncremen
939941
public Query multiPhraseQuery(TokenStream stream, int slop, boolean enablePositionIncrements, SearchExecutionContext context)
940942
throws IOException {
941943
String field = name();
944+
checkForPositions(true);
942945
if (indexPhrases && slop == 0 && hasGaps(stream) == false) {
943946
stream = new FixedShingleFilter(stream, 2);
944947
field = field + FAST_PHRASE_SUFFIX;
@@ -959,7 +962,7 @@ private static int countTokens(TokenStream ts) throws IOException {
959962
@Override
960963
public Query phrasePrefixQuery(TokenStream stream, int slop, int maxExpansions, SearchExecutionContext context) throws IOException {
961964
if (countTokens(stream) > 1) {
962-
checkForPositions();
965+
checkForPositions(false);
963966
}
964967
return analyzePhrasePrefix(stream, slop, maxExpansions);
965968
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ public Set<NodeFeature> getFeatures() {
3030
);
3131
public static final NodeFeature RESCORER_MISSING_FIELD_BAD_REQUEST = new NodeFeature("search.rescorer.missing.field.bad.request");
3232
public static final NodeFeature INT_SORT_FOR_INT_SHORT_BYTE_FIELDS = new NodeFeature("search.sort.int_sort_for_int_short_byte_fields");
33+
static final NodeFeature MULTI_MATCH_CHECKS_POSITIONS = new NodeFeature("search.multi.match.checks.positions");
3334

3435
@Override
3536
public Set<NodeFeature> getTestFeatures() {
3637
return Set.of(
3738
RETRIEVER_RESCORER_ENABLED,
3839
COMPLETION_FIELD_SUPPORTS_DUPLICATE_SUGGESTIONS,
3940
RESCORER_MISSING_FIELD_BAD_REQUEST,
40-
INT_SORT_FOR_INT_SHORT_BYTE_FIELDS
41+
INT_SORT_FOR_INT_SHORT_BYTE_FIELDS,
42+
MULTI_MATCH_CHECKS_POSITIONS
4143
);
4244
}
4345
}

0 commit comments

Comments
 (0)