Index: lucene/CHANGES.txt =================================================================== --- lucene/CHANGES.txt (revision 1416547) +++ lucene/CHANGES.txt (working copy) @@ -250,6 +250,9 @@ Users of this API can now simply obtain an instance via DocValues#getDirectSource per thread. (Simon Willnauer) +* LUCENE-4580: DrillDown.query variants return a ConstantScoreQuery with boost set to 0.0f + so that documents scores are not affected by running a drill-down query. (Shai Erera) + Documentation * LUCENE-4483: Refer to BytesRef.deepCopyOf in Term's constructor that takes BytesRef. 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 1416547) +++ lucene/facet/src/examples/org/apache/lucene/facet/example/simple/SimpleSearcher.java (working copy) @@ -157,7 +157,7 @@ CategoryPath categoryOfInterest = resIterator.next().getLabel(); // drill-down preparation: turn the base query into a drill-down query for the category of interest - Query q2 = DrillDown.query(baseQuery, categoryOfInterest); + Query q2 = DrillDown.query(new FacetSearchParams(), baseQuery, 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 1416547) +++ lucene/facet/src/java/org/apache/lucene/facet/search/DrillDown.java (working copy) @@ -2,6 +2,7 @@ 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; @@ -29,7 +30,14 @@ */ /** - * Creation of drill down term or query. + * 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 */ @@ -42,9 +50,7 @@ return term(sParams.getFacetIndexingParams(), path); } - /** - * Return a term for drilling down into a category. - */ + /** 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.charsNeededForFullPath()]; @@ -53,35 +59,47 @@ } /** - * Return a query for drilling down into all given categories (AND). + * Returns a drill-down {@link Query} over all given categories, assuming all + * are required (e.g. {@code AND}). You can construct a query with different + * modes (e.g. {@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. + * * @see #term(FacetSearchParams, CategoryPath) * @see #query(FacetSearchParams, Query, CategoryPath...) */ public static final Query query(FacetIndexingParams iParams, CategoryPath... paths) { - if (paths==null || paths.length==0) { + if (paths == null || paths.length == 0) { throw new IllegalArgumentException("Empty category path not allowed for drill down query!"); } - if (paths.length==1) { - return new TermQuery(term(iParams, paths[0])); + + 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.MUST); + } + q = bq; } - BooleanQuery res = new BooleanQuery(); - for (CategoryPath cp : paths) { - res.add(new TermQuery(term(iParams, cp)), Occur.MUST); - } + + final ConstantScoreQuery res = new ConstantScoreQuery(q); + res.setBoost(0.0f); return res; } /** - * Return a query for drilling down into all given categories (AND). - * @see #term(FacetSearchParams, CategoryPath) - * @see #query(FacetSearchParams, Query, CategoryPath...) + * @see #query(FacetIndexingParams, Query, CategoryPath...) */ public static final Query query(FacetSearchParams sParams, CategoryPath... paths) { return query(sParams.getFacetIndexingParams(), paths); } /** - * Turn a base query into a drilling-down query for all given category paths (AND). + * Wrap a given {@link Query} as a drill-down query over the given categories. + * * @see #query(FacetIndexingParams, CategoryPath...) */ public static final Query query(FacetIndexingParams iParams, Query baseQuery, CategoryPath... paths) { @@ -92,19 +110,10 @@ } /** - * Turn a base query into a drilling-down query for all given category paths (AND). - * @see #query(FacetSearchParams, CategoryPath...) + * @see #query(FacetIndexingParams, Query, CategoryPath...) */ public static final Query query(FacetSearchParams sParams, Query baseQuery, CategoryPath... paths) { return query(sParams.getFacetIndexingParams(), baseQuery, paths); } - /** - * Turn a base query into a drilling-down query using the default {@link FacetSearchParams} - * @see #query(FacetSearchParams, Query, CategoryPath...) - */ - public static final Query query(Query baseQuery, CategoryPath... paths) { - return query(new FacetSearchParams(), baseQuery, paths); - } - } Index: lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java =================================================================== --- lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (revision 1416547) +++ lucene/facet/src/test/org/apache/lucene/facet/search/DrillDownTest.java (working copy) @@ -8,28 +8,28 @@ import org.apache.lucene.document.Document; import org.apache.lucene.document.Field; import org.apache.lucene.document.TextField; +import org.apache.lucene.facet.index.CategoryDocumentBuilder; +import org.apache.lucene.facet.index.params.CategoryListParams; +import org.apache.lucene.facet.index.params.PerDimensionIndexingParams; +import org.apache.lucene.facet.search.params.FacetSearchParams; +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.store.Directory; +import org.apache.lucene.util.LuceneTestCase; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.facet.index.CategoryDocumentBuilder; -import org.apache.lucene.facet.index.params.CategoryListParams; -import org.apache.lucene.facet.index.params.PerDimensionIndexingParams; -import org.apache.lucene.facet.search.params.FacetSearchParams; -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; - /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -160,14 +160,14 @@ // 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(q, new CategoryPath("b")); + Query q2 = DrillDown.query(defaultParams, q, 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(fooQuery, new CategoryPath("b")); + Query q4 = DrillDown.query(defaultParams, fooQuery, new CategoryPath("b")); docs = searcher.search(q4, 100); assertEquals(10, docs.totalHits); } @@ -187,5 +187,38 @@ 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, 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, 0.00000000000); + } + } + + @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, new CategoryPath("a")); + TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query + for (ScoreDoc sd : docs.scoreDocs) { + assertEquals(0.0f, sd.score, 0.00000000000); + } + } + }