diff --git a/docs/changelog/138647.yaml b/docs/changelog/138647.yaml new file mode 100644 index 0000000000000..66c2320c7b482 --- /dev/null +++ b/docs/changelog/138647.yaml @@ -0,0 +1,6 @@ +pr: 138647 +summary: Run aggregations on aggregate metric double with default metric +area: ES|QL +type: feature +issues: + - 136297 diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java index a60f125884816..8071e3ff3762d 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NumberFieldMapper.java @@ -2120,6 +2120,7 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) { return switch (cfg.function()) { case MV_MAX -> type.blockLoaderFromDocValuesMvMax(name()); case MV_MIN -> type.blockLoaderFromDocValuesMvMin(name()); + case AMD_COUNT, AMD_DEFAULT, AMD_MAX, AMD_MIN, AMD_SUM -> type.blockLoaderFromDocValues(name()); default -> throw new UnsupportedOperationException("unknown fusion config [" + cfg.function() + "]"); }; } @@ -2140,7 +2141,7 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) { public boolean supportsBlockLoaderConfig(BlockLoaderFunctionConfig config, FieldExtractPreference preference) { if (hasDocValues() && (preference != FieldExtractPreference.STORED || isSyntheticSource)) { return switch (config.function()) { - case MV_MAX, MV_MIN -> true; + case AMD_MIN, AMD_MAX, AMD_SUM, AMD_COUNT, AMD_DEFAULT, MV_MAX, MV_MIN -> true; default -> false; }; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/BlockLoaderFunctionConfig.java b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/BlockLoaderFunctionConfig.java index 5dffee58b5583..71d96378e5ca1 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/blockloader/BlockLoaderFunctionConfig.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/blockloader/BlockLoaderFunctionConfig.java @@ -46,6 +46,7 @@ public int hashCode() { enum Function { AMD_COUNT, + AMD_DEFAULT, AMD_MAX, AMD_MIN, AMD_SUM, diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleArrayBlock.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleArrayBlock.java index ef2540f8516a1..29816f74756a7 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleArrayBlock.java @@ -353,6 +353,7 @@ public Block getMetricBlock(int index) { @Override public String toString() { String valuesString = Stream.of(AggregateMetricDoubleBlockBuilder.Metric.values()) + .filter(metric -> metric != AggregateMetricDoubleBlockBuilder.Metric.DEFAULT) .map(metric -> metric.getLabel() + "=" + getMetricBlock(metric.getIndex())) .collect(Collectors.joining(", ", "[", "]")); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleBlockBuilder.java index 0f41b1646f658..dbfd8b2e6619a 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleBlockBuilder.java @@ -186,7 +186,8 @@ public enum Metric { MIN(0, "min"), MAX(1, "max"), SUM(2, "sum"), - COUNT(3, "value_count"); + COUNT(3, "value_count"), + DEFAULT(4, "default"); private final int index; private final String label; @@ -210,6 +211,7 @@ public static Metric indexToMetric(int i) { case 1 -> MAX; case 2 -> SUM; case 3 -> COUNT; + case 4 -> DEFAULT; default -> null; }; } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec index d51ad47b95cbb..ec1c2e76ccd86 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec @@ -1138,3 +1138,59 @@ max_deriv:double | max_rate:double | time_bucket:date_nanos | cluster:keyword 16.0871 | 11.8608 | 2024-05-10T00:15:00.000Z | prod 10.2206 | 6.9807 | 2024-05-10T00:20:00.000Z | prod ; + +DefaultMetricWithFrom +required_capability: ts_command_v0 +required_capability: aggregate_metric_double_default_metric +FROM k8s-downsampled +| STATS max = max(network.eth0.tx), std_dev = ROUND(STD_DEV(network.eth0.tx), 7) by pod +| sort pod +; + +max:double | std_dev:double | pod:keyword +1060.0 | 331.5018947 | one +824.0 | 151.9744943 | three +1419.0 | 364.4118888 | two +; + +DefaultMetricWithTS +required_capability: ts_command_v0 +required_capability: aggregate_metric_double_default_metric +TS k8s-downsampled +| STATS max = max(network.eth0.tx), std_dev = ROUND(STD_DEV(network.eth0.tx), 7) by pod +| sort pod +; + +max:double | std_dev:double | pod:keyword +1060.0 | 355.3786713 | one +824.0 | 180.6918802 | three +815.0 | 102.3469046 | two +; + +DefaultMetricWithFromImplicitCasting +required_capability: ts_command_v0 +required_capability: aggregate_metric_double_default_metric +FROM k8s* +| STATS min = min(network.eth0.tx), std_dev = ROUND(STD_DEV(network.eth0.tx), 7) by pod +| sort pod +; + +min:double | std_dev:double | pod:keyword +18.0 | 380.8643432 | one +48.0 | 340.5483293 | three +20.0 | 431.2070165 | two +; + +DefaultMetricWithTSImplicitCasting +required_capability: ts_command_v0 +required_capability: aggregate_metric_double_default_metric +TS k8s* +| STATS min = min(network.eth0.tx), std_dev = ROUND(STD_DEV(network.eth0.tx), 7) by pod +| sort pod +; + +min:double | std_dev:double | pod:keyword +193.0 | 427.9853269 | one +715.0 | 208.6955678 | three +602.0 | 375.7757842 | two +; 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 4a71a3e313dea..bdc5c12c3a7e1 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 @@ -959,6 +959,11 @@ public enum Cap { */ AGGREGATE_METRIC_DOUBLE_V0, + /** + * Support running all aggregations on aggregate_metric_double using the default metric + */ + AGGREGATE_METRIC_DOUBLE_DEFAULT_METRIC, + /** * Support change point detection "CHANGE_POINT". */ diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 30d7fd3473968..262b8a9b8fd50 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -55,6 +55,7 @@ import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.expression.NamedExpressions; import org.elasticsearch.xpack.esql.expression.UnresolvedNamePattern; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition; import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction; @@ -75,11 +76,13 @@ import org.elasticsearch.xpack.esql.expression.function.aggregate.Sum; import org.elasticsearch.xpack.esql.expression.function.aggregate.SumOverTime; import org.elasticsearch.xpack.esql.expression.function.aggregate.SummationMode; +import org.elasticsearch.xpack.esql.expression.function.aggregate.TimeSeriesAggregateFunction; import org.elasticsearch.xpack.esql.expression.function.aggregate.Values; import org.elasticsearch.xpack.esql.expression.function.grouping.GroupingFunction; import org.elasticsearch.xpack.esql.expression.function.inference.CompletionFunction; import org.elasticsearch.xpack.esql.expression.function.inference.InferenceFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; +import org.elasticsearch.xpack.esql.expression.function.scalar.UnaryScalarFunction; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Case; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest; import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Least; @@ -95,10 +98,12 @@ import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToLong; import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToString; import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToUnsignedLong; +import org.elasticsearch.xpack.esql.expression.function.scalar.multivalue.MvCount; import org.elasticsearch.xpack.esql.expression.function.scalar.nulls.Coalesce; import org.elasticsearch.xpack.esql.expression.function.vector.VectorFunction; import org.elasticsearch.xpack.esql.expression.predicate.Predicates; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.DateTimeArithmeticOperation; +import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Div; import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.EsqlArithmeticOperation; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.EsqlBinaryComparison; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.In; @@ -224,6 +229,7 @@ public class Analyzer extends ParameterizedRuleExecutor("Finish Analysis", Limiter.ONCE, new AddImplicitLimit(), new AddImplicitForkLimit(), new UnionTypesCleanup()) @@ -2242,8 +2248,13 @@ private static void typeResolutions( */ private static class ImplicitCastAggregateMetricDoubles extends Rule { + private boolean isTimeSeries = false; + @Override public LogicalPlan apply(LogicalPlan plan) { + Holder indexMode = new Holder<>(IndexMode.STANDARD); + plan.forEachUp(EsRelation.class, esRelation -> { indexMode.set(esRelation.indexMode()); }); + isTimeSeries = indexMode.get() == IndexMode.TIME_SERIES; return plan.transformUp(Aggregate.class, p -> p.childrenResolved() == false ? p : doRule(p)); } @@ -2251,35 +2262,13 @@ private LogicalPlan doRule(Aggregate plan) { Map unionFields = new HashMap<>(); Holder aborted = new Holder<>(Boolean.FALSE); var newPlan = plan.transformExpressionsOnly(AggregateFunction.class, aggFunc -> { - if (aggFunc.field() instanceof FieldAttribute fa && fa.field() instanceof InvalidMappedField mtf) { - if (mtf.types().contains(AGGREGATE_METRIC_DOUBLE) == false - || mtf.types().stream().allMatch(f -> f == AGGREGATE_METRIC_DOUBLE || f.isNumeric()) == false) { - aborted.set(Boolean.TRUE); - return aggFunc; - } - Map typeConverters = typeConverters(aggFunc, fa, mtf); - if (typeConverters == null) { - aborted.set(Boolean.TRUE); - return aggFunc; - } - var newField = unionFields.computeIfAbsent( - Attribute.rawTemporaryName(fa.name(), aggFunc.functionName(), aggFunc.sourceText()), - newName -> new FieldAttribute( - fa.source(), - fa.parentName(), - fa.qualifier(), - newName, - MultiTypeEsField.resolveFrom(mtf, typeConverters), - fa.nullable(), - null, - true - ) - ); - List children = new ArrayList<>(aggFunc.children()); - children.set(0, newField); - return aggFunc.replaceChildren(children); + Expression child; + if (aggFunc.field() instanceof ToAggregateMetricDouble toAMD) { + child = tryToTransformFunction(aggFunc, toAMD.field(), aborted, unionFields); + } else { + child = tryToTransformFunction(aggFunc, aggFunc.field(), aborted, unionFields); } - return aggFunc; + return child; }); if (unionFields.isEmpty() || aborted.get()) { return plan; @@ -2287,27 +2276,77 @@ private LogicalPlan doRule(Aggregate plan) { return ResolveUnionTypes.addGeneratedFieldsToEsRelations(newPlan, unionFields.values().stream().toList()); } - private Map typeConverters(AggregateFunction aggFunc, FieldAttribute fa, InvalidMappedField mtf) { - var metric = getMetric(aggFunc); - if (metric == null) { - return null; + private Expression tryToTransformFunction( + AggregateFunction aggFunc, + Expression field, + Holder aborted, + Map unionFields + ) { + if (field instanceof FieldAttribute fa && fa.field() instanceof InvalidMappedField imf) { + if (imf.types().contains(AGGREGATE_METRIC_DOUBLE) == false + || imf.types().stream().allMatch(f -> f == AGGREGATE_METRIC_DOUBLE || f.isNumeric()) == false) { + aborted.set(Boolean.TRUE); + return aggFunc; + } + + // break down Avg and AvgOverTime so we grab the correct submetrics + if (aggFunc instanceof Avg avg) { + return new Div( + aggFunc.source(), + new Sum(aggFunc.source(), field, aggFunc.filter(), aggFunc.window(), avg.summationMode()), + new Count(aggFunc.source(), field, aggFunc.filter(), aggFunc.window()) + ); + } + if (aggFunc instanceof AvgOverTime) { + return new Div( + aggFunc.source(), + new SumOverTime(aggFunc.source(), field, aggFunc.filter(), aggFunc.window()), + new CountOverTime(aggFunc.source(), field, aggFunc.filter(), aggFunc.window()) + ); + } + + Map typeConverters = typeConverters(aggFunc, fa, imf); + var newField = unionFields.computeIfAbsent( + Attribute.rawTemporaryName(fa.name(), aggFunc.functionName(), aggFunc.sourceText()), + newName -> new FieldAttribute( + fa.source(), + fa.parentName(), + fa.qualifier(), + newName, + MultiTypeEsField.resolveFrom(imf, typeConverters), + fa.nullable(), + null, + true + ) + ); + List children = new ArrayList<>(aggFunc.children()); + children.set(0, newField); + // break down Count so we compute the sum of the count submetrics, rather than the number of documents present + if (aggFunc instanceof Count) { + return new Sum(aggFunc.source(), children.getFirst()); + } + if (aggFunc instanceof CountOverTime) { + return new SumOverTime(aggFunc.source(), children.getFirst(), aggFunc.filter(), aggFunc.window()); + } + return aggFunc.replaceChildren(children); } + return aggFunc; + } + + private Map typeConverters(AggregateFunction aggFunc, FieldAttribute fa, InvalidMappedField mtf) { + var metric = getMetric(aggFunc, isTimeSeries); Map typeConverter = new HashMap<>(); for (DataType type : mtf.types()) { final ConvertFunction convert; - // Counting on aggregate metric double has unique behavior in that we cannot just provide the number of - // documents, instead we have to look inside the aggregate metric double's count field and sum those together. - // Grabbing the count value with FromAggregateMetricDouble the same way we do with min/max/sum would result in - // a single Int field, and incorrectly be treated as 1 document (instead of however many originally went into - // the aggregate metric double). - if (metric == AggregateMetricDoubleBlockBuilder.Metric.COUNT) { - convert = new ToAggregateMetricDouble(fa.source(), fa); - } else if (type == AGGREGATE_METRIC_DOUBLE) { + if (type == AGGREGATE_METRIC_DOUBLE) { convert = FromAggregateMetricDouble.withMetric(aggFunc.source(), fa, metric); - } else if (type.isNumeric()) { - convert = new ToDouble(fa.source(), fa); + } else if (metric == AggregateMetricDoubleBlockBuilder.Metric.COUNT) { + // we have a numeric on hand so calculate MvCount on it so we can plug it into Sum(metric.count) + var tempConvert = new MvCount(aggFunc.source(), fa); + typeConverter.put(type.typeName(), countConvert(tempConvert, fa.source(), type, mtf)); + continue; } else { - return null; + convert = new ToDouble(fa.source(), fa); } Expression expression = ResolveUnionTypes.typeSpecificConvert(convert, fa.source(), type, mtf); typeConverter.put(type.typeName(), expression); @@ -2315,7 +2354,33 @@ private Map typeConverters(AggregateFunction aggFunc, FieldA return typeConverter; } - private static AggregateMetricDoubleBlockBuilder.Metric getMetric(AggregateFunction aggFunc) { + private Expression countConvert(UnaryScalarFunction convert, Source source, DataType type, InvalidMappedField imf) { + EsField field = new EsField(imf.getName(), type, imf.getProperties(), imf.isAggregatable(), imf.getTimeSeriesFieldType()); + FieldAttribute originalFieldAttr = (FieldAttribute) convert.field(); + FieldAttribute resolvedAttr = new FieldAttribute( + source, + originalFieldAttr.parentName(), + originalFieldAttr.qualifier(), + originalFieldAttr.name(), + field, + originalFieldAttr.nullable(), + originalFieldAttr.id(), + true + ); + List children = new ArrayList<>(convert.children()); + children.set(0, resolvedAttr); + return convert.replaceChildren(children); + } + + private static boolean hasNativeSupport(AggregateFunction aggFunc, boolean isTimeSeries) { + return aggFunc instanceof AggregateMetricDoubleNativeSupport + && (isTimeSeries == false || aggFunc instanceof TimeSeriesAggregateFunction); + } + + private static AggregateMetricDoubleBlockBuilder.Metric getMetric(AggregateFunction aggFunc, boolean isTimeSeries) { + if (hasNativeSupport(aggFunc, isTimeSeries) == false) { + return AggregateMetricDoubleBlockBuilder.Metric.DEFAULT; + } if (aggFunc instanceof Max || aggFunc instanceof MaxOverTime) { return AggregateMetricDoubleBlockBuilder.Metric.MAX; } @@ -2328,16 +2393,47 @@ private static AggregateMetricDoubleBlockBuilder.Metric getMetric(AggregateFunct if (aggFunc instanceof Count || aggFunc instanceof CountOverTime) { return AggregateMetricDoubleBlockBuilder.Metric.COUNT; } - if (aggFunc instanceof Avg || aggFunc instanceof AvgOverTime) { - return AggregateMetricDoubleBlockBuilder.Metric.COUNT; - } if (aggFunc instanceof Present || aggFunc instanceof PresentOverTime) { return AggregateMetricDoubleBlockBuilder.Metric.COUNT; } if (aggFunc instanceof Absent || aggFunc instanceof AbsentOverTime) { return AggregateMetricDoubleBlockBuilder.Metric.COUNT; } - return null; + return AggregateMetricDoubleBlockBuilder.Metric.DEFAULT; + } + } + + /** + * Takes aggregation functions that don't natively support AggregateMetricDouble (i.e. aggregations other than + * min, max, sum, count, avg) that receive an AggregateMetricDouble as input, and inserts a call to + * FROM_AGGREGATE_METRIC_DOUBLE to fetch the DEFAULT metric. + */ + private static class InsertFromAggregateMetricDouble extends Rule { + @Override + public LogicalPlan apply(LogicalPlan plan) { + return plan.transformUp(Aggregate.class, p -> p.childrenResolved() == false ? p : doRule(p)); + } + + private LogicalPlan doRule(Aggregate plan) { + Holder indexMode = new Holder<>(IndexMode.STANDARD); + plan.forEachUp(EsRelation.class, esRelation -> { indexMode.set(esRelation.indexMode()); }); + final boolean isTimeSeries = indexMode.get() == IndexMode.TIME_SERIES; + return plan.transformExpressionsOnly(AggregateFunction.class, aggFunc -> { + if (ImplicitCastAggregateMetricDoubles.hasNativeSupport(aggFunc, isTimeSeries)) { + return aggFunc; + } + if (aggFunc.field() instanceof FieldAttribute fa && fa.field().getDataType() == AGGREGATE_METRIC_DOUBLE) { + Expression newField = FromAggregateMetricDouble.withMetric( + fa.source(), + fa, + AggregateMetricDoubleBlockBuilder.Metric.DEFAULT + ); + List children = new ArrayList<>(aggFunc.children()); + children.set(0, newField); + return aggFunc.replaceChildren(children); + } + return aggFunc; + }); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/AggregateMetricDoubleNativeSupport.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/AggregateMetricDoubleNativeSupport.java new file mode 100644 index 0000000000000..2433b34d9ee40 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/AggregateMetricDoubleNativeSupport.java @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.expression.function; + +/** + * Marker interface to say that an aggregate funciton supports aggregate_metric_double natively + * i.e. max, min, sum, count, avg + */ +public interface AggregateMetricDoubleNativeSupport {} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Absent.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Absent.java index 93e6d4d3be64f..b1f6d9ed1ac3f 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Absent.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Absent.java @@ -16,6 +16,7 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -35,7 +36,7 @@ * The function that checks for the absence of a field in the output result. * An absence means that the input expression does not yield a non-null value. */ -public class Absent extends AggregateFunction implements SurrogateExpression { +public class Absent extends AggregateFunction implements SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Absent", Absent::new); @FunctionInfo( diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbsentOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbsentOverTime.java index 2546b54f367b3..c981b22a09684 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbsentOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbsentOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -28,7 +29,7 @@ /** * Similar to {@link Absent}, but it is used to check the absence of values over a time series in the given field. */ -public class AbsentOverTime extends TimeSeriesAggregateFunction { +public class AbsentOverTime extends TimeSeriesAggregateFunction implements AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "AbsentOverTime", 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 770f8c8275a6e..472e9fbd817d2 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 @@ -16,6 +16,7 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; @@ -33,7 +34,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.EXPONENTIAL_HISTOGRAM; -public class Avg extends AggregateFunction implements SurrogateExpression { +public class Avg extends AggregateFunction implements SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Avg", Avg::new); private final Expression summationMode; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java index aa61a0d64e29c..f37320106e2c5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AvgOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -31,7 +32,7 @@ /** * Similar to {@link Avg}, but it is used to calculate the average value over a time series of values from the given field. */ -public class AvgOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { +public class AvgOverTime extends TimeSeriesAggregateFunction implements OptionalArgument, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "AvgOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Count.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Count.java index e047e78fea4ff..d810fdb2eb852 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Count.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Count.java @@ -20,6 +20,7 @@ import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; @@ -37,7 +38,7 @@ import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isType; -public class Count extends AggregateFunction implements ToAggregator, SurrogateExpression { +public class Count extends AggregateFunction implements ToAggregator, SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Count", Count::new); @FunctionInfo( diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java index ed37ccdb273f1..1ec8afeecaaf0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -31,7 +32,7 @@ /** * Similar to {@link Count}, but it is used to calculate the count of values over a time series from the given field. */ -public class CountOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { +public class CountOverTime extends TimeSeriesAggregateFunction implements OptionalArgument, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "CountOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Max.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Max.java index f930cae4d2905..44ab9edceda38 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Max.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Max.java @@ -26,6 +26,7 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; @@ -43,7 +44,7 @@ import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; -public class Max extends AggregateFunction implements ToAggregator, SurrogateExpression { +public class Max extends AggregateFunction implements ToAggregator, SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Max", Max::new); private static final Map> SUPPLIERS = Map.ofEntries( diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java index 0258f8600c5c2..fa95e9759a092 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -31,7 +32,7 @@ /** * Similar to {@link Max}, but it is used to calculate the maximum value over a time series of values from the given field. */ -public class MaxOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { +public class MaxOverTime extends TimeSeriesAggregateFunction implements OptionalArgument, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "MaxOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Min.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Min.java index f16f4e7f34e3f..ae8b8aa4b8215 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Min.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Min.java @@ -26,6 +26,7 @@ import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; @@ -43,7 +44,7 @@ import static java.util.Collections.emptyList; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; -public class Min extends AggregateFunction implements ToAggregator, SurrogateExpression { +public class Min extends AggregateFunction implements ToAggregator, SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Min", Min::new); private static final Map> SUPPLIERS = Map.ofEntries( diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java index 1db4b68dcff42..1c3497c37b23c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -31,7 +32,7 @@ /** * Similar to {@link Min}, but it is used to calculate the minimum value over a time series of values from the given field. */ -public class MinOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { +public class MinOverTime extends TimeSeriesAggregateFunction implements OptionalArgument, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "MinOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Present.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Present.java index 3d0b04f6f26f0..4c151217e396a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Present.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Present.java @@ -17,6 +17,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -36,7 +37,7 @@ * The function that checks for the presence of a field in the output result. * Presence means that the input expression yields any non-null value. */ -public class Present extends AggregateFunction implements ToAggregator { +public class Present extends AggregateFunction implements ToAggregator, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Present", Present::new); @FunctionInfo( diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/PresentOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/PresentOverTime.java index 79d73ddeba5fc..e44721477f63a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/PresentOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/PresentOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -30,7 +31,7 @@ /** * Similar to {@link Present}, but it is used to check the presence of values over a time series in the given field. */ -public class PresentOverTime extends TimeSeriesAggregateFunction { +public class PresentOverTime extends TimeSeriesAggregateFunction implements AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "PresentOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Sum.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Sum.java index ef6a73287a1b9..24a3066de02c4 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Sum.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/Sum.java @@ -23,6 +23,7 @@ import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.expression.SurrogateExpression; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; @@ -47,7 +48,7 @@ /** * Sum all values of a field in matching documents. */ -public class Sum extends NumericAggregate implements SurrogateExpression { +public class Sum extends NumericAggregate implements SurrogateExpression, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "Sum", Sum::new); private final Expression summationMode; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java index 1bc5211535eaf..f42d02a05fe5b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java @@ -14,6 +14,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport; import org.elasticsearch.xpack.esql.expression.function.Example; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesTo; import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; @@ -31,7 +32,7 @@ /** * Similar to {@link Sum}, but it is used to calculate the sum of values over a time series from the given field. */ -public class SumOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { +public class SumOverTime extends TimeSeriesAggregateFunction implements OptionalArgument, AggregateMetricDoubleNativeSupport { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "SumOverTime", diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromAggregateMetricDouble.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromAggregateMetricDouble.java index 261190b0cedea..68d7a56bda121 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromAggregateMetricDouble.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/FromAggregateMetricDouble.java @@ -29,6 +29,7 @@ import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; +import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.blockloader.BlockLoaderExpression; @@ -45,6 +46,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.DOUBLE; import static org.elasticsearch.xpack.esql.core.type.DataType.INTEGER; +import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.NULL; public class FromAggregateMetricDouble extends EsqlScalarFunction implements ConvertFunction, BlockLoaderExpression { @@ -62,7 +64,7 @@ public FromAggregateMetricDouble( Source source, @Param( name = "aggregate_metric_double", - type = { "aggregate_metric_double" }, + type = { "aggregate_metric_double", "int", "double", "long" }, description = "Aggregate double metric to convert." ) Expression field, @Param(name = "subfieldIndex", type = "int", description = "Index of subfield") Expression subfieldIndex @@ -194,12 +196,14 @@ public Expression field() { @Override public Set supportedTypes() { - return Set.of(AGGREGATE_METRIC_DOUBLE); + return Set.of(AGGREGATE_METRIC_DOUBLE, INTEGER, LONG, DOUBLE); } @Override public PushedBlockLoaderExpression tryPushToFieldLoading(SearchStats stats) { - if (field() instanceof FieldAttribute f && f.dataType() == AGGREGATE_METRIC_DOUBLE) { + if (field() instanceof FieldAttribute f + && f.dataType() == AGGREGATE_METRIC_DOUBLE + && (f.field() instanceof MultiTypeEsField) == false) { var folded = subfieldIndex.fold(FoldContext.small()); if (folded == null) { throw new IllegalArgumentException("Subfield Index was null"); @@ -210,6 +214,7 @@ public PushedBlockLoaderExpression tryPushToFieldLoading(SearchStats stats) { case MAX -> BlockLoaderFunctionConfig.Function.AMD_MAX; case SUM -> BlockLoaderFunctionConfig.Function.AMD_SUM; case COUNT -> BlockLoaderFunctionConfig.Function.AMD_COUNT; + case DEFAULT -> BlockLoaderFunctionConfig.Function.AMD_DEFAULT; case null -> throw new IllegalArgumentException("Received invalid subfield index [" + subfield + "]."); }; return new PushedBlockLoaderExpression(f, new BlockLoaderFunctionConfig.JustFunction(functionConfig)); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java index 3faf6c6bd732f..8483dbeb088e8 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/EsPhysicalOperationProviders.java @@ -79,6 +79,7 @@ import org.elasticsearch.xpack.esql.core.type.KeywordEsField; import org.elasticsearch.xpack.esql.core.type.MultiTypeEsField; import org.elasticsearch.xpack.esql.core.type.PotentiallyUnmappedKeywordEsField; +import org.elasticsearch.xpack.esql.expression.function.blockloader.BlockLoaderExpression; import org.elasticsearch.xpack.esql.expression.function.scalar.EsqlScalarFunction; import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec; import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec.Sort; @@ -89,6 +90,7 @@ import org.elasticsearch.xpack.esql.planner.LocalExecutionPlanner.LocalExecutionPlannerContext; import org.elasticsearch.xpack.esql.planner.LocalExecutionPlanner.PhysicalOperation; import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; +import org.elasticsearch.xpack.esql.stats.SearchStats; import java.io.IOException; import java.util.ArrayList; @@ -214,9 +216,16 @@ private BlockLoader getBlockLoaderFor(int shardId, Attribute attr, MappedFieldTy // Use the fully qualified name `cluster:index-name` because multiple types are resolved on coordinator with the cluster prefix String indexName = shardContext.ctx.getFullyQualifiedIndex().getName(); Expression conversion = unionTypes.getConversionExpressionForIndex(indexName); - return conversion == null - ? BlockLoader.CONSTANT_NULLS - : new TypeConvertingBlockLoader(blockLoader, (EsqlScalarFunction) conversion); + if (conversion == null) { + return BlockLoader.CONSTANT_NULLS; + } + if (conversion instanceof BlockLoaderExpression ble) { + BlockLoaderExpression.PushedBlockLoaderExpression e = ble.tryPushToFieldLoading(SearchStats.EMPTY); + if (e != null) { + return shardContext.blockLoader(fieldName, isUnsupported, fieldExtractPreference, e.config()); + } + } + return new TypeConvertingBlockLoader(blockLoader, (EsqlScalarFunction) conversion); } return blockLoader; } 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 bd43ff784438b..059699a7ae6f5 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 @@ -4712,13 +4712,10 @@ public void testImplicitCastingForAggregateMetricDouble() { ), TEST_VERIFIER ); - var e = expectThrows(VerificationException.class, () -> analyze(""" - from k8s* | stats std_dev(metric_field) - """, analyzer)); - assertThat( - e.getMessage(), - containsString("Cannot use field [metric_field] due to ambiguities being mapped as [2] incompatible types") - ); + var stddevPlan = analyze(""" + from k8s* | stats std_dev = std_dev(metric_field) + """, analyzer); + assertProjection(stddevPlan, "std_dev"); var plan = analyze(""" from k8s* | stats max = max(metric_field), diff --git a/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldMapper.java b/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldMapper.java index de13cdd9996ff..dc0ca31038d11 100644 --- a/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldMapper.java +++ b/x-pack/plugin/mapper-aggregate-metric/src/main/java/org/elasticsearch/xpack/aggregatemetric/mapper/AggregateMetricDoubleFieldMapper.java @@ -508,6 +508,7 @@ public BlockLoader blockLoader(BlockLoaderContext blContext) { case AMD_MAX -> Metric.max; case AMD_MIN -> Metric.min; case AMD_SUM -> Metric.sum; + case AMD_DEFAULT -> defaultMetric; default -> null; }; if (metric == null) { @@ -531,7 +532,7 @@ private BlockLoader getIndividualBlockLoader(Metric metric) { @Override public boolean supportsBlockLoaderConfig(BlockLoaderFunctionConfig config, FieldExtractPreference preference) { return switch (config.function()) { - case AMD_MIN, AMD_MAX, AMD_SUM, AMD_COUNT -> true; + case AMD_MIN, AMD_MAX, AMD_SUM, AMD_COUNT, AMD_DEFAULT -> true; default -> false; }; } 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 e4f72810e19d7..aec7b7bbd1bcc 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 @@ -864,3 +864,31 @@ error on rename timestamp: - match: { error.reason: '/(verification_exception:\ )?Found\ 1\ problem\nline\ 3:13:\ \[rate\(k8s.pod.network.tx\)\]\ requires\ the\ \[@timestamp\]\ field,\ which\ was\ either\ not\ present\ in\ the\ source\ index,\ or\ has\ been\ dropped\ or\ renamed/' } +--- +Stats using min as default metric for aggregate metric double: + - requires: + test_runner_features: [capabilities] + capabilities: + - method: POST + path: /_query + parameters: [] + capabilities: [aggregate_metric_double_default_metric] + reason: "Default metric" + - do: + allowed_warnings_regex: + - "No limit defined, adding default limit of \\[.*\\]" + esql.query: + body: + query: 'FROM test3 | STATS max(agg_metric), min(agg_metric), std_dev = round(std_dev(agg_metric), 5)' + + - length: {values: 1} + - length: {values.0: 3} + - match: {columns.0.name: "max(agg_metric)"} + - match: {columns.0.type: "double"} + - match: {columns.1.name: "min(agg_metric)"} + - match: {columns.1.type: "double"} + - match: {columns.2.name: "std_dev"} + - match: {columns.2.type: "double"} + - match: {values.0.0: 17.0} + - match: {values.0.1: -3.0} + - match: {values.0.2: 2.62467}