diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/to_aggregate_metric_double.md b/docs/reference/query-languages/esql/_snippets/functions/description/to_aggregate_metric_double.md
new file mode 100644
index 0000000000000..144c427ff07cb
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/to_aggregate_metric_double.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Encode a numeric to an aggregate_metric_double.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/to_aggregate_metric_double.md b/docs/reference/query-languages/esql/_snippets/functions/layout/to_aggregate_metric_double.md
new file mode 100644
index 0000000000000..52439c620383c
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/to_aggregate_metric_double.md
@@ -0,0 +1,20 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+## `TO_AGGREGATE_METRIC_DOUBLE` [esql-to_aggregate_metric_double]
+
+**Syntax**
+
+:::{image} ../../../images/functions/to_aggregate_metric_double.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/to_aggregate_metric_double.md
+:::
+
+:::{include} ../description/to_aggregate_metric_double.md
+:::
+
+:::{include} ../types/to_aggregate_metric_double.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/to_aggregate_metric_double.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/to_aggregate_metric_double.md
new file mode 100644
index 0000000000000..5204e46661d48
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/to_aggregate_metric_double.md
@@ -0,0 +1,7 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`number`
+: Input value. The input can be a single-valued column or an expression.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/to_aggregate_metric_double.md b/docs/reference/query-languages/esql/_snippets/functions/types/to_aggregate_metric_double.md
new file mode 100644
index 0000000000000..cfe51aa1472c1
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/to_aggregate_metric_double.md
@@ -0,0 +1,8 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| number | result |
+| --- | --- |
+aggregate_metric_double
+
diff --git a/docs/reference/query-languages/esql/images/functions/to_aggregate_metric_double.svg b/docs/reference/query-languages/esql/images/functions/to_aggregate_metric_double.svg
new file mode 100644
index 0000000000000..12550278d6e36
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/to_aggregate_metric_double.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/to_aggregate_metric_double.json b/docs/reference/query-languages/esql/kibana/definition/functions/to_aggregate_metric_double.json
new file mode 100644
index 0000000000000..0336ca89e19e9
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/to_aggregate_metric_double.json
@@ -0,0 +1,9 @@
+{
+ "comment" : "This is generated by ESQL’s AbstractFunctionTestCase. Do no edit it. See ../README.md for how to regenerate it.",
+ "type" : "scalar",
+ "name" : "to_aggregate_metric_double",
+ "description" : "Encode a numeric to an aggregate_metric_double.",
+ "signatures" : [ ],
+ "preview" : false,
+ "snapshot_only" : false
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/to_aggregate_metric_double.md b/docs/reference/query-languages/esql/kibana/docs/functions/to_aggregate_metric_double.md
new file mode 100644
index 0000000000000..0dea481d1a773
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/to_aggregate_metric_double.md
@@ -0,0 +1,7 @@
+
+
+### TO_AGGREGATE_METRIC_DOUBLE
+Encode a numeric to an aggregate_metric_double.
+
diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java
index 3ace93ece62f0..c9d8391bd0ee8 100644
--- a/server/src/main/java/org/elasticsearch/TransportVersions.java
+++ b/server/src/main/java/org/elasticsearch/TransportVersions.java
@@ -185,6 +185,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_THREAD_NAME_IN_DRIVER_PROFILE = def(9_027_0_00);
public static final TransportVersion INFERENCE_CONTEXT = def(9_028_0_00);
public static final TransportVersion ML_INFERENCE_DEEPSEEK = def(9_029_00_0);
+ public static final TransportVersion ESQL_AGGREGATE_METRIC_DOUBLE_LITERAL = def(9_030_0_00);
/*
* STOP! READ THIS FIRST! No, really,
diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java
index 4fdc32ea41e12..febe76f3da33d 100644
--- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java
+++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/type/DataType.java
@@ -557,7 +557,6 @@ public static boolean isRepresentable(DataType t) {
&& t != SOURCE
&& t != HALF_FLOAT
&& t != PARTIAL_AGG
- && t != AGGREGATE_METRIC_DOUBLE
&& t.isCounter() == false;
}
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 4f1c6faf520be..af76ba6c96b49 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
@@ -7,9 +7,17 @@
package org.elasticsearch.compute.data;
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.TransportVersions;
+import org.elasticsearch.common.io.stream.GenericNamedWriteable;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.core.Releasables;
import org.elasticsearch.index.mapper.BlockLoader;
+import java.io.IOException;
+
public class AggregateMetricDoubleBlockBuilder extends AbstractBlockBuilder implements BlockLoader.AggregateMetricDoubleBuilder {
private DoubleBlockBuilder minBuilder;
@@ -161,11 +169,107 @@ public String getLabel() {
}
}
- public record AggregateMetricDoubleLiteral(Double min, Double max, Double sum, Integer count) {
+ public record AggregateMetricDoubleLiteral(Double min, Double max, Double sum, Integer count) implements GenericNamedWriteable {
public AggregateMetricDoubleLiteral {
min = min.isNaN() ? null : min;
max = max.isNaN() ? null : max;
sum = sum.isNaN() ? null : sum;
}
+
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
+ GenericNamedWriteable.class,
+ "AggregateMetricDoubleLiteral",
+ AggregateMetricDoubleLiteral::new
+ );
+
+ @Override
+ public String getWriteableName() {
+ return "AggregateMetricDoubleLiteral";
+ }
+
+ public AggregateMetricDoubleLiteral(StreamInput input) throws IOException {
+ this(input.readOptionalDouble(), input.readOptionalDouble(), input.readOptionalDouble(), input.readOptionalInt());
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ out.writeOptionalDouble(min);
+ out.writeOptionalDouble(max);
+ out.writeOptionalDouble(sum);
+ out.writeOptionalInt(count);
+ }
+
+ @Override
+ public TransportVersion getMinimalSupportedVersion() {
+ return TransportVersions.ESQL_AGGREGATE_METRIC_DOUBLE_LITERAL;
+ }
+
+ }
+
+ public static class AggregateMetricDoubleVectorBuilder extends AbstractBlockBuilder {
+ private final DoubleBlockBuilder valuesBuilder;
+
+ public AggregateMetricDoubleVectorBuilder(int estimatedSize, BlockFactory blockFactory) {
+ super(blockFactory);
+ valuesBuilder = new DoubleBlockBuilder(estimatedSize, blockFactory);
+ }
+
+ @Override
+ protected int valuesLength() {
+ throw new UnsupportedOperationException("Not available on aggregate_metric_double");
+ }
+
+ @Override
+ protected void growValuesArray(int newSize) {
+ throw new UnsupportedOperationException("Not available on aggregate_metric_double");
+ }
+
+ @Override
+ protected int elementSize() {
+ throw new UnsupportedOperationException("Not available on aggregate_metric_double");
+ }
+
+ @Override
+ public Block.Builder copyFrom(Block block, int beginInclusive, int endExclusive) {
+ // TODO
+ return null;
+ }
+
+ @Override
+ public Block.Builder mvOrdering(Block.MvOrdering mvOrdering) {
+ // TODO
+ return null;
+ }
+
+ public void appendValue(double value) {
+ valuesBuilder.appendDouble(value);
+ }
+
+ @Override
+ public Block build() {
+ Block[] blocks = new Block[4];
+ Block block = null;
+ IntBlock countVector = null;
+ boolean success = false;
+ try {
+ finish();
+ block = valuesBuilder.build();
+ countVector = blockFactory.newConstantIntBlockWith(1, block.getPositionCount());
+ blocks[Metric.MIN.getIndex()] = block;
+ blocks[Metric.MAX.getIndex()] = block;
+ block.incRef();
+ blocks[Metric.SUM.getIndex()] = block;
+ block.incRef();
+ blocks[Metric.COUNT.getIndex()] = countVector;
+ CompositeBlock compositeBlock = new CompositeBlock(blocks);
+ success = true;
+ return compositeBlock;
+ } finally {
+ if (success == false) {
+ Releasables.closeExpectNoException(blocks);
+ }
+ }
+ }
+
}
}
diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java
index 55053f509591d..07939b8848fa0 100644
--- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java
+++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java
@@ -436,6 +436,10 @@ public AggregateMetricDoubleBlockBuilder newAggregateMetricDoubleBlockBuilder(in
return new AggregateMetricDoubleBlockBuilder(estimatedSize, this);
}
+ public AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleVectorBuilder newAggregateMetricDoubleVectorBuilder(int estimatedSize) {
+ return new AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleVectorBuilder(estimatedSize, this);
+ }
+
public final Block newConstantAggregateMetricDoubleBlock(
AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral value,
int positions
diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java
index 8773a3b9785e0..1d6012a8a73de 100644
--- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java
+++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java
@@ -285,7 +285,19 @@ private static Object valueAtOffset(Block block, int offset) {
DocVector v = ((DocBlock) block).asVector();
yield new Doc(v.shards().getInt(offset), v.segments().getInt(offset), v.docs().getInt(offset));
}
- case COMPOSITE -> throw new IllegalArgumentException("can't read values from composite blocks");
+ case COMPOSITE -> {
+ CompositeBlock compositeBlock = (CompositeBlock) block;
+ var minBlock = (DoubleBlock) compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.MIN.getIndex());
+ var maxBlock = (DoubleBlock) compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.MAX.getIndex());
+ var sumBlock = (DoubleBlock) compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.SUM.getIndex());
+ var countBlock = (IntBlock) compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.COUNT.getIndex());
+ yield new AggregateMetricDoubleLiteral(
+ minBlock.getDouble(offset),
+ maxBlock.getDouble(offset),
+ sumBlock.getDouble(offset),
+ countBlock.getInt(offset)
+ );
+ }
case UNKNOWN -> throw new IllegalArgumentException("can't read values from [" + block + "]");
};
}
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
index 0ddb19f8a8216..19e8f0ef36278 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
@@ -20,6 +20,7 @@
import org.elasticsearch.common.regex.Regex;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
+import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder;
import org.elasticsearch.compute.data.BlockFactory;
import org.elasticsearch.compute.data.BlockUtils;
import org.elasticsearch.compute.data.BytesRefBlock;
@@ -788,6 +789,12 @@ public static Literal randomLiteral(DataType type) {
case CARTESIAN_POINT -> CARTESIAN.asWkb(ShapeTestUtils.randomPoint());
case GEO_SHAPE -> GEO.asWkb(GeometryTestUtils.randomGeometry(randomBoolean()));
case CARTESIAN_SHAPE -> CARTESIAN.asWkb(ShapeTestUtils.randomGeometry(randomBoolean()));
+ case AGGREGATE_METRIC_DOUBLE -> new AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral(
+ randomDouble(),
+ randomDouble(),
+ randomDouble(),
+ randomInt()
+ );
case NULL -> null;
case SOURCE -> {
try {
@@ -798,8 +805,9 @@ public static Literal randomLiteral(DataType type) {
throw new UncheckedIOException(e);
}
}
- case UNSUPPORTED, OBJECT, DOC_DATA_TYPE, TSID_DATA_TYPE, PARTIAL_AGG, AGGREGATE_METRIC_DOUBLE ->
- throw new IllegalArgumentException("can't make random values for [" + type.typeName() + "]");
+ case UNSUPPORTED, OBJECT, DOC_DATA_TYPE, TSID_DATA_TYPE, PARTIAL_AGG -> throw new IllegalArgumentException(
+ "can't make random values for [" + type.typeName() + "]"
+ );
}, type);
}
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 96c07f166dc28..ed69880ad03a5 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
@@ -880,7 +880,12 @@ public enum Cap {
/**
* Do {@code TO_LOWER} and {@code TO_UPPER} process all field values?
*/
- TO_LOWER_MV;
+ TO_LOWER_MV,
+
+ /**
+ * Support for to_aggregate_metric_double function
+ */
+ AGGREGATE_METRIC_DOUBLE_CONVERT_TO(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG);
private final boolean enabled;
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
index dba0ec799f312..b93028b3e5897 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/ExpressionWritables.java
@@ -14,6 +14,7 @@
import org.elasticsearch.xpack.esql.expression.function.fulltext.FullTextWritables;
import org.elasticsearch.xpack.esql.expression.function.scalar.ScalarFunctionWritables;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FromBase64;
+import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToAggregateMetricDouble;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToBase64;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToBoolean;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToCartesianPoint;
@@ -180,6 +181,7 @@ public static List unaryScalars() {
entries.add(StY.ENTRY);
entries.add(Tan.ENTRY);
entries.add(Tanh.ENTRY);
+ entries.add(ToAggregateMetricDouble.ENTRY);
entries.add(ToBase64.ENTRY);
entries.add(ToBoolean.ENTRY);
entries.add(ToCartesianPoint.ENTRY);
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
index d88b3fe1a4ade..1d6c4d5344abb 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/EsqlFunctionRegistry.java
@@ -42,6 +42,7 @@
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Greatest;
import org.elasticsearch.xpack.esql.expression.function.scalar.conditional.Least;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.FromBase64;
+import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToAggregateMetricDouble;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToBase64;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToBoolean;
import org.elasticsearch.xpack.esql.expression.function.scalar.convert.ToCartesianPoint;
@@ -376,6 +377,7 @@ private static FunctionDefinition[][] functions() {
// conversion functions
new FunctionDefinition[] {
def(FromBase64.class, FromBase64::new, "from_base64"),
+ def(ToAggregateMetricDouble.class, ToAggregateMetricDouble::new, "to_aggregate_metric_double", "to_aggregatemetricdouble"),
def(ToBase64.class, ToBase64::new, "to_base64"),
def(ToBoolean.class, ToBoolean::new, "to_boolean", "to_bool"),
def(ToCartesianPoint.class, ToCartesianPoint::new, "to_cartesianpoint"),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDouble.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDouble.java
new file mode 100644
index 0000000000000..acd2324aafe98
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDouble.java
@@ -0,0 +1,416 @@
+/*
+ * 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.scalar.convert;
+
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder;
+import org.elasticsearch.compute.data.Block;
+import org.elasticsearch.compute.data.DoubleBlock;
+import org.elasticsearch.compute.data.DoubleVector;
+import org.elasticsearch.compute.data.IntBlock;
+import org.elasticsearch.compute.data.LongBlock;
+import org.elasticsearch.compute.data.Page;
+import org.elasticsearch.compute.data.Vector;
+import org.elasticsearch.compute.operator.DriverContext;
+import org.elasticsearch.compute.operator.EvalOperator;
+import org.elasticsearch.core.Releasables;
+import org.elasticsearch.search.aggregations.metrics.CompensatedSum;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+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.FunctionInfo;
+import org.elasticsearch.xpack.esql.expression.function.Param;
+import org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+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;
+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.UNSIGNED_LONG;
+
+public class ToAggregateMetricDouble extends AbstractConvertFunction {
+
+ private static final Map EVALUATORS = Map.ofEntries(
+ Map.entry(AGGREGATE_METRIC_DOUBLE, (source, fieldEval) -> fieldEval),
+ Map.entry(DOUBLE, DoubleFactory::new),
+ Map.entry(INTEGER, IntFactory::new),
+ Map.entry(LONG, LongFactory::new),
+ Map.entry(UNSIGNED_LONG, UnsignedLongFactory::new)
+ );
+
+ public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
+ Expression.class,
+ "ToAggregateMetricDouble",
+ ToAggregateMetricDouble::new
+ );
+
+ @FunctionInfo(returnType = "aggregate_metric_double", description = "Encode a numeric to an aggregate_metric_double.")
+ public ToAggregateMetricDouble(
+ Source source,
+ @Param(
+ name = "number",
+ type = { "double", "long", "unsigned_long", "integer", "aggregate_metric_double" },
+ description = "Input value. The input can be a single-valued column or an expression."
+ ) Expression field
+ ) {
+ super(source, field);
+ }
+
+ private ToAggregateMetricDouble(StreamInput in) throws IOException {
+ super(in);
+ }
+
+ @Override
+ public String getWriteableName() {
+ return ENTRY.name;
+ }
+
+ @Override
+ protected TypeResolution resolveType() {
+ if (childrenResolved() == false) {
+ return new TypeResolution("Unresolved children");
+ }
+ return isType(
+ field,
+ dt -> dt == DataType.AGGREGATE_METRIC_DOUBLE || dt == DataType.DOUBLE || dt == LONG || dt == INTEGER || dt == UNSIGNED_LONG,
+ sourceText(),
+ DEFAULT,
+ "numeric or aggregate_metric_double"
+ );
+ }
+
+ @Override
+ public DataType dataType() {
+ return AGGREGATE_METRIC_DOUBLE;
+ }
+
+ @Override
+ public Expression replaceChildren(List newChildren) {
+ return new ToAggregateMetricDouble(source(), newChildren.get(0));
+ }
+
+ @Override
+ protected NodeInfo extends Expression> info() {
+ return NodeInfo.create(this, ToAggregateMetricDouble::new, field);
+ }
+
+ @Override
+ protected Map factories() {
+ return EVALUATORS;
+ }
+
+ public static class DoubleFactory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory fieldEvaluator;
+
+ public DoubleFactory(Source source, EvalOperator.ExpressionEvaluator.Factory fieldEvaluator) {
+ this.fieldEvaluator = fieldEvaluator;
+ this.source = source;
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromDoubleEvaluator[" + "field=" + fieldEvaluator + "]";
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator get(DriverContext context) {
+ final EvalOperator.ExpressionEvaluator eval = fieldEvaluator.get(context);
+
+ return new EvalOperator.ExpressionEvaluator() {
+ private Block evalBlock(Block block) {
+ int positionCount = block.getPositionCount();
+ DoubleBlock doubleBlock = (DoubleBlock) block;
+ try (
+ AggregateMetricDoubleBlockBuilder result = context.blockFactory()
+ .newAggregateMetricDoubleBlockBuilder(positionCount)
+ ) {
+ CompensatedSum compensatedSum = new CompensatedSum();
+ for (int p = 0; p < positionCount; p++) {
+ int valueCount = doubleBlock.getValueCount(p);
+ int start = doubleBlock.getFirstValueIndex(p);
+ int end = start + valueCount;
+ if (valueCount == 0) {
+ result.appendNull();
+ continue;
+ }
+ // First iteration of the loop is manual to support having -0.0 as an input consistently
+ double current = doubleBlock.getDouble(start);
+ double min = current;
+ double max = current;
+ compensatedSum.reset(current, 0);
+ for (int i = start + 1; i < end; i++) {
+ current = doubleBlock.getDouble(i);
+ min = Math.min(min, current);
+ max = Math.max(max, current);
+ compensatedSum.add(current);
+ }
+ result.min().appendDouble(min);
+ result.max().appendDouble(max);
+ result.sum().appendDouble(compensatedSum.value());
+ result.count().appendInt(valueCount);
+ }
+ return result.build();
+ }
+ }
+
+ private Block evalVector(Vector vector) {
+ int positionCount = vector.getPositionCount();
+ DoubleVector doubleVector = (DoubleVector) vector;
+ try (
+ AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleVectorBuilder builder = context.blockFactory()
+ .newAggregateMetricDoubleVectorBuilder(positionCount)
+ ) {
+ for (int p = 0; p < positionCount; p++) {
+ double value = doubleVector.getDouble(p);
+ builder.appendValue(value);
+ }
+ return builder.build();
+ }
+ }
+
+ @Override
+ public Block eval(Page page) {
+ try (Block block = eval.eval(page)) {
+ Vector vector = block.asVector();
+ return vector == null ? evalBlock(block) : evalVector(vector);
+ }
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(eval);
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromDoubleEvaluator[field=" + eval + "]";
+ }
+ };
+ }
+ }
+
+ public static class IntFactory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory fieldEvaluator;
+
+ public IntFactory(Source source, EvalOperator.ExpressionEvaluator.Factory fieldEvaluator) {
+ this.fieldEvaluator = fieldEvaluator;
+ this.source = source;
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromIntEvaluator[" + "field=" + fieldEvaluator + "]";
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator get(DriverContext context) {
+ final EvalOperator.ExpressionEvaluator eval = fieldEvaluator.get(context);
+
+ return new EvalOperator.ExpressionEvaluator() {
+ @Override
+ public Block eval(Page page) {
+ try (Block block = eval.eval(page)) {
+ int positionCount = block.getPositionCount();
+ IntBlock intBlock = (IntBlock) block;
+ try (
+ AggregateMetricDoubleBlockBuilder result = context.blockFactory()
+ .newAggregateMetricDoubleBlockBuilder(positionCount)
+ ) {
+ CompensatedSum sum = new CompensatedSum();
+ for (int p = 0; p < positionCount; p++) {
+ int valueCount = intBlock.getValueCount(p);
+ int start = intBlock.getFirstValueIndex(p);
+ int end = start + valueCount;
+ if (valueCount == 0) {
+ result.appendNull();
+ continue;
+ }
+ double min = Double.POSITIVE_INFINITY;
+ double max = Double.NEGATIVE_INFINITY;
+ for (int i = start; i < end; i++) {
+ double current = intBlock.getInt(i);
+ min = Math.min(min, current);
+ max = Math.max(max, current);
+ sum.add(current);
+ }
+ result.min().appendDouble(min);
+ result.max().appendDouble(max);
+ result.sum().appendDouble(sum.value());
+ result.count().appendInt(valueCount);
+ sum.reset(0, 0);
+ }
+ return result.build();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(eval);
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromIntEvaluator[field=" + eval + "]";
+ }
+ };
+ }
+ }
+
+ public static class LongFactory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory fieldEvaluator;
+
+ public LongFactory(Source source, EvalOperator.ExpressionEvaluator.Factory fieldEvaluator) {
+ this.fieldEvaluator = fieldEvaluator;
+ this.source = source;
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromLongEvaluator[" + "field=" + fieldEvaluator + "]";
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator get(DriverContext context) {
+ final EvalOperator.ExpressionEvaluator eval = fieldEvaluator.get(context);
+
+ return new EvalOperator.ExpressionEvaluator() {
+ @Override
+ public Block eval(Page page) {
+ try (Block block = eval.eval(page)) {
+ int positionCount = block.getPositionCount();
+ LongBlock longBlock = (LongBlock) block;
+ try (
+ AggregateMetricDoubleBlockBuilder result = context.blockFactory()
+ .newAggregateMetricDoubleBlockBuilder(positionCount)
+ ) {
+ CompensatedSum sum = new CompensatedSum();
+ for (int p = 0; p < positionCount; p++) {
+ int valueCount = longBlock.getValueCount(p);
+ int start = longBlock.getFirstValueIndex(p);
+ int end = start + valueCount;
+ if (valueCount == 0) {
+ result.appendNull();
+ continue;
+ }
+ double min = Double.POSITIVE_INFINITY;
+ double max = Double.NEGATIVE_INFINITY;
+ for (int i = start; i < end; i++) {
+ double current = longBlock.getLong(i);
+ min = Math.min(min, current);
+ max = Math.max(max, current);
+ sum.add(current);
+ }
+ result.min().appendDouble(min);
+ result.max().appendDouble(max);
+ result.sum().appendDouble(sum.value());
+ result.count().appendInt(valueCount);
+ sum.reset(0, 0);
+ }
+ return result.build();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(eval);
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromLongEvaluator[field=" + eval + "]";
+ }
+ };
+ }
+ }
+
+ public static class UnsignedLongFactory implements EvalOperator.ExpressionEvaluator.Factory {
+ private final Source source;
+
+ private final EvalOperator.ExpressionEvaluator.Factory fieldEvaluator;
+
+ public UnsignedLongFactory(Source source, EvalOperator.ExpressionEvaluator.Factory fieldEvaluator) {
+ this.fieldEvaluator = fieldEvaluator;
+ this.source = source;
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromUnsignedLongEvaluator[" + "field=" + fieldEvaluator + "]";
+ }
+
+ @Override
+ public EvalOperator.ExpressionEvaluator get(DriverContext context) {
+ final EvalOperator.ExpressionEvaluator eval = fieldEvaluator.get(context);
+
+ return new EvalOperator.ExpressionEvaluator() {
+ @Override
+ public Block eval(Page page) {
+ try (Block block = eval.eval(page)) {
+ int positionCount = block.getPositionCount();
+ LongBlock longBlock = (LongBlock) block;
+ try (
+ AggregateMetricDoubleBlockBuilder result = context.blockFactory()
+ .newAggregateMetricDoubleBlockBuilder(positionCount)
+ ) {
+ CompensatedSum sum = new CompensatedSum();
+ for (int p = 0; p < positionCount; p++) {
+ int valueCount = longBlock.getValueCount(p);
+ int start = longBlock.getFirstValueIndex(p);
+ int end = start + valueCount;
+ if (valueCount == 0) {
+ result.appendNull();
+ continue;
+ }
+ double min = Double.POSITIVE_INFINITY;
+ double max = Double.NEGATIVE_INFINITY;
+ for (int i = start; i < end; i++) {
+ var current = EsqlDataTypeConverter.unsignedLongToDouble(longBlock.getLong(p));
+ min = Math.min(min, current);
+ max = Math.max(max, current);
+ sum.add(current);
+ }
+ result.min().appendDouble(min);
+ result.max().appendDouble(max);
+ result.sum().appendDouble(sum.value());
+ result.count().appendInt(valueCount);
+ sum.reset(0, 0);
+ }
+ return result.build();
+ }
+ }
+ }
+
+ @Override
+ public void close() {
+ Releasables.closeExpectNoException(eval);
+ }
+
+ @Override
+ public String toString() {
+ return "ToAggregateMetricDoubleFromUnsignedLongEvaluator[field=" + eval + "]";
+ }
+ };
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java
index 3e644f3e61b05..8e396e4753f09 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/SerializationTestUtils.java
@@ -10,9 +10,11 @@
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.GenericNamedWriteable;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.ExistsQueryBuilder;
import org.elasticsearch.index.query.MatchAllQueryBuilder;
@@ -112,6 +114,13 @@ public static NamedWriteableRegistry writableRegistry() {
entries.add(SingleValueQuery.ENTRY);
entries.addAll(ExpressionWritables.getNamedWriteables());
entries.addAll(PlanWritables.getNamedWriteables());
+ entries.add(
+ new NamedWriteableRegistry.Entry(
+ GenericNamedWriteable.class,
+ AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral.ENTRY.name,
+ AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral::new
+ )
+ );
return new NamedWriteableRegistry(entries);
}
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDoubleTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDoubleTests.java
new file mode 100644
index 0000000000000..14910572d8c9d
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToAggregateMetricDoubleTests.java
@@ -0,0 +1,91 @@
+/*
+ * 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.scalar.convert;
+
+import com.carrotsearch.randomizedtesting.annotations.Name;
+import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
+
+import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
+import org.elasticsearch.xpack.esql.core.tree.Source;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase;
+import org.elasticsearch.xpack.esql.expression.function.FunctionName;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+import static java.util.Collections.emptyList;
+
+@FunctionName("to_aggregate_metric_double")
+public class ToAggregateMetricDoubleTests extends AbstractScalarFunctionTestCase {
+ public ToAggregateMetricDoubleTests(@Name("TestCase") Supplier testCaseSupplier) {
+ this.testCase = testCaseSupplier.get();
+ }
+
+ @Override
+ protected Expression build(Source source, List args) {
+ if (args.get(0).dataType() == DataType.AGGREGATE_METRIC_DOUBLE) {
+ assumeTrue("Test sometimes wraps literals as fields", args.get(0).foldable());
+ }
+ return new ToAggregateMetricDouble(source, args.get(0));
+ }
+
+ @ParametersFactory
+ public static Iterable