Skip to content

Commit e4ee91a

Browse files
authored
[ES|QL] Render aggregate_metric_double (#122660)
This commit allows users to read aggregate_metric_double fields from indices in ES|QL, with any subset of metrics.
1 parent 43665f0 commit e4ee91a

File tree

13 files changed

+250
-17
lines changed

13 files changed

+250
-17
lines changed

docs/changelog/122660.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 122660
2+
summary: Render `aggregate_metric_double`
3+
area: "ES|QL"
4+
type: enhancement
5+
issues: []

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AggregateMetricDoubleBlockBuilder.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,20 +139,26 @@ public BlockLoader.IntBuilder count() {
139139
}
140140

141141
public enum Metric {
142-
MIN(0),
143-
MAX(1),
144-
SUM(2),
145-
COUNT(3);
142+
MIN(0, "min"),
143+
MAX(1, "max"),
144+
SUM(2, "sum"),
145+
COUNT(3, "value_count");
146146

147147
private final int index;
148+
private final String label;
148149

149-
Metric(int index) {
150+
Metric(int index, String label) {
150151
this.index = index;
152+
this.label = label;
151153
}
152154

153155
public int getIndex() {
154156
return index;
155157
}
158+
159+
public String getLabel() {
160+
return label;
161+
}
156162
}
157163

158164
public record AggregateMetricDoubleLiteral(Double min, Double max, Double sum, Integer count) {

x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/CompositeBlock.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ public int getFirstValueIndex(int position) {
9696

9797
@Override
9898
public int getValueCount(int position) {
99-
return blocks[0].getValueCount(position);
99+
int max = 0;
100+
for (var block : blocks) {
101+
max = Math.max(max, block.getValueCount(position));
102+
}
103+
return max;
100104
}
101105

102106
@Override

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlCapabilities.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,12 @@ public enum Cap {
824824
/**
825825
* Support partial_results
826826
*/
827-
SUPPORT_PARTIAL_RESULTS;
827+
SUPPORT_PARTIAL_RESULTS,
828+
829+
/**
830+
* Support for rendering aggregate_metric_double type
831+
*/
832+
AGGREGATE_METRIC_DOUBLE_RENDERING(AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG);
828833

829834
private final boolean enabled;
830835

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/PositionToXContent.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.compute.data.Block;
1414
import org.elasticsearch.compute.data.BooleanBlock;
1515
import org.elasticsearch.compute.data.BytesRefBlock;
16+
import org.elasticsearch.compute.data.CompositeBlock;
1617
import org.elasticsearch.compute.data.DoubleBlock;
1718
import org.elasticsearch.compute.data.IntBlock;
1819
import org.elasticsearch.compute.data.LongBlock;
@@ -25,6 +26,7 @@
2526
import java.io.IOException;
2627

2728
import static org.elasticsearch.xpack.esql.core.util.NumericUtils.unsignedLongAsNumber;
29+
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.aggregateMetricDoubleBlockToString;
2830
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToString;
2931
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ipToString;
3032
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.nanoTimeToString;
@@ -148,8 +150,14 @@ protected XContentBuilder valueToXContent(XContentBuilder builder, ToXContent.Pa
148150
return builder.value(versionToString(val));
149151
}
150152
};
151-
// TODO: Add implementation for aggregate_metric_double
152-
case NULL, AGGREGATE_METRIC_DOUBLE -> new PositionToXContent(block) {
153+
case AGGREGATE_METRIC_DOUBLE -> new PositionToXContent(block) {
154+
@Override
155+
protected XContentBuilder valueToXContent(XContentBuilder builder, ToXContent.Params params, int valueIndex)
156+
throws IOException {
157+
return builder.value(aggregateMetricDoubleBlockToString((CompositeBlock) block, valueIndex));
158+
}
159+
};
160+
case NULL -> new PositionToXContent(block) {
153161
@Override
154162
protected XContentBuilder valueToXContent(XContentBuilder builder, ToXContent.Params params, int valueIndex)
155163
throws IOException {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/ResponseValueUtils.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.elasticsearch.compute.data.Block;
1515
import org.elasticsearch.compute.data.BooleanBlock;
1616
import org.elasticsearch.compute.data.BytesRefBlock;
17+
import org.elasticsearch.compute.data.CompositeBlock;
1718
import org.elasticsearch.compute.data.DoubleBlock;
1819
import org.elasticsearch.compute.data.IntBlock;
1920
import org.elasticsearch.compute.data.LongBlock;
@@ -30,6 +31,7 @@
3031
import java.util.List;
3132

3233
import static org.elasticsearch.xpack.esql.core.util.NumericUtils.unsignedLongAsNumber;
34+
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.aggregateMetricDoubleBlockToString;
3335
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToString;
3436
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.ipToString;
3537
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.nanoTimeToString;
@@ -132,7 +134,8 @@ private static Object valueAt(DataType dataType, Block block, int offset, BytesR
132134
case GEO_POINT, GEO_SHAPE, CARTESIAN_POINT, CARTESIAN_SHAPE -> spatialToString(
133135
((BytesRefBlock) block).getBytesRef(offset, scratch)
134136
);
135-
case UNSUPPORTED, AGGREGATE_METRIC_DOUBLE -> (String) null;
137+
case AGGREGATE_METRIC_DOUBLE -> aggregateMetricDoubleBlockToString((CompositeBlock) block, offset);
138+
case UNSUPPORTED -> (String) null;
136139
case SOURCE -> {
137140
BytesRef val = ((BytesRefBlock) block).getBytesRef(offset, scratch);
138141
try {

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.List;
2525
import java.util.Map;
2626

27+
import static org.elasticsearch.xpack.esql.core.type.DataType.AGGREGATE_METRIC_DOUBLE;
2728
import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN;
2829
import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_POINT;
2930
import static org.elasticsearch.xpack.esql.core.type.DataType.CARTESIAN_SHAPE;
@@ -67,7 +68,8 @@ public class ToString extends AbstractConvertFunction implements EvaluatorMapper
6768
Map.entry(GEO_POINT, ToStringFromGeoPointEvaluator.Factory::new),
6869
Map.entry(CARTESIAN_POINT, ToStringFromCartesianPointEvaluator.Factory::new),
6970
Map.entry(CARTESIAN_SHAPE, ToStringFromCartesianShapeEvaluator.Factory::new),
70-
Map.entry(GEO_SHAPE, ToStringFromGeoShapeEvaluator.Factory::new)
71+
Map.entry(GEO_SHAPE, ToStringFromGeoShapeEvaluator.Factory::new),
72+
Map.entry(AGGREGATE_METRIC_DOUBLE, ToStringFromAggregateMetricDoubleEvaluator.Factory::new)
7173
);
7274

7375
@FunctionInfo(
@@ -82,6 +84,7 @@ public ToString(
8284
@Param(
8385
name = "field",
8486
type = {
87+
"aggregate_metric_double",
8588
"boolean",
8689
"cartesian_point",
8790
"cartesian_shape",
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.esql.expression.function.scalar.convert;
9+
10+
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.compute.data.Block;
12+
import org.elasticsearch.compute.data.BytesRefBlock;
13+
import org.elasticsearch.compute.data.CompositeBlock;
14+
import org.elasticsearch.compute.data.Vector;
15+
import org.elasticsearch.compute.operator.DriverContext;
16+
import org.elasticsearch.compute.operator.EvalOperator;
17+
import org.elasticsearch.xpack.esql.core.tree.Source;
18+
19+
import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.aggregateMetricDoubleBlockToString;
20+
21+
public class ToStringFromAggregateMetricDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator {
22+
public ToStringFromAggregateMetricDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, DriverContext driverContext) {
23+
super(driverContext, field, source);
24+
}
25+
26+
@Override
27+
protected String name() {
28+
return "ToStringFromAggregateMetricDouble";
29+
}
30+
31+
@Override
32+
protected Block evalVector(Vector v) {
33+
return evalBlock(v.asBlock());
34+
}
35+
36+
private static BytesRef evalValue(CompositeBlock compositeBlock, int index) {
37+
return new BytesRef(aggregateMetricDoubleBlockToString(compositeBlock, index));
38+
}
39+
40+
@Override
41+
public Block evalBlock(Block b) {
42+
CompositeBlock block = (CompositeBlock) b;
43+
int positionCount = block.getPositionCount();
44+
try (BytesRefBlock.Builder builder = driverContext.blockFactory().newBytesRefBlockBuilder(positionCount)) {
45+
for (int p = 0; p < positionCount; p++) {
46+
if (block.isNull(p)) {
47+
builder.appendNull();
48+
} else {
49+
builder.appendBytesRef(evalValue(block, p));
50+
}
51+
}
52+
return builder.build();
53+
}
54+
}
55+
56+
public static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
57+
private final Source source;
58+
private final EvalOperator.ExpressionEvaluator.Factory field;
59+
60+
public Factory(EvalOperator.ExpressionEvaluator.Factory field, Source source) {
61+
this.field = field;
62+
this.source = source;
63+
}
64+
65+
@Override
66+
public EvalOperator.ExpressionEvaluator get(DriverContext context) {
67+
return new ToStringFromAggregateMetricDoubleEvaluator(field.get(context), source, context);
68+
}
69+
}
70+
}

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/type/EsqlDataTypeConverter.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@
88
package org.elasticsearch.xpack.esql.type;
99

1010
import org.apache.lucene.util.BytesRef;
11+
import org.elasticsearch.common.Strings;
1112
import org.elasticsearch.common.io.stream.StreamInput;
1213
import org.elasticsearch.common.io.stream.StreamOutput;
1314
import org.elasticsearch.common.logging.LoggerMessageFormat;
1415
import org.elasticsearch.common.lucene.BytesRefs;
1516
import org.elasticsearch.common.time.DateFormatter;
1617
import org.elasticsearch.common.time.DateFormatters;
1718
import org.elasticsearch.common.time.DateUtils;
19+
import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder.Metric;
20+
import org.elasticsearch.compute.data.CompositeBlock;
21+
import org.elasticsearch.compute.data.DoubleBlock;
22+
import org.elasticsearch.compute.data.IntBlock;
1823
import org.elasticsearch.search.DocValueFormat;
24+
import org.elasticsearch.xcontent.XContentBuilder;
25+
import org.elasticsearch.xcontent.json.JsonXContent;
1926
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
2027
import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException;
2128
import org.elasticsearch.xpack.esql.core.expression.Expression;
@@ -664,6 +671,26 @@ public static long booleanToUnsignedLong(boolean number) {
664671
return number ? ONE_AS_UNSIGNED_LONG : ZERO_AS_UNSIGNED_LONG;
665672
}
666673

674+
public static String aggregateMetricDoubleBlockToString(CompositeBlock compositeBlock, int index) {
675+
try (XContentBuilder builder = JsonXContent.contentBuilder()) {
676+
builder.startObject();
677+
for (Metric metric : List.of(Metric.MIN, Metric.MAX, Metric.SUM)) {
678+
var block = compositeBlock.getBlock(metric.getIndex());
679+
if (block.isNull(index) == false) {
680+
builder.field(metric.getLabel(), ((DoubleBlock) block).getDouble(index));
681+
}
682+
}
683+
var countBlock = compositeBlock.getBlock(Metric.COUNT.getIndex());
684+
if (countBlock.isNull(index) == false) {
685+
builder.field(Metric.COUNT.getLabel(), ((IntBlock) countBlock).getInt(index));
686+
}
687+
builder.endObject();
688+
return Strings.toString(builder);
689+
} catch (IOException e) {
690+
throw new IllegalStateException("error rendering aggregate metric double", e);
691+
}
692+
}
693+
667694
public enum EsqlConverter implements Converter {
668695

669696
STRING_TO_DATE_PERIOD(x -> EsqlDataTypeConverter.parseTemporalAmount(x, DataType.DATE_PERIOD)),

x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1580,7 +1580,7 @@ public void testRegexOnInt() {
15801580

15811581
public void testUnsupportedTypesWithToString() {
15821582
// DATE_PERIOD and TIME_DURATION types have been added, but not really patched through the engine; i.e. supported.
1583-
final String supportedTypes = "boolean or cartesian_point or cartesian_shape or date_nanos or datetime "
1583+
final String supportedTypes = "aggregate_metric_double or boolean or cartesian_point or cartesian_shape or date_nanos or datetime "
15841584
+ "or geo_point or geo_shape or ip or numeric or string or version";
15851585
verifyUnsupported(
15861586
"row period = 1 year | eval to_string(period)",

0 commit comments

Comments
 (0)