diff --git a/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java new file mode 100644 index 00000000000..ed4b9416908 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/search/aggregations/metrics/avg/ParsedAvg.java @@ -0,0 +1,64 @@ +/* + * 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.search.aggregations.metrics.avg; + +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.search.aggregations.metrics.ParsedSingleValueNumericMetricsAggregation; + +import java.io.IOException; + +public class ParsedAvg extends ParsedSingleValueNumericMetricsAggregation implements Avg { + + @Override + public double getValue() { + return value(); + } + + @Override + protected String getType() { + return AvgAggregationBuilder.NAME; + } + + @Override + protected XContentBuilder doXContentBody(XContentBuilder builder, Params params) throws IOException { + // InternalAvg renders value only if the avg normalizer (count) is not 0. + // We parse back `null` as Double.POSITIVE_INFINITY so we check for that value here to get the same xContent output + boolean hasValue = value != Double.POSITIVE_INFINITY; + builder.field(CommonFields.VALUE.getPreferredName(), hasValue ? value : null); + if (hasValue && valueAsString != null) { + builder.field(CommonFields.VALUE_AS_STRING.getPreferredName(), valueAsString); + } + return builder; + } + + private static final ObjectParser PARSER = new ObjectParser<>(ParsedAvg.class.getSimpleName(), true, ParsedAvg::new); + + static { + declareSingeValueFields(PARSER, Double.POSITIVE_INFINITY); + } + + public static ParsedAvg fromXContent(XContentParser parser, final String name) { + ParsedAvg avg = PARSER.apply(parser, null); + avg.setName(name); + return avg; + } +} \ No newline at end of file diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java b/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java index e1d3fe5bcde..bfa17179851 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/InternalAggregationTestCase.java @@ -33,6 +33,8 @@ import org.elasticsearch.indices.breaker.NoneCircuitBreakerService; import org.elasticsearch.rest.action.search.RestSearchAction; import org.elasticsearch.script.ScriptService; import org.elasticsearch.search.SearchModule; +import org.elasticsearch.search.aggregations.metrics.avg.AvgAggregationBuilder; +import org.elasticsearch.search.aggregations.metrics.avg.ParsedAvg; import org.elasticsearch.search.aggregations.metrics.cardinality.CardinalityAggregationBuilder; import org.elasticsearch.search.aggregations.metrics.cardinality.ParsedCardinality; import org.elasticsearch.search.aggregations.metrics.max.MaxAggregationBuilder; @@ -76,6 +78,7 @@ public abstract class InternalAggregationTestCase namedXContents.put(MinAggregationBuilder.NAME, (p, c) -> ParsedMin.fromXContent(p, (String) c)); namedXContents.put(MaxAggregationBuilder.NAME, (p, c) -> ParsedMax.fromXContent(p, (String) c)); namedXContents.put(SumAggregationBuilder.NAME, (p, c) -> ParsedSum.fromXContent(p, (String) c)); + namedXContents.put(AvgAggregationBuilder.NAME, (p, c) -> ParsedAvg.fromXContent(p, (String) c)); return namedXContents.entrySet().stream() .map(entry -> new NamedXContentRegistry.Entry(Aggregation.class, new ParseField(entry.getKey()), entry.getValue())) diff --git a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/avg/InternalAvgTests.java b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/avg/InternalAvgTests.java index 0600d7299b4..bfdf37d5775 100644 --- a/core/src/test/java/org/elasticsearch/search/aggregations/metrics/avg/InternalAvgTests.java +++ b/core/src/test/java/org/elasticsearch/search/aggregations/metrics/avg/InternalAvgTests.java @@ -22,6 +22,7 @@ package org.elasticsearch.search.aggregations.metrics.avg; import org.elasticsearch.common.io.stream.Writeable.Reader; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.search.aggregations.InternalAggregationTestCase; +import org.elasticsearch.search.aggregations.ParsedAggregation; import org.elasticsearch.search.aggregations.pipeline.PipelineAggregator; import java.util.List; @@ -31,9 +32,9 @@ public class InternalAvgTests extends InternalAggregationTestCase { @Override protected InternalAvg createTestInstance(String name, List pipelineAggregators, Map metaData) { - return new InternalAvg(name, randomDoubleBetween(0, 100000, true), randomNonNegativeLong() % 100000, - randomFrom(DocValueFormat.BOOLEAN, DocValueFormat.GEOHASH, DocValueFormat.IP, DocValueFormat.RAW), pipelineAggregators, - metaData); + DocValueFormat formatter = randomFrom(new DocValueFormat.Decimal("###.##"), DocValueFormat.BOOLEAN, DocValueFormat.RAW); + long count = frequently() ? randomNonNegativeLong() % 100000 : 0; + return new InternalAvg(name, randomDoubleBetween(0, 100000, true), count, formatter, pipelineAggregators, metaData); } @Override @@ -53,4 +54,14 @@ public class InternalAvgTests extends InternalAggregationTestCase { assertEquals(sum, reduced.getSum(), 0.0000001); assertEquals(sum / counts, reduced.value(), 0.0000001); } + + @Override + protected void assertFromXContent(InternalAvg avg, ParsedAggregation parsedAggregation) { + ParsedAvg parsed = ((ParsedAvg) parsedAggregation); + assertEquals(avg.getValue(), parsed.getValue(), Double.MIN_VALUE); + // we don't print out VALUE_AS_STRING for avg.getCount() == 0, so we cannot get the exact same value back + if (avg.getCount() != 0) { + assertEquals(avg.getValueAsString(), parsed.getValueAsString()); + } + } }