From c3a906dd79a54a4c3a1661421d952d889161d3ec Mon Sep 17 00:00:00 2001 From: Igor Motov Date: Wed, 14 Oct 2020 11:56:57 -0400 Subject: [PATCH] Add value_count mode to rate agg Adds a new value count mode to the rate aggregation. Closes #63575 --- .../metrics/rate-aggregation.asciidoc | 82 +++++++++++++- .../rate/AbstractRateAggregator.java | 6 ++ .../rate/HistogramRateAggregator.java | 16 ++- .../analytics/rate/NumericRateAggregator.java | 18 +++- .../rate/RateAggregationBuilder.java | 62 +++++++---- .../analytics/rate/RateAggregatorFactory.java | 8 +- .../rate/RateAggregatorSupplier.java | 1 + .../xpack/analytics/rate/RateMode.java | 24 +++++ .../rate/RateAggregationBuilderTests.java | 3 + .../analytics/rate/RateAggregatorTests.java | 102 ++++++++++++++++++ 10 files changed, 293 insertions(+), 29 deletions(-) create mode 100644 x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateMode.java diff --git a/docs/reference/aggregations/metrics/rate-aggregation.asciidoc b/docs/reference/aggregations/metrics/rate-aggregation.asciidoc index 5d2be2520fa19..ef4704533013d 100644 --- a/docs/reference/aggregations/metrics/rate-aggregation.asciidoc +++ b/docs/reference/aggregations/metrics/rate-aggregation.asciidoc @@ -93,8 +93,8 @@ be automatically calculated by multiplying monthly rate by 12. // TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/] Instead of counting the number of documents, it is also possible to calculate a sum of all values of the fields in the documents in each -bucket. The following request will group all sales records into monthly bucket and than calculate the total monthly sales and convert them -into average daily sales. +bucket or the number of values in each bucket. The following request will group all sales records into monthly bucket and than calculate +the total monthly sales and convert them into average daily sales. [source,console] -------------------------------------------------- @@ -164,6 +164,84 @@ The response will contain the average daily sale prices for each month. -------------------------------------------------- // TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/] +By adding the `mode` parameter with the value `value_count`, we can change the calculation from `sum` to the number of values of the field: + +[source,console] +-------------------------------------------------- +GET sales/_search +{ + "size": 0, + "aggs": { + "by_date": { + "date_histogram": { + "field": "date", + "calendar_interval": "month" <1> + }, + "aggs": { + "avg_number_of_sales_per_year": { + "rate": { + "field": "price", <2> + "unit": "year", <3> + "mode": "value_count" <4> + } + } + } + } + } +} +-------------------------------------------------- +// TEST[setup:sales] +<1> Histogram is grouped by month. +<2> Calculate number of of all sale prices +<3> Convert to annual counts +<4> Changing the mode to value count + +The response will contain the average daily sale prices for each month. + +[source,console-result] +-------------------------------------------------- +{ + ... + "aggregations" : { + "by_date" : { + "buckets" : [ + { + "key_as_string" : "2015/01/01 00:00:00", + "key" : 1420070400000, + "doc_count" : 3, + "avg_number_of_sales_per_year" : { + "value" : 36.0 + } + }, + { + "key_as_string" : "2015/02/01 00:00:00", + "key" : 1422748800000, + "doc_count" : 2, + "avg_number_of_sales_per_year" : { + "value" : 24.0 + } + }, + { + "key_as_string" : "2015/03/01 00:00:00", + "key" : 1425168000000, + "doc_count" : 2, + "avg_number_of_sales_per_year" : { + "value" : 24.0 + } + } + ] + } + } +} +-------------------------------------------------- +// TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/] + +By default `sum` mode is used. + +`"mode": "sum"`:: calculate the sum of all values field +`"mode": "value_count"`:: use the number of values in the field + +The `mode` parameter can only be used with fields and scripts. ==== Relationship between bucket sizes and rate diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/AbstractRateAggregator.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/AbstractRateAggregator.java index 937e925703066..2017ba9fd11d7 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/AbstractRateAggregator.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/AbstractRateAggregator.java @@ -26,6 +26,7 @@ public abstract class AbstractRateAggregator extends NumericMetricsAggregator.Si protected final ValuesSource valuesSource; private final DocValueFormat format; private final Rounding.DateTimeUnit rateUnit; + protected final RateMode rateMode; private final SizedBucketAggregator sizedBucketAggregator; protected DoubleArray sums; @@ -35,6 +36,7 @@ public AbstractRateAggregator( String name, ValuesSourceConfig valuesSourceConfig, Rounding.DateTimeUnit rateUnit, + RateMode rateMode, SearchContext context, Aggregator parent, Map metadata @@ -45,8 +47,12 @@ public AbstractRateAggregator( if (valuesSource != null) { sums = context.bigArrays().newDoubleArray(1, true); compensations = context.bigArrays().newDoubleArray(1, true); + if (rateMode == null) { + rateMode = RateMode.SUM; + } } this.rateUnit = rateUnit; + this.rateMode = rateMode; this.sizedBucketAggregator = findSizedBucketAncestor(); } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/HistogramRateAggregator.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/HistogramRateAggregator.java index 033549a90e0da..53f4c07221cd6 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/HistogramRateAggregator.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/HistogramRateAggregator.java @@ -27,11 +27,12 @@ public HistogramRateAggregator( String name, ValuesSourceConfig valuesSourceConfig, Rounding.DateTimeUnit rateUnit, + RateMode rateMode, SearchContext context, Aggregator parent, Map metadata ) throws IOException { - super(name, valuesSourceConfig, rateUnit, context, parent, metadata); + super(name, valuesSourceConfig, rateUnit, rateMode, context, parent, metadata); } @Override @@ -51,7 +52,18 @@ public void collect(int doc, long bucket) throws IOException { double sum = sums.get(bucket); double compensation = compensations.get(bucket); kahanSummation.reset(sum, compensation); - kahanSummation.add(sketch.value()); + final double value; + switch (rateMode) { + case SUM: + value = sketch.value(); + break; + case VALUE_COUNT: + value = sketch.count(); + break; + default: + throw new IllegalArgumentException("Unsupported rate mode " + rateMode); + } + kahanSummation.add(value); compensations.set(bucket, kahanSummation.delta()); sums.set(bucket, kahanSummation.value()); } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/NumericRateAggregator.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/NumericRateAggregator.java index e37c9386b1553..403fa181fd9ad 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/NumericRateAggregator.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/NumericRateAggregator.java @@ -26,11 +26,12 @@ public NumericRateAggregator( String name, ValuesSourceConfig valuesSourceConfig, Rounding.DateTimeUnit rateUnit, + RateMode rateMode, SearchContext context, Aggregator parent, Map metadata ) throws IOException { - super(name, valuesSourceConfig, rateUnit, context, parent, metadata); + super(name, valuesSourceConfig, rateUnit, rateMode, context, parent, metadata); } @Override @@ -51,10 +52,17 @@ public void collect(int doc, long bucket) throws IOException { double sum = sums.get(bucket); double compensation = compensations.get(bucket); kahanSummation.reset(sum, compensation); - - for (int i = 0; i < valuesCount; i++) { - double value = values.nextValue(); - kahanSummation.add(value); + switch (rateMode) { + case SUM: + for (int i = 0; i < valuesCount; i++) { + kahanSummation.add(values.nextValue()); + } + break; + case VALUE_COUNT: + kahanSummation.add(valuesCount); + break; + default: + throw new IllegalArgumentException("Unsupported rate mode " + rateMode); } compensations.set(bucket, kahanSummation.delta()); diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilder.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilder.java index 79e04ebf7b75b..12120e88a892d 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilder.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilder.java @@ -5,6 +5,11 @@ */ package org.elasticsearch.xpack.analytics.rate; +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import org.elasticsearch.Version; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Rounding; import org.elasticsearch.common.io.stream.StreamInput; @@ -24,13 +29,10 @@ import org.elasticsearch.search.aggregations.support.ValuesSourceRegistry; import org.elasticsearch.search.aggregations.support.ValuesSourceType; -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafOnly { public static final String NAME = "rate"; public static final ParseField UNIT_FIELD = new ParseField("unit"); + public static final ParseField MODE_FIELD = new ParseField("mode"); public static final ValuesSourceRegistry.RegistryKey REGISTRY_KEY = new ValuesSourceRegistry.RegistryKey<>( NAME, RateAggregatorSupplier.class @@ -40,9 +42,11 @@ public class RateAggregationBuilder extends ValuesSourceAggregationBuilder.LeafO static { ValuesSourceAggregationBuilder.declareFields(PARSER, true, true, false, false); PARSER.declareString(RateAggregationBuilder::rateUnit, UNIT_FIELD); + PARSER.declareString(RateAggregationBuilder::rateMode, MODE_FIELD); } Rounding.DateTimeUnit rateUnit; + RateMode rateMode; public static void registerAggregators(ValuesSourceRegistry.Builder builder) { RateAggregatorFactory.registerAggregators(builder); @@ -58,6 +62,8 @@ protected RateAggregationBuilder( Map metadata ) { super(clone, factoriesBuilder, metadata); + this.rateUnit = clone.rateUnit; + this.rateMode = clone.rateMode; } @Override @@ -76,6 +82,11 @@ public RateAggregationBuilder(StreamInput in) throws IOException { } else { rateUnit = null; } + if (in.getVersion().onOrAfter(Version.V_8_0_0)) { + if (in.readBoolean()) { + rateMode = in.readEnum(RateMode.class); + } + } } @Override @@ -90,6 +101,14 @@ protected void innerWriteTo(StreamOutput out) throws IOException { } else { out.writeByte((byte) 0); } + if (out.getVersion().onOrAfter(Version.V_8_0_0)) { + if (rateMode != null) { + out.writeBoolean(true); + out.writeEnum(rateMode); + } else { + out.writeBoolean(false); + } + } } @Override @@ -104,7 +123,12 @@ protected RateAggregatorFactory innerBuild( AggregatorFactory parent, AggregatorFactories.Builder subFactoriesBuilder ) throws IOException { - return new RateAggregatorFactory(name, config, rateUnit, context, parent, subFactoriesBuilder, metadata); + if (field() == null && script() == null) { + if (rateMode != null) { + throw new IllegalArgumentException("The mode parameter is only supported with field or script"); + } + } + return new RateAggregatorFactory(name, config, rateUnit, rateMode, context, parent, subFactoriesBuilder, metadata); } @Override @@ -112,6 +136,9 @@ public XContentBuilder doXContentBody(XContentBuilder builder, Params params) th if (rateUnit != null) { builder.field(UNIT_FIELD.getPreferredName(), rateUnit.shortName()); } + if (rateMode != null) { + builder.field(MODE_FIELD.getPreferredName(), rateMode.value()); + } return builder; } @@ -129,6 +156,15 @@ public RateAggregationBuilder rateUnit(Rounding.DateTimeUnit rateUnit) { return this; } + public RateAggregationBuilder rateMode(String rateMode) { + return rateMode(RateMode.resolve(rateMode)); + } + + public RateAggregationBuilder rateMode(RateMode rateMode) { + this.rateMode = rateMode; + return this; + } + static Rounding.DateTimeUnit parse(String rateUnit) { Rounding.DateTimeUnit parsedRate = DateHistogramAggregationBuilder.DATE_FIELD_UNITS.get(rateUnit); if (parsedRate == null) { @@ -140,17 +176,7 @@ static Rounding.DateTimeUnit parse(String rateUnit) { @Override protected ValuesSourceConfig resolveConfig(AggregationContext context) { if (field() == null && script() == null) { - return new ValuesSourceConfig( - CoreValuesSourceType.NUMERIC, - null, - true, - null, - null, - 1.0, - null, - DocValueFormat.RAW, - context - ); + return new ValuesSourceConfig(CoreValuesSourceType.NUMERIC, null, true, null, null, 1.0, null, DocValueFormat.RAW, context); } else { return super.resolveConfig(context); } @@ -162,11 +188,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; RateAggregationBuilder that = (RateAggregationBuilder) o; - return rateUnit == that.rateUnit; + return rateUnit == that.rateUnit && rateMode == that.rateMode; } @Override public int hashCode() { - return Objects.hash(super.hashCode(), rateUnit); + return Objects.hash(super.hashCode(), rateUnit, rateMode); } } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorFactory.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorFactory.java index afdcb9ba1193f..fcf7515db1801 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorFactory.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorFactory.java @@ -29,10 +29,13 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory { private final Rounding.DateTimeUnit rateUnit; + private final RateMode rateMode; + RateAggregatorFactory( String name, ValuesSourceConfig config, Rounding.DateTimeUnit rateUnit, + RateMode rateMode, AggregationContext context, AggregatorFactory parent, AggregatorFactories.Builder subFactoriesBuilder, @@ -40,6 +43,7 @@ class RateAggregatorFactory extends ValuesSourceAggregatorFactory { ) throws IOException { super(name, config, context, parent, subFactoriesBuilder, metadata); this.rateUnit = rateUnit; + this.rateMode = rateMode; } static void registerAggregators(ValuesSourceRegistry.Builder builder) { @@ -59,7 +63,7 @@ static void registerAggregators(ValuesSourceRegistry.Builder builder) { @Override protected Aggregator createUnmapped(SearchContext searchContext, Aggregator parent, Map metadata) throws IOException { - return new AbstractRateAggregator(name, config, rateUnit, searchContext, parent, metadata) { + return new AbstractRateAggregator(name, config, rateUnit, rateMode, searchContext, parent, metadata) { @Override public LeafBucketCollector getLeafCollector(LeafReaderContext ctx, LeafBucketCollector sub) { return LeafBucketCollector.NO_OP_COLLECTOR; @@ -76,6 +80,6 @@ protected Aggregator doCreateInternal( ) throws IOException { return context.getValuesSourceRegistry() .getAggregator(RateAggregationBuilder.REGISTRY_KEY, config) - .build(name, config, rateUnit, searchContext, parent, metadata); + .build(name, config, rateUnit, rateMode, searchContext, parent, metadata); } } diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorSupplier.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorSupplier.java index e2b1894b08b57..a700be2715908 100644 --- a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorSupplier.java +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorSupplier.java @@ -19,6 +19,7 @@ Aggregator build( String name, ValuesSourceConfig valuesSourceConfig, Rounding.DateTimeUnit rateUnit, + RateMode rateMode, SearchContext context, Aggregator parent, Map metadata diff --git a/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateMode.java b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateMode.java new file mode 100644 index 0000000000000..059387ee4158d --- /dev/null +++ b/x-pack/plugin/analytics/src/main/java/org/elasticsearch/xpack/analytics/rate/RateMode.java @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.analytics.rate; + +import java.util.Locale; + +/** + * Rate mode - value_count or sum + */ +public enum RateMode { + VALUE_COUNT, SUM; + + public static RateMode resolve(String name) { + return RateMode.valueOf(name.toUpperCase(Locale.ROOT)); + } + + public String value() { + return name().toLowerCase(Locale.ROOT); + } + +} diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilderTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilderTests.java index ce3e67d28037d..a276ec5cc859b 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilderTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregationBuilderTests.java @@ -56,6 +56,9 @@ protected RateAggregationBuilder createTestInstance() { } else { aggregationBuilder.script(new Script(randomAlphaOfLength(10))); } + if (randomBoolean()) { + aggregationBuilder.rateMode(randomFrom(RateMode.values())); + } } if (randomBoolean()) { aggregationBuilder.rateUnit(randomFrom(Rounding.DateTimeUnit.values())); diff --git a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorTests.java b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorTests.java index e69d23f066cf5..2878f41da6efe 100644 --- a/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorTests.java +++ b/x-pack/plugin/analytics/src/test/java/org/elasticsearch/xpack/analytics/rate/RateAggregatorTests.java @@ -142,6 +142,31 @@ public void testDocValuesMonthToMonth() throws IOException { }); } + public void testDocValuesMonthToMonthValueCount() throws IOException { + MappedFieldType dateType = dateFieldType(DATE_FIELD); + MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER); + RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month") + .field("val") + .rateMode("value_count"); + + DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date"); + dateHistogramAggregationBuilder.field(DATE_FIELD); + dateHistogramAggregationBuilder.calendarInterval(new DateHistogramInterval("month")); + + dateHistogramAggregationBuilder.subAggregation(rateAggregationBuilder); + testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> { + iw.addDocument(doc("2010-03-12T01:07:45", new SortedNumericDocValuesField("val", 1))); + iw.addDocument(doc("2010-04-01T03:43:34", new SortedNumericDocValuesField("val", 3))); + iw.addDocument( + doc("2010-04-27T03:43:34", new SortedNumericDocValuesField("val", 4), new SortedNumericDocValuesField("val", 5)) + ); + }, (Consumer) dh -> { + assertThat(dh.getBuckets(), hasSize(2)); + assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).value(), closeTo(1.0, 0.000001)); + assertThat(((InternalRate) dh.getBuckets().get(1).getAggregations().asList().get(0)).value(), closeTo(3.0, 0.000001)); + }, dateType, numType); + } + public void testDocValuesMonthToMonthDefaultRate() throws IOException { testCase(new MatchAllDocsQuery(), "month", true, null, "val", iw -> { iw.addDocument(doc("2010-03-12T01:07:45", new NumericDocValuesField("val", 1))); @@ -260,6 +285,9 @@ public void testDoubleWrapping() throws IOException { MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER); MappedFieldType dateType = dateFieldType(DATE_FIELD); RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) .calendarInterval(new DateHistogramInterval("month")) .subAggregation(rateAggregationBuilder); @@ -291,6 +319,9 @@ public void testKeywordSandwich() throws IOException { MappedFieldType dateType = dateFieldType(DATE_FIELD); MappedFieldType keywordType = new KeywordFieldMapper.KeywordFieldType("term"); RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } TermsAggregationBuilder termsAggregationBuilder = new TermsAggregationBuilder("my_term").field("term") .subAggregation(rateAggregationBuilder); DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) @@ -348,6 +379,9 @@ public void testFilter() throws IOException { MappedFieldType dateType = dateFieldType(DATE_FIELD); MappedFieldType keywordType = new KeywordFieldMapper.KeywordFieldType("term"); RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) .calendarInterval(new DateHistogramInterval("month")) @@ -371,6 +405,9 @@ public void testFormatter() throws IOException { RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month") .field("val") .format("00.0/M"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) .calendarInterval(new DateHistogramInterval("month")) @@ -392,6 +429,9 @@ public void testHistogramFieldMonthToMonth() throws IOException { MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap()); MappedFieldType dateType = dateFieldType(DATE_FIELD); RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) .calendarInterval(new DateHistogramInterval("month")) @@ -411,6 +451,9 @@ public void testHistogramFieldMonthToYear() throws IOException { MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap()); MappedFieldType dateType = dateFieldType(DATE_FIELD); RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").field("val"); + if (randomBoolean()) { + rateAggregationBuilder.rateMode("sum"); + } DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) .calendarInterval(new DateHistogramInterval("year")) @@ -425,6 +468,47 @@ public void testHistogramFieldMonthToYear() throws IOException { }, dateType, histType); } + public void testHistogramFieldMonthToMonthValueCount() throws IOException { + MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap()); + MappedFieldType dateType = dateFieldType(DATE_FIELD); + RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month") + .rateMode("value_count") + .field("val"); + + DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) + .calendarInterval(new DateHistogramInterval("month")) + .subAggregation(rateAggregationBuilder); + + testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> { + iw.addDocument(doc("2010-03-01T00:00:00", histogramFieldDocValues("val", new double[] { 1, 2 }))); + iw.addDocument(doc("2010-04-01T00:00:00", histogramFieldDocValues("val", new double[] { 3, 4, 5 }))); + }, (Consumer) dh -> { + assertThat(dh.getBuckets(), hasSize(2)); + assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).getValue(), closeTo(2.0, 0.000001)); + assertThat(((InternalRate) dh.getBuckets().get(1).getAggregations().asList().get(0)).getValue(), closeTo(3.0, 0.000001)); + }, dateType, histType); + } + + public void testHistogramFieldMonthToYearValueCount() throws IOException { + MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap()); + MappedFieldType dateType = dateFieldType(DATE_FIELD); + RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month") + .rateMode("value_count") + .field("val"); + + DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) + .calendarInterval(new DateHistogramInterval("year")) + .subAggregation(rateAggregationBuilder); + + testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> { + iw.addDocument(doc("2010-03-01T00:00:00", histogramFieldDocValues("val", new double[] { 1, 2 }))); + iw.addDocument(doc("2010-04-01T00:00:00", histogramFieldDocValues("val", new double[] { 3, 4, 5 }))); + }, (Consumer) dh -> { + assertThat(dh.getBuckets(), hasSize(1)); + assertThat(((InternalRate) dh.getBuckets().get(0).getAggregations().asList().get(0)).getValue(), closeTo(5.0 / 12, 0.000001)); + }, dateType, histType); + } + public void testFilterWithHistogramField() throws IOException { MappedFieldType histType = new HistogramFieldMapper.HistogramFieldType("val", Collections.emptyMap()); MappedFieldType dateType = dateFieldType(DATE_FIELD); @@ -449,6 +533,24 @@ public void testFilterWithHistogramField() throws IOException { }, dateType, histType, keywordType); } + public void testModeWithoutField() { + MappedFieldType dateType = dateFieldType(DATE_FIELD); + MappedFieldType numType = new NumberFieldMapper.NumberFieldType("val", NumberFieldMapper.NumberType.INTEGER); + RateAggregationBuilder rateAggregationBuilder = new RateAggregationBuilder("my_rate").rateUnit("month").rateMode("sum"); + + DateHistogramAggregationBuilder dateHistogramAggregationBuilder = new DateHistogramAggregationBuilder("my_date").field(DATE_FIELD) + .calendarInterval(new DateHistogramInterval("month")) + .subAggregation(rateAggregationBuilder); + + IllegalArgumentException ex = expectThrows( + IllegalArgumentException.class, + () -> testCase(dateHistogramAggregationBuilder, new MatchAllDocsQuery(), iw -> { + iw.addDocument(doc("2010-03-12T01:07:45", new SortedNumericDocValuesField("val", 1))); + }, h -> { fail("Shouldn't be here"); }, dateType, numType) + ); + assertEquals("The mode parameter is only supported with field or script", ex.getMessage()); + } + private void testCase( Query query, String interval,