Merge pull request #18658 from rmuir/jodaTime

improve date api for expressions/painless fields
This commit is contained in:
Robert Muir 2016-05-31 12:33:22 -04:00
commit 0373c62a57
16 changed files with 467 additions and 37 deletions

View File

@ -27,6 +27,7 @@ import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
import org.joda.time.MutableDateTime; import org.joda.time.MutableDateTime;
import org.joda.time.ReadableDateTime;
import java.util.AbstractList; import java.util.AbstractList;
import java.util.Collections; import java.util.Collections;
@ -131,7 +132,7 @@ public interface ScriptDocValues<T> extends List<T> {
return Collections.unmodifiableList(this); return Collections.unmodifiableList(this);
} }
public MutableDateTime getDate() { public ReadableDateTime getDate() {
date.setMillis(getValue()); date.setMillis(getValue());
return date; return date;
} }

View File

@ -69,6 +69,8 @@ import java.util.Set;
* <li>{@link org.joda.time.DateTimeUtils}</li> * <li>{@link org.joda.time.DateTimeUtils}</li>
* <li>{@link org.joda.time.DateTimeZone}</li> * <li>{@link org.joda.time.DateTimeZone}</li>
* <li>{@link org.joda.time.Instant}</li> * <li>{@link org.joda.time.Instant}</li>
* <li>{@link org.joda.time.ReadableDateTime}</li>
* <li>{@link org.joda.time.ReadableInstant}</li>
* </ul> * </ul>
*/ */
public final class ClassPermission extends BasicPermission { 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.DateTime.class.getName(),
org.joda.time.DateTimeUtils.class.getName(), org.joda.time.DateTimeUtils.class.getName(),
org.joda.time.DateTimeZone.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()
))); )));
/** /**

View File

@ -42,6 +42,8 @@ scripts, simply set the `lang` parameter to `expression`.
|`doc['field_name'].empty` |A boolean indicating if the field has no |`doc['field_name'].empty` |A boolean indicating if the field has no
values within the doc. 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'].min()` |The minimum value of the field in this document.
|`doc['field_name'].max()` |The maximum 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'].avg()` |The average of the values in this document.
|`doc['field_name'].sum()` |The sum 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`. 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] [float]
=== Date field API === Date field API
Date fields are treated as the number of milliseconds since January 1, 1970 and 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",] [cols="<,<",options="header",]
|======================================================================= |=======================================================================
|Expression |Description |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: 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] [float]
=== `geo_point` field API === `geo_point` field API

View File

@ -35,14 +35,20 @@ final class DateField {
// supported variables // supported variables
static final String VALUE_VARIABLE = "value"; static final String VALUE_VARIABLE = "value";
static final String EMPTY_VARIABLE = "empty"; static final String EMPTY_VARIABLE = "empty";
static final String LENGTH_VARIABLE = "length";
// supported methods // 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 MINIMUM_METHOD = "min";
static final String MAXIMUM_METHOD = "max"; static final String MAXIMUM_METHOD = "max";
static final String AVERAGE_METHOD = "avg"; static final String AVERAGE_METHOD = "avg";
static final String MEDIAN_METHOD = "median"; static final String MEDIAN_METHOD = "median";
static final String SUM_METHOD = "sum"; static final String SUM_METHOD = "sum";
static final String COUNT_METHOD = "count"; static final String COUNT_METHOD = "count";
// date-specific
static final String GET_YEAR_METHOD = "getYear"; static final String GET_YEAR_METHOD = "getYear";
static final String GET_MONTH_METHOD = "getMonth"; static final String GET_MONTH_METHOD = "getMonth";
static final String GET_DAY_OF_MONTH_METHOD = "getDayOfMonth"; static final String GET_DAY_OF_MONTH_METHOD = "getDayOfMonth";
@ -56,6 +62,8 @@ final class DateField {
return new FieldDataValueSource(fieldData, MultiValueMode.MIN); return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
case EMPTY_VARIABLE: case EMPTY_VARIABLE:
return new EmptyMemberValueSource(fieldData); return new EmptyMemberValueSource(fieldData);
case LENGTH_VARIABLE:
return new CountMethodValueSource(fieldData);
default: default:
throw new IllegalArgumentException("Member variable [" + variable + "] does not exist for date field [" + fieldName + "]."); 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) { static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
switch (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: case MINIMUM_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.MIN); return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
case MAXIMUM_METHOD: case MAXIMUM_METHOD:

View File

@ -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 + "].");
}
}
}

View File

@ -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;
}
}

View File

@ -51,6 +51,7 @@ import java.security.AccessController;
import java.security.PrivilegedAction; import java.security.PrivilegedAction;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -148,6 +149,7 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
String fieldname = null; String fieldname = null;
String methodname = null; String methodname = null;
String variablename = "value"; // .value is the default for doc['field'], its optional. 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); VariableContext[] parts = VariableContext.parse(variable);
if (parts[0].text.equals("doc") == false) { if (parts[0].text.equals("doc") == false) {
throw new ParseException("Unknown variable [" + parts[0].text + "]", 0); throw new ParseException("Unknown variable [" + parts[0].text + "]", 0);
@ -167,8 +169,20 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
} }
} }
if (parts.length > 3) { 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()"); throw new IllegalArgumentException("Variable [" + variable + "] does not follow an allowed format of either doc['field'] or doc['field'].method()");
} }
}
MappedFieldType fieldType = mapper.fullName(fieldname); MappedFieldType fieldType = mapper.fullName(fieldname);
@ -191,12 +205,21 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
} }
} else if (fieldType instanceof LegacyDateFieldMapper.DateFieldType || } else if (fieldType instanceof LegacyDateFieldMapper.DateFieldType ||
fieldType instanceof DateFieldMapper.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) { if (methodname == null) {
valueSource = DateField.getVariable(fieldData, fieldname, variablename); valueSource = DateField.getVariable(fieldData, fieldname, variablename);
} else { } else {
valueSource = DateField.getMethod(fieldData, fieldname, methodname); valueSource = DateField.getMethod(fieldData, fieldname, methodname);
} }
}
} else if (fieldData instanceof IndexNumericFieldData) { } else if (fieldData instanceof IndexNumericFieldData) {
// number // number
if (methodname == null) { if (methodname == null) {

View File

@ -34,6 +34,11 @@ final class GeoField {
static final String LAT_VARIABLE = "lat"; static final String LAT_VARIABLE = "lat";
static final String LON_VARIABLE = "lon"; 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) { static ValueSource getVariable(IndexFieldData<?> fieldData, String fieldName, String variable) {
switch (variable) { switch (variable) {
case EMPTY_VARIABLE: case EMPTY_VARIABLE:
@ -48,6 +53,15 @@ final class GeoField {
} }
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) { 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 + "]."); throw new IllegalArgumentException("Member method [" + method + "] does not exist for geo field [" + fieldName + "].");
} }
}
} }

View File

@ -33,8 +33,12 @@ final class NumericField {
// supported variables // supported variables
static final String VALUE_VARIABLE = "value"; static final String VALUE_VARIABLE = "value";
static final String EMPTY_VARIABLE = "empty"; static final String EMPTY_VARIABLE = "empty";
static final String LENGTH_VARIABLE = "length";
// supported methods // 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 MINIMUM_METHOD = "min";
static final String MAXIMUM_METHOD = "max"; static final String MAXIMUM_METHOD = "max";
static final String AVERAGE_METHOD = "avg"; static final String AVERAGE_METHOD = "avg";
@ -48,6 +52,8 @@ final class NumericField {
return new FieldDataValueSource(fieldData, MultiValueMode.MIN); return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
case EMPTY_VARIABLE: case EMPTY_VARIABLE:
return new EmptyMemberValueSource(fieldData); return new EmptyMemberValueSource(fieldData);
case LENGTH_VARIABLE:
return new CountMethodValueSource(fieldData);
default: default:
throw new IllegalArgumentException("Member variable [" + variable + "] does not exist for " + throw new IllegalArgumentException("Member variable [" + variable + "] does not exist for " +
"numeric field [" + fieldName + "]."); "numeric field [" + fieldName + "].");
@ -56,6 +62,12 @@ final class NumericField {
static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) { static ValueSource getMethod(IndexFieldData<?> fieldData, String fieldName, String method) {
switch (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: case MINIMUM_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.MIN); return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
case MAXIMUM_METHOD: case MAXIMUM_METHOD:

View File

@ -170,6 +170,34 @@ public class MoreExpressionTests extends ESIntegTestCase {
assertEquals(1983.0, hits.getAt(1).field("foo").getValue(), 0.0D); 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 { public void testMultiValueMethods() throws Exception {
ElasticsearchAssertions.assertAcked(prepareCreate("test").addMapping("doc", "double0", "type=double", "double1", "type=double", "double2", "type=double")); ElasticsearchAssertions.assertAcked(prepareCreate("test").addMapping("doc", "double0", "type=double", "double1", "type=double", "double2", "type=double"));
ensureGreen("test"); ensureGreen("test");

View File

@ -50,7 +50,8 @@ public final class Definition {
"java.time.zone.txt", "java.time.zone.txt",
"java.util.txt", "java.util.txt",
"java.util.function.txt", "java.util.function.txt",
"java.util.stream.txt")); "java.util.stream.txt",
"joda.time.txt"));
private static final Definition INSTANCE = new Definition(); private static final Definition INSTANCE = new Definition();

View File

@ -99,7 +99,7 @@ public final class Variables {
// Method variables. // Method variables.
// This reference. Internal use only. // 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. // Input map of variables passed to the script.
addVariable("[" + Reserved.PARAMS + "]", Definition.getType("Map"), Reserved.PARAMS, true, true); addVariable("[" + Reserved.PARAMS + "]", Definition.getType("Map"), Reserved.PARAMS, true, true);

View File

@ -643,9 +643,11 @@ class GregorianCalendar -> java.util.GregorianCalendar extends Calendar,Comparab
GregorianCalendar <init>(int,int,int,int,int,int) GregorianCalendar <init>(int,int,int,int,int,int)
GregorianCalendar <init>(TimeZone) GregorianCalendar <init>(TimeZone)
GregorianCalendar <init>(TimeZone,Locale) GregorianCalendar <init>(TimeZone,Locale)
GregorianCalendar from(ZonedDateTime)
Date getGregorianChange() Date getGregorianChange()
boolean isLeapYear(int) boolean isLeapYear(int)
void setGregorianChange(Date) void setGregorianChange(Date)
ZonedDateTime toZonedDateTime()
} }
class HashMap -> java.util.HashMap extends AbstractMap,Map,Object { class HashMap -> java.util.HashMap extends AbstractMap,Map,Object {

View File

@ -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)
}

View File

@ -59,28 +59,29 @@ class def -> java.lang.Object {
#### ES Scripting API #### 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 getLat()
double getLon() 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() String getValue()
List getValues() 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() long getValue()
List getValues() 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() double getValue()
List getValues() List getValues()
} }
class GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints extends List,Collection,Iterable,Object { class org.elasticsearch.index.fielddata.ScriptDocValues.GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints extends List,Collection,Iterable,Object {
GeoPoint getValue() org.elasticsearch.common.geo.GeoPoint getValue()
List getValues() List getValues()
double getLat() double getLat()
double getLon() double getLon()
@ -111,9 +112,9 @@ class GeoPoints -> org.elasticsearch.index.fielddata.ScriptDocValues$GeoPoints e
# for testing. # for testing.
# currently FeatureTest exposes overloaded constructor, field load store, and overloaded static methods # currently FeatureTest exposes overloaded constructor, field load store, and overloaded static methods
class FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object { class org.elasticsearch.painless.FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object {
FeatureTest <init>() org.elasticsearch.painless.FeatureTest <init>()
FeatureTest <init>(int,int) org.elasticsearch.painless.FeatureTest <init>(int,int)
int getX() int getX()
int getY() int getY()
void setX(int) void setX(int)
@ -121,7 +122,3 @@ class FeatureTest -> org.elasticsearch.painless.FeatureTest extends Object {
boolean overloadedStatic() boolean overloadedStatic()
boolean overloadedStatic(boolean) boolean overloadedStatic(boolean)
} }
# currently needed internally
class Executable -> org.elasticsearch.painless.Executable {
}

View File

@ -41,12 +41,14 @@ public class OverloadTests extends ScriptTestCase {
} }
public void testConstructor() { public void testConstructor() {
assertEquals(true, exec("FeatureTest f = new FeatureTest(); return f.x == 0 && f.y == 0;")); assertEquals(true, exec("org.elasticsearch.painless.FeatureTest f = new org.elasticsearch.painless.FeatureTest();" +
assertEquals(true, exec("FeatureTest f = new FeatureTest(1, 2); return f.x == 1 && f.y == 2;")); "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() { public void testStatic() {
assertEquals(true, exec("return FeatureTest.overloadedStatic();")); assertEquals(true, exec("return org.elasticsearch.painless.FeatureTest.overloadedStatic();"));
assertEquals(false, exec("return FeatureTest.overloadedStatic(false);")); assertEquals(false, exec("return org.elasticsearch.painless.FeatureTest.overloadedStatic(false);"));
} }
} }