001: package org.apache.lucene.search;
002:
003: /**
004: * Licensed to the Apache Software Foundation (ASF) under one or more
005: * contributor license agreements. See the NOTICE file distributed with
006: * this work for additional information regarding copyright ownership.
007: * The ASF licenses this file to You under the Apache License, Version 2.0
008: * (the "License"); you may not use this file except in compliance with
009: * the License. You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019:
020: import junit.framework.TestCase;
021: import org.apache.lucene.util.LuceneTestCase;
022: import org.apache.lucene.analysis.WhitespaceAnalyzer;
023: import org.apache.lucene.document.Document;
024: import org.apache.lucene.document.Field;
025: import org.apache.lucene.index.IndexReader;
026: import org.apache.lucene.index.IndexWriter;
027: import org.apache.lucene.index.Term;
028: import org.apache.lucene.store.Directory;
029: import org.apache.lucene.store.RAMDirectory;
030:
031: import java.text.DecimalFormat;
032: import java.util.Random;
033:
034: /** Test that BooleanQuery.setMinimumNumberShouldMatch works.
035: */
036: public class TestBooleanMinShouldMatch extends LuceneTestCase {
037:
038: public Directory index;
039: public IndexReader r;
040: public IndexSearcher s;
041:
042: public void setUp() throws Exception {
043:
044: super .setUp();
045:
046: String[] data = new String[] { "A 1 2 3 4 5 6",
047: "Z 4 5 6", null, "B 2 4 5 6",
048: "Y 3 5 6", null, "C 3 6", "X 4 5 6" };
049:
050: index = new RAMDirectory();
051: IndexWriter writer = new IndexWriter(index,
052: new WhitespaceAnalyzer(), true);
053:
054: for (int i = 0; i < data.length; i++) {
055: Document doc = new Document();
056: doc.add(new Field("id", String.valueOf(i), Field.Store.YES,
057: Field.Index.UN_TOKENIZED));//Field.Keyword("id",String.valueOf(i)));
058: doc.add(new Field("all", "all", Field.Store.YES,
059: Field.Index.UN_TOKENIZED));//Field.Keyword("all","all"));
060: if (null != data[i]) {
061: doc.add(new Field("data", data[i], Field.Store.YES,
062: Field.Index.TOKENIZED));//Field.Text("data",data[i]));
063: }
064: writer.addDocument(doc);
065: }
066:
067: writer.optimize();
068: writer.close();
069:
070: r = IndexReader.open(index);
071: s = new IndexSearcher(r);
072:
073: //System.out.println("Set up " + getName());
074: }
075:
076: public void verifyNrHits(Query q, int expected) throws Exception {
077: Hits h = s.search(q);
078: if (expected != h.length()) {
079: printHits(getName(), h);
080: }
081: assertEquals("result count", expected, h.length());
082: QueryUtils.check(q, s);
083: }
084:
085: public void testAllOptional() throws Exception {
086:
087: BooleanQuery q = new BooleanQuery();
088: for (int i = 1; i <= 4; i++) {
089: q.add(new TermQuery(new Term("data", "" + i)),
090: BooleanClause.Occur.SHOULD);//false, false);
091: }
092: q.setMinimumNumberShouldMatch(2); // match at least two of 4
093: verifyNrHits(q, 2);
094: }
095:
096: public void testOneReqAndSomeOptional() throws Exception {
097:
098: /* one required, some optional */
099: BooleanQuery q = new BooleanQuery();
100: q.add(new TermQuery(new Term("all", "all")),
101: BooleanClause.Occur.MUST);//true, false);
102: q.add(new TermQuery(new Term("data", "5")),
103: BooleanClause.Occur.SHOULD);//false, false);
104: q.add(new TermQuery(new Term("data", "4")),
105: BooleanClause.Occur.SHOULD);//false, false);
106: q.add(new TermQuery(new Term("data", "3")),
107: BooleanClause.Occur.SHOULD);//false, false);
108:
109: q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
110:
111: verifyNrHits(q, 5);
112: }
113:
114: public void testSomeReqAndSomeOptional() throws Exception {
115:
116: /* two required, some optional */
117: BooleanQuery q = new BooleanQuery();
118: q.add(new TermQuery(new Term("all", "all")),
119: BooleanClause.Occur.MUST);//true, false);
120: q.add(new TermQuery(new Term("data", "6")),
121: BooleanClause.Occur.MUST);//true, false);
122: q.add(new TermQuery(new Term("data", "5")),
123: BooleanClause.Occur.SHOULD);//false, false);
124: q.add(new TermQuery(new Term("data", "4")),
125: BooleanClause.Occur.SHOULD);//false, false);
126: q.add(new TermQuery(new Term("data", "3")),
127: BooleanClause.Occur.SHOULD);//false, false);
128:
129: q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
130:
131: verifyNrHits(q, 5);
132: }
133:
134: public void testOneProhibAndSomeOptional() throws Exception {
135:
136: /* one prohibited, some optional */
137: BooleanQuery q = new BooleanQuery();
138: q.add(new TermQuery(new Term("data", "1")),
139: BooleanClause.Occur.SHOULD);//false, false);
140: q.add(new TermQuery(new Term("data", "2")),
141: BooleanClause.Occur.SHOULD);//false, false);
142: q.add(new TermQuery(new Term("data", "3")),
143: BooleanClause.Occur.MUST_NOT);//false, true );
144: q.add(new TermQuery(new Term("data", "4")),
145: BooleanClause.Occur.SHOULD);//false, false);
146:
147: q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
148:
149: verifyNrHits(q, 1);
150: }
151:
152: public void testSomeProhibAndSomeOptional() throws Exception {
153:
154: /* two prohibited, some optional */
155: BooleanQuery q = new BooleanQuery();
156: q.add(new TermQuery(new Term("data", "1")),
157: BooleanClause.Occur.SHOULD);//false, false);
158: q.add(new TermQuery(new Term("data", "2")),
159: BooleanClause.Occur.SHOULD);//false, false);
160: q.add(new TermQuery(new Term("data", "3")),
161: BooleanClause.Occur.MUST_NOT);//false, true );
162: q.add(new TermQuery(new Term("data", "4")),
163: BooleanClause.Occur.SHOULD);//false, false);
164: q.add(new TermQuery(new Term("data", "C")),
165: BooleanClause.Occur.MUST_NOT);//false, true );
166:
167: q.setMinimumNumberShouldMatch(2); // 2 of 3 optional
168:
169: verifyNrHits(q, 1);
170: }
171:
172: public void testOneReqOneProhibAndSomeOptional() throws Exception {
173:
174: /* one required, one prohibited, some optional */
175: BooleanQuery q = new BooleanQuery();
176: q.add(new TermQuery(new Term("data", "6")),
177: BooleanClause.Occur.MUST);// true, false);
178: q.add(new TermQuery(new Term("data", "5")),
179: BooleanClause.Occur.SHOULD);//false, false);
180: q.add(new TermQuery(new Term("data", "4")),
181: BooleanClause.Occur.SHOULD);//false, false);
182: q.add(new TermQuery(new Term("data", "3")),
183: BooleanClause.Occur.MUST_NOT);//false, true );
184: q.add(new TermQuery(new Term("data", "2")),
185: BooleanClause.Occur.SHOULD);//false, false);
186: q.add(new TermQuery(new Term("data", "1")),
187: BooleanClause.Occur.SHOULD);//false, false);
188:
189: q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
190:
191: verifyNrHits(q, 1);
192: }
193:
194: public void testSomeReqOneProhibAndSomeOptional() throws Exception {
195:
196: /* two required, one prohibited, some optional */
197: BooleanQuery q = new BooleanQuery();
198: q.add(new TermQuery(new Term("all", "all")),
199: BooleanClause.Occur.MUST);//true, false);
200: q.add(new TermQuery(new Term("data", "6")),
201: BooleanClause.Occur.MUST);//true, false);
202: q.add(new TermQuery(new Term("data", "5")),
203: BooleanClause.Occur.SHOULD);//false, false);
204: q.add(new TermQuery(new Term("data", "4")),
205: BooleanClause.Occur.SHOULD);//false, false);
206: q.add(new TermQuery(new Term("data", "3")),
207: BooleanClause.Occur.MUST_NOT);//false, true );
208: q.add(new TermQuery(new Term("data", "2")),
209: BooleanClause.Occur.SHOULD);//false, false);
210: q.add(new TermQuery(new Term("data", "1")),
211: BooleanClause.Occur.SHOULD);//false, false);
212:
213: q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
214:
215: verifyNrHits(q, 1);
216: }
217:
218: public void testOneReqSomeProhibAndSomeOptional() throws Exception {
219:
220: /* one required, two prohibited, some optional */
221: BooleanQuery q = new BooleanQuery();
222: q.add(new TermQuery(new Term("data", "6")),
223: BooleanClause.Occur.MUST);//true, false);
224: q.add(new TermQuery(new Term("data", "5")),
225: BooleanClause.Occur.SHOULD);//false, false);
226: q.add(new TermQuery(new Term("data", "4")),
227: BooleanClause.Occur.SHOULD);//false, false);
228: q.add(new TermQuery(new Term("data", "3")),
229: BooleanClause.Occur.MUST_NOT);//false, true );
230: q.add(new TermQuery(new Term("data", "2")),
231: BooleanClause.Occur.SHOULD);//false, false);
232: q.add(new TermQuery(new Term("data", "1")),
233: BooleanClause.Occur.SHOULD);//false, false);
234: q.add(new TermQuery(new Term("data", "C")),
235: BooleanClause.Occur.MUST_NOT);//false, true );
236:
237: q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
238:
239: verifyNrHits(q, 1);
240: }
241:
242: public void testSomeReqSomeProhibAndSomeOptional() throws Exception {
243:
244: /* two required, two prohibited, some optional */
245: BooleanQuery q = new BooleanQuery();
246: q.add(new TermQuery(new Term("all", "all")),
247: BooleanClause.Occur.MUST);//true, false);
248: q.add(new TermQuery(new Term("data", "6")),
249: BooleanClause.Occur.MUST);//true, false);
250: q.add(new TermQuery(new Term("data", "5")),
251: BooleanClause.Occur.SHOULD);//false, false);
252: q.add(new TermQuery(new Term("data", "4")),
253: BooleanClause.Occur.SHOULD);//false, false);
254: q.add(new TermQuery(new Term("data", "3")),
255: BooleanClause.Occur.MUST_NOT);//false, true );
256: q.add(new TermQuery(new Term("data", "2")),
257: BooleanClause.Occur.SHOULD);//false, false);
258: q.add(new TermQuery(new Term("data", "1")),
259: BooleanClause.Occur.SHOULD);//false, false);
260: q.add(new TermQuery(new Term("data", "C")),
261: BooleanClause.Occur.MUST_NOT);//false, true );
262:
263: q.setMinimumNumberShouldMatch(3); // 3 of 4 optional
264:
265: verifyNrHits(q, 1);
266: }
267:
268: public void testMinHigherThenNumOptional() throws Exception {
269:
270: /* two required, two prohibited, some optional */
271: BooleanQuery q = new BooleanQuery();
272: q.add(new TermQuery(new Term("all", "all")),
273: BooleanClause.Occur.MUST);//true, false);
274: q.add(new TermQuery(new Term("data", "6")),
275: BooleanClause.Occur.MUST);//true, false);
276: q.add(new TermQuery(new Term("data", "5")),
277: BooleanClause.Occur.SHOULD);//false, false);
278: q.add(new TermQuery(new Term("data", "4")),
279: BooleanClause.Occur.SHOULD);//false, false);
280: q.add(new TermQuery(new Term("data", "3")),
281: BooleanClause.Occur.MUST_NOT);//false, true );
282: q.add(new TermQuery(new Term("data", "2")),
283: BooleanClause.Occur.SHOULD);//false, false);
284: q.add(new TermQuery(new Term("data", "1")),
285: BooleanClause.Occur.SHOULD);//false, false);
286: q.add(new TermQuery(new Term("data", "C")),
287: BooleanClause.Occur.MUST_NOT);//false, true );
288:
289: q.setMinimumNumberShouldMatch(90); // 90 of 4 optional ?!?!?!
290:
291: verifyNrHits(q, 0);
292: }
293:
294: public void testMinEqualToNumOptional() throws Exception {
295:
296: /* two required, two optional */
297: BooleanQuery q = new BooleanQuery();
298: q.add(new TermQuery(new Term("all", "all")),
299: BooleanClause.Occur.SHOULD);//false, false);
300: q.add(new TermQuery(new Term("data", "6")),
301: BooleanClause.Occur.MUST);//true, false);
302: q.add(new TermQuery(new Term("data", "3")),
303: BooleanClause.Occur.MUST);//true, false);
304: q.add(new TermQuery(new Term("data", "2")),
305: BooleanClause.Occur.SHOULD);//false, false);
306:
307: q.setMinimumNumberShouldMatch(2); // 2 of 2 optional
308:
309: verifyNrHits(q, 1);
310: }
311:
312: public void testOneOptionalEqualToMin() throws Exception {
313:
314: /* two required, one optional */
315: BooleanQuery q = new BooleanQuery();
316: q.add(new TermQuery(new Term("all", "all")),
317: BooleanClause.Occur.MUST);//true, false);
318: q.add(new TermQuery(new Term("data", "3")),
319: BooleanClause.Occur.SHOULD);//false, false);
320: q.add(new TermQuery(new Term("data", "2")),
321: BooleanClause.Occur.MUST);//true, false);
322:
323: q.setMinimumNumberShouldMatch(1); // 1 of 1 optional
324:
325: verifyNrHits(q, 1);
326: }
327:
328: public void testNoOptionalButMin() throws Exception {
329:
330: /* two required, no optional */
331: BooleanQuery q = new BooleanQuery();
332: q.add(new TermQuery(new Term("all", "all")),
333: BooleanClause.Occur.MUST);//true, false);
334: q.add(new TermQuery(new Term("data", "2")),
335: BooleanClause.Occur.MUST);//true, false);
336:
337: q.setMinimumNumberShouldMatch(1); // 1 of 0 optional
338:
339: verifyNrHits(q, 0);
340: }
341:
342: public void testRandomQueries() throws Exception {
343: final Random rnd = new Random(0);
344:
345: String field = "data";
346: String[] vals = { "1", "2", "3", "4", "5", "6", "A", "Z", "B",
347: "Y", "Z", "X", "foo" };
348: int maxLev = 4;
349:
350: // callback object to set a random setMinimumNumberShouldMatch
351: TestBoolean2.Callback minNrCB = new TestBoolean2.Callback() {
352: public void postCreate(BooleanQuery q) {
353: BooleanClause[] c = q.getClauses();
354: int opt = 0;
355: for (int i = 0; i < c.length; i++) {
356: if (c[i].getOccur() == BooleanClause.Occur.SHOULD)
357: opt++;
358: }
359: q.setMinimumNumberShouldMatch(rnd.nextInt(opt + 2));
360: }
361: };
362:
363: // increase number of iterations for more complete testing
364: for (int i = 0; i < 1000; i++) {
365: int lev = rnd.nextInt(maxLev);
366: BooleanQuery q1 = TestBoolean2.randBoolQuery(new Random(i),
367: lev, field, vals, null);
368: // BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(i), lev, field, vals, minNrCB);
369: BooleanQuery q2 = TestBoolean2.randBoolQuery(new Random(i),
370: lev, field, vals, null);
371: // only set minimumNumberShouldMatch on the top level query since setting
372: // at a lower level can change the score.
373: minNrCB.postCreate(q2);
374:
375: // Can't use Hits because normalized scores will mess things
376: // up. The non-sorting version of search() that returns TopDocs
377: // will not normalize scores.
378: TopDocs top1 = s.search(q1, null, 100);
379: TopDocs top2 = s.search(q2, null, 100);
380:
381: QueryUtils.check(q1, s);
382: QueryUtils.check(q2, s);
383:
384: // The constrained query
385: // should be a superset to the unconstrained query.
386: if (top2.totalHits > top1.totalHits) {
387: TestCase.fail("Constrained results not a subset:\n"
388: + CheckHits.topdocsString(top1, 0, 0)
389: + CheckHits.topdocsString(top2, 0, 0)
390: + "for query:" + q2.toString());
391: }
392:
393: for (int hit = 0; hit < top2.totalHits; hit++) {
394: int id = top2.scoreDocs[hit].doc;
395: float score = top2.scoreDocs[hit].score;
396: boolean found = false;
397: // find this doc in other hits
398: for (int other = 0; other < top1.totalHits; other++) {
399: if (top1.scoreDocs[other].doc == id) {
400: found = true;
401: float otherScore = top1.scoreDocs[other].score;
402: // check if scores match
403: if (Math.abs(otherScore - score) > 1.0e-6f) {
404: TestCase.fail("Doc "
405: + id
406: + " scores don't match\n"
407: + CheckHits.topdocsString(top1, 0,
408: 0)
409: + CheckHits.topdocsString(top2, 0,
410: 0) + "for query:"
411: + q2.toString());
412: }
413: }
414: }
415:
416: // check if subset
417: if (!found)
418: TestCase.fail("Doc " + id + " not found\n"
419: + CheckHits.topdocsString(top1, 0, 0)
420: + CheckHits.topdocsString(top2, 0, 0)
421: + "for query:" + q2.toString());
422: }
423: }
424: // System.out.println("Total hits:"+tot);
425: }
426:
427: protected void printHits(String test, Hits h) throws Exception {
428:
429: System.err.println("------- " + test + " -------");
430:
431: DecimalFormat f = new DecimalFormat("0.000000");
432:
433: for (int i = 0; i < h.length(); i++) {
434: Document d = h.doc(i);
435: float score = h.score(i);
436: System.err.println("#" + i + ": " + f.format(score) + " - "
437: + d.get("id") + " - " + d.get("data"));
438: }
439: }
440: }
|