-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
Migrate scripted metric aggregation scripts to ScriptContext design #30111
Changes from 1 commit
2710d5a
0f9ba91
1f7f477
2beee07
22079c2
0b98c8a
3d3c914
c16e700
d28c019
802db8b
096b136
793e47b
8b35cf1
46526a1
c8a9256
1be1660
13986d0
c28eece
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
/* | ||
* 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.painless; | ||
|
||
import org.elasticsearch.painless.spi.Whitelist; | ||
import org.elasticsearch.script.MetricAggScripts; | ||
import org.elasticsearch.script.ScriptContext; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class MetricAggScriptsTests extends ScriptTestCase { | ||
@Override | ||
protected Map<ScriptContext<?>, List<Whitelist>> scriptContexts() { | ||
Map<ScriptContext<?>, List<Whitelist>> contexts = new HashMap<>(); | ||
contexts.put(MetricAggScripts.InitScript.CONTEXT, Whitelist.BASE_WHITELISTS); | ||
contexts.put(MetricAggScripts.MapScript.CONTEXT, Whitelist.BASE_WHITELISTS); | ||
contexts.put(MetricAggScripts.CombineScript.CONTEXT, Whitelist.BASE_WHITELISTS); | ||
contexts.put(MetricAggScripts.ReduceScript.CONTEXT, Whitelist.BASE_WHITELISTS); | ||
return contexts; | ||
} | ||
|
||
public void testInitBasic() { | ||
MetricAggScripts.InitScript.Factory factory = scriptEngine.compile("test", | ||
"agg.testField = params.initialVal", MetricAggScripts.InitScript.CONTEXT, Collections.emptyMap()); | ||
|
||
Map<String, Object> params = new HashMap<>(); | ||
Map<String, Object> agg = new HashMap<>(); | ||
|
||
params.put("initialVal", 10); | ||
|
||
MetricAggScripts.InitScript script = factory.newInstance(params, agg); | ||
script.execute(); | ||
|
||
assert(agg.containsKey("testField")); | ||
assertEquals(10, agg.get("testField")); | ||
} | ||
|
||
public void testMapBasic() { | ||
MetricAggScripts.MapScript.Factory factory = scriptEngine.compile("test", | ||
"agg.testField = 2*_score", MetricAggScripts.MapScript.CONTEXT, Collections.emptyMap()); | ||
|
||
Map<String, Object> params = new HashMap<>(); | ||
Map<String, Object> agg = new HashMap<>(); | ||
double _score = 0.5; | ||
|
||
MetricAggScripts.MapScript.LeafFactory leafFactory = factory.newFactory(params, agg, null); | ||
MetricAggScripts.MapScript script = leafFactory.newInstance(null); | ||
|
||
script.execute(_score); | ||
|
||
assert(agg.containsKey("testField")); | ||
assertEquals(1.0, agg.get("testField")); | ||
} | ||
|
||
public void testCombineBasic() { | ||
MetricAggScripts.CombineScript.Factory factory = scriptEngine.compile("test", | ||
"agg.testField = params.initialVal; return agg.testField + params.inc", MetricAggScripts.CombineScript.CONTEXT, | ||
Collections.emptyMap()); | ||
|
||
Map<String, Object> params = new HashMap<>(); | ||
Map<String, Object> agg = new HashMap<>(); | ||
|
||
params.put("initialVal", 10); | ||
params.put("inc", 2); | ||
|
||
MetricAggScripts.CombineScript script = factory.newInstance(params, agg); | ||
Object res = script.execute(); | ||
|
||
assert(agg.containsKey("testField")); | ||
assertEquals(10, agg.get("testField")); | ||
assertEquals(12, res); | ||
} | ||
|
||
public void testReduceBasic() { | ||
MetricAggScripts.ReduceScript.Factory factory = scriptEngine.compile("test", | ||
"aggs[0].testField + aggs[1].testField", MetricAggScripts.ReduceScript.CONTEXT, Collections.emptyMap()); | ||
|
||
Map<String, Object> params = new HashMap<>(); | ||
List<Object> aggs = new ArrayList<>(); | ||
|
||
Map<String, Object> agg1 = new HashMap<>(), agg2 = new HashMap<>(); | ||
agg1.put("testField", 1); | ||
agg2.put("testField", 2); | ||
|
||
aggs.add(agg1); | ||
aggs.add(agg2); | ||
|
||
MetricAggScripts.ReduceScript script = factory.newInstance(params, aggs); | ||
Object res = script.execute(); | ||
assertEquals(3, res); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* 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.util.List; | ||
import java.util.Map; | ||
|
||
public class MetricAggScripts { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we call this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely -- that is much clearer. I will make that change ASAP. |
||
private abstract static class ParamsAndAggBase { | ||
private final Map<String, Object> params; | ||
private final Object agg; | ||
|
||
ParamsAndAggBase(Map<String, Object> params, Object agg) { | ||
this.params = params; | ||
this.agg = agg; | ||
} | ||
|
||
public Map<String, Object> getParams() { | ||
return params; | ||
} | ||
|
||
public Object getAgg() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any reason that agg should be provided via execute() parameter instead of via an accessor like this? |
||
return agg; | ||
} | ||
} | ||
|
||
public abstract static class InitScript extends ParamsAndAggBase { | ||
public InitScript(Map<String, Object> params, Object agg) { | ||
super(params, agg); | ||
} | ||
|
||
public abstract void execute(); | ||
|
||
public interface Factory { | ||
InitScript newInstance(Map<String, Object> params, Object agg); | ||
} | ||
|
||
public static String[] PARAMETERS = {}; | ||
public static ScriptContext<Factory> CONTEXT = new ScriptContext<>("aggs_init", Factory.class); | ||
} | ||
|
||
public abstract static class MapScript extends ParamsAndAggBase { | ||
private final LeafSearchLookup leafLookup; | ||
|
||
public MapScript(Map<String, Object> params, Object agg, SearchLookup lookup, LeafReaderContext leafContext) { | ||
super(params, agg); | ||
|
||
this.leafLookup = leafContext == null ? null : lookup.getLeafSearchLookup(leafContext); | ||
} | ||
|
||
// Return the doc as a map (instead of LeafDocLookup) in order to abide by type whitelisting rules for | ||
// Painless scripts. | ||
public Map<String, ScriptDocValues<?>> getDoc() { | ||
return leafLookup == null ? null : leafLookup.doc(); | ||
} | ||
|
||
public void setDocument(int docId) { | ||
if (leafLookup != null) { | ||
leafLookup.setDocument(docId); | ||
} | ||
} | ||
|
||
public abstract void execute(double _score); | ||
|
||
public interface LeafFactory { | ||
MapScript newInstance(LeafReaderContext ctx); | ||
} | ||
|
||
public interface Factory { | ||
LeafFactory newFactory(Map<String, Object> params, Object agg, SearchLookup lookup); | ||
} | ||
|
||
public static String[] PARAMETERS = new String[] {"_score"}; | ||
public static ScriptContext<Factory> CONTEXT = new ScriptContext<>("aggs_map", Factory.class); | ||
} | ||
|
||
public abstract static class CombineScript extends ParamsAndAggBase { | ||
public CombineScript(Map<String, Object> params, Object agg) { | ||
super(params, agg); | ||
} | ||
|
||
public abstract Object execute(); | ||
|
||
public interface Factory { | ||
CombineScript newInstance(Map<String, Object> params, Object agg); | ||
} | ||
|
||
public static String[] PARAMETERS = {}; | ||
public static ScriptContext<Factory> CONTEXT = new ScriptContext<>("aggs_combine", Factory.class); | ||
} | ||
|
||
public abstract static class ReduceScript { | ||
private final Map<String, Object> params; | ||
private final List<Object> aggs; | ||
|
||
public ReduceScript(Map<String, Object> params, List<Object> aggs) { | ||
this.params = params; | ||
this.aggs = aggs; | ||
} | ||
|
||
public Map<String, Object> getParams() { | ||
return params; | ||
} | ||
|
||
public List<Object> getAggs() { | ||
return aggs; | ||
} | ||
|
||
public abstract Object execute(); | ||
|
||
public interface Factory { | ||
ReduceScript newInstance(Map<String, Object> params, List<Object> aggs); | ||
} | ||
|
||
public static String[] PARAMETERS = {}; | ||
public static ScriptContext<Factory> CONTEXT = new ScriptContext<>("aggs_reduce", Factory.class); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,7 +22,7 @@ | |
import org.elasticsearch.common.io.stream.StreamInput; | ||
import org.elasticsearch.common.io.stream.StreamOutput; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
import org.elasticsearch.script.ExecutableScript; | ||
import org.elasticsearch.script.MetricAggScripts; | ||
import org.elasticsearch.script.Script; | ||
import org.elasticsearch.search.aggregations.InternalAggregation; | ||
import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; | ||
|
@@ -89,15 +89,16 @@ public InternalAggregation doReduce(List<InternalAggregation> aggregations, Redu | |
InternalScriptedMetric firstAggregation = ((InternalScriptedMetric) aggregations.get(0)); | ||
List<Object> aggregation; | ||
if (firstAggregation.reduceScript != null && reduceContext.isFinalReduce()) { | ||
Map<String, Object> vars = new HashMap<>(); | ||
vars.put("_aggs", aggregationObjects); | ||
Map<String, Object> params = new HashMap<>(); | ||
if (firstAggregation.reduceScript.getParams() != null) { | ||
vars.putAll(firstAggregation.reduceScript.getParams()); | ||
params.putAll(firstAggregation.reduceScript.getParams()); | ||
} | ||
ExecutableScript.Factory factory = reduceContext.scriptService().compile( | ||
firstAggregation.reduceScript, ExecutableScript.AGGS_CONTEXT); | ||
ExecutableScript script = factory.newInstance(vars); | ||
aggregation = Collections.singletonList(script.run()); | ||
params.put("_aggs", aggregationObjects); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this needed? I thought that the reason to use script contexts was to that we then wouldn't need to put the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This (along with analogous code for If a clean break is acceptable then this would be removed and some of the tests could be cleaned up. But I would assume we want some compatibility leniency and possibly some warning messages or other notification (which I have not implemented yet -- item 2 on my list in the first comment on this PR). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, understood. I'm +1 on keeping this for backwards compatibility, thanks for explaining. Maybe we could add a comment just noting that its there for backwards compatibility to avoid someone removing it thinking its just left over? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. I will add a comment here and in other relevant spots. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Glad you brought this up -- when looking for spots that needed a comment I found a leftover usage of |
||
|
||
MetricAggScripts.ReduceScript.Factory factory = reduceContext.scriptService().compile( | ||
firstAggregation.reduceScript, MetricAggScripts.ReduceScript.CONTEXT); | ||
MetricAggScripts.ReduceScript script = factory.newInstance(params, aggregationObjects); | ||
aggregation = Collections.singletonList(script.execute()); | ||
} else if (reduceContext.isFinalReduce()) { | ||
aggregation = Collections.singletonList(aggregationObjects); | ||
} else { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added these tests just for convenient sanity checking that the new contexts were working. But perhaps they're redundant with more general Painless tests of script parameter access functionality.