LUCENE-7337: empty boolean query now rewrites to MatchNoDocsQuery instead of vice/versa

This commit is contained in:
Mike McCandless 2016-06-21 11:26:19 -04:00
parent 5e2f340cfa
commit a3fc7efbcc
10 changed files with 76 additions and 20 deletions

View File

@ -50,6 +50,10 @@ Improvements
* LUCENE-7345: RAMDirectory now enforces write-once files as well
(Robert Muir, Mike McCandless)
* LUCENE-7337: MatchNoDocsQuery now scores with 0 normalization factor
and empty boolean queries now rewrite to MatchNoDocsQuery instead of
vice/versa (Jim Ferenczi via Mike McCandless)
Optimizations
* LUCENE-7330: Speed up conjunction queries. (Adrien Grand)

View File

@ -147,4 +147,4 @@ public class KNearestNeighborDocumentClassifier extends KNearestNeighborClassifi
}
return indexSearcher.search(mltQuery.build(), k);
}
}
}

View File

@ -228,6 +228,10 @@ public class BooleanQuery extends Query implements Iterable<BooleanClause> {
@Override
public Query rewrite(IndexReader reader) throws IOException {
if (clauses.size() == 0) {
return new MatchNoDocsQuery();
}
// optimize 1-clause queries
if (clauses.size() == 1) {
BooleanClause c = clauses.get(0);

View File

@ -18,22 +18,62 @@ package org.apache.lucene.search;
import java.io.IOException;
import java.util.Set;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.index.Term;
/**
* A query that matches no documents.
* A query that matches no documents.
*/
public class MatchNoDocsQuery extends Query {
@Override
public Query rewrite(IndexReader reader) throws IOException {
// Rewrite to an empty BooleanQuery so no Scorer or Weight is required
return new BooleanQuery.Builder().build();
public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException {
return new Weight(this) {
@Override
public void extractTerms(Set<Term> terms) {
}
@Override
public Explanation explain(LeafReaderContext context, int doc) throws IOException {
return Explanation.noMatch("");
}
@Override
public Scorer scorer(LeafReaderContext context) throws IOException {
return null;
}
@Override
public final float getValueForNormalization() throws IOException {
return 0;
}
@Override
public void normalize(float norm, float boost) {
}
/** Return the normalization factor for this weight. */
protected final float queryNorm() {
return 0;
}
/** Return the boost for this weight. */
protected final float boost() {
return 0;
}
/** Return the score produced by this {@link Weight}. */
protected final float score() {
return 0;
}
};
}
@Override
public String toString(String field) {
return "";
return "";
}
@Override

View File

@ -55,8 +55,8 @@ public class TestBoostQuery extends LuceneTestCase {
IndexSearcher searcher = new IndexSearcher(new MultiReader());
// inner queries are rewritten
Query q = new BoostQuery(new MatchNoDocsQuery(), 2);
assertEquals(new BoostQuery(new BooleanQuery.Builder().build(), 2), searcher.rewrite(q));
Query q = new BoostQuery(new BooleanQuery.Builder().build(), 2);
assertEquals(new BoostQuery(new MatchNoDocsQuery(), 2), searcher.rewrite(q));
// boosts are merged
q = new BoostQuery(new BoostQuery(new MatchAllDocsQuery(), 3), 2);

View File

@ -55,11 +55,7 @@ public class TestMatchNoDocsQuery extends LuceneTestCase {
hits = is.search(new MatchNoDocsQuery(), 1000).scoreDocs;
assertEquals(0, hits.length);
// A MatchNoDocsQuery rewrites to an empty BooleanQuery
MatchNoDocsQuery mndq = new MatchNoDocsQuery();
Query rewritten = mndq.rewrite(ir);
assertTrue(rewritten instanceof BooleanQuery);
assertEquals(0, ((BooleanQuery) rewritten).clauses().size());
hits = is.search(mndq, 1000).scoreDocs;
assertEquals(0, hits.length);

View File

@ -196,9 +196,15 @@ public class TestMultiTermQueryRewrites extends LuceneTestCase {
}
assertEquals("The multi-segment case must produce same rewritten query", q1, q2);
assertEquals("The multi-segment case with duplicates must produce same rewritten query", q1, q3);
checkBooleanQueryBoosts((BooleanQuery) q1);
checkBooleanQueryBoosts((BooleanQuery) q2);
checkBooleanQueryBoosts((BooleanQuery) q3);
if (q1 instanceof MatchNoDocsQuery) {
assertTrue(q1 instanceof MatchNoDocsQuery);
assertTrue(q3 instanceof MatchNoDocsQuery);
} else {
checkBooleanQueryBoosts((BooleanQuery) q1);
checkBooleanQueryBoosts((BooleanQuery) q2);
checkBooleanQueryBoosts((BooleanQuery) q3);
assert false;
}
}
public void testBoosts() throws Exception {

View File

@ -96,8 +96,7 @@ public class TestWildcard extends LuceneTestCase {
wq.setRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_REWRITE);
assertMatches(searcher, wq, 0);
Query q = searcher.rewrite(wq);
assertTrue(q instanceof BooleanQuery);
assertEquals(0, ((BooleanQuery) q).clauses().size());
assertTrue(q instanceof MatchNoDocsQuery);
reader.close();
indexStore.close();
}

View File

@ -46,9 +46,9 @@ public class BoostingQueryTest extends LuceneTestCase {
public void testRewrite() throws IOException {
IndexReader reader = new MultiReader();
BoostingQuery q = new BoostingQuery(new MatchNoDocsQuery(), new MatchAllDocsQuery(), 3);
BoostingQuery q = new BoostingQuery(new BooleanQuery.Builder().build(), new MatchAllDocsQuery(), 3);
Query rewritten = new IndexSearcher(reader).rewrite(q);
Query expectedRewritten = new BoostingQuery(new BooleanQuery.Builder().build(), new MatchAllDocsQuery(), 3);
Query expectedRewritten = new BoostingQuery(new MatchNoDocsQuery(), new MatchAllDocsQuery(), 3);
assertEquals(expectedRewritten, rewritten);
assertSame(rewritten, rewritten.rewrite(reader));
}

View File

@ -31,6 +31,7 @@ import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.BoostQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchNoDocsQuery;
import org.apache.lucene.search.MultiTermQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
@ -295,6 +296,12 @@ public class ComplexPhraseQueryParser extends QueryParser {
allSpanClauses[i] = new SpanTermQuery(new Term(field,
"Dummy clause because no terms found - must match nothing"));
}
} else if (qc instanceof MatchNoDocsQuery) {
// Insert fake term e.g. phrase query was for "Fred Smithe*" and
// there were no "Smithe*" terms - need to
// prevent match on just "Fred".
allSpanClauses[i] = new SpanTermQuery(new Term(field,
"Dummy clause because no terms found - must match nothing"));
} else {
if (qc instanceof TermQuery) {
TermQuery tq = (TermQuery) qc;