public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
Expression expr = (Expression)compiledScript;
MapperService mapper = lookup.doc().mapperService();
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
// instead of complicating SimpleBindings (which should stay simple)
SimpleBindings bindings = new SimpleBindings();
ReplaceableConstValueSource specialValue = null;
for (String variable : expr.variables) {
if (variable.equals("_score")) {
bindings.add(new SortField("_score", SortField.Type.SCORE));
} else if (variable.equals("_value")) {
specialValue = new ReplaceableConstValueSource();
bindings.add("_value", specialValue);
// noop: _value is special for aggregations, and is handled in ExpressionScriptBindings
// TODO: if some uses it in a scoring expression, they will get a nasty failure when evaluating...need a
// way to know this is for aggregations and so _value is ok to have...
} else if (vars != null && vars.containsKey(variable)) {
// TODO: document and/or error if vars contains _score?
// NOTE: by checking for the variable in vars first, it allows masking document fields with a global constant,
// but if we were to reverse it, we could provide a way to supply dynamic defaults for documents missing the field?
Object value = vars.get(variable);
if (value instanceof Number) {
bindings.add(variable, new DoubleConstValueSource(((Number)value).doubleValue()));
} else {
throw new ExpressionScriptCompilationException("Parameter [" + variable + "] must be a numeric type");
}
} else {
VariableContext[] parts = VariableContext.parse(variable);
if (parts[0].text.equals("doc") == false) {
throw new ExpressionScriptCompilationException("Unknown variable [" + parts[0].text + "] in expression");
}
if (parts.length < 2 || parts[1].type != VariableContext.Type.STR_INDEX) {
throw new ExpressionScriptCompilationException("Variable 'doc' in expression must be used with a specific field like: doc['myfield'].value");
}
if (parts.length < 3 || parts[2].type != VariableContext.Type.MEMBER || parts[2].text.equals("value") == false) {
throw new ExpressionScriptCompilationException("Invalid member for field data in expression. Only '.value' is currently supported.");
}
String fieldname = parts[1].text;
FieldMapper<?> field = mapper.smartNameFieldMapper(fieldname);
if (field == null) {
throw new ExpressionScriptCompilationException("Field [" + fieldname + "] used in expression does not exist in mappings");
}
if (field.isNumeric() == false) {
// TODO: more context (which expression?)
throw new ExpressionScriptCompilationException("Field [" + fieldname + "] used in expression must be numeric");
}
IndexFieldData<?> fieldData = lookup.doc().fieldDataService().getForField((NumberFieldMapper)field);
bindings.add(variable, new FieldDataValueSource(fieldData));
}
}
return new ExpressionScript((Expression)compiledScript, bindings, specialValue);
}