Merge pull request #18658 from rmuir/jodaTime
improve date api for expressions/painless fields
This commit is contained in:
commit
0373c62a57
|
@ -27,6 +27,7 @@ import org.elasticsearch.common.geo.GeoPoint;
|
|||
import org.elasticsearch.common.unit.DistanceUnit;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.MutableDateTime;
|
||||
import org.joda.time.ReadableDateTime;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collections;
|
||||
|
@ -131,7 +132,7 @@ public interface ScriptDocValues<T> extends List<T> {
|
|||
return Collections.unmodifiableList(this);
|
||||
}
|
||||
|
||||
public MutableDateTime getDate() {
|
||||
public ReadableDateTime getDate() {
|
||||
date.setMillis(getValue());
|
||||
return date;
|
||||
}
|
||||
|
|
|
@ -69,6 +69,8 @@ import java.util.Set;
|
|||
* <li>{@link org.joda.time.DateTimeUtils}</li>
|
||||
* <li>{@link org.joda.time.DateTimeZone}</li>
|
||||
* <li>{@link org.joda.time.Instant}</li>
|
||||
* <li>{@link org.joda.time.ReadableDateTime}</li>
|
||||
* <li>{@link org.joda.time.ReadableInstant}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public final class ClassPermission extends BasicPermission {
|
||||
|
@ -102,7 +104,9 @@ public final class ClassPermission extends BasicPermission {
|
|||
org.joda.time.DateTime.class.getName(),
|
||||
org.joda.time.DateTimeUtils.class.getName(),
|
||||
org.joda.time.DateTimeZone.class.getName(),
|
||||
org.joda.time.Instant.class.getName()
|
||||
org.joda.time.Instant.class.getName(),
|
||||
org.joda.time.ReadableDateTime.class.getName(),
|
||||
org.joda.time.ReadableInstant.class.getName()
|
||||
)));
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,8 @@ scripts, simply set the `lang` parameter to `expression`.
|
|||
|`doc['field_name'].empty` |A boolean indicating if the field has no
|
||||
values within the doc.
|
||||
|
||||
|`doc['field_name'].length` |The number of values in this document.
|
||||
|
||||
|`doc['field_name'].min()` |The minimum value of the field in this document.
|
||||
|
||||
|`doc['field_name'].max()` |The maximum value of the field in this document.
|
||||
|
@ -51,8 +53,6 @@ values within the doc.
|
|||
|`doc['field_name'].avg()` |The average of the values in this document.
|
||||
|
||||
|`doc['field_name'].sum()` |The sum of the values in this document.
|
||||
|
||||
|`doc['field_name'].count()` |The number of values in this document.
|
||||
|=======================================================================
|
||||
|
||||
When a document is missing the field completely, by default the value will be treated as `0`.
|
||||
|
@ -69,27 +69,47 @@ For example: `doc['on_sale'].value ? doc['price'].value * 0.5 : doc['price'].val
|
|||
[float]
|
||||
=== Date field API
|
||||
Date fields are treated as the number of milliseconds since January 1, 1970 and
|
||||
support the Numeric Fields API above, with these additional methods:
|
||||
support the Numeric Fields API above, plus access to some date-specific fields:
|
||||
|
||||
[cols="<,<",options="header",]
|
||||
|=======================================================================
|
||||
|Expression |Description
|
||||
|`doc['field_name'].getYear()` |Year component, e.g. `1970`.
|
||||
|`doc['field_name'].date.centuryOfEra`|Century (1-2920000)
|
||||
|
||||
|`doc['field_name'].getMonth()` |Month component (0-11), e.g. `0` for January.
|
||||
|`doc['field_name'].date.dayOfMonth`|Day (1-31), e.g. `1` for the first of the month.
|
||||
|
||||
|`doc['field_name'].getDayOfMonth()` |Day component, e.g. `1` for the first of the month.
|
||||
|`doc['field_name'].date.dayOfWeek`|Day of the week (1-7), e.g. `1` for Monday.
|
||||
|
||||
|`doc['field_name'].getHourOfDay()` |Hour component (0-23)
|
||||
|`doc['field_name'].date.dayOfYear`|Day of the year, e.g. `1` for January 1.
|
||||
|
||||
|`doc['field_name'].getMinutes()` |Minutes component (0-59)
|
||||
|`doc['field_name'].date.era`|Era: `0` for BC, `1` for AD.
|
||||
|
||||
|`doc['field_name'].getSeconds()` |Seconds component (0-59)
|
||||
|`doc['field_name'].date.hourOfDay`|Hour (0-23).
|
||||
|
||||
|`doc['field_name'].date.millisOfDay`|Milliseconds within the day (0-86399999).
|
||||
|
||||
|`doc['field_name'].date.millisOfSecond`|Milliseconds within the second (0-999).
|
||||
|
||||
|`doc['field_name'].date.minuteOfDay`|Minute within the day (0-1439).
|
||||
|
||||
|`doc['field_name'].date.minuteOfHour`|Minute within the hour (0-59).
|
||||
|
||||
|`doc['field_name'].date.monthOfYear`|Month within the year (1-12), e.g. `1` for January.
|
||||
|
||||
|`doc['field_name'].date.secondOfDay`|Second within the day (0-86399).
|
||||
|
||||
|`doc['field_name'].date.secondOfMinute`|Second within the minute (0-59).
|
||||
|
||||
|`doc['field_name'].date.year`|Year (-292000000 - 292000000).
|
||||
|
||||
|`doc['field_name'].date.yearOfCentury`|Year within the century (1-100).
|
||||
|
||||
|`doc['field_name'].date.yearOfEra`|Year within the era (1-292000000).
|
||||
|=======================================================================
|
||||
|
||||
The following example shows the difference in years between the `date` fields date0 and date1:
|
||||
|
||||
`doc['date1'].getYear() - doc['date0'].getYear()`
|
||||
`doc['date1'].date.year - doc['date0'].date.year`
|
||||
|
||||
[float]
|
||||
=== `geo_point` field API
|
||||
|
|
|
@ -35,14 +35,20 @@ final class DateField {
|
|||
// supported variables
|
||||
static final String VALUE_VARIABLE = "value";
|
||||
static final String EMPTY_VARIABLE = "empty";
|
||||
static final String LENGTH_VARIABLE = "length";
|
||||
|
||||
// supported methods
|
||||
static final String GETVALUE_METHOD = "getValue";
|
||||
static final String ISEMPTY_METHOD = "isEmpty";
|
||||
static final String SIZE_METHOD = "size";
|
||||
static final String MINIMUM_METHOD = "min";
|
||||
static final String MAXIMUM_METHOD = "max";
|
||||
static final String AVERAGE_METHOD = "avg";
|
||||
static final String MEDIAN_METHOD = "median";
|
||||
static final String SUM_METHOD = "sum";
|
||||
static final String COUNT_METHOD = "count";
|
||||
|
||||
// date-specific
|
||||
static final String GET_YEAR_METHOD = "getYear";
|
||||
static final String GET_MONTH_METHOD = "getMonth";
|
||||
static final String GET_DAY_OF_MONTH_METHOD = "getDayOfMonth";
|
||||
|
@ -56,6 +62,8 @@ final class DateField {
|
|||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case EMPTY_VARIABLE:
|
||||
return new EmptyMemberValueSource(fieldData);
|
||||
case LENGTH_VARIABLE:
|
||||
return new CountMethodValueSource(fieldData);
|
||||
default:
|
||||
throw new IllegalArgumentException("Member variable [" + variable + "] does not exist for date field [" + fieldName + "].");
|
||||
}
|
||||
|
@ -63,6 +71,12 @@ final class DateField {
|
|||
|
||||
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
|
||||
switch (method) {
|
||||
case GETVALUE_METHOD:
|
||||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case ISEMPTY_METHOD:
|
||||
return new EmptyMemberValueSource(fieldData);
|
||||
case SIZE_METHOD:
|
||||
return new CountMethodValueSource(fieldData);
|
||||
case MINIMUM_METHOD:
|
||||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case MAXIMUM_METHOD:
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package org.elasticsearch.script.expression;
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.search.MultiValueMode;
|
||||
import org.joda.time.ReadableDateTime;
|
||||
|
||||
/**
|
||||
* Expressions API for date objects (.date)
|
||||
*/
|
||||
final class DateObject {
|
||||
// no instance
|
||||
private DateObject() {}
|
||||
|
||||
// supported variables
|
||||
static final String CENTURY_OF_ERA_VARIABLE = "centuryOfEra";
|
||||
static final String DAY_OF_MONTH_VARIABLE = "dayOfMonth";
|
||||
static final String DAY_OF_WEEK_VARIABLE = "dayOfWeek";
|
||||
static final String DAY_OF_YEAR_VARIABLE = "dayOfYear";
|
||||
static final String ERA_VARIABLE = "era";
|
||||
static final String HOUR_OF_DAY_VARIABLE = "hourOfDay";
|
||||
static final String MILLIS_OF_DAY_VARIABLE = "millisOfDay";
|
||||
static final String MILLIS_OF_SECOND_VARIABLE = "millisOfSecond";
|
||||
static final String MINUTE_OF_DAY_VARIABLE = "minuteOfDay";
|
||||
static final String MINUTE_OF_HOUR_VARIABLE = "minuteOfHour";
|
||||
static final String MONTH_OF_YEAR_VARIABLE = "monthOfYear";
|
||||
static final String SECOND_OF_DAY_VARIABLE = "secondOfDay";
|
||||
static final String SECOND_OF_MINUTE_VARIABLE = "secondOfMinute";
|
||||
static final String WEEK_OF_WEEK_YEAR_VARIABLE = "weekOfWeekyear";
|
||||
static final String WEEK_YEAR_VARIABLE = "weekyear";
|
||||
static final String YEAR_VARIABLE = "year";
|
||||
static final String YEAR_OF_CENTURY_VARIABLE = "yearOfCentury";
|
||||
static final String YEAR_OF_ERA_VARIABLE = "yearOfEra";
|
||||
|
||||
// supported methods
|
||||
static final String GETCENTURY_OF_ERA_METHOD = "getCenturyOfEra";
|
||||
static final String GETDAY_OF_MONTH_METHOD = "getDayOfMonth";
|
||||
static final String GETDAY_OF_WEEK_METHOD = "getDayOfWeek";
|
||||
static final String GETDAY_OF_YEAR_METHOD = "getDayOfYear";
|
||||
static final String GETERA_METHOD = "getEra";
|
||||
static final String GETHOUR_OF_DAY_METHOD = "getHourOfDay";
|
||||
static final String GETMILLIS_OF_DAY_METHOD = "getMillisOfDay";
|
||||
static final String GETMILLIS_OF_SECOND_METHOD = "getMillisOfSecond";
|
||||
static final String GETMINUTE_OF_DAY_METHOD = "getMinuteOfDay";
|
||||
static final String GETMINUTE_OF_HOUR_METHOD = "getMinuteOfHour";
|
||||
static final String GETMONTH_OF_YEAR_METHOD = "getMonthOfYear";
|
||||
static final String GETSECOND_OF_DAY_METHOD = "getSecondOfDay";
|
||||
static final String GETSECOND_OF_MINUTE_METHOD = "getSecondOfMinute";
|
||||
static final String GETWEEK_OF_WEEK_YEAR_METHOD = "getWeekOfWeekyear";
|
||||
static final String GETWEEK_YEAR_METHOD = "getWeekyear";
|
||||
static final String GETYEAR_METHOD = "getYear";
|
||||
static final String GETYEAR_OF_CENTURY_METHOD = "getYearOfCentury";
|
||||
static final String GETYEAR_OF_ERA_METHOD = "getYearOfEra";
|
||||
|
||||
static ValueSource getVariable(IndexFieldData<?> fieldData, String fieldName, String variable) {
|
||||
switch (variable) {
|
||||
case CENTURY_OF_ERA_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getCenturyOfEra);
|
||||
case DAY_OF_MONTH_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfMonth);
|
||||
case DAY_OF_WEEK_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfWeek);
|
||||
case DAY_OF_YEAR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getDayOfYear);
|
||||
case ERA_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getEra);
|
||||
case HOUR_OF_DAY_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getHourOfDay);
|
||||
case MILLIS_OF_DAY_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMillisOfDay);
|
||||
case MILLIS_OF_SECOND_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMillisOfSecond);
|
||||
case MINUTE_OF_DAY_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMinuteOfDay);
|
||||
case MINUTE_OF_HOUR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMinuteOfHour);
|
||||
case MONTH_OF_YEAR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getMonthOfYear);
|
||||
case SECOND_OF_DAY_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getSecondOfDay);
|
||||
case SECOND_OF_MINUTE_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getSecondOfMinute);
|
||||
case WEEK_OF_WEEK_YEAR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getWeekOfWeekyear);
|
||||
case WEEK_YEAR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getWeekyear);
|
||||
case YEAR_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYear);
|
||||
case YEAR_OF_CENTURY_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYearOfCentury);
|
||||
case YEAR_OF_ERA_VARIABLE:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, variable, ReadableDateTime::getYearOfEra);
|
||||
default:
|
||||
throw new IllegalArgumentException("Member variable [" + variable +
|
||||
"] does not exist for date object on field [" + fieldName + "].");
|
||||
}
|
||||
}
|
||||
|
||||
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
|
||||
switch (method) {
|
||||
case GETCENTURY_OF_ERA_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getCenturyOfEra);
|
||||
case GETDAY_OF_MONTH_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfMonth);
|
||||
case GETDAY_OF_WEEK_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfWeek);
|
||||
case GETDAY_OF_YEAR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getDayOfYear);
|
||||
case GETERA_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getEra);
|
||||
case GETHOUR_OF_DAY_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getHourOfDay);
|
||||
case GETMILLIS_OF_DAY_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMillisOfDay);
|
||||
case GETMILLIS_OF_SECOND_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMillisOfSecond);
|
||||
case GETMINUTE_OF_DAY_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMinuteOfDay);
|
||||
case GETMINUTE_OF_HOUR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMinuteOfHour);
|
||||
case GETMONTH_OF_YEAR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getMonthOfYear);
|
||||
case GETSECOND_OF_DAY_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getSecondOfDay);
|
||||
case GETSECOND_OF_MINUTE_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getSecondOfMinute);
|
||||
case GETWEEK_OF_WEEK_YEAR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getWeekOfWeekyear);
|
||||
case GETWEEK_YEAR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getWeekyear);
|
||||
case GETYEAR_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYear);
|
||||
case GETYEAR_OF_CENTURY_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYearOfCentury);
|
||||
case GETYEAR_OF_ERA_METHOD:
|
||||
return new DateObjectValueSource(fieldData, MultiValueMode.MIN, method, ReadableDateTime::getYearOfEra);
|
||||
default:
|
||||
throw new IllegalArgumentException("Member method [" + method +
|
||||
"] does not exist for date object on field [" + fieldName + "].");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.script.expression;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.queries.function.FunctionValues;
|
||||
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
|
||||
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
|
||||
import org.elasticsearch.index.fielddata.IndexFieldData;
|
||||
import org.elasticsearch.index.fielddata.NumericDoubleValues;
|
||||
import org.elasticsearch.search.MultiValueMode;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.joda.time.MutableDateTime;
|
||||
import org.joda.time.ReadableDateTime;
|
||||
|
||||
/** Extracts a portion of a date field with joda time */
|
||||
class DateObjectValueSource extends FieldDataValueSource {
|
||||
|
||||
final String methodName;
|
||||
final ToIntFunction<ReadableDateTime> function;
|
||||
|
||||
DateObjectValueSource(IndexFieldData<?> indexFieldData, MultiValueMode multiValueMode,
|
||||
String methodName, ToIntFunction<ReadableDateTime> function) {
|
||||
super(indexFieldData, multiValueMode);
|
||||
|
||||
Objects.requireNonNull(methodName);
|
||||
|
||||
this.methodName = methodName;
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("rawtypes") // ValueSource uses a rawtype
|
||||
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
|
||||
AtomicNumericFieldData leafData = (AtomicNumericFieldData) fieldData.load(leaf);
|
||||
MutableDateTime joda = new MutableDateTime(0, DateTimeZone.UTC);
|
||||
NumericDoubleValues docValues = multiValueMode.select(leafData.getDoubleValues(), 0d);
|
||||
return new DoubleDocValues(this) {
|
||||
@Override
|
||||
public double doubleVal(int docId) {
|
||||
long millis = (long)docValues.get(docId);
|
||||
joda.setMillis(millis);
|
||||
return function.applyAsInt(joda);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String description() {
|
||||
return methodName + ": field(" + fieldData.getFieldName() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
if (!super.equals(o)) return false;
|
||||
|
||||
DateObjectValueSource that = (DateObjectValueSource) o;
|
||||
return methodName.equals(that.methodName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + methodName.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -51,6 +51,7 @@ import java.security.AccessController;
|
|||
import java.security.PrivilegedAction;
|
||||
import java.text.ParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -148,6 +149,7 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
|
|||
String fieldname = null;
|
||||
String methodname = null;
|
||||
String variablename = "value"; // .value is the default for doc['field'], its optional.
|
||||
boolean dateAccessor = false; // true if the variable is of type doc['field'].date.xxx
|
||||
VariableContext[] parts = VariableContext.parse(variable);
|
||||
if (parts[0].text.equals("doc") == false) {
|
||||
throw new ParseException("Unknown variable [" + parts[0].text + "]", 0);
|
||||
|
@ -167,8 +169,20 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
|
|||
}
|
||||
}
|
||||
if (parts.length > 3) {
|
||||
// access to the .date "object" within the field
|
||||
if (parts.length == 4 && ("date".equals(parts[2].text) || "getDate".equals(parts[2].text))) {
|
||||
if (parts[3].type == VariableContext.Type.METHOD) {
|
||||
methodname = parts[3].text;
|
||||
dateAccessor = true;
|
||||
} else if (parts[3].type == VariableContext.Type.MEMBER) {
|
||||
variablename = parts[3].text;
|
||||
dateAccessor = true;
|
||||
}
|
||||
}
|
||||
if (!dateAccessor) {
|
||||
throw new IllegalArgumentException("Variable [" + variable + "] does not follow an allowed format of either doc['field'] or doc['field'].method()");
|
||||
}
|
||||
}
|
||||
|
||||
MappedFieldType fieldType = mapper.fullName(fieldname);
|
||||
|
||||
|
@ -191,12 +205,21 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
|
|||
}
|
||||
} else if (fieldType instanceof LegacyDateFieldMapper.DateFieldType ||
|
||||
fieldType instanceof DateFieldMapper.DateFieldType) {
|
||||
// date
|
||||
if (dateAccessor) {
|
||||
// date object
|
||||
if (methodname == null) {
|
||||
valueSource = DateObject.getVariable(fieldData, fieldname, variablename);
|
||||
} else {
|
||||
valueSource = DateObject.getMethod(fieldData, fieldname, methodname);
|
||||
}
|
||||
} else {
|
||||
// date field itself
|
||||
if (methodname == null) {
|
||||
valueSource = DateField.getVariable(fieldData, fieldname, variablename);
|
||||
} else {
|
||||
valueSource = DateField.getMethod(fieldData, fieldname, methodname);
|
||||
}
|
||||
}
|
||||
} else if (fieldData instanceof IndexNumericFieldData) {
|
||||
// number
|
||||
if (methodname == null) {
|
||||
|
|
|
@ -34,6 +34,11 @@ final class GeoField {
|
|||
static final String LAT_VARIABLE = "lat";
|
||||
static final String LON_VARIABLE = "lon";
|
||||
|
||||
// supported methods
|
||||
static final String ISEMPTY_METHOD = "isEmpty";
|
||||
static final String GETLAT_METHOD = "getLat";
|
||||
static final String GETLON_METHOD = "getLon";
|
||||
|
||||
static ValueSource getVariable(IndexFieldData<?> fieldData, String fieldName, String variable) {
|
||||
switch (variable) {
|
||||
case EMPTY_VARIABLE:
|
||||
|
@ -48,6 +53,15 @@ final class GeoField {
|
|||
}
|
||||
|
||||
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
|
||||
switch (method) {
|
||||
case ISEMPTY_METHOD:
|
||||
return new GeoEmptyValueSource(fieldData);
|
||||
case GETLAT_METHOD:
|
||||
return new GeoLatitudeValueSource(fieldData);
|
||||
case GETLON_METHOD:
|
||||
return new GeoLongitudeValueSource(fieldData);
|
||||
default:
|
||||
throw new IllegalArgumentException("Member method [" + method + "] does not exist for geo field [" + fieldName + "].");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,12 @@ final class NumericField {
|
|||
// supported variables
|
||||
static final String VALUE_VARIABLE = "value";
|
||||
static final String EMPTY_VARIABLE = "empty";
|
||||
static final String LENGTH_VARIABLE = "length";
|
||||
|
||||
// supported methods
|
||||
static final String GETVALUE_METHOD = "getValue";
|
||||
static final String ISEMPTY_METHOD = "isEmpty";
|
||||
static final String SIZE_METHOD = "size";
|
||||
static final String MINIMUM_METHOD = "min";
|
||||
static final String MAXIMUM_METHOD = "max";
|
||||
static final String AVERAGE_METHOD = "avg";
|
||||
|
@ -48,6 +52,8 @@ final class NumericField {
|
|||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case EMPTY_VARIABLE:
|
||||
return new EmptyMemberValueSource(fieldData);
|
||||
case LENGTH_VARIABLE:
|
||||
return new CountMethodValueSource(fieldData);
|
||||
default:
|
||||
throw new IllegalArgumentException("Member variable [" + variable + "] does not exist for " +
|
||||
"numeric field [" + fieldName + "].");
|
||||
|
@ -56,6 +62,12 @@ final class NumericField {
|
|||
|
||||
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
|
||||
switch (method) {
|
||||
case GETVALUE_METHOD:
|
||||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case ISEMPTY_METHOD:
|
||||
return new EmptyMemberValueSource(fieldData);
|
||||
case SIZE_METHOD:
|
||||
return new CountMethodValueSource(fieldData);
|
||||
case MINIMUM_METHOD:
|
||||
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
|
||||
case MAXIMUM_METHOD:
|
||||
|
|
|
@ -170,6 +170,34 @@ public class MoreExpressionTests extends ESIntegTestCase {
|
|||
assertEquals(1983.0, hits.getAt(1).field("foo").getValue(), 0.0D);
|
||||
}
|
||||
|
||||
public void testDateObjectMethods() throws Exception {
|
||||
ElasticsearchAssertions.assertAcked(prepareCreate("test").addMapping("doc", "date0", "type=date", "date1", "type=date"));
|
||||
ensureGreen("test");
|
||||
indexRandom(true,
|
||||
client().prepareIndex("test", "doc", "1").setSource("date0", "2015-04-28T04:02:07Z", "date1", "1985-09-01T23:11:01Z"),
|
||||
client().prepareIndex("test", "doc", "2").setSource("date0", "2013-12-25T11:56:45Z", "date1", "1983-10-13T23:15:00Z"));
|
||||
SearchResponse rsp = buildRequest("doc['date0'].date.secondOfMinute - doc['date0'].date.minuteOfHour").get();
|
||||
assertEquals(2, rsp.getHits().getTotalHits());
|
||||
SearchHits hits = rsp.getHits();
|
||||
assertEquals(5.0, hits.getAt(0).field("foo").getValue(), 0.0D);
|
||||
assertEquals(-11.0, hits.getAt(1).field("foo").getValue(), 0.0D);
|
||||
rsp = buildRequest("doc['date0'].date.getHourOfDay() + doc['date1'].date.dayOfMonth").get();
|
||||
assertEquals(2, rsp.getHits().getTotalHits());
|
||||
hits = rsp.getHits();
|
||||
assertEquals(5.0, hits.getAt(0).field("foo").getValue(), 0.0D);
|
||||
assertEquals(24.0, hits.getAt(1).field("foo").getValue(), 0.0D);
|
||||
rsp = buildRequest("doc['date1'].date.monthOfYear + 1").get();
|
||||
assertEquals(2, rsp.getHits().getTotalHits());
|
||||
hits = rsp.getHits();
|
||||
assertEquals(10.0, hits.getAt(0).field("foo").getValue(), 0.0D);
|
||||
assertEquals(11.0, hits.getAt(1).field("foo").getValue(), 0.0D);
|
||||
rsp = buildRequest("doc['date1'].date.year").get();
|
||||
assertEquals(2, rsp.getHits().getTotalHits());
|
||||
hits = rsp.getHits();
|
||||
assertEquals(1985.0, hits.getAt(0).field("foo").getValue(), 0.0D);
|
||||
assertEquals(1983.0, hits.getAt(1).field("foo").getValue(), 0.0D);
|
||||
}
|
||||
|
||||
public void testMultiValueMethods() throws Exception {
|
||||
ElasticsearchAssertions.assertAcked(prepareCreate("test").addMapping("doc", "double0", "type=double", "double1", "type=double", "double2", "type=double"));
|
||||
ensureGreen("test");
|
||||
|
|
|
@ -50,7 +50,8 @@ public final class Definition {
|
|||
"java.time.zone.txt",
|
||||
"java.util.txt",
|
||||
"java.util.function.txt",
|
||||
"java.util.stream.txt"));
|
||||
"java.util.stream.txt",
|
||||
"joda.time.txt"));
|
||||
|
||||
private static final Definition INSTANCE = new Definition();
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ public final class Variables {
|
|||
// Method variables.
|
||||
|
||||
// This reference. Internal use only.
|
||||
addVariable("[" + Reserved.THIS + "]", Definition.getType("Executable"), Reserved.THIS, true, true);
|
||||
addVariable("[" + Reserved.THIS + "]", Definition.getType("Object"), Reserved.THIS, true, true);
|
||||
|
||||
// Input map of variables passed to the script.
|
||||
addVariable("[" + Reserved.PARAMS + "]", Definition.getType("Map"), Reserved.PARAMS, true, true);
|
||||
|
|
|
@ -643,9 +643,11 @@ class GregorianCalendar -> java.util.GregorianCalendar extends Calendar,Comparab
|
|||
GregorianCalendar <init>(int,int,int,int,int,int)
|
||||
GregorianCalendar <init>(TimeZone)
|
||||
GregorianCalendar <init>(TimeZone,Locale)
|
||||
GregorianCalendar from(ZonedDateTime)
|
||||
Date getGregorianChange()
|
||||
boolean isLeapYear(int)
|
||||
void setGregorianChange(Date)
|
||||
ZonedDateTime toZonedDateTime()
|
||||
}
|
||||
|
||||
class HashMap -> java.util.HashMap extends AbstractMap,Map,Object {
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
#
|
||||
# Painless definition file. This defines the hierarchy of classes,
|
||||
# what methods and fields they have, etc.
|
||||
#
|
||||
|
||||
# NOTE: this just minimal whitelisting of joda time, just to provide
|
||||
# convenient access via the scripting API. classes are fully qualified to avoid
|
||||
# any confusion with java.time
|
||||
|
||||
class org.joda.time.ReadableInstant -> org.joda.time.ReadableInstant extends Comparable {
|
||||
boolean equals(Object)
|
||||
long getMillis()
|
||||
int hashCode()
|
||||
boolean isAfter(org.joda.time.ReadableInstant)
|
||||
boolean isBefore(org.joda.time.ReadableInstant)
|
||||
boolean isEqual(org.joda.time.ReadableInstant)
|
||||
String toString()
|
||||
}
|
||||
|
||||
class org.joda.time.ReadableDateTime -> org.joda.time.ReadableDateTime extends org.joda.time.ReadableInstant,Comparable {
|
||||
int getCenturyOfEra()
|
||||
int getDayOfMonth()
|
||||
int getDayOfWeek()
|
||||
int getDayOfYear()
|
||||
int getEra()
|
||||
int getHourOfDay()
|
||||
int getMillisOfDay()
|
||||
int getMillisOfSecond()
|
||||
int getMinuteOfDay()
|
||||
int getMinuteOfHour()
|
||||
int getMonthOfYear()
|
||||
int getSecondOfDay()
|
||||
int getSecondOfMinute()
|
||||
int getWeekOfWeekyear()
|
||||
int getWeekyear()
|
||||
int getYear()
|
||||
int getYearOfCentury()
|
||||
int getYearOfEra()
|
||||
String toString(String)
|
||||
String toString(String,Locale)
|
||||
}
|
|
@ -59,28 +59,29 @@ class def -> java.lang.Object {
|
|||
|
||||
#### ES Scripting API
|
||||
|
||||
class GeoPoint -> org.elasticsearch.common.geo.GeoPoint extends Object {
|
||||
class org.elasticsearch.common.geo.GeoPoint -> org.elasticsearch.common.geo.GeoPoint extends Object {
|
||||
double getLat()
|
||||
double getLon()
|
||||
}
|
||||
|
||||
class Strings -> org.elasticsearch.index.fielddata.ScriptDocValues$Strings extends List,Collection,Iterable,Object {
|
||||
class org.elasticsearch.index.fielddata.ScriptDocValues.Strings -> org.elasticsearch.index.fielddata.ScriptDocValues$Strings extends List,Collection,Iterable,Object {
|
||||
String getValue()
|
||||
List getValues()
|
||||
}
|
||||
|
||||
class Longs -> org.elasticsearch.index.fielddata.ScriptDocValues$Longs extends List,Collection,Iterable,Object {
|
||||
class org.elasticsearch.index.fielddata.ScriptDocValues.Longs -> org.elasticsearch.index.fielddata.ScriptDocValues$Longs extends List,Collection,Iterable,Object {
|
||||
long getValue()
|
||||
List getValues()
|
||||
org.joda.time.ReadableDateTime getDate()
|
||||
}
|
||||
|
||||
class Doubles -> org.elasticsearch.index.fielddata.ScriptDocValues$Doubles extends List,Collection,Iterable,Object {
|
||||
class org.elasticsearch.index.fielddata.ScriptDocValues.Doubles -> org.elasticsearch.index.fielddata.ScriptDocValues$Doubles extends List,Collection,Iterable,Object {
|
||||
double getValue()
|
||||
List getValues()
|
||||
}
|
||||
|
||||
class GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints extends List,Collection,Iterable,Object {
|
||||
GeoPoint getValue()
|
||||
class org.elasticsearch.index.fielddata.ScriptDocValues.GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints extends List,Collection,Iterable,Object {
|
||||
org.elasticsearch.common.geo.GeoPoint getValue()
|
||||
List getValues()
|
||||
double getLat()
|
||||
double getLon()
|
||||
|
@ -111,9 +112,9 @@ class GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints e
|
|||
|
||||
# for testing.
|
||||
# currently FeatureTest exposes overloaded constructor, field load store, and overloaded static methods
|
||||
class FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object {
|
||||
FeatureTest <init>()
|
||||
FeatureTest <init>(int,int)
|
||||
class org.elasticsearch.painless.FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object {
|
||||
org.elasticsearch.painless.FeatureTest <init>()
|
||||
org.elasticsearch.painless.FeatureTest <init>(int,int)
|
||||
int getX()
|
||||
int getY()
|
||||
void setX(int)
|
||||
|
@ -121,7 +122,3 @@ class FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object {
|
|||
boolean overloadedStatic()
|
||||
boolean overloadedStatic(boolean)
|
||||
}
|
||||
|
||||
# currently needed internally
|
||||
class Executable -> org.elasticsearch.painless.Executable {
|
||||
}
|
||||
|
|
|
@ -41,12 +41,14 @@ public class OverloadTests extends ScriptTestCase {
|
|||
}
|
||||
|
||||
public void testConstructor() {
|
||||
assertEquals(true, exec("FeatureTest f = new FeatureTest(); return f.x == 0 && f.y == 0;"));
|
||||
assertEquals(true, exec("FeatureTest f = new FeatureTest(1, 2); return f.x == 1 && f.y == 2;"));
|
||||
assertEquals(true, exec("org.elasticsearch.painless.FeatureTest f = new org.elasticsearch.painless.FeatureTest();" +
|
||||
"return f.x == 0 && f.y == 0;"));
|
||||
assertEquals(true, exec("org.elasticsearch.painless.FeatureTest f = new org.elasticsearch.painless.FeatureTest(1, 2);" +
|
||||
"return f.x == 1 && f.y == 2;"));
|
||||
}
|
||||
|
||||
public void testStatic() {
|
||||
assertEquals(true, exec("return FeatureTest.overloadedStatic();"));
|
||||
assertEquals(false, exec("return FeatureTest.overloadedStatic(false);"));
|
||||
assertEquals(true, exec("return org.elasticsearch.painless.FeatureTest.overloadedStatic();"));
|
||||
assertEquals(false, exec("return org.elasticsearch.painless.FeatureTest.overloadedStatic(false);"));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue