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 b524853a7cff5..b708685487f9a 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 @@ -25,7 +25,6 @@ import org.elasticsearch.script.BucketAggregationScript; import org.elasticsearch.script.BucketAggregationSelectorScript; import org.elasticsearch.script.ClassPermission; -import org.elasticsearch.script.DocValuesDocReader; import org.elasticsearch.script.FieldScript; import org.elasticsearch.script.FilterScript; import org.elasticsearch.script.NumberSortScript; @@ -332,9 +331,9 @@ private static FieldScript.LeafFactory newFieldScript(Expression expr, SearchLoo */ private static FilterScript.LeafFactory newFilterScript(Expression expr, SearchLookup lookup, @Nullable Map vars) { ScoreScript.LeafFactory searchLeafFactory = newScoreScript(expr, lookup, vars); - return ctx -> { - ScoreScript script = searchLeafFactory.newInstance(new DocValuesDocReader(lookup, ctx)); - return new FilterScript(vars, lookup, ctx) { + return docReader -> { + ScoreScript script = searchLeafFactory.newInstance(docReader); + return new FilterScript(vars, lookup, docReader) { @Override public boolean execute() { return script.execute(null) != 0.0; diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java index 01394f61ea3c8..a0000c1fbe43d 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessPlugin.java @@ -37,6 +37,7 @@ import org.elasticsearch.repositories.RepositoriesService; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestHandler; +import org.elasticsearch.script.FilterScript; import org.elasticsearch.script.IngestScript; import org.elasticsearch.script.NumberSortScript; import org.elasticsearch.script.ScoreScript; @@ -109,6 +110,11 @@ public final class PainlessPlugin extends Plugin implements ScriptPlugin, Extens strSort.add(strSortField); map.put(StringSortScript.CONTEXT, strSort); + List filter = new ArrayList<>(Whitelist.BASE_WHITELISTS); + Whitelist filterWhitelist = WhitelistLoader.loadFromResourceFiles(Whitelist.class, "org.elasticsearch.script.fields.filter.txt"); + filter.add(filterWhitelist); + map.put(FilterScript.CONTEXT, filter); + // Execute context gets everything List test = new ArrayList<>(Whitelist.BASE_WHITELISTS); test.add(movFnWhitelist); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java index 9b47e71979c31..7b72b8cab1424 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/action/PainlessExecuteAction.java @@ -520,9 +520,9 @@ static Response innerShardOperation(Request request, ScriptService scriptService } else if (scriptContext == FilterScript.CONTEXT) { return prepareRamIndex(request, (context, leafReaderContext) -> { FilterScript.Factory factory = scriptService.compile(request.script, FilterScript.CONTEXT); - FilterScript.LeafFactory leafFactory = - factory.newFactory(request.getScript().getParams(), context.lookup()); - FilterScript filterScript = leafFactory.newInstance(leafReaderContext); + SearchLookup lookup = context.lookup(); + FilterScript.LeafFactory leafFactory = factory.newFactory(request.getScript().getParams(), lookup); + FilterScript filterScript = leafFactory.newInstance(new DocValuesDocReader(lookup, leafReaderContext)); filterScript.setDocument(0); boolean result = filterScript.execute(); return new Response(result); diff --git a/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.script.fields.filter.txt b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.script.fields.filter.txt new file mode 100644 index 0000000000000..15ffc4e68f2ef --- /dev/null +++ b/modules/lang-painless/src/main/resources/org/elasticsearch/painless/spi/org.elasticsearch.script.fields.filter.txt @@ -0,0 +1,15 @@ +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License +# 2.0 and the Server Side Public License, v 1; you may not use this file except +# in compliance with, at your election, the Elastic License 2.0 or the Server +# Side Public License, v 1. +# + +# The whitelist for the fields api + +# The scripts must be whitelisted for painless to find the classes for the field API +class org.elasticsearch.script.FilterScript @no_import { +} +class org.elasticsearch.script.FilterScript$Factory @no_import { +} diff --git a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_fields_api.yml b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_fields_api.yml new file mode 100644 index 0000000000000..61749d8333cd4 --- /dev/null +++ b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_fields_api.yml @@ -0,0 +1,160 @@ +# Integration tests for sort script queries using Painless + +setup: +- skip: + version: " - 7.14.99" + reason: "sort script fields api was added in 7.15.0" + +--- +"sort script fields api": + - do: + indices.create: + index: test + body: + settings: + number_of_shards: 2 + mappings: + properties: + dval: + type: double + - do: + index: + index: test + id: d1 + body: {"dval": 10, "sval": "f"} + - do: + index: + index: test + id: d2 + body: {} + - do: + index: + index: test + id: d3 + body: {"dval": 5, "sval": "a"} + - do: + indices.refresh: {} + - do: + search: + rest_total_hits_as_int: true + index: test + body: + sort: + _script: + type: number + script: + source: "field('dval').getValue(3)" + - match: { hits.total: 3 } + - match: { hits.hits.0._id: d2 } + - match: { hits.hits.1._id: d3 } + - match: { hits.hits.2._id: d1 } + - do: + search: + rest_total_hits_as_int: true + index: test + body: + sort: + _script: + type: string + script: + source: "field('sval.keyword').getValue('g')" + - match: { hits.total: 3 } + - match: { hits.hits.0._id: d3 } + - match: { hits.hits.1._id: d1 } + - match: { hits.hits.2._id: d2 } + +--- +"script score fields api": + - do: + indices.create: + index: test + body: + settings: + number_of_shards: 2 + mappings: + properties: + dval: + type: double + - do: + index: + index: test + id: d1 + body: {"dval": 10} + - do: + index: + index: test + id: d2 + body: {} + - do: + index: + index: test + id: d3 + body: {"dval": 5} + - do: + indices.refresh: {} + - do: + search: + rest_total_hits_as_int: true + index: test + body: + query: + script_score: + query: {match_all: {} } + script: + source: "field('dval').getValue(3)" + - match: { hits.total: 3 } + - match: { hits.hits.0._id: d1 } + - match: { hits.hits.1._id: d3 } + - match: { hits.hits.2._id: d2 } + +--- +"filter script fields api": + - do: + indices.create: + index: test + body: + settings: + number_of_shards: 2 + mappings: + properties: + dval: + type: double + - do: + index: + index: test + id: d1 + body: {"dval": 10, "sval": "f"} + - do: + index: + index: test + id: d2 + body: {} + - do: + index: + index: test + id: d3 + body: {"dval": 5, "sval": "a"} + - do: + indices.refresh: {} + - do: + search: + rest_total_hits_as_int: true + body: + query: + bool: + filter: + script: + script: "field('dval').getValue(6) > 6" + - match: { hits.total: 1 } + - match: { hits.hits.0._id: d1 } + - do: + search: + rest_total_hits_as_int: true + body: + query: + bool: + filter: + script: + script: "field('sval.keyword').getValue('b') == 'a'" + - match: { hits.total: 1 } + - match: { hits.hits.0._id: d3 } diff --git a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_sort_script_fields_api.yml b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_sort_script_fields_api.yml deleted file mode 100644 index b2a55fcc77e2d..0000000000000 --- a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/40_sort_script_fields_api.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Integration tests for sort script queries using Painless - -setup: -- skip: - version: " - 7.14.99" - reason: "sort script fields api was added in 7.15.0" - ---- -"sort script fields api": - - do: - indices.create: - index: test - body: - settings: - number_of_shards: 2 - mappings: - properties: - dval: - type: double - - do: - index: - index: test - id: d1 - body: {"dval": 10, "sval": "f"} - - do: - index: - index: test - id: d2 - body: {} - - do: - index: - index: test - id: d3 - body: {"dval": 5, "sval": "a"} - - do: - indices.refresh: {} - - do: - search: - rest_total_hits_as_int: true - index: test - body: - sort: - _script: - type: number - script: - source: "field('dval').getValue(3)" - - match: { hits.total: 3 } - - match: { hits.hits.0._id: d2 } - - match: { hits.hits.1._id: d3 } - - match: { hits.hits.2._id: d1 } - - do: - search: - rest_total_hits_as_int: true - index: test - body: - sort: - _script: - type: string - script: - source: "field('sval.keyword').getValue('g')" - - match: { hits.total: 3 } - - match: { hits.hits.0._id: d3 } - - match: { hits.hits.1._id: d1 } - - match: { hits.hits.2._id: d2 } - diff --git a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/88_script_score_fields_api.yml b/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/88_script_score_fields_api.yml deleted file mode 100644 index 0bfc8e610f587..0000000000000 --- a/modules/lang-painless/src/yamlRestTest/resources/rest-api-spec/test/painless/88_script_score_fields_api.yml +++ /dev/null @@ -1,51 +0,0 @@ -# Integration tests for ScriptScoreQuery using Painless - -setup: -- skip: - version: " - 7.14.99" - reason: "script score fields api was added in 7.15.0" - ---- -"script score fields api": - - do: - indices.create: - index: test - body: - settings: - number_of_shards: 2 - mappings: - properties: - dval: - type: double - - do: - index: - index: test - id: d1 - body: {"dval": 10} - - do: - index: - index: test - id: d2 - body: {} - - do: - index: - index: test - id: d3 - body: {"dval": 5} - - do: - indices.refresh: {} - - do: - search: - rest_total_hits_as_int: true - index: test - body: - query: - script_score: - query: {match_all: {} } - script: - source: "field('dval').getValue(3)" - - match: { hits.total: 3 } - - match: { hits.hits.0._id: d1 } - - match: { hits.hits.1._id: d3 } - - match: { hits.hits.2._id: d2 } - diff --git a/server/src/main/java/org/elasticsearch/index/query/ScriptQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/ScriptQueryBuilder.java index 541742ae81e97..e5b949ab75f53 100644 --- a/server/src/main/java/org/elasticsearch/index/query/ScriptQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/ScriptQueryBuilder.java @@ -24,8 +24,10 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.script.DocValuesDocReader; import org.elasticsearch.script.FilterScript; import org.elasticsearch.script.Script; +import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; import java.util.Objects; @@ -127,18 +129,21 @@ protected Query doToQuery(SearchExecutionContext context) throws IOException { ALLOW_EXPENSIVE_QUERIES.getKey() + "' is set to false."); } FilterScript.Factory factory = context.compile(script, FilterScript.CONTEXT); - FilterScript.LeafFactory filterScript = factory.newFactory(script.getParams(), context.lookup()); - return new ScriptQuery(script, filterScript); + SearchLookup lookup = context.lookup(); + FilterScript.LeafFactory filterScript = factory.newFactory(script.getParams(), lookup); + return new ScriptQuery(script, filterScript, lookup); } static class ScriptQuery extends Query { final Script script; final FilterScript.LeafFactory filterScript; + final SearchLookup lookup; - ScriptQuery(Script script, FilterScript.LeafFactory filterScript) { + ScriptQuery(Script script, FilterScript.LeafFactory filterScript, SearchLookup lookup) { this.script = script; this.filterScript = filterScript; + this.lookup = lookup; } @Override @@ -172,7 +177,7 @@ public Weight createWeight(IndexSearcher searcher, ScoreMode scoreMode, float bo @Override public Scorer scorer(LeafReaderContext context) throws IOException { DocIdSetIterator approximation = DocIdSetIterator.all(context.reader().maxDoc()); - final FilterScript leafScript = filterScript.newInstance(context); + final FilterScript leafScript = filterScript.newInstance(new DocValuesDocReader(lookup, context)); TwoPhaseIterator twoPhase = new TwoPhaseIterator(approximation) { @Override diff --git a/server/src/main/java/org/elasticsearch/script/FilterScript.java b/server/src/main/java/org/elasticsearch/script/FilterScript.java index a76c93dc61f2f..6cbf987243a47 100644 --- a/server/src/main/java/org/elasticsearch/script/FilterScript.java +++ b/server/src/main/java/org/elasticsearch/script/FilterScript.java @@ -10,16 +10,13 @@ import java.io.IOException; import java.util.Map; -import org.apache.lucene.index.LeafReaderContext; -import org.elasticsearch.index.fielddata.ScriptDocValues; -import org.elasticsearch.search.lookup.LeafSearchLookup; import org.elasticsearch.search.lookup.SearchLookup; /** * A script implementation of a query filter. * See {@link org.elasticsearch.index.query.ScriptQueryBuilder}. */ -public abstract class FilterScript { +public abstract class FilterScript extends DocBasedScript { // no parameters for execute, but constant still required... public static final String[] PARAMETERS = {}; @@ -27,12 +24,11 @@ public abstract class FilterScript { /** 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 FilterScript(Map params, SearchLookup lookup, LeafReaderContext leafContext) { + public FilterScript(Map params, SearchLookup lookup, DocReader docReader) { + // searchLookup is taken in for compatibility with expressions. See ExpressionScriptEngine.newFilterScript and + // ExpressionScriptEngine.getDocValueSource for where it's used. + super(docReader); this.params = params; - this.leafLookup = lookup.getLeafSearchLookup(leafContext); } /** Return {@code true} if the current document matches the filter, or {@code false} otherwise. */ @@ -43,23 +39,20 @@ 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); + docReader.setDocument(docid); } /** A factory to construct {@link FilterScript} instances. */ public interface LeafFactory { - FilterScript newInstance(LeafReaderContext ctx) throws IOException; + FilterScript newInstance(DocReader docReader) throws IOException; } /** A factory to construct stateful {@link FilterScript} factories for a specific index. */ public interface Factory extends ScriptFactory { + // searchLookup is taken in for compatibility with expressions. See ExpressionScriptEngine.newFilterScript and + // ExpressionScriptEngine.getDocValueSource for where it's used. LeafFactory newFactory(Map params, SearchLookup lookup); } 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 601e65485c5df..b48889603e87d 100644 --- a/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java +++ b/test/framework/src/main/java/org/elasticsearch/script/MockScriptEngine.java @@ -16,7 +16,6 @@ import org.elasticsearch.index.similarity.ScriptedSimilarity.Query; import org.elasticsearch.index.similarity.ScriptedSimilarity.Term; import org.elasticsearch.search.aggregations.pipeline.MovingFunctionScript; -import org.elasticsearch.search.lookup.LeafSearchLookup; import org.elasticsearch.search.lookup.SearchLookup; import java.io.IOException; @@ -381,13 +380,12 @@ public MockFilterScript(SearchLookup lookup, Map vars, Function< this.script = script; } - public FilterScript newInstance(LeafReaderContext context) throws IOException { - LeafSearchLookup leafLookup = lookup.getLeafSearchLookup(context); - Map ctx = new HashMap<>(leafLookup.asMap()); + public FilterScript newInstance(DocReader docReader) throws IOException { + Map ctx = new HashMap<>(docReader.docAsMap()); if (vars != null) { ctx.putAll(vars); } - return new FilterScript(ctx, lookup, context) { + return new FilterScript(ctx, lookup, docReader) { @Override public boolean execute() { return (boolean) script.apply(ctx); @@ -395,7 +393,7 @@ public boolean execute() { @Override public void setDocument(int doc) { - leafLookup.setDocument(doc); + docReader.setDocument(doc); } }; }