Disallow lang to be used with Stored Scripts (#25610)

Requests that execute a stored script will no longer be allowed to specify the lang of the script. This information is stored in the cluster state making only an id necessary to execute against. Putting a stored script will still require a lang.
This commit is contained in:
Jack Conradson 2017-07-12 07:55:57 -07:00 committed by GitHub
parent 8d7cbc43b5
commit d2b4f7ac5a
16 changed files with 70 additions and 131 deletions

View File

@ -283,7 +283,7 @@ public class CRUDDocumentationIT extends ESRestHighLevelClientTestCase {
request = new UpdateRequest("posts", "doc", "1").fetchSource(true);
//tag::update-request-with-stored-script
Script stored =
new Script(ScriptType.STORED, "painless", "increment-field", parameters); // <1>
new Script(ScriptType.STORED, null, "increment-field", parameters); // <1>
request.script(stored); // <2>
//end::update-request-with-stored-script
updateResponse = client.update(request);

View File

@ -339,7 +339,7 @@ public class QueryDSLDocumentationTests extends ESTestCase {
parameters.put("param1", 5);
scriptQuery(new Script(
ScriptType.STORED, // <1>
"painless", // <2>
null, // <2>
"myscript", // <3>
singletonMap("param1", 5))); // <4>
// end::script_file

View File

@ -19,15 +19,12 @@
package org.elasticsearch.script;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.bytes.BytesArray;
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.logging.DeprecationLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import org.elasticsearch.common.xcontent.ToXContentObject;
@ -66,8 +63,7 @@ import java.util.Objects;
* <li> {@link ScriptType#STORED}
* <ul>
* <li> {@link Script#lang} - the language will be specified when storing the script, so this should
* be {@code null}; however, this can be specified to look up a stored
* script as part of the deprecated API
* be {@code null}
* <li> {@link Script#idOrCode} - specifies the id of the stored script to be looked up, must not be {@code null}
* <li> {@link Script#options} - compiler options will be specified when a stored script is stored,
* so they have no meaning here and must be {@code null}
@ -78,16 +74,6 @@ import java.util.Objects;
*/
public final class Script implements ToXContentObject, Writeable {
/**
* Standard logger necessary for allocation of the deprecation logger.
*/
private static final Logger LOGGER = ESLoggerFactory.getLogger(ScriptMetaData.class);
/**
* Deprecation logger necessary for namespace changes related to stored scripts.
*/
private static final DeprecationLogger DEPRECATION_LOGGER = new DeprecationLogger(LOGGER);
/**
* The name of the of the default scripting language.
*/
@ -233,7 +219,7 @@ public final class Script implements ToXContentObject, Writeable {
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify <id> for an [" + ScriptType.INLINE.getParseField().getPreferredName() + "] script");
"must specify <id> for an inline script");
}
if (options.size() > 1 || options.size() == 1 && options.get(CONTENT_TYPE_OPTION) == null) {
@ -242,26 +228,21 @@ public final class Script implements ToXContentObject, Writeable {
throw new IllegalArgumentException("illegal compiler options [" + options + "] specified");
}
} else if (type == ScriptType.STORED) {
// Only issue this deprecation warning if we aren't using a template. Templates during
// this deprecation phase must always specify the default template language or they would
// possibly pick up a script in a different language as defined by the user under the new
// namespace unintentionally.
if (lang != null && lang.equals(DEFAULT_TEMPLATE_LANG) == false) {
DEPRECATION_LOGGER.deprecated("specifying the field [" + LANG_PARSE_FIELD.getPreferredName() + "] " +
"for executing " + ScriptType.STORED + " scripts is deprecated; use only the field " +
"[" + ScriptType.STORED.getParseField().getPreferredName() + "] to specify an <id>");
if (lang != null) {
throw new IllegalArgumentException(
"illegally specified <lang> for a stored script");
}
if (idOrCode == null) {
throw new IllegalArgumentException(
"must specify <code> for an [" + ScriptType.STORED.getParseField().getPreferredName() + "] script");
"must specify <code> for a stored script");
}
if (options.isEmpty()) {
options = null;
} else {
throw new IllegalArgumentException("field [" + OPTIONS_PARSE_FIELD.getPreferredName() + "] " +
"cannot be specified using a [" + ScriptType.STORED.getParseField().getPreferredName() + "] script");
"cannot be specified using a stored script");
}
}
@ -423,11 +404,14 @@ public final class Script implements ToXContentObject, Writeable {
this.lang = Objects.requireNonNull(lang);
this.options = Collections.unmodifiableMap(Objects.requireNonNull(options));
} else if (type == ScriptType.STORED) {
this.lang = lang;
if (lang != null) {
throw new IllegalArgumentException("lang cannot be specified for stored scripts");
}
this.lang = null;
if (options != null) {
throw new IllegalStateException(
"options must be null for [" + ScriptType.STORED.getParseField().getPreferredName() + "] scripts");
throw new IllegalStateException("options cannot be specified for stored scripts");
}
this.options = null;
@ -455,7 +439,8 @@ public final class Script implements ToXContentObject, Writeable {
// same order as the constructor.
} else if (in.getVersion().onOrAfter(Version.V_5_1_1)) {
this.type = ScriptType.readFrom(in);
this.lang = in.readString();
String lang = in.readString();
this.lang = this.type == ScriptType.STORED ? null : lang;
this.idOrCode = in.readString();
@SuppressWarnings("unchecked")
@ -482,7 +467,7 @@ public final class Script implements ToXContentObject, Writeable {
String lang = in.readOptionalString();
if (lang == null) {
this.lang = DEFAULT_SCRIPT_LANG;
this.lang = this.type == ScriptType.STORED ? null : DEFAULT_SCRIPT_LANG;
} else {
this.lang = lang;
}

View File

@ -231,28 +231,11 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
String id = idOrCode;
// lang may be null when looking up a stored script, so we must get the
// source to retrieve the lang before checking if the context is supported
if (type == ScriptType.STORED) {
// search template requests can possibly pass in the entire path instead
// of just an id for looking up a stored script, so we parse the path and
// check for appropriate errors
String[] path = id.split("/");
if (path.length == 3) {
if (lang != null && lang.equals(path[1]) == false) {
throw new IllegalStateException("conflicting script languages, found [" + path[1] + "] but expected [" + lang + "]");
}
id = path[2];
deprecationLogger.deprecated("use of </lang/id> [" + idOrCode + "] for looking up" +
" stored scripts/templates has been deprecated, use only <id> [" + id + "] instead");
} else if (path.length != 1) {
throw new IllegalArgumentException("illegal stored script format [" + id + "] use only <id>");
}
// a stored script must be pulled from the cluster state every time in case
// * lang and options will both be null when looking up a stored script,
// so we must get the source to retrieve the them before checking if the
// context is supported
// * a stored script must be pulled from the cluster state every time in case
// the script has been updated since the last compilation
StoredScriptSource source = getScriptFromClusterState(id, lang);
lang = source.getLang();
@ -262,7 +245,7 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
// TODO: fix this through some API or something, that's wrong
// special exception to prevent expressions from compiling as update or mapping scripts
boolean expression = "expression".equals(script.getLang());
boolean expression = "expression".equals(lang);
boolean notSupported = context.name.equals(ExecutableScript.UPDATE_CONTEXT.name);
if (expression && notSupported) {
throw new UnsupportedOperationException("scripts of type [" + script.getType() + "]," +
@ -303,7 +286,6 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
try {
// Either an un-cached inline script or indexed script
// If the script type is inline the name will be the same as the code for identification in exceptions
// but give the script engine the chance to be better, give it separate name + source code
// for the inline case, then its anonymous: null.
if (logger.isTraceEnabled()) {
@ -379,22 +361,16 @@ public class ScriptService extends AbstractComponent implements Closeable, Clust
}
StoredScriptSource getScriptFromClusterState(String id, String lang) {
if (lang != null && isLangSupported(lang) == false) {
throw new IllegalArgumentException("unable to get stored script with unsupported lang [" + lang + "]");
}
ScriptMetaData scriptMetadata = clusterState.metaData().custom(ScriptMetaData.TYPE);
if (scriptMetadata == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "]" +
(lang == null ? "" : " using lang [" + lang + "]") + " in cluster state");
throw new ResourceNotFoundException("unable to find script [" + id + "] in cluster state");
}
StoredScriptSource source = scriptMetadata.getStoredScript(id, lang);
if (source == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "]" +
(lang == null ? "" : " using lang [" + lang + "]") + " in cluster state");
throw new ResourceNotFoundException("unable to find script [" + id + "] in cluster state");
}
return source;

View File

@ -18,13 +18,6 @@
*/
package org.elasticsearch.script;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.cluster.ClusterName;
@ -40,6 +33,12 @@ import org.elasticsearch.env.Environment;
import org.elasticsearch.test.ESTestCase;
import org.junit.Before;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.sameInstance;
@ -77,8 +76,11 @@ public class ScriptServiceTests extends ESTestCase {
scriptService = new ScriptService(finalSettings, engines, contexts) {
@Override
StoredScriptSource getScriptFromClusterState(String id, String lang) {
if (lang != null) {
throw new IllegalArgumentException("expected null lang in test");
}
//mock the script that gets retrieved from an index
return new StoredScriptSource(lang, "1+1", Collections.emptyMap());
return new StoredScriptSource("test", "1+1", Collections.emptyMap());
}
};
}
@ -128,7 +130,7 @@ public class ScriptServiceTests extends ESTestCase {
buildScriptService(Settings.EMPTY);
assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT);
assertCompileAccepted("painless", "script", ScriptType.STORED, SearchScript.CONTEXT);
assertCompileAccepted(null, "script", ScriptType.STORED, SearchScript.CONTEXT);
}
public void testAllowAllScriptContextSettings() throws IOException {
@ -146,7 +148,7 @@ public class ScriptServiceTests extends ESTestCase {
buildScriptService(builder.build());
assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT);
assertCompileRejected("painless", "script", ScriptType.STORED, SearchScript.CONTEXT);
assertCompileRejected(null, "script", ScriptType.STORED, SearchScript.CONTEXT);
}
public void testAllowSomeScriptContextSettings() throws IOException {
@ -165,7 +167,7 @@ public class ScriptServiceTests extends ESTestCase {
buildScriptService(builder.build());
assertCompileRejected("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT);
assertCompileRejected("painless", "script", ScriptType.STORED, SearchScript.CONTEXT);
assertCompileRejected(null, "script", ScriptType.STORED, SearchScript.CONTEXT);
}
public void testAllowNoScriptContextSettings() throws IOException {
@ -183,7 +185,7 @@ public class ScriptServiceTests extends ESTestCase {
String type = scriptEngine.getType();
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () ->
scriptService.compile(new Script(randomFrom(ScriptType.values()), type, "test", Collections.emptyMap()), ExecutableScript.INGEST_CONTEXT));
scriptService.compile(new Script(ScriptType.INLINE, type, "test", Collections.emptyMap()), ExecutableScript.INGEST_CONTEXT));
assertThat(e.getMessage(), containsString("script context [" + ExecutableScript.INGEST_CONTEXT.name + "] not supported"));
}
@ -216,7 +218,7 @@ public class ScriptServiceTests extends ESTestCase {
public void testIndexedScriptCountedInCompilationStats() throws IOException {
buildScriptService(Settings.EMPTY);
scriptService.compile(new Script(ScriptType.STORED, "test", "script", Collections.emptyMap()), randomFrom(contexts.values()));
scriptService.compile(new Script(ScriptType.STORED, null, "script", Collections.emptyMap()), randomFrom(contexts.values()));
assertEquals(1L, scriptService.stats().getCompilations());
}

View File

@ -791,13 +791,13 @@ public class ScriptedMetricIT extends ESIntegTestCase {
scriptedMetric("scripted")
.params(params)
.initScript(
new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "initScript_stored", Collections.emptyMap()))
new Script(ScriptType.STORED, null, "initScript_stored", Collections.emptyMap()))
.mapScript(
new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "mapScript_stored", Collections.emptyMap()))
new Script(ScriptType.STORED, null, "mapScript_stored", Collections.emptyMap()))
.combineScript(
new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "combineScript_stored", Collections.emptyMap()))
new Script(ScriptType.STORED, null, "combineScript_stored", Collections.emptyMap()))
.reduceScript(
new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "reduceScript_stored", Collections.emptyMap())))
new Script(ScriptType.STORED, null, "reduceScript_stored", Collections.emptyMap())))
.get();
assertSearchResponse(response);
assertThat(response.getHits().getTotalHits(), equalTo(numDocs));

View File

@ -496,7 +496,7 @@ public class BucketScriptIT extends ESIntegTestCase {
.subAggregation(sum("field4Sum").field(FIELD_4_NAME))
.subAggregation(
bucketScript("seriesArithmetic",
new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "my_script", Collections.emptyMap()),
new Script(ScriptType.STORED, null, "my_script", Collections.emptyMap()),
"field2Sum", "field3Sum", "field4Sum"))).execute().actionGet();
assertSearchResponse(response);

View File

@ -438,7 +438,7 @@ public class BucketSelectorIT extends ESIntegTestCase {
.setContent(new BytesArray("{ \"script\": \"Double.isNaN(_value0) ? false : (_value0 + _value1 > 100)\" }"),
XContentType.JSON));
Script script = new Script(ScriptType.STORED, CustomScriptPlugin.NAME, "my_script", Collections.emptyMap());
Script script = new Script(ScriptType.STORED, null, "my_script", Collections.emptyMap());
SearchResponse response = client()
.prepareSearch("idx")

View File

@ -26,3 +26,11 @@ The `_index` variable has been removed. If you used it for advanced scoring, con
All of the existing scripting security settings have been removed. Instead
they are replaced with `script.allowed_types` and `script.allowed_contexts`.
==== `lang` can no longer be specified when using a stored script as part of a request
The `lang` variable can no longer be specified as part of a request that uses a stored
script otherwise an error will occur. Note that a request using a stored script is
different from a request that puts a stored script. The language of the script has
already been stored as part of the cluster state and an `id` is sufficient to access
all of the information necessary to execute a stored script.

View File

@ -115,28 +115,6 @@ minute will be compiled. You can change this setting dynamically by setting
Scripts may be stored in and retrieved from the cluster state using the
`_scripts` end-point.
==== Deprecated Namespace
The namespace for stored scripts using both `lang` and `id` as a unique
identifier has been deprecated. The new namespace for stored scripts will
only use `id`. Stored scripts with the same `id`, but different `lang`'s
will no longer be allowed in 6.0. To comply with the new namespace for
stored scripts, existing stored scripts should be deleted and put again.
Any scripts that share an `id` but have different `lang`s will need to
be re-named. For example, take the following:
"id": "example", "lang": "painless"
"id": "example", "lang": "expressions"
The above scripts will conflict under the new namespace since the id's are
the same. At least one will have to be re-named to comply with the new
namespace of only `id`.
As a final caveat, stored search templates and stored scripts share
the same namespace, so if a search template has the same `id` as a
stored script, one of the two will have to be re-named as well using
delete and put requests.
==== Request Examples
The following are examples of using a stored script that lives at

View File

@ -136,7 +136,7 @@ public final class ScriptProcessor extends AbstractProcessor {
script = new Script(INLINE, lang, source, (Map<String, Object>)params);
scriptPropertyUsed = "source";
} else if (Strings.hasLength(id)) {
script = new Script(STORED, lang, id, (Map<String, Object>)params);
script = new Script(STORED, null, id, (Map<String, Object>)params);
scriptPropertyUsed = "id";
} else {
throw newConfigurationException(TYPE, processorTag, null, "Could not initialize script");

View File

@ -57,7 +57,7 @@ public class ScriptProcessorFactoryTests extends ESTestCase {
String randomType = randomFrom("id", "source");
configMap.put(randomType, "foo");
ScriptProcessor processor = factory.create(null, randomAlphaOfLength(10), configMap);
assertThat(processor.getScript().getLang(), equalTo(Script.DEFAULT_SCRIPT_LANG));
assertThat(processor.getScript().getLang(), equalTo(randomType.equals("id") ? null : Script.DEFAULT_SCRIPT_LANG));
assertThat(processor.getScript().getType().toString(), equalTo(ingestScriptParamToType.get(randomType)));
assertThat(processor.getScript().getParams(), equalTo(Collections.emptyMap()));
}
@ -69,7 +69,7 @@ public class ScriptProcessorFactoryTests extends ESTestCase {
configMap.put(randomType, "foo");
configMap.put("params", randomParams);
ScriptProcessor processor = factory.create(null, randomAlphaOfLength(10), configMap);
assertThat(processor.getScript().getLang(), equalTo(Script.DEFAULT_SCRIPT_LANG));
assertThat(processor.getScript().getLang(), equalTo(randomType.equals("id") ? null : Script.DEFAULT_SCRIPT_LANG));
assertThat(processor.getScript().getType().toString(), equalTo(ingestScriptParamToType.get(randomType)));
assertThat(processor.getScript().getParams(), equalTo(randomParams));
}

View File

@ -58,7 +58,7 @@ public class StoredExpressionTests extends ESIntegTestCase {
client().prepareIndex("test", "scriptTest", "1").setSource("{\"theField\":\"foo\"}", XContentType.JSON).get();
try {
client().prepareUpdate("test", "scriptTest", "1")
.setScript(new Script(ScriptType.STORED, ExpressionScriptEngine.NAME, "script1", Collections.emptyMap())).get();
.setScript(new Script(ScriptType.STORED, null, "script1", Collections.emptyMap())).get();
fail("update script should have been rejected");
} catch(Exception e) {
assertThat(e.getMessage(), containsString("failed to execute script"));
@ -67,7 +67,7 @@ public class StoredExpressionTests extends ESIntegTestCase {
try {
client().prepareSearch()
.setSource(
new SearchSourceBuilder().scriptField("test1", new Script(ScriptType.STORED, "expression", "script1", Collections.emptyMap())))
new SearchSourceBuilder().scriptField("test1", new Script(ScriptType.STORED, null, "script1", Collections.emptyMap())))
.setIndices("test").setTypes("scriptTest").get();
fail("search script should have been rejected");
} catch(Exception e) {
@ -77,7 +77,7 @@ public class StoredExpressionTests extends ESIntegTestCase {
client().prepareSearch("test")
.setSource(
new SearchSourceBuilder().aggregation(AggregationBuilders.terms("test").script(
new Script(ScriptType.STORED, "expression", "script1", Collections.emptyMap())))).get();
new Script(ScriptType.STORED, null, "script1", Collections.emptyMap())))).get();
} catch (Exception e) {
assertThat(e.toString(), containsString("cannot execute scripts using [aggs] context"));
}

View File

@ -35,6 +35,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.TemplateScript;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.threadpool.ThreadPool;
@ -95,7 +96,8 @@ public class TransportSearchTemplateAction extends HandledTransportAction<Search
static SearchRequest convert(SearchTemplateRequest searchTemplateRequest, SearchTemplateResponse response, ScriptService scriptService,
NamedXContentRegistry xContentRegistry) throws IOException {
Script script = new Script(searchTemplateRequest.getScriptType(), TEMPLATE_LANG, searchTemplateRequest.getScript(),
Script script = new Script(searchTemplateRequest.getScriptType(),
searchTemplateRequest.getScriptType() == ScriptType.STORED ? null : TEMPLATE_LANG, searchTemplateRequest.getScript(),
searchTemplateRequest.getScriptParams() == null ? Collections.emptyMap() : searchTemplateRequest.getScriptParams());
TemplateScript compiledScript = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams());
String source = compiledScript.execute();

View File

@ -18,6 +18,7 @@
*/
package org.elasticsearch.script.mustache;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptResponse;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.search.SearchRequest;
@ -201,12 +202,6 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
getResponse = client().admin().cluster()
.prepareGetStoredScript(MustacheScriptEngine.NAME, "testTemplate").get();
assertNull(getResponse.getSource());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type"))
.setScript("/template_index/mustache/1000").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get());
assertThat(e.getMessage(), containsString("illegal stored script format [/template_index/mustache/1000] use only <id>"));
}
public void testIndexedTemplate() throws Exception {
@ -266,16 +261,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
.get();
assertHitCount(searchResponse.getResponse(), 4);
expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client())
expectThrows(ResourceNotFoundException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest().indices("test").types("type"))
.setScript("/template_index/mustache/1000")
.setScriptType(ScriptType.STORED)
.setScriptParams(templateParams)
.get());
expectThrows(IllegalArgumentException.class, () -> new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest().indices("test").types("type"))
.setScript("/myindex/mustache/1")
.setScript("1000")
.setScriptType(ScriptType.STORED)
.setScriptParams(templateParams)
.get());
@ -283,11 +271,9 @@ public class SearchTemplateIT extends ESSingleNodeTestCase {
templateParams.put("fieldParam", "bar");
searchResponse = new SearchTemplateRequestBuilder(client())
.setRequest(new SearchRequest("test").types("type"))
.setScript("/mustache/2").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.setScript("2").setScriptType(ScriptType.STORED).setScriptParams(templateParams)
.get();
assertHitCount(searchResponse.getResponse(), 1);
assertWarnings("use of </lang/id> [/mustache/2] for looking up" +
" stored scripts/templates has been deprecated, use only <id> [2] instead");
}
// Relates to #10397

View File

@ -223,6 +223,8 @@ public class RoundTripTests extends ESTestCase {
String idOrCode = randomSimpleString(random());
Map<String, Object> params = Collections.emptyMap();
return new Script(type, lang, idOrCode, params);
type = ScriptType.STORED;
return new Script(type, type == ScriptType.STORED ? null : lang, idOrCode, params);
}
}