Skip to content

Commit d1644b3

Browse files
authored
Span term query to convert to match no docs when unmapped field is targeted (#113251)
SpanTermQueryBuilder currently creates a valid SpanTermQuery against unmapped fields. In practice, if the field is unmapped, there won't be a match. This commit changes the toQuery impl to return a MatchNoDocsQuery instead like we do in similar scenarios.
1 parent 6b2cc59 commit d1644b3

File tree

4 files changed

+41
-53
lines changed

4 files changed

+41
-53
lines changed

docs/changelog/113251.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 113251
2+
summary: Span term query to convert to match no docs when unmapped field is targeted
3+
area: Search
4+
type: bug
5+
issues: []

server/src/main/java/org/elasticsearch/index/query/SpanTermQueryBuilder.java

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.elasticsearch.index.query;
1111

1212
import org.apache.lucene.index.Term;
13-
import org.apache.lucene.queries.spans.SpanQuery;
1413
import org.apache.lucene.queries.spans.SpanTermQuery;
1514
import org.apache.lucene.search.BooleanClause;
1615
import org.apache.lucene.search.Query;
@@ -19,8 +18,8 @@
1918
import org.elasticsearch.TransportVersions;
2019
import org.elasticsearch.common.ParsingException;
2120
import org.elasticsearch.common.io.stream.StreamInput;
22-
import org.elasticsearch.common.lucene.BytesRefs;
2321
import org.elasticsearch.index.mapper.MappedFieldType;
22+
import org.elasticsearch.lucene.queries.SpanMatchNoDocsQuery;
2423
import org.elasticsearch.xcontent.ParseField;
2524
import org.elasticsearch.xcontent.XContentParser;
2625

@@ -76,40 +75,37 @@ public SpanTermQueryBuilder(StreamInput in) throws IOException {
7675
}
7776

7877
@Override
79-
protected SpanQuery doToQuery(SearchExecutionContext context) throws IOException {
78+
protected Query doToQuery(SearchExecutionContext context) throws IOException {
8079
MappedFieldType mapper = context.getFieldType(fieldName);
81-
Term term;
8280
if (mapper == null) {
83-
term = new Term(fieldName, BytesRefs.toBytesRef(value));
84-
} else {
85-
if (mapper.getTextSearchInfo().hasPositions() == false) {
86-
throw new IllegalArgumentException(
87-
"Span term query requires position data, but field " + fieldName + " was indexed without position data"
88-
);
89-
}
90-
Query termQuery = mapper.termQuery(value, context);
91-
List<Term> termsList = new ArrayList<>();
92-
termQuery.visit(new QueryVisitor() {
93-
@Override
94-
public QueryVisitor getSubVisitor(BooleanClause.Occur occur, Query parent) {
95-
if (occur == BooleanClause.Occur.MUST || occur == BooleanClause.Occur.FILTER) {
96-
return this;
97-
}
98-
return EMPTY_VISITOR;
81+
return new SpanMatchNoDocsQuery(fieldName, "unmapped field: " + fieldName);
82+
}
83+
if (mapper.getTextSearchInfo().hasPositions() == false) {
84+
throw new IllegalArgumentException(
85+
"Span term query requires position data, but field " + fieldName + " was indexed without position data"
86+
);
87+
}
88+
Query termQuery = mapper.termQuery(value, context);
89+
List<Term> termsList = new ArrayList<>();
90+
termQuery.visit(new QueryVisitor() {
91+
@Override
92+
public QueryVisitor getSubVisitor(BooleanClause.Occur occur, Query parent) {
93+
if (occur == BooleanClause.Occur.MUST || occur == BooleanClause.Occur.FILTER) {
94+
return this;
9995
}
96+
return EMPTY_VISITOR;
97+
}
10098

101-
@Override
102-
public void consumeTerms(Query query, Term... terms) {
103-
termsList.addAll(Arrays.asList(terms));
104-
}
105-
});
106-
if (termsList.size() != 1) {
107-
// This is for safety, but we have called mapper.termQuery above: we really should get one and only one term from the query?
108-
throw new IllegalArgumentException("Cannot extract a term from a query of type " + termQuery.getClass() + ": " + termQuery);
99+
@Override
100+
public void consumeTerms(Query query, Term... terms) {
101+
termsList.addAll(Arrays.asList(terms));
109102
}
110-
term = termsList.get(0);
103+
});
104+
if (termsList.size() != 1) {
105+
// This is for safety, but we have called mapper.termQuery above: we really should get one and only one term from the query?
106+
throw new IllegalArgumentException("Cannot extract a term from a query of type " + termQuery.getClass() + ": " + termQuery);
111107
}
112-
return new SpanTermQuery(term);
108+
return new SpanTermQuery(termsList.get(0));
113109
}
114110

115111
public static SpanTermQueryBuilder fromXContent(XContentParser parser) throws IOException, ParsingException {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99

1010
package org.elasticsearch.index.query;
1111

12-
import org.apache.lucene.index.Term;
1312
import org.apache.lucene.queries.spans.FieldMaskingSpanQuery;
14-
import org.apache.lucene.queries.spans.SpanTermQuery;
1513
import org.apache.lucene.search.BoostQuery;
1614
import org.apache.lucene.search.Query;
1715
import org.elasticsearch.common.ParsingException;
1816
import org.elasticsearch.core.Strings;
17+
import org.elasticsearch.lucene.queries.SpanMatchNoDocsQuery;
1918
import org.elasticsearch.test.AbstractQueryTestCase;
2019

2120
import java.io.IOException;
@@ -105,7 +104,7 @@ public void testJsonWithTopLevelBoost() throws IOException {
105104
}
106105
}""", NAME.getPreferredName());
107106
Query q = parseQuery(json).toQuery(createSearchExecutionContext());
108-
assertEquals(new BoostQuery(new FieldMaskingSpanQuery(new SpanTermQuery(new Term("value", "foo")), "mapped_geo_shape"), 42.0f), q);
107+
assertEquals(new BoostQuery(new FieldMaskingSpanQuery(new SpanMatchNoDocsQuery("value", null), "mapped_geo_shape"), 42.0f), q);
109108
}
110109

111110
public void testJsonWithDeprecatedName() throws IOException {

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

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515
import org.apache.lucene.search.Query;
1616
import org.apache.lucene.search.TermQuery;
1717
import org.elasticsearch.common.ParsingException;
18-
import org.elasticsearch.common.lucene.BytesRefs;
1918
import org.elasticsearch.index.mapper.IdFieldMapper;
2019
import org.elasticsearch.index.mapper.MappedFieldType;
20+
import org.elasticsearch.lucene.queries.SpanMatchNoDocsQuery;
2121
import org.elasticsearch.xcontent.json.JsonStringEncoder;
2222

2323
import java.io.IOException;
@@ -49,18 +49,16 @@ protected SpanTermQueryBuilder createQueryBuilder(String fieldName, Object value
4949

5050
@Override
5151
protected void doAssertLuceneQuery(SpanTermQueryBuilder queryBuilder, Query query, SearchExecutionContext context) throws IOException {
52-
assertThat(query, instanceOf(SpanTermQuery.class));
53-
SpanTermQuery spanTermQuery = (SpanTermQuery) query;
54-
55-
String expectedFieldName = expectedFieldName(queryBuilder.fieldName);
56-
assertThat(spanTermQuery.getTerm().field(), equalTo(expectedFieldName));
57-
5852
MappedFieldType mapper = context.getFieldType(queryBuilder.fieldName());
5953
if (mapper != null) {
54+
String expectedFieldName = expectedFieldName(queryBuilder.fieldName);
55+
assertThat(query, instanceOf(SpanTermQuery.class));
56+
SpanTermQuery spanTermQuery = (SpanTermQuery) query;
57+
assertThat(spanTermQuery.getTerm().field(), equalTo(expectedFieldName));
6058
Term term = ((TermQuery) mapper.termQuery(queryBuilder.value(), null)).getTerm();
6159
assertThat(spanTermQuery.getTerm(), equalTo(term));
6260
} else {
63-
assertThat(spanTermQuery.getTerm().bytes(), equalTo(BytesRefs.toBytesRef(queryBuilder.value())));
61+
assertThat(query, instanceOf(SpanMatchNoDocsQuery.class));
6462
}
6563
}
6664

@@ -117,23 +115,13 @@ public void testParseFailsWithMultipleFields() throws IOException {
117115
assertEquals("[span_term] query doesn't support multiple fields, found [message1] and [message2]", e.getMessage());
118116
}
119117

120-
public void testWithMetadataField() throws IOException {
121-
SearchExecutionContext context = createSearchExecutionContext();
122-
for (String field : new String[] { "field1", "field2" }) {
123-
SpanTermQueryBuilder spanTermQueryBuilder = new SpanTermQueryBuilder(field, "toto");
124-
Query query = spanTermQueryBuilder.toQuery(context);
125-
Query expected = new SpanTermQuery(new Term(field, "toto"));
126-
assertEquals(expected, query);
127-
}
128-
}
129-
130118
public void testWithBoost() throws IOException {
131119
SearchExecutionContext context = createSearchExecutionContext();
132-
for (String field : new String[] { "field1", "field2" }) {
120+
for (String field : new String[] { TEXT_FIELD_NAME, TEXT_ALIAS_FIELD_NAME }) {
133121
SpanTermQueryBuilder spanTermQueryBuilder = new SpanTermQueryBuilder(field, "toto");
134122
spanTermQueryBuilder.boost(10);
135123
Query query = spanTermQueryBuilder.toQuery(context);
136-
Query expected = new BoostQuery(new SpanTermQuery(new Term(field, "toto")), 10);
124+
Query expected = new BoostQuery(new SpanTermQuery(new Term(TEXT_FIELD_NAME, "toto")), 10);
137125
assertEquals(expected, query);
138126
}
139127
}

0 commit comments

Comments
 (0)