Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to Groovy as the default scripting language #6571

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions docs/reference/modules/advanced-scripting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,12 @@ depending on the shard the current document resides in.

`_index.numDocs()`::

Number of documents in shard.
Number of documents in shard.

`_index.maxDoc()`::

Maximal document number in shard.

`_index.numDeletedDocs()`::

Number of deleted documents in shard.
Expand All @@ -62,7 +62,7 @@ Field statistics can be accessed with a subscript operator like this:
`_index['FIELD'].sumttf()`::

Sum of `ttf` over all terms that appear in field `FIELD` in all documents.

`_index['FIELD'].sumdf()`::

The sum of `df` s over all terms that appear in field `FIELD` in all
Expand All @@ -77,7 +77,7 @@ The number of terms in a field cannot be accessed using the `_index` variable. S
=== Term statistics:

Term statistics for a field can be accessed with a subscript operator like
this: `_index['FIELD']['TERM']`. This will never return null, even if term or field does not exist.
this: `_index['FIELD']['TERM']`. This will never return null, even if term or field does not exist.
If you do not need the term frequency, call `_index['FIELD'].get('TERM', 0)`
to avoid uneccesary initialization of the frequencies. The flag will have only
affect is your set the `index_options` to `docs` (see <<mapping-core-types, mapping documentation>>).
Expand Down Expand Up @@ -162,11 +162,11 @@ Positions can be accessed with an iterator that returns an object

Example: sums up all payloads for the term `foo`.

[source,mvel]
[source,groovy]
---------------------------------------------------------
termInfo = _index['my_field'].get('foo',_PAYLOADS);
score = 0;
for (pos : termInfo) {
for (pos in termInfo) {
score = score + pos.payloadAsInt(0);
}
return score;
Expand All @@ -181,4 +181,3 @@ The `_index` variable can only be used to gather statistics for single terms. If
https://lucene.apache.org/core/4_0_0/core/org/apache/lucene/index/Fields.html[Fields]
instance. This object can then be used as described in https://lucene.apache.org/core/4_0_0/core/org/apache/lucene/index/Fields.html[lucene doc] to iterate over fields and then for each field iterate over each term in the field.
The method will return null if the term vectors were not stored.

116 changes: 83 additions & 33 deletions docs/reference/modules/scripting.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,29 @@ expressions. For example, scripts can be used to return "script fields"
as part of a search request, or can be used to evaluate a custom score
for a query and so on.

The scripting module uses by default http://mvel.codehaus.org/[mvel] as
the scripting language with some extensions. mvel is used since it is
extremely fast and very simple to use, and in most cases, simple
expressions are needed (for example, mathematical equations).
deprecated[1.3.0,Groovy has replaced Mvel as the default scripting language]

The scripting module uses by default http://groovy.codehaus.org/[groovy]
(previously http://mvel.codehaus.org/[mvel]) as the scripting language with some
extensions. Groovy is used since it is extremely fast and very simple to use.

Additional `lang` plugins are provided to allow to execute scripts in
different languages. Currently supported plugins are `lang-javascript`
for JavaScript, `lang-groovy` for Groovy, and `lang-python` for Python.
for JavaScript, `lang-mvel` for Mvel, and `lang-python` for Python.
All places where a `script` parameter can be used, a `lang` parameter
(on the same level) can be provided to define the language of the
script. The `lang` options are `mvel`, `js`, `groovy`, `python`, and
script. The `lang` options are `groovy`, `js`, `mvel`, `python`, and
`native`.

added[1.2.0, Dynamic scripting is disabled by default since version 1.2.0]
added[1.2.0, Dynamic scripting is disabled for non-sandboxed languages by default since version 1.2.0]

To increase security, Elasticsearch does not allow you to specify scripts with a
request. Instead, scripts must be placed in the `scripts` directory inside the
configuration directory (the directory where elasticsearch.yml is). Scripts
placed into this directory will automatically be picked up and be available to
be used. Once a script has been placed in this directory, it can be referenced
by name. For example, a script called `calculate-score.mvel` can be referenced
in a request like this:
To increase security, Elasticsearch does not allow you to specify scripts for
non-sandboxed languages with a request. Instead, scripts must be placed in the
`scripts` directory inside the configuration directory (the directory where
elasticsearch.yml is). Scripts placed into this directory will automatically be
picked up and be available to be used. Once a script has been placed in this
directory, it can be referenced by name. For example, a script called
`calculate-score.groovy` can be referenced in a request like this:

[source,sh]
--------------------------------------------------
Expand All @@ -36,13 +37,13 @@ config
├── elasticsearch.yml
├── logging.yml
└── scripts
└── calculate-score.mvel
└── calculate-score.groovy
--------------------------------------------------

[source,sh]
--------------------------------------------------
$ cat config/scripts/calculate-score.mvel
Math.log(_score * 2) + my_modifier
$ cat config/scripts/calculate-score.groovy
log(_score * 2) + my_modifier
--------------------------------------------------

[source,js]
Expand Down Expand Up @@ -75,21 +76,14 @@ exists under, and the file name without the lang extension. For example,
a script placed under `config/scripts/group1/group2/test.py` will be
named `group1_group2_test`.

[float]
=== Default Scripting Language

The default scripting language (assuming no `lang` parameter is
provided) is `mvel`. In order to change it set the `script.default_lang`
to the appropriate language.

[float]
=== Enabling dynamic scripting

We recommend running Elasticsearch behind an application or proxy,
which protects Elasticsearch from the outside world. If users are
allowed to run dynamic scripts (even in a search request), then they
have the same access to your box as the user that Elasticsearch is
running as. For this reason dynamic scripting is disabled by default.
We recommend running Elasticsearch behind an application or proxy, which
protects Elasticsearch from the outside world. If users are allowed to run
dynamic scripts (even in a search request), then they have the same access to
your box as the user that Elasticsearch is running as. For this reason dynamic
scripting is allowed only for sandboxed languages by default.

First, you should not run Elasticsearch as the `root` user, as this would allow
a script to access or do *anything* on your server, without limitations. Second,
Expand All @@ -109,6 +103,54 @@ _native_ Java scripts registered through plugins, it also allows users to run
arbitrary scripts via the API. Instead of sending the name of the file as the
script, the body of the script can be sent instead.

There are three possible configuration values for the `script.disable_dynamic`
setting, the default value is `sandbox`:

[cols="<,<",options="header",]
|=======================================================================
|Value |Description
| `true` |all dynamic scripting is disabled, scripts must be placed in the `config/scripts` directory.
| `false` |all dynamic scripting is enabled, scripts may be sent as strings in requests.
| `sandbox` |scripts may be sent as strings for languages that are sandboxed.
|=======================================================================

[float]
=== Default Scripting Language

The default scripting language (assuming no `lang` parameter is provided) is
`groovy`. In order to change it, set the `script.default_lang` to the
appropriate language.

[float]
=== Groovy Sandboxing

Elasticsearch sandboxes Groovy scripts that are compiled and executed in order
to ensure they don't perform unwanted actions. There are a number of options
that can be used for configuring this sandbox:

`script.groovy.sandbox.receiver_whitelist`::

Comma-separated list of string classes for objects that may have methods
invoked.

`script.groovy.sandbox.package_whitelist`::

Comma-separated list of packages under which new objects may be constructed.

`script.groovy.sandbox.class_whitelist`::

Comma-separated list of classes that are allowed to be constructed.

`script.groovy.sandbox.method_blacklist`::

Comma-separated list of methods that are never allowed to be invoked,
regardless of target object.

`script.groovy.sandbox.enabled`::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably one of the most important sandbox options, so consider moving this to the top of the group. I'd also consider making this listing a table with name, description and default value(s).


Flag to disable the sandbox (defaults to `true` meaning the sandbox is
enabled).

[float]
=== Automatic Script Reloading

Expand All @@ -122,7 +164,7 @@ to `false`.
[float]
=== Native (Java) Scripts

Even though `mvel` is pretty fast, this allows to register native Java based
Even though `groovy` is pretty fast, this allows to register native Java based
scripts for faster execution.

In order to allow for scripts, the `NativeScriptFactory` needs to be
Expand Down Expand Up @@ -267,16 +309,14 @@ loaded for other purposes.


[float]
=== mvel Built In Functions
=== Groovy Built In Functions

There are several built in functions that can be used within scripts.
They include:

[cols="<,<",options="header",]
|=======================================================================
|Function |Description
|`time()` |The current time in milliseconds.

|`sin(a)` |Returns the trigonometric sine of an angle.

|`cos(a)` |Returns the trigonometric cosine of an angle.
Expand Down Expand Up @@ -350,6 +390,16 @@ power of the second argument.
or underflow.
|=======================================================================

[float]
=== Floating point numbers in Groovy

When using floating-point literals in Groovy scripts, Groovy will automatically
use BigDecimal instead of a floating point equivalent to support the
'least-surprising' approach to literal math operations. To use a floating-point
number instead, use the specific suffix on the number (ie, instead of 1.2, use
1.2f). See the http://groovy.codehaus.org/Groovy+Math[Groovy Math] page for more
information.

[float]
=== Arithmetic precision in MVEL

Expand Down
2 changes: 1 addition & 1 deletion rest-api-spec/api/update.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"lang": {
"type": "string",
"description": "The script language (default: mvel)"
"description": "The script language (default: groovy)"
},
"parent": {
"type": "string",
Expand Down
4 changes: 2 additions & 2 deletions rest-api-spec/test/update/15_script.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
id: 1
script: "1"
body:
lang: mvel
lang: groovy
script: "ctx._source.foo = bar"
params: { bar: 'xxx' }

Expand All @@ -40,7 +40,7 @@
index: test_1
type: test
id: 1
lang: mvel
lang: groovy
script: "ctx._source.foo = 'yyy'"

- match: { _index: test_1 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public UpdateRequestBuilder setScript(String script) {
/**
* The language of the script to execute.
* Valid options are: mvel, js, groovy, python, and native (Java)<br>
* Default: mvel
* Default: groovy
* <p>
* Ref: http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/modules-scripting.html
*/
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/elasticsearch/script/ScriptService.java
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public ScriptService(Settings settings, Environment env, Set<ScriptEngineService
TimeValue cacheExpire = componentSettings.getAsTime("cache.expire", null);
logger.debug("using script cache with max_size [{}], expire [{}]", cacheMaxSize, cacheExpire);

this.defaultLang = settings.get(DEFAULT_SCRIPTING_LANGUAGE_SETTING, "mvel");
this.defaultLang = settings.get(DEFAULT_SCRIPTING_LANGUAGE_SETTING, "groovy");
this.dynamicScriptingDisabled = DynamicScriptDisabling.parse(settings.get(DISABLE_DYNAMIC_SCRIPTING_SETTING, DISABLE_DYNAMIC_SCRIPTING_DEFAULT));

CacheBuilder cacheBuilder = CacheBuilder.newBuilder();
Expand Down
14 changes: 7 additions & 7 deletions src/test/java/org/elasticsearch/script/IndexLookupTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public void testDocumentationExample() throws Exception {
initTestData();

String script = "term = _index['float_payload_field'].get('b'," + includeAllFlag
+ "); payloadSum=0; for (pos : term) {payloadSum = pos.payloadAsInt(0)}; payloadSum";
+ "); payloadSum=0; for (pos in term) {payloadSum = pos.payloadAsInt(0)}; payloadSum";

// non existing field: sum should be 0
HashMap<String, Object> zeroArray = new HashMap<>();
Expand All @@ -215,7 +215,7 @@ public void testDocumentationExample() throws Exception {
checkValueInEachDoc(script, zeroArray, 3);

script = "term = _index['int_payload_field'].get('b'," + includeAllFlag
+ "); payloadSum=0; for (pos : term) {payloadSum = payloadSum + pos.payloadAsInt(0)}; payloadSum";
+ "); payloadSum=0; for (pos in term) {payloadSum = payloadSum + pos.payloadAsInt(0)}; payloadSum";

// existing field: sums should be as here:
zeroArray.put("1", 5);
Expand Down Expand Up @@ -263,26 +263,26 @@ public void testIteratorAndRecording() throws Exception {

private String createPositionsArrayScriptGetInfoObjectTwice(String term, String flags, String what) {
String script = "term = _index['int_payload_field'].get('" + term + "'," + flags
+ "); array=[]; for (pos : term) {array.add(pos." + what + ")}; _index['int_payload_field'].get('" + term + "',"
+ flags + "); array=[]; for (pos : term) {array.add(pos." + what + ")}";
+ "); array=[]; for (pos in term) {array.add(pos." + what + ")}; _index['int_payload_field'].get('" + term + "',"
+ flags + "); array=[]; for (pos in term) {array.add(pos." + what + ")}";
return script;
}

private String createPositionsArrayScriptIterateTwice(String term, String flags, String what) {
String script = "term = _index['int_payload_field'].get('" + term + "'," + flags
+ "); array=[]; for (pos : term) {array.add(pos." + what + ")}; array=[]; for (pos : term) {array.add(pos." + what
+ "); array=[]; for (pos in term) {array.add(pos." + what + ")}; array=[]; for (pos in term) {array.add(pos." + what
+ ")}; array";
return script;
}

private String createPositionsArrayScript(String field, String term, String flags, String what) {
String script = "term = _index['" + field + "'].get('" + term + "'," + flags
+ "); array=[]; for (pos : term) {array.add(pos." + what + ")}; array";
+ "); array=[]; for (pos in term) {array.add(pos." + what + ")}; array";
return script;
}

private String createPositionsArrayScriptDefaultGet(String field, String term, String what) {
String script = "term = _index['" + field + "']['" + term + "']; array=[]; for (pos : term) {array.add(pos." + what
String script = "term = _index['" + field + "']['" + term + "']; array=[]; for (pos in term) {array.add(pos." + what
+ ")}; array";
return script;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ public void testFieldCollapsing() throws Exception {
topHits("hits").setSize(1)
)
.subAggregation(
max("max_score").script("_doc.score")
max("max_score").script("_doc.score()")
)
)
.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,7 @@ public void testSortMinValueScript() throws IOException {
// test the long values
SearchResponse searchResponse = client().prepareSearch()
.setQuery(matchAllQuery())
.addScriptField("min", "retval = Long.MAX_VALUE; for (v : doc['lvalue'].values){ retval = min(v, retval) }; retval")
.addScriptField("min", "retval = Long.MAX_VALUE; for (v in doc['lvalue'].values){ retval = min(v, retval) }; retval")
.addSort("ord", SortOrder.ASC).setSize(10)
.execute().actionGet();

Expand All @@ -700,7 +700,7 @@ public void testSortMinValueScript() throws IOException {
// test the double values
searchResponse = client().prepareSearch()
.setQuery(matchAllQuery())
.addScriptField("min", "retval = Double.MAX_VALUE; for (v : doc['dvalue'].values){ retval = min(v, retval) }; retval")
.addScriptField("min", "retval = Double.MAX_VALUE; for (v in doc['dvalue'].values){ retval = min(v, retval) }; retval")
.addSort("ord", SortOrder.ASC).setSize(10)
.execute().actionGet();

Expand All @@ -714,7 +714,7 @@ public void testSortMinValueScript() throws IOException {
// test the string values
searchResponse = client().prepareSearch()
.setQuery(matchAllQuery())
.addScriptField("min", "retval = Integer.MAX_VALUE; for (v : doc['svalue'].values){ retval = min(Integer.parseInt(v), retval) }; retval")
.addScriptField("min", "retval = Integer.MAX_VALUE; for (v in doc['svalue'].values){ retval = min(Integer.parseInt(v), retval) }; retval")
.addSort("ord", SortOrder.ASC).setSize(10)
.execute().actionGet();

Expand All @@ -728,7 +728,7 @@ public void testSortMinValueScript() throws IOException {
// test the geopoint values
searchResponse = client().prepareSearch()
.setQuery(matchAllQuery())
.addScriptField("min", "retval = Double.MAX_VALUE; for (v : doc['gvalue'].values){ retval = min(v.lon, retval) }; retval")
.addScriptField("min", "retval = Double.MAX_VALUE; for (v in doc['gvalue'].values){ retval = min(v.lon, retval) }; retval")
.addSort("ord", SortOrder.ASC).setSize(10)
.execute().actionGet();

Expand Down