diff --git a/server/src/main/java/org/apache/lucene/queries/SpanMatchNoDocsQuery.java b/server/src/main/java/org/apache/lucene/queries/SpanMatchNoDocsQuery.java new file mode 100644 index 0000000000000..451b2bde80af4 --- /dev/null +++ b/server/src/main/java/org/apache/lucene/queries/SpanMatchNoDocsQuery.java @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.lucene.queries; + +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.index.Term; +import org.apache.lucene.index.TermContext; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.spans.SpanQuery; +import org.apache.lucene.search.spans.SpanWeight; +import org.apache.lucene.search.spans.Spans; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * A {@link SpanQuery} that matches no documents. + */ +public class SpanMatchNoDocsQuery extends SpanQuery { + private final String field; + private final String reason; + + public SpanMatchNoDocsQuery(String field, String reason) { + this.field = field; + this.reason = reason; + } + + @Override + public String getField() { + return field; + } + + @Override + public String toString(String field) { + return "SpanMatchNoDocsQuery(\"" + reason + "\")"; + } + + @Override + public boolean equals(Object o) { + return sameClassAs(o); + } + + @Override + public int hashCode() { + return classHash(); + } + + @Override + public SpanWeight createWeight(IndexSearcher searcher, boolean needsScores, float boost) throws IOException { + return new SpanWeight(this, searcher, Collections.emptyMap(), boost) { + @Override + public void extractTermContexts(Map contexts) {} + + @Override + public Spans getSpans(LeafReaderContext ctx, Postings requiredPostings) { + return null; + } + + @Override + public void extractTerms(Set terms) {} + + @Override + public boolean isCacheable(LeafReaderContext ctx) { + return true; + } + }; + } +} diff --git a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java index 850ff46ddd94d..9f8cc5d87a7fc 100644 --- a/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/FuzzyQueryBuilder.java @@ -182,6 +182,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeOptionalString(this.rewrite); } + @Override public String fieldName() { return this.fieldName; } diff --git a/server/src/main/java/org/elasticsearch/index/query/MultiTermQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/MultiTermQueryBuilder.java index be9abfc5e44b6..ee9fa5b114b7d 100644 --- a/server/src/main/java/org/elasticsearch/index/query/MultiTermQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/MultiTermQueryBuilder.java @@ -19,5 +19,8 @@ package org.elasticsearch.index.query; public interface MultiTermQueryBuilder extends QueryBuilder { - + /** + * Get the field name for this query. + */ + String fieldName(); } diff --git a/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java index c1cd99d712a5a..eacb2be100c98 100644 --- a/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/PrefixQueryBuilder.java @@ -87,6 +87,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeOptionalString(rewrite); } + @Override public String fieldName() { return this.fieldName; } diff --git a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java index 62f5f47bea50d..efc947deee18b 100644 --- a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java @@ -151,6 +151,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { /** * Get the field name for this query. */ + @Override public String fieldName() { return this.fieldName; } diff --git a/server/src/main/java/org/elasticsearch/index/query/RegexpQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/RegexpQueryBuilder.java index 39d7c1e2cf016..472c101487443 100644 --- a/server/src/main/java/org/elasticsearch/index/query/RegexpQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/RegexpQueryBuilder.java @@ -104,6 +104,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { } /** Returns the field name used in this query. */ + @Override public String fieldName() { return this.fieldName; } diff --git a/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java index 637d93212912f..3101583a5f38a 100644 --- a/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilder.java @@ -21,9 +21,11 @@ import org.apache.lucene.index.Term; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.TermContext; +import org.apache.lucene.queries.SpanMatchNoDocsQuery; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.MatchNoDocsQuery; import org.apache.lucene.search.MultiTermQuery; import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; @@ -190,9 +192,14 @@ protected Query doToQuery(QueryShardContext context) throws IOException { break; } } - final SpanQuery spanQuery; // no MultiTermQuery extends SpanQuery, so SpanBoostQuery is not supported here assert subQuery instanceof SpanBoostQuery == false; + + if (subQuery instanceof MatchNoDocsQuery) { + return new SpanMatchNoDocsQuery(multiTermQueryBuilder.fieldName(), subQuery.toString()); + } + + final SpanQuery spanQuery; if (subQuery instanceof TermQuery) { /** * Text fields that index prefixes can rewrite prefix queries diff --git a/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java index 2136e030dbdb1..d5fd9fe4a9f26 100644 --- a/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/WildcardQueryBuilder.java @@ -100,6 +100,7 @@ protected void doWriteTo(StreamOutput out) throws IOException { out.writeOptionalString(rewrite); } + @Override public String fieldName() { return fieldName; } diff --git a/server/src/test/java/org/apache/lucene/queries/SpanMatchNoDocsQueryTests.java b/server/src/test/java/org/apache/lucene/queries/SpanMatchNoDocsQueryTests.java new file mode 100644 index 0000000000000..6187fc1f7f6d9 --- /dev/null +++ b/server/src/test/java/org/apache/lucene/queries/SpanMatchNoDocsQueryTests.java @@ -0,0 +1,117 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.lucene.queries; + +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.QueryUtils; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.spans.SpanNearQuery; +import org.apache.lucene.search.spans.SpanOrQuery; +import org.apache.lucene.search.spans.SpanQuery; +import org.apache.lucene.search.spans.SpanTermQuery; +import org.apache.lucene.store.Directory; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; + +public class SpanMatchNoDocsQueryTests extends ESTestCase { + public void testSimple() throws Exception { + SpanMatchNoDocsQuery query = new SpanMatchNoDocsQuery("field", "a good reason"); + assertEquals(query.toString(), "SpanMatchNoDocsQuery(\"a good reason\")"); + Query rewrite = query.rewrite(null); + assertTrue(rewrite instanceof SpanMatchNoDocsQuery); + assertEquals(rewrite.toString(), "SpanMatchNoDocsQuery(\"a good reason\")"); + } + + public void testQuery() throws Exception { + Directory dir = newDirectory(); + Analyzer analyzer = new MockAnalyzer(random()); + IndexWriter iw = new IndexWriter(dir, + newIndexWriterConfig(analyzer).setMaxBufferedDocs(2).setMergePolicy(newLogMergePolicy())); + addDoc("one", iw); + addDoc("two", iw); + addDoc("three", iw); + IndexReader ir = DirectoryReader.open(iw); + IndexSearcher searcher = new IndexSearcher(ir); + + Query query = new SpanMatchNoDocsQuery("unkwown", "field not found"); + assertEquals(searcher.count(query), 0); + + ScoreDoc[] hits; + hits = searcher.search(query, 1000).scoreDocs; + assertEquals(0, hits.length); + assertEquals(query.toString(), "SpanMatchNoDocsQuery(\"field not found\")"); + + SpanOrQuery orQuery = new SpanOrQuery( + new SpanMatchNoDocsQuery("unknown", "field not found"), + new SpanTermQuery(new Term("unknown", "one")) + ); + assertEquals(searcher.count(orQuery), 0); + hits = searcher.search(orQuery, 1000).scoreDocs; + assertEquals(0, hits.length); + + orQuery = new SpanOrQuery( + new SpanMatchNoDocsQuery("key", "a good reason"), + new SpanTermQuery(new Term("key", "one")) + ); + assertEquals(searcher.count(orQuery), 1); + hits = searcher.search(orQuery, 1000).scoreDocs; + assertEquals(1, hits.length); + Query rewrite = orQuery.rewrite(ir); + assertEquals(rewrite, orQuery); + + SpanNearQuery nearQuery = new SpanNearQuery( + new SpanQuery[] {new SpanMatchNoDocsQuery("same", ""), new SpanMatchNoDocsQuery("same", "")}, + 0, true); + assertEquals(searcher.count(nearQuery), 0); + hits = searcher.search(nearQuery, 1000).scoreDocs; + assertEquals(0, hits.length); + rewrite = nearQuery.rewrite(ir); + assertEquals(rewrite, nearQuery); + + iw.close(); + ir.close(); + dir.close(); + } + + public void testEquals() { + Query q1 = new SpanMatchNoDocsQuery("key1", "one"); + Query q2 = new SpanMatchNoDocsQuery("key2", "two"); + assertTrue(q1.equals(q2)); + QueryUtils.check(q1); + } + + private void addDoc(String text, IndexWriter iw) throws IOException { + Document doc = new Document(); + Field f = newTextField("key", text, Field.Store.YES); + doc.add(f); + iw.addDocument(doc); + } + +} diff --git a/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java index e610ed7f5d464..b0fe2ec27a38e 100644 --- a/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/SpanMultiTermQueryBuilderTests.java @@ -25,6 +25,7 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.Term; +import org.apache.lucene.queries.SpanMatchNoDocsQuery; import org.apache.lucene.search.BooleanQuery; import org.apache.lucene.search.BoostQuery; import org.apache.lucene.search.MultiTermQuery; @@ -84,6 +85,9 @@ protected SpanMultiTermQueryBuilder doCreateTestQueryBuilder() { @Override protected void doAssertLuceneQuery(SpanMultiTermQueryBuilder queryBuilder, Query query, SearchContext context) throws IOException { + if (query instanceof SpanMatchNoDocsQuery) { + return; + } if (queryBuilder.innerQuery().boost() != AbstractQueryBuilder.DEFAULT_BOOST) { assertThat(query, instanceOf(SpanBoostQuery.class)); SpanBoostQuery boostQuery = (SpanBoostQuery) query; @@ -100,7 +104,7 @@ protected void doAssertLuceneQuery(SpanMultiTermQueryBuilder queryBuilder, Query } assertThat(multiTermQuery, either(instanceOf(MultiTermQuery.class)).or(instanceOf(TermQuery.class))); assertThat(spanMultiTermQueryWrapper.getWrappedQuery(), - equalTo(new SpanMultiTermQueryWrapper<>((MultiTermQuery)multiTermQuery).getWrappedQuery())); + equalTo(new SpanMultiTermQueryWrapper<>((MultiTermQuery) multiTermQuery).getWrappedQuery())); } public void testIllegalArgument() { @@ -157,6 +161,11 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws public void writeTo(StreamOutput out) throws IOException { } + + @Override + public String fieldName() { + return "foo"; + } } /** diff --git a/server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java b/server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java index 359793adcf6af..0e22f33db77c7 100644 --- a/server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/SpanNearQueryBuilderTests.java @@ -19,6 +19,7 @@ package org.elasticsearch.index.query; +import org.apache.lucene.queries.SpanMatchNoDocsQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.spans.SpanBoostQuery; import org.apache.lucene.search.spans.SpanNearQuery; @@ -53,6 +54,7 @@ protected void doAssertLuceneQuery(SpanNearQueryBuilder queryBuilder, Query quer assertThat(query, either(instanceOf(SpanNearQuery.class)) .or(instanceOf(SpanTermQuery.class)) .or(instanceOf(SpanBoostQuery.class)) + .or(instanceOf(SpanMatchNoDocsQuery.class)) .or(instanceOf(MatchAllQueryBuilder.class))); if (query instanceof SpanNearQuery) { SpanNearQuery spanNearQuery = (SpanNearQuery) query;