numeric statistical facets support

This commit is contained in:
kimchy 2010-06-05 23:44:34 +03:00
parent 57a21bedd2
commit 8aeb589a42
27 changed files with 713 additions and 44 deletions

View File

@ -253,6 +253,7 @@ public class SearchRequestBuilder {
*
* @param name The logical name of the facet, it will be returned under the name
* @param query The query facet
* @see org.elasticsearch.search.facets.query.QueryFacet
*/
public SearchRequestBuilder addFacetQuery(String name, XContentQueryBuilder query) {
facetsBuilder().queryFacet(name, query);
@ -265,22 +266,63 @@ public class SearchRequestBuilder {
*
* @param name The logical name of the facet, it will be returned under the name
* @param query The query facet
* @see org.elasticsearch.search.facets.query.QueryFacet
*/
public SearchRequestBuilder addFacetGlobalQuery(String name, XContentQueryBuilder query) {
facetsBuilder().queryFacetGlobal(name, query);
return this;
}
/**
* Adds a term facet for the provided field name.
*
* @param name The name of the facet
* @param fieldName The field name to run the facet against
* @param size The number of the terms
* @see org.elasticsearch.search.facets.terms.TermsFacet
*/
public SearchRequestBuilder addFacetTerms(String name, String fieldName, int size) {
facetsBuilder().termsFacet(name, fieldName, size);
return this;
}
/**
* Adds a <b>global</b> term facet for the provided field name.
*
* @param name The name of the facet
* @param fieldName The field name to run the facet against
* @param size The number of the terms
* @see org.elasticsearch.search.facets.terms.TermsFacet
*/
public SearchRequestBuilder addFacetGlobalTerms(String name, String fieldName, int size) {
facetsBuilder().termsFacetGlobal(name, fieldName, size);
return this;
}
/**
* Adds a numeric statistical facet for the provided field name.
*
* @param name The name of the facet
* @param fieldName The name of the <b>numeric</b> field
* @see org.elasticsearch.search.facets.statistical.StatisticalFacet
*/
public SearchRequestBuilder addFacetStatistical(String name, String fieldName) {
facetsBuilder().statisticalFacet(name, fieldName);
return this;
}
/**
* Adds a numeric statistical <b>global</b> facet for the provided field name.
*
* @param name The name of the facet
* @param fieldName The name of the <b>numeric</b> field
* @see org.elasticsearch.search.facets.statistical.StatisticalFacet
*/
public SearchRequestBuilder addFacetGlobalStatistical(String name, String fieldName) {
facetsBuilder().statisticalFacetGlobal(name, fieldName);
return this;
}
/**
* Adds a field to be highlighted with default fragment size of 100 characters, and
* default number of fragments of 5.

View File

@ -97,7 +97,7 @@ public abstract class FieldData {
public abstract void forEachValueInDoc(int docId, StringValueInDocProc proc);
public static interface StringValueInDocProc {
void onValue(String value, int docId);
void onValue(int docId, String value);
}
/**

View File

@ -85,4 +85,10 @@ public abstract class NumericFieldData extends FieldData {
public short shortValue(int docId) {
return (short) intValue(docId);
}
public abstract void forEachValueInDoc(int docId, DoubleValueInDocProc proc);
public static interface DoubleValueInDocProc {
void onValue(int docId, double value);
}
}

View File

@ -61,7 +61,17 @@ public class MultiValueDoubleFieldData extends DoubleFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(Double.toString(values[docOrder]), docId);
proc.onValue(docId, Double.toString(values[docOrder]));
}
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int[] docOrders = order[docId];
if (docOrders == null) {
return;
}
for (int docOrder : docOrders) {
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -53,7 +53,15 @@ public class SingleValueDoubleFieldData extends DoubleFieldData {
if (loc == 0) {
return;
}
proc.onValue(Double.toString(values[loc]), docId);
proc.onValue(docId, Double.toString(values[loc]));
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int loc = order[docId];
if (loc == 0) {
return;
}
proc.onValue(docId, values[loc]);
}
@Override public double value(int docId) {

View File

@ -61,7 +61,17 @@ public class MultiValueFloatFieldData extends FloatFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(Float.toString(values[docOrder]), docId);
proc.onValue(docId, Float.toString(values[docOrder]));
}
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int[] docOrders = order[docId];
if (docOrders == null) {
return;
}
for (int docOrder : docOrders) {
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -53,7 +53,15 @@ public class SingleValueFloatFieldData extends FloatFieldData {
if (loc == 0) {
return;
}
proc.onValue(Float.toString(values[loc]), docId);
proc.onValue(docId, Float.toString(values[loc]));
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int loc = order[docId];
if (loc == 0) {
return;
}
proc.onValue(docId, values[loc]);
}
@Override public float value(int docId) {

View File

@ -61,7 +61,17 @@ public class MultiValueIntFieldData extends IntFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(Integer.toString(values[docOrder]), docId);
proc.onValue(docId, Integer.toString(values[docOrder]));
}
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int[] docOrders = order[docId];
if (docOrders == null) {
return;
}
for (int docOrder : docOrders) {
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -53,7 +53,15 @@ public class SingleValueIntFieldData extends IntFieldData {
if (loc == 0) {
return;
}
proc.onValue(Integer.toString(values[loc]), docId);
proc.onValue(docId, Integer.toString(values[loc]));
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int loc = order[docId];
if (loc == 0) {
return;
}
proc.onValue(docId, values[loc]);
}
@Override public int value(int docId) {

View File

@ -61,7 +61,17 @@ public class MultiValueLongFieldData extends LongFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(Long.toString(values[docOrder]), docId);
proc.onValue(docId, Long.toString(values[docOrder]));
}
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int[] docOrders = order[docId];
if (docOrders == null) {
return;
}
for (int docOrder : docOrders) {
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -53,7 +53,15 @@ public class SingleValueLongFieldData extends LongFieldData {
if (loc == 0) {
return;
}
proc.onValue(Long.toString(values[loc]), docId);
proc.onValue(docId, Long.toString(values[loc]));
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int loc = order[docId];
if (loc == 0) {
return;
}
proc.onValue(docId, values[loc]);
}
@Override public long value(int docId) {

View File

@ -61,7 +61,17 @@ public class MultiValueShortFieldData extends ShortFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(Short.toString(values[docOrder]), docId);
proc.onValue(docId, Short.toString(values[docOrder]));
}
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int[] docOrders = order[docId];
if (docOrders == null) {
return;
}
for (int docOrder : docOrders) {
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -53,7 +53,15 @@ public class SingleValueShortFieldData extends ShortFieldData {
if (loc == 0) {
return;
}
proc.onValue(Short.toString(values[loc]), docId);
proc.onValue(docId, Short.toString(values[loc]));
}
@Override public void forEachValueInDoc(int docId, DoubleValueInDocProc proc) {
int loc = order[docId];
if (loc == 0) {
return;
}
proc.onValue(docId, values[loc]);
}
@Override public short value(int docId) {

View File

@ -62,7 +62,7 @@ public class MultiValueStringFieldData extends StringFieldData {
return;
}
for (int docOrder : docOrders) {
proc.onValue(values[docOrder], docId);
proc.onValue(docId, values[docOrder]);
}
}

View File

@ -54,7 +54,7 @@ public class SingleValueStringFieldData extends StringFieldData {
if (loc == 0) {
return;
}
proc.onValue(values[loc], docId);
proc.onValue(docId, values[loc]);
}
@Override public String value(int docId) {

View File

@ -21,7 +21,8 @@ package org.elasticsearch.search.builder;
import org.elasticsearch.index.query.xcontent.XContentQueryBuilder;
import org.elasticsearch.search.facets.query.QueryFacetCollectorParser;
import org.elasticsearch.search.facets.terms.TermFacetCollectorParser;
import org.elasticsearch.search.facets.statistical.StatisticalFacetCollectorParser;
import org.elasticsearch.search.facets.terms.TermsFacetCollectorParser;
import org.elasticsearch.util.xcontent.ToXContent;
import org.elasticsearch.util.xcontent.builder.XContentBuilder;
@ -38,8 +39,9 @@ import static org.elasticsearch.util.collect.Lists.*;
*/
public class SearchSourceFacetsBuilder implements ToXContent {
private List<QueryFacet> queryFacets;
private List<TermsFacet> termsFacets;
private List<BuilderQueryFacet> queryFacets;
private List<BuilderTermsFacet> termsFacets;
private List<BuilderStatisticalFacet> statisticalFacets;
/**
* Adds a query facet (which results in a count facet returned).
@ -51,7 +53,7 @@ public class SearchSourceFacetsBuilder implements ToXContent {
if (queryFacets == null) {
queryFacets = newArrayListWithCapacity(2);
}
queryFacets.add(new QueryFacet(name, query, false));
queryFacets.add(new BuilderQueryFacet(name, query, false));
return this;
}
@ -59,15 +61,14 @@ public class SearchSourceFacetsBuilder implements ToXContent {
* Adds a query facet (which results in a count facet returned) with an option to
* be global on the index or bounded by the search query.
*
* @param name The logical name of the facet, it will be returned under the name
* @param query The query facet
* @param global Should the facet be executed globally or not
* @param name The logical name of the facet, it will be returned under the name
* @param query The query facet
*/
public SearchSourceFacetsBuilder queryFacetGlobal(String name, XContentQueryBuilder query) {
if (queryFacets == null) {
queryFacets = newArrayListWithCapacity(2);
}
queryFacets.add(new QueryFacet(name, query, true));
queryFacets.add(new BuilderQueryFacet(name, query, true));
return this;
}
@ -75,7 +76,7 @@ public class SearchSourceFacetsBuilder implements ToXContent {
if (termsFacets == null) {
termsFacets = newArrayListWithCapacity(2);
}
termsFacets.add(new TermsFacet(name, fieldName, size, false));
termsFacets.add(new BuilderTermsFacet(name, fieldName, size, false));
return this;
}
@ -83,12 +84,28 @@ public class SearchSourceFacetsBuilder implements ToXContent {
if (termsFacets == null) {
termsFacets = newArrayListWithCapacity(2);
}
termsFacets.add(new TermsFacet(name, fieldName, size, true));
termsFacets.add(new BuilderTermsFacet(name, fieldName, size, true));
return this;
}
public SearchSourceFacetsBuilder statisticalFacet(String name, String fieldName) {
if (statisticalFacets == null) {
statisticalFacets = newArrayListWithCapacity(2);
}
statisticalFacets.add(new BuilderStatisticalFacet(name, fieldName, false));
return this;
}
public SearchSourceFacetsBuilder statisticalFacetGlobal(String name, String fieldName) {
if (statisticalFacets == null) {
statisticalFacets = newArrayListWithCapacity(2);
}
statisticalFacets.add(new BuilderStatisticalFacet(name, fieldName, true));
return this;
}
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
if (queryFacets == null && termsFacets == null) {
if (queryFacets == null && termsFacets == null && statisticalFacets == null) {
return;
}
builder.field("facets");
@ -96,7 +113,7 @@ public class SearchSourceFacetsBuilder implements ToXContent {
builder.startObject();
if (queryFacets != null) {
for (QueryFacet queryFacet : queryFacets) {
for (BuilderQueryFacet queryFacet : queryFacets) {
builder.startObject(queryFacet.name());
builder.field(QueryFacetCollectorParser.NAME);
queryFacet.queryBuilder().toXContent(builder, params);
@ -107,10 +124,10 @@ public class SearchSourceFacetsBuilder implements ToXContent {
}
}
if (termsFacets != null) {
for (TermsFacet termsFacet : termsFacets) {
for (BuilderTermsFacet termsFacet : termsFacets) {
builder.startObject(termsFacet.name());
builder.startObject(TermFacetCollectorParser.NAME);
builder.startObject(TermsFacetCollectorParser.NAME);
builder.field("field", termsFacet.fieldName());
builder.field("size", termsFacet.size());
builder.endObject();
@ -123,16 +140,32 @@ public class SearchSourceFacetsBuilder implements ToXContent {
}
}
if (statisticalFacets != null) {
for (BuilderStatisticalFacet statisticalFacet : statisticalFacets) {
builder.startObject(statisticalFacet.name());
builder.startObject(StatisticalFacetCollectorParser.NAME);
builder.field("field", statisticalFacet.fieldName());
builder.endObject();
if (statisticalFacet.global() != null) {
builder.field("global", statisticalFacet.global());
}
builder.endObject();
}
}
builder.endObject();
}
private static class TermsFacet {
private static class BuilderTermsFacet {
private final String name;
private final String fieldName;
private final int size;
private final Boolean global;
private TermsFacet(String name, String fieldName, int size, Boolean global) {
private BuilderTermsFacet(String name, String fieldName, int size, Boolean global) {
this.name = name;
this.fieldName = fieldName;
this.size = size;
@ -156,12 +189,12 @@ public class SearchSourceFacetsBuilder implements ToXContent {
}
}
private static class QueryFacet {
private static class BuilderQueryFacet {
private final String name;
private final XContentQueryBuilder queryBuilder;
private final Boolean global;
private QueryFacet(String name, XContentQueryBuilder queryBuilder, Boolean global) {
private BuilderQueryFacet(String name, XContentQueryBuilder queryBuilder, Boolean global) {
this.name = name;
this.queryBuilder = queryBuilder;
this.global = global;
@ -179,4 +212,28 @@ public class SearchSourceFacetsBuilder implements ToXContent {
return this.global;
}
}
private static class BuilderStatisticalFacet {
private final String name;
private final String fieldName;
private final Boolean global;
private BuilderStatisticalFacet(String name, String fieldName, Boolean global) {
this.name = name;
this.fieldName = fieldName;
this.global = global;
}
public String name() {
return name;
}
public String fieldName() {
return fieldName;
}
public Boolean global() {
return this.global;
}
}
}

View File

@ -29,11 +29,9 @@ import org.elasticsearch.ElasticSearchIllegalArgumentException;
public interface Facet {
enum Type {
/**
* Count type facet.
*/
TERMS(0),
QUERY(1);
QUERY(1),
STATISTICAL(2);
int id;
@ -50,6 +48,8 @@ public interface Facet {
return TERMS;
} else if (id == 1) {
return QUERY;
} else if (id == 2) {
return STATISTICAL;
} else {
throw new ElasticSearchIllegalArgumentException("No match for id [" + id + "]");
}

View File

@ -26,6 +26,10 @@ import org.elasticsearch.ElasticSearchException;
*/
public class FacetPhaseExecutionException extends ElasticSearchException {
public FacetPhaseExecutionException(String facetName, String msg) {
super("Facet [" + facetName + "]: " + msg);
}
public FacetPhaseExecutionException(String facetName, String msg, Throwable t) {
super("Facet [" + facetName + "]: " + msg, t);
}

View File

@ -20,6 +20,7 @@
package org.elasticsearch.search.facets;
import org.elasticsearch.search.facets.query.InternalQueryFacet;
import org.elasticsearch.search.facets.statistical.InternalStatisticalFacet;
import org.elasticsearch.search.facets.terms.InternalTermsFacet;
import org.elasticsearch.util.collect.ImmutableList;
import org.elasticsearch.util.io.stream.StreamInput;
@ -136,6 +137,8 @@ public class Facets implements Streamable, ToXContent, Iterable<Facet> {
facets.add(InternalTermsFacet.readTermsFacet(in));
} else if (id == Facet.Type.QUERY.id()) {
facets.add(InternalQueryFacet.readCountFacet(in));
} else if (id == Facet.Type.STATISTICAL.id()) {
facets.add(InternalStatisticalFacet.readStatisticalFacet(in));
} else {
throw new IOException("Can't handle facet type with id [" + id + "]");
}

View File

@ -24,7 +24,8 @@ import org.elasticsearch.search.SearchParseException;
import org.elasticsearch.search.facets.collector.FacetCollector;
import org.elasticsearch.search.facets.collector.FacetCollectorParser;
import org.elasticsearch.search.facets.query.QueryFacetCollectorParser;
import org.elasticsearch.search.facets.terms.TermFacetCollectorParser;
import org.elasticsearch.search.facets.statistical.StatisticalFacetCollectorParser;
import org.elasticsearch.search.facets.terms.TermsFacetCollectorParser;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.util.MapBuilder;
import org.elasticsearch.util.collect.ImmutableMap;
@ -60,8 +61,9 @@ public class FacetsParseElement implements SearchParseElement {
public FacetsParseElement() {
MapBuilder<String, FacetCollectorParser> builder = newMapBuilder();
builder.put(TermFacetCollectorParser.NAME, new TermFacetCollectorParser());
builder.put(TermsFacetCollectorParser.NAME, new TermsFacetCollectorParser());
builder.put(QueryFacetCollectorParser.NAME, new QueryFacetCollectorParser());
builder.put(StatisticalFacetCollectorParser.NAME, new StatisticalFacetCollectorParser());
this.facetCollectorParsers = builder.immutableMap();
}

View File

@ -0,0 +1,167 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.elasticsearch.search.facets.statistical;
import org.elasticsearch.search.facets.Facet;
import org.elasticsearch.search.facets.InternalFacet;
import org.elasticsearch.util.io.stream.StreamInput;
import org.elasticsearch.util.io.stream.StreamOutput;
import org.elasticsearch.util.xcontent.builder.XContentBuilder;
import java.io.IOException;
/**
* @author kimchy (shay.banon)
*/
public class InternalStatisticalFacet implements StatisticalFacet, InternalFacet {
private String name;
private double min;
private double max;
private double total;
private long count;
private InternalStatisticalFacet() {
}
public InternalStatisticalFacet(String name, double min, double max, double total, long count) {
this.name = name;
this.min = min;
this.max = max;
this.total = total;
this.count = count;
}
@Override public String name() {
return this.name;
}
@Override public String getName() {
return name();
}
@Override public Type type() {
return Type.STATISTICAL;
}
@Override public Type getType() {
return type();
}
@Override public long count() {
return this.count;
}
@Override public long getCount() {
return count();
}
@Override public double total() {
return this.total;
}
@Override public double getTotal() {
return total();
}
@Override public double mean() {
return total / count;
}
@Override public double getMean() {
return mean();
}
@Override public double min() {
return this.min;
}
@Override public double getMin() {
return min();
}
@Override public double max() {
return this.max;
}
@Override public double getMax() {
return max();
}
@Override public Facet aggregate(Iterable<Facet> facets) {
double min = Double.MAX_VALUE;
double max = Double.MIN_VALUE;
double total = 0;
long count = 0;
for (Facet facet : facets) {
if (!facet.name().equals(name)) {
continue;
}
InternalStatisticalFacet statsFacet = (InternalStatisticalFacet) facet;
if (statsFacet.min() < min) {
min = statsFacet.min();
}
if (statsFacet.max() > max) {
max = statsFacet.max();
}
total += statsFacet.total();
count += statsFacet.count();
}
return new InternalStatisticalFacet(name, min, max, total, count);
}
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(name);
builder.field("_type", "stats");
builder.field("count", count);
builder.field("total", total);
builder.field("min", min);
builder.field("max", max);
builder.endObject();
}
public static StatisticalFacet readStatisticalFacet(StreamInput in) throws IOException {
InternalStatisticalFacet facet = new InternalStatisticalFacet();
facet.readFrom(in);
return facet;
}
@Override public void readFrom(StreamInput in) throws IOException {
name = in.readUTF();
count = in.readVLong();
total = in.readDouble();
min = in.readDouble();
max = in.readDouble();
}
@Override public void writeTo(StreamOutput out) throws IOException {
out.writeUTF(name);
out.writeVLong(count);
out.writeDouble(total);
out.writeDouble(min);
out.writeDouble(max);
}
}

View File

@ -0,0 +1,80 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.elasticsearch.search.facets.statistical;
import org.elasticsearch.search.facets.Facet;
/**
* Numeric statistical information.
*
* @author kimchy (shay.banon)
*/
public interface StatisticalFacet extends Facet {
/**
* The number of values counted.
*/
long count();
/**
* The number of values counted.
*/
long getCount();
/**
* The total (sum) of values.
*/
double total();
/**
* The total (sum) of values.
*/
double getTotal();
/**
* The mean (average) of the values.
*/
double mean();
/**
* The mean (average) of the values.
*/
double getMean();
/**
* The minimum value.
*/
double min();
/**
* The minimum value.
*/
double getMin();
/**
* The maximum value.
*/
double max();
/**
* The maximum value.
*/
double getMax();
}

View File

@ -0,0 +1,122 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.elasticsearch.search.facets.statistical;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.index.cache.field.FieldDataCache;
import org.elasticsearch.index.field.FieldData;
import org.elasticsearch.index.field.NumericFieldData;
import org.elasticsearch.index.mapper.FieldMapper;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.search.facets.Facet;
import org.elasticsearch.search.facets.FacetPhaseExecutionException;
import org.elasticsearch.search.facets.collector.FacetCollector;
import java.io.IOException;
import static org.elasticsearch.index.field.FieldDataOptions.*;
/**
* @author kimchy (shay.banon)
*/
public class StatisticalFacetCollector extends FacetCollector {
private final String name;
private final String fieldName;
private final FieldDataCache fieldDataCache;
private final FieldData.Type fieldDataType;
private NumericFieldData fieldData;
private final StatsProc statsProc = new StatsProc();
public StatisticalFacetCollector(String name, String fieldName, FieldDataCache fieldDataCache, MapperService mapperService) {
this.name = name;
this.fieldName = fieldName;
this.fieldDataCache = fieldDataCache;
FieldMapper mapper = mapperService.smartNameFieldMapper(fieldName);
if (mapper == null) {
throw new FacetPhaseExecutionException(name, "No mapping found for field [" + fieldName + "]");
}
fieldDataType = mapper.fieldDataType();
}
@Override public void collect(int doc) throws IOException {
fieldData.forEachValueInDoc(doc, statsProc);
}
@Override public void setNextReader(IndexReader reader, int docBase) throws IOException {
fieldData = (NumericFieldData) fieldDataCache.cache(fieldDataType, reader, fieldName, fieldDataOptions().withFreqs(false));
}
@Override public void setScorer(Scorer scorer) throws IOException {
}
@Override public boolean acceptsDocsOutOfOrder() {
return true;
}
@Override public Facet facet() {
return new InternalStatisticalFacet(name, statsProc.min(), statsProc.max(), statsProc.total(), statsProc.count());
}
public static class StatsProc implements NumericFieldData.DoubleValueInDocProc {
private double min = Double.MAX_VALUE;
private double max = Double.MIN_VALUE;
private double total = 0;
private long count;
@Override public void onValue(int docId, double value) {
count++;
total += value;
if (value < min) {
min = value;
}
if (value > max) {
max = value;
}
}
public double min() {
return min;
}
public double max() {
return max;
}
public double total() {
return total;
}
public long count() {
return count;
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.elasticsearch.search.facets.statistical;
import org.elasticsearch.search.facets.collector.FacetCollector;
import org.elasticsearch.search.facets.collector.FacetCollectorParser;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.util.xcontent.XContentParser;
import java.io.IOException;
/**
* @author kimchy (shay.banon)
*/
public class StatisticalFacetCollectorParser implements FacetCollectorParser {
public static final String NAME = "statistical";
@Override public String name() {
return NAME;
}
@Override public FacetCollector parser(String facetName, XContentParser parser, SearchContext context) throws IOException {
String field = null;
String fieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
fieldName = parser.currentName();
} else if (token.isValue()) {
if ("field".equals(fieldName)) {
field = parser.text();
}
}
}
return new StatisticalFacetCollector(facetName, field, context.fieldDataCache(), context.mapperService());
}
}

View File

@ -42,7 +42,7 @@ import static org.elasticsearch.index.field.FieldDataOptions.*;
/**
* @author kimchy (shay.banon)
*/
public class TermFacetCollector extends FacetCollector {
public class TermsFacetCollector extends FacetCollector {
private static ThreadLocal<ThreadLocals.CleanableValue<Deque<TObjectIntHashMap<String>>>> cache = new ThreadLocal<ThreadLocals.CleanableValue<Deque<TObjectIntHashMap<String>>>>() {
@Override protected ThreadLocals.CleanableValue<Deque<TObjectIntHashMap<String>>> initialValue() {
@ -65,7 +65,7 @@ public class TermFacetCollector extends FacetCollector {
private final AggregatorValueProc aggregator;
public TermFacetCollector(String name, String fieldName, int size, FieldDataCache fieldDataCache, MapperService mapperService) {
public TermsFacetCollector(String name, String fieldName, int size, FieldDataCache fieldDataCache, MapperService mapperService) {
this.name = name;
this.fieldDataCache = fieldDataCache;
this.size = size;
@ -140,7 +140,7 @@ public class TermFacetCollector extends FacetCollector {
this.facets = facets;
}
@Override public void onValue(String value, int docId) {
@Override public void onValue(int docId, String value) {
facets.adjustOrPutValue(value, 1, 1);
}

View File

@ -29,7 +29,7 @@ import java.io.IOException;
/**
* @author kimchy (shay.banon)
*/
public class TermFacetCollectorParser implements FacetCollectorParser {
public class TermsFacetCollectorParser implements FacetCollectorParser {
public static final String NAME = "terms";
@ -54,6 +54,6 @@ public class TermFacetCollectorParser implements FacetCollectorParser {
}
}
}
return new TermFacetCollector(facetName, field, size, context.fieldDataCache(), context.mapperService());
return new TermsFacetCollector(facetName, field, size, context.fieldDataCache(), context.mapperService());
}
}

View File

@ -21,6 +21,8 @@ package org.elasticsearch.test.integration.search.facets;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.index.query.xcontent.QueryBuilders;
import org.elasticsearch.search.facets.statistical.StatisticalFacet;
import org.elasticsearch.search.facets.terms.TermsFacet;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.testng.annotations.AfterClass;
@ -54,7 +56,7 @@ public class SimpleFacetsTests extends AbstractNodesTests {
return client("server1");
}
@Test public void testFieldFacets() throws Exception {
@Test public void testTermsFacets() throws Exception {
try {
client.admin().indices().prepareDelete("test").execute().actionGet();
} catch (Exception e) {
@ -66,7 +68,7 @@ public class SimpleFacetsTests extends AbstractNodesTests {
.field("stag", "111")
.startArray("tag").value("xxx").value("yyy").endArray()
.endObject()).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet();
client.prepareIndex("test", "type1").setSource(jsonBuilder().startObject()
.field("stag", "111")
@ -93,4 +95,42 @@ public class SimpleFacetsTests extends AbstractNodesTests {
assertThat(facet.entries().get(0).term(), equalTo("yyy"));
assertThat(facet.entries().get(0).count(), equalTo(2));
}
@Test public void testStatsFacets() throws Exception {
client.admin().indices().prepareCreate("test").execute().actionGet();
client.prepareIndex("test", "type1").setSource(jsonBuilder().startObject()
.field("num", 1)
.startArray("multi_num").value(1.0).value(2.0f).endArray()
.endObject()).execute().actionGet();
client.admin().indices().prepareFlush().setRefresh(true).execute().actionGet();
client.prepareIndex("test", "type1").setSource(jsonBuilder().startObject()
.field("num", 2)
.startArray("multi_num").value(3.0).value(4.0f).endArray()
.endObject()).execute().actionGet();
client.admin().indices().prepareRefresh().execute().actionGet();
SearchResponse searchResponse = client.prepareSearch()
.setQuery(QueryBuilders.matchAllQuery())
.addFacetStatistical("stats1", "num")
.addFacetStatistical("stats2", "multi_num")
.execute().actionGet();
StatisticalFacet facet = searchResponse.facets().facet(StatisticalFacet.class, "stats1");
assertThat(facet.name(), equalTo(facet.name()));
assertThat(facet.count(), equalTo(2l));
assertThat(facet.total(), equalTo(3d));
assertThat(facet.min(), equalTo(1d));
assertThat(facet.max(), equalTo(2d));
assertThat(facet.mean(), equalTo(1.5d));
facet = searchResponse.facets().facet(StatisticalFacet.class, "stats2");
assertThat(facet.name(), equalTo(facet.name()));
assertThat(facet.count(), equalTo(4l));
assertThat(facet.total(), equalTo(10d));
assertThat(facet.min(), equalTo(1d));
assertThat(facet.max(), equalTo(4d));
assertThat(facet.mean(), equalTo(2.5d));
}
}