From c0482ed77a90d2d845b65aab9a324c18a4e233fe Mon Sep 17 00:00:00 2001 From: Larisa Motova Date: Wed, 2 Jul 2025 11:11:36 -1000 Subject: [PATCH] [ES|QL] Support avg on aggregate metric double (#130421) Other major aggregations (min, max, sum, count) are available but avg was not. This commit adds it in. --- docs/changelog/130421.yaml | 5 ++++ .../xpack/esql/action/EsqlCapabilities.java | 7 +++++- .../expression/function/aggregate/Avg.java | 18 ++++++++------ .../xpack/esql/analysis/AnalyzerTests.java | 4 ++-- .../xpack/esql/analysis/VerifierTests.java | 2 +- .../function/aggregate/AvgErrorTests.java | 9 ++++++- .../rest-api-spec/test/esql/40_tsdb.yml | 24 +++++++++++++++++++ 7 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 docs/changelog/130421.yaml diff --git a/docs/changelog/130421.yaml b/docs/changelog/130421.yaml new file mode 100644 index 0000000000000..ffabb405f6633 --- /dev/null +++ b/docs/changelog/130421.yaml @@ -0,0 +1,5 @@ +pr: 130421 +summary: Support avg on aggregate metric double +area: ES|QL +type: bug +issues: [] diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java index 9a8b71e8e5eea..f80df51ba21d1 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java @@ -1215,7 +1215,12 @@ public enum Cap { /** * (Re)Added EXPLAIN command */ - EXPLAIN(Build.current().isSnapshot()); + EXPLAIN(Build.current().isSnapshot()), + + /** + * Support avg with aggregate metric doubles + */ + AGGREGATE_METRIC_DOUBLE_AVG(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG); private final boolean enabled; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Avg.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Avg.java index 9f32a4a740a72..931321bab4b1b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Avg.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Avg.java @@ -28,6 +28,7 @@ import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; +import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE; public class Avg extends AggregateFunction implements SurrogateExpression { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Avg", Avg::new); @@ -50,7 +51,7 @@ public Avg( Source source, @Param( name = "number", - type = { "double", "integer", "long" }, + type = { "aggregate_metric_double", "double", "integer", "long" }, description = "Expression that outputs values to average." ) Expression field ) { @@ -65,10 +66,10 @@ public Avg(Source source, Expression field, Expression filter) { protected Expression.TypeResolution resolveType() { return isType( field(), - dt -> dt.isNumeric() && dt != DataType.UNSIGNED_LONG, + dt -> dt.isNumeric() && dt != DataType.UNSIGNED_LONG || dt == AGGREGATE_METRIC_DOUBLE, sourceText(), DEFAULT, - "numeric except unsigned_long or counter types" + "aggregate_metric_double or numeric except unsigned_long or counter types" ); } @@ -105,9 +106,12 @@ public Avg withFilter(Expression filter) { public Expression surrogate() { var s = source(); var field = field(); - - return field().foldable() - ? new MvAvg(s, field) - : new Div(s, new Sum(s, field, filter()), new Count(s, field, filter()), dataType()); + if (field.foldable()) { + return new MvAvg(s, field); + } + if (field.dataType() == AGGREGATE_METRIC_DOUBLE) { + return new Div(s, new Sum(s, field, filter()).surrogate(), new Count(s, field, filter()).surrogate()); + } + return new Div(s, new Sum(s, field, filter()), new Count(s, field, filter()), dataType()); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index b99050b8ef090..bf44c47ea9c4d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -2006,7 +2006,7 @@ public void testUnsupportedTypesInStats() { | stats avg(x), count_distinct(x), max(x), median(x), median_absolute_deviation(x), min(x), percentile(x, 10), sum(x) """, """ Found 8 problems - line 2:12: argument of [avg(x)] must be [numeric except unsigned_long or counter types],\ + line 2:12: argument of [avg(x)] must be [aggregate_metric_double or numeric except unsigned_long or counter types],\ found value [x] type [unsigned_long] line 2:20: argument of [count_distinct(x)] must be [any exact type except unsigned_long, _source, or counter types],\ found value [x] type [unsigned_long] @@ -2028,7 +2028,7 @@ public void testUnsupportedTypesInStats() { | stats avg(x), median(x), median_absolute_deviation(x), percentile(x, 10), sum(x) """, """ Found 5 problems - line 2:10: argument of [avg(x)] must be [numeric except unsigned_long or counter types],\ + line 2:10: argument of [avg(x)] must be [aggregate_metric_double or numeric except unsigned_long or counter types],\ found value [x] type [version] line 2:18: argument of [median(x)] must be [numeric except unsigned_long or counter types],\ found value [x] type [version] diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index 14e5c0615e2b4..27c3c1427b7db 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -359,7 +359,7 @@ public void testAggsExpressionsInStatsAggs() { error("from test | stats max(max(salary)) by first_name") ); assertEquals( - "1:25: argument of [avg(first_name)] must be [numeric except unsigned_long or counter types]," + "1:25: argument of [avg(first_name)] must be [aggregate_metric_double or numeric except unsigned_long or counter types]," + " found value [first_name] type [keyword]", error("from test | stats count(avg(first_name)) by first_name") ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgErrorTests.java index 16f80e4564cff..69c14f815a236 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgErrorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgErrorTests.java @@ -32,6 +32,13 @@ protected Expression build(Source source, List args) { @Override protected Matcher expectedTypeErrorMatcher(List> validPerPosition, List signature) { - return equalTo(typeErrorMessage(false, validPerPosition, signature, (v, p) -> "numeric except unsigned_long or counter types")); + return equalTo( + typeErrorMessage( + false, + validPerPosition, + signature, + (v, p) -> "aggregate_metric_double or numeric except unsigned_long or counter types" + ) + ); } } diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_tsdb.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_tsdb.yml index 8d394dee4acd4..e4a6ebc966a8a 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_tsdb.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_tsdb.yml @@ -707,3 +707,27 @@ to_aggregate_metric_double with multi_values: - match: {values.0.1: '{"min":102.44400024414062,"max":195.10000610351562,"sum":297.54400634765625,"value_count":2}'} - match: {values.0.2: '{"min":64.0,"max":1456.0,"sum":2139.0,"value_count":4}'} - match: {values.0.3: '{"min":123.0,"max":1.9418924E7,"sum":1.9433032E7,"value_count":3}'} + +--- +avg of aggregate_metric_double: + - requires: + test_runner_features: [capabilities] + capabilities: + - method: POST + path: /_query + parameters: [] + capabilities: [aggregate_metric_double_avg] + reason: "support avg aggregations with aggregate metric double" + + - do: + allowed_warnings_regex: + - "No limit defined, adding default limit of \\[.*\\]" + esql.query: + body: + query: 'FROM test2 | STATS avg = avg(agg_metric) | KEEP avg' + + - length: {values: 1} + - length: {values.0: 1} + - match: {columns.0.name: "avg"} + - match: {columns.0.type: "double"} + - match: {values.0.0: 4.904761904761905}