Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1438185) +++ lucene/CHANGES.txt (working copy) @@ -73,6 +73,9 @@ API Changes * LUCENE-4709: FacetResultNode no longer has a residue field. (Shai Erera) + +* LUCENE-4716: DrillDown.query now takes Occur, allowing to specify if + categories should be OR'ed or AND'ed. (Shai Erera) Bug Fixes Index: lucene/facet/src/examples/org/apache/lucene/facet/example/simple/SimpleSearcher.java =================================================================== --- lucene/facet/src/examples/org/apache/lucene/facet/example/simple/SimpleSearcher.java (revision 1438185) +++ lucene/facet/src/examples/org/apache/lucene/facet/example/simple/SimpleSearcher.java (working copy) @@ -1,6 +1,5 @@ package org.apache.lucene.facet.example.simple; -import java.util.Arrays; import java.util.Iterator; import java.util.List; @@ -17,6 +16,7 @@ import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.Query; @@ -153,7 +153,7 @@ CategoryPath categoryOfInterest = resIterator.next().label; // drill-down preparation: turn the base query into a drill-down query for the category of interest - Query q2 = DrillDown.query(indexingParams, baseQuery, categoryOfInterest); + Query q2 = DrillDown.query(indexingParams, baseQuery, Occur.MUST, categoryOfInterest); // that's it - search with the new query and we're done! // only documents both matching the base query AND containing the Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java (revision 1438185) +++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java (working copy) @@ -59,18 +59,23 @@ } /** - * Wraps a given {@link Query} as a drill-down query over the given - * categories, assuming all are required (e.g. {@code AND}). You can construct - * a query with different modes (such as {@code OR} or {@code AND} of - * {@code ORs}) by creating a {@link BooleanQuery} and call this method - * several times. Make sure to wrap the query in that case by - * {@link ConstantScoreQuery} and set the boost to 0.0f, so that it doesn't - * affect scoring. + * Wraps a given {@link Query} by a drill-down query over the given + * categories. {@link Occur} defines the relationship between the cateories + * (e.g. {@code OR} or {@code AND}. If you need to construct a more + * complicated relationship, e.g. {@code AND} of {@code ORs}), call this + * method with every group of categories with the same relationship and then + * construct a {@link BooleanQuery} which will wrap all returned queries. It + * is advised to construct that boolean query with coord disabled, and also + * wrap the final query with {@link ConstantScoreQuery} and set its boost to + * {@code 0.0f}. *

+ * NOTE: {@link Occur} only makes sense when there is more than one + * {@link CategoryPath} given. + *

* NOTE: {@code baseQuery} can be {@code null}, in which case only the * {@link Query} over the categories will is returned. */ - public static final Query query(FacetIndexingParams iParams, Query baseQuery, CategoryPath... paths) { + public static final Query query(FacetIndexingParams iParams, Query baseQuery, Occur occur, CategoryPath... paths) { if (paths == null || paths.length == 0) { throw new IllegalArgumentException("Empty category path not allowed for drill down query!"); } @@ -81,7 +86,7 @@ } else { BooleanQuery bq = new BooleanQuery(true); // disable coord for (CategoryPath cp : paths) { - bq.add(new TermQuery(term(iParams, cp)), Occur.MUST); + bq.add(new TermQuery(term(iParams, cp)), occur); } q = bq; } @@ -100,10 +105,10 @@ } /** - * @see #query(FacetIndexingParams, Query, CategoryPath...) + * @see #query(FacetIndexingParams, Query, Occur, CategoryPath...) */ - public static final Query query(FacetSearchParams sParams, Query baseQuery, CategoryPath... paths) { - return query(sParams.indexingParams, baseQuery, paths); + public static final Query query(FacetSearchParams sParams, Query baseQuery, Occur occur, CategoryPath... paths) { + return query(sParams.indexingParams, baseQuery, occur, paths); } } Index: lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java (revision 1438188) +++ lucene/facet/src/test/org/apache/lucene/facet/index/TestFacetsPayloadMigrationReader.java (working copy) @@ -58,6 +58,7 @@ import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.Query; import org.apache.lucene.search.TotalHitCountCollector; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.IOUtils; @@ -284,7 +285,7 @@ for (String dim : expectedCounts.keySet()) { CategoryPath drillDownCP = new CategoryPath(dim); FacetSearchParams fsp = new FacetSearchParams(fip, new CountFacetRequest(drillDownCP, 10)); - Query drillDown = DrillDown.query(fsp, new MatchAllDocsQuery(), drillDownCP); + Query drillDown = DrillDown.query(fsp, new MatchAllDocsQuery(), Occur.MUST, drillDownCP); TotalHitCountCollector total = new TotalHitCountCollector(); FacetsCollector fc = FacetsCollector.create(fsp, indexReader, taxoReader); searcher.search(drillDown, MultiCollector.wrap(fc, total)); Index: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (revision 1438188) +++ lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (working copy) @@ -27,6 +27,7 @@ import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TopDocs; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; import org.junit.AfterClass; import org.junit.BeforeClass; @@ -129,25 +130,25 @@ IndexSearcher searcher = newSearcher(reader); // Making sure the query yields 25 documents with the facet "a" - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); TopDocs docs = searcher.search(q, 100); assertEquals(25, docs.totalHits); // Making sure the query yields 5 documents with the facet "b" and the // previous (facet "a") query as a base query - Query q2 = DrillDown.query(defaultParams, q, new CategoryPath("b")); + Query q2 = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q2, 100); assertEquals(5, docs.totalHits); // Making sure that a query of both facet "a" and facet "b" yields 5 results - Query q3 = DrillDown.query(defaultParams, null, new CategoryPath("a"), new CategoryPath("b")); + Query q3 = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a"), new CategoryPath("b")); docs = searcher.search(q3, 100); assertEquals(5, docs.totalHits); // Check that content:foo (which yields 50% results) and facet/b (which yields 20%) // would gather together 10 results (10%..) Query fooQuery = new TermQuery(new Term("content", "foo")); - Query q4 = DrillDown.query(defaultParams, fooQuery, new CategoryPath("b")); + Query q4 = DrillDown.query(defaultParams, fooQuery, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -157,18 +158,18 @@ IndexSearcher searcher = newSearcher(reader); // Create the base query to start with - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); // Making sure the query yields 5 documents with the facet "b" and the // previous (facet "a") query as a base query - Query q2 = DrillDown.query(defaultParams, q, new CategoryPath("b")); + Query q2 = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("b")); TopDocs docs = searcher.search(q2, 100); assertEquals(5, docs.totalHits); // Check that content:foo (which yields 50% results) and facet/b (which yields 20%) // would gather together 10 results (10%..) Query fooQuery = new TermQuery(new Term("content", "foo")); - Query q4 = DrillDown.query(defaultParams, fooQuery, new CategoryPath("b")); + Query q4 = DrillDown.query(defaultParams, fooQuery, Occur.MUST, new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -203,7 +204,7 @@ } // create a drill-down query with category "a", scores should not change - q = DrillDown.query(defaultParams, q, new CategoryPath("a")); + q = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("a")); docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query for (ScoreDoc sd : docs.scoreDocs) { assertEquals("score of doc=" + sd.doc + " modified", scores[sd.doc], sd.score, 0f); @@ -215,11 +216,21 @@ // verify that drill-down queries (with no base query) returns 0.0 score IndexSearcher searcher = newSearcher(reader); - Query q = DrillDown.query(defaultParams, null, new CategoryPath("a")); + Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query for (ScoreDoc sd : docs.scoreDocs) { assertEquals(0f, sd.score, 0f); } } + + @Test + public void testOrQuery() throws Exception { + IndexSearcher searcher = newSearcher(reader); + + // Making sure that a query of facet "a" or facet "b" yields 0 results + Query q = DrillDown.query(defaultParams, null, Occur.SHOULD, new CategoryPath("a"), new CategoryPath("b")); + TopDocs docs = searcher.search(q, 100); + assertEquals(40, docs.totalHits); + } } Index: lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java (revision 1438188) +++ lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java (working copy) @@ -40,6 +40,7 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; +import org.apache.lucene.search.BooleanClause.Occur; import org.apache.lucene.store.Directory; public class TestDemoFacets extends FacetTestCase { @@ -111,7 +112,7 @@ // Now user drills down on Publish Date/2010: fsp = new FacetSearchParams(new CountFacetRequest(new CategoryPath("Author"), 10)); - Query q2 = DrillDown.query(fsp, new MatchAllDocsQuery(), new CategoryPath("Publish Date/2010", '/')); + Query q2 = DrillDown.query(fsp, new MatchAllDocsQuery(), Occur.MUST, new CategoryPath("Publish Date/2010", '/')); c = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader); searcher.search(q2, c); results = c.getFacetResults();