LUCENE-4716: Add OR support to DrillDown

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1438485 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Shai Erera 2013-01-25 13:11:08 +00:00
parent 57c343aa65
commit 735995f22e
6 changed files with 46 additions and 25 deletions

View File

@ -76,6 +76,9 @@ New Features
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

View File

@ -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.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;
@ -153,7 +153,7 @@ 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, 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

View File

@ -59,18 +59,23 @@ public final class DrillDown {
}
/**
* 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}.
* <p>
* <b>NOTE:</b> {@link Occur} only makes sense when there is more than one
* {@link CategoryPath} given.
* <p>
* <b>NOTE:</b> {@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 @@ public final class DrillDown {
} 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 @@ public final class DrillDown {
}
/**
* @see #query(FacetIndexingParams, Query, CategoryPath...)
* @see #query
*/
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);
}
}

View File

@ -58,6 +58,7 @@ 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;
@ -284,7 +285,7 @@ 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(), 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));

View File

@ -27,6 +27,7 @@ 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;
@ -129,25 +130,25 @@ public class DrillDownTest extends FacetTestCase {
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 @@ public class DrillDownTest extends FacetTestCase {
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 @@ public class DrillDownTest extends FacetTestCase {
}
// 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 @@ 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, 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);
}
}

View File

@ -40,6 +40,7 @@ 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 {
@ -111,7 +112,7 @@ 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(), 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();