diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/count_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/count_over_time.md index 24fedc1dde506..c15112b45e798 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/count_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/count_over_time.md @@ -5,3 +5,6 @@ `field` : +`window` +: the time window over which to compute the count over time + diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/first_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/first_over_time.md index 24fedc1dde506..30b5d34334502 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/first_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/first_over_time.md @@ -5,3 +5,6 @@ `field` : +`window` +: the time window over which to compute the first over time value + diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md index 24fedc1dde506..018bce5e83d5c 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/max_over_time.md @@ -5,3 +5,6 @@ `field` : +`window` +: the time window over which to compute the maximum + diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md index 24fedc1dde506..8fed303e56ac2 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/min_over_time.md @@ -5,3 +5,6 @@ `field` : +`window` +: the time window over which to compute the minimum + diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/sum_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/sum_over_time.md index 24fedc1dde506..3a462163037e6 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/sum_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/sum_over_time.md @@ -5,3 +5,6 @@ `field` : +`window` +: the time window over which to compute the standard deviation + diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/count_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/count_over_time.md index 0cf690e810426..2b17dff91893b 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/count_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/count_over_time.md @@ -2,25 +2,25 @@ **Supported types** -| field | result | -| --- | --- | -| aggregate_metric_double | long | -| boolean | long | -| cartesian_point | long | -| cartesian_shape | long | -| date | long | -| date_nanos | long | -| double | long | -| geo_point | long | -| geo_shape | long | -| geohash | long | -| geohex | long | -| geotile | long | -| integer | long | -| ip | long | -| keyword | long | -| long | long | -| text | long | -| unsigned_long | long | -| version | long | +| field | window | result | +| --- | --- | --- | +| aggregate_metric_double | | long | +| boolean | | long | +| cartesian_point | | long | +| cartesian_shape | | long | +| date | | long | +| date_nanos | | long | +| double | | long | +| geo_point | | long | +| geo_shape | | long | +| geohash | | long | +| geohex | | long | +| geotile | | long | +| integer | | long | +| ip | | long | +| keyword | | long | +| long | | long | +| text | | long | +| unsigned_long | | long | +| version | | long | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/first_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/first_over_time.md index 621b49b68739c..4ca0c6fd10d77 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/first_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/first_over_time.md @@ -2,12 +2,12 @@ **Supported types** -| field | result | -| --- | --- | -| counter_double | double | -| counter_integer | integer | -| counter_long | long | -| double | double | -| integer | integer | -| long | long | +| field | window | result | +| --- | --- | --- | +| counter_double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| counter_integer | time_duration {applies_to}`stack: preview 9.3.0` | integer | +| counter_long | time_duration {applies_to}`stack: preview 9.3.0` | long | +| double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| integer | time_duration {applies_to}`stack: preview 9.3.0` | integer | +| long | time_duration {applies_to}`stack: preview 9.3.0` | long | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md index 9ff4654e9b8c1..c3687d07d047c 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/max_over_time.md @@ -2,18 +2,18 @@ **Supported types** -| field | result | -| --- | --- | -| aggregate_metric_double | double | -| boolean | boolean | -| date | date | -| date_nanos | date_nanos | -| double | double | -| integer | integer | -| ip | ip | -| keyword | keyword | -| long | long | -| text | keyword | -| unsigned_long {applies_to}`stack: ga 9.2.0` | unsigned_long | -| version | version | +| field | window | result | +| --- | --- | --- | +| aggregate_metric_double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| boolean | time_duration {applies_to}`stack: preview 9.3.0` | boolean | +| date | time_duration {applies_to}`stack: preview 9.3.0` | date | +| date_nanos | time_duration {applies_to}`stack: preview 9.3.0` | date_nanos | +| double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| integer | time_duration {applies_to}`stack: preview 9.3.0` | integer | +| ip | time_duration {applies_to}`stack: preview 9.3.0` | ip | +| keyword | time_duration {applies_to}`stack: preview 9.3.0` | keyword | +| long | time_duration {applies_to}`stack: preview 9.3.0` | long | +| text | time_duration {applies_to}`stack: preview 9.3.0` | keyword | +| unsigned_long {applies_to}`stack: ga 9.2.0` | time_duration {applies_to}`stack: preview 9.3.0` | unsigned_long | +| version | time_duration {applies_to}`stack: preview 9.3.0` | version | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md index 9ff4654e9b8c1..c3687d07d047c 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/min_over_time.md @@ -2,18 +2,18 @@ **Supported types** -| field | result | -| --- | --- | -| aggregate_metric_double | double | -| boolean | boolean | -| date | date | -| date_nanos | date_nanos | -| double | double | -| integer | integer | -| ip | ip | -| keyword | keyword | -| long | long | -| text | keyword | -| unsigned_long {applies_to}`stack: ga 9.2.0` | unsigned_long | -| version | version | +| field | window | result | +| --- | --- | --- | +| aggregate_metric_double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| boolean | time_duration {applies_to}`stack: preview 9.3.0` | boolean | +| date | time_duration {applies_to}`stack: preview 9.3.0` | date | +| date_nanos | time_duration {applies_to}`stack: preview 9.3.0` | date_nanos | +| double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| integer | time_duration {applies_to}`stack: preview 9.3.0` | integer | +| ip | time_duration {applies_to}`stack: preview 9.3.0` | ip | +| keyword | time_duration {applies_to}`stack: preview 9.3.0` | keyword | +| long | time_duration {applies_to}`stack: preview 9.3.0` | long | +| text | time_duration {applies_to}`stack: preview 9.3.0` | keyword | +| unsigned_long {applies_to}`stack: ga 9.2.0` | time_duration {applies_to}`stack: preview 9.3.0` | unsigned_long | +| version | time_duration {applies_to}`stack: preview 9.3.0` | version | diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/sum_over_time.md b/docs/reference/query-languages/esql/_snippets/functions/types/sum_over_time.md index dce89b1781d85..356ffbbc80148 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/types/sum_over_time.md +++ b/docs/reference/query-languages/esql/_snippets/functions/types/sum_over_time.md @@ -2,10 +2,10 @@ **Supported types** -| field | result | -| --- | --- | -| aggregate_metric_double | double | -| double | double | -| integer | long | -| long | long | +| field | window | result | +| --- | --- | --- | +| aggregate_metric_double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| double | time_duration {applies_to}`stack: preview 9.3.0` | double | +| integer | time_duration {applies_to}`stack: preview 9.3.0` | long | +| long | time_duration {applies_to}`stack: preview 9.3.0` | long | diff --git a/docs/reference/query-languages/esql/images/functions/count_over_time.svg b/docs/reference/query-languages/esql/images/functions/count_over_time.svg index c43078716e0ab..ec023d543b547 100644 --- a/docs/reference/query-languages/esql/images/functions/count_over_time.svg +++ b/docs/reference/query-languages/esql/images/functions/count_over_time.svg @@ -1 +1 @@ -COUNT_OVER_TIME(field) \ No newline at end of file +COUNT_OVER_TIME(field,window) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/images/functions/first_over_time.svg b/docs/reference/query-languages/esql/images/functions/first_over_time.svg index 3f92ee2c76572..7c608cefc88bd 100644 --- a/docs/reference/query-languages/esql/images/functions/first_over_time.svg +++ b/docs/reference/query-languages/esql/images/functions/first_over_time.svg @@ -1 +1 @@ -FIRST_OVER_TIME(field) \ No newline at end of file +FIRST_OVER_TIME(field,window) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/images/functions/max_over_time.svg b/docs/reference/query-languages/esql/images/functions/max_over_time.svg index 911ed68396f4e..b93d646cf8326 100644 --- a/docs/reference/query-languages/esql/images/functions/max_over_time.svg +++ b/docs/reference/query-languages/esql/images/functions/max_over_time.svg @@ -1 +1 @@ -MAX_OVER_TIME(field) \ No newline at end of file +MAX_OVER_TIME(field,window) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/images/functions/min_over_time.svg b/docs/reference/query-languages/esql/images/functions/min_over_time.svg index f673d28b9a7a3..4439bd09d121c 100644 --- a/docs/reference/query-languages/esql/images/functions/min_over_time.svg +++ b/docs/reference/query-languages/esql/images/functions/min_over_time.svg @@ -1 +1 @@ -MIN_OVER_TIME(field) \ No newline at end of file +MIN_OVER_TIME(field,window) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/images/functions/sum_over_time.svg b/docs/reference/query-languages/esql/images/functions/sum_over_time.svg index 0327e9acdc485..5f2fdd7913baa 100644 --- a/docs/reference/query-languages/esql/images/functions/sum_over_time.svg +++ b/docs/reference/query-languages/esql/images/functions/sum_over_time.svg @@ -1 +1 @@ -SUM_OVER_TIME(field) \ No newline at end of file +SUM_OVER_TIME(field,window) \ No newline at end of file diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/first_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/first_over_time.json index 626af23d0bc2f..db0de20946745 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/first_over_time.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/first_over_time.json @@ -11,6 +11,12 @@ "type" : "counter_double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, @@ -23,6 +29,12 @@ "type" : "counter_integer", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, @@ -35,6 +47,12 @@ "type" : "counter_long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, @@ -47,6 +65,12 @@ "type" : "double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, @@ -59,6 +83,12 @@ "type" : "integer", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, @@ -71,6 +101,12 @@ "type" : "long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the first over time value" } ], "variadic" : false, diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json index c77e666ff1f8e..e7b7e92f5933f 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/max_over_time.json @@ -11,6 +11,12 @@ "type" : "aggregate_metric_double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -23,6 +29,12 @@ "type" : "boolean", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -35,6 +47,12 @@ "type" : "date", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -47,6 +65,12 @@ "type" : "date_nanos", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -59,6 +83,12 @@ "type" : "double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -71,6 +101,12 @@ "type" : "integer", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -83,6 +119,12 @@ "type" : "ip", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -95,6 +137,12 @@ "type" : "keyword", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -107,6 +155,12 @@ "type" : "long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -119,6 +173,12 @@ "type" : "text", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -131,6 +191,12 @@ "type" : "unsigned_long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, @@ -143,6 +209,12 @@ "type" : "version", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the maximum" } ], "variadic" : false, diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json index 3ce782c9dce76..35c4c975fabbb 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/min_over_time.json @@ -11,6 +11,12 @@ "type" : "aggregate_metric_double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -23,6 +29,12 @@ "type" : "boolean", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -35,6 +47,12 @@ "type" : "date", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -47,6 +65,12 @@ "type" : "date_nanos", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -59,6 +83,12 @@ "type" : "double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -71,6 +101,12 @@ "type" : "integer", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -83,6 +119,12 @@ "type" : "ip", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -95,6 +137,12 @@ "type" : "keyword", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -107,6 +155,12 @@ "type" : "long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -119,6 +173,12 @@ "type" : "text", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -131,6 +191,12 @@ "type" : "unsigned_long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, @@ -143,6 +209,12 @@ "type" : "version", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the minimum" } ], "variadic" : false, diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/sum_over_time.json b/docs/reference/query-languages/esql/kibana/definition/functions/sum_over_time.json index d0c130cd229af..e3f0d3229a229 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/sum_over_time.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/sum_over_time.json @@ -11,6 +11,12 @@ "type" : "aggregate_metric_double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the standard deviation" } ], "variadic" : false, @@ -23,6 +29,12 @@ "type" : "double", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the standard deviation" } ], "variadic" : false, @@ -35,6 +47,12 @@ "type" : "integer", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the standard deviation" } ], "variadic" : false, @@ -47,6 +65,12 @@ "type" : "long", "optional" : false, "description" : "" + }, + { + "name" : "window", + "type" : "time_duration", + "optional" : true, + "description" : "the time window over which to compute the standard deviation" } ], "variadic" : false, diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunction.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunction.java index 20eac61fbce6c..ec5ff2caf71fb 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunction.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunction.java @@ -22,7 +22,7 @@ /** * A {@link GroupingAggregatorFunction} that wraps another, and apply a window function on the final aggregation. */ -record WindowGroupingAggregatorFunction(GroupingAggregatorFunction next, AggregatorFunctionSupplier supplier, Duration window) +public record WindowGroupingAggregatorFunction(GroupingAggregatorFunction next, AggregatorFunctionSupplier supplier, Duration window) implements GroupingAggregatorFunction { diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BytesRefLongBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BytesRefLongBlockHash.java index ec52903422ee4..326f6f581934c 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BytesRefLongBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/BytesRefLongBlockHash.java @@ -39,6 +39,7 @@ public final class BytesRefLongBlockHash extends BlockHash { private final int emitBatchSize; private final BytesRefBlockHash bytesHash; private final LongLongHash finalHash; + private long minLongKey = Long.MAX_VALUE; BytesRefLongBlockHash(BlockFactory blockFactory, int bytesChannel, int longsChannel, boolean reverseOutput, int emitBatchSize) { super(blockFactory); @@ -104,7 +105,7 @@ public IntVector add(IntVector bytesHashes, LongVector longsVector) { final int[] ords = new int[positions]; int lastByte = bytesHashes.getInt(0); long lastLong = longsVector.getLong(0); - ords[0] = Math.toIntExact(hashOrdToGroup(finalHash.add(lastByte, lastLong))); + ords[0] = Math.toIntExact(hashOrdToGroup(addGroup(lastByte, lastLong))); boolean constant = true; if (bytesHashes.isConstant()) { for (int i = 1; i < positions; i++) { @@ -112,7 +113,7 @@ public IntVector add(IntVector bytesHashes, LongVector longsVector) { if (nextLong == lastLong) { ords[i] = ords[i - 1]; } else { - ords[i] = Math.toIntExact(hashOrdToGroup(finalHash.add(lastByte, nextLong))); + ords[i] = Math.toIntExact(hashOrdToGroup(addGroup(lastByte, nextLong))); lastLong = nextLong; constant = false; } @@ -124,7 +125,7 @@ public IntVector add(IntVector bytesHashes, LongVector longsVector) { if (nextByte == lastByte && nextLong == lastLong) { ords[i] = ords[i - 1]; } else { - ords[i] = Math.toIntExact(hashOrdToGroup(finalHash.add(nextByte, nextLong))); + ords[i] = Math.toIntExact(hashOrdToGroup(addGroup(nextByte, nextLong))); lastByte = nextByte; lastLong = nextLong; constant = false; @@ -215,8 +216,21 @@ public long getLongKeyFromGroup(long groupId) { return finalHash.getKey2(groupId); } - public long getGroupId(long bytesRefKey, long longKey) { - return finalHash.find(bytesRefKey, longKey); + public long getGroupId(long bytesKey, long longKey) { + return finalHash.find(bytesKey, longKey); + } + + public long addGroup(long bytesKey, long longKey) { + minLongKey = Math.min(minLongKey, longKey); + return finalHash.add(bytesKey, longKey); + } + + public long numGroups() { + return finalHash.size(); + } + + public long getMinLongKey() { + return minLongKey; } @Override diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/HashAggregationOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/HashAggregationOperator.java index 4e562352b6b76..ba61e17ae742e 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/HashAggregationOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/HashAggregationOperator.java @@ -83,7 +83,7 @@ public String describe() { private boolean finished; private Page output; - private final BlockHash blockHash; + final BlockHash blockHash; protected final List aggregators; @@ -240,7 +240,7 @@ public void finish() { var evaluationContext = evaluationContext(blockHash, keys); for (int i = 0; i < aggregators.size(); i++) { var aggregator = aggregators.get(i); - aggregator.evaluate(blocks, offset, selected, evaluationContext); + evaluateAggregator(aggregator, blocks, offset, selected, evaluationContext); offset += aggBlockCounts[i]; } output = new Page(blocks); @@ -257,6 +257,16 @@ public void finish() { } } + protected void evaluateAggregator( + GroupingAggregator aggregator, + Block[] blocks, + int offset, + IntVector selected, + GroupingAggregatorEvaluationContext evaluationContext + ) { + aggregator.evaluate(blocks, offset, selected, evaluationContext); + } + protected GroupingAggregatorEvaluationContext evaluationContext(BlockHash blockHash, Block[] keys) { return new GroupingAggregatorEvaluationContext(driverContext); } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperator.java index bc6438d8508b5..a817e0920a303 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperator.java @@ -8,21 +8,31 @@ package org.elasticsearch.compute.operator; import org.elasticsearch.common.Rounding; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.IntArray; import org.elasticsearch.compute.Describable; import org.elasticsearch.compute.aggregation.AggregatorMode; import org.elasticsearch.compute.aggregation.GroupingAggregator; import org.elasticsearch.compute.aggregation.GroupingAggregatorEvaluationContext; +import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction; import org.elasticsearch.compute.aggregation.TimeSeriesGroupingAggregatorEvaluationContext; +import org.elasticsearch.compute.aggregation.WindowGroupingAggregatorFunction; import org.elasticsearch.compute.aggregation.blockhash.BlockHash; import org.elasticsearch.compute.aggregation.blockhash.BytesRefLongBlockHash; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.ElementType; +import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.LongBlock; +import org.elasticsearch.core.AbstractRefCounted; +import org.elasticsearch.core.Releasable; +import org.elasticsearch.core.Releasables; import org.elasticsearch.index.mapper.DateFieldMapper; import java.time.Duration; import java.util.ArrayList; import java.util.List; +import java.util.Set; import java.util.function.Supplier; import static java.util.stream.Collectors.joining; @@ -69,6 +79,7 @@ public String describe() { private final Rounding.Prepared timeBucket; private final DateFieldMapper.Resolution timeResolution; + private ExpandingGroups expandingGroups = null; public TimeSeriesAggregationOperator( Rounding.Prepared timeBucket, @@ -82,6 +93,156 @@ public TimeSeriesAggregationOperator( this.timeResolution = timeResolution; } + @Override + public void finish() { + expandWindowBuckets(); + super.finish(); + } + + private long largestWindowMillis() { + long largestWindow = Long.MIN_VALUE; + for (GroupingAggregator aggregator : aggregators) { + if (aggregator.aggregatorFunction() instanceof WindowGroupingAggregatorFunction aggregatorFunction) { + largestWindow = Math.max(largestWindow, aggregatorFunction.window().toMillis()); + } + } + return largestWindow; + } + + /* + * Expands window buckets to ensure all required time buckets are present for time-series aggregations. + * This is equivalent to sliding the window over the raw input. + * + * For example, given these two data points: + * ``` + * |_tsid| cluster| host | timestamp | metric | + * | t1 | prod | h1 | 2025-04-15T01:12:00Z | 100 | + * | t2 | prod | h2 | 2025-04-15T01:14:00Z | 200 | + * ``` + * Without expanding, the within time-series aggregation yields: + * ``` + * _tsid | VALUES(cluster) | BUCKET | SUM_OVER_TIME | + * t1 | prod | 2025-04-15T01:12:00Z | 100 | + * t2 | prod | 2025-04-15T01:14:00Z | 200 | + * ``` + * And the final result is: + * ``` + * cluster | bucket | SUM | + * prod | 2025-04-15T01:12:00Z | 100 | + * prod | 2025-04-15T01:14:00Z | 200 | + * ``` + * + * While `bucket=5s` and no window: + * ``` + * TS ... + * | WHERE TRANGE('2025-04-15T01:10:00Z', '2025-04-15T01:15:00Z') + * | STATS sum(sum_over_time(metric)) BY host, TBUCKET(5s) + * ``` + * Yields: + * ``` + * cluster | bucket | SUM | + * prod | 2025-04-15T01:10:00Z | 300 | + * ``` + * + * The correct result should be as if we slide over the raw input: + * ``` + * cluster | bucket | SUM | + * prod | 2025-04-15T01:10:00Z | 300 | + * prod | 2025-04-15T01:11:00Z | 300 | + * prod | 2025-04-15T01:12:00Z | 300 | + * prod | 2025-04-15T01:13:00Z | 200 | + * prod | 2025-04-15T01:14:00Z | 200 | + * ``` + * + * In order to achieve this, we need to fill in the missing buckets between (timestamp-window, timestamp) + * during the aggregation phase, so that the within time-series aggregation produces: + * ``` + * _tsid |VALUES(cluster) | BUCKET | SUM_OVER_TIME | + * t1 | prod | 2025-04-15T01:10:00Z | 100 | + * t1 | prod | 2025-04-15T01:11:00Z | 100 | + * t1 | prod | 2025-04-15T01:12:00Z | 100 | + * t2 | prod | 2025-04-15T01:10:00Z | 200 | + * t2 | prod | 2025-04-15T01:11:00Z | 200 | + * t2 | prod | 2025-04-15T01:12:00Z | 200 | + * t2 | prod | 2025-04-15T01:13:00Z | 200 | + * t2 | prod | 2025-04-15T01:14:00Z | 200 | + * ``` + */ + private void expandWindowBuckets() { + for (GroupingAggregator aggregator : aggregators) { + if (aggregator.mode().isOutputPartial()) { + return; + } + } + final long windowMillis = largestWindowMillis(); + if (windowMillis <= 0) { + return; + } + BytesRefLongBlockHash tsBlockHash = (BytesRefLongBlockHash) blockHash; + final long numGroups = tsBlockHash.numGroups(); + if (numGroups == 0) { + return; + } + this.expandingGroups = new ExpandingGroups(driverContext.bigArrays()); + for (long groupId = 0; groupId < numGroups; groupId++) { + long tsid = tsBlockHash.getBytesRefKeyFromGroup(groupId); + long endTimestamp = tsBlockHash.getLongKeyFromGroup(groupId); + long bucket = timeBucket.nextRoundingValue(endTimestamp - timeResolution.convert(largestWindowMillis())); + bucket = Math.max(bucket, tsBlockHash.getMinLongKey()); + // Fill the missing buckets between (timestamp-window, timestamp) + while (bucket < endTimestamp) { + if (tsBlockHash.addGroup(tsid, bucket) >= 0) { + expandingGroups.addGroup(Math.toIntExact(groupId)); + } + bucket = timeBucket.nextRoundingValue(bucket); + } + } + } + + @Override + protected void evaluateAggregator( + GroupingAggregator aggregator, + Block[] blocks, + int offset, + IntVector selected, + GroupingAggregatorEvaluationContext evaluationContext + ) { + if (expandingGroups != null && expandingGroups.count > 0 && isValuesAggregator(aggregator.aggregatorFunction())) { + try (var valuesSelected = selectedForValuesAggregator(driverContext.blockFactory(), selected, expandingGroups)) { + super.evaluateAggregator(aggregator, blocks, offset, valuesSelected, evaluationContext); + } + } else { + super.evaluateAggregator(aggregator, blocks, offset, selected, evaluationContext); + } + } + + private static IntVector selectedForValuesAggregator(BlockFactory blockFactory, IntVector selected, ExpandingGroups expandingGroups) { + try (var builder = blockFactory.newIntVectorFixedBuilder(selected.getPositionCount())) { + int first = selected.getPositionCount() - expandingGroups.count; + for (int i = 0; i < first; i++) { + builder.appendInt(i, selected.getInt(i)); + } + for (int i = 0; i < expandingGroups.count; i++) { + builder.appendInt(first + i, expandingGroups.getGroup(i)); + } + return builder.build(); + } + } + + // generated classes are not available during javadoc + private static final Set VALUES_CLASSES = Set.of( + "org.elasticsearch.compute.aggregation.ValuesBooleanGroupingAggregatorFunction", + "org.elasticsearch.compute.aggregation.ValuesBytesRefGroupingAggregatorFunction", + "org.elasticsearch.compute.aggregation.ValuesIntGroupingAggregatorFunction", + "org.elasticsearch.compute.aggregation.ValuesLongGroupingAggregatorFunction", + "org.elasticsearch.compute.aggregation.ValuesDoubleGroupingAggregatorFunction", + "org.elasticsearch.compute.aggregation.DimensionValuesByteRefGroupingAggregatorFunction" + ); + + static boolean isValuesAggregator(GroupingAggregatorFunction aggregatorFunction) { + return VALUES_CLASSES.contains(aggregatorFunction.getClass().getName()); + } + @Override protected GroupingAggregatorEvaluationContext evaluationContext(BlockHash blockHash, Block[] keys) { if (keys.length < 2) { @@ -104,21 +265,54 @@ public long rangeEndInMillis(int groupId) { @Override public List groupIdsFromWindow(int startingGroupId, Duration window) { - long ordinal = hash.getBytesRefKeyFromGroup(startingGroupId); - long startTimestamp = hash.getLongKeyFromGroup(startingGroupId); + long tsid = hash.getBytesRefKeyFromGroup(startingGroupId); + long bucket = hash.getLongKeyFromGroup(startingGroupId); List results = new ArrayList<>(); results.add(startingGroupId); - long endTimestamp = startTimestamp + timeResolution.convert(window.toMillis()); - long nextTimestamp = startTimestamp; - while ((nextTimestamp = timeBucket.nextRoundingValue(nextTimestamp)) < endTimestamp) { - long nextGroupId = hash.getGroupId(ordinal, nextTimestamp); + long endTimestamp = bucket + timeResolution.convert(window.toMillis()); + while ((bucket = timeBucket.nextRoundingValue(bucket)) < endTimestamp) { + long nextGroupId = hash.getGroupId(tsid, bucket); if (nextGroupId != -1) { results.add(Math.toIntExact(nextGroupId)); } } - assert nextTimestamp == endTimestamp : "expected to end at the original timestamp bucket"; return results; } }; } + + static class ExpandingGroups extends AbstractRefCounted implements Releasable { + private final BigArrays bigArrays; + private IntArray newGroups; + private int count; + + ExpandingGroups(BigArrays bigArrays) { + this.bigArrays = bigArrays; + this.newGroups = bigArrays.newIntArray(128); + } + + void addGroup(int groupId) { + newGroups = bigArrays.grow(newGroups, count + 1); + newGroups.set(count++, groupId); + } + + int getGroup(int index) { + return newGroups.get(index); + } + + @Override + protected void closeInternal() { + newGroups.close(); + } + + @Override + public void close() { + decRef(); + } + } + + @Override + public void close() { + Releasables.close(expandingGroups, super::close); + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunctionTests.java index 2fb70e60e2a89..6895c62bf8bf9 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/WindowGroupingAggregatorFunctionTests.java @@ -59,6 +59,7 @@ protected Operator.OperatorFactory simpleWithMode(SimpleOptions options, Aggrega protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { final long START_TIME = DateFieldMapper.DEFAULT_DATE_TIME_FORMATTER.parseMillis("2025-11-13"); List groups = List.of(new BytesRef("a"), new BytesRef("b"), new BytesRef("c"), new BytesRef("d")); + size = 2; List> rows = new ArrayList<>(size); for (int i = 0; i < size; i++) { long tsOffset = randomLongBetween(0, 20 * 60 * 1000); @@ -81,35 +82,31 @@ public String toString() { } Map expected = new TreeMap<>(Comparator.comparing(Key::tsid).thenComparingLong(Key::bucket)); // original groups + long oneMinute = TimeValue.timeValueMinutes(1).millis(); + long smallestBucket = Long.MAX_VALUE; for (Page page : input) { - BytesRefBlock tsids = page.getBlock(0); LongBlock timestamp = page.getBlock(1); - IntBlock values = page.getBlock(2); - var scratch = new BytesRef(); - for (int p = 0; p < page.getPositionCount(); p++) { - long bucket = timestamp.getLong(p); - var tsid = tsids.getBytesRef(p, scratch).utf8ToString(); - Key key = new Key(tsid, bucket); - long val = values.getInt(p); - expected.merge(key, val, Long::sum); + for (int p = 0; p < timestamp.getPositionCount(); p++) { + smallestBucket = Math.min(timestamp.getLong(p), smallestBucket); } } - // window for (Page page : input) { BytesRefBlock tsids = page.getBlock(0); LongBlock timestamp = page.getBlock(1); IntBlock values = page.getBlock(2); var scratch = new BytesRef(); - for (int m = 1; m <= 4; m++) { - long offset = TimeValue.timeValueMinutes(m).millis(); - for (int p = 0; p < page.getPositionCount(); p++) { - long bucket = timestamp.getLong(p) - offset; - var tsid = tsids.getBytesRef(p, scratch).utf8ToString(); - Key key = new Key(tsid, bucket); - long val = values.getInt(p); - if (expected.containsKey(key)) { + for (int p = 0; p < page.getPositionCount(); p++) { + long bucket = timestamp.getLong(p); + var tsid = tsids.getBytesRef(p, scratch).utf8ToString(); + // slide the window over the last 5 minutes + // bucket = 00:06 -> it should generate buckets at 00:02, 00:03, 00:04, 00:05, 00:06 + for (int i = 0; i < 5; i++) { + if (bucket >= smallestBucket) { + Key key = new Key(tsid, bucket); + long val = values.getInt(p); expected.merge(key, val, Long::sum); } + bucket = bucket - oneMinute; } } } @@ -173,4 +170,8 @@ protected final int aggregatorIntermediateBlockCount() { return agg.intermediateBlockCount(); } } + + public void testMissingGroup() { + + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperatorTests.java new file mode 100644 index 0000000000000..fa98f81fd9402 --- /dev/null +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TimeSeriesAggregationOperatorTests.java @@ -0,0 +1,40 @@ +/* + * 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.compute.operator; + +import org.elasticsearch.compute.aggregation.DimensionValuesByteRefGroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.GroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.ValuesBooleanGroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.ValuesBytesRefGroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.ValuesIntGroupingAggregatorFunction; +import org.elasticsearch.compute.aggregation.ValuesLongGroupingAggregatorFunction; +import org.elasticsearch.compute.data.BlockFactory; +import org.elasticsearch.compute.test.ComputeTestCase; + +import java.util.List; +import java.util.function.BiFunction; + +public class TimeSeriesAggregationOperatorTests extends ComputeTestCase { + + public void testValuesAggregator() { + BlockFactory blockFactory = blockFactory(); + DriverContext driverContext = new DriverContext(blockFactory.bigArrays(), blockFactory, "test"); + List, DriverContext, GroupingAggregatorFunction>> functions = List.of( + ValuesBooleanGroupingAggregatorFunction::create, + ValuesIntGroupingAggregatorFunction::create, + ValuesLongGroupingAggregatorFunction::create, + ValuesBytesRefGroupingAggregatorFunction::create, + DimensionValuesByteRefGroupingAggregatorFunction::new + ); + for (var fn : functions) { + try (GroupingAggregatorFunction aggregator = fn.apply(List.of(randomNonNegativeInt()), driverContext)) { + assertTrue(TimeSeriesAggregationOperator.isValuesAggregator(aggregator)); + } + } + } +} diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-avg-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-avg-over-time.csv-spec index ec6b0b60ff1f3..b0113cb200576 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-avg-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-avg-over-time.csv-spec @@ -210,7 +210,7 @@ events:double | pod:keyword | time_bucket:datetime avg_over_time_with_window required_capability: ts_command_v0 -required_capability: time_series_window_v0 +required_capability: time_series_window_v1 // tag::avg_over_time_with_window[] TS k8s | STATS events = sum(avg_over_time(events_received, 10minute)) by pod, time_bucket = tbucket(5minute) diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-count-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-count-over-time.csv-spec index edaab92cab869..e3a7d40ba841f 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-count-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-count-over-time.csv-spec @@ -30,6 +30,38 @@ bytes_in:long | cluster:keyword | time_bucket:datetime 6 | qa | 2024-05-10T00:20:00.000Z ; +count_over_time_with_window +required_capability: ts_command_v0 +required_capability: time_series_window_v1 + +TS k8s +| STATS bytes_in = sum(count_over_time(network.bytes_in, 10 minute)) BY cluster, time_bucket = tbucket(2minute) +| SORT time_bucket, cluster +| LIMIT 20; + +bytes_in:long | cluster:keyword | time_bucket:datetime +23 | prod | 2024-05-10T00:00:00.000Z +37 | qa | 2024-05-10T00:00:00.000Z +24 | staging | 2024-05-10T00:00:00.000Z +21 | prod | 2024-05-10T00:02:00.000Z +43 | qa | 2024-05-10T00:02:00.000Z +24 | staging | 2024-05-10T00:02:00.000Z +22 | prod | 2024-05-10T00:04:00.000Z +42 | qa | 2024-05-10T00:04:00.000Z +25 | staging | 2024-05-10T00:04:00.000Z +25 | prod | 2024-05-10T00:06:00.000Z +41 | qa | 2024-05-10T00:06:00.000Z +30 | staging | 2024-05-10T00:06:00.000Z +32 | prod | 2024-05-10T00:08:00.000Z +41 | qa | 2024-05-10T00:08:00.000Z +30 | staging | 2024-05-10T00:08:00.000Z +31 | prod | 2024-05-10T00:10:00.000Z +39 | qa | 2024-05-10T00:10:00.000Z +23 | staging | 2024-05-10T00:10:00.000Z +33 | prod | 2024-05-10T00:12:00.000Z +35 | qa | 2024-05-10T00:12:00.000Z +; + count_over_time_of_boolean required_capability: ts_command_v0 TS k8s | STATS eth0_up = min(count_over_time(network.eth0.up)) BY cluster, time_bucket = bucket(@timestamp,10minute) | SORT time_bucket, cluster | LIMIT 10; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-first-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-first-over-time.csv-spec index e871415630189..ac119a1a10b5f 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-first-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-first-over-time.csv-spec @@ -48,6 +48,33 @@ tx:long | cluster:keyword | time_bucket:datetime 749 | staging | 2024-05-10T00:20:00.000Z ; +first_over_time_with_window +required_capability: ts_command_v0 +required_capability: time_series_window_v1 + +TS k8s | WHERE pod == "one" +| STATS tx = sum(first_over_time(network.bytes_in, 10 minute)) BY cluster, time_bucket = tbucket(5minute) +| SORT time_bucket, cluster +| LIMIT 20; + +tx:long | cluster:keyword | time_bucket:datetime +354 | prod | 2024-05-10T00:00:00.000Z +278 | qa | 2024-05-10T00:00:00.000Z +626 | staging | 2024-05-10T00:00:00.000Z +485 | prod | 2024-05-10T00:05:00.000Z +839 | qa | 2024-05-10T00:05:00.000Z +680 | staging | 2024-05-10T00:05:00.000Z +262 | prod | 2024-05-10T00:10:00.000Z +114 | qa | 2024-05-10T00:10:00.000Z +604 | staging | 2024-05-10T00:10:00.000Z +354 | prod | 2024-05-10T00:15:00.000Z +219 | qa | 2024-05-10T00:15:00.000Z +516 | staging | 2024-05-10T00:15:00.000Z +953 | prod | 2024-05-10T00:20:00.000Z +917 | qa | 2024-05-10T00:20:00.000Z +749 | staging | 2024-05-10T00:20:00.000Z +; + first_over_time_older_than_10d required_capability: ts_command_v0 TS k8s | WHERE cluster == "qa" AND @timestamp < now() - 10 day | STATS cost = avg(first_over_time(network.eth0.rx)) BY pod, time_bucket = bucket(@timestamp, 10minute) | SORT time_bucket, pod | LIMIT 5; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-max-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-max-over-time.csv-spec index cb009954bb2f8..bbad782fbd564 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-max-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-max-over-time.csv-spec @@ -50,6 +50,38 @@ ip:ip | cluster:keyword | time_bucket:datetime 10.10.20.34 | staging | 2024-05-10T00:03:00.000Z ; +max_over_time_with_window +required_capability: ts_command_v0 +required_capability: time_series_window_v1 + +TS k8s +| STATS ip = max(max_over_time(client.ip, 3 minute)) BY cluster, time_bucket = bucket(@timestamp,1minute) +| SORT time_bucket, cluster +| LIMIT 20; + +ip:ip | cluster:keyword | time_bucket:datetime +10.10.20.35 | prod | 2024-05-10T00:00:00.000Z +10.10.20.34 | qa | 2024-05-10T00:00:00.000Z +10.10.20.35 | staging | 2024-05-10T00:00:00.000Z +10.10.20.35 | prod | 2024-05-10T00:01:00.000Z +10.10.20.35 | qa | 2024-05-10T00:01:00.000Z +10.10.20.35 | staging | 2024-05-10T00:01:00.000Z +10.10.20.35 | prod | 2024-05-10T00:02:00.000Z +10.10.20.35 | qa | 2024-05-10T00:02:00.000Z +10.10.20.35 | staging | 2024-05-10T00:02:00.000Z +10.10.20.35 | prod | 2024-05-10T00:03:00.000Z +10.10.20.35 | qa | 2024-05-10T00:03:00.000Z +10.10.20.34 | staging | 2024-05-10T00:03:00.000Z +10.10.20.35 | prod | 2024-05-10T00:04:00.000Z +10.10.20.35 | qa | 2024-05-10T00:04:00.000Z +10.10.20.34 | staging | 2024-05-10T00:04:00.000Z +10.10.20.35 | prod | 2024-05-10T00:05:00.000Z +10.10.20.35 | qa | 2024-05-10T00:05:00.000Z +10.10.20.34 | staging | 2024-05-10T00:05:00.000Z +10.10.20.35 | prod | 2024-05-10T00:06:00.000Z +10.10.20.35 | qa | 2024-05-10T00:06:00.000Z +; + max_over_time_of_long required_capability: ts_command_v0 TS k8s | STATS bytes_in = sum(max_over_time(network.bytes_in)) BY time_bucket = bucket(@timestamp,1minute) | SORT bytes_in DESC, time_bucket | LIMIT 10; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-min-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-min-over-time.csv-spec index 4f6baf466a938..3ac4c55bbd4ee 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-min-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-min-over-time.csv-spec @@ -67,6 +67,38 @@ bytes_in:long | time_bucket:datetime 2430 | 2024-05-10T00:19:00.000Z ; +min_over_time_with_window +required_capability: ts_command_v0 +required_capability: time_series_window_v1 + +TS k8s +| STATS bytes_in = sum(min_over_time(network.bytes_in, 3 minute)) BY time_bucket = tbucket(1minute) +| SORT time_bucket +| LIMIT 20; + +bytes_in:long | time_bucket:datetime +3701 | 2024-05-10T00:00:00.000Z +3925 | 2024-05-10T00:01:00.000Z +3243 | 2024-05-10T00:02:00.000Z +2074 | 2024-05-10T00:03:00.000Z +801 | 2024-05-10T00:04:00.000Z +513 | 2024-05-10T00:05:00.000Z +1569 | 2024-05-10T00:06:00.000Z +1582 | 2024-05-10T00:07:00.000Z +1637 | 2024-05-10T00:08:00.000Z +1226 | 2024-05-10T00:09:00.000Z +2238 | 2024-05-10T00:10:00.000Z +2640 | 2024-05-10T00:11:00.000Z +1517 | 2024-05-10T00:12:00.000Z +2774 | 2024-05-10T00:13:00.000Z +2732 | 2024-05-10T00:14:00.000Z +1665 | 2024-05-10T00:15:00.000Z +779 | 2024-05-10T00:16:00.000Z +1760 | 2024-05-10T00:17:00.000Z +2103 | 2024-05-10T00:18:00.000Z +4632 | 2024-05-10T00:19:00.000Z +; + min_over_time_of_long_grouping required_capability: ts_command_v0 TS k8s | STATS bytes_in = sum(min_over_time(network.bytes_in)) BY cluster, time_bucket = bucket(@timestamp,1minute) | SORT bytes_in DESC, time_bucket | LIMIT 10; diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-rate.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-rate.csv-spec index 48db7b1ddce97..31a791c019832 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-rate.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-rate.csv-spec @@ -39,28 +39,31 @@ rate_bytes_in:double | cluster:keyword | time_bucket:datetime rate_with_window required_capability: ts_command_v0 -required_capability: time_series_window_v0 +required_capability: time_series_window_v1 // tag::rate_with_window[] TS k8s | WHERE TRANGE("2024-05-10T00:05:00.000Z", "2024-05-10T00:10:00.000Z") | STATS rate_bytes_in=avg(rate(network.total_bytes_in, 5minute)) BY cluster, time_bucket = bucket(@timestamp,1minute) // end::rate_with_window[] -| SORT time_bucket, cluster | LIMIT 20; +| SORT time_bucket, cluster | LIMIT 30; // tag::rate_with_window-result[] rate_bytes_in:double | cluster:keyword | time_bucket:datetime -4.6385399587454375 | prod | 2024-05-10T00:05:00.000Z +3.3993754763729274 | prod | 2024-05-10T00:05:00.000Z 13.638384356589611 | qa | 2024-05-10T00:05:00.000Z -5.025874999999999 | prod | 2024-05-10T00:06:00.000Z +7.307225056633641 | staging | 2024-05-10T00:05:00.000Z +2.323347222222222 | prod | 2024-05-10T00:06:00.000Z 12.1780146563676 | qa | 2024-05-10T00:06:00.000Z -8.963922302737519 | staging | 2024-05-10T00:06:00.000Z +7.366225979602791 | staging | 2024-05-10T00:06:00.000Z +// end::rate_with_window-result[] +2.307777777777778 | prod | 2024-05-10T00:07:00.000Z 10.485661064982633 | qa | 2024-05-10T00:07:00.000Z +5.233055555555556 | staging | 2024-05-10T00:07:00.000Z 2.1985625418700354 | prod | 2024-05-10T00:08:00.000Z -5.243666666666666 | qa | 2024-05-10T00:08:00.000Z +5.589111111111111 | qa | 2024-05-10T00:08:00.000Z 5.098564814814814 | staging | 2024-05-10T00:08:00.000Z 2.4409081196581193 | prod | 2024-05-10T00:09:00.000Z 5.502833333333333 | qa | 2024-05-10T00:09:00.000Z 3.8278 | staging | 2024-05-10T00:09:00.000Z -// end::rate_with_window-result[] ; rate_of_double_no_grouping diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-sum-over-time.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-sum-over-time.csv-spec index 9d985ac8ae780..a5ad315b69bfd 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-sum-over-time.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/k8s-timeseries-sum-over-time.csv-spec @@ -95,6 +95,34 @@ tx:long | cluster:keyword | time_bucket:datetime 987 | staging | 2024-05-10T00:20:00.000Z ; +sum_over_time_with_window +required_capability: ts_command_v0 +required_capability: time_series_window_v1 + +TS k8s +| WHERE pod == "one" +| STATS tx = sum(sum_over_time(network.bytes_in, 10m)) BY cluster, time_bucket = TBUCKET(5minute) +| SORT time_bucket, cluster +| LIMIT 20; + +tx:long | cluster:keyword | time_bucket:datetime +2637 | prod | 2024-05-10T00:00:00.000Z +5792 | qa | 2024-05-10T00:00:00.000Z +2965 | staging | 2024-05-10T00:00:00.000Z +4613 | prod | 2024-05-10T00:05:00.000Z +8912 | qa | 2024-05-10T00:05:00.000Z +3369 | staging | 2024-05-10T00:05:00.000Z +6617 | prod | 2024-05-10T00:10:00.000Z +6946 | qa | 2024-05-10T00:10:00.000Z +2208 | staging | 2024-05-10T00:10:00.000Z +5214 | prod | 2024-05-10T00:15:00.000Z +4292 | qa | 2024-05-10T00:15:00.000Z +1507 | staging | 2024-05-10T00:15:00.000Z +1900 | prod | 2024-05-10T00:20:00.000Z +1320 | qa | 2024-05-10T00:20:00.000Z +987 | staging | 2024-05-10T00:20:00.000Z +; + sum_over_time_older_than_10d required_capability: ts_command_v0 required_capability: aggregate_metric_double_v0 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 60b693601dd5a..f3703ebdeb64a 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 @@ -1677,7 +1677,7 @@ public enum Cap { /** * Support grouping window in time-series for example: rate(counter, "1m") or avg_over_time(field, "5m") */ - TIME_SERIES_WINDOW_V0, + TIME_SERIES_WINDOW_V1, /** * PromQL support in ESQL 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 efe8e17497a38..f6322d0f87ab2 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 @@ -539,18 +539,18 @@ private static FunctionDefinition[][] functions() { defTS(Delta.class, bi(Delta::new), "delta"), defTS(Increase.class, bi(Increase::new), "increase"), defTS(Deriv.class, bi(Deriv::new), "deriv"), - def(MaxOverTime.class, uni(MaxOverTime::new), "max_over_time"), - def(MinOverTime.class, uni(MinOverTime::new), "min_over_time"), - def(SumOverTime.class, uni(SumOverTime::new), "sum_over_time"), + def(MaxOverTime.class, bi(MaxOverTime::new), "max_over_time"), + def(MinOverTime.class, bi(MinOverTime::new), "min_over_time"), + def(SumOverTime.class, bi(SumOverTime::new), "sum_over_time"), def(StdDevOverTime.class, uni(StdDevOverTime::new), "stddev_over_time"), def(VarianceOverTime.class, uni(VarianceOverTime::new), "variance_over_time", "stdvar_over_time"), - def(CountOverTime.class, uni(CountOverTime::new), "count_over_time"), + def(CountOverTime.class, bi(CountOverTime::new), "count_over_time"), def(CountDistinctOverTime.class, bi(CountDistinctOverTime::new), "count_distinct_over_time"), def(PresentOverTime.class, uni(PresentOverTime::new), "present_over_time"), def(AbsentOverTime.class, uni(AbsentOverTime::new), "absent_over_time"), def(AvgOverTime.class, bi(AvgOverTime::new), "avg_over_time"), defTS3(LastOverTime.class, LastOverTime::new, "last_over_time"), - defTS(FirstOverTime.class, bi(FirstOverTime::new), "first_over_time"), + defTS3(FirstOverTime.class, FirstOverTime::new, "first_over_time"), def(PercentileOverTime.class, bi(PercentileOverTime::new), "percentile_over_time"), // dense vector function def(TextEmbedding.class, bi(TextEmbedding::new), "text_embedding") } }; diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java index b285d372a83a8..ed37ccdb273f1 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTime.java @@ -19,17 +19,19 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; +import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; import org.elasticsearch.xpack.esql.expression.function.Param; import java.io.IOException; import java.util.List; +import java.util.Objects; import static java.util.Collections.emptyList; /** * Similar to {@link Count}, but it is used to calculate the count of values over a time series from the given field. */ -public class CountOverTime extends TimeSeriesAggregateFunction { +public class CountOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "CountOverTime", @@ -68,9 +70,15 @@ public CountOverTime( "text", "unsigned_long", "version" } - ) Expression field + ) Expression field, + @Param( + name = "window", + type = { "time_duration" }, + description = "the time window over which to compute the count over time", + optional = true + ) Expression window ) { - this(source, field, Literal.TRUE, NO_WINDOW); + this(source, field, Literal.TRUE, Objects.requireNonNullElse(window, NO_WINDOW)); } public CountOverTime(Source source, Expression field, Expression filter, Expression window) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTime.java index 70b0a2d7a3c11..d3bcf63489f73 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTime.java @@ -33,6 +33,7 @@ import java.io.IOException; import java.util.List; +import java.util.Objects; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.DEFAULT; import static org.elasticsearch.xpack.esql.core.expression.TypeResolutions.ParamOrdinal.SECOND; @@ -62,9 +63,15 @@ public FirstOverTime( name = "field", type = { "counter_long", "counter_integer", "counter_double", "long", "integer", "double" } ) Expression field, + @Param( + name = "window", + type = { "time_duration" }, + description = "the time window over which to compute the first over time value", + optional = true + ) Expression window, Expression timestamp ) { - this(source, field, Literal.TRUE, NO_WINDOW, timestamp); + this(source, field, Literal.TRUE, Objects.requireNonNullElse(window, NO_WINDOW), timestamp); } public FirstOverTime(Source source, Expression field, Expression filter, Expression window, Expression timestamp) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java index fe875eb0fd7db..1f9f92c55f328 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTime.java @@ -19,17 +19,19 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; +import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; import org.elasticsearch.xpack.esql.expression.function.Param; import java.io.IOException; import java.util.List; +import java.util.Objects; import static java.util.Collections.emptyList; /** * Similar to {@link Max}, but it is used to calculate the maximum value over a time series of values from the given field. */ -public class MaxOverTime extends TimeSeriesAggregateFunction { +public class MaxOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "MaxOverTime", @@ -61,9 +63,15 @@ public MaxOverTime( "text", "unsigned_long", "version" } - ) Expression field + ) Expression field, + @Param( + name = "window", + type = { "time_duration" }, + description = "the time window over which to compute the maximum", + optional = true + ) Expression window ) { - this(source, field, Literal.TRUE, NO_WINDOW); + this(source, field, Literal.TRUE, Objects.requireNonNullElse(window, NO_WINDOW)); } public MaxOverTime(Source source, Expression field, Expression filter, Expression window) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java index b94ff220eeac4..7da14818f8850 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTime.java @@ -19,17 +19,19 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; +import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; import org.elasticsearch.xpack.esql.expression.function.Param; import java.io.IOException; import java.util.List; +import java.util.Objects; import static java.util.Collections.emptyList; /** * Similar to {@link Min}, but it is used to calculate the minimum value over a time series of values from the given field. */ -public class MinOverTime extends TimeSeriesAggregateFunction { +public class MinOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "MinOverTime", @@ -61,9 +63,15 @@ public MinOverTime( "text", "unsigned_long", "version" } - ) Expression field + ) Expression field, + @Param( + name = "window", + type = { "time_duration" }, + description = "the time window over which to compute the minimum", + optional = true + ) Expression window ) { - this(source, field, Literal.TRUE, NO_WINDOW); + this(source, field, Literal.TRUE, Objects.requireNonNullElse(window, NO_WINDOW)); } public MinOverTime(Source source, Expression field, Expression filter, Expression window) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java index cc9509082d1c0..5830f60a8d3f7 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTime.java @@ -19,17 +19,19 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.FunctionInfo; import org.elasticsearch.xpack.esql.expression.function.FunctionType; +import org.elasticsearch.xpack.esql.expression.function.OptionalArgument; import org.elasticsearch.xpack.esql.expression.function.Param; import java.io.IOException; import java.util.List; +import java.util.Objects; import static java.util.Collections.emptyList; /** * Similar to {@link Sum}, but it is used to calculate the sum of values over a time series from the given field. */ -public class SumOverTime extends TimeSeriesAggregateFunction { +public class SumOverTime extends TimeSeriesAggregateFunction implements OptionalArgument { public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry( Expression.class, "SumOverTime", @@ -46,9 +48,15 @@ public class SumOverTime extends TimeSeriesAggregateFunction { ) public SumOverTime( Source source, - @Param(name = "field", type = { "aggregate_metric_double", "double", "integer", "long" }) Expression field + @Param(name = "field", type = { "aggregate_metric_double", "double", "integer", "long" }) Expression field, + @Param( + name = "window", + type = { "time_duration" }, + description = "the time window over which to compute the standard deviation", + optional = true + ) Expression window ) { - this(source, field, Literal.TRUE, NO_WINDOW); + this(source, field, Literal.TRUE, Objects.requireNonNullElse(window, NO_WINDOW)); } public SumOverTime(Source source, Expression field, Expression filter, Expression window) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/promql/function/PromqlFunctionRegistry.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/promql/function/PromqlFunctionRegistry.java index 9d14c5891993e..8f85b5c215be0 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/promql/function/PromqlFunctionRegistry.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/promql/function/PromqlFunctionRegistry.java @@ -71,10 +71,10 @@ private static FunctionDefinition[][] functionDefinitions() { // Aggregation range functions new FunctionDefinition[] { withinSeriesOverTimeWithWindow("avg_over_time", AvgOverTime::new), - withinSeriesOverTime("count_over_time", CountOverTime::new), - withinSeriesOverTime("max_over_time", MaxOverTime::new), - withinSeriesOverTime("min_over_time", MinOverTime::new), - withinSeriesOverTime("sum_over_time", SumOverTime::new), + withinSeriesOverTimeWithWindow("count_over_time", CountOverTime::new), + withinSeriesOverTimeWithWindow("max_over_time", MaxOverTime::new), + withinSeriesOverTimeWithWindow("min_over_time", MinOverTime::new), + withinSeriesOverTimeWithWindow("sum_over_time", SumOverTime::new), withinSeriesOverTime("stddev_over_time", StdDevOverTime::new), withinSeriesOverTime("stdvar_over_time", VarianceOverTime::new) }, // Selection range functions (require timestamp) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTimeTests.java index dc2520cb509c9..8175ed836d6da 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/CountOverTimeTests.java @@ -30,6 +30,6 @@ public static Iterable parameters() { @Override protected Expression build(Source source, List args) { - return new CountOverTime(source, args.get(0)); + return new CountOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTimeTests.java index af0efff1e754e..7fe78e08f1271 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/FirstOverTimeTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.AbstractAggregationTestCase; import org.elasticsearch.xpack.esql.expression.function.DocsV3Support; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.MultiRowTestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; @@ -24,6 +25,7 @@ import java.util.function.Supplier; import java.util.stream.IntStream; +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.appliesTo; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; @@ -101,6 +103,8 @@ private static TestCaseSupplier makeSupplier(TestCaseSupplier.TypedDataSupplier public static List signatureTypes(List params) { assertThat(params, hasSize(2)); assertThat(params.get(1).dataType(), equalTo(DataType.DATETIME)); - return List.of(params.get(0)); + var preview = appliesTo(FunctionAppliesToLifecycle.PREVIEW, "9.3.0", "", false); + DocsV3Support.Param window = new DocsV3Support.Param(DataType.TIME_DURATION, List.of(preview)); + return List.of(params.get(0), window); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeErrorTests.java index 9f73a45738f99..628bce821009d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeErrorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeErrorTests.java @@ -27,7 +27,7 @@ protected List cases() { @Override protected Expression build(Source source, List args) { - return new MaxOverTime(source, args.get(0)); + return new MaxOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeTests.java index d4275453fed51..20e342fe3cfc2 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MaxOverTimeTests.java @@ -11,12 +11,17 @@ 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.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.DocsV3Support; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import java.util.List; import java.util.function.Supplier; +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.appliesTo; + public class MaxOverTimeTests extends AbstractFunctionTestCase { public MaxOverTimeTests(Supplier testCaseSupplier) { testCase = testCaseSupplier.get(); @@ -29,6 +34,12 @@ public static Iterable parameters() { @Override protected Expression build(Source source, List args) { - return new MaxOverTime(source, args.get(0)); + return new MaxOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); + } + + public static List signatureTypes(List params) { + var preview = appliesTo(FunctionAppliesToLifecycle.PREVIEW, "9.3.0", "", false); + DocsV3Support.Param window = new DocsV3Support.Param(DataType.TIME_DURATION, List.of(preview)); + return List.of(params.get(0), window); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeErrorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeErrorTests.java index f7d93a034292e..4f14d81132d1d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeErrorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeErrorTests.java @@ -27,7 +27,7 @@ protected List cases() { @Override protected Expression build(Source source, List args) { - return new MinOverTime(source, args.get(0)); + return new MinOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeTests.java index 13fc6b4888dc7..8a790d995cd40 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/MinOverTimeTests.java @@ -11,12 +11,17 @@ 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.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.DocsV3Support; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import java.util.List; import java.util.function.Supplier; +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.appliesTo; + public class MinOverTimeTests extends AbstractFunctionTestCase { public MinOverTimeTests(Supplier testCaseSupplier) { testCase = testCaseSupplier.get(); @@ -29,6 +34,12 @@ public static Iterable parameters() { @Override protected Expression build(Source source, List args) { - return new MinOverTime(source, args.get(0)); + return new MinOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); + } + + public static List signatureTypes(List params) { + var preview = appliesTo(FunctionAppliesToLifecycle.PREVIEW, "9.3.0", "", false); + DocsV3Support.Param window = new DocsV3Support.Param(DataType.TIME_DURATION, List.of(preview)); + return List.of(params.get(0), window); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTimeTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTimeTests.java index 3abc37a655bd2..bc399889adb01 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTimeTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/SumOverTimeTests.java @@ -12,12 +12,17 @@ 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.AbstractFunctionTestCase; +import org.elasticsearch.xpack.esql.expression.function.DocsV3Support; +import org.elasticsearch.xpack.esql.expression.function.FunctionAppliesToLifecycle; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import java.util.List; import java.util.function.Supplier; +import static org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier.appliesTo; + public class SumOverTimeTests extends AbstractFunctionTestCase { public SumOverTimeTests(@Name("TestCase") Supplier testCaseSupplier) { this.testCase = testCaseSupplier.get(); @@ -30,6 +35,12 @@ public static Iterable parameters() { @Override protected Expression build(Source source, List args) { - return new SumOverTime(source, args.get(0)); + return new SumOverTime(source, args.get(0), AggregateFunction.NO_WINDOW); + } + + public static List signatureTypes(List params) { + var preview = appliesTo(FunctionAppliesToLifecycle.PREVIEW, "9.3.0", "", false); + DocsV3Support.Param window = new DocsV3Support.Param(DataType.TIME_DURATION, List.of(preview)); + return List.of(params.get(0), window); } }