Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1444481) +++ lucene/CHANGES.txt (working copy) @@ -66,6 +66,9 @@ * 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 Index: lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java =================================================================== --- lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java (revision 1444481) +++ lucene/demo/src/java/org/apache/lucene/demo/facet/simple/SimpleSearcher.java (working copy) @@ -7,7 +7,7 @@ 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.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 @@ 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 Index: lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java (revision 1444481) +++ lucene/facet/src/java/org/apache/lucene/facet/sampling/TakmiSampleFixer.java (working copy) @@ -10,7 +10,7 @@ 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 @@ } 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, Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java (revision 1444481) +++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java (working copy) @@ -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); - } - -} Index: lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java =================================================================== --- lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java (revision 0) +++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java (working copy) @@ -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); + } + +} Property changes on: lucene/facet/src/java/org/apache/lucene/facet/search/DrillDownQuery.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java (revision 1444481) +++ lucene/facet/src/test/org/apache/lucene/facet/params/FacetIndexingParamsTest.java (working copy) @@ -3,7 +3,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.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 @@ + 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); Index: lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java (revision 1444481) +++ lucene/facet/src/test/org/apache/lucene/facet/params/PerDimensionIndexingParamsTest.java (working copy) @@ -6,7 +6,7 @@ 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 @@ 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); Index: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java (revision 0) +++ lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java (working copy) @@ -0,0 +1,303 @@ +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; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field; +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.FacetIndexingParams; +import org.apache.lucene.facet.params.PerDimensionIndexingParams; +import org.apache.lucene.facet.taxonomy.CategoryPath; +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.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.store.Directory; +import org.apache.lucene.util.IOUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/* + * 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. + */ + +public class DrillDownQueryTest extends FacetTestCase { + + private static IndexReader reader; + private static DirectoryTaxonomyReader taxo; + private static Directory dir; + private static Directory taxoDir; + + private FacetIndexingParams defaultParams; + private PerDimensionIndexingParams nonDefaultParams; + + @AfterClass + public static void afterClassDrillDownQueryTest() throws Exception { + IOUtils.close(reader, taxo, dir, taxoDir); + } + + @BeforeClass + public static void beforeClassDrillDownQueryTest() throws Exception { + dir = newDirectory(); + 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); + + for (int i = 0; i < 100; i++) { + ArrayList paths = new ArrayList(); + Document doc = new Document(); + if (i % 2 == 0) { // 50 + doc.add(new TextField("content", "foo", Field.Store.NO)); + } + if (i % 3 == 0) { // 33 + doc.add(new TextField("content", "bar", Field.Store.NO)); + } + if (i % 4 == 0) { // 25 + 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")); + } + FacetFields facetFields = new FacetFields(taxoWriter); + if (paths.size() > 0) { + facetFields.addFields(doc, paths); + } + writer.addDocument(doc); + } + + taxoWriter.close(); + reader = writer.getReader(); + writer.close(); + + taxo = new DirectoryTaxonomyReader(taxoDir); + } + + 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 = DrillDownQuery.term(defaultParams, new CategoryPath("a")); + assertEquals(new Term(defaultField, "a"), termA); + + 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" + 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 + 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 + DrillDownQuery q3 = new DrillDownQuery(defaultParams); + q3.add(new CategoryPath("a")); + q3.add(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")); + DrillDownQuery q4 = new DrillDownQuery(defaultParams, fooQuery); + q4.add(new CategoryPath("b")); + docs = searcher.search(q4, 100); + assertEquals(10, docs.totalHits); + } + + @Test + public void testQueryImplicitDefaultParams() throws IOException { + IndexSearcher searcher = newSearcher(reader); + + // Create the base query to start with + 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 + 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")); + DrillDownQuery q4 = new DrillDownQuery(defaultParams, fooQuery); + q4.add(new CategoryPath("b")); + docs = searcher.search(q4, 100); + assertEquals(10, docs.totalHits); + } + + @Test + public void testScoring() throws IOException { + // verify that drill-down queries do not modify scores + IndexSearcher searcher = newSearcher(reader); + + float[] scores = new float[reader.maxDoc()]; + + Query q = new TermQuery(new Term("content", "foo")); + TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query + for (ScoreDoc sd : docs.scoreDocs) { + scores[sd.doc] = sd.score; + } + + // create a drill-down query with category "a", scores should not change + 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); + } + } + + @Test + public void testScoringNoBaseQuery() throws IOException { + // verify that drill-down queries (with no base query) returns 0.0 score + IndexSearcher searcher = newSearcher(reader); + + 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); + } + } + + @Test + 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); + } + +} Property changes on: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownQueryTest.java ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (revision 1444481) +++ lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (working copy) @@ -1,236 +0,0 @@ -package org.apache.lucene.facet.search; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import org.apache.lucene.analysis.MockAnalyzer; -import org.apache.lucene.analysis.MockTokenizer; -import org.apache.lucene.document.Document; -import org.apache.lucene.document.Field; -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.FacetIndexingParams; -import org.apache.lucene.facet.params.PerDimensionIndexingParams; -import org.apache.lucene.facet.taxonomy.CategoryPath; -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.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.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.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -/* - * 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. - */ - -public class DrillDownTest 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)); - } - - @BeforeClass - public static void createIndexes() throws IOException { - dir = newDirectory(); - RandomIndexWriter writer = new RandomIndexWriter(random(), dir, - newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.KEYWORD, false))); - - taxoDir = newDirectory(); - TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir); - - for (int i = 0; i < 100; i++) { - ArrayList paths = new ArrayList(); - Document doc = new Document(); - if (i % 2 == 0) { // 50 - doc.add(new TextField("content", "foo", Field.Store.NO)); - } - if (i % 3 == 0) { // 33 - doc.add(new TextField("content", "bar", Field.Store.NO)); - } - if (i % 4 == 0) { // 25 - paths.add(new CategoryPath("a")); - } - if (i % 5 == 0) { // 20 - paths.add(new CategoryPath("b")); - } - FacetFields facetFields = new FacetFields(taxoWriter); - if (paths.size() > 0) { - facetFields.addFields(doc, paths); - } - writer.addDocument(doc); - } - - taxoWriter.close(); - reader = writer.getReader(); - writer.close(); - - 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); - } - - @Test - public void testDefaultField() { - String defaultField = CategoryListParams.DEFAULT_FIELD; - - Term termA = DrillDown.term(defaultParams, new CategoryPath("a")); - assertEquals(new Term(defaultField, "a"), termA); - - Term termB = DrillDown.term(defaultParams, new CategoryPath("b")); - assertEquals(new Term(defaultField, "b"), termB); - } - - @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")); - 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")); - 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")); - 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, Occur.MUST, new CategoryPath("b")); - docs = searcher.search(q4, 100); - assertEquals(10, docs.totalHits); - } - - @Test - public void testQueryImplicitDefaultParams() throws IOException { - IndexSearcher searcher = newSearcher(reader); - - // Create the base query to start with - 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, 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, Occur.MUST, 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 - IndexSearcher searcher = newSearcher(reader); - - float[] scores = new float[reader.maxDoc()]; - - Query q = new TermQuery(new Term("content", "foo")); - TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query - for (ScoreDoc sd : docs.scoreDocs) { - scores[sd.doc] = sd.score; - } - - // 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 - for (ScoreDoc sd : docs.scoreDocs) { - assertEquals("score of doc=" + sd.doc + " modified", scores[sd.doc], sd.score, 0f); - } - } - - @Test - public void testScoringNoBaseQuery() throws IOException { - // 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")); - 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 1444481) +++ lucene/facet/src/test/org/apache/lucene/facet/search/TestDemoFacets.java (working copy) @@ -37,8 +37,6 @@ 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 @@ // 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(); Index: lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java (revision 1444481) +++ lucene/facet/src/test/org/apache/lucene/facet/util/TestFacetsPayloadMigrationReader.java (working copy) @@ -24,14 +24,14 @@ 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.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.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 @@ 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));