mirror of https://github.com/apache/lucene.git
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:
parent
57c343aa65
commit
735995f22e
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in New Issue