diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java index b15467d24ba..242c2d9237d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/StoredScriptsIT.java @@ -47,7 +47,7 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase { Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); PutStoredScriptRequest request = - new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource); + new PutStoredScriptRequest(id, "score", new BytesArray("{}"), XContentType.JSON, scriptSource); assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync)); GetStoredScriptRequest getRequest = new GetStoredScriptRequest("calculate-score"); @@ -66,7 +66,7 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase { Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); PutStoredScriptRequest request = - new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource); + new PutStoredScriptRequest(id, "score", new BytesArray("{}"), XContentType.JSON, scriptSource); assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync)); DeleteStoredScriptRequest deleteRequest = new DeleteStoredScriptRequest(id); @@ -89,7 +89,7 @@ public class StoredScriptsIT extends ESRestHighLevelClientTestCase { Collections.singletonMap(Script.CONTENT_TYPE_OPTION, XContentType.JSON.mediaType())); PutStoredScriptRequest request = - new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource); + new PutStoredScriptRequest(id, "score", new BytesArray("{}"), XContentType.JSON, scriptSource); assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync)); Map script = getAsMap("/_scripts/" + id); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java index 9165c5cf10d..ac4f2269f9c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/StoredScriptsDocumentationIT.java @@ -307,7 +307,7 @@ public class StoredScriptsDocumentationIT extends ESRestHighLevelClientTestCase private void putStoredScript(String id, StoredScriptSource scriptSource) throws IOException { PutStoredScriptRequest request = - new PutStoredScriptRequest(id, "search", new BytesArray("{}"), XContentType.JSON, scriptSource); + new PutStoredScriptRequest(id, "score", new BytesArray("{}"), XContentType.JSON, scriptSource); assertAcked(execute(request, highLevelClient()::putScript, highLevelClient()::putScriptAsync)); } } diff --git a/docs/painless/painless-contexts/painless-field-context.asciidoc b/docs/painless/painless-contexts/painless-field-context.asciidoc index 4c767ca3891..80307b25ea5 100644 --- a/docs/painless/painless-contexts/painless-field-context.asciidoc +++ b/docs/painless/painless-contexts/painless-field-context.asciidoc @@ -14,13 +14,10 @@ a customized value for each document in the results of a query. Contains the fields of the specified document where each field is a `List` of values. -{ref}/mapping-source-field.html[`ctx['_source']`] (`Map`):: +{ref}/mapping-source-field.html[`params['_source']`] (`Map`, read-only):: Contains extracted JSON in a `Map` and `List` structure for the fields existing in a stored document. -`_score` (`double` read-only):: - The original score of the specified document. - *Return* `Object`:: @@ -28,4 +25,4 @@ a customized value for each document in the results of a query. *API* -The standard <> is available. \ No newline at end of file +The standard <> is available. diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionFieldScript.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionFieldScript.java new file mode 100644 index 00000000000..14fd1dd6c12 --- /dev/null +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionFieldScript.java @@ -0,0 +1,67 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.script.expression; + +import org.apache.lucene.expressions.Expression; +import org.apache.lucene.expressions.SimpleBindings; +import org.apache.lucene.index.LeafReaderContext; +import org.apache.lucene.search.DoubleValues; +import org.apache.lucene.search.DoubleValuesSource; +import org.elasticsearch.script.FieldScript; +import org.elasticsearch.script.GeneralScriptException; + +import java.io.IOException; + +public class ExpressionFieldScript implements FieldScript.LeafFactory { + private final Expression exprScript; + private final DoubleValuesSource source; + + ExpressionFieldScript(Expression e, SimpleBindings b) { + this.exprScript = e; + this.source = exprScript.getDoubleValuesSource(b); + } + + @Override + public FieldScript newInstance(final LeafReaderContext leaf) throws IOException { + return new FieldScript() { + + // Fake the scorer until setScorer is called. + DoubleValues values = source.getValues(leaf, null); + + @Override + public Object execute() { + try { + return values.doubleValue(); + } catch (Exception exception) { + throw new GeneralScriptException("Error evaluating " + exprScript, exception); + } + } + + @Override + public void setDocument(int d) { + try { + values.advanceExact(d); + } catch (IOException e) { + throw new IllegalStateException("Can't advance to doc using " + exprScript, e); + } + } + }; + } +} diff --git a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java index 0ece3834653..d719f7a2cbc 100644 --- a/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java +++ b/modules/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngine.java @@ -41,6 +41,7 @@ import org.elasticsearch.script.AggregationScript; import org.elasticsearch.script.BucketAggregationScript; import org.elasticsearch.script.BucketAggregationSelectorScript; import org.elasticsearch.script.ClassPermission; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.FilterScript; import org.elasticsearch.script.NumberSortScript; import org.elasticsearch.script.ScoreScript; @@ -139,6 +140,9 @@ public class ExpressionScriptEngine extends AbstractComponent implements ScriptE } else if (context.instanceClazz.equals(NumberSortScript.class)) { NumberSortScript.Factory factory = (p, lookup) -> newSortScript(expr, lookup, p); return context.factoryClazz.cast(factory); + } else if (context.instanceClazz.equals(FieldScript.class)) { + FieldScript.Factory factory = (p, lookup) -> newFieldScript(expr, lookup, p); + return context.factoryClazz.cast(factory); } throw new IllegalArgumentException("expression engine does not know how to handle script context [" + context.name + "]"); } @@ -289,6 +293,23 @@ public class ExpressionScriptEngine extends AbstractComponent implements ScriptE return new ExpressionAggregationScript(expr, bindings, specialValue); } + private FieldScript.LeafFactory newFieldScript(Expression expr, SearchLookup lookup, @Nullable Map vars) { + SimpleBindings bindings = new SimpleBindings(); + for (String variable : expr.variables) { + try { + if (vars != null && vars.containsKey(variable)) { + bindFromParams(vars, bindings, variable); + } else { + final ValueSource valueSource = getDocValueSource(variable, lookup); + bindings.add(variable, valueSource.asDoubleValuesSource()); + } + } catch (Exception e) { + throw convertToScriptException("link error", expr.sourceText, variable, e); + } + } + return new ExpressionFieldScript(expr, bindings); + } + /** * This is a hack for filter scripts, which must return booleans instead of doubles as expression do. * See https://github.com/elastic/elasticsearch/issues/26429. diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java similarity index 75% rename from modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java rename to modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java index 33e6239002e..b1872b30f1f 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/ExpressionFieldScriptTests.java @@ -24,10 +24,9 @@ import org.elasticsearch.index.fielddata.AtomicNumericFieldData; import org.elasticsearch.index.fielddata.IndexNumericFieldData; import org.elasticsearch.index.fielddata.SortedNumericDoubleValues; import org.elasticsearch.index.mapper.MapperService; -import org.elasticsearch.index.mapper.NumberFieldMapper.NumberFieldType; -import org.elasticsearch.index.mapper.NumberFieldMapper.NumberType; +import org.elasticsearch.index.mapper.NumberFieldMapper; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.ScriptException; -import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.lookup.SearchLookup; import org.elasticsearch.test.ESTestCase; @@ -35,12 +34,13 @@ import java.io.IOException; import java.text.ParseException; import java.util.Collections; +import static org.hamcrest.Matchers.equalTo; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ExpressionTests extends ESTestCase { +public class ExpressionFieldScriptTests extends ESTestCase { private ExpressionScriptEngine service; private SearchLookup lookup; @@ -48,7 +48,7 @@ public class ExpressionTests extends ESTestCase { public void setUp() throws Exception { super.setUp(); - NumberFieldType fieldType = new NumberFieldType(NumberType.DOUBLE); + NumberFieldMapper.NumberFieldType fieldType = new NumberFieldMapper.NumberFieldType(NumberFieldMapper.NumberType.DOUBLE); MapperService mapperService = mock(MapperService.class); when(mapperService.fullName("field")).thenReturn(fieldType); when(mapperService.fullName("alias")).thenReturn(fieldType); @@ -68,18 +68,11 @@ public class ExpressionTests extends ESTestCase { lookup = new SearchLookup(mapperService, ignored -> fieldData, null); } - private SearchScript.LeafFactory compile(String expression) { - SearchScript.Factory factory = service.compile(null, expression, SearchScript.CONTEXT, Collections.emptyMap()); + private FieldScript.LeafFactory compile(String expression) { + FieldScript.Factory factory = service.compile(null, expression, FieldScript.CONTEXT, Collections.emptyMap()); return factory.newFactory(Collections.emptyMap(), lookup); } - public void testNeedsScores() { - assertFalse(compile("1.2").needs_score()); - assertFalse(compile("doc['field'].value").needs_score()); - assertTrue(compile("1/_score").needs_score()); - assertTrue(compile("doc['field'].value * _score").needs_score()); - } - public void testCompileError() { ScriptException e = expectThrows(ScriptException.class, () -> { compile("doc['field'].value * *@#)(@$*@#$ + 4"); @@ -95,18 +88,18 @@ public class ExpressionTests extends ESTestCase { } public void testFieldAccess() throws IOException { - SearchScript script = compile("doc['field'].value").newInstance(null); + FieldScript script = compile("doc['field'].value").newInstance(null); script.setDocument(1); - double result = script.runAsDouble(); - assertEquals(2.718, result, 0.0); + Object result = script.execute(); + assertThat(result, equalTo(2.718)); } public void testFieldAccessWithFieldAlias() throws IOException { - SearchScript script = compile("doc['alias'].value").newInstance(null); + FieldScript script = compile("doc['alias'].value").newInstance(null); script.setDocument(1); - double result = script.runAsDouble(); - assertEquals(2.718, result, 0.0); + Object result = script.execute(); + assertThat(result, equalTo(2.718)); } } diff --git a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/StoredExpressionTests.java b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/StoredExpressionTests.java index 4bf8af9a12e..7f7f30f271a 100644 --- a/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/StoredExpressionTests.java +++ b/modules/lang-expression/src/test/java/org/elasticsearch/script/expression/StoredExpressionTests.java @@ -70,7 +70,7 @@ public class StoredExpressionTests extends ESIntegTestCase { .setIndices("test").setTypes("scriptTest").get(); fail("search script should have been rejected"); } catch(Exception e) { - assertThat(e.toString(), containsString("cannot execute scripts using [search] context")); + assertThat(e.toString(), containsString("cannot execute scripts using [field] context")); } try { client().prepareSearch("test") diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java index e69a1ad5dcf..5a4c5de015b 100644 --- a/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/ScriptTestCase.java @@ -27,7 +27,6 @@ import org.elasticsearch.painless.lookup.PainlessLookupBuilder; import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.script.ScriptContext; import org.elasticsearch.script.ScriptException; -import org.elasticsearch.script.SearchScript; import org.elasticsearch.test.ESTestCase; import org.junit.Before; @@ -67,7 +66,6 @@ public abstract class ScriptTestCase extends ESTestCase { */ protected Map, List> scriptContexts() { Map, List> contexts = new HashMap<>(); - contexts.put(SearchScript.CONTEXT, Whitelist.BASE_WHITELISTS); contexts.put(PainlessTestScript.CONTEXT, Whitelist.BASE_WHITELISTS); return contexts; } diff --git a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/16_update2.yml b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/16_update2.yml index 253676bda8e..999733ff14b 100644 --- a/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/16_update2.yml +++ b/modules/lang-painless/src/test/resources/rest-api-spec/test/painless/16_update2.yml @@ -38,12 +38,12 @@ catch: bad_request put_script: id: "1" - context: "search" + context: "score" body: { "script": {"lang": "painless", "source": "_score * foo bar + doc['myParent.weight'].value"} } - do: catch: /compile error/ put_script: id: "1" - context: "search" + context: "score" body: { "script": {"lang": "painless", "source": "_score * foo bar + doc['myParent.weight'].value"} } diff --git a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java index 9e3bc66e7d5..ca35db5a81b 100644 --- a/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java +++ b/plugins/examples/painless-whitelist/src/main/java/org/elasticsearch/example/painlesswhitelist/ExampleWhitelistExtension.java @@ -19,15 +19,15 @@ package org.elasticsearch.example.painlesswhitelist; -import java.util.Collections; -import java.util.List; -import java.util.Map; - import org.elasticsearch.painless.spi.PainlessExtension; import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.script.SearchScript; + +import java.util.Collections; +import java.util.List; +import java.util.Map; /** An extension of painless which adds a whitelist. */ public class ExampleWhitelistExtension implements PainlessExtension { @@ -37,6 +37,6 @@ public class ExampleWhitelistExtension implements PainlessExtension { @Override public Map, List> getContextWhitelists() { - return Collections.singletonMap(SearchScript.CONTEXT, Collections.singletonList(WHITELIST)); + return Collections.singletonMap(FieldScript.CONTEXT, Collections.singletonList(WHITELIST)); } } diff --git a/server/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java b/server/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java index 58d271bb820..1fe781f38ce 100644 --- a/server/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/InnerHitContextBuilder.java @@ -20,7 +20,7 @@ package org.elasticsearch.index.query; import org.elasticsearch.index.IndexSettings; -import org.elasticsearch.script.SearchScript; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.fetch.subphase.DocValueFieldsContext; import org.elasticsearch.search.fetch.subphase.InnerHitsContext; @@ -88,10 +88,10 @@ public abstract class InnerHitContextBuilder { if (innerHitBuilder.getScriptFields() != null) { for (SearchSourceBuilder.ScriptField field : innerHitBuilder.getScriptFields()) { QueryShardContext innerContext = innerHitsContext.getQueryShardContext(); - SearchScript.Factory factory = innerContext.getScriptService().compile(field.script(), SearchScript.CONTEXT); - SearchScript.LeafFactory searchScript = factory.newFactory(field.script().getParams(), innerHitsContext.lookup()); + FieldScript.Factory factory = innerContext.getScriptService().compile(field.script(), FieldScript.CONTEXT); + FieldScript.LeafFactory fieldScript = factory.newFactory(field.script().getParams(), innerHitsContext.lookup()); innerHitsContext.scriptFields().add(new org.elasticsearch.search.fetch.subphase.ScriptFieldsContext.ScriptField( - field.fieldName(), searchScript, field.ignoreFailure())); + field.fieldName(), fieldScript, field.ignoreFailure())); } } if (innerHitBuilder.getFetchSourceContext() != null) { diff --git a/server/src/main/java/org/elasticsearch/script/FieldScript.java b/server/src/main/java/org/elasticsearch/script/FieldScript.java new file mode 100644 index 00000000000..98649dbb330 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/script/FieldScript.java @@ -0,0 +1,108 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.script; + +import org.apache.lucene.index.LeafReaderContext; +import org.elasticsearch.index.fielddata.ScriptDocValues; +import org.elasticsearch.search.lookup.LeafSearchLookup; +import org.elasticsearch.search.lookup.SearchLookup; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A script to produce dynamic values for return fields. + */ +public abstract class FieldScript { + + public static final String[] PARAMETERS = {}; + + private static final Map DEPRECATIONS; + + static { + Map deprecations = new HashMap<>(); + deprecations.put( + "doc", + "Accessing variable [doc] via [params.doc] from within a field script " + + "is deprecated in favor of directly accessing [doc]." + ); + deprecations.put( + "_doc", + "Accessing variable [doc] via [params._doc] from within a field script " + + "is deprecated in favor of directly accessing [doc]." + ); + DEPRECATIONS = Collections.unmodifiableMap(deprecations); + } + + /** The generic runtime parameters for the script. */ + private final Map params; + + /** A leaf lookup for the bound segment this script will operate on. */ + private final LeafSearchLookup leafLookup; + + public FieldScript(Map params, SearchLookup lookup, LeafReaderContext leafContext) { + this.leafLookup = lookup.getLeafSearchLookup(leafContext); + params = new HashMap<>(params); + params.putAll(leafLookup.asMap()); + this.params = new ParameterMap(params, DEPRECATIONS); + } + + // for expression engine + protected FieldScript() { + params = null; + leafLookup = null; + } + + public abstract Object execute(); + + /** The leaf lookup for the Lucene segment this script was created for. */ + protected final LeafSearchLookup getLeafLookup() { + return leafLookup; + } + + /** Return the parameters for this script. */ + public Map getParams() { + return params; + } + + /** The doc lookup for the Lucene segment this script was created for. */ + public final Map> getDoc() { + return leafLookup.doc(); + } + + /** Set the current document to run the script on next. */ + public void setDocument(int docid) { + leafLookup.setDocument(docid); + } + + /** A factory to construct {@link SearchScript} instances. */ + public interface LeafFactory { + FieldScript newInstance(LeafReaderContext ctx) throws IOException; + } + + public interface Factory { + LeafFactory newFactory(Map params, SearchLookup lookup); + } + + /** The context used to compile {@link FieldScript} factories. */ + public static final ScriptContext CONTEXT = new ScriptContext<>("field", Factory.class); +} diff --git a/server/src/main/java/org/elasticsearch/script/ScriptModule.java b/server/src/main/java/org/elasticsearch/script/ScriptModule.java index 24dd491e3a1..1c53ef133de 100644 --- a/server/src/main/java/org/elasticsearch/script/ScriptModule.java +++ b/server/src/main/java/org/elasticsearch/script/ScriptModule.java @@ -40,7 +40,7 @@ public class ScriptModule { public static final Map> CORE_CONTEXTS; static { CORE_CONTEXTS = Stream.of( - SearchScript.CONTEXT, + FieldScript.CONTEXT, AggregationScript.CONTEXT, ScoreScript.CONTEXT, NumberSortScript.CONTEXT, diff --git a/server/src/main/java/org/elasticsearch/search/SearchService.java b/server/src/main/java/org/elasticsearch/search/SearchService.java index d8829bd11d3..6512e25fc0b 100644 --- a/server/src/main/java/org/elasticsearch/search/SearchService.java +++ b/server/src/main/java/org/elasticsearch/search/SearchService.java @@ -58,8 +58,8 @@ import org.elasticsearch.index.shard.SearchOperationListener; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.indices.cluster.IndicesClusterStateService.AllocatedIndices.IndexRemovalReason; import org.elasticsearch.node.ResponseCollectorService; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.ScriptService; -import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.aggregations.AggregationInitializationException; import org.elasticsearch.search.aggregations.AggregatorFactories; import org.elasticsearch.search.aggregations.InternalAggregation; @@ -863,8 +863,8 @@ public class SearchService extends AbstractLifecycleComponent implements IndexEv + IndexSettings.MAX_SCRIPT_FIELDS_SETTING.getKey() + "] index level setting."); } for (org.elasticsearch.search.builder.SearchSourceBuilder.ScriptField field : source.scriptFields()) { - SearchScript.Factory factory = scriptService.compile(field.script(), SearchScript.CONTEXT); - SearchScript.LeafFactory searchScript = factory.newFactory(field.script().getParams(), context.lookup()); + FieldScript.Factory factory = scriptService.compile(field.script(), FieldScript.CONTEXT); + FieldScript.LeafFactory searchScript = factory.newFactory(field.script().getParams(), context.lookup()); context.scriptFields().add(new ScriptField(field.fieldName(), searchScript, field.ignoreFailure())); } } diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/TopHitsAggregationBuilder.java b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/TopHitsAggregationBuilder.java index c2add245058..debbacdc619 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/metrics/TopHitsAggregationBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/metrics/TopHitsAggregationBuilder.java @@ -28,8 +28,8 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.index.IndexSettings; import org.elasticsearch.index.query.QueryShardContext; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.Script; -import org.elasticsearch.script.SearchScript; import org.elasticsearch.search.aggregations.AbstractAggregationBuilder; import org.elasticsearch.search.aggregations.AggregationBuilder; import org.elasticsearch.search.aggregations.AggregationInitializationException; @@ -566,8 +566,8 @@ public class TopHitsAggregationBuilder extends AbstractAggregationBuilder scriptFields = context.scriptFields().fields(); final IndexReader reader = context.searcher().getIndexReader(); for (SearchHit hit : hits) { @@ -64,7 +64,7 @@ public final class ScriptFieldsFetchSubPhase implements FetchSubPhase { leafScripts[i].setDocument(docId); final Object value; try { - value = leafScripts[i].run(); + value = leafScripts[i].execute(); CollectionUtils.ensureNoSelfReferences(value, "ScriptFieldsFetchSubPhase leaf script " + i); } catch (RuntimeException e) { if (scriptFields.get(i).ignoreException()) { @@ -91,9 +91,9 @@ public final class ScriptFieldsFetchSubPhase implements FetchSubPhase { } } - private SearchScript[] createLeafScripts(LeafReaderContext context, - List scriptFields) { - SearchScript[] scripts = new SearchScript[scriptFields.size()]; + private FieldScript[] createLeafScripts(LeafReaderContext context, + List scriptFields) { + FieldScript[] scripts = new FieldScript[scriptFields.size()]; for (int i = 0; i < scripts.length; i++) { try { scripts[i] = scriptFields.get(i).script().newInstance(context); diff --git a/server/src/test/java/org/elasticsearch/script/ScriptServiceTests.java b/server/src/test/java/org/elasticsearch/script/ScriptServiceTests.java index 5d1a2013a92..22a250710ba 100644 --- a/server/src/test/java/org/elasticsearch/script/ScriptServiceTests.java +++ b/server/src/test/java/org/elasticsearch/script/ScriptServiceTests.java @@ -150,22 +150,22 @@ public class ScriptServiceTests extends ESTestCase { public void testInlineScriptCompiledOnceCache() throws IOException { buildScriptService(Settings.EMPTY); Script script = new Script(ScriptType.INLINE, "test", "1+1", Collections.emptyMap()); - SearchScript.Factory factoryScript1 = scriptService.compile(script, SearchScript.CONTEXT); - SearchScript.Factory factoryScript2 = scriptService.compile(script, SearchScript.CONTEXT); + FieldScript.Factory factoryScript1 = scriptService.compile(script, FieldScript.CONTEXT); + FieldScript.Factory factoryScript2 = scriptService.compile(script, FieldScript.CONTEXT); assertThat(factoryScript1, sameInstance(factoryScript2)); } public void testAllowAllScriptTypeSettings() throws IOException { buildScriptService(Settings.EMPTY); - assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); - assertCompileAccepted(null, "script", ScriptType.STORED, SearchScript.CONTEXT); + assertCompileAccepted("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); + assertCompileAccepted(null, "script", ScriptType.STORED, FieldScript.CONTEXT); } public void testAllowAllScriptContextSettings() throws IOException { buildScriptService(Settings.EMPTY); - assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); + assertCompileAccepted("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); assertCompileAccepted("painless", "script", ScriptType.INLINE, AggregationScript.CONTEXT); assertCompileAccepted("painless", "script", ScriptType.INLINE, UpdateScript.CONTEXT); assertCompileAccepted("painless", "script", ScriptType.INLINE, IngestScript.CONTEXT); @@ -176,16 +176,16 @@ public class ScriptServiceTests extends ESTestCase { builder.put("script.allowed_types", "inline"); buildScriptService(builder.build()); - assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); - assertCompileRejected(null, "script", ScriptType.STORED, SearchScript.CONTEXT); + assertCompileAccepted("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); + assertCompileRejected(null, "script", ScriptType.STORED, FieldScript.CONTEXT); } public void testAllowSomeScriptContextSettings() throws IOException { Settings.Builder builder = Settings.builder(); - builder.put("script.allowed_contexts", "search, aggs"); + builder.put("script.allowed_contexts", "field, aggs"); buildScriptService(builder.build()); - assertCompileAccepted("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); + assertCompileAccepted("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); assertCompileAccepted("painless", "script", ScriptType.INLINE, AggregationScript.CONTEXT); assertCompileRejected("painless", "script", ScriptType.INLINE, UpdateScript.CONTEXT); } @@ -195,8 +195,8 @@ public class ScriptServiceTests extends ESTestCase { builder.put("script.allowed_types", "none"); buildScriptService(builder.build()); - assertCompileRejected("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); - assertCompileRejected(null, "script", ScriptType.STORED, SearchScript.CONTEXT); + assertCompileRejected("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); + assertCompileRejected(null, "script", ScriptType.STORED, FieldScript.CONTEXT); } public void testAllowNoScriptContextSettings() throws IOException { @@ -204,7 +204,7 @@ public class ScriptServiceTests extends ESTestCase { builder.put("script.allowed_contexts", "none"); buildScriptService(builder.build()); - assertCompileRejected("painless", "script", ScriptType.INLINE, SearchScript.CONTEXT); + assertCompileRejected("painless", "script", ScriptType.INLINE, FieldScript.CONTEXT); assertCompileRejected("painless", "script", ScriptType.INLINE, AggregationScript.CONTEXT); } diff --git a/server/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java b/server/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java index 4bd12c97734..1a86b3b1da2 100644 --- a/server/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java +++ b/server/src/test/java/org/elasticsearch/search/fields/SearchFieldsIT.java @@ -106,7 +106,8 @@ public class SearchFieldsIT extends ESIntegTestCase { scripts.put("doc['num1'].value * factor", vars -> { Map doc = (Map) vars.get("doc"); ScriptDocValues.Doubles num1 = (ScriptDocValues.Doubles) doc.get("num1"); - Double factor = (Double) vars.get("factor"); + Map params = (Map) vars.get("params"); + Double factor = (Double) params.get("factor"); return num1.getValue() * factor; }); diff --git a/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java b/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java index 64a9d97e3f4..3c4c0da6322 100644 --- a/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java +++ b/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java @@ -93,6 +93,17 @@ public class MockScriptEngine implements ScriptEngine { if (context.instanceClazz.equals(SearchScript.class)) { SearchScript.Factory factory = mockCompiled::createSearchScript; return context.factoryClazz.cast(factory); + } else if (context.instanceClazz.equals(FieldScript.class)) { + FieldScript.Factory factory = (parameters, lookup) -> + ctx -> new FieldScript(parameters, lookup, ctx) { + @Override + public Object execute() { + Map vars = createVars(parameters); + vars.putAll(getLeafLookup().asMap()); + return script.apply(vars); + } + }; + return context.factoryClazz.cast(factory); } else if(context.instanceClazz.equals(TermsSetQueryScript.class)) { TermsSetQueryScript.Factory factory = (parameters, lookup) -> (TermsSetQueryScript.LeafFactory) ctx -> new TermsSetQueryScript(parameters, lookup, ctx) { @@ -276,6 +287,12 @@ public class MockScriptEngine implements ScriptEngine { throw new IllegalArgumentException("mock script engine does not know how to handle context [" + context.name + "]"); } + private Map createVars(Map params) { + Map vars = new HashMap<>(); + vars.put("params", params); + return vars; + } + public class MockCompiledScript { private final String name; diff --git a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java index f0ca0cb8730..feacbabe5e2 100644 --- a/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java +++ b/x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/plugin/SqlPainlessExtension.java @@ -10,15 +10,15 @@ import org.elasticsearch.painless.spi.Whitelist; import org.elasticsearch.painless.spi.WhitelistLoader; import org.elasticsearch.script.AggregationScript; import org.elasticsearch.script.BucketAggregationSelectorScript; +import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.FilterScript; +import org.elasticsearch.script.NumberSortScript; import org.elasticsearch.script.ScriptContext; -import org.elasticsearch.script.SearchScript; +import org.elasticsearch.script.StringSortScript; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.elasticsearch.script.NumberSortScript; -import org.elasticsearch.script.StringSortScript; import static java.util.Collections.singletonList; @@ -32,7 +32,7 @@ public class SqlPainlessExtension implements PainlessExtension { List list = singletonList(WHITELIST); whitelist.put(FilterScript.CONTEXT, list); whitelist.put(AggregationScript.CONTEXT, list); - whitelist.put(SearchScript.CONTEXT, list); + whitelist.put(FieldScript.CONTEXT, list); whitelist.put(NumberSortScript.CONTEXT, list); whitelist.put(StringSortScript.CONTEXT, list); whitelist.put(BucketAggregationSelectorScript.CONTEXT, list);