Skip to content

Commit a41bc47

Browse files
committed
Disallow duplicate parameter names between scripted agg and script (elastic#28819)
If a scripted metric aggregation has aggregation params and script params which have the same name, throw an IllegalArgumentException when merging the parameter lists.
1 parent ccf2417 commit a41bc47

File tree

3 files changed

+51
-10
lines changed

3 files changed

+51
-10
lines changed

server/src/main/java/org/elasticsearch/search/aggregations/metrics/scripted/ScriptedMetricAggregatorFactory.java

+10-8
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,17 @@ private static <T> T deepCopyParams(T original, SearchContext context) {
136136
}
137137

138138
private static Map<String, Object> mergeParams(Map<String, Object> agg, Map<String, Object> script) {
139-
// TODO Should we throw an exception when param names conflict between aggregation and script? Need to add test coverage
140-
// for error or override behavior depending on the decision. Should this check be added at call time or at
141-
// construction?
139+
// Start with script params
140+
Map<String, Object> combined = new HashMap<>(script);
141+
142+
// Add in agg params, throwing an exception if any conflicts are detected
143+
for (Map.Entry<String, Object> aggEntry : agg.entrySet()) {
144+
if (combined.putIfAbsent(aggEntry.getKey(), aggEntry.getValue()) != null) {
145+
throw new IllegalArgumentException("Parameter name \"" + aggEntry.getKey() +
146+
"\" used in both aggregation and script parameters");
147+
}
148+
}
142149

143-
// Aggregation level commands need to win in case of conflict so that params can keep the same identity and
144-
// content across all the scripts that are run in the aggregation.
145-
Map<String, Object> combined = new HashMap<>();
146-
combined.putAll(script);
147-
combined.putAll(agg);
148150
return combined;
149151
}
150152
}

server/src/test/java/org/elasticsearch/search/aggregations/metrics/ScriptedMetricIT.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
package org.elasticsearch.search.aggregations.metrics;
2121

2222
import org.elasticsearch.action.index.IndexRequestBuilder;
23+
import org.elasticsearch.action.search.SearchPhaseExecutionException;
24+
import org.elasticsearch.action.search.SearchRequestBuilder;
2325
import org.elasticsearch.action.search.SearchResponse;
2426
import org.elasticsearch.common.bytes.BytesArray;
2527
import org.elasticsearch.common.settings.Settings;
@@ -62,6 +64,7 @@
6264
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
6365
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
6466
import static org.hamcrest.Matchers.allOf;
67+
import static org.hamcrest.Matchers.containsString;
6568
import static org.hamcrest.Matchers.equalTo;
6669
import static org.hamcrest.Matchers.greaterThan;
6770
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -322,11 +325,11 @@ public void testMap() {
322325
assertThat(numShardsRun, greaterThan(0));
323326
}
324327

325-
public void testMapWithParams() {
328+
public void testExplicitAggParam() {
326329
Map<String, Object> params = new HashMap<>();
327330
params.put("_agg", new ArrayList<>());
328331

329-
Script mapScript = new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "_agg.add(1)", params);
332+
Script mapScript = new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "_agg.add(1)", Collections.emptyMap());
330333

331334
SearchResponse response = client().prepareSearch("idx")
332335
.setQuery(matchAllQuery())
@@ -1001,4 +1004,16 @@ public void testDontCacheScripts() throws Exception {
10011004
assertThat(client().admin().indices().prepareStats("cache_test_idx").setRequestCache(true).get().getTotal().getRequestCache()
10021005
.getMissCount(), equalTo(0L));
10031006
}
1007+
1008+
public void testConflictingAggAndScriptParams() {
1009+
Map<String, Object> params = Collections.singletonMap("param1", "12");
1010+
Script mapScript = new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "_agg.add(1)", params);
1011+
1012+
SearchRequestBuilder builder = client().prepareSearch("idx")
1013+
.setQuery(matchAllQuery())
1014+
.addAggregation(scriptedMetric("scripted").params(params).mapScript(mapScript));
1015+
1016+
SearchPhaseExecutionException ex = expectThrows(SearchPhaseExecutionException.class, builder::get);
1017+
assertThat(ex.getCause().getMessage(), containsString("Parameter name \"param1\" used in both aggregation and script parameters"));
1018+
}
10041019
}

server/src/test/java/org/elasticsearch/search/aggregations/metrics/scripted/ScriptedMetricAggregatorTests.java

+24
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public class ScriptedMetricAggregatorTests extends AggregatorTestCase {
7171
Collections.singletonMap("itemValue", 12));
7272
private static final Script COMBINE_SCRIPT_PARAMS = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "combineScriptParams",
7373
Collections.singletonMap("divisor", 4));
74+
private static final String CONFLICTING_PARAM_NAME = "initialValue";
7475

7576
private static final Map<String, Function<Map<String, Object>, Object>> SCRIPTS = new HashMap<>();
7677

@@ -233,6 +234,29 @@ public void testScriptParamsPassedThrough() throws IOException {
233234
}
234235
}
235236

237+
public void testConflictingAggAndScriptParams() throws IOException {
238+
try (Directory directory = newDirectory()) {
239+
try (RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory)) {
240+
for (int i = 0; i < 100; i++) {
241+
indexWriter.addDocument(singleton(new SortedNumericDocValuesField("number", i)));
242+
}
243+
}
244+
245+
try (IndexReader indexReader = DirectoryReader.open(directory)) {
246+
ScriptedMetricAggregationBuilder aggregationBuilder = new ScriptedMetricAggregationBuilder(AGG_NAME);
247+
Map<String, Object> aggParams = Collections.singletonMap(CONFLICTING_PARAM_NAME, "blah");
248+
aggregationBuilder.params(aggParams).initScript(INIT_SCRIPT_PARAMS).mapScript(MAP_SCRIPT_PARAMS).
249+
combineScript(COMBINE_SCRIPT_PARAMS);
250+
251+
IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () ->
252+
search(newSearcher(indexReader, true, true), new MatchAllDocsQuery(), aggregationBuilder)
253+
);
254+
assertEquals("Parameter name \"" + CONFLICTING_PARAM_NAME + "\" used in both aggregation and script parameters",
255+
ex.getMessage());
256+
}
257+
}
258+
}
259+
236260
/**
237261
* We cannot use Mockito for mocking QueryShardContext in this case because
238262
* script-related methods (e.g. QueryShardContext#getLazyExecutableScript)

0 commit comments

Comments
 (0)