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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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 @@
-
\ No newline at end of file
+
\ 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