Skip to content

Commit ecb53f6

Browse files
committed
[ES|QL] Render aggregate_metric_double
1 parent cffbccb commit ecb53f6

File tree

10 files changed

+189
-10
lines changed

10 files changed

+189
-10
lines changed

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",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
int valueCount = block.getValueCount(p);
47+
int start = block.getFirstValueIndex(p);
48+
int end = start + valueCount;
49+
boolean positionOpened = false;
50+
boolean valuesAppended = false;
51+
for (int i = start; i < end; i++) {
52+
BytesRef value = evalValue(block, i);
53+
if (positionOpened == false && valueCount > 1) {
54+
builder.beginPositionEntry();
55+
positionOpened = true;
56+
}
57+
builder.appendBytesRef(value);
58+
valuesAppended = true;
59+
}
60+
if (valuesAppended == false) {
61+
builder.appendNull();
62+
} else if (positionOpened) {
63+
builder.endPositionEntry();
64+
}
65+
}
66+
return builder.build();
67+
}
68+
}
69+
70+
public static class Factory implements EvalOperator.ExpressionEvaluator.Factory {
71+
private final Source source;
72+
private final EvalOperator.ExpressionEvaluator.Factory field;
73+
74+
public Factory(EvalOperator.ExpressionEvaluator.Factory field, Source source) {
75+
this.field = field;
76+
this.source = source;
77+
}
78+
79+
@Override
80+
public EvalOperator.ExpressionEvaluator get(DriverContext context) {
81+
return new ToStringFromAggregateMetricDoubleEvaluator(field.get(context), source, context);
82+
}
83+
}
84+
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
import org.elasticsearch.common.time.DateFormatter;
1616
import org.elasticsearch.common.time.DateFormatters;
1717
import org.elasticsearch.common.time.DateUtils;
18+
import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder;
19+
import org.elasticsearch.compute.data.CompositeBlock;
20+
import org.elasticsearch.compute.data.DoubleBlock;
21+
import org.elasticsearch.compute.data.IntBlock;
1822
import org.elasticsearch.search.DocValueFormat;
1923
import org.elasticsearch.xpack.esql.core.InvalidArgumentException;
2024
import org.elasticsearch.xpack.esql.core.QlIllegalArgumentException;
@@ -58,6 +62,7 @@
5862
import java.util.List;
5963
import java.util.Locale;
6064
import java.util.Map;
65+
import java.util.StringJoiner;
6166
import java.util.function.BiFunction;
6267
import java.util.function.Function;
6368

@@ -664,6 +669,27 @@ public static long booleanToUnsignedLong(boolean number) {
664669
return number ? ONE_AS_UNSIGNED_LONG : ZERO_AS_UNSIGNED_LONG;
665670
}
666671

672+
public static String aggregateMetricDoubleBlockToString(CompositeBlock compositeBlock, int index) {
673+
var minBlock = compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.MIN.getIndex());
674+
var maxBlock = compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.MAX.getIndex());
675+
var sumBlock = compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.SUM.getIndex());
676+
var countBlock = compositeBlock.getBlock(AggregateMetricDoubleBlockBuilder.Metric.COUNT.getIndex());
677+
final StringJoiner joiner = new StringJoiner(", ");
678+
if (maxBlock.isNull(index) == false) {
679+
joiner.add("\"max\": " + ((DoubleBlock) maxBlock).getDouble(index));
680+
}
681+
if (minBlock.isNull(index) == false) {
682+
joiner.add("\"min\": " + ((DoubleBlock) minBlock).getDouble(index));
683+
}
684+
if (sumBlock.isNull(index) == false) {
685+
joiner.add("\"sum\": " + ((DoubleBlock) sumBlock).getDouble(index));
686+
}
687+
if (countBlock.isNull(index) == false) {
688+
joiner.add("\"value_count\": " + ((IntBlock) countBlock).getInt(index));
689+
}
690+
return "{ " + joiner + " }";
691+
}
692+
667693
public enum EsqlConverter implements Converter {
668694

669695
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)",

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_tsdb.yml

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,52 @@ stats on aggregate_metric_double missing min and max:
445445
- match: {values.0.2: 1.0}
446446
- match: {values.0.3: 10}
447447

448+
---
449+
render aggregate_metric_double when missing min and max:
450+
- requires:
451+
test_runner_features: [ capabilities ]
452+
capabilities:
453+
- method: POST
454+
path: /_query
455+
parameters: [ ]
456+
capabilities: [ aggregate_metric_double_rendering ]
457+
reason: "Support for rendering aggregate_metric_doubles"
458+
- do:
459+
allowed_warnings_regex:
460+
- "No limit defined, adding default limit of \\[.*\\]"
461+
esql.query:
462+
body:
463+
query: 'FROM test4 | KEEP agg_metric'
464+
465+
- length: {values: 1}
466+
- length: {values.0: 1}
467+
- match: {columns.0.name: "agg_metric"}
468+
- match: {columns.0.type: "aggregate_metric_double"}
469+
- match: {values.0.0: '{ "sum": 1.0, "value_count": 10 }'}
470+
471+
---
472+
to_string aggregate_metric_double:
473+
- requires:
474+
test_runner_features: [ capabilities ]
475+
capabilities:
476+
- method: POST
477+
path: /_query
478+
parameters: [ ]
479+
capabilities: [ aggregate_metric_double_rendering ]
480+
reason: "Support for rendering aggregate_metric_doubles"
481+
- do:
482+
allowed_warnings_regex:
483+
- "No limit defined, adding default limit of \\[.*\\]"
484+
esql.query:
485+
body:
486+
query: 'FROM test4 | EVAL agg = to_string(agg_metric) | KEEP agg'
487+
488+
- length: {values: 1}
489+
- length: {values.0: 1}
490+
- match: {columns.0.name: "agg"}
491+
- match: {columns.0.type: "keyword"}
492+
- match: {values.0.0: '{ "sum": 1.0, "value_count": 10 }'}
493+
448494
---
449495
from index pattern unsupported counter:
450496
- requires:

x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/esql/40_unsupported_types.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,8 @@ unsupported:
119119
- method: POST
120120
path: /_query
121121
parameters: [ ]
122-
capabilities: [ aggregate_metric_double ]
123-
reason: "support for aggregate_metric_double type"
122+
capabilities: [ aggregate_metric_double_rendering ]
123+
reason: "support for rendering aggregate_metric_double type"
124124

125125
- do:
126126
allowed_warnings_regex:
@@ -190,7 +190,7 @@ unsupported:
190190
- match: { columns.28.type: integer }
191191

192192
- length: { values: 1 }
193-
- match: { values.0.0: null }
193+
- match: { values.0.0: '{ "max": 3.0, "min": 1.0, "sum": 10.1, "value_count": 5 }' }
194194
- match: { values.0.1: null }
195195
- match: { values.0.2: null }
196196
- match: { values.0.3: "2015-01-01T12:10:30.123456789Z" }

0 commit comments

Comments
 (0)