diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/all_first.md b/docs/reference/query-languages/esql/_snippets/functions/description/all_first.md
new file mode 100644
index 0000000000000..010c06c5a9787
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/all_first.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Calculates the earliest value of a field, and can operate on null values.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/all_last.md b/docs/reference/query-languages/esql/_snippets/functions/description/all_last.md
new file mode 100644
index 0000000000000..3a755cd859dba
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/description/all_last.md
@@ -0,0 +1,6 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Description**
+
+Calculates the latest value of a field, and can operate on null values.
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/all_first.md b/docs/reference/query-languages/esql/_snippets/functions/examples/all_first.md
new file mode 100644
index 0000000000000..7139e13472834
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/all_first.md
@@ -0,0 +1,29 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS first_val = ALL_FIRST(number, @timestamp)
+```
+
+| first_val:long |
+| --- |
+| null |
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/examples/all_last.md b/docs/reference/query-languages/esql/_snippets/functions/examples/all_last.md
new file mode 100644
index 0000000000000..037bd4d206669
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/examples/all_last.md
@@ -0,0 +1,32 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Example**
+
+```esql
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS last_val = ALL_LAST(number, @timestamp) BY name
+```
+
+| last_val:long | name:keyword |
+| --- | --- |
+| 4 | alpha |
+| 5 | bravo |
+| 6 | charlie |
+| null | delta |
+
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/all_first.md b/docs/reference/query-languages/esql/_snippets/functions/layout/all_first.md
new file mode 100644
index 0000000000000..014e7c851f0f3
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/all_first.md
@@ -0,0 +1,27 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `ALL_FIRST` [esql-all_first]
+```{applies_to}
+stack: development
+serverless: preview
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/all_first.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/all_first.md
+:::
+
+:::{include} ../description/all_first.md
+:::
+
+:::{include} ../types/all_first.md
+:::
+
+:::{include} ../examples/all_first.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/layout/all_last.md b/docs/reference/query-languages/esql/_snippets/functions/layout/all_last.md
new file mode 100644
index 0000000000000..29a3ac27992e8
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/layout/all_last.md
@@ -0,0 +1,27 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+## `ALL_LAST` [esql-all_last]
+```{applies_to}
+stack: development
+serverless: preview
+```
+
+**Syntax**
+
+:::{image} ../../../images/functions/all_last.svg
+:alt: Embedded
+:class: text-center
+:::
+
+
+:::{include} ../parameters/all_last.md
+:::
+
+:::{include} ../description/all_last.md
+:::
+
+:::{include} ../types/all_last.md
+:::
+
+:::{include} ../examples/all_last.md
+:::
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/all_first.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/all_first.md
new file mode 100644
index 0000000000000..78c98d3923a2e
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/all_first.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`value`
+: Values to return
+
+`sort`
+: Sort key
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/all_last.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/all_last.md
new file mode 100644
index 0000000000000..78c98d3923a2e
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/all_last.md
@@ -0,0 +1,10 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Parameters**
+
+`value`
+: Values to return
+
+`sort`
+: Sort key
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/all_first.md b/docs/reference/query-languages/esql/_snippets/functions/types/all_first.md
new file mode 100644
index 0000000000000..ca982dfb580bc
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/all_first.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| value | sort | result |
+| --- | --- | --- |
+| double | date | double |
+| double | date_nanos | double |
+| integer | date | integer |
+| integer | date_nanos | integer |
+| keyword | date | keyword |
+| keyword | date_nanos | keyword |
+| long | date | long |
+| long | date_nanos | long |
+| text | date | keyword |
+| text | date_nanos | keyword |
+
diff --git a/docs/reference/query-languages/esql/_snippets/functions/types/all_last.md b/docs/reference/query-languages/esql/_snippets/functions/types/all_last.md
new file mode 100644
index 0000000000000..ca982dfb580bc
--- /dev/null
+++ b/docs/reference/query-languages/esql/_snippets/functions/types/all_last.md
@@ -0,0 +1,17 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+**Supported types**
+
+| value | sort | result |
+| --- | --- | --- |
+| double | date | double |
+| double | date_nanos | double |
+| integer | date | integer |
+| integer | date_nanos | integer |
+| keyword | date | keyword |
+| keyword | date_nanos | keyword |
+| long | date | long |
+| long | date_nanos | long |
+| text | date | keyword |
+| text | date_nanos | keyword |
+
diff --git a/docs/reference/query-languages/esql/images/functions/all_first.svg b/docs/reference/query-languages/esql/images/functions/all_first.svg
new file mode 100644
index 0000000000000..e0b1e19f92cb5
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/all_first.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/images/functions/all_last.svg b/docs/reference/query-languages/esql/images/functions/all_last.svg
new file mode 100644
index 0000000000000..0f08160c4fae6
--- /dev/null
+++ b/docs/reference/query-languages/esql/images/functions/all_last.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/all_first.json b/docs/reference/query-languages/esql/kibana/definition/functions/all_first.json
new file mode 100644
index 0000000000000..75bd76e078a1f
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/all_first.json
@@ -0,0 +1,193 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "agg",
+ "name" : "all_first",
+ "description" : "Calculates the earliest value of a field, and can operate on null values.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "text",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "text",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ }
+ ],
+ "examples" : [
+ "ROW row = [\n # @timestamp | name | number\n \"2025-11-25T00:00:00.000Z | alpha | \",\n \"2025-11-25T00:00:01.000Z | alpha | 2\",\n \"2025-11-25T00:00:02.000Z | bravo | \",\n \"2025-11-25T00:00:03.000Z | alpha | 4\",\n \"2025-11-25T00:00:04.000Z | bravo | 5\",\n \"2025-11-25T00:00:05.000Z | charlie | 6\",\n \"2025-11-25T00:00:06.000Z | delta | \"\n]\n| MV_EXPAND row\n| DISSECT row \"\"\"%{@timestamp} | %{name} | %{number}\"\"\"\n| KEEP @timestamp, name, number\n| EVAL @timestamp = TO_DATETIME(@timestamp),\n name = TRIM(name),\n number = TO_LONG(number)\n| STATS first_val = ALL_FIRST(number, @timestamp)"
+ ],
+ "preview" : true,
+ "snapshot_only" : true
+}
diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/all_last.json b/docs/reference/query-languages/esql/kibana/definition/functions/all_last.json
new file mode 100644
index 0000000000000..5286def5c3626
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/definition/functions/all_last.json
@@ -0,0 +1,193 @@
+{
+ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.",
+ "type" : "agg",
+ "name" : "all_last",
+ "description" : "Calculates the latest value of a field, and can operate on null values.",
+ "signatures" : [
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "double",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "double"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "integer",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "integer"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "keyword",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "long",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "long"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "text",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ },
+ {
+ "params" : [
+ {
+ "name" : "value",
+ "type" : "text",
+ "optional" : false,
+ "description" : "Values to return"
+ },
+ {
+ "name" : "sort",
+ "type" : "date_nanos",
+ "optional" : false,
+ "description" : "Sort key"
+ }
+ ],
+ "variadic" : false,
+ "returnType" : "keyword"
+ }
+ ],
+ "examples" : [
+ "ROW row = [\n # @timestamp | name | number\n \"2025-11-25T00:00:00.000Z | alpha | \",\n \"2025-11-25T00:00:01.000Z | alpha | 2\",\n \"2025-11-25T00:00:02.000Z | bravo | \",\n \"2025-11-25T00:00:03.000Z | alpha | 4\",\n \"2025-11-25T00:00:04.000Z | bravo | 5\",\n \"2025-11-25T00:00:05.000Z | charlie | 6\",\n \"2025-11-25T00:00:06.000Z | delta | \"\n]\n| MV_EXPAND row\n| DISSECT row \"\"\"%{@timestamp} | %{name} | %{number}\"\"\"\n| KEEP @timestamp, name, number\n| EVAL @timestamp = TO_DATETIME(@timestamp),\n name = TRIM(name),\n number = TO_LONG(number)\n| STATS last_val = ALL_LAST(number, @timestamp) BY name"
+ ],
+ "preview" : true,
+ "snapshot_only" : true
+}
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/all_first.md b/docs/reference/query-languages/esql/kibana/docs/functions/all_first.md
new file mode 100644
index 0000000000000..7918982bea93e
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/all_first.md
@@ -0,0 +1,24 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### ALL FIRST
+Calculates the earliest value of a field, and can operate on null values.
+
+```esql
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS first_val = ALL_FIRST(number, @timestamp)
+```
diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/all_last.md b/docs/reference/query-languages/esql/kibana/docs/functions/all_last.md
new file mode 100644
index 0000000000000..a2c6f7983a2db
--- /dev/null
+++ b/docs/reference/query-languages/esql/kibana/docs/functions/all_last.md
@@ -0,0 +1,24 @@
+% This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.
+
+### ALL LAST
+Calculates the latest value of a field, and can operate on null values.
+
+```esql
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS last_val = ALL_LAST(number, @timestamp) BY name
+```
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_all_first_all_last.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_all_first_all_last.csv-spec
index 5ee6458680315..8ae74ae581174 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_all_first_all_last.csv-spec
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/stats_all_first_all_last.csv-spec
@@ -4,13 +4,13 @@ required_capability: all_last
ROW row = [
# @timestamp | name | number
- "2023-01-23T00:00:00.000Z | alpha | ",
- "2023-01-23T00:00:01.000Z | alpha | 2",
- "2023-01-23T00:00:02.000Z | bravo | ",
- "2023-01-23T00:00:03.000Z | alpha | 4",
- "2023-01-23T00:00:04.000Z | bravo | 5",
- "2023-01-23T00:00:05.000Z | charlie | 6",
- "2023-01-23T00:00:06.000Z | delta | "
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
]
| MV_EXPAND row
| DISSECT row """%{@timestamp} | %{name} | %{number}"""
@@ -45,13 +45,13 @@ required_capability: all_last
ROW row = [
# @timestamp | name | number
- "2023-01-23T00:00:00.000Z | alpha | ",
- "2023-01-23T00:00:01.000Z | alpha | 2",
- "2023-01-23T00:00:02.000Z | bravo | ",
- "2023-01-23T00:00:03.000Z | alpha | 4",
- "2023-01-23T00:00:04.000Z | bravo | 5",
- "2023-01-23T00:00:05.000Z | charlie | 6",
- "2023-01-23T00:00:06.000Z | delta | "
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
]
| MV_EXPAND row
| DISSECT row """%{@timestamp} | %{name} | %{number}"""
@@ -83,3 +83,70 @@ null | null | null | null | 5
6 | 6 | 6.0 | 6 | 6 | 6 | 6.0 | 6 | charlie
null | null | null | null | null | null | null | null | delta
;
+
+all_first_long_by_date
+required_capability: all_first
+// tag::all_first[]
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS first_val = ALL_FIRST(number, @timestamp)
+// end::all_first[]
+;
+
+warning:Line 15:10: evaluation of [TO_LONG(number)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 15:10: org.elasticsearch.xpack.esql.core.InvalidArgumentException: Cannot parse number []
+
+// tag::all_first-result[]
+first_val:long
+null
+// end::all_first-result[]
+;
+
+all_last_long_by_date
+required_capability: all_last
+// tag::all_last[]
+ROW row = [
+ # @timestamp | name | number
+ "2025-11-25T00:00:00.000Z | alpha | ",
+ "2025-11-25T00:00:01.000Z | alpha | 2",
+ "2025-11-25T00:00:02.000Z | bravo | ",
+ "2025-11-25T00:00:03.000Z | alpha | 4",
+ "2025-11-25T00:00:04.000Z | bravo | 5",
+ "2025-11-25T00:00:05.000Z | charlie | 6",
+ "2025-11-25T00:00:06.000Z | delta | "
+]
+| MV_EXPAND row
+| DISSECT row """%{@timestamp} | %{name} | %{number}"""
+| KEEP @timestamp, name, number
+| EVAL @timestamp = TO_DATETIME(@timestamp),
+ name = TRIM(name),
+ number = TO_LONG(number)
+| STATS last_val = ALL_LAST(number, @timestamp) BY name
+// end::all_last[]
+;
+
+warning:Line 15:10: evaluation of [TO_LONG(number)] failed, treating result as null. Only first 20 failures recorded.
+warning:Line 15:10: org.elasticsearch.xpack.esql.core.InvalidArgumentException: Cannot parse number []
+
+// tag::all_last-result[]
+last_val:long | name:keyword
+4 | alpha
+5 | bravo
+6 | charlie
+null | delta
+// end::all_last-result[]
+;
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbstractFirstLastTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbstractFirstLastTestCase.java
new file mode 100644
index 0000000000000..394d772de2962
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/aggregate/AbstractFirstLastTestCase.java
@@ -0,0 +1,80 @@
+/*
+ * 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.xpack.esql.expression.function.aggregate;
+
+import org.elasticsearch.common.collect.Iterators;
+import org.elasticsearch.xpack.esql.core.type.DataType;
+import org.elasticsearch.xpack.esql.expression.function.AbstractAggregationTestCase;
+import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier;
+import org.hamcrest.Matchers;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.elasticsearch.xpack.esql.expression.function.MultiRowTestCaseSupplier.unlimitedSuppliers;
+import static org.hamcrest.Matchers.anyOf;
+
+public abstract class AbstractFirstLastTestCase extends AbstractAggregationTestCase {
+
+ public static Iterable