1 package org.apache.lucene.search;
4 * Licensed to the Apache Software Foundation (ASF) under one or more
5 * contributor license agreements. See the NOTICE file distributed with
6 * this work for additional information regarding copyright ownership.
7 * The ASF licenses this file to You under the Apache License, Version 2.0
8 * (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
20 import org.apache.lucene.util.LuceneTestCase;
21 import org.apache.lucene.document.Document;
22 import org.apache.lucene.document.Field;
23 import org.apache.lucene.index.IndexReader;
24 import org.apache.lucene.index.RandomIndexWriter;
25 import org.apache.lucene.index.Term;
26 import org.apache.lucene.store.Directory;
27 import org.junit.AfterClass;
28 import org.junit.BeforeClass;
30 import java.text.DecimalFormat;
31 import java.util.Random;
33 /** Test that BooleanQuery.setMinimumNumberShouldMatch works.
35 public class TestBooleanMinShouldMatch extends LuceneTestCase {
37 private static Directory index;
38 private static IndexReader r;
39 private static IndexSearcher s;
42 public static void beforeClass() throws Exception {
43 String[] data = new String [] {
54 index = newDirectory();
55 RandomIndexWriter w = new RandomIndexWriter(random, index);
57 for (int i = 0; i < data.length; i++) {
58 Document doc = new Document();
59 doc.add(newField("id", String.valueOf(i), Field.Store.YES, Field.Index.NOT_ANALYZED));//Field.Keyword("id",String.valueOf(i)));
60 doc.add(newField("all", "all", Field.Store.YES, Field.Index.NOT_ANALYZED));//Field.Keyword("all","all"));
61 if (null != data[i]) {
62 doc.add(newField("data", data[i], Field.Store.YES, Field.Index.ANALYZED));//Field.Text("data",data[i]));
70 //System.out.println("Set up " + getName());
74 public static void afterClass() throws Exception {
84 public void verifyNrHits(Query q, int expected) throws Exception {
85 ScoreDoc[] h = s.search(q, null, 1000).scoreDocs;
86 if (expected != h.length) {
87 printHits(getName(), h, s);
89 assertEquals("result count", expected, h.length);
90 QueryUtils.check(random, q,s);
93 public void testAllOptional() throws Exception {
95 BooleanQuery q = new BooleanQuery();
96 for (int i = 1; i <=4; i++) {
97 q.add(new TermQuery(new Term("data",""+i)), BooleanClause.Occur.SHOULD);//false, false);
99 q.setMinimumNumberShouldMatch(2); // match at least two of 4
103 public void testOneReqAndSomeOptional() throws Exception {
105 /* one required, some optional */
106 BooleanQuery q = new BooleanQuery();
107 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.MUST);//true, false);
108 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
109 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
110 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.SHOULD);//false, false);
112 q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
117 public void testSomeReqAndSomeOptional() throws Exception {
119 /* two required, some optional */
120 BooleanQuery q = new BooleanQuery();
121 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.MUST);//true, false);
122 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
123 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
124 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
125 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.SHOULD);//false, false);
127 q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
132 public void testOneProhibAndSomeOptional() throws Exception {
134 /* one prohibited, some optional */
135 BooleanQuery q = new BooleanQuery();
136 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
137 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
138 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
139 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
141 q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
146 public void testSomeProhibAndSomeOptional() throws Exception {
148 /* two prohibited, some optional */
149 BooleanQuery q = new BooleanQuery();
150 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
151 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
152 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
153 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
154 q.add(new TermQuery(new Term("data", "C" )), BooleanClause.Occur.MUST_NOT);//false, true );
156 q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
161 public void testOneReqOneProhibAndSomeOptional() throws Exception {
163 /* one required, one prohibited, some optional */
164 BooleanQuery q = new BooleanQuery();
165 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);// true, false);
166 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
167 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
168 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
169 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
170 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
172 q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
177 public void testSomeReqOneProhibAndSomeOptional() throws Exception {
179 /* two required, one prohibited, some optional */
180 BooleanQuery q = new BooleanQuery();
181 q.add(new TermQuery(new Term("all", "all")), BooleanClause.Occur.MUST);//true, false);
182 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
183 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
184 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
185 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
186 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
187 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
189 q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
194 public void testOneReqSomeProhibAndSomeOptional() throws Exception {
196 /* one required, two prohibited, some optional */
197 BooleanQuery q = new BooleanQuery();
198 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
199 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
200 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
201 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
202 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
203 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
204 q.add(new TermQuery(new Term("data", "C" )), BooleanClause.Occur.MUST_NOT);//false, true );
206 q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
211 public void testSomeReqSomeProhibAndSomeOptional() throws Exception {
213 /* two required, two prohibited, some optional */
214 BooleanQuery q = new BooleanQuery();
215 q.add(new TermQuery(new Term("all", "all")), BooleanClause.Occur.MUST);//true, false);
216 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
217 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
218 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
219 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
220 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
221 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
222 q.add(new TermQuery(new Term("data", "C" )), BooleanClause.Occur.MUST_NOT);//false, true );
224 q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
229 public void testMinHigherThenNumOptional() throws Exception {
231 /* two required, two prohibited, some optional */
232 BooleanQuery q = new BooleanQuery();
233 q.add(new TermQuery(new Term("all", "all")), BooleanClause.Occur.MUST);//true, false);
234 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
235 q.add(new TermQuery(new Term("data", "5" )), BooleanClause.Occur.SHOULD);//false, false);
236 q.add(new TermQuery(new Term("data", "4" )), BooleanClause.Occur.SHOULD);//false, false);
237 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST_NOT);//false, true );
238 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
239 q.add(new TermQuery(new Term("data", "1" )), BooleanClause.Occur.SHOULD);//false, false);
240 q.add(new TermQuery(new Term("data", "C" )), BooleanClause.Occur.MUST_NOT);//false, true );
242 q.setMinimumNumberShouldMatch(90); // 90 of 4 optional ?!?!?!
247 public void testMinEqualToNumOptional() throws Exception {
249 /* two required, two optional */
250 BooleanQuery q = new BooleanQuery();
251 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.SHOULD);//false, false);
252 q.add(new TermQuery(new Term("data", "6" )), BooleanClause.Occur.MUST);//true, false);
253 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.MUST);//true, false);
254 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.SHOULD);//false, false);
256 q.setMinimumNumberShouldMatch(2); // 2 of 2 optional
261 public void testOneOptionalEqualToMin() throws Exception {
263 /* two required, one optional */
264 BooleanQuery q = new BooleanQuery();
265 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.MUST);//true, false);
266 q.add(new TermQuery(new Term("data", "3" )), BooleanClause.Occur.SHOULD);//false, false);
267 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.MUST);//true, false);
269 q.setMinimumNumberShouldMatch(1); // 1 of 1 optional
274 public void testNoOptionalButMin() throws Exception {
276 /* two required, no optional */
277 BooleanQuery q = new BooleanQuery();
278 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.MUST);//true, false);
279 q.add(new TermQuery(new Term("data", "2" )), BooleanClause.Occur.MUST);//true, false);
281 q.setMinimumNumberShouldMatch(1); // 1 of 0 optional
286 public void testNoOptionalButMin2() throws Exception {
288 /* one required, no optional */
289 BooleanQuery q = new BooleanQuery();
290 q.add(new TermQuery(new Term("all", "all" )), BooleanClause.Occur.MUST);//true, false);
292 q.setMinimumNumberShouldMatch(1); // 1 of 0 optional
297 public void testRandomQueries() throws Exception {
299 String[] vals = {"1","2","3","4","5","6","A","Z","B","Y","Z","X","foo"};
302 // callback object to set a random setMinimumNumberShouldMatch
303 TestBoolean2.Callback minNrCB = new TestBoolean2.Callback() {
304 public void postCreate(BooleanQuery q) {
305 BooleanClause[] c =q.getClauses();
307 for (int i=0; i<c.length;i++) {
308 if (c[i].getOccur() == BooleanClause.Occur.SHOULD) opt++;
310 q.setMinimumNumberShouldMatch(random.nextInt(opt+2));
316 // increase number of iterations for more complete testing
317 int num = atLeast(10);
318 for (int i = 0; i < num; i++) {
319 int lev = random.nextInt(maxLev);
320 final long seed = random.nextLong();
321 BooleanQuery q1 = TestBoolean2.randBoolQuery(new Random(seed), true, lev, field, vals, null);
322 // BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(seed), lev, field, vals, minNrCB);
323 BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(seed), true, lev, field, vals, null);
324 // only set minimumNumberShouldMatch on the top level query since setting
325 // at a lower level can change the score.
326 minNrCB.postCreate(q2);
328 // Can't use Hits because normalized scores will mess things
329 // up. The non-sorting version of search() that returns TopDocs
330 // will not normalize scores.
331 TopDocs top1 = s.search(q1,null,100);
332 TopDocs top2 = s.search(q2,null,100);
334 QueryUtils.check(random, q1,s);
335 QueryUtils.check(random, q2,s);
337 // The constrained query
338 // should be a superset to the unconstrained query.
339 if (top2.totalHits > top1.totalHits) {
340 fail("Constrained results not a subset:\n"
341 + CheckHits.topdocsString(top1,0,0)
342 + CheckHits.topdocsString(top2,0,0)
343 + "for query:" + q2.toString());
346 for (int hit=0; hit<top2.totalHits; hit++) {
347 int id = top2.scoreDocs[hit].doc;
348 float score = top2.scoreDocs[hit].score;
350 // find this doc in other hits
351 for (int other=0; other<top1.totalHits; other++) {
352 if (top1.scoreDocs[other].doc == id) {
354 float otherScore = top1.scoreDocs[other].score;
355 // check if scores match
356 if (Math.abs(otherScore-score)>1.0e-6f) {
357 fail("Doc " + id + " scores don't match\n"
358 + CheckHits.topdocsString(top1,0,0)
359 + CheckHits.topdocsString(top2,0,0)
360 + "for query:" + q2.toString());
366 if (!found) fail("Doc " + id + " not found\n"
367 + CheckHits.topdocsString(top1,0,0)
368 + CheckHits.topdocsString(top2,0,0)
369 + "for query:" + q2.toString());
372 // System.out.println("Total hits:"+tot);
377 protected void printHits(String test, ScoreDoc[] h, Searcher searcher) throws Exception {
379 System.err.println("------- " + test + " -------");
381 DecimalFormat f = new DecimalFormat("0.000000");
383 for (int i = 0; i < h.length; i++) {
384 Document d = searcher.doc(h[i].doc);
385 float score = h[i].score;
386 System.err.println("#" + i + ": " + f.format(score) + " - " +
387 d.get("id") + " - " + d.get("data"));