LUCENE-7276: MatchNoDocsQuery now inclues an optional reason for why it was used

This commit is contained in:
Mike McCandless 2016-07-08 13:07:54 -04:00
parent be8d56ada6
commit cbbc505268
15 changed files with 154 additions and 54 deletions

View File

@ -89,6 +89,9 @@ Improvements
control whether to split on whitespace prior to text analysis. Default
behavior remains unchanged: split-on-whitespace=true. (Steve Rowe)
* LUCENE-7276: MatchNoDocsQuery now includes an optional reason for
why it was used (Jim Ferenczi via Mike McCandless)
Optimizations
* LUCENE-7330, LUCENE-7339: Speed up conjunction queries. (Adrien Grand)

View File

@ -212,7 +212,7 @@ public final class BinaryPoint extends Field {
if (bytesPerDim == -1) {
// There are no points, and we cannot guess the bytesPerDim here, so we return an equivalent query:
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("empty BinaryPoint.newSetQuery");
}
// Don't unexpectedly change the user's incoming values array:

View File

@ -207,7 +207,7 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (clauses.size() == 0) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("empty BooleanQuery");
}
// optimize 1-clause queries
@ -226,7 +226,7 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
return new BoostQuery(new ConstantScoreQuery(query), 0);
case MUST_NOT:
// no positive clauses
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("pure negative BooleanQuery");
default:
throw new AssertionError();
}

View File

@ -28,6 +28,19 @@ import org.apache.lucene.index.Term;
*/
public class MatchNoDocsQuery extends Query {
private final String reason;
/** Default constructor */
public MatchNoDocsQuery() {
this("");
}
/** Provides a reason explaining why this query was used */
public MatchNoDocsQuery(String reason) {
this.reason = reason;
}
@Override
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return new Weight(this) {
@ -37,7 +50,7 @@ public class MatchNoDocsQuery extends Query {
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
return Explanation.noMatch("");
return Explanation.noMatch(reason);
}
@Override
@ -73,7 +86,7 @@ public class MatchNoDocsQuery extends Query {
@Override
public String toString(String field) {
return "";
return "MatchNoDocsQuery(\"" + reason + "\")";
}
@Override

View File

@ -317,7 +317,7 @@ public class MultiPhraseQuery extends Query {
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (termArrays.length == 0) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("empty MultiPhraseQuery");
} else if (termArrays.length == 1) { // optimize one-term case
Term[] terms = termArrays[0];
BooleanQuery.Builder builder = new BooleanQuery.Builder();

View File

@ -271,7 +271,7 @@ public class PhraseQuery extends Query {
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (terms.length == 0) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("empty PhraseQuery");
} else if (terms.length == 1) {
return new TermQuery(terms[0]);
} else if (positions[0] != 0) {

View File

@ -23,10 +23,10 @@ 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.IndexWriter;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.util.LuceneTestCase;
/**
@ -41,23 +41,52 @@ public class TestMatchNoDocsQuery extends LuceneTestCase {
analyzer = new MockAnalyzer(random());
}
public void testSimple() throws Exception {
MatchNoDocsQuery query = new MatchNoDocsQuery();
assertEquals(query.toString(), "MatchNoDocsQuery(\"\")");
query = new MatchNoDocsQuery("field 'title' not found");
assertEquals(query.toString(), "MatchNoDocsQuery(\"field 'title' not found\")");
Query rewrite = query.rewrite(null);
assertTrue(rewrite instanceof MatchNoDocsQuery);
assertEquals(rewrite.toString(), "MatchNoDocsQuery(\"field 'title' not found\")");
}
public void testQuery() throws Exception {
Directory dir = newDirectory();
IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(analyzer).setMaxBufferedDocs(2).setMergePolicy(newLogMergePolicy()));
addDoc("one", iw);
addDoc("two", iw);
addDoc("three four", iw);
addDoc("three", iw);
IndexReader ir = DirectoryReader.open(iw);
IndexSearcher searcher = new IndexSearcher(ir);
Query query = new MatchNoDocsQuery("field not found");
assertEquals(searcher.count(query), 0);
IndexSearcher is = newSearcher(ir);
ScoreDoc[] hits;
hits = is.search(new MatchNoDocsQuery(), 1000).scoreDocs;
hits = searcher.search(new MatchNoDocsQuery(), 1000).scoreDocs;
assertEquals(0, hits.length);
assertEquals(query.toString(), "MatchNoDocsQuery(\"field not found\")");
MatchNoDocsQuery mndq = new MatchNoDocsQuery();
hits = is.search(mndq, 1000).scoreDocs;
BooleanQuery.Builder bq = new BooleanQuery.Builder();
bq.add(new BooleanClause(new TermQuery(new Term("key", "five")), BooleanClause.Occur.SHOULD));
bq.add(new BooleanClause(new MatchNoDocsQuery("field not found"), BooleanClause.Occur.MUST));
query = bq.build();
assertEquals(searcher.count(query), 0);
hits = searcher.search(new MatchNoDocsQuery(), 1000).scoreDocs;
assertEquals(0, hits.length);
assertEquals(query.toString(), "key:five +MatchNoDocsQuery(\"field not found\")");
bq = new BooleanQuery.Builder();
bq.add(new BooleanClause(new TermQuery(new Term("key", "one")), BooleanClause.Occur.SHOULD));
bq.add(new BooleanClause(new MatchNoDocsQuery("field not found"), BooleanClause.Occur.SHOULD));
query = bq.build();
assertEquals(query.toString(), "key:one MatchNoDocsQuery(\"field not found\")");
assertEquals(searcher.count(query), 1);
hits = searcher.search(query, 1000).scoreDocs;
Query rewrite = query.rewrite(ir);
assertEquals(1, hits.length);
assertEquals(rewrite.toString(), "key:one MatchNoDocsQuery(\"field not found\")");
iw.close();
ir.close();
@ -77,5 +106,4 @@ public class TestMatchNoDocsQuery extends LuceneTestCase {
doc.add(f);
iw.addDocument(doc);
}
}

View File

@ -494,7 +494,7 @@ public final class JoinUtil {
int numSegments = indexReader.leaves().size();
final long valueCount;
if (numSegments == 0) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("JoinUtil.createJoinQuery with no segments");
} else if (numSegments == 1) {
// No need to use the ordinal map, because there is just one segment.
ordinalMap = null;
@ -503,7 +503,7 @@ public final class JoinUtil {
if (joinSortedDocValues != null) {
valueCount = joinSortedDocValues.getValueCount();
} else {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("JoinUtil.createJoinQuery: no join values");
}
} else {
if (ordinalMap == null) {

View File

@ -119,7 +119,7 @@ public class CommonTermsQuery extends Query {
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (this.terms.isEmpty()) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("CommonTermsQuery with no terms");
} else if (this.terms.size() == 1) {
return newTermQuery(this.terms.get(0), null);
}

View File

@ -150,7 +150,7 @@ public class SimpleQueryParser extends QueryBuilder {
State state = new State(data, buffer, 0, data.length);
parseSubQuery(state);
if (state.top == null) {
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("empty string passed to query parser");
} else {
return state.top;
}

View File

@ -38,6 +38,7 @@ import org.apache.lucene.queryparser.util.QueryParserTestBase; // javadocs
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
@ -61,7 +62,7 @@ import org.junit.BeforeClass;
*
* @see QueryParserTestBase
*/
//TODO: refactor this to actually extend that class, overriding the tests
//TODO: refactor this to actually extend that class (QueryParserTestBase), overriding the tests
//that it adjusts to fit the precedence requirement, adding its extra tests.
public class TestPrecedenceQueryParser extends LuceneTestCase {
@ -166,6 +167,20 @@ public class TestPrecedenceQueryParser extends LuceneTestCase {
}
}
public void assertMatchNoDocsQuery(String queryString, Analyzer a) throws Exception {
assertMatchNoDocsQuery(getQuery(queryString, a));
}
public void assertMatchNoDocsQuery(Query query) throws Exception {
if (query instanceof MatchNoDocsQuery) {
// good
} else if (query instanceof BooleanQuery && ((BooleanQuery) query).clauses().size() == 0) {
// good
} else {
fail("expected MatchNoDocsQuery or an empty BooleanQuery but got: " + query);
}
}
public void assertWildcardQueryEquals(String query, boolean lowercase,
String result) throws Exception {
PrecedenceQueryParser qp = getParser(null);
@ -282,7 +297,7 @@ public class TestPrecedenceQueryParser extends LuceneTestCase {
public void testNumber() throws Exception {
// The numbers go away because SimpleAnalzyer ignores them
assertQueryEquals("3", null, "");
assertMatchNoDocsQuery("3", null);
assertQueryEquals("term 1.0 1 2", null, "term");
assertQueryEquals("term term1 term2", null, "term term term");
@ -367,8 +382,8 @@ public class TestPrecedenceQueryParser extends LuceneTestCase {
// QueryParser behavior
assertQueryEquals("term AND NOT phrase term", qpAnalyzer,
"(+term -(phrase1 phrase2)) term");
assertQueryEquals("stop", qpAnalyzer, "");
assertQueryEquals("stop OR stop AND stop", qpAnalyzer, "");
assertMatchNoDocsQuery("stop", qpAnalyzer);
assertMatchNoDocsQuery("stop OR stop AND stop", qpAnalyzer);
assertTrue(getQuery("term term term", qpAnalyzer) instanceof BooleanQuery);
assertTrue(getQuery("term +stop", qpAnalyzer) instanceof TermQuery);
}

View File

@ -32,6 +32,7 @@ import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfi
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.store.Directory;
@ -55,9 +56,22 @@ public class TestMultiFieldQPHelper extends LuceneTestCase {
assertStopQueryEquals("one stop", "b:one t:one");
assertStopQueryEquals("one (stop)", "b:one t:one");
assertStopQueryEquals("one ((stop))", "b:one t:one");
assertStopQueryEquals("stop", "");
assertStopQueryEquals("(stop)", "");
assertStopQueryEquals("((stop))", "");
assertStopQueryIsMatchNoDocsQuery("stop");
assertStopQueryIsMatchNoDocsQuery("(stop)");
assertStopQueryIsMatchNoDocsQuery("((stop))");
}
// verify parsing of query using a stopping analyzer
private void assertStopQueryIsMatchNoDocsQuery(String qtxt) throws Exception {
String[] fields = { "b", "t" };
Occur occur[] = { Occur.SHOULD, Occur.SHOULD };
TestQPHelper.QPTestAnalyzer a = new TestQPHelper.QPTestAnalyzer();
StandardQueryParser mfqp = new StandardQueryParser();
mfqp.setMultiFields(fields);
mfqp.setAnalyzer(a);
Query q = mfqp.parse(qtxt, null);
assertTrue(q instanceof MatchNoDocsQuery);
}
// verify parsing of query using a stopping analyzer
@ -205,12 +219,12 @@ public class TestMultiFieldQPHelper extends LuceneTestCase {
String[] queries6 = { "((+stop))", "+((stop))" };
q = QueryParserUtil.parse(queries6, fields, stopA);
assertEquals(" ", q.toString());
assertEquals("MatchNoDocsQuery(\"\") MatchNoDocsQuery(\"\")", q.toString());
//assertEquals(" ", q.toString());
String[] queries7 = { "one ((+stop)) +more", "+((stop)) +two" };
q = QueryParserUtil.parse(queries7, fields, stopA);
assertEquals("(b:one +b:more) (+t:two)", q.toString());
}
public void testStaticMethod2() throws QueryNodeException {

View File

@ -53,8 +53,8 @@ import org.apache.lucene.queryparser.flexible.core.processors.QueryNodeProcessor
import org.apache.lucene.queryparser.flexible.messages.MessageImpl;
import org.apache.lucene.queryparser.flexible.standard.config.StandardQueryConfigHandler;
import org.apache.lucene.queryparser.flexible.standard.nodes.WildcardQueryNode;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.FuzzyQuery;
@ -245,6 +245,20 @@ public class TestQPHelper extends LuceneTestCase {
}
}
public void assertMatchNoDocsQuery(String queryString, Analyzer a) throws Exception {
assertMatchNoDocsQuery(getQuery(queryString, a));
}
public void assertMatchNoDocsQuery(Query query) throws Exception {
if (query instanceof MatchNoDocsQuery) {
// good
} else if (query instanceof BooleanQuery && ((BooleanQuery) query).clauses().size() == 0) {
// good
} else {
fail("expected MatchNoDocsQuery or an empty BooleanQuery but got: " + query);
}
}
public void assertQueryEqualsAllowLeadingWildcard(String query, Analyzer a, String result)
throws Exception {
Query q = getQueryAllowLeadingWildcard(query, a);
@ -522,7 +536,7 @@ public class TestQPHelper extends LuceneTestCase {
public void testNumber() throws Exception {
// The numbers go away because SimpleAnalzyer ignores them
assertQueryEquals("3", null, "");
assertMatchNoDocsQuery("3", null);
assertQueryEquals("term 1.0 1 2", null, "term");
assertQueryEquals("term term1 term2", null, "term term term");
@ -659,14 +673,14 @@ public class TestQPHelper extends LuceneTestCase {
assertQueryEquals("term AND NOT phrase term", qpAnalyzer,
"+term -(phrase1 phrase2) term");
assertQueryEquals("stop^3", qpAnalyzer, "");
assertQueryEquals("stop", qpAnalyzer, "");
assertQueryEquals("(stop)^3", qpAnalyzer, "");
assertQueryEquals("((stop))^3", qpAnalyzer, "");
assertQueryEquals("(stop^3)", qpAnalyzer, "");
assertQueryEquals("((stop)^3)", qpAnalyzer, "");
assertQueryEquals("(stop)", qpAnalyzer, "");
assertQueryEquals("((stop))", qpAnalyzer, "");
assertMatchNoDocsQuery("stop^3", qpAnalyzer);
assertMatchNoDocsQuery("stop", qpAnalyzer);
assertMatchNoDocsQuery("(stop)^3", qpAnalyzer);
assertMatchNoDocsQuery("((stop))^3", qpAnalyzer);
assertMatchNoDocsQuery("(stop^3)", qpAnalyzer);
assertMatchNoDocsQuery("((stop)^3)", qpAnalyzer);
assertMatchNoDocsQuery("(stop)", qpAnalyzer);
assertMatchNoDocsQuery("((stop))", qpAnalyzer);
assertTrue(getQuery("term term term", qpAnalyzer) instanceof BooleanQuery);
assertTrue(getQuery("term +stop", qpAnalyzer) instanceof TermQuery);
}
@ -992,7 +1006,7 @@ public class TestQPHelper extends LuceneTestCase {
q = qp2.parse("the^3", "field");
// "the" is a stop word so the result is an empty query:
assertNotNull(q);
assertEquals("", q.toString());
assertMatchNoDocsQuery(q);
assertFalse(q instanceof BoostQuery);
}

View File

@ -161,8 +161,7 @@ public abstract class QueryParserTestBase extends LuceneTestCase {
return getQuery(query, (Analyzer)null);
}
public void assertQueryEquals(String query, Analyzer a, String result)
throws Exception {
public void assertQueryEquals(String query, Analyzer a, String result) throws Exception {
Query q = getQuery(query, a);
String s = q.toString("field");
if (!s.equals(result)) {
@ -171,6 +170,20 @@ public abstract class QueryParserTestBase extends LuceneTestCase {
}
}
public void assertMatchNoDocsQuery(String queryString, Analyzer a) throws Exception {
assertMatchNoDocsQuery(getQuery(queryString, a));
}
public void assertMatchNoDocsQuery(Query query) throws Exception {
if (query instanceof MatchNoDocsQuery) {
// good
} else if (query instanceof BooleanQuery && ((BooleanQuery) query).clauses().size() == 0) {
// good
} else {
fail("expected MatchNoDocsQuery or an empty BooleanQuery but got: " + query);
}
}
public void assertQueryEquals(CommonQueryParserConfiguration cqpC, String field, String query, String result)
throws Exception {
Query q = getQuery(query, cqpC);
@ -418,7 +431,7 @@ public abstract class QueryParserTestBase extends LuceneTestCase {
public void testNumber() throws Exception {
// The numbers go away because SimpleAnalzyer ignores them
assertQueryEquals("3", null, "");
assertMatchNoDocsQuery("3", null);
assertQueryEquals("term 1.0 1 2", null, "term");
assertQueryEquals("term term1 term2", null, "term term term");
@ -540,14 +553,14 @@ public abstract class QueryParserTestBase extends LuceneTestCase {
// "term phrase1 phrase2 term");
assertQueryEquals("term AND NOT phrase term", qpAnalyzer,
"+term -(phrase1 phrase2) term");
assertQueryEquals("stop^3", qpAnalyzer, "");
assertQueryEquals("stop", qpAnalyzer, "");
assertQueryEquals("(stop)^3", qpAnalyzer, "");
assertQueryEquals("((stop))^3", qpAnalyzer, "");
assertQueryEquals("(stop^3)", qpAnalyzer, "");
assertQueryEquals("((stop)^3)", qpAnalyzer, "");
assertQueryEquals("(stop)", qpAnalyzer, "");
assertQueryEquals("((stop))", qpAnalyzer, "");
assertMatchNoDocsQuery("stop^3", qpAnalyzer);
assertMatchNoDocsQuery("stop", qpAnalyzer);
assertMatchNoDocsQuery("(stop)^3", qpAnalyzer);
assertMatchNoDocsQuery("((stop))^3", qpAnalyzer);
assertMatchNoDocsQuery("(stop^3)", qpAnalyzer);
assertMatchNoDocsQuery("((stop)^3)", qpAnalyzer);
assertMatchNoDocsQuery("(stop)", qpAnalyzer);
assertMatchNoDocsQuery("((stop))", qpAnalyzer);
assertTrue(getQuery("term term term", qpAnalyzer) instanceof BooleanQuery);
assertTrue(getQuery("term +stop", qpAnalyzer) instanceof TermQuery);
@ -881,7 +894,7 @@ public abstract class QueryParserTestBase extends LuceneTestCase {
q = getQuery("the^3", qp2);
// "the" is a stop word so the result is an empty query:
assertNotNull(q);
assertEquals("", q.toString());
assertMatchNoDocsQuery(q);
assertFalse(q instanceof BoostQuery);
}

View File

@ -191,12 +191,12 @@ public class LatLonPoint extends Field {
// and should not drag in extra bogus junk! TODO: should encodeCeil just throw ArithmeticException to be less trappy here?
if (minLatitude == 90.0) {
// range cannot match as 90.0 can never exist
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLatitude=90.0");
}
if (minLongitude == 180.0) {
if (maxLongitude == 180.0) {
// range cannot match as 180.0 can never exist
return new MatchNoDocsQuery();
return new MatchNoDocsQuery("LatLonPoint.newBoxQuery with minLongitude=maxLongitude=180.0");
} else if (maxLongitude < minLongitude) {
// encodeCeil() with dateline wrapping!
minLongitude = -180.0;