Skip to content

Commit bcd8d15

Browse files
authored
[ES|QL] Support some stats on aggregate_metric_double (#120343)
Adds non-grouping support for min, max, sum, and count, using CompositeBlock as the underlying block type and an internal FromAggregateMetricDouble function to handle converting from CompositeBlock to the correct metric subfields. Closes #110649
1 parent c6e722e commit bcd8d15

File tree

36 files changed

+1019
-102
lines changed

36 files changed

+1019
-102
lines changed

docs/changelog/120343.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 120343
2+
summary: Support some stats on aggregate_metric_double
3+
area: "ES|QL"
4+
type: enhancement
5+
issues:
6+
- 110649

server/src/main/java/org/elasticsearch/index/mapper/BlockLoader.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,8 @@ interface BlockFactory {
417417
SingletonOrdinalsBuilder singletonOrdinalsBuilder(SortedDocValues ordinals, int count);
418418

419419
// TODO support non-singleton ords
420+
421+
AggregateMetricDoubleBuilder aggregateMetricDoubleBuilder(int count);
420422
}
421423

422424
/**
@@ -501,4 +503,16 @@ interface SingletonOrdinalsBuilder extends Builder {
501503
*/
502504
SingletonOrdinalsBuilder appendOrd(int value);
503505
}
506+
507+
interface AggregateMetricDoubleBuilder extends Builder {
508+
509+
DoubleBuilder min();
510+
511+
DoubleBuilder max();
512+
513+
DoubleBuilder sum();
514+
515+
IntBuilder count();
516+
517+
}
504518
}

test/framework/src/main/java/org/elasticsearch/index/mapper/TestBlock.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ public SingletonOrdsBuilder appendOrd(int value) {
147147
}
148148
return new SingletonOrdsBuilder();
149149
}
150+
151+
@Override
152+
public BlockLoader.AggregateMetricDoubleBuilder aggregateMetricDoubleBuilder(int count) {
153+
return null;
154+
}
150155
};
151156
}
152157

x-pack/plugin/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
104104
task.skipTest("esql/180_match_operator/match with disjunctions", "Disjunctions in full text functions work now")
105105
// Expected deprecation warning to compat yaml tests:
106106
task.addAllowedWarningRegex(".*rollup functionality will be removed in Elasticsearch.*")
107+
task.skipTest("esql/40_tsdb/from doc with aggregate_metric_double", "TODO: support for subset of metric fields")
108+
task.skipTest("esql/40_tsdb/stats on aggregate_metric_double", "TODO: support for subset of metric fields")
109+
task.skipTest("esql/40_tsdb/from index pattern unsupported counter", "TODO: support for subset of metric fields")
110+
task.skipTest("esql/40_unsupported_types/unsupported", "TODO: support for subset of metric fields")
111+
task.skipTest("esql/40_unsupported_types/unsupported with sort", "TODO: support for subset of metric fields")
107112
})
108113

109114
tasks.named('yamlRestCompatTest').configure {

x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/plugin/EsqlCorePlugin.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
public class EsqlCorePlugin extends Plugin implements ExtensiblePlugin {
1515

1616
public static final FeatureFlag SEMANTIC_TEXT_FEATURE_FLAG = new FeatureFlag("esql_semantic_text");
17+
public static final FeatureFlag AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG = new FeatureFlag("esql_aggregate_metric_double");
1718
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,9 @@ public enum DataType {
307307
* loaded from the index and ESQL will load these fields as strings without their attached
308308
* chunks or embeddings.
309309
*/
310-
SEMANTIC_TEXT(builder().esType("semantic_text").unknownSize());
310+
SEMANTIC_TEXT(builder().esType("semantic_text").unknownSize()),
311+
312+
AGGREGATE_METRIC_DOUBLE(builder().esType("aggregate_metric_double").estimatedSize(Double.BYTES * 3 + Integer.BYTES));
311313

312314
/**
313315
* Types that are actively being built. These types are not returned
@@ -316,7 +318,8 @@ public enum DataType {
316318
* check that sending them to a function produces a sane error message.
317319
*/
318320
public static final Map<DataType, FeatureFlag> UNDER_CONSTRUCTION = Map.ofEntries(
319-
Map.entry(SEMANTIC_TEXT, EsqlCorePlugin.SEMANTIC_TEXT_FEATURE_FLAG)
321+
Map.entry(SEMANTIC_TEXT, EsqlCorePlugin.SEMANTIC_TEXT_FEATURE_FLAG),
322+
Map.entry(AGGREGATE_METRIC_DOUBLE, EsqlCorePlugin.AGGREGATE_METRIC_DOUBLE_FEATURE_FLAG)
320323
);
321324

322325
private final String typeName;
@@ -553,6 +556,7 @@ public static boolean isRepresentable(DataType t) {
553556
&& t != SOURCE
554557
&& t != HALF_FLOAT
555558
&& t != PARTIAL_AGG
559+
&& t != AGGREGATE_METRIC_DOUBLE
556560
&& t.isCounter() == false;
557561
}
558562

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
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.compute.data;
9+
10+
import org.elasticsearch.core.Releasables;
11+
import org.elasticsearch.index.mapper.BlockLoader;
12+
13+
public class AggregateMetricDoubleBlockBuilder extends AbstractBlockBuilder implements BlockLoader.AggregateMetricDoubleBuilder {
14+
15+
private DoubleBlockBuilder minBuilder;
16+
private DoubleBlockBuilder maxBuilder;
17+
private DoubleBlockBuilder sumBuilder;
18+
private IntBlockBuilder countBuilder;
19+
20+
public AggregateMetricDoubleBlockBuilder(int estimatedSize, BlockFactory blockFactory) {
21+
super(blockFactory);
22+
minBuilder = null;
23+
maxBuilder = null;
24+
sumBuilder = null;
25+
countBuilder = null;
26+
try {
27+
minBuilder = new DoubleBlockBuilder(estimatedSize, blockFactory);
28+
maxBuilder = new DoubleBlockBuilder(estimatedSize, blockFactory);
29+
sumBuilder = new DoubleBlockBuilder(estimatedSize, blockFactory);
30+
countBuilder = new IntBlockBuilder(estimatedSize, blockFactory);
31+
} finally {
32+
if (countBuilder == null) {
33+
Releasables.closeWhileHandlingException(minBuilder, maxBuilder, sumBuilder, countBuilder);
34+
}
35+
}
36+
}
37+
38+
@Override
39+
protected int valuesLength() {
40+
throw new UnsupportedOperationException("Not available on aggregate_metric_double");
41+
}
42+
43+
@Override
44+
protected void growValuesArray(int newSize) {
45+
throw new UnsupportedOperationException("Not available on aggregate_metric_double");
46+
}
47+
48+
@Override
49+
protected int elementSize() {
50+
throw new UnsupportedOperationException("Not available on aggregate_metric_double");
51+
}
52+
53+
@Override
54+
public Block.Builder copyFrom(Block block, int beginInclusive, int endExclusive) {
55+
Block minBlock;
56+
Block maxBlock;
57+
Block sumBlock;
58+
Block countBlock;
59+
if (block.areAllValuesNull()) {
60+
minBlock = block;
61+
maxBlock = block;
62+
sumBlock = block;
63+
countBlock = block;
64+
} else {
65+
CompositeBlock composite = (CompositeBlock) block;
66+
minBlock = composite.getBlock(Metric.MIN.getIndex());
67+
maxBlock = composite.getBlock(Metric.MAX.getIndex());
68+
sumBlock = composite.getBlock(Metric.SUM.getIndex());
69+
countBlock = composite.getBlock(Metric.COUNT.getIndex());
70+
}
71+
minBuilder.copyFrom(minBlock, beginInclusive, endExclusive);
72+
maxBuilder.copyFrom(maxBlock, beginInclusive, endExclusive);
73+
sumBuilder.copyFrom(sumBlock, beginInclusive, endExclusive);
74+
countBuilder.copyFrom(countBlock, beginInclusive, endExclusive);
75+
return this;
76+
}
77+
78+
@Override
79+
public AbstractBlockBuilder appendNull() {
80+
minBuilder.appendNull();
81+
maxBuilder.appendNull();
82+
sumBuilder.appendNull();
83+
countBuilder.appendNull();
84+
return this;
85+
}
86+
87+
@Override
88+
public Block.Builder mvOrdering(Block.MvOrdering mvOrdering) {
89+
minBuilder.mvOrdering(mvOrdering);
90+
maxBuilder.mvOrdering(mvOrdering);
91+
sumBuilder.mvOrdering(mvOrdering);
92+
countBuilder.mvOrdering(mvOrdering);
93+
return this;
94+
}
95+
96+
@Override
97+
public Block build() {
98+
Block[] blocks = new Block[4];
99+
boolean success = false;
100+
try {
101+
finish();
102+
blocks[Metric.MIN.getIndex()] = minBuilder.build();
103+
blocks[Metric.MAX.getIndex()] = maxBuilder.build();
104+
blocks[Metric.SUM.getIndex()] = sumBuilder.build();
105+
blocks[Metric.COUNT.getIndex()] = countBuilder.build();
106+
CompositeBlock block = new CompositeBlock(blocks);
107+
success = true;
108+
return block;
109+
} finally {
110+
if (success == false) {
111+
Releasables.closeExpectNoException(blocks);
112+
}
113+
}
114+
}
115+
116+
@Override
117+
protected void extraClose() {
118+
Releasables.closeExpectNoException(minBuilder, maxBuilder, sumBuilder, countBuilder);
119+
}
120+
121+
@Override
122+
public BlockLoader.DoubleBuilder min() {
123+
return minBuilder;
124+
}
125+
126+
@Override
127+
public BlockLoader.DoubleBuilder max() {
128+
return maxBuilder;
129+
}
130+
131+
@Override
132+
public BlockLoader.DoubleBuilder sum() {
133+
return sumBuilder;
134+
}
135+
136+
@Override
137+
public BlockLoader.IntBuilder count() {
138+
return countBuilder;
139+
}
140+
141+
public enum Metric {
142+
MIN(0),
143+
MAX(1),
144+
SUM(2),
145+
COUNT(3);
146+
147+
private final int index;
148+
149+
Metric(int index) {
150+
this.index = index;
151+
}
152+
153+
public int getIndex() {
154+
return index;
155+
}
156+
}
157+
158+
public record AggregateMetricDoubleLiteral(Double min, Double max, Double sum, Integer count) {
159+
public AggregateMetricDoubleLiteral {
160+
min = min.isNaN() ? null : min;
161+
max = max.isNaN() ? null : max;
162+
sum = sum.isNaN() ? null : sum;
163+
}
164+
}
165+
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,39 @@ public Block newConstantNullBlock(int positions) {
432432
return b;
433433
}
434434

435+
public AggregateMetricDoubleBlockBuilder newAggregateMetricDoubleBlockBuilder(int estimatedSize) {
436+
return new AggregateMetricDoubleBlockBuilder(estimatedSize, this);
437+
}
438+
439+
public final Block newConstantAggregateMetricDoubleBlock(
440+
AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral value,
441+
int positions
442+
) {
443+
try (AggregateMetricDoubleBlockBuilder builder = newAggregateMetricDoubleBlockBuilder(positions)) {
444+
if (value.min() != null) {
445+
builder.min().appendDouble(value.min());
446+
} else {
447+
builder.min().appendNull();
448+
}
449+
if (value.max() != null) {
450+
builder.max().appendDouble(value.max());
451+
} else {
452+
builder.max().appendNull();
453+
}
454+
if (value.sum() != null) {
455+
builder.sum().appendDouble(value.sum());
456+
} else {
457+
builder.sum().appendNull();
458+
}
459+
if (value.count() != null) {
460+
builder.count().appendInt(value.count());
461+
} else {
462+
builder.count().appendNull();
463+
}
464+
return builder.build();
465+
}
466+
}
467+
435468
/**
436469
* Returns the maximum number of bytes that a Block should be backed by a primitive array before switching to using BigArrays.
437470
*/

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import org.apache.lucene.util.BytesRef;
1111
import org.elasticsearch.common.Randomness;
12+
import org.elasticsearch.compute.data.AggregateMetricDoubleBlockBuilder.AggregateMetricDoubleLiteral;
1213
import org.elasticsearch.core.Releasable;
1314
import org.elasticsearch.core.Releasables;
1415

@@ -233,6 +234,14 @@ private static Block constantBlock(BlockFactory blockFactory, ElementType type,
233234
case BYTES_REF -> blockFactory.newConstantBytesRefBlockWith(toBytesRef(val), size);
234235
case DOUBLE -> blockFactory.newConstantDoubleBlockWith((double) val, size);
235236
case BOOLEAN -> blockFactory.newConstantBooleanBlockWith((boolean) val, size);
237+
case COMPOSITE -> {
238+
if (val instanceof AggregateMetricDoubleLiteral aggregateMetricDoubleLiteral) {
239+
yield blockFactory.newConstantAggregateMetricDoubleBlock(aggregateMetricDoubleLiteral, size);
240+
}
241+
throw new UnsupportedOperationException(
242+
"Composite block but received value that wasn't AggregateMetricDoubleLiteral [" + val + "]"
243+
);
244+
}
236245
default -> throw new UnsupportedOperationException("unsupported element type [" + type + "]");
237246
};
238247
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,22 @@ public int getTotalValueCount() {
9191

9292
@Override
9393
public int getFirstValueIndex(int position) {
94-
throw new UnsupportedOperationException("Composite block");
94+
return blocks[0].getFirstValueIndex(position);
9595
}
9696

9797
@Override
9898
public int getValueCount(int position) {
99-
throw new UnsupportedOperationException("Composite block");
99+
return blocks[0].getValueCount(position);
100100
}
101101

102102
@Override
103103
public boolean isNull(int position) {
104-
throw new UnsupportedOperationException("Composite block");
104+
for (Block block : blocks) {
105+
if (block.isNull(position) == false) {
106+
return false;
107+
}
108+
}
109+
return true;
105110
}
106111

107112
@Override

0 commit comments

Comments
 (0)