[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.
This commit is contained in:
Larisa Motova 2025-07-02 11:11:36 -10:00 committed by GitHub
parent 5d0c5e02bd
commit e149db0eec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 12 deletions

View File

@ -0,0 +1,5 @@
pr: 130421
summary: Support avg on aggregate metric double
area: ES|QL
type: bug
issues: []

View File

@ -1220,7 +1220,12 @@ public class EsqlCapabilities {
/**
* FUSE command
*/
FUSE(Build.current().isSnapshot());
FUSE(Build.current().isSnapshot()),
/**
* Support avg with aggregate metric doubles
*/
AGGREGATE_METRIC_DOUBLE_AVG(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG);
private final boolean enabled;

View File

@ -28,6 +28,7 @@ import java.util.List;
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 class Avg extends AggregateFunction implements SurrogateExpression {
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 class Avg extends AggregateFunction implements SurrogateExpression {
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 class Avg extends AggregateFunction implements SurrogateExpression {
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());
}
}

View File

@ -2006,7 +2006,7 @@ public class AnalyzerTests extends ESTestCase {
| 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 class AnalyzerTests extends ESTestCase {
| 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]

View File

@ -359,7 +359,7 @@ public class VerifierTests extends ESTestCase {
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")
);

View File

@ -32,6 +32,13 @@ public class AvgErrorTests extends ErrorsForCasesWithoutExamplesTestCase {
@Override
protected Matcher<String> expectedTypeErrorMatcher(List<Set<DataType>> validPerPosition, List<DataType> 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"
)
);
}
}

View File

@ -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}