Skip to content

Commit ef8a008

Browse files
leontyevdvnot-napoleonelasticsearchmachine
authored
ESQL: GROUP BY ALL - Part 1 (#137367)
Adds support for bare time-series aggregate functions and a _timeseries column into the output. The _timeseries column contain a BASE64 representation of a _tsid value. Part of #136253 --------- Co-authored-by: Mark Tozzi <[email protected]> Co-authored-by: elasticsearchmachine <[email protected]>
1 parent 2af0640 commit ef8a008

File tree

15 files changed

+716
-104
lines changed

15 files changed

+716
-104
lines changed

docs/changelog/137367.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 137367
2+
summary: GROUP BY ALL
3+
area: ES|QL
4+
type: enhancement
5+
issues: []

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public class MetadataAttribute extends TypedAttribute {
3030
public static final String TSID_FIELD = "_tsid";
3131
public static final String SCORE = "_score";
3232
public static final String INDEX = "_index";
33+
public static final String TIMESERIES = "_timeseries";
3334

3435
static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
3536
Attribute.class,

x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/generative/GenerativeForkRestTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ protected void shouldSkipTest(String testName) throws IOException {
6262

6363
assumeFalse("Tests using PROMQL are not supported for now", testCase.requiredCapabilities.contains(PROMQL_V0.capabilityName()));
6464

65+
assumeFalse(
66+
"Tests using GROUP_BY_ALL are skipped since we add a new _timeseries field",
67+
testCase.requiredCapabilities.contains(METRICS_GROUP_BY_ALL.capabilityName())
68+
);
69+
6570
assumeTrue("Cluster needs to support FORK", hasCapabilities(adminClient(), List.of(FORK_V9.capabilityName())));
6671
}
6772
}

x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries.csv-spec

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -784,5 +784,192 @@ max_deriv:double | max_rate:double | time_bucket:datetime | cluster:keyword
784784
8.922491 | 11.56274 | 2024-05-10T00:10:00.000Z | prod
785785
16.62316 | 11.86081 | 2024-05-10T00:15:00.000Z | prod
786786
9.026268 | 6.980661 | 2024-05-10T00:20:00.000Z | prod
787+
;
788+
789+
bare_count_over_time_outputs_dimensions
790+
required_capability: ts_command_v0
791+
required_capability: metrics_group_by_all
792+
793+
TS k8s
794+
| STATS count = count_over_time(network.cost)
795+
| SORT count DESC
796+
;
797+
ignoreOrder:true
798+
799+
count:long | _timeseries:keyword
800+
29 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg=
801+
29 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss=
802+
24 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt
803+
24 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4=
804+
23 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC
805+
22 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG
806+
20 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H
807+
16 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3
808+
13 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB
809+
;
810+
811+
bare_count_over_time_with_tbucket_outputs_dimensions
812+
required_capability: ts_command_v0
813+
required_capability: metrics_group_by_all
814+
815+
TS k8s
816+
| STATS count = count_over_time(network.cost) BY tbucket = TBUCKET(1 hour)
817+
| SORT count DESC
818+
;
819+
ignoreOrder:true
820+
821+
count:long | _timeseries:keyword | tbucket:datetime
822+
29 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg= | 2024-05-10T00:00:00.000Z
823+
29 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss= | 2024-05-10T00:00:00.000Z
824+
24 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt | 2024-05-10T00:00:00.000Z
825+
24 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4= | 2024-05-10T00:00:00.000Z
826+
23 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC | 2024-05-10T00:00:00.000Z
827+
22 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG | 2024-05-10T00:00:00.000Z
828+
20 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H | 2024-05-10T00:00:00.000Z
829+
16 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3 | 2024-05-10T00:00:00.000Z
830+
13 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB | 2024-05-10T00:00:00.000Z
831+
;
832+
833+
bare_avg_over_time_outputs_dimensions
834+
required_capability: ts_command_v0
835+
required_capability: metrics_group_by_all
787836

837+
TS k8s
838+
| STATS avg = avg_over_time(network.cost)
839+
| SORT avg DESC
840+
;
841+
ignoreOrder:true
842+
843+
avg:double | _timeseries:keyword
844+
8.375 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG
845+
7.262931034482759 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss=
846+
6.870689655172414 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg=
847+
6.635416666666667 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4=
848+
6.46875 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H
849+
5.869791666666667 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt
850+
5.586956521739131 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC
851+
5.1826923076923075 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB
852+
4.0703125 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3
853+
;
854+
855+
bare_avg_over_time_with_tbucket_outputs_dimensions
856+
required_capability: ts_command_v0
857+
required_capability: metrics_group_by_all
858+
859+
TS k8s
860+
| STATS avg = avg_over_time(network.cost) BY tbucket = TBUCKET(1 hour)
861+
| SORT avg DESC
862+
;
863+
ignoreOrder:true
864+
865+
avg:double | _timeseries:keyword | tbucket:datetime
866+
8.375 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG | 2024-05-10T00:00:00.000Z
867+
7.262931034482759 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss= | 2024-05-10T00:00:00.000Z
868+
6.870689655172414 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg= | 2024-05-10T00:00:00.000Z
869+
6.635416666666667 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4= | 2024-05-10T00:00:00.000Z
870+
6.46875 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H | 2024-05-10T00:00:00.000Z
871+
5.869791666666667 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt | 2024-05-10T00:00:00.000Z
872+
5.586956521739131 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC | 2024-05-10T00:00:00.000Z
873+
5.1826923076923075 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB | 2024-05-10T00:00:00.000Z
874+
4.0703125 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3 | 2024-05-10T00:00:00.000Z
875+
;
876+
877+
bare_sum_over_time_outputs_dimensions
878+
required_capability: ts_command_v0
879+
required_capability: metrics_group_by_all
880+
881+
TS k8s
882+
| STATS sum = sum_over_time(network.cost)
883+
| SORT sum DESC
884+
;
885+
ignoreOrder:true
886+
887+
sum:double | _timeseries:keyword
888+
210.625 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss=
889+
199.25 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg=
890+
184.25 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG
891+
159.25 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4=
892+
140.875 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt
893+
129.375 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H
894+
128.5 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC
895+
67.375 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB
896+
65.125 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3
897+
;
898+
899+
bare_sum_over_time_with_tbucket_outputs_dimensions
900+
required_capability: ts_command_v0
901+
required_capability: metrics_group_by_all
902+
903+
TS k8s
904+
| STATS sum = sum_over_time(network.cost) BY tbucket = TBUCKET(1 hour)
905+
| SORT sum DESC
906+
;
907+
ignoreOrder:true
908+
909+
sum:double | _timeseries:keyword | tbucket:datetime
910+
210.625 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss= | 2024-05-10T00:00:00.000Z
911+
199.25 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg= | 2024-05-10T00:00:00.000Z
912+
184.25 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG | 2024-05-10T00:00:00.000Z
913+
159.25 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4= | 2024-05-10T00:00:00.000Z
914+
140.875 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt | 2024-05-10T00:00:00.000Z
915+
129.375 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H | 2024-05-10T00:00:00.000Z
916+
128.5 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC | 2024-05-10T00:00:00.000Z
917+
67.375 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB | 2024-05-10T00:00:00.000Z
918+
65.125 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3 | 2024-05-10T00:00:00.000Z
919+
;
920+
921+
bare_rate_outputs_dimensions
922+
required_capability: ts_command_v0
923+
required_capability: metrics_group_by_all
924+
925+
TS k8s
926+
| STATS rate = rate(network.total_bytes_in)
927+
| SORT rate DESC
928+
;
929+
ignoreOrder:true
930+
931+
rate:double | _timeseries:keyword
932+
13.17372515125324 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg=
933+
10.649149922720246 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss=
934+
9.485643970467596 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4=
935+
8.716707021791768 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC
936+
8.434290687554395 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG
937+
7.377611940298507 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H
938+
7.340495867768595 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3
939+
6.54326561324304 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt
940+
4.689830508474576 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB
941+
;
942+
943+
bare_rate_with_tbucket_outputs_dimensions
944+
required_capability: ts_command_v0
945+
required_capability: metrics_group_by_all
946+
947+
TS k8s
948+
| STATS rate = rate(network.total_bytes_in) BY tbucket = TBUCKET(1 hour)
949+
| SORT rate DESC
950+
;
951+
ignoreOrder:true
952+
953+
rate:double | _timeseries:keyword | tbucket:datetime
954+
4.379885057471264 | KBGrBhmEnziumfgfOq1dn9NyLSzHck3A2MQiFrxrlvTQFP7YxbJ0rYg= | 2024-05-10T00:00:00.000Z
955+
3.9597701149425295 | KBGrBhmEnziumfgfOq1dn9NyLSzHbdLj0kfy/m+tXh+yxVR6b3E17ss= | 2024-05-10T00:00:00.000Z
956+
3.3457754629629632 | KBGrBhmEnziumfgfOq1dn9NyLSzHmDfU38IhLHmDYPRNCcqXvvDolM4= | 2024-05-10T00:00:00.000Z
957+
3.130434782608696 | LAhW8dgJbuvWP9LgvcDhXAbo6om1bdLj0gEKCKoCwWbTcgIrvk8UnM8+ysUC | 2024-05-10T00:00:00.000Z
958+
2.8741946517412935 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3ck3A2FKjdGi/h3SR4+8pqYaQpJ7ugp3H | 2024-05-10T00:00:00.000Z
959+
2.830347222222222 | LAhW8dgJbuvWP9LgvcDhXAbo6om1mDfU3wEKCKoEKNc42gDQvRlUNqiHo1HG | 2024-05-10T00:00:00.000Z
960+
2.621423611111111 | LAhW8dgJbuvWP9LgvcDhXAbo6om1ck3A2AEKCKqbxalopXodUMoz3uMBXeY3 | 2024-05-10T00:00:00.000Z
961+
2.5258595644176904 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3mDfU31KjdGhvDlv8ZkMvESD7elSu/RKt | 2024-05-10T00:00:00.000Z
962+
1.655470085470086 | LAhW8dgJbuvWP9LgvcDhXAYU6Nh3bdLj0lKjdGjLA+rkYcBfHLSx+bhGbGPB | 2024-05-10T00:00:00.000Z
963+
;
964+
965+
wrapped_rate
966+
required_capability: ts_command_v0
967+
required_capability: metrics_group_by_all
968+
969+
TS k8s
970+
| STATS max_rate = MAX(rate(network.total_bytes_in))
971+
;
972+
973+
max_rate:double
974+
13.17372515125324
788975
;

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,10 @@ public enum Cap {
10431043
*/
10441044
@Deprecated
10451045
METRICS_COMMAND(Build.current().isSnapshot()),
1046+
/**
1047+
* Enables automatically grouping by all dimension fields in TS mode queries
1048+
*/
1049+
METRICS_GROUP_BY_ALL(),
10461050

10471051
/**
10481052
* Are the {@code documents_found} and {@code values_loaded} fields available

x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,8 @@ public class Analyzer extends ParameterizedRuleExecutor<LogicalPlan, AnalyzerCon
214214
new ResolveLookupTables(),
215215
new ResolveFunctions(),
216216
new ResolveInference(),
217-
new DateMillisToNanosInEsRelation()
217+
new DateMillisToNanosInEsRelation(),
218+
new TimeSeriesGroupByAll()
218219
),
219220
new Batch<>(
220221
"Resolution",
@@ -1255,7 +1256,7 @@ private LogicalPlan resolveKeep(Project p, List<Attribute> childOutput) {
12551256
private LogicalPlan resolveDrop(Drop drop, List<Attribute> childOutput) {
12561257
List<NamedExpression> resolvedProjections = new ArrayList<>(childOutput);
12571258

1258-
for (var ne : drop.removals()) {
1259+
for (NamedExpression ne : drop.removals()) {
12591260
List<? extends NamedExpression> resolved;
12601261

12611262
if (ne instanceof UnresolvedNamePattern np) {
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
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.analysis;
9+
10+
import org.elasticsearch.xpack.esql.EsqlIllegalArgumentException;
11+
import org.elasticsearch.xpack.esql.core.expression.Alias;
12+
import org.elasticsearch.xpack.esql.core.expression.Attribute;
13+
import org.elasticsearch.xpack.esql.core.expression.Expression;
14+
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
15+
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
16+
import org.elasticsearch.xpack.esql.core.expression.NamedExpression;
17+
import org.elasticsearch.xpack.esql.core.type.DataType;
18+
import org.elasticsearch.xpack.esql.core.type.EsField;
19+
import org.elasticsearch.xpack.esql.expression.function.Functions;
20+
import org.elasticsearch.xpack.esql.expression.function.aggregate.AggregateFunction;
21+
import org.elasticsearch.xpack.esql.expression.function.aggregate.TimeSeriesAggregateFunction;
22+
import org.elasticsearch.xpack.esql.expression.function.aggregate.Values;
23+
import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
24+
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
25+
import org.elasticsearch.xpack.esql.plan.logical.TimeSeriesAggregate;
26+
import org.elasticsearch.xpack.esql.rule.Rule;
27+
28+
import java.util.ArrayList;
29+
import java.util.List;
30+
import java.util.Map;
31+
32+
/**
33+
* This rule implements the "group by all" logic for time series aggregations. It is intended to work in conjunction with
34+
* {@link org.elasticsearch.xpack.esql.optimizer.rules.logical.TranslateTimeSeriesAggregate}, and should be run before that
35+
* rule. This rule adds output columns corresponding to the dimensions on the indices involved in the query, as discovered
36+
* by the {@link org.elasticsearch.xpack.esql.session.IndexResolver}. Despite the name, this does not actually group on the
37+
* dimension values, for efficiency reasons.
38+
* <p>
39+
* This rule will operate on "bare" over time aggregations.
40+
*/
41+
public class TimeSeriesGroupByAll extends Rule<LogicalPlan, LogicalPlan> {
42+
@Override
43+
public LogicalPlan apply(LogicalPlan logicalPlan) {
44+
return logicalPlan.transformUp(node -> node instanceof TimeSeriesAggregate, this::rule);
45+
}
46+
47+
public LogicalPlan rule(TimeSeriesAggregate aggregate) {
48+
AggregateFunction lastTSAggFunction = null;
49+
AggregateFunction lastNonTSAggFunction = null;
50+
51+
List<NamedExpression> newAggregateFunctions = new ArrayList<>(aggregate.aggregates().size());
52+
for (NamedExpression agg : aggregate.aggregates()) {
53+
if (agg instanceof Alias alias && alias.child() instanceof AggregateFunction af) {
54+
if (af instanceof TimeSeriesAggregateFunction tsAgg) {
55+
newAggregateFunctions.add(new Alias(alias.source(), alias.name(), new Values(tsAgg.source(), tsAgg)));
56+
lastTSAggFunction = tsAgg;
57+
} else {
58+
newAggregateFunctions.add(agg);
59+
lastNonTSAggFunction = af;
60+
}
61+
} else {
62+
newAggregateFunctions.add(agg);
63+
}
64+
}
65+
if (lastTSAggFunction == null) {
66+
return aggregate;
67+
}
68+
69+
if (lastNonTSAggFunction != null) {
70+
throw new EsqlIllegalArgumentException(
71+
"Cannot mix time-series aggregate [{}] and regular aggregate [{}] in the same TimeSeriesAggregate.",
72+
lastTSAggFunction.sourceText(),
73+
lastNonTSAggFunction.sourceText()
74+
);
75+
}
76+
77+
var timeSeries = new FieldAttribute(
78+
aggregate.source(),
79+
null,
80+
null,
81+
MetadataAttribute.TIMESERIES,
82+
new EsField(MetadataAttribute.TIMESERIES, DataType.KEYWORD, Map.of(), false, EsField.TimeSeriesFieldType.DIMENSION)
83+
);
84+
List<Expression> groupings = new ArrayList<>();
85+
groupings.add(timeSeries);
86+
87+
for (Expression grouping : aggregate.groupings()) {
88+
if (Functions.isGrouping(Alias.unwrap(grouping)) == false) {
89+
throw new EsqlIllegalArgumentException(
90+
"Cannot mix time-series aggregate and grouping attributes. Found [{}].",
91+
grouping.sourceText()
92+
);
93+
}
94+
groupings.add(grouping);
95+
}
96+
97+
TimeSeriesAggregate newStats = new TimeSeriesAggregate(
98+
aggregate.source(),
99+
aggregate.child(),
100+
groupings,
101+
newAggregateFunctions,
102+
null
103+
);
104+
// insert the time_series
105+
return newStats.transformDown(EsRelation.class, r -> {
106+
ArrayList<Attribute> attributes = new ArrayList<>(r.output());
107+
attributes.add(timeSeries);
108+
return new EsRelation(
109+
r.source(),
110+
r.indexPattern(),
111+
r.indexMode(),
112+
r.originalIndices(),
113+
r.concreteIndices(),
114+
r.indexNameWithModes(),
115+
attributes
116+
);
117+
});
118+
}
119+
}

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static org.elasticsearch.compute.ann.Fixed.Scope.THREAD_LOCAL;
3434
import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.isString;
3535
import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD;
36+
import static org.elasticsearch.xpack.esql.core.type.DataType.TSID_DATA_TYPE;
3637

3738
public class ToBase64 extends UnaryScalarFunction {
3839
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(Expression.class, "ToBase64", ToBase64::new);
@@ -42,7 +43,10 @@ public class ToBase64 extends UnaryScalarFunction {
4243
description = "Encode a string to a base64 string.",
4344
examples = @Example(file = "string", tag = "to_base64")
4445
)
45-
public ToBase64(Source source, @Param(name = "string", type = { "keyword", "text" }, description = "A string.") Expression string) {
46+
public ToBase64(
47+
Source source,
48+
@Param(name = "string", type = { "keyword", "text", "_tsid" }, description = "A string.") Expression string
49+
) {
4650
super(source, string);
4751
}
4852

@@ -60,7 +64,9 @@ protected TypeResolution resolveType() {
6064
if (childrenResolved() == false) {
6165
return new TypeResolution("Unresolved children");
6266
}
63-
return isString(field, sourceText(), TypeResolutions.ParamOrdinal.DEFAULT);
67+
68+
return TypeResolutions.isType(field, dt -> dt == TSID_DATA_TYPE, sourceText(), TypeResolutions.ParamOrdinal.DEFAULT, "_tsid")
69+
.or(isString(field, sourceText(), TypeResolutions.ParamOrdinal.DEFAULT));
6470
}
6571

6672
@Override

0 commit comments

Comments
 (0)