field stats: Added a `format` option to index constraint that allows to specify date index constraint values in a different format then the for specified in the mapping.
Closes #14804
This commit is contained in:
parent
062abf813b
commit
8a454dae33
|
@ -122,9 +122,11 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param value The string to be parsed
|
* @param value The string to be parsed
|
||||||
* @return The concrete object represented by the string argument
|
* @param optionalFormat A string describing how to parse the specified value. Whether this parameter is supported
|
||||||
|
* depends on the implementation. If optionalFormat is specified and the implementation
|
||||||
|
* doesn't support it an {@link UnsupportedOperationException} is thrown
|
||||||
*/
|
*/
|
||||||
protected abstract T valueOf(String value);
|
protected abstract T valueOf(String value, String optionalFormat);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges the provided stats into this stats instance.
|
* Merges the provided stats into this stats instance.
|
||||||
|
@ -153,7 +155,7 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
*/
|
*/
|
||||||
public boolean match(IndexConstraint constraint) {
|
public boolean match(IndexConstraint constraint) {
|
||||||
int cmp;
|
int cmp;
|
||||||
T value = valueOf(constraint.getValue());
|
T value = valueOf(constraint.getValue(), constraint.getOptionalFormat());
|
||||||
if (constraint.getProperty() == IndexConstraint.Property.MIN) {
|
if (constraint.getProperty() == IndexConstraint.Property.MIN) {
|
||||||
cmp = minValue.compareTo(value);
|
cmp = minValue.compareTo(value);
|
||||||
} else if (constraint.getProperty() == IndexConstraint.Property.MAX) {
|
} else if (constraint.getProperty() == IndexConstraint.Property.MAX) {
|
||||||
|
@ -245,7 +247,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected java.lang.Long valueOf(String value) {
|
protected java.lang.Long valueOf(String value, String optionalFormat) {
|
||||||
|
if (optionalFormat != null) {
|
||||||
|
throw new UnsupportedOperationException("custom format isn't supported");
|
||||||
|
}
|
||||||
return java.lang.Long.valueOf(value);
|
return java.lang.Long.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +300,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected java.lang.Float valueOf(String value) {
|
protected java.lang.Float valueOf(String value, String optionalFormat) {
|
||||||
|
if (optionalFormat != null) {
|
||||||
|
throw new UnsupportedOperationException("custom format isn't supported");
|
||||||
|
}
|
||||||
return java.lang.Float.valueOf(value);
|
return java.lang.Float.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -345,7 +353,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected java.lang.Double valueOf(String value) {
|
protected java.lang.Double valueOf(String value, String optionalFormat) {
|
||||||
|
if (optionalFormat != null) {
|
||||||
|
throw new UnsupportedOperationException("custom format isn't supported");
|
||||||
|
}
|
||||||
return java.lang.Double.valueOf(value);
|
return java.lang.Double.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +410,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected BytesRef valueOf(String value) {
|
protected BytesRef valueOf(String value, String optionalFormat) {
|
||||||
|
if (optionalFormat != null) {
|
||||||
|
throw new UnsupportedOperationException("custom format isn't supported");
|
||||||
|
}
|
||||||
return new BytesRef(value);
|
return new BytesRef(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -448,7 +462,11 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected java.lang.Long valueOf(String value) {
|
protected java.lang.Long valueOf(String value, String optionalFormat) {
|
||||||
|
FormatDateTimeFormatter dateFormatter = this.dateFormatter;
|
||||||
|
if (optionalFormat != null) {
|
||||||
|
dateFormatter = Joda.forPattern(optionalFormat);
|
||||||
|
}
|
||||||
return dateFormatter.parser().parseMillis(value);
|
return dateFormatter.parser().parseMillis(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.action.fieldstats;
|
package org.elasticsearch.action.fieldstats;
|
||||||
|
|
||||||
|
import org.elasticsearch.Version;
|
||||||
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;
|
||||||
|
@ -121,22 +122,24 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
||||||
currentName = parser.currentName();
|
currentName = parser.currentName();
|
||||||
} else if (fieldToken == Token.START_OBJECT) {
|
} else if (fieldToken == Token.START_OBJECT) {
|
||||||
IndexConstraint.Property property = IndexConstraint.Property.parse(currentName);
|
IndexConstraint.Property property = IndexConstraint.Property.parse(currentName);
|
||||||
Token propertyToken = parser.nextToken();
|
String value = null;
|
||||||
|
String optionalFormat = null;
|
||||||
|
IndexConstraint.Comparison comparison = null;
|
||||||
|
for (Token propertyToken = parser.nextToken(); propertyToken != Token.END_OBJECT; propertyToken = parser.nextToken()) {
|
||||||
|
if (propertyToken.isValue()) {
|
||||||
|
if ("format".equals(parser.currentName())) {
|
||||||
|
optionalFormat = parser.text();
|
||||||
|
} else {
|
||||||
|
comparison = IndexConstraint.Comparison.parse(parser.currentName());
|
||||||
|
value = parser.text();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (propertyToken != Token.FIELD_NAME) {
|
if (propertyToken != Token.FIELD_NAME) {
|
||||||
throw new IllegalArgumentException("unexpected token [" + propertyToken + "]");
|
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 + "]");
|
|
||||||
}
|
}
|
||||||
|
indexConstraints.add(new IndexConstraint(field, property, comparison, value, optionalFormat));
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unexpected token [" + fieldToken + "]");
|
throw new IllegalArgumentException("unexpected token [" + fieldToken + "]");
|
||||||
}
|
}
|
||||||
|
@ -189,6 +192,9 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
||||||
out.writeByte(indexConstraint.getProperty().getId());
|
out.writeByte(indexConstraint.getProperty().getId());
|
||||||
out.writeByte(indexConstraint.getComparison().getId());
|
out.writeByte(indexConstraint.getComparison().getId());
|
||||||
out.writeString(indexConstraint.getValue());
|
out.writeString(indexConstraint.getValue());
|
||||||
|
if (out.getVersion().onOrAfter(Version.V_2_0_1)) {
|
||||||
|
out.writeOptionalString(indexConstraint.getOptionalFormat());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.writeString(level);
|
out.writeString(level);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,12 @@
|
||||||
|
|
||||||
package org.elasticsearch.action.fieldstats;
|
package org.elasticsearch.action.fieldstats;
|
||||||
|
|
||||||
|
import org.elasticsearch.Version;
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class IndexConstraint {
|
public class IndexConstraint {
|
||||||
|
|
||||||
|
@ -30,37 +32,68 @@ public class IndexConstraint {
|
||||||
private final Property property;
|
private final Property property;
|
||||||
private final Comparison comparison;
|
private final Comparison comparison;
|
||||||
private final String value;
|
private final String value;
|
||||||
|
private final String optionalFormat;
|
||||||
|
|
||||||
IndexConstraint(StreamInput input) throws IOException {
|
IndexConstraint(StreamInput input) throws IOException {
|
||||||
this.field = input.readString();
|
this.field = input.readString();
|
||||||
this.property = Property.read(input.readByte());
|
this.property = Property.read(input.readByte());
|
||||||
this.comparison = Comparison.read(input.readByte());
|
this.comparison = Comparison.read(input.readByte());
|
||||||
this.value = input.readString();
|
this.value = input.readString();
|
||||||
|
if (input.getVersion().onOrAfter(Version.V_2_0_1)) {
|
||||||
|
this.optionalFormat = input.readOptionalString();
|
||||||
|
} else {
|
||||||
|
this.optionalFormat = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IndexConstraint(String field, Property property, Comparison comparison, String value) {
|
public IndexConstraint(String field, Property property, Comparison comparison, String value) {
|
||||||
this.field = field;
|
this(field, property, comparison, value, null);
|
||||||
this.property = property;
|
|
||||||
this.comparison = comparison;
|
|
||||||
this.value = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IndexConstraint(String field, Property property, Comparison comparison, String value, String optionalFormat) {
|
||||||
|
this.field = Objects.requireNonNull(field);
|
||||||
|
this.property = Objects.requireNonNull(property);
|
||||||
|
this.comparison = Objects.requireNonNull(comparison);
|
||||||
|
this.value = Objects.requireNonNull(value);
|
||||||
|
this.optionalFormat = optionalFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return On what field the constraint is going to be applied on
|
||||||
|
*/
|
||||||
public String getField() {
|
public String getField() {
|
||||||
return field;
|
return field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return How to compare the specified value against the field property (lt, lte, gt and gte)
|
||||||
|
*/
|
||||||
public Comparison getComparison() {
|
public Comparison getComparison() {
|
||||||
return comparison;
|
return comparison;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return On what property of a field the contraint is going to be applied on (min or max value)
|
||||||
|
*/
|
||||||
public Property getProperty() {
|
public Property getProperty() {
|
||||||
return property;
|
return property;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The value to compare against
|
||||||
|
*/
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return An optional format, that specifies how the value string is converted in the native value of the field.
|
||||||
|
* Not all field types support this and right now only date field supports this option.
|
||||||
|
*/
|
||||||
|
public String getOptionalFormat() {
|
||||||
|
return optionalFormat;
|
||||||
|
}
|
||||||
|
|
||||||
public enum Property {
|
public enum Property {
|
||||||
|
|
||||||
MIN((byte) 0),
|
MIN((byte) 0),
|
||||||
|
|
|
@ -42,7 +42,7 @@ public class FieldStatsRequestTests extends ESTestCase {
|
||||||
assertThat(request.getFields()[3], equalTo("field4"));
|
assertThat(request.getFields()[3], equalTo("field4"));
|
||||||
assertThat(request.getFields()[4], equalTo("field5"));
|
assertThat(request.getFields()[4], equalTo("field5"));
|
||||||
|
|
||||||
assertThat(request.getIndexConstraints().length, equalTo(6));
|
assertThat(request.getIndexConstraints().length, equalTo(8));
|
||||||
assertThat(request.getIndexConstraints()[0].getField(), equalTo("field2"));
|
assertThat(request.getIndexConstraints()[0].getField(), equalTo("field2"));
|
||||||
assertThat(request.getIndexConstraints()[0].getValue(), equalTo("9"));
|
assertThat(request.getIndexConstraints()[0].getValue(), equalTo("9"));
|
||||||
assertThat(request.getIndexConstraints()[0].getProperty(), equalTo(MAX));
|
assertThat(request.getIndexConstraints()[0].getProperty(), equalTo(MAX));
|
||||||
|
@ -67,6 +67,16 @@ public class FieldStatsRequestTests extends ESTestCase {
|
||||||
assertThat(request.getIndexConstraints()[5].getValue(), equalTo("9"));
|
assertThat(request.getIndexConstraints()[5].getValue(), equalTo("9"));
|
||||||
assertThat(request.getIndexConstraints()[5].getProperty(), equalTo(MAX));
|
assertThat(request.getIndexConstraints()[5].getProperty(), equalTo(MAX));
|
||||||
assertThat(request.getIndexConstraints()[5].getComparison(), equalTo(LT));
|
assertThat(request.getIndexConstraints()[5].getComparison(), equalTo(LT));
|
||||||
|
assertThat(request.getIndexConstraints()[6].getField(), equalTo("field1"));
|
||||||
|
assertThat(request.getIndexConstraints()[6].getValue(), equalTo("2014-01-01"));
|
||||||
|
assertThat(request.getIndexConstraints()[6].getProperty(), equalTo(MIN));
|
||||||
|
assertThat(request.getIndexConstraints()[6].getComparison(), equalTo(GTE));
|
||||||
|
assertThat(request.getIndexConstraints()[6].getOptionalFormat(), equalTo("date_optional_time"));
|
||||||
|
assertThat(request.getIndexConstraints()[7].getField(), equalTo("field1"));
|
||||||
|
assertThat(request.getIndexConstraints()[7].getValue(), equalTo("2015-01-01"));
|
||||||
|
assertThat(request.getIndexConstraints()[7].getProperty(), equalTo(MAX));
|
||||||
|
assertThat(request.getIndexConstraints()[7].getComparison(), equalTo(LT));
|
||||||
|
assertThat(request.getIndexConstraints()[7].getOptionalFormat(), equalTo("date_optional_time"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,8 @@ import org.elasticsearch.action.fieldstats.FieldStatsResponse;
|
||||||
import org.elasticsearch.action.fieldstats.IndexConstraint;
|
import org.elasticsearch.action.fieldstats.IndexConstraint;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.DateTimeZone;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -359,4 +361,33 @@ public class FieldStatsTests extends ESSingleNodeTestCase {
|
||||||
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testDateFiltering_optionalFormat() {
|
||||||
|
createIndex("test1", Settings.EMPTY, "type", "value", "type=date,format=strict_date_optional_time");
|
||||||
|
client().prepareIndex("test1", "type").setSource("value", "2014-01-01T00:00:00.000Z").get();
|
||||||
|
createIndex("test2", Settings.EMPTY, "type", "value", "type=date,format=strict_date_optional_time");
|
||||||
|
client().prepareIndex("test2", "type").setSource("value", "2014-01-02T00:00:00.000Z").get();
|
||||||
|
client().admin().indices().prepareRefresh().get();
|
||||||
|
|
||||||
|
DateTime dateTime1 = new DateTime(2014, 1, 1, 0, 0, 0, 0, DateTimeZone.UTC);
|
||||||
|
DateTime dateTime2 = new DateTime(2014, 1, 2, 0, 0, 0, 0, DateTimeZone.UTC);
|
||||||
|
FieldStatsResponse response = client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, String.valueOf(dateTime1.getMillis()), "epoch_millis"), new IndexConstraint("value", MAX, LTE, String.valueOf(dateTime2.getMillis()), "epoch_millis"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().size(), equalTo(1));
|
||||||
|
assertThat(response.getIndicesMergedFieldStats().get("test2").get("value").getMinValue(), equalTo("2014-01-02T00:00:00.000Z"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
client().prepareFieldStats()
|
||||||
|
.setFields("value")
|
||||||
|
.setIndexContraints(new IndexConstraint("value", MIN, GT, String.valueOf(dateTime1.getMillis()), "xyz"))
|
||||||
|
.setLevel("indices")
|
||||||
|
.get();
|
||||||
|
fail("IllegalArgumentException should have been thrown");
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
assertThat(e.getMessage(), containsString("Invalid format"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -28,6 +28,16 @@
|
||||||
"max_value" : {
|
"max_value" : {
|
||||||
"lt": 9
|
"lt": 9
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"field1": {
|
||||||
|
"min_value" : {
|
||||||
|
"gte": "2014-01-01",
|
||||||
|
"format" : "date_optional_time"
|
||||||
|
},
|
||||||
|
"max_value" : {
|
||||||
|
"lt": "2015-01-01",
|
||||||
|
"format" : "date_optional_time"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -240,7 +240,7 @@ curl -XPOST "http://localhost:9200/_field_stats?level=indices" -d '{
|
||||||
"index_constraints" : { <2>
|
"index_constraints" : { <2>
|
||||||
"creation_date" : { <3>
|
"creation_date" : { <3>
|
||||||
"min_value" : { <4>
|
"min_value" : { <4>
|
||||||
"gte" : "2014-01-01T00:00:00.000Z",
|
"gte" : "2014-01-01T00:00:00.000Z"
|
||||||
},
|
},
|
||||||
"max_value" : {
|
"max_value" : {
|
||||||
"lt" : "2015-01-01T00:00:00.000Z"
|
"lt" : "2015-01-01T00:00:00.000Z"
|
||||||
|
@ -263,3 +263,25 @@ Each index constraint support the following comparisons:
|
||||||
`gt`:: Greater-than
|
`gt`:: Greater-than
|
||||||
`lte`:: Less-than or equal to
|
`lte`:: Less-than or equal to
|
||||||
`lt`:: Less-than
|
`lt`:: Less-than
|
||||||
|
|
||||||
|
Field stats index constraints on date fields optionally accept a `format` option, used to parse the constraint's value.
|
||||||
|
If missing, the format configured in the field's mapping is used.
|
||||||
|
|
||||||
|
[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-01",
|
||||||
|
"format" : "date_optional_time"
|
||||||
|
},
|
||||||
|
"max_value" : {
|
||||||
|
"lt" : "2015-01-01",
|
||||||
|
"format" : "date_optional_time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
--------------------------------------------------
|
Loading…
Reference in New Issue