Add script support to value_count aggregations.

Close #5001
This commit is contained in:
Adrien Grand 2014-02-04 11:52:45 +01:00
parent 9333a3c28a
commit 6777be60ce
4 changed files with 121 additions and 21 deletions

View File

@ -2,7 +2,7 @@
=== Value Count === Value Count
A `single-value` metrics aggregation that counts the number of values that are extracted from the aggregated documents. A `single-value` metrics aggregation that counts the number of values that are extracted from the aggregated documents.
These values can be extracted either from specific fields in the documents. Typically, These values can be extracted either from specific fields in the documents, or be generated by a provided script. Typically,
this aggregator will be used in conjunction with other single-value aggregations. For example, when computing the `avg` this aggregator will be used in conjunction with other single-value aggregations. For example, when computing the `avg`
one might be interested in the number of values the average is computed over. one might be interested in the number of values the average is computed over.
@ -33,3 +33,17 @@ Response:
The name of the aggregation (`grades_count` above) also serves as the key by which the aggregation result can be The name of the aggregation (`grades_count` above) also serves as the key by which the aggregation result can be
retrieved from the returned response. retrieved from the returned response.
==== Script
Counting the values generated by a script:
[source,js]
--------------------------------------------------
{
...,
"aggs" : {
"grades_count" : { "value_count" : { "script" : "doc['grade'].value" } }
}
}
--------------------------------------------------

View File

@ -19,31 +19,15 @@
package org.elasticsearch.search.aggregations.metrics.valuecount; package org.elasticsearch.search.aggregations.metrics.valuecount;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.search.aggregations.metrics.ValuesSourceMetricsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.MetricsAggregationBuilder;
import java.io.IOException;
/** /**
* *
*/ */
public class ValueCountBuilder extends MetricsAggregationBuilder<ValueCountBuilder> { public class ValueCountBuilder extends ValuesSourceMetricsAggregationBuilder<ValueCountBuilder> {
private String field;
public ValueCountBuilder(String name) { public ValueCountBuilder(String name) {
super(name, InternalValueCount.TYPE.name()); super(name, InternalValueCount.TYPE.name());
} }
public ValueCountBuilder field(String field) {
this.field = field;
return this;
}
@Override
protected void internalXContent(XContentBuilder builder, Params params) throws IOException {
if (field != null) {
builder.field("field", field);
}
}
} }

View File

@ -30,6 +30,7 @@ import org.elasticsearch.search.aggregations.support.bytes.BytesValuesSource;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.Map;
/** /**
* *
@ -47,6 +48,10 @@ public class ValueCountParser implements Aggregator.Parser {
ValuesSourceConfig<BytesValuesSource> config = new ValuesSourceConfig<BytesValuesSource>(BytesValuesSource.class); ValuesSourceConfig<BytesValuesSource> config = new ValuesSourceConfig<BytesValuesSource>(BytesValuesSource.class);
String field = null; String field = null;
String script = null;
String scriptLang = null;
Map<String, Object> scriptParams = null;
boolean assumeUnique = false;
XContentParser.Token token; XContentParser.Token token;
String currentFieldName = null; String currentFieldName = null;
@ -56,14 +61,36 @@ public class ValueCountParser implements Aggregator.Parser {
} else if (token == XContentParser.Token.VALUE_STRING) { } else if (token == XContentParser.Token.VALUE_STRING) {
if ("field".equals(currentFieldName)) { if ("field".equals(currentFieldName)) {
field = parser.text(); field = parser.text();
} else if ("script".equals(currentFieldName)) {
script = parser.text();
} else if ("lang".equals(currentFieldName)) {
scriptLang = parser.text();
} else { } else {
throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "]."); throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "].");
} }
} else if (token == XContentParser.Token.VALUE_BOOLEAN) {
if ("script_values_unique".equals(currentFieldName)) {
assumeUnique = parser.booleanValue();
} else {
throw new SearchParseException(context, "Unknown key for a " + token + " in [" + aggregationName + "]: [" + currentFieldName + "].");
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("params".equals(currentFieldName)) {
scriptParams = parser.map();
}
} else { } else {
throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "]."); throw new SearchParseException(context, "Unexpected token " + token + " in [" + aggregationName + "].");
} }
} }
if (script != null) {
config.script(context.scriptService().search(context.lookup(), scriptLang, script, scriptParams));
}
if (!assumeUnique) {
config.ensureUnique(true);
}
if (field == null) { if (field == null) {
return new ValueCountAggregator.Factory(aggregationName, config); return new ValueCountAggregator.Factory(aggregationName, config);
} }

View File

@ -23,7 +23,6 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount; import org.elasticsearch.search.aggregations.metrics.valuecount.ValueCount;
import org.elasticsearch.test.ElasticsearchIntegrationTest; import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -124,4 +123,80 @@ public class ValueCountTests extends ElasticsearchIntegrationTest {
assertThat(valueCount.getName(), equalTo("count")); assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(20l)); assertThat(valueCount.getValue(), equalTo(20l));
} }
@Test
public void singleValuedScript() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(count("count").script("doc['value'].value"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));
ValueCount valueCount = searchResponse.getAggregations().get("count");
assertThat(valueCount, notNullValue());
assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(10l));
}
@Test
public void multiValuedScript() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(count("count").script("doc['values'].values"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));
ValueCount valueCount = searchResponse.getAggregations().get("count");
assertThat(valueCount, notNullValue());
assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(20l));
}
@Test
public void singleValuedScriptWithParams() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(count("count").script("doc[s].value").param("s", "value"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));
ValueCount valueCount = searchResponse.getAggregations().get("count");
assertThat(valueCount, notNullValue());
assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(10l));
}
@Test
public void multiValuedScriptWithParams() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(count("count").script("doc[s].values").param("s", "values"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));
ValueCount valueCount = searchResponse.getAggregations().get("count");
assertThat(valueCount, notNullValue());
assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(20l));
}
@Test
public void deduplication() throws Exception {
SearchResponse searchResponse = client().prepareSearch("idx")
.setQuery(matchAllQuery())
.addAggregation(count("count").script("doc['values'].values + [5L]"))
.execute().actionGet();
assertThat(searchResponse.getHits().getTotalHits(), equalTo(10l));
ValueCount valueCount = searchResponse.getAggregations().get("count");
assertThat(valueCount, notNullValue());
assertThat(valueCount.getName(), equalTo("count"));
assertThat(valueCount.getValue(), equalTo(28l));
}
} }