mirror of https://github.com/apache/lucene.git
LUCENE-5298: add SumValueSourceFacetRequest
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1538431 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
dab5e2aa89
commit
442e3253fc
|
@ -135,6 +135,10 @@ New Features
|
||||||
numeric DocValues fields of documents, without re-indexing them.
|
numeric DocValues fields of documents, without re-indexing them.
|
||||||
(Shai Erera, Mike McCandless, Robert Muir)
|
(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
|
Bug Fixes
|
||||||
|
|
||||||
* LUCENE-4998: Fixed a few places to pass IOContext.READONCE instead
|
* LUCENE-4998: Fixed a few places to pass IOContext.READONCE instead
|
||||||
|
|
|
@ -33,22 +33,27 @@
|
||||||
<pathelement path="${analyzers-common.jar}"/>
|
<pathelement path="${analyzers-common.jar}"/>
|
||||||
<pathelement path="${queryparser.jar}"/>
|
<pathelement path="${queryparser.jar}"/>
|
||||||
<pathelement path="${lucene-core.jar}"/>
|
<pathelement path="${lucene-core.jar}"/>
|
||||||
|
<pathelement path="${queries.jar}"/>
|
||||||
<pathelement path="${facet.jar}"/>
|
<pathelement path="${facet.jar}"/>
|
||||||
|
<pathelement path="${expressions.jar}"/>
|
||||||
|
<fileset dir="../expressions/lib"/>
|
||||||
<fileset dir="lib"/>
|
<fileset dir="lib"/>
|
||||||
</path>
|
</path>
|
||||||
|
|
||||||
<target name="javadocs" depends="javadocs-analyzers-common,javadocs-queryparser,javadocs-facet,compile-core">
|
<target name="javadocs" depends="javadocs-analyzers-common,javadocs-queryparser,javadocs-facet,javadocs-expressions,compile-core">
|
||||||
<!-- we link the example source in the javadocs, as its ref'ed elsewhere -->
|
<!-- we link the example source in the javadocs, as its ref'ed elsewhere -->
|
||||||
<invoke-module-javadoc linksource="yes">
|
<invoke-module-javadoc linksource="yes">
|
||||||
<links>
|
<links>
|
||||||
<link href="../analyzers-common"/>
|
<link href="../analyzers-common"/>
|
||||||
<link href="../queryparser"/>
|
<link href="../queryparser"/>
|
||||||
|
<link href="../queries"/>
|
||||||
<link href="../facet"/>
|
<link href="../facet"/>
|
||||||
|
<link href="../expressions"/>
|
||||||
</links>
|
</links>
|
||||||
</invoke-module-javadoc>
|
</invoke-module-javadoc>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<target name="compile-core" depends="jar-analyzers-common,jar-queryparser,jar-facet,common.compile-core" />
|
<target name="compile-core" depends="jar-analyzers-common,jar-queryparser,jar-queries,jar-facet,jar-expressions,common.compile-core" />
|
||||||
|
|
||||||
<target name="default" depends="jar-core,build-web-demo"/>
|
<target name="default" depends="jar-core,build-web-demo"/>
|
||||||
|
|
||||||
|
|
|
@ -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<FacetResult> 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<FacetResult> facetResults = fc.getFacetResults();
|
||||||
|
|
||||||
|
indexReader.close();
|
||||||
|
taxoReader.close();
|
||||||
|
|
||||||
|
return facetResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs the search example. */
|
||||||
|
public List<FacetResult> 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<FacetResult> results = new ExpressionAggregationFacetsExample().runSearch();
|
||||||
|
for (FacetResult res : results) {
|
||||||
|
System.out.println(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -93,7 +93,7 @@ public class SimpleFacetsExample {
|
||||||
new CountFacetRequest(new CategoryPath("Publish Date"), 10),
|
new CountFacetRequest(new CategoryPath("Publish Date"), 10),
|
||||||
new CountFacetRequest(new CategoryPath("Author"), 10));
|
new CountFacetRequest(new CategoryPath("Author"), 10));
|
||||||
|
|
||||||
// Aggregatses the facet counts
|
// Aggregates the facet counts
|
||||||
FacetsCollector fc = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader);
|
FacetsCollector fc = FacetsCollector.create(fsp, searcher.getIndexReader(), taxoReader);
|
||||||
|
|
||||||
// MatchAllDocsQuery is for "browsing" (counts facets
|
// MatchAllDocsQuery is for "browsing" (counts facets
|
||||||
|
|
|
@ -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<FacetResult> facetResults = new ExpressionAggregationFacetsExample().runSearch();
|
||||||
|
assertEquals("A (0.000)\n B (2.236)\n C (1.732)\n", toSimpleString(facetResults.get(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -25,11 +25,26 @@
|
||||||
|
|
||||||
<import file="../module-build.xml"/>
|
<import file="../module-build.xml"/>
|
||||||
|
|
||||||
|
<path id="classpath">
|
||||||
|
<path refid="base.classpath"/>
|
||||||
|
<pathelement path="${queries.jar}"/>
|
||||||
|
</path>
|
||||||
|
|
||||||
|
<target name="compile-core" depends="jar-queries,common.compile-core" />
|
||||||
|
|
||||||
|
<target name="javadocs" depends="javadocs-queries,compile-core">
|
||||||
|
<invoke-module-javadoc>
|
||||||
|
<links>
|
||||||
|
<link href="../queries"/>
|
||||||
|
</links>
|
||||||
|
</invoke-module-javadoc>
|
||||||
|
</target>
|
||||||
|
|
||||||
<target name="run-encoding-benchmark" depends="compile-test">
|
<target name="run-encoding-benchmark" depends="compile-test">
|
||||||
<java classname="org.apache.lucene.util.encoding.EncodingSpeed" fork="true" failonerror="true">
|
<java classname="org.apache.lucene.util.encoding.EncodingSpeed" fork="true" failonerror="true">
|
||||||
<classpath refid="test.classpath" />
|
<classpath refid="test.classpath" />
|
||||||
<classpath path="${build.dir}/classes/test" />
|
<classpath path="${build.dir}/classes/test" />
|
||||||
</java>
|
</java>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -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<String, Scorer> context = new HashMap<String, Scorer>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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<FacetResult> 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<FacetResult> 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<FacetResult> res = fc.getFacetResults();
|
||||||
|
assertEquals("a (10)\n 1 (6)\n 0 (4)\n", FacetTestUtils.toSimpleString(res.get(0)));
|
||||||
|
|
||||||
|
IOUtils.close(taxo, taxoDir, r, indexDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue