Removed deprecated 1.x script and template syntax

Closes #13729
This commit is contained in:
Martijn van Groningen 2016-07-12 10:51:58 +02:00
parent 88d3527178
commit 2c3165d080
35 changed files with 365 additions and 1969 deletions

View File

@ -39,8 +39,6 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptService.ScriptType;
@ -637,8 +635,6 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest>
}
public UpdateRequest source(BytesReference source) throws Exception {
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
Map<String, Object> scriptParams = null;
Script script = null;
try (XContentParser parser = XContentFactory.xContent(source).createParser(source)) {
XContentParser.Token token = parser.nextToken();
@ -649,11 +645,8 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest>
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if ("script".equals(currentFieldName) && token == XContentParser.Token.START_OBJECT) {
//here we don't have settings available, unable to throw strict deprecation exceptions
} else if ("script".equals(currentFieldName)) {
script = Script.parse(parser, ParseFieldMatcher.EMPTY);
} else if ("params".equals(currentFieldName)) {
scriptParams = parser.map();
} else if ("scripted_upsert".equals(currentFieldName)) {
scriptedUpsert = parser.booleanValue();
} else if ("upsert".equals(currentFieldName)) {
@ -680,16 +673,6 @@ public class UpdateRequest extends InstanceShardOperationRequest<UpdateRequest>
if (fields != null) {
fields(fields.toArray(new String[fields.size()]));
}
} else {
//here we don't have settings available, unable to throw deprecation exceptions
scriptParameterParser.token(currentFieldName, token, parser, ParseFieldMatcher.EMPTY);
}
}
// Don't have a script using the new API so see if it is specified with the old API
if (script == null) {
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), scriptParams);
}
}
if (script != null) {

View File

@ -25,7 +25,6 @@ import org.apache.lucene.search.Query;
import org.apache.lucene.search.RandomAccessWeight;
import org.apache.lucene.search.Weight;
import org.apache.lucene.util.Bits;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.io.stream.StreamInput;
@ -36,15 +35,12 @@ import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.Script.ScriptField;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.lookup.SearchLookup;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@ -97,11 +93,8 @@ public class ScriptQueryBuilder extends AbstractQueryBuilder<ScriptQueryBuilder>
public static Optional<ScriptQueryBuilder> fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
// also, when caching, since its isCacheable is false, will result in loading all bit set...
Script script = null;
Map<String, Object> params = null;
float boost = AbstractQueryBuilder.DEFAULT_BOOST;
String queryName = null;
@ -116,9 +109,6 @@ public class ScriptQueryBuilder extends AbstractQueryBuilder<ScriptQueryBuilder>
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseContext.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseContext.getParseFieldMatcher());
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, PARAMS_FIELD)) {
// TODO remove in 3.0 (here to support old script APIs)
params = parser.map();
} else {
throw new ParsingException(parser.getTokenLocation(), "[script] query does not support [" + currentFieldName + "]");
}
@ -127,25 +117,14 @@ public class ScriptQueryBuilder extends AbstractQueryBuilder<ScriptQueryBuilder>
queryName = parser.text();
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, AbstractQueryBuilder.BOOST_FIELD)) {
boost = parser.floatValue();
} else if (!scriptParameterParser.token(currentFieldName, token, parser, parseContext.getParseFieldMatcher())) {
} else if (parseContext.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseContext.getParseFieldMatcher());
} else {
throw new ParsingException(parser.getTokenLocation(), "[script] query does not support [" + currentFieldName + "]");
}
}
}
if (script == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
if (params == null) {
params = new HashMap<>();
}
script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
} else if (params != null) {
throw new ParsingException(parser.getTokenLocation(),
"script params must be specified inside script object in a [script] filter");
}
if (script == null) {
throw new ParsingException(parser.getTokenLocation(), "script must be provided with a [script] filter");
}

View File

@ -20,7 +20,6 @@ package org.elasticsearch.index.query;
import org.apache.lucene.search.Query;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
@ -30,13 +29,10 @@ import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.Template;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
@ -48,13 +44,6 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
public static final String NAME = "template";
public static final ParseField QUERY_NAME_FIELD = new ParseField(NAME);
private static final Map<String, ScriptService.ScriptType> parametersToTypes = new HashMap<>();
static {
parametersToTypes.put("query", ScriptService.ScriptType.INLINE);
parametersToTypes.put("file", ScriptService.ScriptType.FILE);
parametersToTypes.put("id", ScriptService.ScriptType.STORED);
}
/** Template to fill. */
private final Template template;
@ -73,32 +62,6 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
return template;
}
/**
* @param template
* the template to use for that query.
* @param vars
* the parameters to fill the template with.
* @deprecated Use {@link #TemplateQueryBuilder(Template)} instead.
* */
@Deprecated
public TemplateQueryBuilder(String template, Map<String, Object> vars) {
this(new Template(template, ScriptService.ScriptType.INLINE, null, null, vars));
}
/**
* @param template
* the template to use for that query.
* @param vars
* the parameters to fill the template with.
* @param templateType
* what kind of template (INLINE,FILE,ID)
* @deprecated Use {@link #TemplateQueryBuilder(Template)} instead.
* */
@Deprecated
public TemplateQueryBuilder(String template, ScriptService.ScriptType templateType, Map<String, Object> vars) {
this(new Template(template, templateType, null, null, vars));
}
/**
* Read from a stream.
*/
@ -118,42 +81,6 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
template.toXContent(builder, builderParams);
}
/**
* In the simplest case, parse template string and variables from the request,
* compile the template and execute the template against the given variables.
*/
public static Optional<TemplateQueryBuilder> fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
Template template = parse(parser, parseContext.getParseFieldMatcher());
return Optional.of(new TemplateQueryBuilder(template));
}
public static Template parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher, String... parameters) throws IOException {
Map<String, ScriptService.ScriptType> parameterMap = new HashMap<>(parametersToTypes);
for (String parameter : parameters) {
parameterMap.put(parameter, ScriptService.ScriptType.INLINE);
}
return parse(parser, parameterMap, parseFieldMatcher);
}
public static Template parse(String defaultLang, XContentParser parser,
ParseFieldMatcher parseFieldMatcher, String... parameters) throws IOException {
Map<String, ScriptService.ScriptType> parameterMap = new HashMap<>(parametersToTypes);
for (String parameter : parameters) {
parameterMap.put(parameter, ScriptService.ScriptType.INLINE);
}
return Template.parse(parser, parameterMap, defaultLang, parseFieldMatcher);
}
public static Template parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException {
return parse(parser, parametersToTypes, parseFieldMatcher);
}
public static Template parse(XContentParser parser, Map<String, ScriptService.ScriptType> parameterMap,
ParseFieldMatcher parseFieldMatcher) throws IOException {
return Template.parse(parser, parameterMap, parseFieldMatcher);
}
@Override
public String getWriteableName() {
return NAME;
@ -182,7 +109,7 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
try (XContentParser qSourceParser = XContentFactory.xContent(querySource).createParser(querySource)) {
final QueryParseContext queryParseContext = queryRewriteContext.newParseContext(qSourceParser);
final QueryBuilder queryBuilder = queryParseContext.parseInnerQueryBuilder().orElseThrow(
() -> new ParsingException(qSourceParser.getTokenLocation(), "inner query in [" + NAME + "] cannot be empty"));;
() -> new ParsingException(qSourceParser.getTokenLocation(), "inner query in [" + NAME + "] cannot be empty"));
if (boost() != DEFAULT_BOOST || queryName() != null) {
final BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.must(queryBuilder);
@ -191,4 +118,14 @@ public class TemplateQueryBuilder extends AbstractQueryBuilder<TemplateQueryBuil
return queryBuilder;
}
}
/**
* In the simplest case, parse template string and variables from the request,
* compile the template and execute the template against the given variables.
*/
public static Optional<TemplateQueryBuilder> fromXContent(QueryParseContext parseContext) throws IOException {
XContentParser parser = parseContext.parser();
Template template = Template.parse(parser, parseContext.getParseFieldMatcher());
return Optional.of(new TemplateQueryBuilder(template));
}
}

View File

@ -32,14 +32,10 @@ import org.elasticsearch.index.query.QueryShardException;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.Script.ScriptField;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.SearchScript;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
@ -111,41 +107,21 @@ public class ScriptScoreFunctionBuilder extends ScoreFunctionBuilder<ScriptScore
public static ScriptScoreFunctionBuilder fromXContent(QueryParseContext parseContext)
throws IOException, ParsingException {
XContentParser parser = parseContext.parser();
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
Script script = null;
Map<String, Object> vars = null;
String currentFieldName = null;
XContentParser.Token token;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
} else {
if (parseContext.getParseFieldMatcher().match(currentFieldName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseContext.getParseFieldMatcher());
} else if ("params".equals(currentFieldName)) { // TODO remove in 3.0 (here to support old script APIs)
vars = parser.map();
} else {
throw new ParsingException(parser.getTokenLocation(), NAME + " query does not support [" + currentFieldName + "]");
}
} else if (token.isValue()) {
if (!scriptParameterParser.token(currentFieldName, token, parser, parseContext.getParseFieldMatcher())) {
throw new ParsingException(parser.getTokenLocation(), NAME + " query does not support [" + currentFieldName + "]");
}
}
}
if (script == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
if (vars == null) {
vars = new HashMap<>();
}
script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), vars);
}
} else if (vars != null) {
throw new ParsingException(parser.getTokenLocation(), "script params must be specified inside script object");
}
if (script == null) {
throw new ParsingException(parser.getTokenLocation(), NAME + " requires 'script' field");
}

View File

@ -33,12 +33,6 @@ import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.support.RestActions;
import org.elasticsearch.rest.action.support.RestStatusToXContentListener;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.rest.RestRequest.Method.POST;
@ -64,18 +58,6 @@ public class RestUpdateAction extends BaseRestHandler {
updateRequest.consistencyLevel(WriteConsistencyLevel.fromString(consistencyLevel));
}
updateRequest.docAsUpsert(request.paramAsBoolean("doc_as_upsert", updateRequest.docAsUpsert()));
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
scriptParameterParser.parseParams(request);
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
Map<String, Object> scriptParams = new HashMap<>();
for (Map.Entry<String, String> entry : request.params().entrySet()) {
if (entry.getKey().startsWith("sp_")) {
scriptParams.put(entry.getKey().substring(3), entry.getValue());
}
}
updateRequest.script(new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), scriptParams));
}
String sField = request.param("fields");
if (sField != null) {
String[] sFields = Strings.splitStringByCommaToArray(sField);

View File

@ -26,24 +26,18 @@ import org.elasticsearch.script.Script.ScriptParseException;
import org.elasticsearch.script.ScriptService.ScriptType;
import java.io.IOException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public abstract class AbstractScriptParser<S extends Script> {
protected abstract String parseInlineScript(XContentParser parser) throws IOException;
public abstract String parseInlineScript(XContentParser parser) throws IOException;
protected abstract S createScript(String script, ScriptType type, String lang, Map<String, Object> params);
protected abstract S createSimpleScript(XContentParser parser) throws IOException;
@Deprecated
protected Map<String, ScriptType> getAdditionalScriptParameters() {
return Collections.emptyMap();
}
public S parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException {
XContentParser.Token token = parser.currentToken();
@ -68,24 +62,24 @@ public abstract class AbstractScriptParser<S extends Script> {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.INLINE.getParseField()) || parseFieldMatcher.match(currentFieldName, ScriptService.SCRIPT_INLINE)) {
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.INLINE.getParseField())) {
type = ScriptType.INLINE;
script = parseInlineScript(parser);
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.FILE.getParseField()) || parseFieldMatcher.match(currentFieldName, ScriptService.SCRIPT_FILE)) {
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.FILE.getParseField())) {
type = ScriptType.FILE;
if (token == XContentParser.Token.VALUE_STRING) {
script = parser.text();
} else {
throw new ScriptParseException("expected a string value for field [{}], but found [{}]", currentFieldName, token);
}
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.STORED.getParseField()) || parseFieldMatcher.match(currentFieldName, ScriptService.SCRIPT_ID)) {
} else if (parseFieldMatcher.match(currentFieldName, ScriptType.STORED.getParseField())) {
type = ScriptType.STORED;
if (token == XContentParser.Token.VALUE_STRING) {
script = parser.text();
} else {
throw new ScriptParseException("expected a string value for field [{}], but found [{}]", currentFieldName, token);
}
} else if (parseFieldMatcher.match(currentFieldName, ScriptField.LANG) || parseFieldMatcher.match(currentFieldName, ScriptService.SCRIPT_LANG)) {
} else if (parseFieldMatcher.match(currentFieldName, ScriptField.LANG)) {
if (token == XContentParser.Token.VALUE_STRING) {
lang = parser.text();
} else {
@ -98,14 +92,7 @@ public abstract class AbstractScriptParser<S extends Script> {
throw new ScriptParseException("expected an object for field [{}], but found [{}]", currentFieldName, token);
}
} else {
// TODO remove this in 3.0
ScriptType paramScriptType = getAdditionalScriptParameters().get(currentFieldName);
if (paramScriptType != null) {
script = parseInlineScript(parser);
type = paramScriptType;
} else {
throw new ScriptParseException("unexpected field [{}]", currentFieldName);
}
throw new ScriptParseException("unexpected field [{}]", currentFieldName);
}
}
if (script == null) {
@ -135,7 +122,7 @@ public abstract class AbstractScriptParser<S extends Script> {
Entry<String, Object> entry = itr.next();
String parameterName = entry.getKey();
Object parameterValue = entry.getValue();
if (parseFieldMatcher.match(parameterName, ScriptField.LANG) || parseFieldMatcher.match(parameterName, ScriptService.SCRIPT_LANG)) {
if (parseFieldMatcher.match(parameterName, ScriptField.LANG)) {
if (parameterValue instanceof String || parameterValue == null) {
lang = (String) parameterValue;
if (removeMatchedEntries) {
@ -153,7 +140,7 @@ public abstract class AbstractScriptParser<S extends Script> {
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
} else if (parseFieldMatcher.match(parameterName, ScriptType.INLINE.getParseField()) || parseFieldMatcher.match(parameterName, ScriptService.SCRIPT_INLINE)) {
} else if (parseFieldMatcher.match(parameterName, ScriptType.INLINE.getParseField())) {
if (parameterValue instanceof String || parameterValue == null) {
script = (String) parameterValue;
type = ScriptType.INLINE;
@ -163,7 +150,7 @@ public abstract class AbstractScriptParser<S extends Script> {
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
} else if (parseFieldMatcher.match(parameterName, ScriptType.FILE.getParseField()) || parseFieldMatcher.match(parameterName, ScriptService.SCRIPT_FILE)) {
} else if (parseFieldMatcher.match(parameterName, ScriptType.FILE.getParseField())) {
if (parameterValue instanceof String || parameterValue == null) {
script = (String) parameterValue;
type = ScriptType.FILE;
@ -173,7 +160,7 @@ public abstract class AbstractScriptParser<S extends Script> {
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
} else if (parseFieldMatcher.match(parameterName, ScriptType.STORED.getParseField()) || parseFieldMatcher.match(parameterName, ScriptService.SCRIPT_ID)) {
} else if (parseFieldMatcher.match(parameterName, ScriptType.STORED.getParseField())) {
if (parameterValue instanceof String || parameterValue == null) {
script = (String) parameterValue;
type = ScriptType.STORED;

View File

@ -121,7 +121,7 @@ public class Script implements ToXContent, Writeable {
}
doWriteTo(out);
}
protected void doWriteTo(StreamOutput out) throws IOException {};
/**
@ -253,7 +253,7 @@ public class Script implements ToXContent, Writeable {
}
@Override
protected String parseInlineScript(XContentParser parser) throws IOException {
public String parseInlineScript(XContentParser parser) throws IOException {
return parser.text();
}
}

View File

@ -23,13 +23,10 @@ import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.cluster.DiffableUtils;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
@ -42,8 +39,6 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
public final class ScriptMetaData implements MetaData.Custom {
@ -70,7 +65,10 @@ public final class ScriptMetaData implements MetaData.Custom {
if (scriptAsBytes == null) {
return null;
}
return parseStoredScript(scriptAsBytes);
}
public static String parseStoredScript(BytesReference scriptAsBytes) {
// Scripts can be stored via API in several ways:
// 1) wrapped into a 'script' json object or field
// 2) wrapped into a 'template' json object or field

View File

@ -1,234 +0,0 @@
/*
* 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;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.xcontent.ToXContent.Params;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.script.Script.ScriptParseException;
import org.elasticsearch.script.ScriptService.ScriptType;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class ScriptParameterParser {
public static final String FILE_SUFFIX = "_file";
public static final String INDEXED_SUFFIX = "_id";
private Map<String, ScriptParameterValue> parameterValues = new HashMap<>();
private Set<ParseField> inlineParameters;
private Set<ParseField> fileParameters;
private Set<ParseField> indexedParameters;
private String lang = null;
public ScriptParameterParser() {
this(null);
}
public ScriptParameterParser(Set<String> parameterNames) {
if (parameterNames == null || parameterNames.isEmpty()) {
inlineParameters = Collections.singleton(ScriptService.SCRIPT_INLINE);
fileParameters = Collections.singleton(ScriptService.SCRIPT_FILE);
indexedParameters = Collections.singleton(ScriptService.SCRIPT_ID);
} else {
inlineParameters = new HashSet<>();
fileParameters = new HashSet<>();
indexedParameters = new HashSet<>();
for (String parameterName : parameterNames) {
if (ParseFieldMatcher.EMPTY.match(parameterName, ScriptService.SCRIPT_LANG)) {
throw new IllegalArgumentException("lang is reserved and cannot be used as a parameter name");
}
inlineParameters.add(new ParseField(parameterName));
fileParameters.add(new ParseField(parameterName + FILE_SUFFIX));
indexedParameters.add(new ParseField(parameterName + INDEXED_SUFFIX));
}
}
}
public boolean token(String currentFieldName, XContentParser.Token token, XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException {
if (token == XContentParser.Token.VALUE_STRING) {
if (parseFieldMatcher.match(currentFieldName, ScriptService.SCRIPT_LANG)) {
lang = parser.text();
return true;
} else {
for (ParseField parameter : inlineParameters) {
if (parseFieldMatcher.match(currentFieldName, parameter)) {
String coreParameterName = parameter.getPreferredName();
putParameterValue(coreParameterName, parser.textOrNull(), ScriptType.INLINE);
return true;
}
}
for (ParseField parameter : fileParameters) {
if (parseFieldMatcher.match(currentFieldName, parameter)) {
String coreParameterName = parameter.getPreferredName().replace(FILE_SUFFIX, "");
putParameterValue(coreParameterName, parser.textOrNull(), ScriptType.FILE);
return true;
}
}
for (ParseField parameter : indexedParameters) {
if (parseFieldMatcher.match(currentFieldName, parameter)) {
String coreParameterName = parameter.getPreferredName().replace(INDEXED_SUFFIX, "");
putParameterValue(coreParameterName, parser.textOrNull(), ScriptType.STORED);
return true;
}
}
}
}
return false;
}
public void parseConfig(Map<String, Object> config, boolean removeMatchedEntries, ParseFieldMatcher parseFieldMatcher) {
for (Iterator<Map.Entry<String, Object>> itr = config.entrySet().iterator(); itr.hasNext();) {
Map.Entry<String, Object> entry = itr.next();
String parameterName = entry.getKey();
Object parameterValue = entry.getValue();
if (parseFieldMatcher.match(parameterName, ScriptService.SCRIPT_LANG)) {
if (parameterValue instanceof String || parameterValue == null) {
lang = (String) parameterValue;
if (removeMatchedEntries) {
itr.remove();
}
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
} else {
for (ParseField parameter : inlineParameters) {
if (parseFieldMatcher.match(parameterName, parameter)) {
String coreParameterName = parameter.getPreferredName();
String stringValue;
if (parameterValue instanceof String) {
stringValue = (String) parameterValue;
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
putParameterValue(coreParameterName, stringValue, ScriptType.INLINE);
if (removeMatchedEntries) {
itr.remove();
}
}
}
for (ParseField parameter : fileParameters) {
if (parseFieldMatcher.match(parameterName, parameter)) {
String coreParameterName = parameter.getPreferredName().replace(FILE_SUFFIX, "");;
String stringValue;
if (parameterValue instanceof String) {
stringValue = (String) parameterValue;
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
putParameterValue(coreParameterName, stringValue, ScriptType.FILE);
if (removeMatchedEntries) {
itr.remove();
}
}
}
for (ParseField parameter : indexedParameters) {
if (parseFieldMatcher.match(parameterName, parameter)) {
String coreParameterName = parameter.getPreferredName().replace(INDEXED_SUFFIX, "");
String stringValue = null;
if (parameterValue instanceof String) {
stringValue = (String) parameterValue;
} else {
throw new ScriptParseException("Value must be of type String: [" + parameterName + "]");
}
putParameterValue(coreParameterName, stringValue, ScriptType.STORED);
if (removeMatchedEntries) {
itr.remove();
}
}
}
}
}
}
private void putParameterValue(String coreParameterName, String script, ScriptType scriptType) {
if (parameterValues.get(coreParameterName) == null) {
parameterValues.put(coreParameterName, new ScriptParameterValue(script, scriptType));
} else {
throw new ScriptParseException("Only one of [" + coreParameterName + ", " + coreParameterName
+ FILE_SUFFIX + ", " + coreParameterName + INDEXED_SUFFIX + "] is allowed.");
}
}
public void parseParams(Params params) {
lang = params.param(ScriptService.SCRIPT_LANG.getPreferredName());
for (ParseField parameter : inlineParameters) {
String value = params.param(parameter.getPreferredName());
if (value != null) {
String coreParameterName = parameter.getPreferredName();
putParameterValue(coreParameterName, value, ScriptType.INLINE);
}
}
for (ParseField parameter : fileParameters) {
String value = params.param(parameter.getPreferredName());
if (value != null) {
String coreParameterName = parameter.getPreferredName().replace(FILE_SUFFIX, "");
putParameterValue(coreParameterName, value, ScriptType.FILE);
}
}
for (ParseField parameter : indexedParameters) {
String value = params.param(parameter.getPreferredName());
if (value != null) {
String coreParameterName = parameter.getPreferredName().replace(INDEXED_SUFFIX, "");
putParameterValue(coreParameterName, value, ScriptType.STORED);
}
}
}
public ScriptParameterValue getDefaultScriptParameterValue() {
return getScriptParameterValue(ScriptService.SCRIPT_INLINE.getPreferredName());
}
public ScriptParameterValue getScriptParameterValue(String parameterName) {
return parameterValues.get(parameterName);
}
public String lang() {
return lang;
}
public static class ScriptParameterValue {
private String script;
private ScriptType scriptType;
public ScriptParameterValue(String script, ScriptType scriptType) {
this.script = script;
this.scriptType = scriptType;
}
public String script() {
return script;
}
public ScriptType scriptType() {
return scriptType;
}
}
}

View File

@ -52,10 +52,7 @@ import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.util.concurrent.ConcurrentCollections;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.env.Environment;
import org.elasticsearch.index.query.TemplateQueryBuilder;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.watcher.FileChangesListener;
import org.elasticsearch.watcher.FileWatcher;
@ -109,31 +106,6 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
private ClusterState clusterState;
/**
* @deprecated Use {@link org.elasticsearch.script.Script.ScriptField} instead. This should be removed in
* 2.0
*/
@Deprecated
public static final ParseField SCRIPT_LANG = new ParseField("lang","script_lang");
/**
* @deprecated Use {@link ScriptType#getParseField()} instead. This should
* be removed in 2.0
*/
@Deprecated
public static final ParseField SCRIPT_FILE = new ParseField("script_file");
/**
* @deprecated Use {@link ScriptType#getParseField()} instead. This should
* be removed in 2.0
*/
@Deprecated
public static final ParseField SCRIPT_ID = new ParseField("script_id");
/**
* @deprecated Use {@link ScriptType#getParseField()} instead. This should
* be removed in 2.0
*/
@Deprecated
public static final ParseField SCRIPT_INLINE = new ParseField("script");
public ScriptService(Settings settings, Environment env,
ResourceWatcherService resourceWatcherService, ScriptEngineRegistry scriptEngineRegistry,
ScriptContextRegistry scriptContextRegistry, ScriptSettings scriptSettings) throws IOException {
@ -346,47 +318,42 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
return script;
}
void validate(String id, String scriptLang, BytesReference scriptBytes) {
void validateStoredScript(String id, String scriptLang, BytesReference scriptBytes) {
validateScriptSize(id, scriptBytes.length());
try (XContentParser parser = XContentFactory.xContent(scriptBytes).createParser(scriptBytes)) {
parser.nextToken();
Template template = TemplateQueryBuilder.parse(scriptLang, parser, parseFieldMatcher, "params", "script", "template");
if (Strings.hasLength(template.getScript())) {
//Just try and compile it
try {
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(scriptLang);
//we don't know yet what the script will be used for, but if all of the operations for this lang with
//indexed scripts are disabled, it makes no sense to even compile it.
if (isAnyScriptContextEnabled(scriptLang, ScriptType.STORED)) {
Object compiled = scriptEngineService.compile(id, template.getScript(), Collections.emptyMap());
if (compiled == null) {
throw new IllegalArgumentException("Unable to parse [" + template.getScript() +
"] lang [" + scriptLang + "] (ScriptService.compile returned null)");
}
} else {
logger.warn(
"skipping compile of script [{}], lang [{}] as all scripted operations are disabled for indexed scripts",
template.getScript(), scriptLang);
String script = ScriptMetaData.parseStoredScript(scriptBytes);
if (Strings.hasLength(scriptBytes)) {
//Just try and compile it
try {
ScriptEngineService scriptEngineService = getScriptEngineServiceForLang(scriptLang);
//we don't know yet what the script will be used for, but if all of the operations for this lang with
//indexed scripts are disabled, it makes no sense to even compile it.
if (isAnyScriptContextEnabled(scriptLang, ScriptType.STORED)) {
Object compiled = scriptEngineService.compile(id, script, Collections.emptyMap());
if (compiled == null) {
throw new IllegalArgumentException("Unable to parse [" + script + "] lang [" + scriptLang +
"] (ScriptService.compile returned null)");
}
} catch (ScriptException good) {
// TODO: remove this when all script engines have good exceptions!
throw good; // its already good!
} catch (Exception e) {
throw new IllegalArgumentException("Unable to parse [" + template.getScript() +
"] lang [" + scriptLang + "]", e);
} else {
logger.warn(
"skipping compile of script [{}], lang [{}] as all scripted operations are disabled for indexed scripts",
script, scriptLang);
}
} else {
throw new IllegalArgumentException("Unable to find script in : " + scriptBytes.utf8ToString());
} catch (ScriptException good) {
// TODO: remove this when all script engines have good exceptions!
throw good; // its already good!
} catch (Exception e) {
throw new IllegalArgumentException("Unable to parse [" + script +
"] lang [" + scriptLang + "]", e);
}
} catch (IOException e) {
throw new IllegalArgumentException("failed to parse template script", e);
} else {
throw new IllegalArgumentException("Unable to find script in : " + scriptBytes.utf8ToString());
}
}
public void storeScript(ClusterService clusterService, PutStoredScriptRequest request, ActionListener<PutStoredScriptResponse> listener) {
String scriptLang = validateScriptLanguage(request.scriptLang());
//verify that the script compiles
validate(request.id(), scriptLang, request.script());
validateStoredScript(request.id(), scriptLang, request.script());
clusterService.submitStateUpdateTask("put-script-" + request.id(), new AckedClusterStateUpdateTask<PutStoredScriptResponse>(request, listener) {
@Override

View File

@ -31,7 +31,6 @@ import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.ScriptService.ScriptType;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
public class Template extends Script {
@ -112,21 +111,11 @@ public class Template extends Script {
}
public static Script parse(Map<String, Object> config, boolean removeMatchedEntries, ParseFieldMatcher parseFieldMatcher) {
return new TemplateParser(Collections.emptyMap(), DEFAULT_LANG).parse(config, removeMatchedEntries, parseFieldMatcher);
return new TemplateParser(DEFAULT_LANG).parse(config, removeMatchedEntries, parseFieldMatcher);
}
public static Template parse(XContentParser parser, ParseFieldMatcher parseFieldMatcher) throws IOException {
return new TemplateParser(Collections.emptyMap(), DEFAULT_LANG).parse(parser, parseFieldMatcher);
}
@Deprecated
public static Template parse(XContentParser parser, Map<String, ScriptType> additionalTemplateFieldNames, ParseFieldMatcher parseFieldMatcher) throws IOException {
return new TemplateParser(additionalTemplateFieldNames, DEFAULT_LANG).parse(parser, parseFieldMatcher);
}
@Deprecated
public static Template parse(XContentParser parser, Map<String, ScriptType> additionalTemplateFieldNames, String defaultLang, ParseFieldMatcher parseFieldMatcher) throws IOException {
return new TemplateParser(additionalTemplateFieldNames, defaultLang).parse(parser, parseFieldMatcher);
return new TemplateParser(DEFAULT_LANG).parse(parser, parseFieldMatcher);
}
@Override
@ -150,11 +139,9 @@ public class Template extends Script {
private static class TemplateParser extends AbstractScriptParser<Template> {
private XContentType contentType = null;
private final Map<String, ScriptType> additionalTemplateFieldNames;
private String defaultLang;
public TemplateParser(Map<String, ScriptType> additionalTemplateFieldNames, String defaultLang) {
this.additionalTemplateFieldNames = additionalTemplateFieldNames;
public TemplateParser(String defaultLang) {
this.defaultLang = defaultLang;
}
@ -169,7 +156,7 @@ public class Template extends Script {
}
@Override
protected String parseInlineScript(XContentParser parser) throws IOException {
public String parseInlineScript(XContentParser parser) throws IOException {
if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
contentType = parser.contentType();
XContentBuilder builder = XContentFactory.contentBuilder(contentType);
@ -179,11 +166,6 @@ public class Template extends Script {
}
}
@Override
protected Map<String, ScriptType> getAdditionalScriptParameters() {
return additionalTemplateFieldNames;
}
@Override
protected String getDefaultScriptLang() {
return defaultLang;

View File

@ -33,16 +33,12 @@ import org.elasticsearch.script.ExecutableScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.Script.ScriptField;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.search.aggregations.InternalAggregation;
import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class ScriptHeuristic extends SignificanceHeuristic {
@ -155,37 +151,19 @@ public class ScriptHeuristic extends SignificanceHeuristic {
String heuristicName = parser.currentName();
Script script = null;
XContentParser.Token token;
Map<String, Object> params = null;
String currentFieldName = null;
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token.equals(XContentParser.Token.FIELD_NAME)) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
} else {
if (parseFieldMatcher.match(currentFieldName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseFieldMatcher);
} else if ("params".equals(currentFieldName)) { // TODO remove in 3.0 (here to support old script APIs)
params = parser.map();
} else {
throw new ElasticsearchParseException("failed to parse [{}] significance heuristic. unknown object [{}]", heuristicName, currentFieldName);
}
} else if (!scriptParameterParser.token(currentFieldName, token, parser, parseFieldMatcher)) {
throw new ElasticsearchParseException("failed to parse [{}] significance heuristic. unknown field [{}]", heuristicName, currentFieldName);
}
}
if (script == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
if (params == null) {
params = new HashMap<>();
}
script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
} else if (params != null) {
throw new ElasticsearchParseException("failed to parse [{}] significance heuristic. script params must be specified inside script object", heuristicName);
}
if (script == null) {
throw new ElasticsearchParseException("failed to parse [{}] significance heuristic. no script found in script_heuristic", heuristicName);
}

View File

@ -27,12 +27,10 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
import org.elasticsearch.search.aggregations.AggregatorFactories.Builder;
import org.elasticsearch.search.aggregations.InternalAggregation.Type;
import org.elasticsearch.search.aggregations.AggregatorFactory;
import org.elasticsearch.search.aggregations.InternalAggregation.Type;
import org.elasticsearch.search.aggregations.support.AggregationContext;
import java.io.IOException;
@ -52,7 +50,6 @@ public class ScriptedMetricAggregationBuilder extends AbstractAggregationBuilder
private static final ParseField COMBINE_SCRIPT_FIELD = new ParseField("combine_script");
private static final ParseField REDUCE_SCRIPT_FIELD = new ParseField("reduce_script");
private static final ParseField PARAMS_FIELD = new ParseField("params");
private static final ParseField REDUCE_PARAMS_FIELD = new ParseField("reduce_params");
private Script initScript;
private Script mapScript;
@ -222,7 +219,6 @@ public class ScriptedMetricAggregationBuilder extends AbstractAggregationBuilder
Script combineScript = null;
Script reduceScript = null;
Map<String, Object> params = null;
Map<String, Object> reduceParams = null;
XContentParser.Token token;
String currentFieldName = null;
Set<String> scriptParameters = new HashSet<>();
@ -230,13 +226,12 @@ public class ScriptedMetricAggregationBuilder extends AbstractAggregationBuilder
scriptParameters.add(MAP_SCRIPT_FIELD.getPreferredName());
scriptParameters.add(COMBINE_SCRIPT_FIELD.getPreferredName());
scriptParameters.add(REDUCE_SCRIPT_FIELD.getPreferredName());
ScriptParameterParser scriptParameterParser = new ScriptParameterParser(scriptParameters);
XContentParser parser = context.parser();
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
} else if (token == XContentParser.Token.START_OBJECT || token == XContentParser.Token.VALUE_STRING) {
if (context.getParseFieldMatcher().match(currentFieldName, INIT_SCRIPT_FIELD)) {
initScript = Script.parse(parser, context.getParseFieldMatcher());
} else if (context.getParseFieldMatcher().match(currentFieldName, MAP_SCRIPT_FIELD)) {
@ -245,64 +240,18 @@ public class ScriptedMetricAggregationBuilder extends AbstractAggregationBuilder
combineScript = Script.parse(parser, context.getParseFieldMatcher());
} else if (context.getParseFieldMatcher().match(currentFieldName, REDUCE_SCRIPT_FIELD)) {
reduceScript = Script.parse(parser, context.getParseFieldMatcher());
} else if (context.getParseFieldMatcher().match(currentFieldName, PARAMS_FIELD)) {
} else if (token == XContentParser.Token.START_OBJECT &&
context.getParseFieldMatcher().match(currentFieldName, PARAMS_FIELD)) {
params = parser.map();
} else if (context.getParseFieldMatcher().match(currentFieldName, REDUCE_PARAMS_FIELD)) {
reduceParams = parser.map();
} else {
throw new ParsingException(parser.getTokenLocation(),
"Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "].");
}
} else if (token.isValue()) {
if (!scriptParameterParser.token(currentFieldName, token, parser, context.getParseFieldMatcher())) {
throw new ParsingException(parser.getTokenLocation(),
"Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "].");
}
} else {
throw new ParsingException(parser.getTokenLocation(), "Unexpected token " + token + " in [" + aggregationName + "].");
}
}
if (initScript == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getScriptParameterValue(INIT_SCRIPT_FIELD.getPreferredName());
if (scriptValue != null) {
initScript = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
} else if (initScript.getParams() != null) {
throw new ParsingException(parser.getTokenLocation(), "init_script params are not supported. Parameters for the "
+ "init_script must be specified in the params field on the scripted_metric aggregator not inside the init_script "
+ "object");
}
if (mapScript == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getScriptParameterValue(MAP_SCRIPT_FIELD.getPreferredName());
if (scriptValue != null) {
mapScript = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
} else if (mapScript.getParams() != null) {
throw new ParsingException(parser.getTokenLocation(), "map_script params are not supported. Parameters for the map_script "
+ "must be specified in the params field on the scripted_metric aggregator not inside the map_script object");
}
if (combineScript == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getScriptParameterValue(COMBINE_SCRIPT_FIELD.getPreferredName());
if (scriptValue != null) {
combineScript = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
} else if (combineScript.getParams() != null) {
throw new ParsingException(parser.getTokenLocation(),
"combine_script params are not supported. Parameters for the "
+ "combine_script must be specified in the params field on the scripted_metric aggregator not inside the "
+ "combine_script object");
}
if (reduceScript == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getScriptParameterValue(REDUCE_SCRIPT_FIELD.getPreferredName());
if (scriptValue != null) {
reduceScript = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), reduceParams);
}
}
if (mapScript == null) {
throw new ParsingException(parser.getTokenLocation(), "map_script field is required in [" + aggregationName + "].");
}

View File

@ -49,8 +49,6 @@ import org.elasticsearch.script.LeafSearchScript;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.Script.ScriptField;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptParameterParser;
import org.elasticsearch.script.ScriptParameterParser.ScriptParameterValue;
import org.elasticsearch.script.SearchScript;
import org.elasticsearch.search.DocValueFormat;
import org.elasticsearch.search.MultiValueMode;
@ -74,7 +72,6 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
public static final ParseField SORTMODE_FIELD = new ParseField("mode");
public static final ParseField NESTED_PATH_FIELD = new ParseField("nested_path");
public static final ParseField NESTED_FILTER_FIELD = new ParseField("nested_filter");
public static final ParseField PARAMS_FIELD = new ParseField("params");
private final Script script;
@ -231,7 +228,6 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
* in '{ "foo": { "order" : "asc"} }'. When parsing the inner object, the field name can be passed in via this argument
*/
public static ScriptSortBuilder fromXContent(QueryParseContext context, String elementName) throws IOException {
ScriptParameterParser scriptParameterParser = new ScriptParameterParser();
XContentParser parser = context.parser();
ParseFieldMatcher parseField = context.getParseFieldMatcher();
Script script = null;
@ -240,7 +236,6 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
SortOrder order = null;
Optional<QueryBuilder> nestedFilter = Optional.empty();
String nestedPath = null;
Map<String, Object> params = new HashMap<>();
XContentParser.Token token;
String currentName = parser.currentName();
@ -250,8 +245,6 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
} else if (token == XContentParser.Token.START_OBJECT) {
if (parseField.match(currentName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseField);
} else if (parseField.match(currentName, PARAMS_FIELD)) {
params = parser.map();
} else if (parseField.match(currentName, NESTED_FILTER_FIELD)) {
nestedFilter = context.parseInnerQueryBuilder();
} else {
@ -260,14 +253,14 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
} else if (token.isValue()) {
if (parseField.match(currentName, ORDER_FIELD)) {
order = SortOrder.fromString(parser.text());
} else if (scriptParameterParser.token(currentName, token, parser, parseField)) {
// Do Nothing (handled by ScriptParameterParser
} else if (parseField.match(currentName, TYPE_FIELD)) {
type = ScriptSortType.fromString(parser.text());
} else if (parseField.match(currentName, SORTMODE_FIELD)) {
sortMode = SortMode.fromString(parser.text());
} else if (parseField.match(currentName, NESTED_PATH_FIELD)) {
nestedPath = parser.text();
} else if (parseField.match(currentName, ScriptField.SCRIPT)) {
script = Script.parse(parser, parseField);
} else {
throw new ParsingException(parser.getTokenLocation(), "[" + NAME + "] failed to parse field [" + currentName + "]");
}
@ -276,16 +269,6 @@ public class ScriptSortBuilder extends SortBuilder<ScriptSortBuilder> {
}
}
if (script == null) { // Didn't find anything using the new API so try using the old one instead
ScriptParameterValue scriptValue = scriptParameterParser.getDefaultScriptParameterValue();
if (scriptValue != null) {
if (params == null) {
params = new HashMap<>();
}
script = new Script(scriptValue.script(), scriptValue.scriptType(), scriptParameterParser.lang(), params);
}
}
ScriptSortBuilder result = new ScriptSortBuilder(script, type);
if (order != null) {
result.order(order);

View File

@ -59,6 +59,18 @@ public class UpdateRequestTests extends ESTestCase {
Map<String, Object> params = script.getParams();
assertThat(params, nullValue());
// simple verbose script
request.source(XContentFactory.jsonBuilder().startObject()
.startObject("script").field("inline", "script1").endObject()
.endObject());
script = request.script();
assertThat(script, notNullValue());
assertThat(script.getScript(), equalTo("script1"));
assertThat(script.getType(), equalTo(ScriptType.INLINE));
assertThat(script.getLang(), nullValue());
params = script.getParams();
assertThat(params, nullValue());
// script with params
request = new UpdateRequest("test", "type", "1");
request.source(XContentFactory.jsonBuilder().startObject().startObject("script").field("inline", "script1").startObject("params")

View File

@ -48,7 +48,7 @@ public class ScriptQueryBuilderTests extends AbstractQueryTestCase<ScriptQueryBu
expectThrows(IllegalArgumentException.class, () -> new ScriptQueryBuilder((Script) null));
}
public void testFromJson() throws IOException {
public void testFromJsonVerbose() throws IOException {
String json =
"{\n" +
" \"script\" : {\n" +
@ -67,4 +67,18 @@ public class ScriptQueryBuilderTests extends AbstractQueryTestCase<ScriptQueryBu
assertEquals(json, "mockscript", parsed.script().getLang());
}
public void testFromJson() throws IOException {
String json =
"{\n" +
" \"script\" : {\n" +
" \"script\" : \"5\"," +
" \"boost\" : 1.0,\n" +
" \"_name\" : \"PcKdEyPOmR\"\n" +
" }\n" +
"}";
ScriptQueryBuilder parsed = (ScriptQueryBuilder) parseQuery(json);
assertEquals(json, "5", parsed.script().getScript());
}
}

View File

@ -100,7 +100,7 @@ public class TemplateQueryBuilderTests extends AbstractQueryTestCase<TemplateQue
public void testRawEscapedTemplate() throws IOException {
String expectedTemplateString = "{\"match_{{template}}\": {}}\"";
String query = "{\"template\": {\"query\": \"{\\\"match_{{template}}\\\": {}}\\\"\",\"params\" : {\"template\" : \"all\"}}}";
String query = "{\"template\": {\"inline\": \"{\\\"match_{{template}}\\\": {}}\\\"\",\"params\" : {\"template\" : \"all\"}}}";
Map<String, Object> params = new HashMap<>();
params.put("template", "all");
QueryBuilder expectedBuilder = new TemplateQueryBuilder(new Template(expectedTemplateString, ScriptType.INLINE, null, null,
@ -110,7 +110,7 @@ public class TemplateQueryBuilderTests extends AbstractQueryTestCase<TemplateQue
public void testRawTemplate() throws IOException {
String expectedTemplateString = "{\"match_{{template}}\":{}}";
String query = "{\"template\": {\"query\": {\"match_{{template}}\": {}},\"params\" : {\"template\" : \"all\"}}}";
String query = "{\"template\": {\"inline\": {\"match_{{template}}\": {}},\"params\" : {\"template\" : \"all\"}}}";
Map<String, Object> params = new HashMap<>();
params.put("template", "all");
QueryBuilder expectedBuilder = new TemplateQueryBuilder(new Template(expectedTemplateString, ScriptType.INLINE, null,

View File

@ -44,7 +44,6 @@ import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.CoreMatchers.containsString;
@ -444,12 +443,12 @@ public class ScriptServiceTests extends ESTestCase {
int maxSize = 0xFFFF;
buildScriptService(Settings.EMPTY);
// allowed
scriptService.validate("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 13) + "\"}"));
scriptService.validateStoredScript("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 13) + "\"}"));
// disallowed
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> {
scriptService.validate("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 12) + "\"}"));
scriptService.validateStoredScript("_id", "test", new BytesArray("{\"script\":\"" + randomAsciiOfLength(maxSize - 12) + "\"}"));
});
assertThat(e.getMessage(), equalTo(
"Limit of script size in bytes [" + maxSize+ "] has been exceeded for script [_id] with size [" + (maxSize + 1) + "]"));

View File

@ -192,16 +192,13 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
assertNull(builder.getNestedPath());
}
public void testParseJsonOldStyle() throws IOException {
public void testParseJson_simple() throws IOException {
String scriptSort = "{\n" +
"\"_script\" : {\n" +
"\"type\" : \"number\",\n" +
"\"script\" : \"doc['field_name'].value * factor\",\n" +
"\"params\" : {\n" +
"\"factor\" : 1.1\n" +
"},\n" +
"\"mode\" : \"max\",\n" +
"\"order\" : \"asc\"\n" +
"\"type\" : \"number\",\n" +
"\"script\" : \"doc['field_name'].value\",\n" +
"\"mode\" : \"max\",\n" +
"\"order\" : \"asc\"\n" +
"} }\n";
XContentParser parser = XContentFactory.xContent(scriptSort).createParser(scriptSort);
parser.nextToken();
@ -210,9 +207,9 @@ public class ScriptSortBuilderTests extends AbstractSortTestCase<ScriptSortBuild
QueryParseContext context = new QueryParseContext(indicesQueriesRegistry, parser, ParseFieldMatcher.STRICT);
ScriptSortBuilder builder = ScriptSortBuilder.fromXContent(context, null);
assertEquals("doc['field_name'].value * factor", builder.script().getScript());
assertEquals("doc['field_name'].value", builder.script().getScript());
assertNull(builder.script().getLang());
assertEquals(1.1, builder.script().getParams().get("factor"));
assertNull(builder.script().getParams());
assertEquals(ScriptType.INLINE, builder.script().getType());
assertEquals(ScriptSortType.NUMBER, builder.type());
assertEquals(SortOrder.ASC, builder.order());

View File

@ -1,7 +1,7 @@
{ "update" : {"_id" : "1", "_retry_on_conflict" : 2} }
{ "doc" : {"field" : "value"} }
{ "update" : { "_id" : "0", "_type" : "type1", "_index" : "index1" } }
{ "script" : "counter += param1", "lang" : "javascript", "params" : {"param1" : 1}, "upsert" : {"counter" : 1}}
{ "script" : { "inline" : "counter += param1", "lang" : "javascript", "params" : {"param1" : 1}}, "upsert" : {"counter" : 1}}
{ "delete" : { "_id" : "2" } }
{ "create" : { "_id" : "3" } }
{ "field1" : "value3" }

View File

@ -1,6 +1,186 @@
[[breaking_50_scripting]]
=== Script related changes
==== Removed 1.x script and template syntax
The deprecated 1.x syntax of defining inline scripts / templates and referring to file or index base scripts / templates
have been removed.
The `script` and `params` string parameters can no longer be used and instead the `script` object syntax must be used.
This applies for the update api, script sort, `script_score` function, `script` query, `scripted_metric` aggregation and
`script_heuristic` aggregation.
So this usage of inline scripts is no longer allowed:
[source,js]
-----------------------------------
{
"script_score": {
"lang": "groovy",
"script": "Math.log(_score * 2) + my_modifier",
"params": {
"my_modifier": 8
}
}
}
-----------------------------------
and instead this syntax must be used:
[source,js]
-----------------------------------
{
"script_score": {
"script": {
"lang": "groovy",
"inline": "Math.log(_score * 2) + my_modifier",
"params": {
"my_modifier": 8
}
}
}
}
-----------------------------------
The `script` or `script_file` parameter can no longer be used to refer to file based scripts and templates and instead
`file` must be used.
This usage of referring to file based scripts is no longer valid:
[source,js]
-----------------------------------
{
"script_score": {
"script": "calculate-score",
"params": {
"my_modifier": 8
}
}
}
-----------------------------------
This usage is valid:
[source,js]
-----------------------------------
{
"script_score": {
"script": {
"lang": "groovy",
"file": "calculate-score",
"params": {
"my_modifier": 8
}
}
}
}
-----------------------------------
The `script_id` parameter can no longer be used the refer to indexed based scripts and templates and instead `id` must
be used.
This usage of referring to indexed scripts is no longer valid:
[source,js]
-----------------------------------
{
"script_score": {
"script_id": "indexedCalculateScore",
"params": {
"my_modifier": 8
}
}
}
-----------------------------------
This usage is valid:
[source,js]
-----------------------------------
{
"script_score": {
"script": {
"id": "indexedCalculateScore",
"lang" : "groovy",
"params": {
"my_modifier": 8
}
}
}
}
-----------------------------------
====== Template query
The `query` field in the `template` query can no longer be used.
This 1.x syntax can no longer be used:
[source,js]
-----------------------------------
{
"query": {
"template": {
"query": {"match_{{template}}": {}},
"params" : {
"template" : "all"
}
}
}
}
-----------------------------------
and instead the following syntax should be used:
[source,js]
-----------------------------------
{
"query": {
"template": {
"inline": {"match_{{template}}": {}},
"params" : {
"template" : "all"
}
}
}
}
===== Search templates:
The top level `template` field in the search template api has been replaced with consistent template / script object
syntax. This 1.x syntax can no longer be used:
[source,js]
-----------------------------------
{
"template" : {
"query": { "match" : { "{{my_field}}" : "{{my_value}}" } },
"size" : "{{my_size}}"
},
"params" : {
"my_field" : "foo",
"my_value" : "bar",
"my_size" : 5
}
}
-----------------------------------
and instead the following syntax should be used:
[source,js]
-----------------------------------
{
"inline" : {
"query": { "match" : { "{{my_field}}" : "{{my_value}}" } },
"size" : "{{my_size}}"
},
"params" : {
"my_field" : "foo",
"my_value" : "bar",
"my_size" : 5
}
}
-----------------------------------
==== Indexed scripts and templates
Indexed scripts and templates have been replaced by <<modules-scripting-stored-scripts,stored scripts>>
@ -13,8 +193,8 @@ If scripts are really large, other options like native scripts should be conside
Previously indexed scripts in the `.scripts` index will not be used any more as
Elasticsearch will now try to fetch the scripts from the cluster state. Upon upgrading
to 5.x the `.scripts` index will remain to exist, so it can be used by a script to migrate
the stored scripts from the `.scripts` index into the cluster state. The format of the scripts
hasn't changed.
the stored scripts from the `.scripts` index into the cluster state. The current format of the scripts
and templates hasn't been changed, only the 1.x format has been removed.
===== Python migration script
@ -104,3 +284,7 @@ engine doing this was the Javascript engine, which registered "js" and
The Javascript engine previously registered "js" and "javascript". It now only
registers the "js" file extension for on-disk scripts.
==== Removed scripting query string parameters from update rest api
The `script`, `script_id` and `scripting_upsert` query string parameters have been removed from the update api.

View File

@ -455,16 +455,21 @@ public class BulkTests extends ESIntegTestCase {
byte[] addParent = new BytesArray("{\"index\" : { \"_index\" : \"test\", \"_type\" : \"parent\", \"_id\" : \"parent1\"}}\n" +
"{\"field1\" : \"value1\"}\n").array();
byte[] addChild = new BytesArray("{\"update\" : { \"_id\" : \"child1\", \"_type\" : \"child\", \"_index\" : \"test\", \"parent\" : \"parent1\"} }\n" +
byte[] addChild1 = new BytesArray("{\"update\" : { \"_id\" : \"child1\", \"_type\" : \"child\", \"_index\" : \"test\", \"parent\" : \"parent1\"} }\n" +
"{ \"script\" : {\"inline\" : \"ctx._source.field2 = 'value2'\"}, \"upsert\" : {\"field1\" : \"value1\"}}\n").array();
byte[] addChild2 = new BytesArray("{\"update\" : { \"_id\" : \"child1\", \"_type\" : \"child\", \"_index\" : \"test\", \"parent\" : \"parent1\"} }\n" +
"{ \"script\" : \"ctx._source.field2 = 'value2'\", \"upsert\" : {\"field1\" : \"value1\"}}\n").array();
builder.add(addParent, 0, addParent.length);
builder.add(addChild, 0, addChild.length);
builder.add(addChild1, 0, addChild1.length);
builder.add(addChild2, 0, addChild2.length);
BulkResponse bulkResponse = builder.get();
assertThat(bulkResponse.getItems().length, equalTo(2));
assertThat(bulkResponse.getItems().length, equalTo(3));
assertThat(bulkResponse.getItems()[0].isFailed(), equalTo(false));
assertThat(bulkResponse.getItems()[1].isFailed(), equalTo(false));
assertThat(bulkResponse.getItems()[2].isFailed(), equalTo(false));
client().admin().indices().prepareRefresh("test").get();

View File

@ -18,11 +18,11 @@
index: test_1
type: test
id: 1
script: "1"
body:
lang: groovy
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
lang: groovy
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }
- match: { _index: test_1 }
- match: { _type: test }
@ -43,8 +43,10 @@
index: test_1
type: test
id: 1
lang: groovy
script: "ctx._source.foo = 'yyy'"
body:
script:
lang: groovy
inline: "ctx._source.foo = 'yyy'"
- match: { _index: test_1 }
- match: { _type: test }
@ -67,9 +69,10 @@
type: test
id: 1
body:
script: "1"
lang: "doesnotexist"
params: { bar: 'xxx' }
script:
inline: "1"
lang: "doesnotexist"
params: { bar: 'xxx' }
- do:
catch: /script_lang not supported \[doesnotexist\]/
@ -77,6 +80,8 @@
index: test_1
type: test
id: 1
lang: doesnotexist
script: "1"
body:
script:
lang: doesnotexist
inline: "1"

View File

@ -10,8 +10,9 @@
type: test
id: 1
body:
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }
upsert: { foo: baz }
- do:
@ -29,8 +30,9 @@
type: test
id: 1
body:
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }
upsert: { foo: baz }
- do:
@ -47,8 +49,9 @@
type: test
id: 2
body:
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }
upsert: { foo: baz }
scripted_upsert: true

View File

@ -31,8 +31,9 @@
type: test
id: 1
body:
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }
- do:
update:
@ -41,5 +42,6 @@
id: 1
ignore: 404
body:
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = bar"
params: { bar: 'xxx' }

View File

@ -153,7 +153,7 @@ public class TemplateQueryParserTests extends ESTestCase {
}
public void testParser() throws IOException {
String templateString = "{" + "\"query\":{\"match_{{template}}\": {}}," + "\"params\":{\"template\":\"all\"}" + "}";
String templateString = "{" + "\"inline\":{\"match_{{template}}\": {}}," + "\"params\":{\"template\":\"all\"}" + "}";
XContentParser templateSourceParser = XContentFactory.xContent(templateString).createParser(templateString);
QueryShardContext context = contextFactory.get();

View File

@ -26,12 +26,9 @@ import org.elasticsearch.action.search.template.SearchTemplateAction;
import org.elasticsearch.action.search.template.SearchTemplateRequest;
import org.elasticsearch.action.search.template.SearchTemplateRequestBuilder;
import org.elasticsearch.action.search.template.SearchTemplateResponse;
import org.elasticsearch.common.ParseFieldMatcher;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.env.Environment;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TemplateQueryBuilder;
@ -158,12 +155,6 @@ public class TemplateQueryTests extends ESIntegTestCase {
assertHitCount(response.getResponse(), 2);
}
private Template parseTemplate(String template) throws IOException {
try (XContentParser parser = XContentFactory.xContent(template).createParser(template)) {
return TemplateQueryBuilder.parse(parser, ParseFieldMatcher.EMPTY, "params", "template");
}
}
// Relates to #6318
public void testSearchRequestFail() throws Exception {
String query = "{ \"query\": {\"match_all\": {}}, \"size\" : \"{{my_size}}\" }";

View File

@ -18,31 +18,31 @@
- do:
search:
body: { "query": { "template": { "query": { "term": { "text": { "value": "{{template}}" } } }, "params": { "template": "value1" } } } }
body: { "query": { "template": { "inline": { "term": { "text": { "value": "{{template}}" } } }, "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
search:
body: { "query": { "template": { "query": {"match_{{template}}": {}}, "params" : { "template" : "all" } } } }
body: { "query": { "template": { "inline": {"match_{{template}}": {}}, "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
search:
body: { "query": { "template": { "query": "{ \"term\": { \"text\": { \"value\": \"{{template}}\" } } }", "params": { "template": "value1" } } } }
body: { "query": { "template": { "inline": "{ \"term\": { \"text\": { \"value\": \"{{template}}\" } } }", "params": { "template": "value1" } } } }
- match: { hits.total: 1 }
- do:
search:
body: { "query": { "template": { "query": "{\"match_{{template}}\": {}}", "params" : { "template" : "all" } } } }
body: { "query": { "template": { "inline": "{\"match_{{template}}\": {}}", "params" : { "template" : "all" } } } }
- match: { hits.total: 2 }
- do:
search:
body: { "query": { "template": { "query": "{\"query_string\": { \"query\" : \"{{query}}\" }}", "params" : { "query" : "text:\"value2 value3\"" } } } }
body: { "query": { "template": { "inline": "{\"query_string\": { \"query\" : \"{{query}}\" }}", "params" : { "query" : "text:\"value2 value3\"" } } } }
- match: { hits.total: 1 }

View File

@ -28,6 +28,6 @@
msearch:
body:
- index: test_1
- query: { "template": { "query": { "term": { "foo": { "value": "{{template}}" } } }, "params": { "template": "bar" } } }
- query: { "template": { "inline": { "term": { "foo": { "value": "{{template}}" } } }, "params": { "template": "bar" } } }
- match: { responses.0.hits.total: 1 }

View File

@ -15,11 +15,11 @@
index: test_1
type: test
id: 1
script: "1"
body:
lang: painless
script: "ctx._source.foo = params.bar"
params: { bar: 'xxx' }
script:
lang: painless
inline: "ctx._source.foo = params.bar"
params: { bar: 'xxx' }
- match: { _index: test_1 }
- match: { _type: test }
@ -40,8 +40,10 @@
index: test_1
type: test
id: 1
lang: painless
script: "ctx._source.foo = 'yyy'"
body:
script:
lang: painless
inline: "ctx._source.foo = 'yyy'"
- match: { _index: test_1 }
- match: { _type: test }

View File

@ -7,9 +7,10 @@
type: test
id: 1
body:
script: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
upsert: { foo: baz }
- do:
@ -27,9 +28,10 @@
type: test
id: 1
body:
script: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
upsert: { foo: baz }
- do:
@ -46,9 +48,10 @@
type: test
id: 2
body:
script: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
script:
inline: "ctx._source.foo = params.bar"
lang: "painless"
params: { bar: 'xxx' }
upsert: { foo: baz }
scripted_upsert: true

View File

@ -17,8 +17,9 @@
index: source
query:
script:
lang: painless
script: throw new IllegalArgumentException("Cats!")
script:
lang: painless
inline: throw new IllegalArgumentException("Cats!")
dest:
index: dest
- match: {created: 0}

View File

@ -16,8 +16,9 @@
body:
query:
script:
lang: painless
script: throw new IllegalArgumentException("Cats!")
script:
lang: painless
inline: throw new IllegalArgumentException("Cats!")
- match: {updated: 0}
- match: {version_conflicts: 0}
- match: {batches: 0}

View File

@ -53,16 +53,6 @@
"type": "string",
"description": "Specific routing value"
},
"script": {
"description": "The URL-encoded script definition (instead of using request body)"
},
"script_id": {
"description": "The id of a stored script"
},
"scripted_upsert": {
"type": "boolean",
"description": "True if the script referenced in script or script_id should be called to perform inserts - defaults to false"
},
"timeout": {
"type": "time",
"description": "Explicit operation timeout"