Search Facets: Histogram facets to allow to specify different key and value fields

This commit is contained in:
kimchy 2010-06-09 00:50:07 +03:00
parent 5ca050ffcb
commit 64479a11c3
20 changed files with 543 additions and 81 deletions

View File

@ -362,43 +362,85 @@ public class SearchRequestBuilder {
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String keyFieldName, String valueFieldName, long interval) {
facetsBuilder().histogramFacet(name, keyFieldName, valueFieldName, interval);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String fieldName, long interval, @Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacet(name, fieldName, interval, filter);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String keyFieldName, String valueFieldName, long interval, @Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacet(name, keyFieldName, valueFieldName, interval, filter);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
facetsBuilder().histogramFacet(name, fieldName, interval, comparatorType);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
facetsBuilder().histogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacet(name, fieldName, interval, comparatorType, filter);
return this;
}
public SearchRequestBuilder addFacetHistogram(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType, filter);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String fieldName, long interval) {
facetsBuilder().histogramFacetGlobal(name, fieldName, interval);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String keyFieldName, String valueFieldName, long interval) {
facetsBuilder().histogramFacetGlobal(name, keyFieldName, valueFieldName, interval);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String fieldName, long interval, @Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacetGlobal(name, fieldName, interval, filter);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String keyFieldName, String valueFieldName, long interval, @Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacetGlobal(name, keyFieldName, valueFieldName, interval, filter);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
facetsBuilder().histogramFacetGlobal(name, fieldName, interval, comparatorType);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
facetsBuilder().histogramFacetGlobal(name, keyFieldName, valueFieldName, interval, comparatorType);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacetGlobal(name, fieldName, interval, comparatorType, filter);
return this;
}
public SearchRequestBuilder addFacetHistogramGlobal(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
facetsBuilder().histogramFacetGlobal(name, keyFieldName, valueFieldName, interval, comparatorType, filter);
return this;
}
/**
* Adds a field to be highlighted with default fragment size of 100 characters, and
* default number of fragments of 5.

View File

@ -86,6 +86,8 @@ public abstract class NumericFieldData extends FieldData {
return (short) intValue(docId);
}
public abstract double[] doubleValues(int docId);
public abstract void forEachValueInDoc(int docId, DoubleValueInDocProc proc);
public static interface DoubleValueInDocProc {

View File

@ -29,11 +29,11 @@ import org.elasticsearch.util.gnu.trove.TDoubleArrayList;
import java.io.IOException;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public abstract class DoubleFieldData extends NumericFieldData {
static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
public static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
protected final double[] values;
protected final int[] freqs;

View File

@ -23,13 +23,13 @@ import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.util.ThreadLocals;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class MultiValueDoubleFieldData extends DoubleFieldData {
private static final int VALUE_CACHE_SIZE = 100;
private static final int VALUE_CACHE_SIZE = 10;
private static ThreadLocal<ThreadLocals.CleanableValue<double[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
private ThreadLocal<ThreadLocals.CleanableValue<double[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
@Override protected ThreadLocals.CleanableValue<double[][]> initialValue() {
double[][] value = new double[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
@ -75,6 +75,10 @@ public class MultiValueDoubleFieldData extends DoubleFieldData {
}
}
@Override public double[] doubleValues(int docId) {
return values(docId);
}
@Override public double value(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {

View File

@ -22,11 +22,11 @@ package org.elasticsearch.index.field.doubles;
import org.elasticsearch.index.field.FieldDataOptions;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class SingleValueDoubleFieldData extends DoubleFieldData {
private static ThreadLocal<double[]> valuesCache = new ThreadLocal<double[]>() {
private ThreadLocal<double[]> valuesCache = new ThreadLocal<double[]>() {
@Override protected double[] initialValue() {
return new double[1];
}
@ -64,6 +64,10 @@ public class SingleValueDoubleFieldData extends DoubleFieldData {
proc.onValue(docId, values[loc]);
}
@Override public double[] doubleValues(int docId) {
return values(docId);
}
@Override public double value(int docId) {
return values[order[docId]];
}

View File

@ -20,16 +20,27 @@
package org.elasticsearch.index.field.floats;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
import org.elasticsearch.util.ThreadLocals;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class MultiValueFloatFieldData extends FloatFieldData {
private static final int VALUE_CACHE_SIZE = 100;
private static final int VALUE_CACHE_SIZE = 10;
private static ThreadLocal<ThreadLocals.CleanableValue<float[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<float[][]>>() {
private ThreadLocal<ThreadLocals.CleanableValue<double[][]>> doublesValuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
@Override protected ThreadLocals.CleanableValue<double[][]> initialValue() {
double[][] value = new double[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
value[i] = new double[i];
}
return new ThreadLocals.CleanableValue<double[][]>(value);
}
};
private ThreadLocal<ThreadLocals.CleanableValue<float[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<float[][]>>() {
@Override protected ThreadLocals.CleanableValue<float[][]> initialValue() {
float[][] value = new float[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
@ -75,6 +86,23 @@ public class MultiValueFloatFieldData extends FloatFieldData {
}
}
@Override public double[] doubleValues(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] doubles;
if (docOrders.length < VALUE_CACHE_SIZE) {
doubles = doublesValuesCache.get().get()[docOrders.length];
} else {
doubles = new double[docOrders.length];
}
for (int i = 0; i < docOrders.length; i++) {
doubles[i] = values[docOrders[i]];
}
return doubles;
}
@Override public float value(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {

View File

@ -20,13 +20,20 @@
package org.elasticsearch.index.field.floats;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class SingleValueFloatFieldData extends FloatFieldData {
private static ThreadLocal<float[]> valuesCache = new ThreadLocal<float[]>() {
private ThreadLocal<double[]> doublesValuesCache = new ThreadLocal<double[]>() {
@Override protected double[] initialValue() {
return new double[1];
}
};
private ThreadLocal<float[]> valuesCache = new ThreadLocal<float[]>() {
@Override protected float[] initialValue() {
return new float[1];
}
@ -64,6 +71,16 @@ public class SingleValueFloatFieldData extends FloatFieldData {
proc.onValue(docId, values[loc]);
}
@Override public double[] doubleValues(int docId) {
int loc = order[docId];
if (loc == 0) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] ret = doublesValuesCache.get();
ret[0] = values[loc];
return ret;
}
@Override public float value(int docId) {
return values[order[docId]];
}

View File

@ -20,16 +20,27 @@
package org.elasticsearch.index.field.ints;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
import org.elasticsearch.util.ThreadLocals;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class MultiValueIntFieldData extends IntFieldData {
private static final int VALUE_CACHE_SIZE = 100;
private static final int VALUE_CACHE_SIZE = 10;
private static ThreadLocal<ThreadLocals.CleanableValue<int[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<int[][]>>() {
private ThreadLocal<ThreadLocals.CleanableValue<double[][]>> doublesValuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
@Override protected ThreadLocals.CleanableValue<double[][]> initialValue() {
double[][] value = new double[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
value[i] = new double[i];
}
return new ThreadLocals.CleanableValue<double[][]>(value);
}
};
private ThreadLocal<ThreadLocals.CleanableValue<int[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<int[][]>>() {
@Override protected ThreadLocals.CleanableValue<int[][]> initialValue() {
int[][] value = new int[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
@ -75,6 +86,23 @@ public class MultiValueIntFieldData extends IntFieldData {
}
}
@Override public double[] doubleValues(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] doubles;
if (docOrders.length < VALUE_CACHE_SIZE) {
doubles = doublesValuesCache.get().get()[docOrders.length];
} else {
doubles = new double[docOrders.length];
}
for (int i = 0; i < docOrders.length; i++) {
doubles[i] = values[docOrders[i]];
}
return doubles;
}
@Override public int value(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {

View File

@ -20,13 +20,20 @@
package org.elasticsearch.index.field.ints;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class SingleValueIntFieldData extends IntFieldData {
private static ThreadLocal<int[]> valuesCache = new ThreadLocal<int[]>() {
private ThreadLocal<double[]> doublesValuesCache = new ThreadLocal<double[]>() {
@Override protected double[] initialValue() {
return new double[1];
}
};
private ThreadLocal<int[]> valuesCache = new ThreadLocal<int[]>() {
@Override protected int[] initialValue() {
return new int[1];
}
@ -64,6 +71,16 @@ public class SingleValueIntFieldData extends IntFieldData {
proc.onValue(docId, values[loc]);
}
@Override public double[] doubleValues(int docId) {
int loc = order[docId];
if (loc == 0) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] ret = doublesValuesCache.get();
ret[0] = values[loc];
return ret;
}
@Override public int value(int docId) {
return values[order[docId]];
}

View File

@ -20,16 +20,27 @@
package org.elasticsearch.index.field.longs;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
import org.elasticsearch.util.ThreadLocals;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class MultiValueLongFieldData extends LongFieldData {
private static final int VALUE_CACHE_SIZE = 100;
private static final int VALUE_CACHE_SIZE = 10;
private static ThreadLocal<ThreadLocals.CleanableValue<long[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<long[][]>>() {
private ThreadLocal<ThreadLocals.CleanableValue<double[][]>> doublesValuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
@Override protected ThreadLocals.CleanableValue<double[][]> initialValue() {
double[][] value = new double[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
value[i] = new double[i];
}
return new ThreadLocals.CleanableValue<double[][]>(value);
}
};
private ThreadLocal<ThreadLocals.CleanableValue<long[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<long[][]>>() {
@Override protected ThreadLocals.CleanableValue<long[][]> initialValue() {
long[][] value = new long[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
@ -75,6 +86,23 @@ public class MultiValueLongFieldData extends LongFieldData {
}
}
@Override public double[] doubleValues(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] doubles;
if (docOrders.length < VALUE_CACHE_SIZE) {
doubles = doublesValuesCache.get().get()[docOrders.length];
} else {
doubles = new double[docOrders.length];
}
for (int i = 0; i < docOrders.length; i++) {
doubles[i] = values[docOrders[i]];
}
return doubles;
}
@Override public long value(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {

View File

@ -20,13 +20,20 @@
package org.elasticsearch.index.field.longs;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class SingleValueLongFieldData extends LongFieldData {
private static ThreadLocal<long[]> valuesCache = new ThreadLocal<long[]>() {
private ThreadLocal<double[]> doublesValuesCache = new ThreadLocal<double[]>() {
@Override protected double[] initialValue() {
return new double[1];
}
};
private ThreadLocal<long[]> valuesCache = new ThreadLocal<long[]>() {
@Override protected long[] initialValue() {
return new long[1];
}
@ -64,6 +71,16 @@ public class SingleValueLongFieldData extends LongFieldData {
proc.onValue(docId, values[loc]);
}
@Override public double[] doubleValues(int docId) {
int loc = order[docId];
if (loc == 0) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] ret = doublesValuesCache.get();
ret[0] = values[loc];
return ret;
}
@Override public long value(int docId) {
return values[order[docId]];
}

View File

@ -20,16 +20,27 @@
package org.elasticsearch.index.field.shorts;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
import org.elasticsearch.util.ThreadLocals;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class MultiValueShortFieldData extends ShortFieldData {
private static final int VALUE_CACHE_SIZE = 100;
private static final int VALUE_CACHE_SIZE = 10;
private static ThreadLocal<ThreadLocals.CleanableValue<short[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<short[][]>>() {
private ThreadLocal<ThreadLocals.CleanableValue<double[][]>> doublesValuesCache = new ThreadLocal<ThreadLocals.CleanableValue<double[][]>>() {
@Override protected ThreadLocals.CleanableValue<double[][]> initialValue() {
double[][] value = new double[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
value[i] = new double[i];
}
return new ThreadLocals.CleanableValue<double[][]>(value);
}
};
private ThreadLocal<ThreadLocals.CleanableValue<short[][]>> valuesCache = new ThreadLocal<ThreadLocals.CleanableValue<short[][]>>() {
@Override protected ThreadLocals.CleanableValue<short[][]> initialValue() {
short[][] value = new short[VALUE_CACHE_SIZE][];
for (int i = 0; i < value.length; i++) {
@ -75,6 +86,23 @@ public class MultiValueShortFieldData extends ShortFieldData {
}
}
@Override public double[] doubleValues(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] doubles;
if (docOrders.length < VALUE_CACHE_SIZE) {
doubles = doublesValuesCache.get().get()[docOrders.length];
} else {
doubles = new double[docOrders.length];
}
for (int i = 0; i < docOrders.length; i++) {
doubles[i] = values[docOrders[i]];
}
return doubles;
}
@Override public short value(int docId) {
int[] docOrders = order[docId];
if (docOrders == null) {

View File

@ -20,13 +20,20 @@
package org.elasticsearch.index.field.shorts;
import org.elasticsearch.index.field.FieldDataOptions;
import org.elasticsearch.index.field.doubles.DoubleFieldData;
/**
* @author kimchy (Shay Banon)
* @author kimchy (shay.banon)
*/
public class SingleValueShortFieldData extends ShortFieldData {
private static ThreadLocal<short[]> valuesCache = new ThreadLocal<short[]>() {
private ThreadLocal<double[]> doublesValuesCache = new ThreadLocal<double[]>() {
@Override protected double[] initialValue() {
return new double[1];
}
};
private ThreadLocal<short[]> valuesCache = new ThreadLocal<short[]>() {
@Override protected short[] initialValue() {
return new short[1];
}
@ -68,6 +75,16 @@ public class SingleValueShortFieldData extends ShortFieldData {
return values[order[docId]];
}
@Override public double[] doubleValues(int docId) {
int loc = order[docId];
if (loc == 0) {
return DoubleFieldData.EMPTY_DOUBLE_ARRAY;
}
double[] ret = doublesValuesCache.get();
ret[0] = values[loc];
return ret;
}
@Override public short[] values(int docId) {
int loc = order[docId];
if (loc == 0) {

View File

@ -134,44 +134,78 @@ public class SearchSourceFacetsBuilder implements ToXContent {
}
public SearchSourceFacetsBuilder histogramFacet(String name, String fieldName, long interval) {
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.VALUE, null);
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.KEY, null);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String keyFieldName, String valueFieldName, long interval) {
return histogramFacet(name, keyFieldName, valueFieldName, interval, HistogramFacet.ComparatorType.KEY, null);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String fieldName, long interval, @Nullable XContentFilterBuilder filter) {
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.VALUE, filter);
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.KEY, filter);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String keyFieldName, String valueFieldName, long interval, @Nullable XContentFilterBuilder filter) {
return histogramFacet(name, keyFieldName, valueFieldName, interval, HistogramFacet.ComparatorType.KEY, filter);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
return histogramFacet(name, fieldName, interval, comparatorType, null);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType,
public SearchSourceFacetsBuilder histogramFacet(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
return histogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType, null);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String keyFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
return histogramFacet(name, keyFieldName, null, interval, comparatorType, filter);
}
public SearchSourceFacetsBuilder histogramFacet(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
if (histogramFacets == null) {
histogramFacets = newArrayListWithCapacity(2);
}
histogramFacets.add(new BuilderHistogramFacet(name, fieldName, interval, comparatorType, filter, false));
histogramFacets.add(new BuilderHistogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType, filter, false));
return this;
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String fieldName, long interval) {
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.VALUE, null);
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.KEY, null);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String keyFieldName, String valueFieldName, long interval) {
return histogramFacet(name, keyFieldName, valueFieldName, interval, HistogramFacet.ComparatorType.KEY, null);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String fieldName, long interval, @Nullable XContentFilterBuilder filter) {
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.VALUE, filter);
return histogramFacet(name, fieldName, interval, HistogramFacet.ComparatorType.KEY, filter);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String keyFieldName, String valueFieldName, long interval, @Nullable XContentFilterBuilder filter) {
return histogramFacet(name, keyFieldName, valueFieldName, interval, HistogramFacet.ComparatorType.KEY, filter);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
return histogramFacetGlobal(name, fieldName, interval, comparatorType, null);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType) {
return histogramFacetGlobal(name, keyFieldName, valueFieldName, interval, comparatorType, null);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
return histogramFacetGlobal(name, fieldName, null, interval, comparatorType, filter);
}
public SearchSourceFacetsBuilder histogramFacetGlobal(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
@Nullable XContentFilterBuilder filter) {
if (histogramFacets == null) {
histogramFacets = newArrayListWithCapacity(2);
}
histogramFacets.add(new BuilderHistogramFacet(name, fieldName, interval, comparatorType, filter, true));
histogramFacets.add(new BuilderHistogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType, filter, true));
return this;
}
@ -247,7 +281,12 @@ public class SearchSourceFacetsBuilder implements ToXContent {
builder.startObject(histogramFacet.name());
builder.startObject(HistogramFacetCollectorParser.NAME);
builder.field("field", histogramFacet.fieldName());
if (histogramFacet.valueFieldName() != null && !histogramFacet.keyFieldName().equals(histogramFacet.valueFieldName())) {
builder.field("key_field", histogramFacet.keyFieldName());
builder.field("value_field", histogramFacet.valueFieldName());
} else {
builder.field("field", histogramFacet.keyFieldName());
}
builder.field("interval", histogramFacet.interval());
builder.field("comparator", histogramFacet.comparatorType().description());
builder.endObject();
@ -366,20 +405,22 @@ public class SearchSourceFacetsBuilder implements ToXContent {
private static class BuilderHistogramFacet {
private final String name;
private final String fieldName;
private final String keyFieldName;
private final String valueFieldName;
private final long interval;
private final HistogramFacet.ComparatorType comparatorType;
private final XContentFilterBuilder filter;
private final Boolean global;
private BuilderHistogramFacet(String name, String fieldName, long interval, XContentFilterBuilder filter, Boolean global) {
this(name, fieldName, interval, HistogramFacet.ComparatorType.VALUE, filter, global);
private BuilderHistogramFacet(String name, String keyFieldName, String valueFieldName, long interval, XContentFilterBuilder filter, Boolean global) {
this(name, keyFieldName, valueFieldName, interval, HistogramFacet.ComparatorType.KEY, filter, global);
}
private BuilderHistogramFacet(String name, String fieldName, long interval, HistogramFacet.ComparatorType comparatorType,
private BuilderHistogramFacet(String name, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType,
XContentFilterBuilder filter, Boolean global) {
this.name = name;
this.fieldName = fieldName;
this.keyFieldName = keyFieldName;
this.valueFieldName = valueFieldName;
this.interval = interval;
this.comparatorType = comparatorType;
this.filter = filter;
@ -390,8 +431,12 @@ public class SearchSourceFacetsBuilder implements ToXContent {
return name;
}
public String fieldName() {
return fieldName;
public String keyFieldName() {
return keyFieldName;
}
public String valueFieldName() {
return valueFieldName;
}
public long interval() {

View File

@ -32,20 +32,24 @@ import java.util.List;
*/
public interface HistogramFacet extends Facet, Iterable<HistogramFacet.Entry> {
String fieldName();
String keyFieldName();
String getFieldName();
String getKeyFieldName();
String valueFieldName();
String getValueFieldName();
List<Entry> entries();
List<Entry> getEntries();
public static enum ComparatorType {
VALUE((byte) 0, "value", new Comparator<Entry>() {
KEY((byte) 0, "key", new Comparator<Entry>() {
@Override public int compare(Entry o1, Entry o2) {
// really, there should not be two entries with the same value
int i = (int) (o1.value() - o2.value());
int i = (int) (o1.key() - o2.key());
if (i == 0) {
i = System.identityHashCode(o1) - System.identityHashCode(o2);
}
@ -105,7 +109,7 @@ public interface HistogramFacet extends Facet, Iterable<HistogramFacet.Entry> {
public static ComparatorType fromId(byte id) {
if (id == 0) {
return VALUE;
return KEY;
} else if (id == 1) {
return COUNT;
} else if (id == 2) {
@ -115,8 +119,8 @@ public interface HistogramFacet extends Facet, Iterable<HistogramFacet.Entry> {
}
public static ComparatorType fromString(String type) {
if ("value".equals(type)) {
return VALUE;
if ("key".equals(type)) {
return KEY;
} else if ("count".equals(type)) {
return COUNT;
} else if ("total".equals(type)) {
@ -128,22 +132,22 @@ public interface HistogramFacet extends Facet, Iterable<HistogramFacet.Entry> {
public class Entry {
private final long value;
private final long key;
private final long count;
private final double total;
public Entry(long value, long count, double total) {
this.value = value;
public Entry(long key, long count, double total) {
this.key = key;
this.count = count;
this.total = total;
}
public long value() {
return value;
public long key() {
return key;
}
public long getValue() {
return value();
public long getKey() {
return key();
}
public long count() {

View File

@ -36,6 +36,9 @@ import java.io.IOException;
import static org.elasticsearch.index.field.FieldDataOptions.*;
/**
* A histogram facet collector that uses the same field as the key as well as the
* value.
*
* @author kimchy (shay.banon)
*/
public class HistogramFacetCollector extends AbstractFacetCollector {
@ -79,7 +82,11 @@ public class HistogramFacetCollector extends AbstractFacetCollector {
}
@Override public Facet facet() {
return new InternalHistogramFacet(facetName, fieldName, interval, comparatorType, histoProc.counts(), histoProc.totals());
return new InternalHistogramFacet(facetName, fieldName, fieldName, interval, comparatorType, histoProc.counts(), histoProc.totals());
}
public static long bucket(double value, long interval) {
return (((long) (value / interval)) * interval);
}
public static class HistogramProc implements NumericFieldData.DoubleValueInDocProc {
@ -95,7 +102,7 @@ public class HistogramFacetCollector extends AbstractFacetCollector {
}
@Override public void onValue(int docId, double value) {
long bucket = (((long) (value / interval)) * interval);
long bucket = bucket(value, interval);
counts.adjustOrPutValue(bucket, 1, 1);
totals.adjustOrPutValue(bucket, value, value);
}

View File

@ -23,6 +23,7 @@ import org.elasticsearch.search.facets.FacetPhaseExecutionException;
import org.elasticsearch.search.facets.collector.FacetCollector;
import org.elasticsearch.search.facets.collector.FacetCollectorParser;
import org.elasticsearch.search.internal.SearchContext;
import org.elasticsearch.util.TimeValue;
import org.elasticsearch.util.xcontent.XContentParser;
import java.io.IOException;
@ -39,26 +40,36 @@ public class HistogramFacetCollectorParser implements FacetCollectorParser {
}
@Override public FacetCollector parser(String facetName, XContentParser parser, SearchContext context) throws IOException {
String field = null;
String fieldName = null;
String keyField = null;
String valueField = null;
long interval = -1;
HistogramFacet.ComparatorType comparatorType = HistogramFacet.ComparatorType.VALUE;
HistogramFacet.ComparatorType comparatorType = HistogramFacet.ComparatorType.KEY;
XContentParser.Token token;
String fieldName = null;
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();
keyField = parser.text();
} else if ("key_field".equals(fieldName) || "keyField".equals(fieldName)) {
keyField = parser.text();
} else if ("value_field".equals(fieldName) || "valueField".equals(fieldName)) {
valueField = parser.text();
} else if ("interval".equals(fieldName)) {
interval = parser.longValue();
} else if ("time_interval".equals(fieldName)) {
interval = TimeValue.parseTimeValue(parser.text(), null).millis();
} else if ("comparator".equals(fieldName)) {
comparatorType = HistogramFacet.ComparatorType.fromString(parser.text());
}
}
}
if (keyField == null) {
throw new FacetPhaseExecutionException(facetName, "key field is required to be set for histogram facet, either using [field] or using [key_field]");
}
if (interval == -1) {
throw new FacetPhaseExecutionException(facetName, "[interval] is required to be set for histogram facet");
}
@ -66,6 +77,12 @@ public class HistogramFacetCollectorParser implements FacetCollectorParser {
if (interval < 0) {
throw new FacetPhaseExecutionException(facetName, "[interval] is required to be positive for histogram facet");
}
return new HistogramFacetCollector(facetName, field, interval, comparatorType, context.fieldDataCache(), context.mapperService());
if (valueField == null || keyField.equals(valueField)) {
return new HistogramFacetCollector(facetName, keyField, interval, comparatorType, context.fieldDataCache(), context.mapperService());
} else {
// we have a value field, and its different than the key
return new KeyValueHistogramFacetCollector(facetName, keyField, valueField, interval, comparatorType, context.fieldDataCache(), context.mapperService());
}
}
}

View File

@ -46,7 +46,8 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
private String name;
private String fieldName;
private String keyFieldName;
private String valueFieldName;
private long interval;
@ -61,9 +62,10 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
private InternalHistogramFacet() {
}
public InternalHistogramFacet(String name, String fieldName, long interval, ComparatorType comparatorType, TLongLongHashMap counts, TLongDoubleHashMap totals) {
public InternalHistogramFacet(String name, String keyFieldName, String valueFieldName, long interval, ComparatorType comparatorType, TLongLongHashMap counts, TLongDoubleHashMap totals) {
this.name = name;
this.fieldName = fieldName;
this.keyFieldName = keyFieldName;
this.valueFieldName = valueFieldName;
this.interval = interval;
this.comparatorType = comparatorType;
this.counts = counts;
@ -78,12 +80,20 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
return name();
}
@Override public String fieldName() {
return this.fieldName;
@Override public String keyFieldName() {
return this.keyFieldName;
}
@Override public String getFieldName() {
return fieldName();
@Override public String getKeyFieldName() {
return keyFieldName();
}
@Override public String valueFieldName() {
return this.valueFieldName;
}
@Override public String getValueFieldName() {
return valueFieldName();
}
@Override public Type type() {
@ -161,19 +171,20 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
totals = EMPTY_LONG_DOUBLE_MAP;
}
return new InternalHistogramFacet(name, fieldName, interval, comparatorType, counts, totals);
return new InternalHistogramFacet(name, keyFieldName, valueFieldName, interval, comparatorType, counts, totals);
}
@Override public void toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(name);
builder.field("_type", "histogram");
builder.field("_field", fieldName);
builder.field("_key_field", keyFieldName);
builder.field("_value_field", valueFieldName);
builder.field("_comparator", comparatorType.description());
builder.field("_interval", interval);
builder.startArray("entries");
for (Entry entry : computeEntries()) {
builder.startObject();
builder.field("value", entry.value());
builder.field("key", entry.key());
builder.field("count", entry.count());
builder.field("total", entry.total());
builder.field("mean", entry.mean());
@ -191,7 +202,8 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
@Override public void readFrom(StreamInput in) throws IOException {
name = in.readUTF();
fieldName = in.readUTF();
keyFieldName = in.readUTF();
valueFieldName = in.readUTF();
interval = in.readVLong();
comparatorType = ComparatorType.fromId(in.readByte());
@ -212,7 +224,8 @@ public class InternalHistogramFacet implements HistogramFacet, InternalFacet {
@Override public void writeTo(StreamOutput out) throws IOException {
out.writeUTF(name);
out.writeUTF(fieldName);
out.writeUTF(keyFieldName);
out.writeUTF(valueFieldName);
out.writeVLong(interval);
out.writeByte(comparatorType.id());
// optimize the write, since we know we have the same buckets as keys

View File

@ -0,0 +1,131 @@
/*
* 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.histogram;
import org.apache.lucene.index.IndexReader;
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.support.AbstractFacetCollector;
import org.elasticsearch.util.gnu.trove.TLongDoubleHashMap;
import org.elasticsearch.util.gnu.trove.TLongLongHashMap;
import java.io.IOException;
import static org.elasticsearch.index.field.FieldDataOptions.*;
/**
* A histogram facet collector that uses different fields for the key and the value.
*
* @author kimchy (shay.banon)
*/
public class KeyValueHistogramFacetCollector extends AbstractFacetCollector {
private final String keyFieldName;
private final String valueFieldName;
private final long interval;
private final HistogramFacet.ComparatorType comparatorType;
private final FieldDataCache fieldDataCache;
private final FieldData.Type keyFieldDataType;
private NumericFieldData keyFieldData;
private final FieldData.Type valueFieldDataType;
private NumericFieldData valueFieldData;
private final TLongLongHashMap counts = new TLongLongHashMap();
private final TLongDoubleHashMap totals = new TLongDoubleHashMap();
public KeyValueHistogramFacetCollector(String facetName, String keyFieldName, String valueFieldName, long interval, HistogramFacet.ComparatorType comparatorType, FieldDataCache fieldDataCache, MapperService mapperService) {
super(facetName);
this.keyFieldName = keyFieldName;
this.valueFieldName = valueFieldName;
this.interval = interval;
this.comparatorType = comparatorType;
this.fieldDataCache = fieldDataCache;
FieldMapper mapper = mapperService.smartNameFieldMapper(keyFieldName);
if (mapper == null) {
throw new FacetPhaseExecutionException(facetName, "No mapping found for key_field [" + keyFieldName + "]");
}
keyFieldDataType = mapper.fieldDataType();
mapper = mapperService.smartNameFieldMapper(valueFieldName);
if (mapper == null) {
throw new FacetPhaseExecutionException(facetName, "No mapping found for value_field [" + valueFieldName + "]");
}
valueFieldDataType = mapper.fieldDataType();
}
@Override protected void doCollect(int doc) throws IOException {
if (keyFieldData.multiValued()) {
if (valueFieldData.multiValued()) {
// both multi valued, intersect based on the minimum size
double[] keys = keyFieldData.doubleValues(doc);
double[] values = valueFieldData.doubleValues(doc);
int size = Math.min(keys.length, values.length);
for (int i = 0; i < size; i++) {
long bucket = HistogramFacetCollector.bucket(keys[i], interval);
counts.adjustOrPutValue(bucket, 1, 1);
totals.adjustOrPutValue(bucket, values[i], values[i]);
}
} else {
// key multi valued, value is a single value
double value = valueFieldData.doubleValue(doc);
for (double key : keyFieldData.doubleValues(doc)) {
long bucket = HistogramFacetCollector.bucket(key, interval);
counts.adjustOrPutValue(bucket, 1, 1);
totals.adjustOrPutValue(bucket, value, value);
}
}
} else {
// single key value, compute the bucket once
long bucket = HistogramFacetCollector.bucket(keyFieldData.doubleValue(doc), interval);
if (valueFieldData.multiValued()) {
for (double value : valueFieldData.doubleValues(doc)) {
counts.adjustOrPutValue(bucket, 1, 1);
totals.adjustOrPutValue(bucket, value, value);
}
} else {
// both key and value are not multi valued
double value = valueFieldData.doubleValue(doc);
counts.adjustOrPutValue(bucket, 1, 1);
totals.adjustOrPutValue(bucket, value, value);
}
}
}
@Override protected void doSetNextReader(IndexReader reader, int docBase) throws IOException {
keyFieldData = (NumericFieldData) fieldDataCache.cache(keyFieldDataType, reader, keyFieldName, fieldDataOptions().withFreqs(false));
valueFieldData = (NumericFieldData) fieldDataCache.cache(valueFieldDataType, reader, valueFieldName, fieldDataOptions().withFreqs(false));
}
@Override public Facet facet() {
return new InternalHistogramFacet(facetName, keyFieldName, valueFieldName, interval, comparatorType, counts, totals);
}
}

View File

@ -186,16 +186,17 @@ public class SimpleFacetsTests extends AbstractNodesTests {
.setQuery(QueryBuilders.matchAllQuery())
.addFacetHistogram("stats1", "num", 100)
.addFacetHistogram("stats2", "multi_num", 10)
.addFacetHistogram("stats3", "num", "multi_num", 100)
.execute().actionGet();
HistogramFacet facet = searchResponse.facets().facet(HistogramFacet.class, "stats1");
assertThat(facet.name(), equalTo("stats1"));
assertThat(facet.entries().size(), equalTo(2));
assertThat(facet.entries().get(0).value(), equalTo(1000l));
assertThat(facet.entries().get(0).key(), equalTo(1000l));
assertThat(facet.entries().get(0).count(), equalTo(2l));
assertThat(facet.entries().get(0).total(), equalTo(2120d));
assertThat(facet.entries().get(0).mean(), equalTo(1060d));
assertThat(facet.entries().get(1).value(), equalTo(1100l));
assertThat(facet.entries().get(1).key(), equalTo(1100l));
assertThat(facet.entries().get(1).count(), equalTo(1l));
assertThat(facet.entries().get(1).total(), equalTo(1175d));
assertThat(facet.entries().get(1).mean(), equalTo(1175d));
@ -203,17 +204,29 @@ public class SimpleFacetsTests extends AbstractNodesTests {
facet = searchResponse.facets().facet(HistogramFacet.class, "stats2");
assertThat(facet.name(), equalTo("stats2"));
assertThat(facet.entries().size(), equalTo(3));
assertThat(facet.entries().get(0).value(), equalTo(10l));
assertThat(facet.entries().get(0).key(), equalTo(10l));
assertThat(facet.entries().get(0).count(), equalTo(3l));
assertThat(facet.entries().get(0).total(), equalTo(45d));
assertThat(facet.entries().get(0).mean(), equalTo(15d));
assertThat(facet.entries().get(1).value(), equalTo(20l));
assertThat(facet.entries().get(1).key(), equalTo(20l));
assertThat(facet.entries().get(1).count(), equalTo(2l));
assertThat(facet.entries().get(1).total(), equalTo(48d));
assertThat(facet.entries().get(1).mean(), equalTo(24d));
assertThat(facet.entries().get(2).value(), equalTo(30l));
assertThat(facet.entries().get(2).key(), equalTo(30l));
assertThat(facet.entries().get(2).count(), equalTo(1l));
assertThat(facet.entries().get(2).total(), equalTo(31d));
assertThat(facet.entries().get(2).mean(), equalTo(31d));
facet = searchResponse.facets().facet(HistogramFacet.class, "stats3");
assertThat(facet.name(), equalTo("stats3"));
assertThat(facet.entries().size(), equalTo(2));
assertThat(facet.entries().get(0).key(), equalTo(1000l));
assertThat(facet.entries().get(0).count(), equalTo(4l));
assertThat(facet.entries().get(0).total(), equalTo(82d));
assertThat(facet.entries().get(0).mean(), equalTo(20.5d));
assertThat(facet.entries().get(1).key(), equalTo(1100l));
assertThat(facet.entries().get(1).count(), equalTo(2l));
assertThat(facet.entries().get(1).total(), equalTo(42d));
assertThat(facet.entries().get(1).mean(), equalTo(21d));
}
}