From 835296f20a17c12c66b4f043074c94e3ddd5c2b5 Mon Sep 17 00:00:00 2001 From: Mike McCandless Date: Fri, 16 Dec 2016 09:56:51 -0500 Subject: [PATCH] LUCENE-7587: add helper FacetQuery and MultiFacetQuery classes to simplify drill down implementation --- lucene/CHANGES.txt | 4 + .../org/apache/lucene/facet/FacetQuery.java | 52 ++++++++++ .../apache/lucene/facet/MultiFacetQuery.java | 61 ++++++++++++ .../apache/lucene/facet/TestFacetQuery.java | 98 +++++++++++++++++++ 4 files changed, 215 insertions(+) create mode 100644 lucene/facet/src/java/org/apache/lucene/facet/FacetQuery.java create mode 100644 lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java create mode 100644 lucene/facet/src/test/org/apache/lucene/facet/TestFacetQuery.java diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 7e614693fd5..47cd6e8a22f 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -74,6 +74,10 @@ New features * LUCENE-7590: Added DocValuesStatsCollector to compute statistics on DocValues fields. (Shai Erera) +* LUCENE-7587: The new FacetQuery and MultiFacetQuery helper classes + make it simpler to execute drill down when drill sideways counts are + not needed (Emmanuel Keller via Mike McCandless) + Bug Fixes * LUCENE-7547: JapaneseTokenizerFactory was failing to close the diff --git a/lucene/facet/src/java/org/apache/lucene/facet/FacetQuery.java b/lucene/facet/src/java/org/apache/lucene/facet/FacetQuery.java new file mode 100644 index 00000000000..ec20292c13a --- /dev/null +++ b/lucene/facet/src/java/org/apache/lucene/facet/FacetQuery.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package org.apache.lucene.facet; + +import org.apache.lucene.index.Term; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; + +/** + * A term {@link Query} over a {@link FacetField}. + *

+ * NOTE:This helper class is an alternative to {@link DrillDownQuery} + * especially in cases where you don't intend to use {@link DrillSideways} + * + * @lucene.experimental + */ +public class FacetQuery extends TermQuery { + + /** + * Creates a new {@code FacetQuery} filtering the query on the given dimension. + */ + public FacetQuery(final FacetsConfig facetsConfig, final String dimension, final String... path) { + super(toTerm(facetsConfig.getDimConfig(dimension), dimension, path)); + } + + /** + * Creates a new {@code FacetQuery} filtering the query on the given dimension. + *

+ * NOTE:Uses FacetsConfig.DEFAULT_DIM_CONFIG. + */ + public FacetQuery(final String dimension, final String... path) { + super(toTerm(FacetsConfig.DEFAULT_DIM_CONFIG, dimension, path)); + } + + static Term toTerm(final FacetsConfig.DimConfig dimConfig, final String dimension, final String... path) { + return new Term(dimConfig.indexFieldName, FacetsConfig.pathToString(dimension, path)); + } +} diff --git a/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java b/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java new file mode 100644 index 00000000000..dd212c68c9c --- /dev/null +++ b/lucene/facet/src/java/org/apache/lucene/facet/MultiFacetQuery.java @@ -0,0 +1,61 @@ +/* + * 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. + */ +package org.apache.lucene.facet; + +import org.apache.lucene.index.Term; +import org.apache.lucene.queries.TermsQuery; +import org.apache.lucene.search.Query; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * A multi-terms {@link Query} over a {@link FacetField}. + *

+ * NOTE:This helper class is an alternative to {@link DrillDownQuery} + * especially in cases where you don't intend to use {@link DrillSideways} + * + * @lucene.experimental + * @see org.apache.lucene.queries.TermsQuery + */ +public class MultiFacetQuery extends TermsQuery { + + /** + * Creates a new {@code MultiFacetQuery} filtering the query on the given dimension. + */ + public MultiFacetQuery(final FacetsConfig facetsConfig, final String dimension, final String[]... paths) { + super(toTerms(facetsConfig.getDimConfig(dimension), dimension, paths)); + } + + /** + * Creates a new {@code MultiFacetQuery} filtering the query on the given dimension. + *

+ * NOTE:Uses FacetsConfig.DEFAULT_DIM_CONFIG. + */ + public MultiFacetQuery(final String dimension, final String[]... paths) { + super(toTerms(FacetsConfig.DEFAULT_DIM_CONFIG, dimension, paths)); + } + + static Collection toTerms(final FacetsConfig.DimConfig dimConfig, final String dimension, + final String[]... paths) { + final Collection terms = new ArrayList<>(paths.length); + for (String[] path : paths) + terms.add(FacetQuery.toTerm(dimConfig, dimension, path)); + return terms; + } + +} diff --git a/lucene/facet/src/test/org/apache/lucene/facet/TestFacetQuery.java b/lucene/facet/src/test/org/apache/lucene/facet/TestFacetQuery.java new file mode 100644 index 00000000000..f3aa0793d77 --- /dev/null +++ b/lucene/facet/src/test/org/apache/lucene/facet/TestFacetQuery.java @@ -0,0 +1,98 @@ +/* + * 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. + */ +package org.apache.lucene.facet; + +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.sortedset.SortedSetDocValuesFacetField; +import org.apache.lucene.index.IndexReader; +import org.apache.lucene.index.IndexableField; +import org.apache.lucene.index.RandomIndexWriter; +import org.apache.lucene.search.IndexSearcher; +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; + +import java.io.IOException; + +public class TestFacetQuery extends FacetTestCase { + + private static Directory indexDirectory; + private static RandomIndexWriter indexWriter; + private static IndexReader indexReader; + private static IndexSearcher searcher; + private static FacetsConfig config; + + private static final IndexableField[] DOC_SINGLEVALUED = + new IndexableField[] { new SortedSetDocValuesFacetField("Author", "Mark Twain") }; + + private static final IndexableField[] DOC_MULTIVALUED = + new SortedSetDocValuesFacetField[] { new SortedSetDocValuesFacetField("Author", "Kurt Vonnegut") }; + + private static final IndexableField[] DOC_NOFACET = + new IndexableField[] { new TextField("Hello", "World", Field.Store.YES) }; + + @BeforeClass + public static void createTestIndex() throws IOException { + indexDirectory = newDirectory(); + // create and open an index writer + indexWriter = new RandomIndexWriter(random(), indexDirectory, + newIndexWriterConfig(new MockAnalyzer(random(), MockTokenizer.WHITESPACE, false))); + + config = new FacetsConfig(); + + indexDocuments(DOC_SINGLEVALUED, DOC_MULTIVALUED, DOC_NOFACET); + + indexReader = indexWriter.getReader(); + // prepare searcher to search against + searcher = newSearcher(indexReader); + } + + private static void indexDocuments(IndexableField[]... docs) throws IOException { + for (IndexableField[] fields : docs) { + for (IndexableField field : fields) { + Document doc = new Document(); + doc.add(field); + indexWriter.addDocument(config.build(doc)); + } + } + } + + @AfterClass + public static void closeTestIndex() throws IOException { + IOUtils.close(indexReader, indexWriter, indexDirectory); + } + + @Test + public void testSingleValued() throws Exception { + TopDocs topDocs = searcher.search(new FacetQuery("Author", "Mark Twain"), 10); + assertEquals(1, topDocs.totalHits); + } + + @Test + public void testMultiValued() throws Exception { + TopDocs topDocs = searcher.search( + new MultiFacetQuery("Author", new String[] { "Mark Twain" }, new String[] { "Kurt Vonnegut" }), 10); + assertEquals(2, topDocs.totalHits); + } +}