LUCENE-4750: convert DrillDown to DrillDownQuery

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1444482 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Shai Erera 2013-02-10 06:57:22 +00:00
parent 91b1e22789
commit 456b429d10
11 changed files with 294 additions and 192 deletions

View File

@ -66,6 +66,9 @@ Changes in backwards compatibility policy
* LUCENE-4761: Facet packages reorganized. Should be easy to fix your import * LUCENE-4761: Facet packages reorganized. Should be easy to fix your import
statements, if you use an IDE such as Eclipse. (Shai Erera) 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 Optimizations
* LUCENE-4687: BloomFilterPostingsFormat now lazily initializes delegate * LUCENE-4687: BloomFilterPostingsFormat now lazily initializes delegate

View File

@ -7,7 +7,7 @@ import org.apache.lucene.demo.facet.ExampleUtils;
import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.FacetIndexingParams;
import org.apache.lucene.facet.params.FacetSearchParams; import org.apache.lucene.facet.params.FacetSearchParams;
import org.apache.lucene.facet.search.CountFacetRequest; 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.FacetRequest;
import org.apache.lucene.facet.search.FacetResult; import org.apache.lucene.facet.search.FacetResult;
import org.apache.lucene.facet.search.FacetResultNode; 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.facet.taxonomy.TaxonomyReader;
import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.MultiCollector;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
@ -156,7 +155,8 @@ public class SimpleSearcher {
CategoryPath categoryOfInterest = resIterator.next().label; CategoryPath categoryOfInterest = resIterator.next().label;
// drill-down preparation: turn the base query into a drill-down query for the category of interest // 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! // that's it - search with the new query and we're done!
// only documents both matching the base query AND containing the // only documents both matching the base query AND containing the

View File

@ -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. We now show the required code lines for implementing such a drill-down.
<pre class="prettyprint lang-java linenums"> <pre class="prettyprint lang-java linenums">
Query baseQuery = queryParser.parse("tennis racquet"); 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));
</pre> </pre>
<p> <p>
In line 1 the original user query is created and then used to obtain information on In line 1 the original user query is created and then used to obtain information on

View File

@ -10,7 +10,7 @@ import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.Bits; import org.apache.lucene.util.Bits;
import org.apache.lucene.facet.params.FacetSearchParams; 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.FacetResult;
import org.apache.lucene.facet.search.FacetResultNode; import org.apache.lucene.facet.search.FacetResultNode;
import org.apache.lucene.facet.search.ScoredDocIDs; import org.apache.lucene.facet.search.ScoredDocIDs;
@ -105,7 +105,7 @@ class TakmiSampleFixer implements SampleFixer {
} }
CategoryPath catPath = fresNode.label; CategoryPath catPath = fresNode.label;
Term drillDownTerm = DrillDown.term(searchParams, catPath); Term drillDownTerm = DrillDownQuery.term(searchParams.indexingParams, catPath);
// TODO (Facet): avoid Multi*? // TODO (Facet): avoid Multi*?
Bits liveDocs = MultiFields.getLiveDocs(indexReader); Bits liveDocs = MultiFields.getLiveDocs(indexReader);
int updatedCount = countIntersection(MultiFields.getTermDocsEnum(indexReader, liveDocs, int updatedCount = countIntersection(MultiFields.getTermDocsEnum(indexReader, liveDocs,

View File

@ -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.
* <p>
* <b>NOTE:</b> 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}.
* <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, 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);
}
}

View File

@ -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}.
* <p>
* <b>NOTE:</b> 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<String> drillDownDims = new HashSet<String>();
private final FacetIndexingParams fip;
/* Used by clone() */
private DrillDownQuery(FacetIndexingParams fip, BooleanQuery query, Set<String> 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);
}
}

View File

@ -3,7 +3,7 @@ package org.apache.lucene.facet.params;
import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestCase;
import org.apache.lucene.facet.params.CategoryListParams; import org.apache.lucene.facet.params.CategoryListParams;
import org.apache.lucene.facet.params.FacetIndexingParams; 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.taxonomy.CategoryPath;
import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.facet.util.PartitionsUtils;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
@ -39,7 +39,7 @@ public class FacetIndexingParamsTest extends FacetTestCase {
+ dfip.getFacetDelimChar() + "b"; + dfip.getFacetDelimChar() + "b";
CategoryPath cp = new CategoryPath("a", "b"); CategoryPath cp = new CategoryPath("a", "b");
assertEquals("wrong drill-down term", new Term("$facets", assertEquals("wrong drill-down term", new Term("$facets",
expectedDDText), DrillDown.term(dfip,cp)); expectedDDText), DrillDownQuery.term(dfip,cp));
char[] buf = new char[20]; char[] buf = new char[20];
int numchars = dfip.drillDownTermText(cp, buf); int numchars = dfip.drillDownTermText(cp, buf);
assertEquals("3 characters should be written", 3, numchars); assertEquals("3 characters should be written", 3, numchars);

View File

@ -6,7 +6,7 @@ import org.apache.lucene.facet.FacetTestCase;
import org.apache.lucene.facet.params.CategoryListParams; import org.apache.lucene.facet.params.CategoryListParams;
import org.apache.lucene.facet.params.FacetIndexingParams; import org.apache.lucene.facet.params.FacetIndexingParams;
import org.apache.lucene.facet.params.PerDimensionIndexingParams; 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.taxonomy.CategoryPath;
import org.apache.lucene.facet.util.PartitionsUtils; import org.apache.lucene.facet.util.PartitionsUtils;
import org.apache.lucene.index.Term; 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); assertEquals("Expected default category list field is $facets", "$facets", ifip.getCategoryListParams(null).field);
String expectedDDText = "a" + ifip.getFacetDelimChar() + "b"; String expectedDDText = "a" + ifip.getFacetDelimChar() + "b";
CategoryPath cp = new CategoryPath("a", "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]; char[] buf = new char[20];
int numchars = ifip.drillDownTermText(cp, buf); int numchars = ifip.drillDownTermText(cp, buf);
assertEquals("3 characters should be written", 3, numchars); assertEquals("3 characters should be written", 3, numchars);

View File

@ -1,9 +1,44 @@
package org.apache.lucene.facet.search; 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.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import org.apache.lucene.analysis.MockAnalyzer; import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.analysis.MockTokenizer; 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.RandomIndexWriter;
import org.apache.lucene.index.Term; import org.apache.lucene.index.Term;
import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.Query; import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc; import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery; import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs; import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.store.Directory; import org.apache.lucene.store.Directory;
import org.apache.lucene.util.IOUtils;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -50,28 +86,27 @@ import org.junit.Test;
* limitations under the License. * 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 IndexReader reader;
private static DirectoryTaxonomyReader taxo; private static DirectoryTaxonomyReader taxo;
private static Directory dir; private static Directory dir;
private static Directory taxoDir; private static Directory taxoDir;
public DrillDownTest() { private FacetIndexingParams defaultParams;
Map<CategoryPath,CategoryListParams> paramsMap = new HashMap<CategoryPath,CategoryListParams>(); private PerDimensionIndexingParams nonDefaultParams;
paramsMap.put(new CategoryPath("a"), randomCategoryListParams("testing_facets_a"));
paramsMap.put(new CategoryPath("b"), randomCategoryListParams("testing_facets_b")); @AfterClass
nonDefaultParams = new PerDimensionIndexingParams(paramsMap); public static void afterClassDrillDownQueryTest() throws Exception {
defaultParams = new FacetIndexingParams(randomCategoryListParams(CategoryListParams.DEFAULT_FIELD)); IOUtils.close(reader, taxo, dir, taxoDir);
} }
@BeforeClass @BeforeClass
public static void createIndexes() throws IOException { public static void beforeClassDrillDownQueryTest() throws Exception {
dir = newDirectory(); dir = newDirectory();
RandomIndexWriter writer = new RandomIndexWriter(random(), dir, Random r = random();
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random(), MockTokenizer.KEYWORD, false))); RandomIndexWriter writer = new RandomIndexWriter(r, dir,
newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(r, MockTokenizer.KEYWORD, false)));
taxoDir = newDirectory(); taxoDir = newDirectory();
TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir); TaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir);
@ -86,7 +121,11 @@ public class DrillDownTest extends FacetTestCase {
doc.add(new TextField("content", "bar", Field.Store.NO)); doc.add(new TextField("content", "bar", Field.Store.NO));
} }
if (i % 4 == 0) { // 25 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 if (i % 5 == 0) { // 20
paths.add(new CategoryPath("b")); paths.add(new CategoryPath("b"));
@ -105,50 +144,66 @@ public class DrillDownTest extends FacetTestCase {
taxo = new DirectoryTaxonomyReader(taxoDir); taxo = new DirectoryTaxonomyReader(taxoDir);
} }
@Test public DrillDownQueryTest() {
public void testTermNonDefault() { Map<CategoryPath,CategoryListParams> paramsMap = new HashMap<CategoryPath,CategoryListParams>();
Term termA = DrillDown.term(nonDefaultParams, new CategoryPath("a")); paramsMap.put(new CategoryPath("a"), randomCategoryListParams("testing_facets_a"));
assertEquals(new Term("testing_facets_a", "a"), termA); paramsMap.put(new CategoryPath("b"), randomCategoryListParams("testing_facets_b"));
nonDefaultParams = new PerDimensionIndexingParams(paramsMap);
Term termB = DrillDown.term(nonDefaultParams, new CategoryPath("b")); defaultParams = new FacetIndexingParams(randomCategoryListParams(CategoryListParams.DEFAULT_FIELD));
assertEquals(new Term("testing_facets_b", "b"), termB);
} }
@Test @Test
public void testDefaultField() { public void testDefaultField() {
String defaultField = CategoryListParams.DEFAULT_FIELD; 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); 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); 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 @Test
public void testQuery() throws IOException { public void testQuery() throws IOException {
IndexSearcher searcher = newSearcher(reader); IndexSearcher searcher = newSearcher(reader);
// Making sure the query yields 25 documents with the facet "a" // 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); TopDocs docs = searcher.search(q, 100);
assertEquals(25, docs.totalHits); assertEquals(25, docs.totalHits);
// Making sure the query yields 5 documents with the facet "b" and the // Making sure the query yields 5 documents with the facet "b" and the
// previous (facet "a") query as a base query // 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); docs = searcher.search(q2, 100);
assertEquals(5, docs.totalHits); assertEquals(5, docs.totalHits);
// Making sure that a query of both facet "a" and facet "b" yields 5 results // 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); 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%) // Check that content:foo (which yields 50% results) and facet/b (which yields 20%)
// would gather together 10 results (10%..) // would gather together 10 results (10%..)
Query fooQuery = new TermQuery(new Term("content", "foo")); 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); docs = searcher.search(q4, 100);
assertEquals(10, docs.totalHits); assertEquals(10, docs.totalHits);
} }
@ -158,38 +213,25 @@ public class DrillDownTest extends FacetTestCase {
IndexSearcher searcher = newSearcher(reader); IndexSearcher searcher = newSearcher(reader);
// Create the base query to start with // 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 // Making sure the query yields 5 documents with the facet "b" and the
// previous (facet "a") query as a base query // 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); TopDocs docs = searcher.search(q2, 100);
assertEquals(5, docs.totalHits); assertEquals(5, docs.totalHits);
// Check that content:foo (which yields 50% results) and facet/b (which yields 20%) // Check that content:foo (which yields 50% results) and facet/b (which yields 20%)
// would gather together 10 results (10%..) // would gather together 10 results (10%..)
Query fooQuery = new TermQuery(new Term("content", "foo")); 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); docs = searcher.search(q4, 100);
assertEquals(10, docs.totalHits); 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 @Test
public void testScoring() throws IOException { public void testScoring() throws IOException {
// verify that drill-down queries do not modify scores // 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 // create a drill-down query with category "a", scores should not change
q = DrillDown.query(defaultParams, q, Occur.MUST, new CategoryPath("a")); DrillDownQuery q2 = new DrillDownQuery(defaultParams, q);
docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query q2.add(new CategoryPath("a"));
docs = searcher.search(q2, reader.maxDoc()); // fetch all available docs to this query
for (ScoreDoc sd : docs.scoreDocs) { for (ScoreDoc sd : docs.scoreDocs) {
assertEquals("score of doc=" + sd.doc + " modified", scores[sd.doc], sd.score, 0f); 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 // verify that drill-down queries (with no base query) returns 0.0 score
IndexSearcher searcher = newSearcher(reader); 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 TopDocs docs = searcher.search(q, reader.maxDoc()); // fetch all available docs to this query
for (ScoreDoc sd : docs.scoreDocs) { for (ScoreDoc sd : docs.scoreDocs) {
assertEquals(0f, sd.score, 0f); assertEquals(0f, sd.score, 0f);
@ -224,13 +268,36 @@ public class DrillDownTest extends FacetTestCase {
} }
@Test @Test
public void testOrQuery() throws Exception { public void testTermNonDefault() {
IndexSearcher searcher = newSearcher(reader); Term termA = DrillDownQuery.term(nonDefaultParams, new CategoryPath("a"));
assertEquals(new Term("testing_facets_a", "a"), termA);
// 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);
}
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);
}
} }

View File

@ -37,8 +37,6 @@ import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.RandomIndexWriter; import org.apache.lucene.index.RandomIndexWriter;
import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery; 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; import org.apache.lucene.store.Directory;
public class TestDemoFacets extends FacetTestCase { public class TestDemoFacets extends FacetTestCase {
@ -110,7 +108,8 @@ public class TestDemoFacets extends FacetTestCase {
// Now user drills down on Publish Date/2010: // Now user drills down on Publish Date/2010:
fsp = new FacetSearchParams(new CountFacetRequest(new CategoryPath("Author"), 10)); 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); c = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader);
searcher.search(q2, c); searcher.search(q2, c);
results = c.getFacetResults(); results = c.getFacetResults();

View File

@ -24,14 +24,14 @@ import org.apache.lucene.document.TextField;
import org.apache.lucene.facet.FacetTestCase; import org.apache.lucene.facet.FacetTestCase;
import org.apache.lucene.facet.index.FacetFields; import org.apache.lucene.facet.index.FacetFields;
import org.apache.lucene.facet.params.CategoryListParams; 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.FacetIndexingParams;
import org.apache.lucene.facet.params.FacetSearchParams; import org.apache.lucene.facet.params.FacetSearchParams;
import org.apache.lucene.facet.params.PerDimensionIndexingParams; import org.apache.lucene.facet.params.PerDimensionIndexingParams;
import org.apache.lucene.facet.params.PerDimensionOrdinalPolicy; 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.CategoryListIterator;
import org.apache.lucene.facet.search.CountFacetRequest; 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.FacetRequest;
import org.apache.lucene.facet.search.FacetResult; import org.apache.lucene.facet.search.FacetResult;
import org.apache.lucene.facet.search.FacetResultNode; 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.TaxonomyWriter;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader;
import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; 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.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DirectoryReader; 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.MatchAllDocsQuery;
import org.apache.lucene.search.MultiCollector; import org.apache.lucene.search.MultiCollector;
import org.apache.lucene.search.PrefixQuery; import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TotalHitCountCollector; import org.apache.lucene.search.TotalHitCountCollector;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.store.Directory; import org.apache.lucene.store.Directory;
import org.apache.lucene.util.BytesRef; import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IOUtils; import org.apache.lucene.util.IOUtils;
@ -289,7 +285,8 @@ public class TestFacetsPayloadMigrationReader extends FacetTestCase {
for (String dim : expectedCounts.keySet()) { for (String dim : expectedCounts.keySet()) {
CategoryPath drillDownCP = new CategoryPath(dim); CategoryPath drillDownCP = new CategoryPath(dim);
FacetSearchParams fsp = new FacetSearchParams(fip, new CountFacetRequest(drillDownCP, 10)); 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(); TotalHitCountCollector total = new TotalHitCountCollector();
FacetsCollector fc = FacetsCollector.create(fsp, indexReader, taxoReader); FacetsCollector fc = FacetsCollector.create(fsp, indexReader, taxoReader);
searcher.search(drillDown, MultiCollector.wrap(fc, total)); searcher.search(drillDown, MultiCollector.wrap(fc, total));