diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt index 09cbd22752a..d3ceb7df760 100644 --- a/lucene/CHANGES.txt +++ b/lucene/CHANGES.txt @@ -135,6 +135,10 @@ New Features numeric DocValues fields of documents, without re-indexing them. (Shai Erera, Mike McCandless, Robert Muir) +* LUCENE-5298: Add SumValueSourceFacetRequest for aggregating facets by + a ValueSource, such as a NumericDocValuesField or an expression. + (Shai Erera) + Bug Fixes * LUCENE-4998: Fixed a few places to pass IOContext.READONCE instead diff --git a/lucene/demo/build.xml b/lucene/demo/build.xml index 074601aac24..0550660859a 100644 --- a/lucene/demo/build.xml +++ b/lucene/demo/build.xml @@ -33,22 +33,27 @@ + + + - + + + - + diff --git a/lucene/demo/src/java/org/apache/lucene/demo/facet/ExpressionAggregationFacetsExample.java b/lucene/demo/src/java/org/apache/lucene/demo/facet/ExpressionAggregationFacetsExample.java new file mode 100644 index 00000000000..7588561c5cc --- /dev/null +++ b/lucene/demo/src/java/org/apache/lucene/demo/facet/ExpressionAggregationFacetsExample.java @@ -0,0 +1,136 @@ +package org.apache.lucene.demo.facet; + +import java.io.IOException; +import java.text.ParseException; +import java.util.Collections; +import java.util.List; + +import org.apache.lucene.analysis.core.WhitespaceAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.Field.Store; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.document.TextField; +import org.apache.lucene.expressions.Expression; +import org.apache.lucene.expressions.SimpleBindings; +import org.apache.lucene.expressions.js.JavascriptCompiler; +import org.apache.lucene.facet.index.FacetFields; +import org.apache.lucene.facet.params.FacetSearchParams; +import org.apache.lucene.facet.search.FacetResult; +import org.apache.lucene.facet.search.FacetsCollector; +import org.apache.lucene.facet.search.SumValueSourceFacetRequest; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.facet.taxonomy.TaxonomyReader; +import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyReader; +import org.apache.lucene.facet.taxonomy.directory.DirectoryTaxonomyWriter; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.index.IndexWriterConfig; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.SortField; +import org.apache.lucene.store.Directory; +import org.apache.lucene.store.RAMDirectory; + +/* + * 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. + */ + +/** Shows facets aggregation by an expression. */ +public class ExpressionAggregationFacetsExample { + + private final Directory indexDir = new RAMDirectory(); + private final Directory taxoDir = new RAMDirectory(); + + /** Empty constructor */ + public ExpressionAggregationFacetsExample() {} + + private void add(IndexWriter indexWriter, FacetFields facetFields, String text, String category, long popularity) throws IOException { + Document doc = new Document(); + doc.add(new TextField("c", text, Store.NO)); + doc.add(new NumericDocValuesField("popularity", popularity)); + facetFields.addFields(doc, Collections.singletonList(new CategoryPath(category, '/'))); + indexWriter.addDocument(doc); + } + + /** Build the example index. */ + private void index() throws IOException { + IndexWriter indexWriter = new IndexWriter(indexDir, new IndexWriterConfig(FacetExamples.EXAMPLES_VER, + new WhitespaceAnalyzer(FacetExamples.EXAMPLES_VER))); + + // Writes facet ords to a separate directory from the main index + DirectoryTaxonomyWriter taxoWriter = new DirectoryTaxonomyWriter(taxoDir); + + // Reused across documents, to add the necessary facet fields + FacetFields facetFields = new FacetFields(taxoWriter); + + add(indexWriter, facetFields, "foo bar", "A/B", 5L); + add(indexWriter, facetFields, "foo foo bar", "A/C", 3L); + + indexWriter.close(); + taxoWriter.close(); + } + + /** User runs a query and aggregates facets. */ + private List search() throws IOException, ParseException { + DirectoryReader indexReader = DirectoryReader.open(indexDir); + IndexSearcher searcher = new IndexSearcher(indexReader); + TaxonomyReader taxoReader = new DirectoryTaxonomyReader(taxoDir); + + // Aggregate categories by an expression that combines the document's score + // and its popularity field + Expression expr = JavascriptCompiler.compile("_score * sqrt(popularity)"); + SimpleBindings bindings = new SimpleBindings(); + bindings.add(new SortField("_score", SortField.Type.SCORE)); // the score of the document + bindings.add(new SortField("popularity", SortField.Type.LONG)); // the value of the 'popularity' field + + FacetSearchParams fsp = new FacetSearchParams( + new SumValueSourceFacetRequest(new CategoryPath("A"), 10, expr.getValueSource(bindings), true)); + + // Aggregates the facet values + FacetsCollector fc = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader); + + // MatchAllDocsQuery is for "browsing" (counts facets + // for all non-deleted docs in the index); normally + // you'd use a "normal" query, and use MultiCollector to + // wrap collecting the "normal" hits and also facets: + searcher.search(new MatchAllDocsQuery(), fc); + + // Retrieve results + List facetResults = fc.getFacetResults(); + + indexReader.close(); + taxoReader.close(); + + return facetResults; + } + + /** Runs the search example. */ + public List runSearch() throws IOException, ParseException { + index(); + return search(); + } + + /** Runs the search and drill-down examples and prints the results. */ + public static void main(String[] args) throws Exception { + System.out.println("Facet counting example:"); + System.out.println("-----------------------"); + List results = new ExpressionAggregationFacetsExample().runSearch(); + for (FacetResult res : results) { + System.out.println(res); + } + } + +} diff --git a/lucene/demo/src/java/org/apache/lucene/demo/facet/SimpleFacetsExample.java b/lucene/demo/src/java/org/apache/lucene/demo/facet/SimpleFacetsExample.java index ca310f423a0..4379c50b77f 100644 --- a/lucene/demo/src/java/org/apache/lucene/demo/facet/SimpleFacetsExample.java +++ b/lucene/demo/src/java/org/apache/lucene/demo/facet/SimpleFacetsExample.java @@ -93,7 +93,7 @@ public class SimpleFacetsExample { new CountFacetRequest(new CategoryPath("Publish Date"), 10), new CountFacetRequest(new CategoryPath("Author"), 10)); - // Aggregatses the facet counts + // Aggregates the facet counts FacetsCollector fc = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader); // MatchAllDocsQuery is for "browsing" (counts facets diff --git a/lucene/demo/src/test/org/apache/lucene/demo/facet/TestExpressionAggregationFacetsExample.java b/lucene/demo/src/test/org/apache/lucene/demo/facet/TestExpressionAggregationFacetsExample.java new file mode 100644 index 00000000000..ebac9d3c0ab --- /dev/null +++ b/lucene/demo/src/test/org/apache/lucene/demo/facet/TestExpressionAggregationFacetsExample.java @@ -0,0 +1,49 @@ +package org.apache.lucene.demo.facet; + +import java.util.List; +import java.util.Locale; + +import org.apache.lucene.facet.search.FacetResult; +import org.apache.lucene.facet.search.FacetResultNode; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.Test; + +/* + * 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. + */ + +public class TestExpressionAggregationFacetsExample extends LuceneTestCase { + + private static String toSimpleString(FacetResult fr) { + StringBuilder sb = new StringBuilder(); + toSimpleString(fr.getFacetRequest().categoryPath.length, 0, sb, fr.getFacetResultNode(), ""); + return sb.toString(); + } + + private static void toSimpleString(int startLength, int depth, StringBuilder sb, FacetResultNode node, String indent) { + sb.append(String.format(Locale.ROOT, "%s%s (%.3f)\n", indent, node.label.components[startLength + depth - 1], node.value)); + for (FacetResultNode childNode : node.subResults) { + toSimpleString(startLength, depth + 1, sb, childNode, indent + " "); + } + } + + @Test + public void testSimple() throws Exception { + List facetResults = new ExpressionAggregationFacetsExample().runSearch(); + assertEquals("A (0.000)\n B (2.236)\n C (1.732)\n", toSimpleString(facetResults.get(0))); + } + +} diff --git a/lucene/facet/build.xml b/lucene/facet/build.xml index f4349e0381a..9c4ba59dce8 100644 --- a/lucene/facet/build.xml +++ b/lucene/facet/build.xml @@ -25,11 +25,26 @@ + + + + + + + + + + + + + + + - + diff --git a/lucene/facet/src/java/org/apache/lucene/facet/search/SumValueSourceFacetRequest.java b/lucene/facet/src/java/org/apache/lucene/facet/search/SumValueSourceFacetRequest.java new file mode 100644 index 00000000000..3c5288b19e5 --- /dev/null +++ b/lucene/facet/src/java/org/apache/lucene/facet/search/SumValueSourceFacetRequest.java @@ -0,0 +1,194 @@ +package org.apache.lucene.facet.search; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.facet.params.CategoryListParams; +import org.apache.lucene.facet.params.FacetIndexingParams; +import org.apache.lucene.facet.search.FacetsCollector.MatchingDocs; +import org.apache.lucene.facet.search.OrdinalValueResolver.FloatValueResolver; +import org.apache.lucene.facet.taxonomy.CategoryPath; +import org.apache.lucene.facet.taxonomy.TaxonomyReader; +import org.apache.lucene.queries.function.FunctionValues; +import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.util.IntsRef; + +/* + * 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. + */ + +/** + * A {@link FacetRequest} which aggregates categories by the sum of the values, + * returned by a {@link ValueSource}, in the documents they are associated with. + * This allows aggregating the value of a category by e.g. summing the value of + * a {@link NumericDocValuesField} indexed for the document, or a more complex + * expression (from multiple fields) using the expressions module. + * + * @lucene.experimental + */ +public class SumValueSourceFacetRequest extends FacetRequest { + + private static abstract class SumValueSourceFacetsAggregator implements FacetsAggregator { + + protected final ValueSource valueSource; + protected final IntsRef ordinals = new IntsRef(32); + + protected SumValueSourceFacetsAggregator(ValueSource valueSource) { + this.valueSource = valueSource; + } + + private float doRollup(int ordinal, int[] children, int[] siblings, float[] values) { + float value = 0f; + while (ordinal != TaxonomyReader.INVALID_ORDINAL) { + float childValue = values[ordinal]; + childValue += doRollup(children[ordinal], children, siblings, values); + values[ordinal] = childValue; + value += childValue; + ordinal = siblings[ordinal]; + } + return value; + } + + @Override + public void rollupValues(FacetRequest fr, int ordinal, int[] children, int[] siblings, FacetArrays facetArrays) { + float[] values = facetArrays.getFloatArray(); + values[ordinal] += doRollup(children[ordinal], children, siblings, values); + } + + @Override + public OrdinalValueResolver createOrdinalValueResolver(FacetRequest facetRequest, FacetArrays arrays) { + return new FloatValueResolver(arrays); + } + + } + + private static class ScoreValueSourceFacetsAggregator extends SumValueSourceFacetsAggregator { + + private static final class FakeScorer extends Scorer { + float score; + int docID; + FakeScorer() { super(null); } + @Override public float score() throws IOException { return score; } + @Override public int freq() throws IOException { throw new UnsupportedOperationException(); } + @Override public int docID() { return docID; } + @Override public int nextDoc() throws IOException { throw new UnsupportedOperationException(); } + @Override public int advance(int target) throws IOException { throw new UnsupportedOperationException(); } + @Override public long cost() { return 0; } + } + + ScoreValueSourceFacetsAggregator(ValueSource valueSource) { + super(valueSource); + } + + @Override + public void aggregate(MatchingDocs matchingDocs, CategoryListParams clp, FacetArrays facetArrays) throws IOException { + final CategoryListIterator cli = clp.createCategoryListIterator(0); + if (!cli.setNextReader(matchingDocs.context)) { + return; + } + + assert matchingDocs.scores != null; + + final FakeScorer scorer = new FakeScorer(); + Map context = new HashMap(); + context.put("scorer", scorer); + + final FunctionValues fvalues = valueSource.getValues(context, matchingDocs.context); + final int length = matchingDocs.bits.length(); + final float[] aggValues = facetArrays.getFloatArray(); + int doc = 0; + int scoresIdx = 0; + while (doc < length && (doc = matchingDocs.bits.nextSetBit(doc)) != -1) { + scorer.docID = doc; + scorer.score = matchingDocs.scores[scoresIdx++]; + cli.getOrdinals(doc, ordinals); + final int upto = ordinals.offset + ordinals.length; + float val = (float) fvalues.doubleVal(doc); + for (int i = ordinals.offset; i < upto; i++) { + aggValues[ordinals.ints[i]] += val; + } + ++doc; + } + } + + @Override + public boolean requiresDocScores() { + return true; + } + } + + private static class NoScoreValueSourceFacetsAggregator extends SumValueSourceFacetsAggregator { + + NoScoreValueSourceFacetsAggregator(ValueSource valueSource) { + super(valueSource); + } + + @Override + public void aggregate(MatchingDocs matchingDocs, CategoryListParams clp, FacetArrays facetArrays) throws IOException { + final CategoryListIterator cli = clp.createCategoryListIterator(0); + if (!cli.setNextReader(matchingDocs.context)) { + return; + } + + final FunctionValues fvalues = valueSource.getValues(Collections.emptyMap(), matchingDocs.context); + final int length = matchingDocs.bits.length(); + final float[] aggValues = facetArrays.getFloatArray(); + int doc = 0; + while (doc < length && (doc = matchingDocs.bits.nextSetBit(doc)) != -1) { + cli.getOrdinals(doc, ordinals); + final int upto = ordinals.offset + ordinals.length; + float val = (float) fvalues.doubleVal(doc); + for (int i = ordinals.offset; i < upto; i++) { + aggValues[ordinals.ints[i]] += val; + } + ++doc; + } + } + + @Override + public boolean requiresDocScores() { + return false; + } + } + + private final ValueSource valueSource; + private final boolean requiresDocScores; + + /** + * Constructor which takes the {@link ValueSource} from which to read the + * documents' values. You can also specify if the value source requires + * document scores or not. + */ + public SumValueSourceFacetRequest(CategoryPath path, int num, ValueSource valueSource, boolean requiresDocScores) { + super(path, num); + this.valueSource = valueSource; + this.requiresDocScores = requiresDocScores; + } + + @Override + public FacetsAggregator createFacetsAggregator(FacetIndexingParams fip) { + if (requiresDocScores) { + return new ScoreValueSourceFacetsAggregator(valueSource); + } else { + return new NoScoreValueSourceFacetsAggregator(valueSource); + } + } + +} diff --git a/lucene/facet/src/test/org/apache/lucene/facet/search/TestSumValueSourceFacetRequest.java b/lucene/facet/src/test/org/apache/lucene/facet/search/TestSumValueSourceFacetRequest.java new file mode 100644 index 00000000000..315f31cd378 --- /dev/null +++ b/lucene/facet/src/test/org/apache/lucene/facet/search/TestSumValueSourceFacetRequest.java @@ -0,0 +1,185 @@ +package org.apache.lucene.facet.search; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.apache.lucene.analysis.MockAnalyzer; +import org.apache.lucene.document.Document; +import org.apache.lucene.document.NumericDocValuesField; +import org.apache.lucene.facet.FacetTestCase; +import org.apache.lucene.facet.FacetTestUtils; +import org.apache.lucene.facet.index.FacetFields; +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; +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.AtomicReaderContext; +import org.apache.lucene.index.DirectoryReader; +import org.apache.lucene.index.IndexWriter; +import org.apache.lucene.queries.function.FunctionQuery; +import org.apache.lucene.queries.function.FunctionValues; +import org.apache.lucene.queries.function.ValueSource; +import org.apache.lucene.queries.function.docvalues.DoubleDocValues; +import org.apache.lucene.queries.function.valuesource.LongFieldSource; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.MultiCollector; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.Scorer; +import org.apache.lucene.search.TopScoreDocCollector; +import org.apache.lucene.store.Directory; +import org.apache.lucene.util.IOUtils; +import org.junit.Test; + +/* + * 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. + */ + +public class TestSumValueSourceFacetRequest extends FacetTestCase { + + @Test + public void testNoScore() throws Exception { + Directory indexDir = newDirectory(); + Directory taxoDir = newDirectory(); + + TaxonomyWriter taxonomyWriter = new DirectoryTaxonomyWriter(taxoDir); + IndexWriter iw = new IndexWriter(indexDir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random()))); + + FacetFields facetFields = new FacetFields(taxonomyWriter); + for (int i = 0; i < 4; i++) { + Document doc = new Document(); + doc.add(new NumericDocValuesField("price", (i+1))); + facetFields.addFields(doc, Collections.singletonList(new CategoryPath("a", Integer.toString(i % 2)))); + iw.addDocument(doc); + } + + taxonomyWriter.close(); + iw.close(); + + DirectoryReader r = DirectoryReader.open(indexDir); + DirectoryTaxonomyReader taxo = new DirectoryTaxonomyReader(taxoDir); + + ValueSource valueSource = new LongFieldSource("price"); + FacetSearchParams fsp = new FacetSearchParams(new SumValueSourceFacetRequest(new CategoryPath("a"), 10, valueSource, false)); + FacetsCollector fc = FacetsCollector.create(fsp, r, taxo); + newSearcher(r).search(new MatchAllDocsQuery(), fc); + + List res = fc.getFacetResults(); + assertEquals("a (0)\n 1 (6)\n 0 (4)\n", FacetTestUtils.toSimpleString(res.get(0))); + + IOUtils.close(taxo, taxoDir, r, indexDir); + } + + @Test + public void testWithScore() throws Exception { + Directory indexDir = newDirectory(); + Directory taxoDir = newDirectory(); + + TaxonomyWriter taxonomyWriter = new DirectoryTaxonomyWriter(taxoDir); + IndexWriter iw = new IndexWriter(indexDir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random()))); + + FacetFields facetFields = new FacetFields(taxonomyWriter); + for (int i = 0; i < 4; i++) { + Document doc = new Document(); + doc.add(new NumericDocValuesField("price", (i+1))); + facetFields.addFields(doc, Collections.singletonList(new CategoryPath("a", Integer.toString(i % 2)))); + iw.addDocument(doc); + } + + taxonomyWriter.close(); + iw.close(); + + DirectoryReader r = DirectoryReader.open(indexDir); + DirectoryTaxonomyReader taxo = new DirectoryTaxonomyReader(taxoDir); + + ValueSource valueSource = new ValueSource() { + @Override + public FunctionValues getValues(@SuppressWarnings("rawtypes") Map context, AtomicReaderContext readerContext) throws IOException { + final Scorer scorer = (Scorer) context.get("scorer"); + assert scorer != null; + return new DoubleDocValues(this) { + @Override + public double doubleVal(int document) { + try { + return scorer.score(); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + }; + } + + @Override public boolean equals(Object o) { return o == this; } + @Override public int hashCode() { return System.identityHashCode(this); } + @Override public String description() { return "score()"; } + }; + + FacetSearchParams fsp = new FacetSearchParams(new SumValueSourceFacetRequest(new CategoryPath("a"), 10, valueSource, true)); + FacetsCollector fc = FacetsCollector.create(fsp, r, taxo); + TopScoreDocCollector tsdc = TopScoreDocCollector.create(10, true); + // score documents by their 'price' field - makes asserting the correct counts for the categories easier + Query q = new FunctionQuery(new LongFieldSource("price")); + newSearcher(r).search(q, MultiCollector.wrap(tsdc, fc)); + + List res = fc.getFacetResults(); + assertEquals("a (0)\n 1 (6)\n 0 (4)\n", FacetTestUtils.toSimpleString(res.get(0))); + + IOUtils.close(taxo, taxoDir, r, indexDir); + } + + @Test + public void testRollupValues() throws Exception { + Directory indexDir = newDirectory(); + Directory taxoDir = newDirectory(); + + TaxonomyWriter taxonomyWriter = new DirectoryTaxonomyWriter(taxoDir); + IndexWriter iw = new IndexWriter(indexDir, newIndexWriterConfig(TEST_VERSION_CURRENT, new MockAnalyzer(random()))); + FacetIndexingParams fip = new FacetIndexingParams(new CategoryListParams() { + @Override + public OrdinalPolicy getOrdinalPolicy(String dimension) { + return OrdinalPolicy.NO_PARENTS; + } + }); + FacetFields facetFields = new FacetFields(taxonomyWriter, fip); + for (int i = 0; i < 4; i++) { + Document doc = new Document(); + doc.add(new NumericDocValuesField("price", (i+1))); + facetFields.addFields(doc, Collections.singletonList(new CategoryPath("a", Integer.toString(i % 2), "1"))); + iw.addDocument(doc); + } + + taxonomyWriter.close(); + iw.close(); + + DirectoryReader r = DirectoryReader.open(indexDir); + DirectoryTaxonomyReader taxo = new DirectoryTaxonomyReader(taxoDir); + + ValueSource valueSource = new LongFieldSource("price"); + FacetSearchParams fsp = new FacetSearchParams(fip, new SumValueSourceFacetRequest(new CategoryPath("a"), 10, valueSource, false)); + FacetsCollector fc = FacetsCollector.create(fsp, r, taxo); + newSearcher(r).search(new MatchAllDocsQuery(), fc); + + List res = fc.getFacetResults(); + assertEquals("a (10)\n 1 (6)\n 0 (4)\n", FacetTestUtils.toSimpleString(res.get(0))); + + IOUtils.close(taxo, taxoDir, r, indexDir); + } + +}