Skip to content

Commit c26bd60

Browse files
authored
Fix (simple)_query_string to ignore removed terms (#28871)
This change ensures that we ignore terms removed from the analysis rather than returning a match_no_docs query for the part that contain the stop word. For instance a query like "the AND fox" should ignore "the" if it is considered as a stop word instead of adding a match_no_docs query. This change also fixes the analysis of prefix terms that start with a stop word (e.g. `the*`). In such case if `analyze_wildcard` is true and `the` is considered as a stop word this part of the query is rewritten into a match_no_docs query. Since it's a prefix query this change forces the prefix query on `the` even if it is removed from the analysis. Fixes #28855 Fixes #28856
1 parent 5689dc1 commit c26bd60

File tree

7 files changed

+74
-10
lines changed

7 files changed

+74
-10
lines changed

server/src/main/java/org/elasticsearch/index/search/MatchQuery.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ public void writeTo(StreamOutput out) throws IOException {
102102

103103
public enum ZeroTermsQuery implements Writeable {
104104
NONE(0),
105-
ALL(1);
105+
ALL(1),
106+
// this is used internally to make sure that query_string and simple_query_string
107+
// ignores query part that removes all tokens.
108+
NULL(2);
106109

107110
private final int ordinal;
108111

@@ -312,10 +315,16 @@ protected final Query termQuery(MappedFieldType fieldType, BytesRef value, boole
312315
}
313316

314317
protected Query zeroTermsQuery() {
315-
if (zeroTermsQuery == DEFAULT_ZERO_TERMS_QUERY) {
316-
return Queries.newMatchNoDocsQuery("Matching no documents because no terms present.");
318+
switch (zeroTermsQuery) {
319+
case NULL:
320+
return null;
321+
case NONE:
322+
return Queries.newMatchNoDocsQuery("Matching no documents because no terms present");
323+
case ALL:
324+
return Queries.newMatchAllQuery();
325+
default:
326+
throw new IllegalStateException("unknown zeroTermsQuery " + zeroTermsQuery);
317327
}
318-
return Queries.newMatchAllQuery();
319328
}
320329

321330
private class MatchQueryBuilder extends QueryBuilder {

server/src/main/java/org/elasticsearch/index/search/QueryStringQueryParser.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ private QueryStringQueryParser(QueryShardContext context, String defaultField,
147147
this.context = context;
148148
this.fieldsAndWeights = Collections.unmodifiableMap(fieldsAndWeights);
149149
this.queryBuilder = new MultiMatchQuery(context);
150+
queryBuilder.setZeroTermsQuery(MatchQuery.ZeroTermsQuery.NULL);
150151
queryBuilder.setLenient(lenient);
151152
this.lenient = lenient;
152153
}
@@ -343,7 +344,6 @@ protected Query getFieldQuery(String field, String queryText, int slop) throws P
343344
if (fields.isEmpty()) {
344345
return newUnmappedFieldQuery(field);
345346
}
346-
final Query query;
347347
Analyzer oldAnalyzer = queryBuilder.analyzer;
348348
int oldSlop = queryBuilder.phraseSlop;
349349
try {
@@ -353,7 +353,7 @@ protected Query getFieldQuery(String field, String queryText, int slop) throws P
353353
queryBuilder.setAnalyzer(forceAnalyzer);
354354
}
355355
queryBuilder.setPhraseSlop(slop);
356-
query = queryBuilder.parse(MultiMatchQueryBuilder.Type.PHRASE, fields, queryText, null);
356+
Query query = queryBuilder.parse(MultiMatchQueryBuilder.Type.PHRASE, fields, queryText, null);
357357
return applySlop(query, slop);
358358
} catch (IOException e) {
359359
throw new ParseException(e.getMessage());
@@ -555,7 +555,7 @@ private Query getPossiblyAnalyzedPrefixQuery(String field, String termStr) throw
555555
}
556556

557557
if (tlist.size() == 0) {
558-
return new MatchNoDocsQuery("analysis was empty for " + field + ":" + termStr);
558+
return super.getPrefixQuery(field, termStr);
559559
}
560560

561561
if (tlist.size() == 1 && tlist.get(0).size() == 1) {
@@ -763,7 +763,7 @@ private PhraseQuery addSlopToPhrase(PhraseQuery query, int slop) {
763763
@Override
764764
public Query parse(String query) throws ParseException {
765765
if (query.trim().isEmpty()) {
766-
return queryBuilder.zeroTermsQuery();
766+
return Queries.newMatchNoDocsQuery("Matching no documents because no terms present");
767767
}
768768
return super.parse(query);
769769
}

server/src/main/java/org/elasticsearch/index/search/SimpleQueryStringQueryParser.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public SimpleQueryStringQueryParser(Analyzer analyzer, Map<String, Float> weight
7474
this.queryBuilder = new MultiMatchQuery(context);
7575
this.queryBuilder.setAutoGenerateSynonymsPhraseQuery(settings.autoGenerateSynonymsPhraseQuery());
7676
this.queryBuilder.setLenient(settings.lenient());
77+
this.queryBuilder.setZeroTermsQuery(MatchQuery.ZeroTermsQuery.NULL);
7778
if (analyzer != null) {
7879
this.queryBuilder.setAnalyzer(analyzer);
7980
}

server/src/test/java/org/elasticsearch/index/query/MatchQueryBuilderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ protected MatchQueryBuilder doCreateTestQueryBuilder() {
111111
}
112112

113113
if (randomBoolean()) {
114-
matchQuery.zeroTermsQuery(randomFrom(MatchQuery.ZeroTermsQuery.values()));
114+
matchQuery.zeroTermsQuery(randomFrom(ZeroTermsQuery.ALL, ZeroTermsQuery.NONE));
115115
}
116116

117117
if (randomBoolean()) {

server/src/test/java/org/elasticsearch/index/query/MultiMatchQueryBuilderTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ protected MultiMatchQueryBuilder doCreateTestQueryBuilder() {
129129
query.cutoffFrequency((float) 10 / randomIntBetween(1, 100));
130130
}
131131
if (randomBoolean()) {
132-
query.zeroTermsQuery(randomFrom(MatchQuery.ZeroTermsQuery.values()));
132+
query.zeroTermsQuery(randomFrom(MatchQuery.ZeroTermsQuery.NONE, MatchQuery.ZeroTermsQuery.ALL));
133133
}
134134
if (randomBoolean()) {
135135
query.autoGenerateSynonymsPhraseQuery(randomBoolean());

server/src/test/java/org/elasticsearch/index/query/QueryStringQueryBuilderTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,33 @@ public void testToFuzzyQuery() throws Exception {
10521052
assertEquals(expected, query);
10531053
}
10541054

1055+
public void testWithStopWords() throws Exception {
1056+
assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
1057+
Query query = new QueryStringQueryBuilder("the quick fox")
1058+
.field(STRING_FIELD_NAME)
1059+
.analyzer("english")
1060+
.toQuery(createShardContext());
1061+
BooleanQuery expected = new BooleanQuery.Builder()
1062+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "quick")), Occur.SHOULD)
1063+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "fox")), Occur.SHOULD)
1064+
.build();
1065+
assertEquals(expected, query);
1066+
}
1067+
1068+
public void testWithPrefixStopWords() throws Exception {
1069+
assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
1070+
Query query = new QueryStringQueryBuilder("the* quick fox")
1071+
.field(STRING_FIELD_NAME)
1072+
.analyzer("english")
1073+
.toQuery(createShardContext());
1074+
BooleanQuery expected = new BooleanQuery.Builder()
1075+
.add(new PrefixQuery(new Term(STRING_FIELD_NAME, "the")), Occur.SHOULD)
1076+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "quick")), Occur.SHOULD)
1077+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "fox")), Occur.SHOULD)
1078+
.build();
1079+
assertEquals(expected, query);
1080+
}
1081+
10551082
private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings, Settings indexSettings) {
10561083
Settings build = Settings.builder().put(oldIndexSettings)
10571084
.put(indexSettings)

server/src/test/java/org/elasticsearch/index/query/SimpleQueryStringBuilderTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,33 @@ public void testLenientToPrefixQuery() throws Exception {
625625
assertEquals(expected, query);
626626
}
627627

628+
public void testWithStopWords() throws Exception {
629+
assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
630+
Query query = new SimpleQueryStringBuilder("the quick fox")
631+
.field(STRING_FIELD_NAME)
632+
.analyzer("english")
633+
.toQuery(createShardContext());
634+
BooleanQuery expected = new BooleanQuery.Builder()
635+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "quick")), BooleanClause.Occur.SHOULD)
636+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "fox")), BooleanClause.Occur.SHOULD)
637+
.build();
638+
assertEquals(expected, query);
639+
}
640+
641+
public void testWithPrefixStopWords() throws Exception {
642+
assumeTrue("test runs only when at least a type is registered", getCurrentTypes().length > 0);
643+
Query query = new SimpleQueryStringBuilder("the* quick fox")
644+
.field(STRING_FIELD_NAME)
645+
.analyzer("english")
646+
.toQuery(createShardContext());
647+
BooleanQuery expected = new BooleanQuery.Builder()
648+
.add(new PrefixQuery(new Term(STRING_FIELD_NAME, "the")), BooleanClause.Occur.SHOULD)
649+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "quick")), BooleanClause.Occur.SHOULD)
650+
.add(new TermQuery(new Term(STRING_FIELD_NAME, "fox")), BooleanClause.Occur.SHOULD)
651+
.build();
652+
assertEquals(expected, query);
653+
}
654+
628655
private static IndexMetaData newIndexMeta(String name, Settings oldIndexSettings, Settings indexSettings) {
629656
Settings build = Settings.builder().put(oldIndexSettings)
630657
.put(indexSettings)

0 commit comments

Comments
 (0)