mirror of https://github.com/apache/lucene.git
BooleanQuery/BooleanScorer minNrShouldMatch implementation: LUCENE-395
git-svn-id: https://svn.apache.org/repos/asf/lucene/java/trunk@345056 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
31c271c84b
commit
c40c7cd114
|
@ -179,6 +179,10 @@ New features
|
|||
number of terms the range can cover. Both endpoints may also be open.
|
||||
(Yonik Seeley, LUCENE-383)
|
||||
|
||||
26. Added ability to specify a minimum number of optional clauses that
|
||||
must match in a BooleanQuery. See BooleanQuery.setMinimumNumberShouldMatch().
|
||||
(Paul Elschot, Chris Hostetter via Yonik Seeley, LUCENE-395)
|
||||
|
||||
API Changes
|
||||
|
||||
1. Several methods and fields have been deprecated. The API documentation
|
||||
|
|
|
@ -107,6 +107,41 @@ public class BooleanQuery extends Query {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a minimum number of the optional BooleanClauses
|
||||
* which must be satisifed.
|
||||
*
|
||||
* <p>
|
||||
* By default no optional clauses are neccessary for a match
|
||||
* (unless there are no required clauses). If this method is used,
|
||||
* then the specified numebr of clauses is required.
|
||||
* </p>
|
||||
* <p>
|
||||
* Use of this method is totally independant of specifying that
|
||||
* any specific clauses are required (or prohibited). This number will
|
||||
* only be compared against the number of matching optional clauses.
|
||||
* </p>
|
||||
* <p>
|
||||
* EXPERT NOTE: Using this method will force the use of BooleanWeight2,
|
||||
* regardless of wether setUseScorer14(true) has been called.
|
||||
* </p>
|
||||
*
|
||||
* @param min the number of optional clauses that must match
|
||||
* @see #setUseScorer14
|
||||
*/
|
||||
public void setMinimumNumberShouldMatch(int min) {
|
||||
this.minNrShouldMatch = min;
|
||||
}
|
||||
protected int minNrShouldMatch = 0;
|
||||
|
||||
/**
|
||||
* Gets the minimum number of the optional BooleanClauses
|
||||
* which must be satisifed.
|
||||
*/
|
||||
public int getMinimumNumberShouldMatch() {
|
||||
return minNrShouldMatch;
|
||||
}
|
||||
|
||||
/** Adds a clause to a boolean query. Clauses may be:
|
||||
* <ul>
|
||||
* <li><code>required</code> which means that documents which <i>do not</i>
|
||||
|
@ -299,7 +334,8 @@ public class BooleanQuery extends Query {
|
|||
* and scores documents in document number order.
|
||||
*/
|
||||
public Scorer scorer(IndexReader reader) throws IOException {
|
||||
BooleanScorer2 result = new BooleanScorer2(similarity);
|
||||
BooleanScorer2 result = new BooleanScorer2(similarity,
|
||||
minNrShouldMatch);
|
||||
|
||||
for (int i = 0 ; i < weights.size(); i++) {
|
||||
BooleanClause c = (BooleanClause)clauses.elementAt(i);
|
||||
|
@ -327,6 +363,12 @@ public class BooleanQuery extends Query {
|
|||
}
|
||||
|
||||
protected Weight createWeight(Searcher searcher) throws IOException {
|
||||
|
||||
if (0 < minNrShouldMatch) {
|
||||
// :TODO: should we throw an exception if getUseScorer14 ?
|
||||
return new BooleanWeight2(searcher);
|
||||
}
|
||||
|
||||
return getUseScorer14() ? (Weight) new BooleanWeight(searcher)
|
||||
: (Weight) new BooleanWeight2(searcher);
|
||||
}
|
||||
|
@ -382,7 +424,8 @@ public class BooleanQuery extends Query {
|
|||
/** Prints a user-readable version of this query. */
|
||||
public String toString(String field) {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
if (getBoost() != 1.0) {
|
||||
boolean needParens=(getBoost() != 1.0) || (getMinimumNumberShouldMatch()>0) ;
|
||||
if (needParens) {
|
||||
buffer.append("(");
|
||||
}
|
||||
|
||||
|
@ -405,8 +448,17 @@ public class BooleanQuery extends Query {
|
|||
buffer.append(" ");
|
||||
}
|
||||
|
||||
if (getBoost() != 1.0) {
|
||||
if (needParens) {
|
||||
buffer.append(")");
|
||||
}
|
||||
|
||||
if (getMinimumNumberShouldMatch()>0) {
|
||||
buffer.append('~');
|
||||
buffer.append(getMinimumNumberShouldMatch());
|
||||
}
|
||||
|
||||
if (getBoost() != 1.0f)
|
||||
{
|
||||
buffer.append(ToStringUtils.boost(getBoost()));
|
||||
}
|
||||
|
||||
|
@ -419,12 +471,14 @@ public class BooleanQuery extends Query {
|
|||
return false;
|
||||
BooleanQuery other = (BooleanQuery)o;
|
||||
return (this.getBoost() == other.getBoost())
|
||||
&& this.clauses.equals(other.clauses);
|
||||
&& this.clauses.equals(other.clauses)
|
||||
&& this.getMinimumNumberShouldMatch() == other.getMinimumNumberShouldMatch();
|
||||
}
|
||||
|
||||
/** Returns a hash code value for this object.*/
|
||||
public int hashCode() {
|
||||
return Float.floatToIntBits(getBoost()) ^ clauses.hashCode();
|
||||
return Float.floatToIntBits(getBoost()) ^ clauses.hashCode()
|
||||
+ getMinimumNumberShouldMatch();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -62,9 +62,33 @@ class BooleanScorer2 extends Scorer {
|
|||
*/
|
||||
private Scorer countingSumScorer = null;
|
||||
|
||||
public BooleanScorer2(Similarity similarity) {
|
||||
/** The number of optionalScorers that need to match (if there are any) */
|
||||
private final int minNrShouldMatch;
|
||||
|
||||
/** Create a BooleanScorer2.
|
||||
* @param similarity The similarity to be used.
|
||||
* @param minNrShouldMatch The minimum number of optional added scorers
|
||||
* that should match during the search.
|
||||
* In case no required scorers are added,
|
||||
* at least one of the optional scorers will have to
|
||||
* match during the search.
|
||||
*/
|
||||
public BooleanScorer2(Similarity similarity, int minNrShouldMatch) {
|
||||
super(similarity);
|
||||
if (minNrShouldMatch < 0) {
|
||||
throw new IllegalArgumentException("Minimum number of optional scorers should not be negative");
|
||||
}
|
||||
coordinator = new Coordinator();
|
||||
this.minNrShouldMatch = minNrShouldMatch;
|
||||
}
|
||||
|
||||
/** Create a BooleanScorer2.
|
||||
* In no required scorers are added,
|
||||
* at least one of the optional scorers will have to match during the search.
|
||||
* @param similarity The similarity to be used.
|
||||
*/
|
||||
public BooleanScorer2(Similarity similarity) {
|
||||
this(similarity, 0);
|
||||
}
|
||||
|
||||
public void add(final Scorer scorer, boolean required, boolean prohibited) {
|
||||
|
@ -126,10 +150,11 @@ class BooleanScorer2 extends Scorer {
|
|||
}
|
||||
}
|
||||
|
||||
private Scorer countingDisjunctionSumScorer(List scorers)
|
||||
private Scorer countingDisjunctionSumScorer(List scorers,
|
||||
int minMrShouldMatch)
|
||||
// each scorer from the list counted as a single matcher
|
||||
{
|
||||
return new DisjunctionSumScorer(scorers) {
|
||||
return new DisjunctionSumScorer(scorers, minMrShouldMatch) {
|
||||
private int lastScoredDoc = -1;
|
||||
public float score() throws IOException {
|
||||
if (doc() > lastScoredDoc) {
|
||||
|
@ -143,9 +168,8 @@ class BooleanScorer2 extends Scorer {
|
|||
|
||||
private static Similarity defaultSimilarity = new DefaultSimilarity();
|
||||
|
||||
private Scorer countingConjunctionSumScorer(List requiredScorers)
|
||||
private Scorer countingConjunctionSumScorer(List requiredScorers) {
|
||||
// each scorer from the list counted as a single matcher
|
||||
{
|
||||
final int requiredNrMatchers = requiredScorers.size();
|
||||
ConjunctionScorer cs = new ConjunctionScorer(defaultSimilarity) {
|
||||
private int lastScoredDoc = -1;
|
||||
|
@ -169,91 +193,89 @@ class BooleanScorer2 extends Scorer {
|
|||
return cs;
|
||||
}
|
||||
|
||||
private Scorer dualConjunctionSumScorer(Scorer req1, Scorer req2) { // non counting.
|
||||
final int requiredNrMatchers = requiredScorers.size();
|
||||
ConjunctionScorer cs = new ConjunctionScorer(defaultSimilarity);
|
||||
// All scorers match, so defaultSimilarity super.score() always has 1 as
|
||||
// the coordination factor.
|
||||
// Therefore the sum of the scores of two scorers
|
||||
// is used as score.
|
||||
cs.add(req1);
|
||||
cs.add(req2);
|
||||
return cs;
|
||||
}
|
||||
|
||||
/** Returns the scorer to be used for match counting and score summing.
|
||||
* Uses requiredScorers, optionalScorers and prohibitedScorers.
|
||||
*/
|
||||
private Scorer makeCountingSumScorer()
|
||||
// each scorer counted as a single matcher
|
||||
{
|
||||
if (requiredScorers.size() == 0) {
|
||||
private Scorer makeCountingSumScorer() { // each scorer counted as a single matcher
|
||||
return (requiredScorers.size() == 0)
|
||||
? makeCountingSumScorerNoReq()
|
||||
: makeCountingSumScorerSomeReq();
|
||||
}
|
||||
|
||||
private Scorer makeCountingSumScorerNoReq() { // No required scorers
|
||||
if (optionalScorers.size() == 0) {
|
||||
return new NonMatchingScorer(); // only prohibited scorers
|
||||
} else if (optionalScorers.size() == 1) {
|
||||
return makeCountingSumScorer2( // the only optional scorer is required
|
||||
new SingleMatchScorer((Scorer) optionalScorers.get(0)),
|
||||
new ArrayList()); // no optional scorers left
|
||||
} else { // more than 1 optionalScorers, no required scorers
|
||||
return makeCountingSumScorer2( // at least one optional scorer is required
|
||||
countingDisjunctionSumScorer(optionalScorers),
|
||||
new ArrayList()); // no optional scorers left
|
||||
return new NonMatchingScorer(); // no clauses or only prohibited clauses
|
||||
} else { // No required scorers. At least one optional scorer.
|
||||
// minNrShouldMatch optional scorers are required, but at least 1
|
||||
int nrOptRequired = (minNrShouldMatch < 1) ? 1 : minNrShouldMatch;
|
||||
if (optionalScorers.size() < nrOptRequired) {
|
||||
return new NonMatchingScorer(); // fewer optional clauses than minimum (at least 1) that should match
|
||||
} else { // optionalScorers.size() >= nrOptRequired, no required scorers
|
||||
Scorer requiredCountingSumScorer =
|
||||
(optionalScorers.size() > nrOptRequired)
|
||||
? countingDisjunctionSumScorer(optionalScorers, nrOptRequired)
|
||||
: // optionalScorers.size() == nrOptRequired (all optional scorers are required), no required scorers
|
||||
(optionalScorers.size() == 1)
|
||||
? new SingleMatchScorer((Scorer) optionalScorers.get(0))
|
||||
: countingConjunctionSumScorer(optionalScorers);
|
||||
return addProhibitedScorers( requiredCountingSumScorer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Scorer makeCountingSumScorerSomeReq() { // At least one required scorer.
|
||||
if (optionalScorers.size() < minNrShouldMatch) {
|
||||
return new NonMatchingScorer(); // fewer optional clauses than minimum that should match
|
||||
} else if (optionalScorers.size() == minNrShouldMatch) { // all optional scorers also required.
|
||||
ArrayList allReq = new ArrayList(requiredScorers);
|
||||
allReq.addAll(optionalScorers);
|
||||
return addProhibitedScorers( countingConjunctionSumScorer(allReq));
|
||||
} else { // optionalScorers.size() > minNrShouldMatch, and at least one required scorer
|
||||
Scorer requiredCountingSumScorer =
|
||||
(requiredScorers.size() == 1)
|
||||
? new SingleMatchScorer((Scorer) requiredScorers.get(0))
|
||||
: countingConjunctionSumScorer(requiredScorers);
|
||||
if (minNrShouldMatch > 0) { // use a required disjunction scorer over the optional scorers
|
||||
return addProhibitedScorers(
|
||||
dualConjunctionSumScorer( // non counting
|
||||
requiredCountingSumScorer,
|
||||
countingDisjunctionSumScorer(
|
||||
optionalScorers,
|
||||
minNrShouldMatch)));
|
||||
} else { // minNrShouldMatch == 0
|
||||
return new ReqOptSumScorer(
|
||||
addProhibitedScorers(requiredCountingSumScorer),
|
||||
((optionalScorers.size() == 1)
|
||||
? new SingleMatchScorer((Scorer) optionalScorers.get(0))
|
||||
: countingDisjunctionSumScorer(optionalScorers, 1))); // require 1 in combined, optional scorer.
|
||||
}
|
||||
} else if (requiredScorers.size() == 1) { // 1 required
|
||||
return makeCountingSumScorer2(
|
||||
new SingleMatchScorer((Scorer) requiredScorers.get(0)),
|
||||
optionalScorers);
|
||||
} else { // more required scorers
|
||||
return makeCountingSumScorer2(
|
||||
countingConjunctionSumScorer(requiredScorers),
|
||||
optionalScorers);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the scorer to be used for match counting and score summing.
|
||||
* Uses the arguments and prohibitedScorers.
|
||||
* Uses the given required scorer and the prohibitedScorers.
|
||||
* @param requiredCountingSumScorer A required scorer already built.
|
||||
* @param optionalScorers A list of optional scorers, possibly empty.
|
||||
*/
|
||||
private Scorer makeCountingSumScorer2(
|
||||
Scorer requiredCountingSumScorer,
|
||||
List optionalScorers) // not match counting
|
||||
private Scorer addProhibitedScorers(Scorer requiredCountingSumScorer)
|
||||
{
|
||||
if (optionalScorers.size() == 0) { // no optional
|
||||
if (prohibitedScorers.size() == 0) { // no prohibited
|
||||
return requiredCountingSumScorer;
|
||||
} else if (prohibitedScorers.size() == 1) { // no optional, 1 prohibited
|
||||
return new ReqExclScorer(
|
||||
requiredCountingSumScorer,
|
||||
(Scorer) prohibitedScorers.get(0)); // not match counting
|
||||
} else { // no optional, more prohibited
|
||||
return new ReqExclScorer(
|
||||
requiredCountingSumScorer,
|
||||
new DisjunctionSumScorer(prohibitedScorers)); // score unused. not match counting
|
||||
}
|
||||
} else if (optionalScorers.size() == 1) { // 1 optional
|
||||
return makeCountingSumScorer3(
|
||||
requiredCountingSumScorer,
|
||||
new SingleMatchScorer((Scorer) optionalScorers.get(0)));
|
||||
} else { // more optional
|
||||
return makeCountingSumScorer3(
|
||||
requiredCountingSumScorer,
|
||||
countingDisjunctionSumScorer(optionalScorers));
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns the scorer to be used for match counting and score summing.
|
||||
* Uses the arguments and prohibitedScorers.
|
||||
* @param requiredCountingSumScorer A required scorer already built.
|
||||
* @param optionalCountingSumScorer An optional scorer already built.
|
||||
*/
|
||||
private Scorer makeCountingSumScorer3(
|
||||
Scorer requiredCountingSumScorer,
|
||||
Scorer optionalCountingSumScorer)
|
||||
{
|
||||
if (prohibitedScorers.size() == 0) { // no prohibited
|
||||
return new ReqOptSumScorer(requiredCountingSumScorer,
|
||||
optionalCountingSumScorer);
|
||||
} else if (prohibitedScorers.size() == 1) { // 1 prohibited
|
||||
return new ReqOptSumScorer(
|
||||
new ReqExclScorer(requiredCountingSumScorer,
|
||||
(Scorer) prohibitedScorers.get(0)), // not match counting
|
||||
optionalCountingSumScorer);
|
||||
} else { // more prohibited
|
||||
return new ReqOptSumScorer(
|
||||
new ReqExclScorer(
|
||||
requiredCountingSumScorer,
|
||||
new DisjunctionSumScorer(prohibitedScorers)), // score unused. not match counting
|
||||
optionalCountingSumScorer);
|
||||
}
|
||||
return (prohibitedScorers.size() == 0)
|
||||
? requiredCountingSumScorer // no prohibited
|
||||
: new ReqExclScorer(requiredCountingSumScorer,
|
||||
((prohibitedScorers.size() == 1)
|
||||
? (Scorer) prohibitedScorers.get(0)
|
||||
: new DisjunctionSumScorer(prohibitedScorers)));
|
||||
}
|
||||
|
||||
/** Scores and collects all matching documents.
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
package org.apache.lucene.search;
|
||||
|
||||
/**
|
||||
* Copyright 2005 Apache Software Foundation
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
|
||||
import org.apache.lucene.store.RAMDirectory;
|
||||
import org.apache.lucene.store.Directory;
|
||||
|
||||
import org.apache.lucene.index.IndexWriter;
|
||||
import org.apache.lucene.index.IndexReader;
|
||||
import org.apache.lucene.index.Term;
|
||||
|
||||
import org.apache.lucene.analysis.WhitespaceAnalyzer;
|
||||
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
|
||||
import org.apache.lucene.queryParser.QueryParser;
|
||||
import org.apache.lucene.queryParser.ParseException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Random;
|
||||
|
||||
/** Test that BooleanQuery.setMinimumNumberShouldMatch works.
|
||||
*/
|
||||
public class TestBooleanMinShouldMatch extends TestCase {
|
||||
|
||||
|
||||
public Directory index;
|
||||
public IndexReader r;
|
||||
public IndexSearcher s;
|
||||
|
||||
public void setUp() throws Exception {
|
||||
|
||||
|
||||
String[] data = new String [] {
|
||||
"A 1 2 3 4 5 6",
|
||||
"Z 4 5 6",
|
||||
null,
|
||||
"B 2 4 5 6",
|
||||
"Y 3 5 6",
|
||||
null,
|
||||
"C 3 6",
|
||||
"X 4 5 6"
|
||||
};
|
||||
|
||||
index = new RAMDirectory();
|
||||
IndexWriter writer = new IndexWriter(index,
|
||||
new WhitespaceAnalyzer(),
|
||||
true);
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
Document doc = new Document();
|
||||
doc.add(Field.Keyword("id",String.valueOf(i)));
|
||||
doc.add(Field.Keyword("all","all"));
|
||||
if (null != data[i]) {
|
||||
doc.add(Field.Text("data",data[i]));
|
||||
}
|
||||
writer.addDocument(doc);
|
||||
}
|
||||
|
||||
writer.optimize();
|
||||
writer.close();
|
||||
|
||||
r = IndexReader.open(index);
|
||||
s = new IndexSearcher(r);
|
||||
|
||||
//System.out.println("Set up " + getName());
|
||||
}
|
||||
|
||||
public void verifyNrHits(Query q, int expected) throws Exception {
|
||||
Hits h = s.search(q);
|
||||
if (expected != h.length()) {
|
||||
printHits(getName(), h);
|
||||
}
|
||||
assertEquals("result count", expected, h.length());
|
||||
}
|
||||
|
||||
public void testAllOptional() throws Exception {
|
||||
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
for (int i = 1; i <=4; i++) {
|
||||
q.add(new TermQuery(new Term("data",""+i)), false, false);
|
||||
}
|
||||
q.setMinimumNumberShouldMatch(2); // match at least two of 4
|
||||
verifyNrHits(q, 2);
|
||||
}
|
||||
|
||||
public void testOneReqAndSomeOptional() throws Exception {
|
||||
|
||||
/* one required, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
|
||||
|
||||
verifyNrHits(q, 5);
|
||||
}
|
||||
|
||||
public void testSomeReqAndSomeOptional() throws Exception {
|
||||
|
||||
/* two required, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
|
||||
|
||||
verifyNrHits(q, 5);
|
||||
}
|
||||
|
||||
public void testOneProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* one prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testSomeProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* two prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "C" )), false, true );
|
||||
|
||||
q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testOneReqOneProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* one required, one prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testSomeReqOneProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* two required, one prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all")), true, false);
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testOneReqSomeProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* one required, two prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "C" )), false, true );
|
||||
|
||||
q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testSomeReqSomeProhibAndSomeOptional() throws Exception {
|
||||
|
||||
/* two required, two prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all")), true, false);
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "C" )), false, true );
|
||||
|
||||
q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testMinHigherThenNumOptional() throws Exception {
|
||||
|
||||
/* two required, two prohibited, some optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all")), true, false);
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "5" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "4" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, true );
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "1" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "C" )), false, true );
|
||||
|
||||
q.setMinimumNumberShouldMatch(90); // 90 of 4 optional ?!?!?!
|
||||
|
||||
verifyNrHits(q, 0);
|
||||
}
|
||||
|
||||
public void testMinEqualToNumOptional() throws Exception {
|
||||
|
||||
/* two required, two optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "6" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "2" )), false, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(2); // 2 of 2 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testOneOptionalEqualToMin() throws Exception {
|
||||
|
||||
/* two required, one optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "3" )), false, false);
|
||||
q.add(new TermQuery(new Term("data", "2" )), true, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(1); // 1 of 1 optional
|
||||
|
||||
verifyNrHits(q, 1);
|
||||
}
|
||||
|
||||
public void testNoOptionalButMin() throws Exception {
|
||||
|
||||
/* two required, no optional */
|
||||
BooleanQuery q = new BooleanQuery();
|
||||
q.add(new TermQuery(new Term("all", "all" )), true, false);
|
||||
q.add(new TermQuery(new Term("data", "2" )), true, false);
|
||||
|
||||
q.setMinimumNumberShouldMatch(1); // 1 of 0 optional
|
||||
|
||||
verifyNrHits(q, 0);
|
||||
}
|
||||
|
||||
|
||||
public void testRandomQueries() throws Exception {
|
||||
final Random rnd = new Random(0);
|
||||
|
||||
String field="data";
|
||||
String[] vals = {"1","2","3","4","5","6","A","Z","B","Y","Z","X","foo"};
|
||||
int maxLev=4;
|
||||
|
||||
// callback object to set a random setMinimumNumberShouldMatch
|
||||
TestBoolean2.Callback minNrCB = new TestBoolean2.Callback() {
|
||||
public void postCreate(BooleanQuery q) {
|
||||
BooleanClause[] c =q.getClauses();
|
||||
int opt=0;
|
||||
for (int i=0; i<c.length;i++) {
|
||||
if (c[i].getOccur() == BooleanClause.Occur.SHOULD) opt++;
|
||||
}
|
||||
q.setMinimumNumberShouldMatch(rnd.nextInt(opt+2));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
int tot=0;
|
||||
// increase number of iterations for more complete testing
|
||||
for (int i=0; i<1000; i++) {
|
||||
int lev = rnd.nextInt(maxLev);
|
||||
BooleanQuery q1 = TestBoolean2.randBoolQuery(new Random(i), lev, field, vals, null);
|
||||
// BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(i), lev, field, vals, minNrCB);
|
||||
BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(i), lev, field, vals, null);
|
||||
// only set minimumNumberShouldMatch on the top level query since setting
|
||||
// at a lower level can change the score.
|
||||
minNrCB.postCreate(q2);
|
||||
|
||||
// Can't use Hits because normalized scores will mess things
|
||||
// up. The non-sorting version of search() that returns TopDocs
|
||||
// will not normalize scores.
|
||||
TopDocs top1 = s.search(q1,null,100);
|
||||
TopDocs top2 = s.search(q2,null,100);
|
||||
tot+=top2.totalHits;
|
||||
|
||||
// The constrained query
|
||||
// should be a superset to the unconstrained query.
|
||||
if (top2.totalHits > top1.totalHits) {
|
||||
TestCase.fail("Constrained results not a subset:\n"
|
||||
+ CheckHits.topdocsString(top1,0,0)
|
||||
+ CheckHits.topdocsString(top2,0,0)
|
||||
+ "for query:" + q2.toString());
|
||||
}
|
||||
|
||||
for (int hit=0; hit<top2.totalHits; hit++) {
|
||||
int id = top2.scoreDocs[hit].doc;
|
||||
float score = top2.scoreDocs[hit].score;
|
||||
boolean found=false;
|
||||
// find this doc in other hits
|
||||
for (int other=0; other<top1.totalHits; other++) {
|
||||
if (top1.scoreDocs[other].doc == id) {
|
||||
found=true;
|
||||
float otherScore = top1.scoreDocs[other].score;
|
||||
// check if scores match
|
||||
if (Math.abs(otherScore-score)>1.0e-6f) {
|
||||
TestCase.fail("Doc " + id + " scores don't match\n"
|
||||
+ CheckHits.topdocsString(top1,0,0)
|
||||
+ CheckHits.topdocsString(top2,0,0)
|
||||
+ "for query:" + q2.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if subset
|
||||
if (!found) TestCase.fail("Doc " + id + " not found\n"
|
||||
+ CheckHits.topdocsString(top1,0,0)
|
||||
+ CheckHits.topdocsString(top2,0,0)
|
||||
+ "for query:" + q2.toString());
|
||||
}
|
||||
}
|
||||
// System.out.println("Total hits:"+tot);
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected void printHits(String test, Hits h) throws Exception {
|
||||
|
||||
System.err.println("------- " + test + " -------");
|
||||
|
||||
DecimalFormat f = new DecimalFormat("0.000000");
|
||||
|
||||
for (int i = 0; i < h.length(); i++) {
|
||||
Document d = h.doc(i);
|
||||
float score = h.score(i);
|
||||
System.err.println("#" + i + ": " + f.format(score) + " - " +
|
||||
d.get("id") + " - " + d.get("data"));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue