Skip to content

Commit

Permalink
Disallow duplicate parameter names between scripted agg and script (e…
Browse files Browse the repository at this point in the history
…lastic#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.
  • Loading branch information
rationull committed Mar 19, 2018
1 parent ccf2417 commit a41bc47
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,15 +136,17 @@ private static <T> T deepCopyParams(T original, SearchContext context) {
}

private static Map<String, Object> mergeParams(Map<String, Object> agg, Map<String, Object> script) {
// TODO Should we throw an exception when param names conflict between aggregation and script? Need to add test coverage
// for error or override behavior depending on the decision. Should this check be added at call time or at
// construction?
// Start with script params
Map<String, Object> combined = new HashMap<>(script);

// Add in agg params, throwing an exception if any conflicts are detected
for (Map.Entry<String, Object> aggEntry : agg.entrySet()) {
if (combined.putIfAbsent(aggEntry.getKey(), aggEntry.getValue()) != null) {
throw new IllegalArgumentException("Parameter name \"" + aggEntry.getKey() +
"\" used in both aggregation and script parameters");
}
}

// Aggregation level commands need to win in case of conflict so that params can keep the same identity and
// content across all the scripts that are run in the aggregation.
Map<String, Object> combined = new HashMap<>();
combined.putAll(script);
combined.putAll(agg);
return combined;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
package org.elasticsearch.search.aggregations.metrics;

import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.action.search.SearchPhaseExecutionException;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
Expand Down Expand Up @@ -62,6 +64,7 @@
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertSearchResponse;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
Expand Down Expand Up @@ -322,11 +325,11 @@ public void testMap() {
assertThat(numShardsRun, greaterThan(0));
}

public void testMapWithParams() {
public void testExplicitAggParam() {

This comment has been minimized.

Copy link
@rationull

rationull Mar 19, 2018

Author Owner

testMapWithParams was actually working because the params were on the aggregation, not because the params were on the map script. This test may be extraneous now but I renamed it instead of deleting it since it does provide specific coverage for an explicit _agg param, even if that may be kind of a weird case.

Map<String, Object> params = new HashMap<>();
params.put("_agg", new ArrayList<>());

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

SearchResponse response = client().prepareSearch("idx")
.setQuery(matchAllQuery())
Expand Down Expand Up @@ -1001,4 +1004,16 @@ public void testDontCacheScripts() throws Exception {
assertThat(client().admin().indices().prepareStats("cache_test_idx").setRequestCache(true).get().getTotal().getRequestCache()
.getMissCount(), equalTo(0L));
}

public void testConflictingAggAndScriptParams() {
Map<String, Object> params = Collections.singletonMap("param1", "12");
Script mapScript = new Script(ScriptType.INLINE, CustomScriptPlugin.NAME, "_agg.add(1)", params);

SearchRequestBuilder builder = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(scriptedMetric("scripted").params(params).mapScript(mapScript));

SearchPhaseExecutionException ex = expectThrows(SearchPhaseExecutionException.class, builder::get);
assertThat(ex.getCause().getMessage(), containsString("Parameter name \"param1\" used in both aggregation and script parameters"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public class ScriptedMetricAggregatorTests extends AggregatorTestCase {
Collections.singletonMap("itemValue", 12));
private static final Script COMBINE_SCRIPT_PARAMS = new Script(ScriptType.INLINE, MockScriptEngine.NAME, "combineScriptParams",
Collections.singletonMap("divisor", 4));
private static final String CONFLICTING_PARAM_NAME = "initialValue";

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

Expand Down Expand Up @@ -233,6 +234,29 @@ public void testScriptParamsPassedThrough() throws IOException {
}
}

public void testConflictingAggAndScriptParams() throws IOException {
try (Directory directory = newDirectory()) {
try (RandomIndexWriter indexWriter = new RandomIndexWriter(random(), directory)) {
for (int i = 0; i < 100; i++) {
indexWriter.addDocument(singleton(new SortedNumericDocValuesField("number", i)));
}
}

try (IndexReader indexReader = DirectoryReader.open(directory)) {
ScriptedMetricAggregationBuilder aggregationBuilder = new ScriptedMetricAggregationBuilder(AGG_NAME);
Map<String, Object> aggParams = Collections.singletonMap(CONFLICTING_PARAM_NAME, "blah");
aggregationBuilder.params(aggParams).initScript(INIT_SCRIPT_PARAMS).mapScript(MAP_SCRIPT_PARAMS).
combineScript(COMBINE_SCRIPT_PARAMS);

IllegalArgumentException ex = expectThrows(IllegalArgumentException.class, () ->
search(newSearcher(indexReader, true, true), new MatchAllDocsQuery(), aggregationBuilder)
);
assertEquals("Parameter name \"" + CONFLICTING_PARAM_NAME + "\" used in both aggregation and script parameters",
ex.getMessage());
}
}
}

/**
* We cannot use Mockito for mocking QueryShardContext in this case because
* script-related methods (e.g. QueryShardContext#getLazyExecutableScript)
Expand Down

0 comments on commit a41bc47

Please sign in to comment.