Skip to content

Commit e5c51e6

Browse files
authored
Always use constant_score query for match_only_text (#16964) (#16969)
In some cases, when we create a term query over a `match_only_text` field, it may still try to compute scores, which prevents early termination. We should *always* use a constant score query when querying `match_only_text`, since we don't have the statistics required to compute scores. --------- Signed-off-by: Michael Froh <froh@amazon.com> (cherry picked from commit 0b36599)
1 parent 8a17b8a commit e5c51e6

File tree

4 files changed

+52
-1
lines changed

4 files changed

+52
-1
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7878
- Ensure consistency of system flag on IndexMetadata after diff is applied ([#16644](https://github.com/opensearch-project/OpenSearch/pull/16644))
7979
- Skip remote-repositories validations for node-joins when RepositoriesService is not in sync with cluster-state ([#16763](https://github.com/opensearch-project/OpenSearch/pull/16763))
8080
- Fix _list/shards API failing when closed indices are present ([#16606](https://github.com/opensearch-project/OpenSearch/pull/16606))
81+
- Always use `constant_score` query for `match_only_text` field ([#16964](https://github.com/opensearch-project/OpenSearch/pull/16964))
8182

8283
### Security
8384

server/src/main/java/org/opensearch/index/mapper/MatchOnlyTextFieldMapper.java

+11
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.apache.lucene.index.Term;
1717
import org.apache.lucene.search.BooleanClause;
1818
import org.apache.lucene.search.BooleanQuery;
19+
import org.apache.lucene.search.ConstantScoreQuery;
1920
import org.apache.lucene.search.MultiPhraseQuery;
2021
import org.apache.lucene.search.PhraseQuery;
2122
import org.apache.lucene.search.Query;
@@ -290,6 +291,16 @@ public Query phrasePrefixQuery(TokenStream stream, int slop, int maxExpansions,
290291
return new SourceFieldMatchQuery(builder.build(), phrasePrefixQuery, this, context);
291292
}
292293

294+
@Override
295+
public Query termQuery(Object value, QueryShardContext context) {
296+
return new ConstantScoreQuery(super.termQuery(value, context));
297+
}
298+
299+
@Override
300+
public Query termQueryCaseInsensitive(Object value, QueryShardContext context) {
301+
return new ConstantScoreQuery(super.termQueryCaseInsensitive(value, context));
302+
}
303+
293304
private List<List<Term>> getTermsFromTokenStream(TokenStream stream) throws IOException {
294305
final List<List<Term>> termArray = new ArrayList<>();
295306
TermToBytesRefAttribute termAtt = stream.getAttribute(TermToBytesRefAttribute.class);

server/src/test/java/org/opensearch/index/mapper/MatchOnlyTextFieldMapperTests.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
import org.apache.lucene.index.Term;
1616
import org.apache.lucene.search.BooleanClause;
1717
import org.apache.lucene.search.BooleanQuery;
18+
import org.apache.lucene.search.ConstantScoreQuery;
1819
import org.apache.lucene.search.MultiPhraseQuery;
1920
import org.apache.lucene.search.PhraseQuery;
2021
import org.apache.lucene.search.Query;
2122
import org.apache.lucene.search.TermQuery;
2223
import org.apache.lucene.tests.analysis.MockSynonymAnalyzer;
24+
import org.opensearch.common.lucene.search.AutomatonQueries;
2325
import org.opensearch.common.lucene.search.MultiPhrasePrefixQuery;
2426
import org.opensearch.core.common.Strings;
2527
import org.opensearch.core.xcontent.MediaTypeRegistry;
@@ -28,6 +30,7 @@
2830
import org.opensearch.index.query.MatchPhraseQueryBuilder;
2931
import org.opensearch.index.query.QueryShardContext;
3032
import org.opensearch.index.query.SourceFieldMatchQuery;
33+
import org.opensearch.index.query.TermQueryBuilder;
3134
import org.opensearch.index.search.MatchQuery;
3235
import org.junit.Before;
3336

@@ -391,7 +394,7 @@ public void testPhraseQuery() throws IOException {
391394

392395
assertThat(q, is(expectedQuery));
393396
Query q4 = new MatchPhraseQueryBuilder("field", "singleton").toQuery(queryShardContext);
394-
assertThat(q4, is(new TermQuery(new Term("field", "singleton"))));
397+
assertThat(q4, is(new ConstantScoreQuery(new TermQuery(new Term("field", "singleton")))));
395398

396399
Query q2 = new MatchPhraseQueryBuilder("field", "three words here").toQuery(queryShardContext);
397400
expectedQuery = new SourceFieldMatchQuery(
@@ -447,4 +450,22 @@ public void testPhraseQuery() throws IOException {
447450
);
448451
assertThat(q6, is(expectedQuery));
449452
}
453+
454+
public void testTermQuery() throws Exception {
455+
MapperService mapperService = createMapperService(mapping(b -> {
456+
b.startObject("field");
457+
{
458+
b.field("type", textFieldName);
459+
b.field("analyzer", "my_stop_analyzer"); // "standard" will be replaced with MockSynonymAnalyzer
460+
}
461+
b.endObject();
462+
}));
463+
QueryShardContext queryShardContext = createQueryShardContext(mapperService);
464+
465+
Query q = new TermQueryBuilder("field", "foo").rewrite(queryShardContext).toQuery(queryShardContext);
466+
assertEquals(new ConstantScoreQuery(new TermQuery(new Term("field", "foo"))), q);
467+
468+
q = new TermQueryBuilder("field", "foo").caseInsensitive(true).rewrite(queryShardContext).toQuery(queryShardContext);
469+
assertEquals(new ConstantScoreQuery(AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "foo"))), q);
470+
}
450471
}

server/src/test/java/org/opensearch/index/mapper/MatchOnlyTextFieldTypeTests.java

+18
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
package org.opensearch.index.mapper;
1010

11+
import org.apache.lucene.index.Term;
12+
import org.apache.lucene.search.ConstantScoreQuery;
13+
import org.apache.lucene.search.TermQuery;
1114
import org.opensearch.common.lucene.Lucene;
15+
import org.opensearch.common.lucene.search.AutomatonQueries;
1216

1317
public class MatchOnlyTextFieldTypeTests extends TextFieldTypeTests {
1418

@@ -28,4 +32,18 @@ TextFieldMapper.TextFieldType createFieldType(boolean searchable) {
2832
ParametrizedFieldMapper.Parameter.metaParam().get()
2933
);
3034
}
35+
36+
@Override
37+
public void testTermQuery() {
38+
MappedFieldType ft = createFieldType(true);
39+
assertEquals(new ConstantScoreQuery(new TermQuery(new Term("field", "foo"))), ft.termQuery("foo", null));
40+
assertEquals(
41+
new ConstantScoreQuery(AutomatonQueries.caseInsensitiveTermQuery(new Term("field", "fOo"))),
42+
ft.termQueryCaseInsensitive("fOo", null)
43+
);
44+
45+
MappedFieldType unsearchable = createFieldType(false);
46+
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> unsearchable.termQuery("bar", null));
47+
assertEquals("Cannot search on field [field] since it is not indexed.", e.getMessage());
48+
}
3149
}

0 commit comments

Comments
 (0)