diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 40155a3a221..d9413329339 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -66,6 +66,9 @@ Changes in backwards compatibility policy * LUCENE-4761: Facet packages reorganized. Should be easy to fix your import statements, if you use an IDE such as Eclipse. (Shai Erera) +* LUCENE-4750: Convert DrillDown to DrillDownQuery, so you can initialize it + and add drill-down categories to it. (Michael McCandless, Shai Erera) + Optimizations * LUCENE-4687: BloomFilterPostingsFormat now lazily initializes delegate diff --git a/lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java b/lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java index a3ca72683d7..326094dad60 100644 --- a/lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java +++ b/lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java @@ -7,7 +7,7 @@ import org.apache.lucene.demo.facet.ExampleUtils; import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.FacetSearchParams; import org.apache.lucene.facet.search.CountFacetRequest; -import org.apache.lucene.facet.search.DrillDown; +import org.apache.lucene.facet.search.DrillDownQuery; import org.apache.lucene.facet.search.FacetRequest; import org.apache.lucene.facet.search.FacetResult; import org.apache.lucene.facet.search.FacetResultNode; @@ -16,7 +16,6 @@ import org.apache.lucene.facet.taxonomy.CategoryPath; 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; @@ -156,7 +155,8 @@ public class SimpleSearcher { 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, Occur.MUST, categoryOfInterest); + DrillDownQuery q2 = new DrillDownQuery(indexingParams, baseQuery); + q2.add(categoryOfInterest); // that's it - search with the new query and we're done! // only documents both matching the base query AND containing the diff --git a/lucene/facet/src/java/org/apache/lucene/facet/doc-files/userguide.html b/lucene/facet/src/java/org/apache/lucene/facet/doc-files/userguide.html index 051aad0870e..ca68aca72ea 100755 --- a/lucene/facet/src/java/org/apache/lucene/facet/doc-files/userguide.html +++ b/lucene/facet/src/java/org/apache/lucene/facet/doc-files/userguide.html @@ -612,7 +612,8 @@ data set into a portion of it by specifying a certain category, is what we call We now show the required code lines for implementing such a drill-down.
 Query baseQuery = queryParser.parse("tennis racquet");
-Query q2 = DrillDown.query(baseQuery, new CategoryPath("make", "head"), 10));
+DrillDownQuery q2 = new DrillDownQuery(indexingParams, baseQuery);
+q2.add(new CategoryPath("make", "head"), 10));
 

In line 1 the original user query is created and then used to obtain information on diff --git a/lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java b/lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java index 0b3e3f1f777..83536e26a31 100644 --- a/lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java +++ b/lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java @@ -10,7 +10,7 @@ import org.apache.lucene.search.DocIdSetIterator; import org.apache.lucene.util.Bits; import org.apache.lucene.facet.params.FacetSearchParams; -import org.apache.lucene.facet.search.DrillDown; +import org.apache.lucene.facet.search.DrillDownQuery; import org.apache.lucene.facet.search.FacetResult; import org.apache.lucene.facet.search.FacetResultNode; import org.apache.lucene.facet.search.ScoredDocIDs; @@ -105,7 +105,7 @@ class TakmiSampleFixer implements SampleFixer { } CategoryPath catPath = fresNode.label; - Term drillDownTerm = DrillDown.term(searchParams, catPath); + Term drillDownTerm = DrillDownQuery.term(searchParams.indexingParams, catPath); // TODO (Facet): avoid Multi*? Bits liveDocs = MultiFields.getLiveDocs(indexReader); int updatedCount = countIntersection(MultiFields.getTermDocsEnum(indexReader, liveDocs, diff --git a/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java b/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java deleted file mode 100644 index 670df7e796d..00000000000 --- a/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.apache.lucene.facet.search; - -import org.apache.lucene.index.Term; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.ConstantScoreQuery; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.BooleanClause.Occur; - -import org.apache.lucene.facet.params.CategoryListParams; -import org.apache.lucene.facet.params.FacetIndexingParams; -import org.apache.lucene.facet.params.FacetSearchParams; -import org.apache.lucene.facet.taxonomy.CategoryPath; - -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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. - */ - -/** - * Utility class for creating drill-down {@link Query queries} or {@link Term - * terms} over {@link CategoryPath}. This can be used to e.g. narrow down a - * user's search to selected categories. - *

- * NOTE: if you choose to create your own {@link Query} by calling - * {@link #term}, it is recommended to wrap it with {@link ConstantScoreQuery} - * and set the {@link ConstantScoreQuery#setBoost(float) boost} to {@code 0.0f}, - * so that it does not affect the scores of the documents. - * - * @lucene.experimental - */ -public final class DrillDown { - - /** - * @see #term(FacetIndexingParams, CategoryPath) - */ - public static final Term term(FacetSearchParams sParams, CategoryPath path) { - return term(sParams.indexingParams, path); - } - - /** Return a drill-down {@link Term} for a category. */ - public static final Term term(FacetIndexingParams iParams, CategoryPath path) { - CategoryListParams clp = iParams.getCategoryListParams(path); - char[] buffer = new char[path.fullPathLength()]; - iParams.drillDownTermText(path, buffer); - return new Term(clp.field, String.valueOf(buffer)); - } - - /** - * 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, Occur occur, CategoryPath... paths) { - if (paths == null || paths.length == 0) { - throw new IllegalArgumentException("Empty category path not allowed for drill down query!"); - } - - final Query q; - if (paths.length == 1) { - q = new TermQuery(term(iParams, paths[0])); - } else { - BooleanQuery bq = new BooleanQuery(true); // disable coord - for (CategoryPath cp : paths) { - bq.add(new TermQuery(term(iParams, cp)), occur); - } - q = bq; - } - - final ConstantScoreQuery drillDownQuery = new ConstantScoreQuery(q); - drillDownQuery.setBoost(0.0f); - - if (baseQuery == null) { - return drillDownQuery; - } else { - BooleanQuery res = new BooleanQuery(true); - res.add(baseQuery, Occur.MUST); - res.add(drillDownQuery, Occur.MUST); - return res; - } - } - - /** - * @see #query - */ - public static final Query query(FacetSearchParams sParams, Query baseQuery, Occur occur, CategoryPath... paths) { - return query(sParams.indexingParams, baseQuery, occur, paths); - } - -} diff --git a/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java b/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java new file mode 100644 index 00000000000..2851e3147ab --- /dev/null +++ b/lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java @@ -0,0 +1,149 @@ +package org.apache.lucene.facet.search; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.FacetIndexingParams; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause.Occur; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.ConstantScoreQuery; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * A {@link Query} for drill-down over {@link CategoryPath categories}. You + * should call {@link #add(CategoryPath...)} for every group of categories you + * want to drill-down over. Each category in the group is {@code OR'ed} with + * the others, and groups are {@code AND'ed}. + *

+ * NOTE: if you choose to create your own {@link Query} by calling + * {@link #term}, it is recommended to wrap it with {@link ConstantScoreQuery} + * and set the {@link ConstantScoreQuery#setBoost(float) boost} to {@code 0.0f}, + * so that it does not affect the scores of the documents. + * + * @lucene.experimental + */ +public final class DrillDownQuery extends Query { + + /** Return a drill-down {@link Term} for a category. */ + public static final Term term(FacetIndexingParams iParams, CategoryPath path) { + CategoryListParams clp = iParams.getCategoryListParams(path); + char[] buffer = new char[path.fullPathLength()]; + iParams.drillDownTermText(path, buffer); + return new Term(clp.field, String.valueOf(buffer)); + } + + private final BooleanQuery query; + private final Set drillDownDims = new HashSet(); + + private final FacetIndexingParams fip; + + /* Used by clone() */ + private DrillDownQuery(FacetIndexingParams fip, BooleanQuery query, Set drillDownDims) { + this.fip = fip; + this.query = query.clone(); + this.drillDownDims.addAll(drillDownDims); + } + + /** + * Creates a new {@link DrillDownQuery} without a base query, which means that + * you intend to perfor a pure browsing query (equivalent to using + * {@link MatchAllDocsQuery} as base. + */ + public DrillDownQuery(FacetIndexingParams fip) { + this(fip, null); + } + + /** + * Creates a new {@link DrillDownQuery} over the given base query. Can be + * {@code null}, in which case the result {@link Query} from + * {@link #rewrite(IndexReader)} will be a pure browsing query, filtering on + * the added categories only. + */ + public DrillDownQuery(FacetIndexingParams fip, Query baseQuery) { + query = new BooleanQuery(true); // disable coord + if (baseQuery != null) { + query.add(baseQuery, Occur.MUST); + } + this.fip = fip; + } + + /** + * Adds one dimension of drill downs; if you pass multiple values they are + * OR'd, and then the entire dimension is AND'd against the base query. + */ + public void add(CategoryPath... paths) { + Query q; + String dim = paths[0].components[0]; + if (drillDownDims.contains(dim)) { + throw new IllegalArgumentException("dimension '" + dim + "' was already added"); + } + if (paths.length == 1) { + if (paths[0].length == 0) { + throw new IllegalArgumentException("all CategoryPaths must have length > 0"); + } + q = new TermQuery(term(fip, paths[0])); + } else { + BooleanQuery bq = new BooleanQuery(true); // disable coord + for (CategoryPath cp : paths) { + if (cp.length == 0) { + throw new IllegalArgumentException("all CategoryPaths must have length > 0"); + } + if (!cp.components[0].equals(dim)) { + throw new IllegalArgumentException("multiple (OR'd) drill-down paths must be under same dimension; got '" + + dim + "' and '" + cp.components[0] + "'"); + } + bq.add(new TermQuery(term(fip, cp)), Occur.SHOULD); + } + q = bq; + } + drillDownDims.add(dim); + + final ConstantScoreQuery drillDownQuery = new ConstantScoreQuery(q); + drillDownQuery.setBoost(0.0f); + query.add(drillDownQuery, Occur.MUST); + } + + @Override + public DrillDownQuery clone() { + return new DrillDownQuery(fip, query, drillDownDims); + } + + @Override + public int hashCode() { + return query.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DrillDownQuery)) { + return false; + } + + DrillDownQuery other = (DrillDownQuery) obj; + return query.equals(other.query); + } + + @Override + public Query rewrite(IndexReader r) throws IOException { + if (query.clauses().size() == 0) { + // baseQuery given to the ctor was null + no drill-downs were added + // note that if only baseQuery was given to the ctor, but no drill-down terms + // is fine, since the rewritten query will be the original base query. + throw new IllegalStateException("no base query or drill-down categories given"); + } + return query; + } + + @Override + public String toString(String field) { + return query.toString(field); + } + +} diff --git a/lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java index 3f58bb509a3..4f475250737 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java @@ -3,7 +3,7 @@ package org.apache.lucene.facet.params; import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.params.CategoryListParams; import org.apache.lucene.facet.params.FacetIndexingParams; -import org.apache.lucene.facet.search.DrillDown; +import org.apache.lucene.facet.search.DrillDownQuery; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.index.Term; @@ -39,7 +39,7 @@ public class FacetIndexingParamsTest extends FacetTestCase { + dfip.getFacetDelimChar() + "b"; CategoryPath cp = new CategoryPath("a", "b"); assertEquals("wrong drill-down term", new Term("$facets", - expectedDDText), DrillDown.term(dfip,cp)); + expectedDDText), DrillDownQuery.term(dfip,cp)); char[] buf = new char[20]; int numchars = dfip.drillDownTermText(cp, buf); assertEquals("3 characters should be written", 3, numchars); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java b/lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java index 62d9cfe76ac..62aec7c1c04 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java @@ -6,7 +6,7 @@ import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.params.CategoryListParams; import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.PerDimensionIndexingParams; -import org.apache.lucene.facet.search.DrillDown; +import org.apache.lucene.facet.search.DrillDownQuery; import org.apache.lucene.facet.taxonomy.CategoryPath; import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.index.Term; @@ -38,7 +38,7 @@ public class PerDimensionIndexingParamsTest extends FacetTestCase { assertEquals("Expected default category list field is $facets", "$facets", ifip.getCategoryListParams(null).field); String expectedDDText = "a" + ifip.getFacetDelimChar() + "b"; CategoryPath cp = new CategoryPath("a", "b"); - assertEquals("wrong drill-down term", new Term("$facets", expectedDDText), DrillDown.term(ifip,cp)); + assertEquals("wrong drill-down term", new Term("$facets", expectedDDText), DrillDownQuery.term(ifip,cp)); char[] buf = new char[20]; int numchars = ifip.drillDownTermText(cp, buf); assertEquals("3 characters should be written", 3, numchars); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java similarity index 60% rename from lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java rename to lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java index 1c620485afb..b539bf9b5a6 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java @@ -1,9 +1,44 @@ package org.apache.lucene.facet.search; +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; +import java.util.Random; import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockTokenizer; @@ -23,12 +58,13 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.Term; import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.Query; 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.apache.lucene.util.IOUtils; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -50,28 +86,27 @@ import org.junit.Test; * limitations under the License. */ -public class DrillDownTest extends FacetTestCase { +public class DrillDownQueryTest extends FacetTestCase { - private FacetIndexingParams defaultParams; - private PerDimensionIndexingParams nonDefaultParams; private static IndexReader reader; private static DirectoryTaxonomyReader taxo; private static Directory dir; private static Directory taxoDir; - public DrillDownTest() { - Map paramsMap = new HashMap(); - paramsMap.put(new CategoryPath("a"), randomCategoryListParams("testing_facets_a")); - paramsMap.put(new CategoryPath("b"), randomCategoryListParams("testing_facets_b")); - nonDefaultParams = new PerDimensionIndexingParams(paramsMap); - defaultParams = new FacetIndexingParams(randomCategoryListParams(CategoryListParams.DEFAULT_FIELD)); + private FacetIndexingParams defaultParams; + private PerDimensionIndexingParams nonDefaultParams; + + @AfterClass + public static void afterClassDrillDownQueryTest() throws Exception { + IOUtils.close(reader, taxo, dir, taxoDir); } @BeforeClass - public static void createIndexes() throws IOException { + public static void beforeClassDrillDownQueryTest() throws Exception { dir = newDirectory(); - RandomIndexWriter writer = new RandomIndexWriter(random(), dir, - newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.KEYWORD, false))); + Random r = random(); + RandomIndexWriter writer = new RandomIndexWriter(r, dir, + newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(r, MockTokenizer.KEYWORD, false))); taxoDir = newDirectory(); TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir); @@ -86,7 +121,11 @@ public class DrillDownTest extends FacetTestCase { doc.add(new TextField("content", "bar", Field.Store.NO)); } if (i % 4 == 0) { // 25 - paths.add(new CategoryPath("a")); + if (r.nextBoolean()) { + paths.add(new CategoryPath("a/1", '/')); + } else { + paths.add(new CategoryPath("a/2", '/')); + } } if (i % 5 == 0) { // 20 paths.add(new CategoryPath("b")); @@ -105,50 +144,66 @@ public class DrillDownTest extends FacetTestCase { taxo = new DirectoryTaxonomyReader(taxoDir); } - @Test - public void testTermNonDefault() { - Term termA = DrillDown.term(nonDefaultParams, new CategoryPath("a")); - assertEquals(new Term("testing_facets_a", "a"), termA); - - Term termB = DrillDown.term(nonDefaultParams, new CategoryPath("b")); - assertEquals(new Term("testing_facets_b", "b"), termB); + public DrillDownQueryTest() { + Map paramsMap = new HashMap(); + paramsMap.put(new CategoryPath("a"), randomCategoryListParams("testing_facets_a")); + paramsMap.put(new CategoryPath("b"), randomCategoryListParams("testing_facets_b")); + nonDefaultParams = new PerDimensionIndexingParams(paramsMap); + defaultParams = new FacetIndexingParams(randomCategoryListParams(CategoryListParams.DEFAULT_FIELD)); } @Test public void testDefaultField() { String defaultField = CategoryListParams.DEFAULT_FIELD; - Term termA = DrillDown.term(defaultParams, new CategoryPath("a")); + Term termA = DrillDownQuery.term(defaultParams, new CategoryPath("a")); assertEquals(new Term(defaultField, "a"), termA); - Term termB = DrillDown.term(defaultParams, new CategoryPath("b")); + Term termB = DrillDownQuery.term(defaultParams, new CategoryPath("b")); assertEquals(new Term(defaultField, "b"), termB); } + @Test + public void testAndOrs() throws Exception { + IndexSearcher searcher = newSearcher(reader); + + // test (a/1 OR a/2) AND b + DrillDownQuery q = new DrillDownQuery(defaultParams); + q.add(new CategoryPath("a/1", '/'), new CategoryPath("a/2", '/')); + q.add(new CategoryPath("b")); + TopDocs docs = searcher.search(q, 100); + assertEquals(5, docs.totalHits); + } + @Test public void testQuery() throws IOException { IndexSearcher searcher = newSearcher(reader); // Making sure the query yields 25 documents with the facet "a" - Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); + DrillDownQuery q = new DrillDownQuery(defaultParams); + q.add(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, Occur.MUST, new CategoryPath("b")); + DrillDownQuery q2 = new DrillDownQuery(defaultParams, q); + q2.add(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, Occur.MUST, new CategoryPath("a"), new CategoryPath("b")); + DrillDownQuery q3 = new DrillDownQuery(defaultParams); + q3.add(new CategoryPath("a")); + q3.add(new CategoryPath("b")); docs = searcher.search(q3, 100); - assertEquals(5, docs.totalHits); + 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, Occur.MUST, new CategoryPath("b")); + DrillDownQuery q4 = new DrillDownQuery(defaultParams, fooQuery); + q4.add(new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -158,38 +213,25 @@ public class DrillDownTest extends FacetTestCase { IndexSearcher searcher = newSearcher(reader); // Create the base query to start with - Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); + DrillDownQuery q = new DrillDownQuery(defaultParams); + q.add(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, Occur.MUST, new CategoryPath("b")); + DrillDownQuery q2 = new DrillDownQuery(defaultParams, q); + q2.add(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, Occur.MUST, new CategoryPath("b")); + DrillDownQuery q4 = new DrillDownQuery(defaultParams, fooQuery); + q4.add(new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } - @AfterClass - public static void closeIndexes() throws IOException { - if (reader != null) { - reader.close(); - reader = null; - } - - if (taxo != null) { - taxo.close(); - taxo = null; - } - - dir.close(); - taxoDir.close(); - } - @Test public void testScoring() throws IOException { // verify that drill-down queries do not modify scores @@ -204,8 +246,9 @@ public class DrillDownTest extends FacetTestCase { } // create a drill-down query with category "a", scores should not change - q = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("a")); - docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query + DrillDownQuery q2 = new DrillDownQuery(defaultParams, q); + q2.add(new CategoryPath("a")); + docs = searcher.search(q2, 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); } @@ -216,7 +259,8 @@ public class DrillDownTest extends FacetTestCase { // verify that drill-down queries (with no base query) returns 0.0 score IndexSearcher searcher = newSearcher(reader); - Query q = DrillDown.query(defaultParams, null, Occur.MUST, new CategoryPath("a")); + DrillDownQuery q = new DrillDownQuery(defaultParams); + q.add(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); @@ -224,13 +268,36 @@ public class DrillDownTest extends FacetTestCase { } @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); - } + public void testTermNonDefault() { + Term termA = DrillDownQuery.term(nonDefaultParams, new CategoryPath("a")); + assertEquals(new Term("testing_facets_a", "a"), termA); + Term termB = DrillDownQuery.term(nonDefaultParams, new CategoryPath("b")); + assertEquals(new Term("testing_facets_b", "b"), termB); + } + + @Test + public void testClone() throws Exception { + DrillDownQuery q = new DrillDownQuery(defaultParams, new MatchAllDocsQuery()); + q.add(new CategoryPath("a")); + + DrillDownQuery clone = q.clone(); + clone.add(new CategoryPath("b")); + + assertFalse("query wasn't cloned: source=" + q + " clone=" + clone, q.toString().equals(clone.toString())); + } + + @Test(expected=IllegalStateException.class) + public void testNoBaseNorDrillDown() throws Exception { + DrillDownQuery q = new DrillDownQuery(defaultParams); + q.rewrite(reader); + } + + public void testNoDrillDown() throws Exception { + Query base = new MatchAllDocsQuery(); + DrillDownQuery q = new DrillDownQuery(defaultParams, base); + Query rewrite = q.rewrite(reader).rewrite(reader); + assertSame(base, rewrite); + } + } diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java index ac70655af01..284da50ef91 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java @@ -37,8 +37,6 @@ import org.apache.lucene.index.IndexWriterConfig; import org.apache.lucene.index.RandomIndexWriter; 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 { @@ -110,7 +108,8 @@ public class TestDemoFacets extends FacetTestCase { // Now user drills down on Publish Date/2010: fsp = new FacetSearchParams(new CountFacetRequest(new CategoryPath("Author"), 10)); - Query q2 = DrillDown.query(fsp, new MatchAllDocsQuery(), Occur.MUST, new CategoryPath("Publish Date/2010", '/')); + DrillDownQuery q2 = new DrillDownQuery(fsp.indexingParams, new MatchAllDocsQuery()); + q2.add(new CategoryPath("Publish Date/2010", '/')); c = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader); searcher.search(q2, c); results = c.getFacetResults(); diff --git a/lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java b/lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java index 71d39082a86..3e11e025a7c 100644 --- a/lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java +++ b/lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java @@ -24,14 +24,14 @@ import org.apache.lucene.document.TextField; import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.CategoryListParams.OrdinalPolicy; import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.FacetSearchParams; import org.apache.lucene.facet.params.PerDimensionIndexingParams; import org.apache.lucene.facet.params.PerDimensionOrdinalPolicy; -import org.apache.lucene.facet.params.CategoryListParams.OrdinalPolicy; import org.apache.lucene.facet.search.CategoryListIterator; import org.apache.lucene.facet.search.CountFacetRequest; -import org.apache.lucene.facet.search.DrillDown; +import org.apache.lucene.facet.search.DrillDownQuery; import org.apache.lucene.facet.search.FacetRequest; import org.apache.lucene.facet.search.FacetResult; import org.apache.lucene.facet.search.FacetResultNode; @@ -41,8 +41,6 @@ import org.apache.lucene.facet.taxonomy.TaxonomyReader; import org.apache.lucene.facet.taxonomy.TaxonomyWriter; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; -import org.apache.lucene.facet.util.FacetsPayloadMigrationReader; -import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.index.AtomicReader; import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.DirectoryReader; @@ -60,9 +58,7 @@ import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.MatchAllDocsQuery; import org.apache.lucene.search.MultiCollector; 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; @@ -289,7 +285,8 @@ public class TestFacetsPayloadMigrationReader extends FacetTestCase { 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(), Occur.MUST, drillDownCP); + DrillDownQuery drillDown = new DrillDownQuery(fip, new MatchAllDocsQuery()); + drillDown.add(drillDownCP); TotalHitCountCollector total = new TotalHitCountCollector(); FacetsCollector fc = FacetsCollector.create(fsp, indexReader, taxoReader); searcher.search(drillDown, MultiCollector.wrap(fc, total));