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
|
||||
* @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.
|
||||
|
@ -153,7 +155,7 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
*/
|
||||
public boolean match(IndexConstraint constraint) {
|
||||
int cmp;
|
||||
T value = valueOf(constraint.getValue());
|
||||
T value = valueOf(constraint.getValue(), constraint.getOptionalFormat());
|
||||
if (constraint.getProperty() == IndexConstraint.Property.MIN) {
|
||||
cmp = minValue.compareTo(value);
|
||||
} else if (constraint.getProperty() == IndexConstraint.Property.MAX) {
|
||||
|
@ -245,7 +247,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -295,7 +300,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -345,7 +353,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -399,7 +410,10 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
@ -448,7 +462,11 @@ public abstract class FieldStats<T extends Comparable<T>> implements Streamable,
|
|||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package org.elasticsearch.action.fieldstats;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.ValidateActions;
|
||||
import org.elasticsearch.action.support.broadcast.BroadcastRequest;
|
||||
|
@ -121,22 +122,24 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||
currentName = parser.currentName();
|
||||
} else if (fieldToken == Token.START_OBJECT) {
|
||||
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) {
|
||||
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 {
|
||||
throw new IllegalArgumentException("unexpected token [" + fieldToken + "]");
|
||||
}
|
||||
|
@ -189,6 +192,9 @@ public class FieldStatsRequest extends BroadcastRequest<FieldStatsRequest> {
|
|||
out.writeByte(indexConstraint.getProperty().getId());
|
||||
out.writeByte(indexConstraint.getComparison().getId());
|
||||
out.writeString(indexConstraint.getValue());
|
||||
if (out.getVersion().onOrAfter(Version.V_2_0_1)) {
|
||||
out.writeOptionalString(indexConstraint.getOptionalFormat());
|
||||
}
|
||||
}
|
||||
out.writeString(level);
|
||||
}
|
||||
|
|
|
@ -19,10 +19,12 @@
|
|||
|
||||
package org.elasticsearch.action.fieldstats;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
|
||||
public class IndexConstraint {
|
||||
|
||||
|
@ -30,37 +32,68 @@ public class IndexConstraint {
|
|||
private final Property property;
|
||||
private final Comparison comparison;
|
||||
private final String value;
|
||||
private final String optionalFormat;
|
||||
|
||||
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();
|
||||
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) {
|
||||
this.field = field;
|
||||
this.property = property;
|
||||
this.comparison = comparison;
|
||||
this.value = value;
|
||||
this(field, property, comparison, value, null);
|
||||
}
|
||||
|
||||
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() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return How to compare the specified value against the field property (lt, lte, gt and gte)
|
||||
*/
|
||||
public Comparison getComparison() {
|
||||
return comparison;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return On what property of a field the contraint is going to be applied on (min or max value)
|
||||
*/
|
||||
public Property getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The value to compare against
|
||||
*/
|
||||
public String getValue() {
|
||||
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 {
|
||||
|
||||
MIN((byte) 0),
|
||||
|
|
|
@ -42,7 +42,7 @@ public class FieldStatsRequestTests extends ESTestCase {
|
|||
assertThat(request.getFields()[3], equalTo("field4"));
|
||||
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].getValue(), equalTo("9"));
|
||||
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].getProperty(), equalTo(MAX));
|
||||
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.common.settings.Settings;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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"));
|
||||
}
|
||||
|
||||
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" : {
|
||||
"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>
|
||||
"creation_date" : { <3>
|
||||
"min_value" : { <4>
|
||||
"gte" : "2014-01-01T00:00:00.000Z",
|
||||
"gte" : "2014-01-01T00:00:00.000Z"
|
||||
},
|
||||
"max_value" : {
|
||||
"lt" : "2015-01-01T00:00:00.000Z"
|
||||
|
@ -263,3 +263,25 @@ Each index constraint support the following comparisons:
|
|||
`gt`:: Greater-than
|
||||
`lte`:: Less-than or equal to
|
||||
`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