mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-26 06:46:10 +00:00
field stats: added index constraints
Field stats index constraints allows to omit all field stats for indices that don't match with the constraint. An index constraint can exclude indices' field stats based on the `min_value` and `max_value` statistic. This option is only useful if the `level` option is set to `indices`. For example index constraints can be useful to find out the min and max value of a particular property of your data in a time based scenario. The following request only returns field stats for the `answer_count` property for indices holding questions created in the year 2014: curl -XPOST 'http://localhost:9200/_field_stats?level=indices' -d '{ "fields" : ["answer_count"] <1> "index_constraints" : { <2> "creation_date" : { <3> "min_value" : { <4> "gte" : "2014-01-01T00:00:00.000Z", }, "max_value" : { "lt" : "2015-01-01T00:00:00.000Z" } } } }' Closes #11187
This commit is contained in:
parent
3cf78669f2
commit
ef9d70b9b3
@ -31,7 +31,7 @@ import org.elasticsearch.common.xcontent.XContentBuilderString;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public abstract class FieldStats<T> implements Streamable, ToXContent {
|
public abstract class FieldStats<T extends Comparable<T>> implements Streamable, ToXContent {
|
||||||
|
|
||||||
private byte type;
|
private byte type;
|
||||||
private long maxDoc;
|
private long maxDoc;
|
||||||
@ -120,6 +120,12 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
*/
|
*/
|
||||||
public abstract String getMaxValue();
|
public abstract String getMaxValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param value The string to be parsed
|
||||||
|
* @return The concrete object represented by the string argument
|
||||||
|
*/
|
||||||
|
protected abstract T valueOf(String value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the provided stats into this stats instance.
|
* Merges the provided stats into this stats instance.
|
||||||
*/
|
*/
|
||||||
@ -142,6 +148,34 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return <code>true</code> if this instance matches with the provided index constraint, otherwise <code>false</code> is returned
|
||||||
|
*/
|
||||||
|
public boolean match(IndexConstraint constraint) {
|
||||||
|
int cmp;
|
||||||
|
T value = valueOf(constraint.getValue());
|
||||||
|
if (constraint.getProperty() == IndexConstraint.Property.MIN) {
|
||||||
|
cmp = minValue.compareTo(value);
|
||||||
|
} else if (constraint.getProperty() == IndexConstraint.Property.MAX) {
|
||||||
|
cmp = maxValue.compareTo(value);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported property [" + constraint.getProperty() + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (constraint.getComparison()) {
|
||||||
|
case GT:
|
||||||
|
return cmp > 0;
|
||||||
|
case GTE:
|
||||||
|
return cmp >= 0;
|
||||||
|
case LT:
|
||||||
|
return cmp < 0;
|
||||||
|
case LTE:
|
||||||
|
return cmp <= 0;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unsupported comparison [" + constraint.getComparison() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
builder.startObject();
|
builder.startObject();
|
||||||
@ -210,6 +244,11 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
this.maxValue = Math.max(other.maxValue, maxValue);
|
this.maxValue = Math.max(other.maxValue, maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected java.lang.Long valueOf(String value) {
|
||||||
|
return java.lang.Long.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
@ -255,6 +294,11 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
this.maxValue = Math.max(other.maxValue, maxValue);
|
this.maxValue = Math.max(other.maxValue, maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected java.lang.Float valueOf(String value) {
|
||||||
|
return java.lang.Float.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
@ -300,6 +344,11 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
this.maxValue = Math.max(other.maxValue, maxValue);
|
this.maxValue = Math.max(other.maxValue, maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected java.lang.Double valueOf(String value) {
|
||||||
|
return java.lang.Double.valueOf(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
@ -349,6 +398,11 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected BytesRef valueOf(String value) {
|
||||||
|
return new BytesRef(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void toInnerXContent(XContentBuilder builder) throws IOException {
|
protected void toInnerXContent(XContentBuilder builder) throws IOException {
|
||||||
builder.field(Fields.MIN_VALUE, getMinValue());
|
builder.field(Fields.MIN_VALUE, getMinValue());
|
||||||
@ -393,6 +447,11 @@ public abstract class FieldStats<T> implements Streamable, ToXContent {
|
|||||||
return dateFormatter.printer().print(maxValue);
|
return dateFormatter.printer().print(maxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected java.lang.Long valueOf(String value) {
|
||||||
|
return dateFormatter.parser().parseMillis(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void toInnerXContent(XContentBuilder builder) throws IOException {
|
protected void toInnerXContent(XContentBuilder builder) throws IOException {
|
||||||
builder.field(Fields.MIN_VALUE, getMinValue());
|
builder.field(Fields.MIN_VALUE, getMinValue());
|
||||||
|
@ -22,10 +22,17 @@ package org.elasticsearch.action.fieldstats;
|
|||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
import org.elasticsearch.action.ValidateActions;
|
import org.elasticsearch.action.ValidateActions;
|
||||||
import org.elasticsearch.action.support.broadcast.BroadcastRequest;
|
import org.elasticsearch.action.support.broadcast.BroadcastRequest;
|
||||||
|
import org.elasticsearch.common.Strings;
|
||||||
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser.Token;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@ -33,17 +40,113 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||||||
|
|
||||||
public final static String DEFAULT_LEVEL = "cluster";
|
public final static String DEFAULT_LEVEL = "cluster";
|
||||||
|
|
||||||
private String[] fields;
|
private String[] fields = Strings.EMPTY_ARRAY;
|
||||||
private String level = DEFAULT_LEVEL;
|
private String level = DEFAULT_LEVEL;
|
||||||
|
private IndexConstraint[] indexConstraints = new IndexConstraint[0];
|
||||||
|
|
||||||
public String[] fields() {
|
public String[] getFields() {
|
||||||
return fields;
|
return fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void fields(String[] fields) {
|
public void setFields(String[] fields) {
|
||||||
|
if (fields == null) {
|
||||||
|
throw new NullPointerException("specified fields can't be null");
|
||||||
|
}
|
||||||
this.fields = fields;
|
this.fields = fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IndexConstraint[] getIndexConstraints() {
|
||||||
|
return indexConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIndexConstraints(IndexConstraint[] indexConstraints) {
|
||||||
|
if (indexConstraints == null) {
|
||||||
|
throw new NullPointerException("specified index_contraints can't be null");
|
||||||
|
}
|
||||||
|
this.indexConstraints = indexConstraints;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void source(BytesReference content) throws IOException {
|
||||||
|
List<IndexConstraint> indexConstraints = new ArrayList<>();
|
||||||
|
List<String> fields = new ArrayList<>();
|
||||||
|
try (XContentParser parser = XContentHelper.createParser(content)) {
|
||||||
|
String fieldName = null;
|
||||||
|
Token token = parser.nextToken();
|
||||||
|
assert token == Token.START_OBJECT;
|
||||||
|
for (token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
|
||||||
|
switch (token) {
|
||||||
|
case FIELD_NAME:
|
||||||
|
fieldName = parser.currentName();
|
||||||
|
break;
|
||||||
|
case START_OBJECT:
|
||||||
|
if ("index_constraints".equals(fieldName)) {
|
||||||
|
parseIndexContraints(indexConstraints, parser);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unknown field [" + fieldName + "]");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case START_ARRAY:
|
||||||
|
if ("fields".equals(fieldName)) {
|
||||||
|
while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
|
||||||
|
if (token.isValue()) {
|
||||||
|
fields.add(parser.text());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + token + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unknown field [" + fieldName + "]");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + token + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.fields = fields.toArray(new String[fields.size()]);
|
||||||
|
this.indexConstraints = indexConstraints.toArray(new IndexConstraint[indexConstraints.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseIndexContraints(List<IndexConstraint> indexConstraints, XContentParser parser) throws IOException {
|
||||||
|
Token token = parser.currentToken();
|
||||||
|
assert token == Token.START_OBJECT;
|
||||||
|
String field = null;
|
||||||
|
String currentName = null;
|
||||||
|
for (token = parser.nextToken(); token != Token.END_OBJECT; token = parser.nextToken()) {
|
||||||
|
if (token == Token.FIELD_NAME) {
|
||||||
|
field = currentName = parser.currentName();
|
||||||
|
} else if (token == Token.START_OBJECT) {
|
||||||
|
for (Token fieldToken = parser.nextToken(); fieldToken != Token.END_OBJECT; fieldToken = parser.nextToken()) {
|
||||||
|
if (fieldToken == Token.FIELD_NAME) {
|
||||||
|
currentName = parser.currentName();
|
||||||
|
} else if (fieldToken == Token.START_OBJECT) {
|
||||||
|
IndexConstraint.Property property = IndexConstraint.Property.parse(currentName);
|
||||||
|
Token propertyToken = parser.nextToken();
|
||||||
|
if (propertyToken != Token.FIELD_NAME) {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + propertyToken + "]");
|
||||||
|
}
|
||||||
|
IndexConstraint.Comparison comparison = IndexConstraint.Comparison.parse(parser.currentName());
|
||||||
|
propertyToken = parser.nextToken();
|
||||||
|
if (propertyToken.isValue() == false) {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + propertyToken + "]");
|
||||||
|
}
|
||||||
|
String value = parser.text();
|
||||||
|
indexConstraints.add(new IndexConstraint(field, property, comparison, value));
|
||||||
|
|
||||||
|
propertyToken = parser.nextToken();
|
||||||
|
if (propertyToken != Token.END_OBJECT) {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + propertyToken + "]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + fieldToken + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unexpected token [" + token + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String level() {
|
public String level() {
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
@ -58,6 +161,9 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||||||
if ("cluster".equals(level) == false && "indices".equals(level) == false) {
|
if ("cluster".equals(level) == false && "indices".equals(level) == false) {
|
||||||
validationException = ValidateActions.addValidationError("invalid level option [" + level + "]", validationException);
|
validationException = ValidateActions.addValidationError("invalid level option [" + level + "]", validationException);
|
||||||
}
|
}
|
||||||
|
if (fields == null || fields.length == 0) {
|
||||||
|
validationException = ValidateActions.addValidationError("no fields specified", validationException);
|
||||||
|
}
|
||||||
return validationException;
|
return validationException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +171,11 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||||||
public void readFrom(StreamInput in) throws IOException {
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
super.readFrom(in);
|
super.readFrom(in);
|
||||||
fields = in.readStringArray();
|
fields = in.readStringArray();
|
||||||
|
int size = in.readVInt();
|
||||||
|
indexConstraints = new IndexConstraint[size];
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
indexConstraints[i] = new IndexConstraint(in);
|
||||||
|
}
|
||||||
level = in.readString();
|
level = in.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +183,14 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||||||
public void writeTo(StreamOutput out) throws IOException {
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
super.writeTo(out);
|
super.writeTo(out);
|
||||||
out.writeStringArrayNullable(fields);
|
out.writeStringArrayNullable(fields);
|
||||||
|
out.writeVInt(indexConstraints.length);
|
||||||
|
for (IndexConstraint indexConstraint : indexConstraints) {
|
||||||
|
out.writeString(indexConstraint.getField());
|
||||||
|
out.writeByte(indexConstraint.getProperty().getId());
|
||||||
|
out.writeByte(indexConstraint.getComparison().getId());
|
||||||
|
out.writeString(indexConstraint.getValue());
|
||||||
|
}
|
||||||
out.writeString(level);
|
out.writeString(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,12 @@ public class FieldStatsRequestBuilder extends BroadcastOperationRequestBuilder<F
|
|||||||
}
|
}
|
||||||
|
|
||||||
public FieldStatsRequestBuilder setFields(String... fields) {
|
public FieldStatsRequestBuilder setFields(String... fields) {
|
||||||
request().fields(fields);
|
request().setFields(fields);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldStatsRequestBuilder setIndexContraints(IndexConstraint... fields) {
|
||||||
|
request().setIndexConstraints(fields);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ import org.elasticsearch.common.io.stream.StreamOutput;
|
|||||||
import org.elasticsearch.index.shard.ShardId;
|
import org.elasticsearch.index.shard.ShardId;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@ -37,7 +40,12 @@ public class FieldStatsShardRequest extends BroadcastShardRequest {
|
|||||||
|
|
||||||
public FieldStatsShardRequest(ShardId shardId, FieldStatsRequest request) {
|
public FieldStatsShardRequest(ShardId shardId, FieldStatsRequest request) {
|
||||||
super(shardId, request);
|
super(shardId, request);
|
||||||
this.fields = request.fields();
|
Set<String> fields = new HashSet<>();
|
||||||
|
fields.addAll(Arrays.asList(request.getFields()));
|
||||||
|
for (IndexConstraint indexConstraint : request.getIndexConstraints()) {
|
||||||
|
fields.add(indexConstraint.getField());
|
||||||
|
}
|
||||||
|
this.fields = fields.toArray(new String[fields.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] getFields() {
|
public String[] getFields() {
|
||||||
|
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch 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.action.fieldstats;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class IndexConstraint {
|
||||||
|
|
||||||
|
private final String field;
|
||||||
|
private final Property property;
|
||||||
|
private final Comparison comparison;
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
IndexConstraint(StreamInput input) throws IOException {
|
||||||
|
this.field = input.readString();
|
||||||
|
this.property = Property.read(input.readByte());
|
||||||
|
this.comparison = Comparison.read(input.readByte());
|
||||||
|
this.value = input.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IndexConstraint(String field, Property property, Comparison comparison, String value) {
|
||||||
|
this.field = field;
|
||||||
|
this.property = property;
|
||||||
|
this.comparison = comparison;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getField() {
|
||||||
|
return field;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Comparison getComparison() {
|
||||||
|
return comparison;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Property getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Property {
|
||||||
|
|
||||||
|
MIN((byte) 0),
|
||||||
|
MAX((byte) 1);
|
||||||
|
|
||||||
|
private final byte id;
|
||||||
|
|
||||||
|
Property(byte id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property read(byte id) {
|
||||||
|
switch (id) {
|
||||||
|
case 0:
|
||||||
|
return MIN;
|
||||||
|
case 1:
|
||||||
|
return MAX;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown property [" + id + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Property parse(String value) {
|
||||||
|
value = value.toLowerCase(Locale.ROOT);
|
||||||
|
switch (value) {
|
||||||
|
case "min_value":
|
||||||
|
return MIN;
|
||||||
|
case "max_value":
|
||||||
|
return MAX;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown property [" + value + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Comparison {
|
||||||
|
|
||||||
|
LT((byte) 0),
|
||||||
|
LTE((byte) 1),
|
||||||
|
GT((byte) 2),
|
||||||
|
GTE((byte) 3);
|
||||||
|
|
||||||
|
private final byte id;
|
||||||
|
|
||||||
|
Comparison(byte id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Comparison read(byte id) {
|
||||||
|
switch (id) {
|
||||||
|
case 0:
|
||||||
|
return LT;
|
||||||
|
case 1:
|
||||||
|
return LTE;
|
||||||
|
case 2:
|
||||||
|
return GT;
|
||||||
|
case 3:
|
||||||
|
return GTE;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown comparison [" + id + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Comparison parse(String value) {
|
||||||
|
value = value.toLowerCase(Locale.ROOT);
|
||||||
|
switch (value) {
|
||||||
|
case "lt":
|
||||||
|
return LT;
|
||||||
|
case "lte":
|
||||||
|
return LTE;
|
||||||
|
case "gt":
|
||||||
|
return GT;
|
||||||
|
case "gte":
|
||||||
|
return GTE;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown comparison [" + value + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -47,10 +47,7 @@ import org.elasticsearch.threadpool.ThreadPool;
|
|||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicReferenceArray;
|
import java.util.concurrent.atomic.AtomicReferenceArray;
|
||||||
|
|
||||||
public class TransportFieldStatsTransportAction extends TransportBroadcastAction<FieldStatsRequest, FieldStatsResponse, FieldStatsShardRequest, FieldStatsShardResponse> {
|
public class TransportFieldStatsTransportAction extends TransportBroadcastAction<FieldStatsRequest, FieldStatsResponse, FieldStatsShardRequest, FieldStatsShardResponse> {
|
||||||
@ -86,7 +83,7 @@ public class TransportFieldStatsTransportAction extends TransportBroadcastAction
|
|||||||
} else if ("indices".equals(request.level())) {
|
} else if ("indices".equals(request.level())) {
|
||||||
indexName = shardResponse.getIndex();
|
indexName = shardResponse.getIndex();
|
||||||
} else {
|
} else {
|
||||||
// should already have been catched by the FieldStatsRequest#validate(...)
|
// should already have been caught by the FieldStatsRequest#validate(...)
|
||||||
throw new IllegalArgumentException("Illegal level option [" + request.level() + "]");
|
throw new IllegalArgumentException("Illegal level option [" + request.level() + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +101,6 @@ public class TransportFieldStatsTransportAction extends TransportBroadcastAction
|
|||||||
"trying to merge the field stats of field [" + entry.getKey() + "] from index [" + shardResponse.getIndex() + "] but the field type is incompatible, try to set the 'level' option to 'indices'"
|
"trying to merge the field stats of field [" + entry.getKey() + "] from index [" + shardResponse.getIndex() + "] but the field type is incompatible, try to set the 'level' option to 'indices'"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
existing.append(entry.getValue());
|
existing.append(entry.getValue());
|
||||||
} else {
|
} else {
|
||||||
indexMergedFieldStats.put(entry.getKey(), entry.getValue());
|
indexMergedFieldStats.put(entry.getKey(), entry.getValue());
|
||||||
@ -112,6 +108,28 @@ public class TransportFieldStatsTransportAction extends TransportBroadcastAction
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.getIndexConstraints().length != 0) {
|
||||||
|
Set<String> fieldStatFields = new HashSet<>(Arrays.asList(request.getFields()));
|
||||||
|
for (IndexConstraint indexConstraint : request.getIndexConstraints()) {
|
||||||
|
Iterator<Map.Entry<String, Map<String, FieldStats>>> iterator = indicesMergedFieldStats.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<String, Map<String, FieldStats>> entry = iterator.next();
|
||||||
|
FieldStats indexConstraintFieldStats = entry.getValue().get(indexConstraint.getField());
|
||||||
|
if (indexConstraintFieldStats.match(indexConstraint)) {
|
||||||
|
// If the field stats didn't occur in the list of fields in the original request we need to remove the
|
||||||
|
// field stats, because it was never requested and was only needed to validate the index constraint
|
||||||
|
if (fieldStatFields.contains(indexConstraint.getField()) == false) {
|
||||||
|
entry.getValue().remove(indexConstraint.getField());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The index constraint didn't match, so we remove all the field stats of the index we're checking
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new FieldStatsResponse(shardsResponses.length(), successfulShards, failedShards, shardFailures, indicesMergedFieldStats);
|
return new FieldStatsResponse(shardsResponses.length(), successfulShards, failedShards, shardFailures, indicesMergedFieldStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,7 @@ import org.elasticsearch.common.inject.Inject;
|
|||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.rest.*;
|
import org.elasticsearch.rest.*;
|
||||||
|
import org.elasticsearch.rest.action.support.RestActions;
|
||||||
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
import org.elasticsearch.rest.action.support.RestBuilderListener;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -51,12 +52,20 @@ public class RestFieldStatsAction extends BaseRestHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) {
|
public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) throws Exception {
|
||||||
|
if (RestActions.hasBodyContent(request) && request.hasParam("fields")) {
|
||||||
|
throw new IllegalArgumentException("can't specify a request body and [fields] request parameter, either specify a request body or the [fields] request parameter");
|
||||||
|
}
|
||||||
|
|
||||||
final FieldStatsRequest fieldStatsRequest = new FieldStatsRequest();
|
final FieldStatsRequest fieldStatsRequest = new FieldStatsRequest();
|
||||||
fieldStatsRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
|
fieldStatsRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
|
||||||
fieldStatsRequest.indicesOptions(IndicesOptions.fromRequest(request, fieldStatsRequest.indicesOptions()));
|
fieldStatsRequest.indicesOptions(IndicesOptions.fromRequest(request, fieldStatsRequest.indicesOptions()));
|
||||||
fieldStatsRequest.fields(Strings.splitStringByCommaToArray(request.param("fields")));
|
|
||||||
fieldStatsRequest.level(request.param("level", FieldStatsRequest.DEFAULT_LEVEL));
|
fieldStatsRequest.level(request.param("level", FieldStatsRequest.DEFAULT_LEVEL));
|
||||||
|
if (RestActions.hasBodyContent(request)) {
|
||||||
|
fieldStatsRequest.source(RestActions.getRestContent(request));
|
||||||
|
} else {
|
||||||
|
fieldStatsRequest.setFields(Strings.splitStringByCommaToArray(request.param("fields")));
|
||||||
|
}
|
||||||
|
|
||||||
client.fieldStats(fieldStatsRequest, new RestBuilderListener<FieldStatsResponse>(channel) {
|
client.fieldStats(fieldStatsRequest, new RestBuilderListener<FieldStatsResponse>(channel) {
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to Elasticsearch under one or more contributor
|
||||||
|
* license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright
|
||||||
|
* ownership. Elasticsearch 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.action.fieldstats;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.bytes.BytesArray;
|
||||||
|
import org.elasticsearch.common.io.Streams;
|
||||||
|
import org.elasticsearch.test.ElasticsearchTestCase;
|
||||||
|
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Comparison.*;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MAX;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MIN;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
public class FieldStatsRequestTest extends ElasticsearchTestCase {
|
||||||
|
|
||||||
|
public void testFieldsParsing() throws Exception {
|
||||||
|
byte[] data = Streams.copyToBytesFromClasspath("/org/elasticsearch/action/fieldstats/fieldstats-index-constraints-request.json");
|
||||||
|
FieldStatsRequest request = new FieldStatsRequest();
|
||||||
|
request.source(new BytesArray(data));
|
||||||
|
|
||||||
|
assertThat(request.getFields().length, equalTo(5));
|
||||||
|
assertThat(request.getFields()[0], equalTo("field1"));
|
||||||
|
assertThat(request.getFields()[1], equalTo("field2"));
|
||||||
|
assertThat(request.getFields()[2], equalTo("field3"));
|
||||||
|
assertThat(request.getFields()[3], equalTo("field4"));
|
||||||
|
assertThat(request.getFields()[4], equalTo("field5"));
|
||||||
|
|
||||||
|
assertThat(request.getIndexConstraints().length, equalTo(6));
|
||||||
|
assertThat(request.getIndexConstraints()[0].getField(), equalTo("field2"));
|
||||||
|
assertThat(request.getIndexConstraints()[0].getValue(), equalTo("9"));
|
||||||
|
assertThat(request.getIndexConstraints()[0].getProperty(), equalTo(MAX));
|
||||||
|
assertThat(request.getIndexConstraints()[0].getComparison(), equalTo(GTE));
|
||||||
|
assertThat(request.getIndexConstraints()[1].getField(), equalTo("field3"));
|
||||||
|
assertThat(request.getIndexConstraints()[1].getValue(), equalTo("5"));
|
||||||
|
assertThat(request.getIndexConstraints()[1].getProperty(), equalTo(MIN));
|
||||||
|
assertThat(request.getIndexConstraints()[1].getComparison(), equalTo(GT));
|
||||||
|
assertThat(request.getIndexConstraints()[2].getField(), equalTo("field4"));
|
||||||
|
assertThat(request.getIndexConstraints()[2].getValue(), equalTo("a"));
|
||||||
|
assertThat(request.getIndexConstraints()[2].getProperty(), equalTo(MIN));
|
||||||
|
assertThat(request.getIndexConstraints()[2].getComparison(), equalTo(GTE));
|
||||||
|
assertThat(request.getIndexConstraints()[3].getField(), equalTo("field4"));
|
||||||
|
assertThat(request.getIndexConstraints()[3].getValue(), equalTo("g"));
|
||||||
|
assertThat(request.getIndexConstraints()[3].getProperty(), equalTo(MAX));
|
||||||
|
assertThat(request.getIndexConstraints()[3].getComparison(), equalTo(LTE));
|
||||||
|
assertThat(request.getIndexConstraints()[4].getField(), equalTo("field5"));
|
||||||
|
assertThat(request.getIndexConstraints()[4].getValue(), equalTo("2"));
|
||||||
|
assertThat(request.getIndexConstraints()[4].getProperty(), equalTo(MAX));
|
||||||
|
assertThat(request.getIndexConstraints()[4].getComparison(), equalTo(GT));
|
||||||
|
assertThat(request.getIndexConstraints()[5].getField(), equalTo("field5"));
|
||||||
|
assertThat(request.getIndexConstraints()[5].getValue(), equalTo("9"));
|
||||||
|
assertThat(request.getIndexConstraints()[5].getProperty(), equalTo(MAX));
|
||||||
|
assertThat(request.getIndexConstraints()[5].getComparison(), equalTo(LT));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -22,18 +22,19 @@ package org.elasticsearch.fieldstats;
|
|||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
import org.elasticsearch.action.fieldstats.FieldStats;
|
import org.elasticsearch.action.fieldstats.FieldStats;
|
||||||
import org.elasticsearch.action.fieldstats.FieldStatsResponse;
|
import org.elasticsearch.action.fieldstats.FieldStatsResponse;
|
||||||
|
import org.elasticsearch.action.fieldstats.IndexConstraint;
|
||||||
import org.elasticsearch.action.index.IndexRequestBuilder;
|
import org.elasticsearch.action.index.IndexRequestBuilder;
|
||||||
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
import org.elasticsearch.test.ElasticsearchIntegrationTest;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
|
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Comparison.*;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MAX;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MIN;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllSuccessful;
|
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAllSuccessful;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.*;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
@ -205,7 +206,118 @@ public class FieldStatsIntegrationTests extends ElasticsearchIntegrationTest {
|
|||||||
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMaxValue(), equalTo("b"));
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMaxValue(), equalTo("b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void indexRange(String index, long from, long to) throws ExecutionException, InterruptedException {
|
public void testFieldStatsFiltering() throws Exception {
|
||||||
|
assertAcked(prepareCreate("test1").addMapping(
|
||||||
|
"test", "value", "type=long"
|
||||||
|
));
|
||||||
|
assertAcked(prepareCreate("test2").addMapping(
|
||||||
|
"test", "value", "type=long"
|
||||||
|
));
|
||||||
|
assertAcked(prepareCreate("test3").addMapping(
|
||||||
|
"test", "value", "type=long"
|
||||||
|
));
|
||||||
|
ensureGreen("test1", "test2", "test3");
|
||||||
|
|
||||||
|
indexRange("test1", -10, 100);
|
||||||
|
indexRange("test2", 101, 200);
|
||||||
|
indexRange("test3", 201, 300);
|
||||||
|
|
||||||
|
FieldStatsResponse response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "200"), new IndexConstraint("value", MAX , LTE, "300"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMinValue(), equalTo(Long.toString(201)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMaxValue(), equalTo(Long.toString(300)));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MAX, LTE, "200"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo(Long.toString(-10)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMaxValue(), equalTo(Long.toString(100)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo(Long.toString(101)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMaxValue(), equalTo(Long.toString(200)));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "100"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo(Long.toString(101)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMaxValue(), equalTo(Long.toString(200)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMinValue(), equalTo(Long.toString(201)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMaxValue(), equalTo(Long.toString(300)));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "-20"), new IndexConstraint("value", MAX, LT, "-10"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "-100"), new IndexConstraint("value", MAX, LTE, "-20"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "100"), new IndexConstraint("value", MAX, LTE, "200"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo(Long.toString(101)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMaxValue(), equalTo(Long.toString(200)));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "150"), new IndexConstraint("value", MAX, LTE, "300"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertAllSuccessful(response);
|
||||||
|
assertThat(response.getAllFieldStats(), nullValue());
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMinValue(), equalTo(Long.toString(201)));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test3").get("value").getMaxValue(), equalTo(Long.toString(300)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testIncompatibleFilter() throws Exception {
|
||||||
|
assertAcked(prepareCreate("test1").addMapping(
|
||||||
|
"test", "value", "type=long"
|
||||||
|
));
|
||||||
|
indexRange("test1", -10, 100);
|
||||||
|
try {
|
||||||
|
client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MAX, LTE, "abc"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
fail("exception should have been thrown, because value abc is incompatible");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e.getMessage(), equalTo("For input string: \"abc\""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void indexRange(String index, long from, long to) throws Exception {
|
||||||
List<IndexRequestBuilder> requests = new ArrayList<>();
|
List<IndexRequestBuilder> requests = new ArrayList<>();
|
||||||
for (long value = from; value <= to; value++) {
|
for (long value = from; value <= to; value++) {
|
||||||
requests.add(client().prepareIndex(index, "test").setSource("value", value));
|
requests.add(client().prepareIndex(index, "test").setSource("value", value));
|
||||||
|
@ -21,6 +21,7 @@ package org.elasticsearch.fieldstats;
|
|||||||
|
|
||||||
import org.elasticsearch.action.fieldstats.FieldStats;
|
import org.elasticsearch.action.fieldstats.FieldStats;
|
||||||
import org.elasticsearch.action.fieldstats.FieldStatsResponse;
|
import org.elasticsearch.action.fieldstats.FieldStatsResponse;
|
||||||
|
import org.elasticsearch.action.fieldstats.IndexConstraint;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
|
import org.elasticsearch.test.ElasticsearchSingleNodeTest;
|
||||||
|
|
||||||
@ -28,6 +29,9 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Comparison.*;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MAX;
|
||||||
|
import static org.elasticsearch.action.fieldstats.IndexConstraint.Property.MIN;
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -191,4 +195,168 @@ public class FieldStatsTests extends ElasticsearchSingleNodeTest {
|
|||||||
assertThat(result.getIndicesMergedFieldStats().get("_all").get("field1").getMaxValue(), equalTo("b"));
|
assertThat(result.getIndicesMergedFieldStats().get("_all").get("field1").getMaxValue(), equalTo("b"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testNumberFiltering() {
|
||||||
|
createIndex("test1", Settings.EMPTY, "type", "value", "type=long");
|
||||||
|
client().prepareIndex("test1", "test").setSource("value", 1).get();
|
||||||
|
createIndex("test2", Settings.EMPTY, "type", "value", "type=long");
|
||||||
|
client().prepareIndex("test2", "test").setSource("value", 3).get();
|
||||||
|
client().admin().indices().prepareRefresh().get();
|
||||||
|
|
||||||
|
FieldStatsResponse response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("1"));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("3"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "-1"), new IndexConstraint("value", MAX, LTE, "0"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "0"), new IndexConstraint("value", MAX, LT, "1"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "0"), new IndexConstraint("value", MAX, LTE, "1"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("1"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "1"), new IndexConstraint("value", MAX, LTE, "2"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("1"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "1"), new IndexConstraint("value", MAX, LTE, "2"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "2"), new IndexConstraint("value", MAX, LTE, "3"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("3"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "3"), new IndexConstraint("value", MAX, LTE, "4"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("3"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "3"), new IndexConstraint("value", MAX, LTE, "4"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "1"), new IndexConstraint("value", MAX, LTE, "3"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("1"));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("3"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "1"), new IndexConstraint("value", MAX, LT, "3"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testDateFiltering() {
|
||||||
|
createIndex("test1", Settings.EMPTY, "type", "value", "type=date");
|
||||||
|
client().prepareIndex("test1", "test").setSource("value", "2014-01-01T00:00:00.000Z").get();
|
||||||
|
createIndex("test2", Settings.EMPTY, "type", "value", "type=date");
|
||||||
|
client().prepareIndex("test2", "test").setSource("value", "2014-01-02T00:00:00.000Z").get();
|
||||||
|
client().admin().indices().prepareRefresh().get();
|
||||||
|
|
||||||
|
FieldStatsResponse response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("2014-01-01T00:00:00.000Z"));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "2013-12-30T00:00:00.000Z"), new IndexConstraint("value", MAX, LTE, "2013-12-31T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "2013-12-31T00:00:00.000Z"), new IndexConstraint("value", MAX, LTE, "2014-01-01T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("2014-01-01T00:00:00.000Z"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "2014-01-01T00:00:00.000Z"), new IndexConstraint("value", MAX, LTE, "2014-01-02T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, "2014-01-02T00:00:00.000Z"), new IndexConstraint("value", MAX, LTE, "2014-01-03T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(0));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "2014-01-01T23:00:00.000Z"), new IndexConstraint("value", MAX, LTE, "2014-01-02T01:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GTE, "2014-01-01T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("2014-01-01T00:00:00.000Z"));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
|
||||||
|
response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MAX, LTE, "2014-01-02T00:00:00.000Z"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(2));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test1").get("value").getMinValue(), equalTo("2014-01-01T00:00:00.000Z"));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"fields" : [
|
||||||
|
"field1", "field2", "field3", "field4", "field5"
|
||||||
|
],
|
||||||
|
"index_constraints": {
|
||||||
|
"field2": {
|
||||||
|
"max_value" : {
|
||||||
|
"gte": 9
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"field3": {
|
||||||
|
"min_value" : {
|
||||||
|
"gt": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"field4": {
|
||||||
|
"min_value" : {
|
||||||
|
"gte": "a"
|
||||||
|
},
|
||||||
|
"max_value" : {
|
||||||
|
"lte": "g"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"field5": {
|
||||||
|
"max_value" : {
|
||||||
|
"gt": 2
|
||||||
|
},
|
||||||
|
"max_value" : {
|
||||||
|
"lt": 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,6 +33,17 @@ Supported request options:
|
|||||||
`level`:: Defines if field stats should be returned on a per index level or on a
|
`level`:: Defines if field stats should be returned on a per index level or on a
|
||||||
cluster wide level. Valid values are `indices` and `cluster` (default).
|
cluster wide level. Valid values are `indices` and `cluster` (default).
|
||||||
|
|
||||||
|
Alternatively the `fields` option can also be defined in the request body:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
curl -XPOST 'http://localhost:9200/_field_stats?level=indices' -d '{
|
||||||
|
"fields" : ["rating"]
|
||||||
|
}'
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
This is equivalent to the previous request.
|
||||||
|
|
||||||
[float]
|
[float]
|
||||||
=== Field statistics
|
=== Field statistics
|
||||||
|
|
||||||
@ -205,4 +216,46 @@ GET /_field_stats?fields=rating,answer_count,creation_date,display_name&level=in
|
|||||||
|
|
||||||
<1> The `stack` key means it contains all field stats for the `stack` index.
|
<1> The `stack` key means it contains all field stats for the `stack` index.
|
||||||
|
|
||||||
|
[float]
|
||||||
|
=== Field stats index constraints
|
||||||
|
|
||||||
|
Field stats index constraints allows to omit all field stats for indices that don't match with the constraint. An index
|
||||||
|
constraint can exclude indices' field stats based on the `min_value` and `max_value` statistic. This option is only
|
||||||
|
useful if the `level` option is set to `indices`.
|
||||||
|
|
||||||
|
For example index constraints can be useful to find out the min and max value of a particular property of your data in
|
||||||
|
a time based scenario. The following request only returns field stats for the `answer_count` property for indices
|
||||||
|
holding questions created in the year 2014:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
curl -XPOST 'http://localhost:9200/_field_stats?level=indices' -d '{
|
||||||
|
"fields" : ["answer_count"] <1>
|
||||||
|
"index_constraints" : { <2>
|
||||||
|
"creation_date" : { <3>
|
||||||
|
"min_value" : { <4>
|
||||||
|
"gte" : "2014-01-01T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
"max_value" : {
|
||||||
|
"lt" : "2015-01-01T00:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
<1> The fields to compute and return field stats for.
|
||||||
|
<2> The set index constraints. Note that index constrains can be defined for fields that aren't defined in the `fields` option.
|
||||||
|
<3> Index constraints for the field `creation_date`.
|
||||||
|
<4> An index constraint on the `min_value` property of a field statistic.
|
||||||
|
|
||||||
|
For a field, index constraints can be defined on the `min_value` statistic, `max_value` statistic or both.
|
||||||
|
Each index constraint support the following comparisons:
|
||||||
|
|
||||||
|
[horizontal]
|
||||||
|
`gte`:: Greater-than or equal to
|
||||||
|
`gt`:: Greater-than
|
||||||
|
`lte`:: Less-than or equal to
|
||||||
|
`lt`:: Less-than
|
||||||
|
|
||||||
==================================================
|
==================================================
|
||||||
|
@ -41,6 +41,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"body": null
|
"body": {
|
||||||
|
"description": "Field json objects containing the name and optionally a range to filter out indices result, that have results outside the defined bounds",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,3 +50,44 @@
|
|||||||
- match: { indices.test_1.fields.number.doc_count: 1 }
|
- match: { indices.test_1.fields.number.doc_count: 1 }
|
||||||
- match: { indices.test_1.fields.number.min_value: 123 }
|
- match: { indices.test_1.fields.number.min_value: 123 }
|
||||||
- match: { indices.test_1.fields.number.max_value: 123 }
|
- match: { indices.test_1.fields.number.max_value: 123 }
|
||||||
|
|
||||||
|
---
|
||||||
|
"Field stats with filtering":
|
||||||
|
- do:
|
||||||
|
index:
|
||||||
|
index: test_1
|
||||||
|
type: test
|
||||||
|
id: id_1
|
||||||
|
body: { foo: "bar", number: 123 }
|
||||||
|
|
||||||
|
- do:
|
||||||
|
indices.refresh: {}
|
||||||
|
|
||||||
|
- do:
|
||||||
|
field_stats:
|
||||||
|
level: indices
|
||||||
|
index: test_1
|
||||||
|
body: { fields: ["foo"], index_constraints: { number: { min_value : { gte: 100 } } }}
|
||||||
|
|
||||||
|
- match: { indices.test_1.fields.foo.max_doc: 1 }
|
||||||
|
- match: { indices.test_1.fields.foo.doc_count: 1 }
|
||||||
|
- match: { indices.test_1.fields.foo.min_value: "bar" }
|
||||||
|
- match: { indices.test_1.fields.foo.max_value: "bar" }
|
||||||
|
- is_false: indices.test_1.fields.number
|
||||||
|
|
||||||
|
- do:
|
||||||
|
field_stats:
|
||||||
|
level: indices
|
||||||
|
index: test_1
|
||||||
|
body: { fields: ["foo"], index_constraints : { number: { min_value : { gte: 200} } }}
|
||||||
|
|
||||||
|
- is_false: indices.test_1
|
||||||
|
|
||||||
|
---
|
||||||
|
"Field stats both source and fields":
|
||||||
|
- do:
|
||||||
|
catch: request
|
||||||
|
field_stats:
|
||||||
|
index: test_1
|
||||||
|
fields : ["foo"]
|
||||||
|
body: { fields: ["foo"]}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user