Scripting: Make ScriptEngine.compile generic on the script context (#24873)
This commit changes the compile method of ScriptEngine to be generic in the same way it is on ScriptService. This moves the shim of handling the two existing context classes into each script engine, so that each engine can be worked on independently to convert to real handling of contexts.
This commit is contained in:
parent
5581a0b2f0
commit
7d03cff820
|
@ -20,7 +20,6 @@ package org.elasticsearch.plugins;
|
|||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
|
||||
package org.elasticsearch.script;
|
||||
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
@ -40,14 +37,11 @@ public interface ScriptEngine extends Closeable {
|
|||
* Compiles a script.
|
||||
* @param name the name of the script. {@code null} if it is anonymous (inline). For a stored script, its the identifier.
|
||||
* @param code actual source of the script
|
||||
* @param context the context this script will be used for
|
||||
* @param params compile-time parameters (such as flags to the compiler)
|
||||
* @return an opaque compiled script which may be cached and later passed to
|
||||
* @return A compiled script of the CompiledType from {@link ScriptContext}
|
||||
*/
|
||||
Object compile(String name, String code, Map<String, String> params);
|
||||
|
||||
ExecutableScript executable(Object compiledScript, @Nullable Map<String, Object> vars);
|
||||
|
||||
SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars);
|
||||
<CompiledType> CompiledType compile(String name, String code, ScriptContext<CompiledType> context, Map<String, String> params);
|
||||
|
||||
@Override
|
||||
default void close() throws IOException {}
|
||||
|
|
|
@ -312,14 +312,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
|
|||
}
|
||||
// Check whether too many compilations have happened
|
||||
checkCompilationLimit();
|
||||
Object engineCompiled = scriptEngine.compile(id, idOrCode, options);
|
||||
if (context.instanceClazz == ExecutableScript.class) {
|
||||
compiledScript = (ExecutableScript.Compiled) params -> scriptEngine.executable(engineCompiled, params);
|
||||
} else if (context.instanceClazz == SearchScript.class) {
|
||||
compiledScript = (SearchScript.Compiled) (params, lookup) -> scriptEngine.search(engineCompiled, lookup, params);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Script context [" + context.name + "] not supported");
|
||||
}
|
||||
compiledScript = scriptEngine.compile(id, idOrCode, context, options);
|
||||
} catch (ScriptException good) {
|
||||
// TODO: remove this try-catch completely, when all script engines have good exceptions!
|
||||
throw good; // its already good
|
||||
|
@ -439,7 +432,8 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
|
|||
throw new IllegalArgumentException(
|
||||
"cannot put [" + ScriptType.STORED + "] script, no script contexts are enabled");
|
||||
} else {
|
||||
Object compiled = scriptEngine.compile(request.id(), source.getCode(), Collections.emptyMap());
|
||||
// TODO: executable context here is just a placeholder, replace with optional context name passed into PUT stored script req
|
||||
Object compiled = scriptEngine.compile(request.id(), source.getCode(), ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
|
||||
if (compiled == null) {
|
||||
throw new IllegalArgumentException("failed to parse/compile stored script [" + request.id() + "]" +
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.elasticsearch.script.ExecutableScript;
|
|||
import org.elasticsearch.script.ExplainableSearchScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
|
@ -77,19 +78,10 @@ public class ExplainableScriptIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object compile(String scriptName, String scriptSource, Map<String, String> params) {
|
||||
public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
assert scriptSource.equals("explainable_script");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, @Nullable Map<String, Object> vars) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
|
||||
return new SearchScript() {
|
||||
assert context == ScriptContext.SEARCH;
|
||||
SearchScript.Compiled compiled = (p, lookup) -> new SearchScript() {
|
||||
@Override
|
||||
public LeafSearchScript getLeafSearchScript(LeafReaderContext context) throws IOException {
|
||||
return new MyScript(lookup.doc().getLeafDocLookup(context));
|
||||
|
@ -99,6 +91,7 @@ public class ExplainableScriptIT extends ESIntegTestCase {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.common.xcontent.XContentFactory;
|
|||
import org.elasticsearch.plugins.Plugin;
|
||||
import org.elasticsearch.plugins.ScriptPlugin;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
@ -1025,14 +1026,13 @@ public class SuggestSearchIT extends ESIntegTestCase {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object compile(String scriptName, String scriptSource, Map<String, String> params) {
|
||||
return scriptSource;
|
||||
public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
if (context.instanceClazz != ExecutableScript.class) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, Map<String, Object> params) {
|
||||
String script = (String) compiledScript;
|
||||
for (Entry<String, Object> entry : params.entrySet()) {
|
||||
ExecutableScript.Compiled compiled = p -> {
|
||||
String script = scriptSource;
|
||||
for (Entry<String, Object> entry : p.entrySet()) {
|
||||
script = script.replace("{{" + entry.getKey() + "}}", String.valueOf(entry.getValue()));
|
||||
}
|
||||
String result = script;
|
||||
|
@ -1047,11 +1047,8 @@ public class SuggestSearchIT extends ESIntegTestCase {
|
|||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, Map<String, Object> vars) {
|
||||
throw new UnsupportedOperationException("search script not supported");
|
||||
};
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.apache.lucene.expressions.Expression;
|
|||
import org.apache.lucene.expressions.SimpleBindings;
|
||||
import org.apache.lucene.expressions.js.JavascriptCompiler;
|
||||
import org.apache.lucene.expressions.js.VariableContext;
|
||||
import org.apache.lucene.index.LeafReaderContext;
|
||||
import org.apache.lucene.queries.function.ValueSource;
|
||||
import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource;
|
||||
import org.apache.lucene.search.SortField;
|
||||
|
@ -38,11 +39,14 @@ import org.elasticsearch.index.mapper.MappedFieldType;
|
|||
import org.elasticsearch.index.mapper.MapperService;
|
||||
import org.elasticsearch.script.ClassPermission;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
@ -69,11 +73,11 @@ public class ExpressionScriptEngine extends AbstractComponent implements ScriptE
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object compile(String scriptName, String scriptSource, Map<String, String> params) {
|
||||
public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
// classloader created here
|
||||
final SecurityManager sm = System.getSecurityManager();
|
||||
SpecialPermission.check();
|
||||
return AccessController.doPrivileged(new PrivilegedAction<Expression>() {
|
||||
Expression expr = AccessController.doPrivileged(new PrivilegedAction<Expression>() {
|
||||
@Override
|
||||
public Expression run() {
|
||||
try {
|
||||
|
@ -100,11 +104,17 @@ public class ExpressionScriptEngine extends AbstractComponent implements ScriptE
|
|||
}
|
||||
}
|
||||
});
|
||||
if (context.instanceClazz.equals(SearchScript.class)) {
|
||||
SearchScript.Compiled compiled = (p, lookup) -> newSearchScript(expr, lookup, p);
|
||||
return context.compiledClazz.cast(compiled);
|
||||
} else if (context.instanceClazz.equals(ExecutableScript.class)) {
|
||||
ExecutableScript.Compiled compiled = (p) -> new ExpressionExecutableScript(expr, p);
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
throw new IllegalArgumentException("painless does not know how to handle context [" + context.name + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
|
||||
Expression expr = (Expression)compiledScript;
|
||||
private SearchScript newSearchScript(Expression expr, SearchLookup lookup, @Nullable Map<String, Object> vars) {
|
||||
MapperService mapper = lookup.doc().mapperService();
|
||||
// NOTE: if we need to do anything complicated with bindings in the future, we can just extend Bindings,
|
||||
// instead of complicating SimpleBindings (which should stay simple)
|
||||
|
@ -246,9 +256,4 @@ public class ExpressionScriptEngine extends AbstractComponent implements ScriptE
|
|||
stack.add(pointer.toString());
|
||||
throw new ScriptException(message, cause, stack, source, NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, Map<String, Object> vars) {
|
||||
return new ExpressionExecutableScript((Expression) compiledScript, vars);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.script.expression;
|
|||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.IndexService;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
@ -42,8 +43,8 @@ public class ExpressionTests extends ESSingleNodeTestCase {
|
|||
}
|
||||
|
||||
private SearchScript compile(String expression) {
|
||||
Object compiled = service.compile(null, expression, Collections.emptyMap());
|
||||
return service.search(compiled, lookup, Collections.emptyMap());
|
||||
SearchScript.Compiled compiled = service.compile(null, expression, ScriptContext.SEARCH, Collections.emptyMap());
|
||||
return compiled.newInstance(Collections.emptyMap(), lookup);
|
||||
}
|
||||
|
||||
public void testNeedsScores() {
|
||||
|
|
|
@ -31,6 +31,7 @@ import org.elasticsearch.common.logging.ESLoggerFactory;
|
|||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.GeneralScriptException;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
@ -63,10 +64,15 @@ public final class MustacheScriptEngine implements ScriptEngine {
|
|||
* @return a compiled template object for later execution.
|
||||
* */
|
||||
@Override
|
||||
public Object compile(String templateName, String templateSource, Map<String, String> params) {
|
||||
public <T> T compile(String templateName, String templateSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
if (context.instanceClazz.equals(ExecutableScript.class) == false) {
|
||||
throw new IllegalArgumentException("mustache engine does not know how to handle context [" + context.name + "]");
|
||||
}
|
||||
final MustacheFactory factory = createMustacheFactory(params);
|
||||
Reader reader = new FastStringReader(templateSource);
|
||||
return factory.compile(reader, "query-template");
|
||||
Mustache template = factory.compile(reader, "query-template");
|
||||
ExecutableScript.Compiled compiled = p -> new MustacheExecutableScript(template, p);
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
|
||||
private CustomMustacheFactory createMustacheFactory(Map<String, String> params) {
|
||||
|
@ -81,21 +87,11 @@ public final class MustacheScriptEngine implements ScriptEngine {
|
|||
return NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, @Nullable Map<String, Object> vars) {
|
||||
return new MustacheExecutableScript((Mustache) compiledScript, vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used at query execution time by script service in order to execute a query template.
|
||||
* */
|
||||
private class MustacheExecutableScript implements ExecutableScript {
|
||||
/** Compiled template object wrapper. */
|
||||
/** Compiled template. */
|
||||
private Mustache template;
|
||||
/** Parameters to fill above object with. */
|
||||
private Map<String, Object> vars;
|
||||
|
|
|
@ -22,6 +22,7 @@ package org.elasticsearch.script.mustache;
|
|||
import com.github.mustachejava.Mustache;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
|
@ -61,9 +62,9 @@ public class CustomMustacheFactoryTests extends ESTestCase {
|
|||
final ScriptEngine engine = new MustacheScriptEngine();
|
||||
final Map<String, String> params = randomBoolean() ? singletonMap(Script.CONTENT_TYPE_OPTION, JSON_MIME_TYPE) : emptyMap();
|
||||
|
||||
Mustache script = (Mustache) engine.compile(null, "{\"field\": \"{{value}}\"}", params);
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", ScriptContext.EXECUTABLE, params);
|
||||
|
||||
ExecutableScript executable = engine.executable(script, singletonMap("value", "a \"value\""));
|
||||
ExecutableScript executable = compiled.newInstance(singletonMap("value", "a \"value\""));
|
||||
assertThat(executable.run(), equalTo("{\"field\": \"a \\\"value\\\"\"}"));
|
||||
}
|
||||
|
||||
|
@ -71,9 +72,9 @@ public class CustomMustacheFactoryTests extends ESTestCase {
|
|||
final ScriptEngine engine = new MustacheScriptEngine();
|
||||
final Map<String, String> params = singletonMap(Script.CONTENT_TYPE_OPTION, PLAIN_TEXT_MIME_TYPE);
|
||||
|
||||
Mustache script = (Mustache) engine.compile(null, "{\"field\": \"{{value}}\"}", params);
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", ScriptContext.EXECUTABLE, params);
|
||||
|
||||
ExecutableScript executable = engine.executable(script, singletonMap("value", "a \"value\""));
|
||||
ExecutableScript executable = compiled.newInstance(singletonMap("value", "a \"value\""));
|
||||
assertThat(executable.run(), equalTo("{\"field\": \"a \"value\"\"}"));
|
||||
}
|
||||
|
||||
|
@ -81,9 +82,9 @@ public class CustomMustacheFactoryTests extends ESTestCase {
|
|||
final ScriptEngine engine = new MustacheScriptEngine();
|
||||
final Map<String, String> params = singletonMap(Script.CONTENT_TYPE_OPTION, X_WWW_FORM_URLENCODED_MIME_TYPE);
|
||||
|
||||
Mustache script = (Mustache) engine.compile(null, "{\"field\": \"{{value}}\"}", params);
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, "{\"field\": \"{{value}}\"}", ScriptContext.EXECUTABLE, params);
|
||||
|
||||
ExecutableScript executable = engine.executable(script, singletonMap("value", "tilde~ AND date:[2016 FROM*]"));
|
||||
ExecutableScript executable = compiled.newInstance(singletonMap("value", "tilde~ AND date:[2016 FROM*]"));
|
||||
assertThat(executable.run(), equalTo("{\"field\": \"tilde%7E+AND+date%3A%5B2016+FROM*%5D\"}"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
||||
|
@ -55,7 +56,7 @@ public class MustacheScriptEngineTests extends ESTestCase {
|
|||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}" + "}}, \"negative_boost\": {{boost_val}} } }}";
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
vars.put("boost_val", "0.3");
|
||||
String o = (String) qe.executable(qe.compile(null, template, compileParams), vars).run();
|
||||
String o = (String) qe.compile(null, template, ScriptContext.EXECUTABLE, compileParams).newInstance(vars).run();
|
||||
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"solr\"}}}, \"negative_boost\": 0.3 } }}",
|
||||
o);
|
||||
|
@ -66,7 +67,7 @@ public class MustacheScriptEngineTests extends ESTestCase {
|
|||
Map<String, Object> vars = new HashMap<>();
|
||||
vars.put("boost_val", "0.3");
|
||||
vars.put("body_val", "\"quick brown\"");
|
||||
String o = (String) qe.executable(qe.compile(null, template, compileParams), vars).run();
|
||||
String o = (String) qe.compile(null, template, ScriptContext.EXECUTABLE, compileParams).newInstance(vars).run();
|
||||
assertEquals("GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
+ "\"negative\": {\"term\": {\"body\": {\"value\": \"\\\"quick brown\\\"\"}}}, \"negative_boost\": 0.3 } }}",
|
||||
o);
|
||||
|
@ -81,8 +82,8 @@ public class MustacheScriptEngineTests extends ESTestCase {
|
|||
+ "}";
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent, templateString);
|
||||
Script script = Script.parse(parser);
|
||||
Object compiled = qe.compile(null, script.getIdOrCode(), Collections.emptyMap());
|
||||
ExecutableScript executableScript = qe.executable(compiled, script.getParams());
|
||||
ExecutableScript.Compiled compiled = qe.compile(null, script.getIdOrCode(), ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
ExecutableScript executableScript = compiled.newInstance(script.getParams());
|
||||
assertThat(executableScript.run(), equalTo("{\"match_all\":{}}"));
|
||||
}
|
||||
|
||||
|
@ -96,8 +97,8 @@ public class MustacheScriptEngineTests extends ESTestCase {
|
|||
+ "}";
|
||||
XContentParser parser = createParser(JsonXContent.jsonXContent, templateString);
|
||||
Script script = Script.parse(parser);
|
||||
Object compiled = qe.compile(null, script.getIdOrCode(), Collections.emptyMap());
|
||||
ExecutableScript executableScript = qe.executable(compiled, script.getParams());
|
||||
ExecutableScript.Compiled compiled = qe.compile(null, script.getIdOrCode(), ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
ExecutableScript executableScript = compiled.newInstance(script.getParams());
|
||||
assertThat(executableScript.run(), equalTo("{ \"match_all\":{} }"));
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import com.github.mustachejava.MustacheException;
|
|||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.hamcrest.Matcher;
|
||||
|
@ -61,8 +62,8 @@ public class MustacheTests extends ESTestCase {
|
|||
+ "}}, \"negative_boost\": {{boost_val}} } }}";
|
||||
Map<String, Object> params = Collections.singletonMap("boost_val", "0.2");
|
||||
|
||||
Mustache mustache = (Mustache) engine.compile(null, template, Collections.emptyMap());
|
||||
ExecutableScript result = engine.executable(mustache, params);
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, template, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
ExecutableScript result = compiled.newInstance(params);
|
||||
assertEquals(
|
||||
"Mustache templating broken",
|
||||
"GET _search {\"query\": {\"boosting\": {\"positive\": {\"match\": {\"body\": \"gift\"}},"
|
||||
|
@ -73,27 +74,27 @@ public class MustacheTests extends ESTestCase {
|
|||
|
||||
public void testArrayAccess() throws Exception {
|
||||
String template = "{{data.0}} {{data.1}}";
|
||||
Object mustache = engine.compile(null, template, Collections.emptyMap());
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, template, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
Object data = randomFrom(
|
||||
new String[] { "foo", "bar" },
|
||||
Arrays.asList("foo", "bar"));
|
||||
vars.put("data", data);
|
||||
assertThat(engine.executable(mustache, vars).run(), equalTo("foo bar"));
|
||||
assertThat(compiled.newInstance(vars).run(), equalTo("foo bar"));
|
||||
|
||||
// Sets can come out in any order
|
||||
Set<String> setData = new HashSet<>();
|
||||
setData.add("foo");
|
||||
setData.add("bar");
|
||||
vars.put("data", setData);
|
||||
Object output = engine.executable(mustache, vars).run();
|
||||
Object output = compiled.newInstance(vars).run();
|
||||
assertThat(output, instanceOf(String.class));
|
||||
assertThat((String)output, both(containsString("foo")).and(containsString("bar")));
|
||||
}
|
||||
|
||||
public void testArrayInArrayAccess() throws Exception {
|
||||
String template = "{{data.0.0}} {{data.0.1}}";
|
||||
Object mustache = engine.compile(null, template, Collections.emptyMap());
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, template, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
Object data = randomFrom(
|
||||
new String[][] { new String[] { "foo", "bar" }},
|
||||
|
@ -101,25 +102,25 @@ public class MustacheTests extends ESTestCase {
|
|||
singleton(new String[] { "foo", "bar" })
|
||||
);
|
||||
vars.put("data", data);
|
||||
assertThat(engine.executable(mustache, vars).run(), equalTo("foo bar"));
|
||||
assertThat(compiled.newInstance(vars).run(), equalTo("foo bar"));
|
||||
}
|
||||
|
||||
public void testMapInArrayAccess() throws Exception {
|
||||
String template = "{{data.0.key}} {{data.1.key}}";
|
||||
Object mustache = engine.compile(null, template, Collections.emptyMap());
|
||||
ExecutableScript.Compiled compiled= engine.compile(null, template, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
Object data = randomFrom(
|
||||
new Object[] { singletonMap("key", "foo"), singletonMap("key", "bar") },
|
||||
Arrays.asList(singletonMap("key", "foo"), singletonMap("key", "bar")));
|
||||
vars.put("data", data);
|
||||
assertThat(engine.executable(mustache, vars).run(), equalTo("foo bar"));
|
||||
assertThat(compiled.newInstance(vars).run(), equalTo("foo bar"));
|
||||
|
||||
// HashSet iteration order isn't fixed
|
||||
Set<Object> setData = new HashSet<>();
|
||||
setData.add(singletonMap("key", "foo"));
|
||||
setData.add(singletonMap("key", "bar"));
|
||||
vars.put("data", setData);
|
||||
Object output = engine.executable(mustache, vars).run();
|
||||
Object output = compiled.newInstance(vars).run();
|
||||
assertThat(output, instanceOf(String.class));
|
||||
assertThat((String)output, both(containsString("foo")).and(containsString("bar")));
|
||||
}
|
||||
|
@ -130,14 +131,14 @@ public class MustacheTests extends ESTestCase {
|
|||
List<String> randomList = Arrays.asList(generateRandomStringArray(10, 20, false));
|
||||
|
||||
String template = "{{data.array.size}} {{data.list.size}}";
|
||||
Object mustache = engine.compile(null, template, Collections.emptyMap());
|
||||
ExecutableScript.Compiled compiled = engine.compile(null, template, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("array", randomArrayValues);
|
||||
data.put("list", randomList);
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
vars.put("data", data);
|
||||
String expectedString = String.format(Locale.ROOT, "%s %s", randomArrayValues.length, randomList.size());
|
||||
assertThat(engine.executable(mustache, vars).run(), equalTo(expectedString));
|
||||
assertThat(compiled.newInstance(vars).run(), equalTo(expectedString));
|
||||
}
|
||||
|
||||
public void testPrimitiveToJSON() throws Exception {
|
||||
|
@ -372,12 +373,12 @@ public class MustacheTests extends ESTestCase {
|
|||
}
|
||||
|
||||
private void assertScript(String script, Map<String, Object> vars, Matcher<Object> matcher) {
|
||||
Object result = engine.executable(compile(script), vars).run();
|
||||
Object result = compile(script).newInstance(vars).run();
|
||||
assertThat(result, matcher);
|
||||
}
|
||||
|
||||
private Object compile(String script) {
|
||||
private ExecutableScript.Compiled compile(String script) {
|
||||
assertThat("cannot compile null or empty script", script, not(isEmptyOrNullString()));
|
||||
return engine.compile(null, script, Collections.emptyMap());
|
||||
return engine.compile(null, script, ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,10 +26,10 @@ import org.elasticsearch.common.settings.Settings;
|
|||
import org.elasticsearch.painless.Compiler.Loader;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.AccessControlContext;
|
||||
|
@ -98,8 +98,25 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||
static final String INLINE_NAME = "<inline>";
|
||||
|
||||
@Override
|
||||
public Object compile(String scriptName, final String scriptSource, final Map<String, String> params) {
|
||||
return compile(GenericElasticsearchScript.class, scriptName, scriptSource, params);
|
||||
public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
GenericElasticsearchScript painlessScript = compile(GenericElasticsearchScript.class, scriptName, scriptSource, params);
|
||||
if (context.instanceClazz.equals(SearchScript.class)) {
|
||||
SearchScript.Compiled compiled = (p, lookup) -> new SearchScript() {
|
||||
@Override
|
||||
public LeafSearchScript getLeafSearchScript(final LeafReaderContext context) throws IOException {
|
||||
return new ScriptImpl(painlessScript, p, lookup.getLeafSearchLookup(context));
|
||||
}
|
||||
@Override
|
||||
public boolean needsScores() {
|
||||
return painlessScript.uses$_score();
|
||||
}
|
||||
};
|
||||
return context.compiledClazz.cast(compiled);
|
||||
} else if (context.instanceClazz.equals(ExecutableScript.class)) {
|
||||
ExecutableScript.Compiled compiled = (p) -> new ScriptImpl(painlessScript, p, null);
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
throw new IllegalArgumentException("painless does not know how to handle context [" + context.name + "]");
|
||||
}
|
||||
|
||||
<T> T compile(Class<T> iface, String scriptName, final String scriptSource, final Map<String, String> params) {
|
||||
|
@ -168,47 +185,6 @@ public final class PainlessScriptEngine extends AbstractComponent implements Scr
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an {@link ExecutableScript} for later use.
|
||||
* @param compiledScript A previously compiled script.
|
||||
* @param vars The variables to be used in the script.
|
||||
* @return An {@link ExecutableScript} with the currently specified variables.
|
||||
*/
|
||||
@Override
|
||||
public ExecutableScript executable(final Object compiledScript, final Map<String, Object> vars) {
|
||||
return new ScriptImpl((GenericElasticsearchScript) compiledScript, vars, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link SearchScript} for later use.
|
||||
* @param compiledScript A previously compiled script.
|
||||
* @param lookup The object that ultimately allows access to search fields.
|
||||
* @param vars The variables to be used in the script.
|
||||
* @return An {@link SearchScript} with the currently specified variables.
|
||||
*/
|
||||
@Override
|
||||
public SearchScript search(final Object compiledScript, final SearchLookup lookup, final Map<String, Object> vars) {
|
||||
return new SearchScript() {
|
||||
/**
|
||||
* Get the search script that will have access to search field values.
|
||||
* @param context The LeafReaderContext to be used.
|
||||
* @return A script that will have the search fields from the current context available for use.
|
||||
*/
|
||||
@Override
|
||||
public LeafSearchScript getLeafSearchScript(final LeafReaderContext context) throws IOException {
|
||||
return new ScriptImpl((GenericElasticsearchScript) compiledScript, vars, lookup.getLeafSearchLookup(context));
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the score is needed.
|
||||
*/
|
||||
@Override
|
||||
public boolean needsScores() {
|
||||
return ((GenericElasticsearchScript) compiledScript).uses$_score();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ScriptException convertToScriptException(String scriptName, String scriptSource, Throwable t) {
|
||||
// create a script stack: this is just the script portion
|
||||
List<String> scriptStack = new ArrayList<>();
|
||||
|
|
|
@ -21,6 +21,7 @@ package org.elasticsearch.painless;
|
|||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.index.IndexService;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
import org.elasticsearch.test.ESSingleNodeTestCase;
|
||||
|
@ -39,20 +40,20 @@ public class NeedsScoreTests extends ESSingleNodeTestCase {
|
|||
PainlessScriptEngine service = new PainlessScriptEngine(Settings.EMPTY);
|
||||
SearchLookup lookup = new SearchLookup(index.mapperService(), index.fieldData(), null);
|
||||
|
||||
Object compiled = service.compile(null, "1.2", Collections.emptyMap());
|
||||
SearchScript ss = service.search(compiled, lookup, Collections.emptyMap());
|
||||
SearchScript.Compiled compiled = service.compile(null, "1.2", ScriptContext.SEARCH, Collections.emptyMap());
|
||||
SearchScript ss = compiled.newInstance(Collections.emptyMap(), lookup);
|
||||
assertFalse(ss.needsScores());
|
||||
|
||||
compiled = service.compile(null, "doc['d'].value", Collections.emptyMap());
|
||||
ss = service.search(compiled, lookup, Collections.emptyMap());
|
||||
compiled = service.compile(null, "doc['d'].value", ScriptContext.SEARCH, Collections.emptyMap());
|
||||
ss = compiled.newInstance(Collections.emptyMap(), lookup);
|
||||
assertFalse(ss.needsScores());
|
||||
|
||||
compiled = service.compile(null, "1/_score", Collections.emptyMap());
|
||||
ss = service.search(compiled, lookup, Collections.emptyMap());
|
||||
compiled = service.compile(null, "1/_score", ScriptContext.SEARCH, Collections.emptyMap());
|
||||
ss = compiled.newInstance(Collections.emptyMap(), lookup);
|
||||
assertTrue(ss.needsScores());
|
||||
|
||||
compiled = service.compile(null, "doc['d'].value * _score", Collections.emptyMap());
|
||||
ss = service.search(compiled, lookup, Collections.emptyMap());
|
||||
compiled = service.compile(null, "doc['d'].value * _score", ScriptContext.SEARCH, Collections.emptyMap());
|
||||
ss = compiled.newInstance(Collections.emptyMap(), lookup);
|
||||
assertTrue(ss.needsScores());
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
package org.elasticsearch.painless;
|
||||
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -79,9 +80,9 @@ public class ScriptEngineTests extends ScriptTestCase {
|
|||
Map<String, Object> ctx = new HashMap<>();
|
||||
vars.put("ctx", ctx);
|
||||
|
||||
Object compiledScript = scriptEngine.compile(null,
|
||||
"return ctx.value;", Collections.emptyMap());
|
||||
ExecutableScript script = scriptEngine.executable(compiledScript, vars);
|
||||
ExecutableScript.Compiled compiledScript =
|
||||
scriptEngine.compile(null, "return ctx.value;", ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
ExecutableScript script = compiledScript.newInstance(vars);
|
||||
|
||||
ctx.put("value", 1);
|
||||
Object o = script.run();
|
||||
|
@ -94,9 +95,9 @@ public class ScriptEngineTests extends ScriptTestCase {
|
|||
|
||||
public void testChangingVarsCrossExecution2() {
|
||||
Map<String, Object> vars = new HashMap<>();
|
||||
Object compiledScript = scriptEngine.compile(null, "return params['value'];", Collections.emptyMap());
|
||||
|
||||
ExecutableScript script = scriptEngine.executable(compiledScript, vars);
|
||||
ExecutableScript.Compiled compiledScript =
|
||||
scriptEngine.compile(null, "return params['value'];", ScriptContext.EXECUTABLE, Collections.emptyMap());
|
||||
ExecutableScript script = compiledScript.newInstance(vars);
|
||||
|
||||
script.setNextVar("value", 1);
|
||||
Object value = script.run();
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.common.lucene.ScorerAware;
|
|||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.painless.antlr.Walker;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptException;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.junit.Before;
|
||||
|
@ -85,8 +86,8 @@ public abstract class ScriptTestCase extends ESTestCase {
|
|||
definition, null);
|
||||
}
|
||||
// test actual script execution
|
||||
Object compiled = scriptEngine.compile(null, script, compileParams);
|
||||
ExecutableScript executableScript = scriptEngine.executable(compiled, vars);
|
||||
ExecutableScript.Compiled compiled = scriptEngine.compile(null, script, ScriptContext.EXECUTABLE, compileParams);
|
||||
ExecutableScript executableScript = compiled.newInstance(vars);
|
||||
if (scorer != null) {
|
||||
((ScorerAware)executableScript).setScorer(scorer);
|
||||
}
|
||||
|
|
|
@ -33,8 +33,10 @@ import org.elasticsearch.plugins.Plugin;
|
|||
import org.elasticsearch.plugins.ScriptPlugin;
|
||||
import org.elasticsearch.script.ExecutableScript;
|
||||
import org.elasticsearch.script.LeafSearchScript;
|
||||
import org.elasticsearch.script.ScriptContext;
|
||||
import org.elasticsearch.script.ScriptEngine;
|
||||
import org.elasticsearch.script.SearchScript;
|
||||
import org.elasticsearch.search.internal.SearchContext;
|
||||
import org.elasticsearch.search.lookup.SearchLookup;
|
||||
|
||||
/**
|
||||
|
@ -56,10 +58,13 @@ public class ExpertScriptPlugin extends Plugin implements ScriptPlugin {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Function<Map<String,Object>,SearchScript> compile(String scriptName, String scriptSource, Map<String, String> params) {
|
||||
public <T> T compile(String scriptName, String scriptSource, ScriptContext<T> context, Map<String, String> params) {
|
||||
if (context.equals(ScriptContext.SEARCH) == false) {
|
||||
throw new IllegalArgumentException(getType() + " scripts cannot be used for context [" + context.name + "]");
|
||||
}
|
||||
// we use the script "source" as the script identifier
|
||||
if ("pure_df".equals(scriptSource)) {
|
||||
return p -> new SearchScript() {
|
||||
SearchScript.Compiled compiled = (p, lookup) -> new SearchScript() {
|
||||
final String field;
|
||||
final String term;
|
||||
{
|
||||
|
@ -114,22 +119,11 @@ public class ExpertScriptPlugin extends Plugin implements ScriptPlugin {
|
|||
return false;
|
||||
}
|
||||
};
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown script name " + scriptSource);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> params) {
|
||||
Function<Map<String,Object>,SearchScript> scriptFactory = (Function<Map<String,Object>,SearchScript>) compiledScript;
|
||||
return scriptFactory.apply(params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, @Nullable Map<String, Object> params) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
// optionally close resources
|
||||
|
|
|
@ -67,7 +67,7 @@ public class MockScriptEngine implements ScriptEngine {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Object compile(String name, String source, Map<String, String> options) {
|
||||
public <T> T compile(String name, String source, ScriptContext<T> context, Map<String, String> params) {
|
||||
// Scripts are always resolved using the script's source. For inline scripts, it's easy because they don't have names and the
|
||||
// source is always provided. For stored and file scripts, the source of the script must match the key of a predefined script.
|
||||
Function<Map<String, Object>, Object> script = scripts.get(source);
|
||||
|
@ -75,19 +75,15 @@ public class MockScriptEngine implements ScriptEngine {
|
|||
throw new IllegalArgumentException("No pre defined script matching [" + source + "] for script with name [" + name + "], " +
|
||||
"did you declare the mocked script?");
|
||||
}
|
||||
return new MockCompiledScript(name, options, source, script);
|
||||
MockCompiledScript mockCompiled = new MockCompiledScript(name, params, source, script);
|
||||
if (context.instanceClazz.equals(SearchScript.class)) {
|
||||
SearchScript.Compiled compiled = mockCompiled::createSearchScript;
|
||||
return context.compiledClazz.cast(compiled);
|
||||
} else if (context.instanceClazz.equals(ExecutableScript.class)) {
|
||||
ExecutableScript.Compiled compiled = mockCompiled::createExecutableScript;
|
||||
return context.compiledClazz.cast(compiled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExecutableScript executable(Object compiledScript, @Nullable Map<String, Object> vars) {
|
||||
MockCompiledScript compiled = (MockCompiledScript) compiledScript;
|
||||
return compiled.createExecutableScript(vars);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SearchScript search(Object compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
|
||||
MockCompiledScript compiled = (MockCompiledScript) compiledScript;
|
||||
return compiled.createSearchScript(vars, lookup);
|
||||
throw new IllegalArgumentException("mock script engine does not know how to handle context [" + context.name + "]");
|
||||
}
|
||||
|
||||
public class MockCompiledScript {
|
||||
|
|
Loading…
Reference in New Issue