mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-03-27 18:38:41 +00:00
numeric statistical facets support
This commit is contained in:
parent
57a21bedd2
commit
8aeb589a42
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -62,7 +62,7 @@ public class MultiValueStringFieldData extends StringFieldData {
|
||||
return;
|
||||
}
|
||||
for (int docOrder : docOrders) {
|
||||
proc.onValue(values[docOrder], docId);
|
||||
proc.onValue(docId, values[docOrder]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 + "]");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 + "]");
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user