Skip to content

Commit 74f523d

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 309d234 commit 74f523d

File tree

5 files changed

+48
-41
lines changed

5 files changed

+48
-41
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: []

qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/QueryBuilderBWCIT.java

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -154,29 +154,29 @@ public QueryBuilderBWCIT(@Name("cluster") FullClusterRestartUpgradeStatus upgrad
154154
);
155155
addCandidate(
156156
"""
157-
"span_near": {"clauses": [{ "span_term": { "keyword_field": "value1" }}, \
158-
{ "span_term": { "keyword_field": "value2" }}]}
157+
"span_near": {"clauses": [{ "span_term": { "text_field": "value1" }}, \
158+
{ "span_term": { "text_field": "value2" }}]}
159159
""",
160-
new SpanNearQueryBuilder(new SpanTermQueryBuilder("keyword_field", "value1"), 0).addClause(
161-
new SpanTermQueryBuilder("keyword_field", "value2")
160+
new SpanNearQueryBuilder(new SpanTermQueryBuilder("text_field", "value1"), 0).addClause(
161+
new SpanTermQueryBuilder("text_field", "value2")
162162
)
163163
);
164164
addCandidate(
165165
"""
166-
"span_near": {"clauses": [{ "span_term": { "keyword_field": "value1" }}, \
167-
{ "span_term": { "keyword_field": "value2" }}], "slop": 2}
166+
"span_near": {"clauses": [{ "span_term": { "text_field": "value1" }}, \
167+
{ "span_term": { "text_field": "value2" }}], "slop": 2}
168168
""",
169-
new SpanNearQueryBuilder(new SpanTermQueryBuilder("keyword_field", "value1"), 2).addClause(
170-
new SpanTermQueryBuilder("keyword_field", "value2")
169+
new SpanNearQueryBuilder(new SpanTermQueryBuilder("text_field", "value1"), 2).addClause(
170+
new SpanTermQueryBuilder("text_field", "value2")
171171
)
172172
);
173173
addCandidate(
174174
"""
175-
"span_near": {"clauses": [{ "span_term": { "keyword_field": "value1" }}, \
176-
{ "span_term": { "keyword_field": "value2" }}], "slop": 2, "in_order": false}
175+
"span_near": {"clauses": [{ "span_term": { "text_field": "value1" }}, \
176+
{ "span_term": { "text_field": "value2" }}], "slop": 2, "in_order": false}
177177
""",
178-
new SpanNearQueryBuilder(new SpanTermQueryBuilder("keyword_field", "value1"), 2).addClause(
179-
new SpanTermQueryBuilder("keyword_field", "value2")
178+
new SpanNearQueryBuilder(new SpanTermQueryBuilder("text_field", "value1"), 2).addClause(
179+
new SpanTermQueryBuilder("text_field", "value2")
180180
).inOrder(false)
181181
);
182182
}
@@ -204,11 +204,6 @@ public void testQueryBuilderBWC() throws Exception {
204204
mappingsAndSettings.field("type", "percolator");
205205
mappingsAndSettings.endObject();
206206
}
207-
{
208-
mappingsAndSettings.startObject("keyword_field");
209-
mappingsAndSettings.field("type", "keyword");
210-
mappingsAndSettings.endObject();
211-
}
212207
{
213208
mappingsAndSettings.startObject("text_field");
214209
mappingsAndSettings.field("type", "text");

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,14 @@
99

1010
package org.elasticsearch.index.query;
1111

12-
import org.apache.lucene.index.Term;
13-
import org.apache.lucene.queries.spans.SpanQuery;
1412
import org.apache.lucene.queries.spans.SpanTermQuery;
1513
import org.apache.lucene.search.Query;
1614
import org.elasticsearch.TransportVersion;
1715
import org.elasticsearch.TransportVersions;
1816
import org.elasticsearch.common.ParsingException;
1917
import org.elasticsearch.common.io.stream.StreamInput;
20-
import org.elasticsearch.common.lucene.BytesRefs;
2118
import org.elasticsearch.index.mapper.MappedFieldType;
19+
import org.elasticsearch.lucene.queries.SpanMatchNoDocsQuery;
2220
import org.elasticsearch.xcontent.ParseField;
2321
import org.elasticsearch.xcontent.XContentParser;
2422

@@ -71,16 +69,18 @@ public SpanTermQueryBuilder(StreamInput in) throws IOException {
7169
}
7270

7371
@Override
74-
protected SpanQuery doToQuery(SearchExecutionContext context) throws IOException {
72+
protected Query doToQuery(SearchExecutionContext context) throws IOException {
7573
MappedFieldType mapper = context.getFieldType(fieldName);
76-
Term term;
7774
if (mapper == null) {
78-
term = new Term(fieldName, BytesRefs.toBytesRef(value));
79-
} else {
80-
Query termQuery = mapper.termQuery(value, context);
81-
term = MappedFieldType.extractTerm(termQuery);
75+
return new SpanMatchNoDocsQuery(fieldName, "unmapped field: " + fieldName);
8276
}
83-
return new SpanTermQuery(term);
77+
Query termQuery = mapper.termQuery(value, context);
78+
if (mapper.getTextSearchInfo().hasPositions() == false) {
79+
throw new IllegalArgumentException(
80+
"Span term query requires position data, but field " + fieldName + " was indexed without position data"
81+
);
82+
}
83+
return new SpanTermQuery(MappedFieldType.extractTerm(termQuery));
8484
}
8585

8686
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: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@
1111

1212
import org.apache.lucene.index.Term;
1313
import org.apache.lucene.queries.spans.SpanTermQuery;
14+
import org.apache.lucene.search.BoostQuery;
1415
import org.apache.lucene.search.Query;
1516
import org.apache.lucene.search.TermQuery;
1617
import org.elasticsearch.common.ParsingException;
17-
import org.elasticsearch.common.lucene.BytesRefs;
18+
import org.elasticsearch.index.mapper.IdFieldMapper;
1819
import org.elasticsearch.index.mapper.MappedFieldType;
20+
import org.elasticsearch.lucene.queries.SpanMatchNoDocsQuery;
1921
import org.elasticsearch.xcontent.json.JsonStringEncoder;
2022

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

4850
@Override
4951
protected void doAssertLuceneQuery(SpanTermQueryBuilder queryBuilder, Query query, SearchExecutionContext context) throws IOException {
50-
assertThat(query, instanceOf(SpanTermQuery.class));
51-
SpanTermQuery spanTermQuery = (SpanTermQuery) query;
52-
53-
String expectedFieldName = expectedFieldName(queryBuilder.fieldName);
54-
assertThat(spanTermQuery.getTerm().field(), equalTo(expectedFieldName));
55-
5652
MappedFieldType mapper = context.getFieldType(queryBuilder.fieldName());
5753
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));
5858
Term term = ((TermQuery) mapper.termQuery(queryBuilder.value(), null)).getTerm();
5959
assertThat(spanTermQuery.getTerm(), equalTo(term));
6060
} else {
61-
assertThat(spanTermQuery.getTerm().bytes(), equalTo(BytesRefs.toBytesRef(queryBuilder.value())));
61+
assertThat(query, instanceOf(SpanMatchNoDocsQuery.class));
6262
}
6363
}
6464

@@ -115,13 +115,21 @@ public void testParseFailsWithMultipleFields() throws IOException {
115115
assertEquals("[span_term] query doesn't support multiple fields, found [message1] and [message2]", e.getMessage());
116116
}
117117

118-
public void testWithMetadataField() throws IOException {
118+
public void testWithBoost() throws IOException {
119119
SearchExecutionContext context = createSearchExecutionContext();
120-
for (String field : new String[] { "field1", "field2" }) {
120+
for (String field : new String[] { TEXT_FIELD_NAME, TEXT_ALIAS_FIELD_NAME }) {
121121
SpanTermQueryBuilder spanTermQueryBuilder = new SpanTermQueryBuilder(field, "toto");
122+
spanTermQueryBuilder.boost(10);
122123
Query query = spanTermQueryBuilder.toQuery(context);
123-
Query expected = new SpanTermQuery(new Term(field, "toto"));
124+
Query expected = new BoostQuery(new SpanTermQuery(new Term(TEXT_FIELD_NAME, "toto")), 10);
124125
assertEquals(expected, query);
125126
}
126127
}
128+
129+
public void testFieldWithoutPositions() {
130+
SearchExecutionContext context = createSearchExecutionContext();
131+
SpanTermQueryBuilder spanTermQueryBuilder = new SpanTermQueryBuilder(IdFieldMapper.NAME, "1234");
132+
IllegalArgumentException iae = expectThrows(IllegalArgumentException.class, () -> spanTermQueryBuilder.toQuery(context));
133+
assertEquals("Span term query requires position data, but field _id was indexed without position data", iae.getMessage());
134+
}
127135
}

0 commit comments

Comments
 (0)