Skip to content

Commit

Permalink
Scripting: Add Multi-Valued Field Methods to Expressions
Browse files Browse the repository at this point in the history
Add methods to operate on multi-valued fields in the expressions language.
Note that users will still not be able to access individual values
within a multi-valued field.

The following methods will be included:

* min
* max
* avg
* median
* count
* sum

Additionally, changes have been made to MultiValueMode to support the
new median method.

closes #11105
  • Loading branch information
jdconrad committed May 14, 2015
1 parent 1d3a8ad commit a5c0ac0
Show file tree
Hide file tree
Showing 11 changed files with 307 additions and 33 deletions.
2 changes: 2 additions & 0 deletions docs/reference/search/request/sort.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ to. The `mode` option can have the following values:
number based array fields.
`avg`:: Use the average of all values as sort value. Only applicable
for number based array fields.
`median`:: Use the median of all values as sort value. Only applicable
for number based array fields.

===== Sort mode example usage

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.docvalues.DoubleDocValues;
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
import org.elasticsearch.index.fielddata.SortedNumericDoubleValues;

/**
* FunctionValues to get the count of the number of values in a field for a document.
*/
public class CountMethodFunctionValues extends DoubleDocValues {
SortedNumericDoubleValues values;

CountMethodFunctionValues(ValueSource parent, AtomicNumericFieldData fieldData) {
super(parent);

values = fieldData.getDoubleValues();
}

@Override
public double doubleVal(int doc) {
values.setDocument(doc);
return values.count();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.queries.function.FunctionValues;
import org.apache.lucene.queries.function.ValueSource;
import org.elasticsearch.index.fielddata.AtomicFieldData;
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.search.MultiValueMode;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;

/**
* A ValueSource to create FunctionValues to get the count of the number of values in a field for a document.
*/
public class CountMethodValueSource extends ValueSource {
protected IndexFieldData<?> fieldData;

protected CountMethodValueSource(IndexFieldData<?> fieldData) {
Objects.requireNonNull(fieldData);

this.fieldData = fieldData;
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
AtomicFieldData leafData = fieldData.load(leaf);
assert(leafData instanceof AtomicNumericFieldData);

return new CountMethodFunctionValues(this, (AtomicNumericFieldData)leafData);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

FieldDataValueSource that = (FieldDataValueSource) o;

return fieldData.equals(that.fieldData);
}

@Override
public int hashCode() {
return fieldData.hashCode();
}

@Override
public String description() {
return "count: field(" + fieldData.getFieldNames().toString() + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@

import org.apache.lucene.queries.function.ValueSource;
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
import org.elasticsearch.search.MultiValueMode;

class DateMethodFunctionValues extends FieldDataFunctionValues {
private final int calendarType;
private final Calendar calendar;

DateMethodFunctionValues(ValueSource parent, AtomicNumericFieldData data, int calendarType) {
super(parent, data);
DateMethodFunctionValues(ValueSource parent, MultiValueMode multiValueMode, AtomicNumericFieldData data, int calendarType) {
super(parent, multiValueMode, data);

this.calendarType = calendarType;
calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.ROOT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,35 @@
import org.elasticsearch.index.fielddata.AtomicFieldData;
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.search.MultiValueMode;

class DateMethodValueSource extends FieldDataValueSource {

protected final String methodName;
protected final int calendarType;

DateMethodValueSource(IndexFieldData<?> indexFieldData, String methodName, int calendarType) {
super(indexFieldData);
DateMethodValueSource(IndexFieldData<?> indexFieldData, MultiValueMode multiValueMode, String methodName, int calendarType) {
super(indexFieldData, multiValueMode);

Objects.requireNonNull(methodName);

this.methodName = methodName;
this.calendarType = calendarType;
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
AtomicFieldData leafData = fieldData.load(leaf);
assert(leafData instanceof AtomicNumericFieldData);

return new DateMethodFunctionValues(this, multiValueMode, (AtomicNumericFieldData)leafData, calendarType);
}

@Override
public String description() {
return methodName + ": field(" + fieldData.getFieldNames().toString() + ")";
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -64,17 +78,4 @@ public int hashCode() {
result = 31 * result + calendarType;
return result;
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
AtomicFieldData leafData = fieldData.load(leaf);
assert(leafData instanceof AtomicNumericFieldData);

return new DateMethodFunctionValues(this, (AtomicNumericFieldData)leafData, calendarType);
}

@Override
public String description() {
return methodName + ": field(" + fieldData.getFieldNames().toString() + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.lookup.SearchLookup;

import java.text.ParseException;
Expand All @@ -60,6 +61,13 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
protected static final String GET_MINUTES_METHOD = "getMinutes";
protected static final String GET_SECONDS_METHOD = "getSeconds";

protected static final String MINIMUM_METHOD = "min";
protected static final String MAXIMUM_METHOD = "max";
protected static final String AVERAGE_METHOD = "avg";
protected static final String MEDIAN_METHOD = "median";
protected static final String SUM_METHOD = "sum";
protected static final String COUNT_METHOD = "count";

@Inject
public ExpressionScriptEngineService(Settings settings) {
super(settings);
Expand Down Expand Up @@ -156,7 +164,7 @@ public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable

IndexFieldData<?> fieldData = lookup.doc().fieldDataService().getForField((NumberFieldMapper)field);
if (methodname == null) {
bindings.add(variable, new FieldDataValueSource(fieldData));
bindings.add(variable, new FieldDataValueSource(fieldData, MultiValueMode.MIN));
} else {
bindings.add(variable, getMethodValueSource(field, fieldData, fieldname, methodname));
}
Expand All @@ -180,6 +188,18 @@ protected ValueSource getMethodValueSource(FieldMapper<?> field, IndexFieldData<
return getDateMethodValueSource(field, fieldData, fieldName, methodName, Calendar.MINUTE);
case GET_SECONDS_METHOD:
return getDateMethodValueSource(field, fieldData, fieldName, methodName, Calendar.SECOND);
case MINIMUM_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.MIN);
case MAXIMUM_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.MAX);
case AVERAGE_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.AVG);
case MEDIAN_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.MEDIAN);
case SUM_METHOD:
return new FieldDataValueSource(fieldData, MultiValueMode.SUM);
case COUNT_METHOD:
return new CountMethodValueSource(fieldData);
default:
throw new IllegalArgumentException("Member method [" + methodName + "] does not exist.");
}
Expand All @@ -190,7 +210,7 @@ protected ValueSource getDateMethodValueSource(FieldMapper<?> field, IndexFieldD
throw new IllegalArgumentException("Member method [" + methodName + "] can only be used with a date field type, not the field [" + fieldName + "].");
}

return new DateMethodValueSource(fieldData, methodName, calendarType);
return new DateMethodValueSource(fieldData, MultiValueMode.MIN, methodName, calendarType);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
class FieldDataFunctionValues extends DoubleDocValues {
NumericDoubleValues dataAccessor;

FieldDataFunctionValues(ValueSource parent, AtomicNumericFieldData d) {
FieldDataFunctionValues(ValueSource parent, MultiValueMode m, AtomicNumericFieldData d) {
super(parent);
dataAccessor = MultiValueMode.MIN.select(d.getDoubleValues(), 0d);
dataAccessor = m.select(d.getDoubleValues(), 0d);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.index.fielddata.AtomicFieldData;
import org.elasticsearch.index.fielddata.AtomicNumericFieldData;
import org.elasticsearch.index.fielddata.IndexFieldData;
import org.elasticsearch.search.MultiValueMode;

import java.io.IOException;
import java.util.Map;
Expand All @@ -36,18 +37,14 @@
class FieldDataValueSource extends ValueSource {

protected IndexFieldData<?> fieldData;
protected MultiValueMode multiValueMode;

protected FieldDataValueSource(IndexFieldData<?> d) {
protected FieldDataValueSource(IndexFieldData<?> d, MultiValueMode m) {
Objects.requireNonNull(d);
Objects.requireNonNull(m);

fieldData = d;
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
AtomicFieldData leafData = fieldData.load(leaf);
assert(leafData instanceof AtomicNumericFieldData);
return new FieldDataFunctionValues(this, (AtomicNumericFieldData)leafData);
multiValueMode = m;
}

@Override
Expand All @@ -57,12 +54,23 @@ public boolean equals(Object o) {

FieldDataValueSource that = (FieldDataValueSource) o;

return fieldData.equals(that.fieldData);
if (!fieldData.equals(that.fieldData)) return false;
return multiValueMode == that.multiValueMode;

}

@Override
public int hashCode() {
return fieldData.hashCode();
int result = fieldData.hashCode();
result = 31 * result + multiValueMode.hashCode();
return result;
}

@Override
public FunctionValues getValues(Map context, LeafReaderContext leaf) throws IOException {
AtomicFieldData leafData = fieldData.load(leaf);
assert(leafData instanceof AtomicNumericFieldData);
return new FieldDataFunctionValues(this, multiValueMode, (AtomicNumericFieldData)leafData);
}

@Override
Expand Down
Loading

0 comments on commit a5c0ac0

Please sign in to comment.