Added painless execute api. (#29164)
Added an api that allows to execute an arbitrary script and a result to be returned. ``` POST /_scripts/painless/_execute { "script": { "source": "params.var1 / params.var2", "params": { "var1": 1, "var2": 1 } } } ``` Relates to #27875
This commit is contained in:
parent
621a1935b8
commit
8afa7c174f
|
@ -0,0 +1,53 @@
|
||||||
|
[[painless-execute-api]]
|
||||||
|
=== Painless execute API
|
||||||
|
|
||||||
|
The Painless execute API allows an arbitrary script to be executed and a result to be returned.
|
||||||
|
|
||||||
|
[[painless-execute-api-parameters]]
|
||||||
|
.Parameters
|
||||||
|
[options="header"]
|
||||||
|
|======
|
||||||
|
| Name | Required | Default | Description
|
||||||
|
| `script` | yes | - | The script to execute
|
||||||
|
| `context` | no | `execute_api_script` | The context the script should be executed in.
|
||||||
|
|======
|
||||||
|
|
||||||
|
==== Contexts
|
||||||
|
|
||||||
|
Contexts control how scripts are executed, what variables are available at runtime and what the return type is.
|
||||||
|
|
||||||
|
===== Painless test script context
|
||||||
|
|
||||||
|
The `painless_test` context executes scripts as is and do not add any special parameters.
|
||||||
|
The only variable that is available is `params`, which can be used to access user defined values.
|
||||||
|
The result of the script is always converted to a string.
|
||||||
|
If no context is specified then this context is used by default.
|
||||||
|
|
||||||
|
==== Example
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
----------------------------------------------------------------
|
||||||
|
POST /_scripts/painless/_execute
|
||||||
|
{
|
||||||
|
"script": {
|
||||||
|
"source": "params.count / params.total",
|
||||||
|
"params": {
|
||||||
|
"count": 100.0,
|
||||||
|
"total": 1000.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
----------------------------------------------------------------
|
||||||
|
// CONSOLE
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
[source,js]
|
||||||
|
--------------------------------------------------
|
||||||
|
{
|
||||||
|
"result": "0.1"
|
||||||
|
}
|
||||||
|
--------------------------------------------------
|
||||||
|
// TESTRESPONSE
|
|
@ -389,3 +389,5 @@ dispatch *feels* like it'd add a ton of complexity which'd make maintenance and
|
||||||
other improvements much more difficult.
|
other improvements much more difficult.
|
||||||
|
|
||||||
include::painless-debugging.asciidoc[]
|
include::painless-debugging.asciidoc[]
|
||||||
|
|
||||||
|
include::painless-execute-script.asciidoc[]
|
||||||
|
|
|
@ -0,0 +1,338 @@
|
||||||
|
/*
|
||||||
|
* 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.painless;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.Action;
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.ActionRequest;
|
||||||
|
import org.elasticsearch.action.ActionRequestBuilder;
|
||||||
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.ActionResponse;
|
||||||
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
import org.elasticsearch.action.support.HandledTransportAction;
|
||||||
|
import org.elasticsearch.client.ElasticsearchClient;
|
||||||
|
import org.elasticsearch.client.node.NodeClient;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
|
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.rest.BaseRestHandler;
|
||||||
|
import org.elasticsearch.rest.BytesRestResponse;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.rest.RestRequest;
|
||||||
|
import org.elasticsearch.rest.RestResponse;
|
||||||
|
import org.elasticsearch.rest.action.RestBuilderListener;
|
||||||
|
import org.elasticsearch.script.Script;
|
||||||
|
import org.elasticsearch.script.ScriptContext;
|
||||||
|
import org.elasticsearch.script.ScriptService;
|
||||||
|
import org.elasticsearch.script.ScriptType;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.transport.TransportService;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
||||||
|
import static org.elasticsearch.rest.RestRequest.Method.GET;
|
||||||
|
import static org.elasticsearch.rest.RestRequest.Method.POST;
|
||||||
|
import static org.elasticsearch.rest.RestStatus.OK;
|
||||||
|
|
||||||
|
public class PainlessExecuteAction extends Action<PainlessExecuteAction.Request, PainlessExecuteAction.Response,
|
||||||
|
PainlessExecuteAction.RequestBuilder> {
|
||||||
|
|
||||||
|
static final PainlessExecuteAction INSTANCE = new PainlessExecuteAction();
|
||||||
|
private static final String NAME = "cluster:admin/scripts/painless/execute";
|
||||||
|
|
||||||
|
private PainlessExecuteAction() {
|
||||||
|
super(NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
||||||
|
return new RequestBuilder(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response newResponse() {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Request extends ActionRequest implements ToXContent {
|
||||||
|
|
||||||
|
private static final ParseField SCRIPT_FIELD = new ParseField("script");
|
||||||
|
private static final ParseField CONTEXT_FIELD = new ParseField("context");
|
||||||
|
private static final ConstructingObjectParser<Request, Void> PARSER = new ConstructingObjectParser<>(
|
||||||
|
"painless_execute_request", args -> new Request((Script) args[0], (SupportedContext) args[1]));
|
||||||
|
|
||||||
|
static {
|
||||||
|
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> Script.parse(p), SCRIPT_FIELD);
|
||||||
|
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
|
||||||
|
// For now only accept an empty json object:
|
||||||
|
XContentParser.Token token = p.nextToken();
|
||||||
|
assert token == XContentParser.Token.FIELD_NAME;
|
||||||
|
String contextType = p.currentName();
|
||||||
|
token = p.nextToken();
|
||||||
|
assert token == XContentParser.Token.START_OBJECT;
|
||||||
|
token = p.nextToken();
|
||||||
|
assert token == XContentParser.Token.END_OBJECT;
|
||||||
|
token = p.nextToken();
|
||||||
|
assert token == XContentParser.Token.END_OBJECT;
|
||||||
|
return SupportedContext.valueOf(contextType.toUpperCase(Locale.ROOT));
|
||||||
|
}, CONTEXT_FIELD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Script script;
|
||||||
|
private SupportedContext context;
|
||||||
|
|
||||||
|
static Request parse(XContentParser parser) throws IOException {
|
||||||
|
return PARSER.parse(parser, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Request(Script script, SupportedContext context) {
|
||||||
|
this.script = Objects.requireNonNull(script);
|
||||||
|
this.context = context != null ? context : SupportedContext.PAINLESS_TEST;
|
||||||
|
}
|
||||||
|
|
||||||
|
Request() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Script getScript() {
|
||||||
|
return script;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SupportedContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionRequestValidationException validate() {
|
||||||
|
ActionRequestValidationException validationException = null;
|
||||||
|
if (script.getType() != ScriptType.INLINE) {
|
||||||
|
validationException = addValidationError("only inline scripts are supported", validationException);
|
||||||
|
}
|
||||||
|
return validationException;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
super.readFrom(in);
|
||||||
|
script = new Script(in);
|
||||||
|
context = SupportedContext.fromId(in.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
script.writeTo(out);
|
||||||
|
out.writeByte(context.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For testing only:
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
builder.field(SCRIPT_FIELD.getPreferredName(), script);
|
||||||
|
builder.startObject(CONTEXT_FIELD.getPreferredName());
|
||||||
|
{
|
||||||
|
builder.startObject(context.name());
|
||||||
|
builder.endObject();
|
||||||
|
}
|
||||||
|
builder.endObject();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Request request = (Request) o;
|
||||||
|
return Objects.equals(script, request.script) &&
|
||||||
|
context == request.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(script, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SupportedContext {
|
||||||
|
|
||||||
|
PAINLESS_TEST((byte) 0);
|
||||||
|
|
||||||
|
private final byte id;
|
||||||
|
|
||||||
|
SupportedContext(byte id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SupportedContext fromId(byte id) {
|
||||||
|
switch (id) {
|
||||||
|
case 0:
|
||||||
|
return PAINLESS_TEST;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("unknown context [" + id + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder> {
|
||||||
|
|
||||||
|
RequestBuilder(ElasticsearchClient client) {
|
||||||
|
super(client, INSTANCE, new Request());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Response extends ActionResponse implements ToXContentObject {
|
||||||
|
|
||||||
|
private Object result;
|
||||||
|
|
||||||
|
Response() {}
|
||||||
|
|
||||||
|
Response(Object result) {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
super.readFrom(in);
|
||||||
|
result = in.readGenericValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
out.writeGenericValue(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||||
|
builder.startObject();
|
||||||
|
builder.field("result", result);
|
||||||
|
return builder.endObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Response response = (Response) o;
|
||||||
|
return Objects.equals(result, response.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class PainlessTestScript {
|
||||||
|
|
||||||
|
private final Map<String, Object> params;
|
||||||
|
|
||||||
|
public PainlessTestScript(Map<String, Object> params) {
|
||||||
|
this.params = params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the parameters for this script. */
|
||||||
|
public Map<String, Object> getParams() {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Object execute();
|
||||||
|
|
||||||
|
public interface Factory {
|
||||||
|
|
||||||
|
PainlessTestScript newInstance(Map<String, Object> params);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String[] PARAMETERS = {};
|
||||||
|
public static final ScriptContext<Factory> CONTEXT = new ScriptContext<>("painless_test", Factory.class);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TransportAction extends HandledTransportAction<Request, Response> {
|
||||||
|
|
||||||
|
|
||||||
|
private final ScriptService scriptService;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TransportAction(Settings settings, ThreadPool threadPool, TransportService transportService,
|
||||||
|
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
|
ScriptService scriptService) {
|
||||||
|
super(settings, NAME, threadPool, transportService, actionFilters, indexNameExpressionResolver, Request::new);
|
||||||
|
this.scriptService = scriptService;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
protected void doExecute(Request request, ActionListener<Response> listener) {
|
||||||
|
switch (request.context) {
|
||||||
|
case PAINLESS_TEST:
|
||||||
|
PainlessTestScript.Factory factory = scriptService.compile(request.script, PainlessTestScript.CONTEXT);
|
||||||
|
PainlessTestScript painlessTestScript = factory.newInstance(request.script.getParams());
|
||||||
|
String result = Objects.toString(painlessTestScript.execute());
|
||||||
|
listener.onResponse(new Response(result));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("unsupported context [" + request.context + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RestAction extends BaseRestHandler {
|
||||||
|
|
||||||
|
RestAction(Settings settings, RestController controller) {
|
||||||
|
super(settings);
|
||||||
|
controller.registerHandler(GET, "/_scripts/painless/_execute", this);
|
||||||
|
controller.registerHandler(POST, "/_scripts/painless/_execute", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "_scripts_painless_execute";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
|
||||||
|
final Request request = Request.parse(restRequest.contentOrSourceParamParser());
|
||||||
|
return channel -> client.executeLocally(INSTANCE, request, new RestBuilderListener<Response>(channel) {
|
||||||
|
@Override
|
||||||
|
public RestResponse buildResponse(Response response, XContentBuilder builder) throws Exception {
|
||||||
|
response.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||||
|
return new BytesRestResponse(OK, builder);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,28 +20,40 @@
|
||||||
package org.elasticsearch.painless;
|
package org.elasticsearch.painless;
|
||||||
|
|
||||||
|
|
||||||
|
import org.elasticsearch.action.ActionRequest;
|
||||||
|
import org.elasticsearch.action.ActionResponse;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
|
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
|
import org.elasticsearch.common.settings.IndexScopedSettings;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.settings.SettingsFilter;
|
||||||
import org.elasticsearch.painless.spi.PainlessExtension;
|
import org.elasticsearch.painless.spi.PainlessExtension;
|
||||||
import org.elasticsearch.painless.spi.Whitelist;
|
import org.elasticsearch.painless.spi.Whitelist;
|
||||||
|
import org.elasticsearch.plugins.ActionPlugin;
|
||||||
import org.elasticsearch.plugins.ExtensiblePlugin;
|
import org.elasticsearch.plugins.ExtensiblePlugin;
|
||||||
import org.elasticsearch.plugins.Plugin;
|
import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.plugins.ScriptPlugin;
|
import org.elasticsearch.plugins.ScriptPlugin;
|
||||||
|
import org.elasticsearch.rest.RestController;
|
||||||
|
import org.elasticsearch.rest.RestHandler;
|
||||||
import org.elasticsearch.script.ScriptContext;
|
import org.elasticsearch.script.ScriptContext;
|
||||||
import org.elasticsearch.script.ScriptEngine;
|
import org.elasticsearch.script.ScriptEngine;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.ServiceLoader;
|
import java.util.ServiceLoader;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers Painless as a plugin.
|
* Registers Painless as a plugin.
|
||||||
*/
|
*/
|
||||||
public final class PainlessPlugin extends Plugin implements ScriptPlugin, ExtensiblePlugin {
|
public final class PainlessPlugin extends Plugin implements ScriptPlugin, ExtensiblePlugin, ActionPlugin {
|
||||||
|
|
||||||
private final Map<ScriptContext<?>, List<Whitelist>> extendedWhitelists = new HashMap<>();
|
private final Map<ScriptContext<?>, List<Whitelist>> extendedWhitelists = new HashMap<>();
|
||||||
|
|
||||||
|
@ -74,4 +86,24 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public List<ScriptContext> getContexts() {
|
||||||
|
return Collections.singletonList(PainlessExecuteAction.PainlessTestScript.CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
|
||||||
|
return Collections.singletonList(
|
||||||
|
new ActionHandler<>(PainlessExecuteAction.INSTANCE, PainlessExecuteAction.TransportAction.class)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RestHandler> getRestHandlers(Settings settings, RestController restController, ClusterSettings clusterSettings,
|
||||||
|
IndexScopedSettings indexScopedSettings, SettingsFilter settingsFilter,
|
||||||
|
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
|
Supplier<DiscoveryNodes> nodesInCluster) {
|
||||||
|
return Collections.singletonList(new PainlessExecuteAction.RestAction(settings, restController));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.painless;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
|
import org.elasticsearch.script.Script;
|
||||||
|
import org.elasticsearch.script.ScriptType;
|
||||||
|
import org.elasticsearch.test.AbstractStreamableXContentTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class PainlessExecuteRequestTests extends AbstractStreamableXContentTestCase<PainlessExecuteAction.Request> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PainlessExecuteAction.Request createTestInstance() {
|
||||||
|
Script script = new Script(randomAlphaOfLength(10));
|
||||||
|
PainlessExecuteAction.Request.SupportedContext context = randomBoolean() ?
|
||||||
|
PainlessExecuteAction.Request.SupportedContext.PAINLESS_TEST : null;
|
||||||
|
return new PainlessExecuteAction.Request(script, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PainlessExecuteAction.Request createBlankInstance() {
|
||||||
|
return new PainlessExecuteAction.Request();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PainlessExecuteAction.Request doParseInstance(XContentParser parser) throws IOException {
|
||||||
|
return PainlessExecuteAction.Request.parse(parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean supportsUnknownFields() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testValidate() {
|
||||||
|
Script script = new Script(ScriptType.STORED, null, randomAlphaOfLength(10), Collections.emptyMap());
|
||||||
|
PainlessExecuteAction.Request request = new PainlessExecuteAction.Request(script, null);
|
||||||
|
Exception e = request.validate();
|
||||||
|
assertNotNull(e);
|
||||||
|
assertEquals("Validation Failed: 1: only inline scripts are supported;", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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.painless;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.AbstractStreamableTestCase;
|
||||||
|
|
||||||
|
public class PainlessExecuteResponseTests extends AbstractStreamableTestCase<PainlessExecuteAction.Response> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PainlessExecuteAction.Response createBlankInstance() {
|
||||||
|
return new PainlessExecuteAction.Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected PainlessExecuteAction.Response createTestInstance() {
|
||||||
|
return new PainlessExecuteAction.Response(randomAlphaOfLength(10));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
---
|
||||||
|
"Execute with defaults":
|
||||||
|
- do:
|
||||||
|
scripts_painless_execute:
|
||||||
|
body:
|
||||||
|
script:
|
||||||
|
source: "params.count / params.total"
|
||||||
|
params:
|
||||||
|
count: 100.0
|
||||||
|
total: 1000.0
|
||||||
|
- match: { result: "0.1" }
|
||||||
|
|
||||||
|
---
|
||||||
|
"Execute with execute_api_script context":
|
||||||
|
- do:
|
||||||
|
scripts_painless_execute:
|
||||||
|
body:
|
||||||
|
script:
|
||||||
|
source: "params.var1 - params.var2"
|
||||||
|
params:
|
||||||
|
var1: 10
|
||||||
|
var2: 100
|
||||||
|
context:
|
||||||
|
painless_test: {}
|
||||||
|
- match: { result: "-90" }
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"scripts_painless_execute": {
|
||||||
|
"documentation": "https://www.elastic.co/guide/en/elasticsearch/painless/master/painless-execute-api.html",
|
||||||
|
"methods": ["GET", "POST"],
|
||||||
|
"url": {
|
||||||
|
"path": "/_scripts/painless/_execute",
|
||||||
|
"paths": ["/_scripts/painless/_execute"],
|
||||||
|
"parts": {
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"description": "The script to execute"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue