Skip to content

Commit 1886454

Browse files
committed
Handle MatchNoDocsQuery in span query wrappers (#34106)
* Handle MatchNoDocsQuery in span query wrappers This change adds a new SpanMatchNoDocsQuery query that replaces MatchNoDocsQuery in the span query wrappers. The `wildcard` query now returns MatchNoDocsQuery if the target field is not in the mapping (#34093) so we need the equivalent span query in order to be able to pass it to other span wrappers. Closes #34105
1 parent c36aae3 commit 1886454

File tree

11 files changed

+232
-3
lines changed

11 files changed

+232
-3
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.lucene.queries;
20+
21+
import org.apache.lucene.index.LeafReaderContext;
22+
import org.apache.lucene.index.Term;
23+
import org.apache.lucene.index.TermContext;
24+
import org.apache.lucene.search.IndexSearcher;
25+
import org.apache.lucene.search.spans.SpanQuery;
26+
import org.apache.lucene.search.spans.SpanWeight;
27+
import org.apache.lucene.search.spans.Spans;
28+
29+
import java.io.IOException;
30+
import java.util.Collections;
31+
import java.util.Map;
32+
import java.util.Set;
33+
34+
/**
35+
* A {@link SpanQuery} that matches no documents.
36+
*/
37+
public class SpanMatchNoDocsQuery extends SpanQuery {
38+
private final String field;
39+
private final String reason;
40+
41+
public SpanMatchNoDocsQuery(String field, String reason) {
42+
this.field = field;
43+
this.reason = reason;
44+
}
45+
46+
@Override
47+
public String getField() {
48+
return field;
49+
}
50+
51+
@Override
52+
public String toString(String field) {
53+
return "SpanMatchNoDocsQuery(\"" + reason + "\")";
54+
}
55+
56+
@Override
57+
public boolean equals(Object o) {
58+
return sameClassAs(o);
59+
}
60+
61+
@Override
62+
public int hashCode() {
63+
return classHash();
64+
}
65+
66+
@Override
67+
public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException {
68+
return new SpanWeight(this, searcher, Collections.emptyMap(), boost) {
69+
@Override
70+
public void extractTermContexts(Map<Term, TermContext> contexts) {}
71+
72+
@Override
73+
public Spans getSpans(LeafReaderContext ctx, Postings requiredPostings) {
74+
return null;
75+
}
76+
77+
@Override
78+
public void extractTerms(Set<Term> terms) {}
79+
80+
@Override
81+
public boolean isCacheable(LeafReaderContext ctx) {
82+
return true;
83+
}
84+
};
85+
}
86+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
182182
out.writeOptionalString(this.rewrite);
183183
}
184184

185+
@Override
185186
public String fieldName() {
186187
return this.fieldName;
187188
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@
1919
package org.elasticsearch.index.query;
2020

2121
public interface MultiTermQueryBuilder extends QueryBuilder {
22-
22+
/**
23+
* Get the field name for this query.
24+
*/
25+
String fieldName();
2326
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
8787
out.writeOptionalString(rewrite);
8888
}
8989

90+
@Override
9091
public String fieldName() {
9192
return this.fieldName;
9293
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
151151
/**
152152
* Get the field name for this query.
153153
*/
154+
@Override
154155
public String fieldName() {
155156
return this.fieldName;
156157
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
104104
}
105105

106106
/** Returns the field name used in this query. */
107+
@Override
107108
public String fieldName() {
108109
return this.fieldName;
109110
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import org.apache.lucene.index.Term;
2222
import org.apache.lucene.index.IndexReader;
2323
import org.apache.lucene.index.TermContext;
24+
import org.apache.lucene.queries.SpanMatchNoDocsQuery;
2425
import org.apache.lucene.search.BooleanQuery;
2526
import org.apache.lucene.search.BoostQuery;
2627
import org.apache.lucene.search.ConstantScoreQuery;
28+
import org.apache.lucene.search.MatchNoDocsQuery;
2729
import org.apache.lucene.search.MultiTermQuery;
2830
import org.apache.lucene.search.PrefixQuery;
2931
import org.apache.lucene.search.Query;
@@ -190,9 +192,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException {
190192
break;
191193
}
192194
}
193-
final SpanQuery spanQuery;
194195
// no MultiTermQuery extends SpanQuery, so SpanBoostQuery is not supported here
195196
assert subQuery instanceof SpanBoostQuery == false;
197+
198+
if (subQuery instanceof MatchNoDocsQuery) {
199+
return new SpanMatchNoDocsQuery(multiTermQueryBuilder.fieldName(), subQuery.toString());
200+
}
201+
202+
final SpanQuery spanQuery;
196203
if (subQuery instanceof TermQuery) {
197204
/**
198205
* Text fields that index prefixes can rewrite prefix queries

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ protected void doWriteTo(StreamOutput out) throws IOException {
100100
out.writeOptionalString(rewrite);
101101
}
102102

103+
@Override
103104
public String fieldName() {
104105
return fieldName;
105106
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.lucene.queries;
21+
22+
import org.apache.lucene.analysis.Analyzer;
23+
import org.apache.lucene.analysis.MockAnalyzer;
24+
import org.apache.lucene.document.Document;
25+
import org.apache.lucene.document.Field;
26+
import org.apache.lucene.index.DirectoryReader;
27+
import org.apache.lucene.index.IndexReader;
28+
import org.apache.lucene.index.IndexWriter;
29+
import org.apache.lucene.index.Term;
30+
import org.apache.lucene.search.IndexSearcher;
31+
import org.apache.lucene.search.Query;
32+
import org.apache.lucene.search.QueryUtils;
33+
import org.apache.lucene.search.ScoreDoc;
34+
import org.apache.lucene.search.spans.SpanNearQuery;
35+
import org.apache.lucene.search.spans.SpanOrQuery;
36+
import org.apache.lucene.search.spans.SpanQuery;
37+
import org.apache.lucene.search.spans.SpanTermQuery;
38+
import org.apache.lucene.store.Directory;
39+
import org.elasticsearch.test.ESTestCase;
40+
41+
import java.io.IOException;
42+
43+
public class SpanMatchNoDocsQueryTests extends ESTestCase {
44+
public void testSimple() throws Exception {
45+
SpanMatchNoDocsQuery query = new SpanMatchNoDocsQuery("field", "a good reason");
46+
assertEquals(query.toString(), "SpanMatchNoDocsQuery(\"a good reason\")");
47+
Query rewrite = query.rewrite(null);
48+
assertTrue(rewrite instanceof SpanMatchNoDocsQuery);
49+
assertEquals(rewrite.toString(), "SpanMatchNoDocsQuery(\"a good reason\")");
50+
}
51+
52+
public void testQuery() throws Exception {
53+
Directory dir = newDirectory();
54+
Analyzer analyzer = new MockAnalyzer(random());
55+
IndexWriter iw = new IndexWriter(dir,
56+
newIndexWriterConfig(analyzer).setMaxBufferedDocs(2).setMergePolicy(newLogMergePolicy()));
57+
addDoc("one", iw);
58+
addDoc("two", iw);
59+
addDoc("three", iw);
60+
IndexReader ir = DirectoryReader.open(iw);
61+
IndexSearcher searcher = new IndexSearcher(ir);
62+
63+
Query query = new SpanMatchNoDocsQuery("unkwown", "field not found");
64+
assertEquals(searcher.count(query), 0);
65+
66+
ScoreDoc[] hits;
67+
hits = searcher.search(query, 1000).scoreDocs;
68+
assertEquals(0, hits.length);
69+
assertEquals(query.toString(), "SpanMatchNoDocsQuery(\"field not found\")");
70+
71+
SpanOrQuery orQuery = new SpanOrQuery(
72+
new SpanMatchNoDocsQuery("unknown", "field not found"),
73+
new SpanTermQuery(new Term("unknown", "one"))
74+
);
75+
assertEquals(searcher.count(orQuery), 0);
76+
hits = searcher.search(orQuery, 1000).scoreDocs;
77+
assertEquals(0, hits.length);
78+
79+
orQuery = new SpanOrQuery(
80+
new SpanMatchNoDocsQuery("key", "a good reason"),
81+
new SpanTermQuery(new Term("key", "one"))
82+
);
83+
assertEquals(searcher.count(orQuery), 1);
84+
hits = searcher.search(orQuery, 1000).scoreDocs;
85+
assertEquals(1, hits.length);
86+
Query rewrite = orQuery.rewrite(ir);
87+
assertEquals(rewrite, orQuery);
88+
89+
SpanNearQuery nearQuery = new SpanNearQuery(
90+
new SpanQuery[] {new SpanMatchNoDocsQuery("same", ""), new SpanMatchNoDocsQuery("same", "")},
91+
0, true);
92+
assertEquals(searcher.count(nearQuery), 0);
93+
hits = searcher.search(nearQuery, 1000).scoreDocs;
94+
assertEquals(0, hits.length);
95+
rewrite = nearQuery.rewrite(ir);
96+
assertEquals(rewrite, nearQuery);
97+
98+
iw.close();
99+
ir.close();
100+
dir.close();
101+
}
102+
103+
public void testEquals() {
104+
Query q1 = new SpanMatchNoDocsQuery("key1", "one");
105+
Query q2 = new SpanMatchNoDocsQuery("key2", "two");
106+
assertTrue(q1.equals(q2));
107+
QueryUtils.check(q1);
108+
}
109+
110+
private void addDoc(String text, IndexWriter iw) throws IOException {
111+
Document doc = new Document();
112+
Field f = newTextField("key", text, Field.Store.YES);
113+
doc.add(f);
114+
iw.addDocument(doc);
115+
}
116+
117+
}

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.apache.lucene.index.IndexReader;
2626
import org.apache.lucene.index.RandomIndexWriter;
2727
import org.apache.lucene.index.Term;
28+
import org.apache.lucene.queries.SpanMatchNoDocsQuery;
2829
import org.apache.lucene.search.BooleanQuery;
2930
import org.apache.lucene.search.BoostQuery;
3031
import org.apache.lucene.search.MultiTermQuery;
@@ -84,6 +85,9 @@ protected SpanMultiTermQueryBuilder doCreateTestQueryBuilder() {
8485

8586
@Override
8687
protected void doAssertLuceneQuery(SpanMultiTermQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException {
88+
if (query instanceof SpanMatchNoDocsQuery) {
89+
return;
90+
}
8791
if (queryBuilder.innerQuery().boost() != AbstractQueryBuilder.DEFAULT_BOOST) {
8892
assertThat(query, instanceOf(SpanBoostQuery.class));
8993
SpanBoostQuery boostQuery = (SpanBoostQuery) query;
@@ -100,7 +104,7 @@ protected void doAssertLuceneQuery(SpanMultiTermQueryBuilder queryBuilder, Query
100104
}
101105
assertThat(multiTermQuery, either(instanceOf(MultiTermQuery.class)).or(instanceOf(TermQuery.class)));
102106
assertThat(spanMultiTermQueryWrapper.getWrappedQuery(),
103-
equalTo(new SpanMultiTermQueryWrapper<>((MultiTermQuery)multiTermQuery).getWrappedQuery()));
107+
equalTo(new SpanMultiTermQueryWrapper<>((MultiTermQuery) multiTermQuery).getWrappedQuery()));
104108
}
105109

106110
public void testIllegalArgument() {
@@ -157,6 +161,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
157161
public void writeTo(StreamOutput out) throws IOException {
158162

159163
}
164+
165+
@Override
166+
public String fieldName() {
167+
return "foo";
168+
}
160169
}
161170

162171
/**

0 commit comments

Comments
 (0)