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"));