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);
+ }
+
+}