From b05526d13cd7604633399d630aa4d6e7b5a95aa1 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 20 Feb 2026 09:59:57 +0100 Subject: [PATCH 01/10] Wire TBUCKET into TimestampBoundsAware Make numeric TBUCKET consume analyzer-injected timestamp bounds and add analysis/verification coverage for auto-bucketing and missing-range validation. --- .../functions/description/tbucket.md | 2 +- .../_snippets/functions/parameters/tbucket.md | 6 +- .../kibana/definition/functions/tbucket.json | 118 +++++++++--------- .../esql/kibana/docs/functions/tbucket.md | 2 +- .../expression/function/grouping/TBucket.java | 40 +++++- .../xpack/esql/analysis/AnalyzerTests.java | 43 +++++++ .../xpack/esql/analysis/VerifierTests.java | 7 ++ 7 files changed, 149 insertions(+), 69 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md b/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md index e3948c079ac30..95c9f8689c3f7 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md +++ b/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md @@ -2,5 +2,5 @@ ## Description -Creates groups of values - buckets - out of a @timestamp attribute. The size of the buckets can either be provided directly as a duration or period, or chosen based on a recommended count and a range. +Creates groups of values - buckets - out of a `@timestamp` attribute. The size of the buckets can either be provided directly as a duration or period, or chosen based on a recommended count and a range. diff --git a/docs/reference/query-languages/esql/_snippets/functions/parameters/tbucket.md b/docs/reference/query-languages/esql/_snippets/functions/parameters/tbucket.md index 6cc2c94220106..a2ff40608a12a 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/parameters/tbucket.md +++ b/docs/reference/query-languages/esql/_snippets/functions/parameters/tbucket.md @@ -3,11 +3,11 @@ ## Parameters `buckets` -: Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` {applies_to}`stack: ga 9.4`. When a duration or period is provided, it is used as the explicit bucket size. +: Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter {applies_to}`stack: ga 9.4`. When a duration or period is provided, it is used as the explicit bucket size. `from` -: Start of the range. Required with a numeric `buckets` {applies_to}`stack: ga 9.4`. +: Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`. `to` -: End of the range. Required with a numeric `buckets` {applies_to}`stack: ga 9.4`. +: End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`. diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json b/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json index d7dc143aeccd1..1640e1eb131c9 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", "type" : "grouping", "name" : "tbucket", - "description" : "Creates groups of values - buckets - out of a @timestamp attribute.\nThe size of the buckets can either be provided directly as a duration or period,\nor chosen based on a recommended count and a range.", + "description" : "Creates groups of values - buckets - out of a `@timestamp` attribute.\nThe size of the buckets can either be provided directly as a duration or period,\nor chosen based on a recommended count and a range.", "signatures" : [ { "params" : [ @@ -10,7 +10,7 @@ "name" : "buckets", "type" : "date_period", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." } ], "variadic" : false, @@ -22,7 +22,7 @@ "name" : "buckets", "type" : "date_period", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." } ], "variadic" : false, @@ -34,19 +34,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -58,19 +58,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -82,19 +82,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -106,19 +106,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -130,19 +130,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -154,19 +154,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "date", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -178,19 +178,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -202,19 +202,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -226,19 +226,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -250,19 +250,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -274,19 +274,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -298,19 +298,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "keyword", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -322,19 +322,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -346,19 +346,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "date", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -370,19 +370,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -394,19 +394,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "keyword", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -418,19 +418,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -442,19 +442,19 @@ "name" : "buckets", "type" : "integer", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." }, { "name" : "from", "type" : "text", "optional" : true, - "description" : "Start of the range. Required with a numeric `buckets`." + "description" : "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." }, { "name" : "to", "type" : "text", "optional" : true, - "description" : "End of the range. Required with a numeric `buckets`." + "description" : "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter." } ], "variadic" : false, @@ -466,7 +466,7 @@ "name" : "buckets", "type" : "time_duration", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." } ], "variadic" : false, @@ -478,7 +478,7 @@ "name" : "buckets", "type" : "time_duration", "optional" : false, - "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to`. When a duration or period is provided, it is used as the explicit bucket size." + "description" : "Target number of buckets, or desired bucket size. When a number is provided, the actual bucket size is derived from `from`/`to` or the `@timestamp` range in the query filter. When a duration or period is provided, it is used as the explicit bucket size." } ], "variadic" : false, diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md b/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md index d96cd497bda64..5240bbe96d7eb 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md @@ -1,7 +1,7 @@ % This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it. ### TBUCKET -Creates groups of values - buckets - out of a @timestamp attribute. +Creates groups of values - buckets - out of a `@timestamp` attribute. The size of the buckets can either be provided directly as a duration or period, or chosen based on a recommended count and a range. diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java index 53aeefa99c52c..19a479e35340c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java @@ -10,7 +10,10 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.compute.expression.ExpressionEvaluator; import org.elasticsearch.core.Nullable; +import org.elasticsearch.xpack.esql.common.Failure; +import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.Expression; +import org.elasticsearch.xpack.esql.core.expression.Literal; import org.elasticsearch.xpack.esql.core.expression.TypeResolutions; import org.elasticsearch.xpack.esql.core.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; @@ -24,6 +27,7 @@ import org.elasticsearch.xpack.esql.expression.function.FunctionType; import org.elasticsearch.xpack.esql.expression.function.Param; import org.elasticsearch.xpack.esql.expression.function.TimestampAware; +import org.elasticsearch.xpack.esql.expression.function.TimestampBoundsAware; import org.elasticsearch.xpack.esql.expression.function.TwoOptionalArguments; import org.elasticsearch.xpack.esql.session.Configuration; @@ -44,13 +48,15 @@ * if it's a duration or period, it's the explicit bucket size. *

* When using a target number of buckets, start/end bounds are needed and can be provided explicitly - * as {@code from}/{@code to} parameters. + * as {@code from}/{@code to} parameters or derived automatically from the query DSL {@code @timestamp} range filter + * via {@link TimestampBoundsAware}. */ public class TBucket extends GroupingFunction.EvaluatableGroupingFunction implements OnlySurrogateExpression, TimestampAware, TwoOptionalArguments, + TimestampBoundsAware.OfExpression, ConfigurationFunction { public static final String NAME = "TBucket"; @@ -65,7 +71,7 @@ public class TBucket extends GroupingFunction.EvaluatableGroupingFunction @FunctionInfo( returnType = { "date", "date_nanos" }, description = """ - Creates groups of values - buckets - out of a @timestamp attribute. + Creates groups of values - buckets - out of a `@timestamp` attribute. The size of the buckets can either be provided directly as a duration or period, or chosen based on a recommended count and a range.""", examples = { @@ -121,20 +127,21 @@ public TBucket( name = "buckets", type = { "integer", "date_period", "time_duration" }, description = "Target number of buckets, or desired bucket size. " - + "When a number is provided, the actual bucket size is derived from `from`/`to` {applies_to}`stack: ga 9.4`. " + + "When a number is provided, the actual bucket size is derived from `from`/`to` " + + "or the `@timestamp` range in the query filter {applies_to}`stack: ga 9.4`. " + "When a duration or period is provided, it is used as the explicit bucket size." ) Expression buckets, @Param( name = "from", type = { "date", "keyword", "text" }, optional = true, - description = "Start of the range. Required with a numeric `buckets` {applies_to}`stack: ga 9.4`." + description = "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`." ) @Nullable Expression from, @Param( name = "to", type = { "date", "keyword", "text" }, optional = true, - description = "End of the range. Required with a numeric `buckets` {applies_to}`stack: ga 9.4`." + description = "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`." ) @Nullable Expression to, Expression timestamp, Configuration configuration @@ -162,6 +169,29 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { throw new UnsupportedOperationException("should be rewritten"); } + @Override + public boolean needsTimestampBounds() { + return buckets.resolved() && buckets.dataType().isWholeNumber() && (from == null || to == null); + } + + @Override + public Expression withTimestampBounds(Literal start, Literal end) { + return new TBucket(source(), buckets, from != null ? from : start, to != null ? to : end, timestamp, configuration); + } + + @Override + public void postAnalysisVerification(Failures failures) { + if (buckets.resolved() && buckets.dataType().isWholeNumber() && (from == null || to == null)) { + failures.add( + Failure.fail( + this, + "numeric bucket count in [{}] requires [from] and [to] parameters or a `@timestamp` range in the query filter", + sourceText() + ) + ); + } + } + @Override public Expression surrogate() { if (buckets.resolved() && buckets.dataType().isWholeNumber()) { diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 14d9e4270946f..aca70a8691be2 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -41,6 +41,7 @@ import org.elasticsearch.xpack.esql.core.expression.UnresolvedAttribute; import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePatternList; import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPatternList; +import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.core.type.EsField; @@ -189,6 +190,7 @@ import static org.elasticsearch.xpack.esql.core.type.DataType.KEYWORD; import static org.elasticsearch.xpack.esql.core.type.DataType.LONG; import static org.elasticsearch.xpack.esql.core.type.DataType.UNSUPPORTED; +import static org.elasticsearch.xpack.esql.plan.QuerySettings.UNMAPPED_FIELDS; import static org.elasticsearch.xpack.esql.type.EsqlDataTypeConverter.dateTimeToString; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; @@ -5130,6 +5132,47 @@ public void testInlineStatsStats() { """, "Found 1 problem\n" + "line 2:15: Unknown column [stats]", "mapping-default.json"); } + public void testTBucketAutoBucketingWithTimestampBounds() { + Instant start = Instant.parse("2024-01-01T00:00:00Z"); + Instant end = Instant.parse("2024-01-02T00:00:00Z"); + var bounds = new QueryDslTimestampBoundsExtractor.TimestampBounds(start, end); + var indexResolution = loadMapping("mapping-sample_data.json", "sample_data"); + var indexResolutions = Map.of(new IndexPattern(Source.EMPTY, "sample_data"), indexResolution); + var mergedResolutions = AnalyzerTestUtils.mergeIndexResolutions(indexResolutions, AnalyzerTestUtils.defaultSubqueryResolution()); + var context = new AnalyzerContext( + EsqlTestUtils.TEST_CFG, + new EsqlFunctionRegistry(), + null, + mergedResolutions, + AnalyzerTestUtils.defaultLookupResolution(), + defaultEnrichResolution(), + defaultInferenceResolution(), + ExternalSourceResolution.EMPTY, + EsqlTestUtils.randomMinimumVersion(), + UNMAPPED_FIELDS.defaultValue(), + bounds + ); + var analyzer = new Analyzer(context, TEST_VERIFIER); + LogicalPlan plan = analyze("FROM sample_data | STATS count = COUNT() BY bucket = TBUCKET(100)", analyzer); + + Limit limit = as(plan, Limit.class); + Aggregate agg = as(limit.child(), Aggregate.class); + + List groupings = agg.groupings(); + assertEquals(1, groupings.size()); + Alias a = as(groupings.get(0), Alias.class); + TBucket tbucket = as(a.child(), TBucket.class); + assertFalse(tbucket.needsTimestampBounds()); + assertNotNull(tbucket.from()); + assertNotNull(tbucket.to()); + FieldAttribute fa = as(tbucket.timestamp(), FieldAttribute.class); + assertEquals("@timestamp", fa.name()); + Literal fromLiteral = as(tbucket.from(), Literal.class); + assertEquals(start.toEpochMilli(), fromLiteral.value()); + Literal toLiteral = as(tbucket.to(), Literal.class); + assertEquals(end.toEpochMilli(), toLiteral.value()); + } + public void testTBucketWithDatePeriodInBothAggregationAndGrouping() { LogicalPlan plan = analyze(""" FROM sample_data diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index b9222c0d07cdb..77b7eb0cb4483 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -3009,6 +3009,13 @@ public void testInvalidTBucketCalls() { containsString("1:50: Cannot convert string [" + interval + "] to [DATE_PERIOD or TIME_DURATION]") ); } + assertThat( + error("from test | stats max(event_duration) by tbucket(100)", sampleDataAnalyzer), + equalTo( + "1:42: [tbucket(100)] requires a time range;" + + " provide explicit from/to parameters or add a @timestamp range to the query filter" + ) + ); } public void testFuse() { From 6df6f91af48e3826d542fa2ffc4f7e043b32adb4 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Wed, 11 Mar 2026 20:21:15 +0100 Subject: [PATCH 02/10] Update docs/changelog/144057.yaml --- docs/changelog/144057.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/144057.yaml diff --git a/docs/changelog/144057.yaml b/docs/changelog/144057.yaml new file mode 100644 index 0000000000000..b0fe7a93e5103 --- /dev/null +++ b/docs/changelog/144057.yaml @@ -0,0 +1,5 @@ +area: ES|QL +issues: [] +pr: 144057 +summary: Wire TBUCKET into `TimestampBoundsAware` +type: enhancement From 40b486b9dc886a1114695567c6bef745d7178f88 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 08:52:17 +0100 Subject: [PATCH 03/10] Improve TBUCKET description with Kibana filter link Made-with: Cursor --- .../esql/_snippets/functions/description/tbucket.md | 2 +- .../esql/kibana/definition/functions/tbucket.json | 2 +- .../esql/kibana/docs/functions/tbucket.md | 9 +++++++-- .../xpack/esql/expression/function/grouping/TBucket.java | 9 +++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md b/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md index 95c9f8689c3f7..30dca93590ea5 100644 --- a/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md +++ b/docs/reference/query-languages/esql/_snippets/functions/description/tbucket.md @@ -2,5 +2,5 @@ ## Description -Creates groups of values - buckets - out of a `@timestamp` attribute. The size of the buckets can either be provided directly as a duration or period, or chosen based on a recommended count and a range. +Creates groups of values - buckets - out of a `@timestamp` attribute. The size of the buckets can be provided directly as a duration or period. Alternatively, the bucket size can be chosen based on a recommended count and a range {applies_to}`stack: ga 9.4`. When using ES|QL in Kibana, the range can be derived automatically from the [`@timestamp` filter](docs-content://explore-analyze/query-filter/languages/esql-kibana.md#_standard_time_filter) that Kibana adds to the query. diff --git a/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json b/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json index 1640e1eb131c9..667e82386b18b 100644 --- a/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json +++ b/docs/reference/query-languages/esql/kibana/definition/functions/tbucket.json @@ -2,7 +2,7 @@ "comment" : "This is generated by ESQL's AbstractFunctionTestCase. Do not edit it. See ../README.md for how to regenerate it.", "type" : "grouping", "name" : "tbucket", - "description" : "Creates groups of values - buckets - out of a `@timestamp` attribute.\nThe size of the buckets can either be provided directly as a duration or period,\nor chosen based on a recommended count and a range.", + "description" : "Creates groups of values - buckets - out of a `@timestamp` attribute.\nThe size of the buckets can be provided directly as a duration or period.\nAlternatively, the bucket size can be chosen based on a recommended count\nand a range.\n\nWhen using ES|QL in Kibana, the range can be derived automatically from the\n`@timestamp` filter\nthat Kibana adds to the query.", "signatures" : [ { "params" : [ diff --git a/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md b/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md index 5240bbe96d7eb..2e9ce75aa605b 100644 --- a/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md +++ b/docs/reference/query-languages/esql/kibana/docs/functions/tbucket.md @@ -2,8 +2,13 @@ ### TBUCKET Creates groups of values - buckets - out of a `@timestamp` attribute. -The size of the buckets can either be provided directly as a duration or period, -or chosen based on a recommended count and a range. +The size of the buckets can be provided directly as a duration or period. +Alternatively, the bucket size can be chosen based on a recommended count +and a range. + +When using ES|QL in Kibana, the range can be derived automatically from the +[`@timestamp` filter](docs-content://explore-analyze/query-filter/languages/esql-kibana.md#_standard_time_filter) +that Kibana adds to the query. ```esql FROM sample_data diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java index 19a479e35340c..5cd196ebe911a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java @@ -72,8 +72,13 @@ public class TBucket extends GroupingFunction.EvaluatableGroupingFunction returnType = { "date", "date_nanos" }, description = """ Creates groups of values - buckets - out of a `@timestamp` attribute. - The size of the buckets can either be provided directly as a duration or period, - or chosen based on a recommended count and a range.""", + The size of the buckets can be provided directly as a duration or period. + Alternatively, the bucket size can be chosen based on a recommended count + and a range {applies_to}`stack: ga 9.4`. + + When using ES|QL in Kibana, the range can be derived automatically from the + [`@timestamp` filter](docs-content://explore-analyze/query-filter/languages/esql-kibana.md#_standard_time_filter) + that Kibana adds to the query.""", examples = { @Example( description = """ From 3a67d25fdf89625fbac6e1f8977b95cf061c5bed Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 10:41:14 +0100 Subject: [PATCH 04/10] Fix checkstyle line length violations in TBucket Made-with: Cursor --- .../xpack/esql/expression/function/grouping/TBucket.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java index 5cd196ebe911a..0265b9067a4c9 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java @@ -140,13 +140,15 @@ public TBucket( name = "from", type = { "date", "keyword", "text" }, optional = true, - description = "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`." + description = "Start of the range. Required with a numeric `buckets` when no `@timestamp` range is in the " + + "query filter {applies_to}`stack: ga 9.4`." ) @Nullable Expression from, @Param( name = "to", type = { "date", "keyword", "text" }, optional = true, - description = "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the query filter {applies_to}`stack: ga 9.4`." + description = "End of the range. Required with a numeric `buckets` when no `@timestamp` range is in the " + + "query filter {applies_to}`stack: ga 9.4`." ) @Nullable Expression to, Expression timestamp, Configuration configuration From ffd16a5fd36bd4b6fbf5201fc9f670f3090ab3f5 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 12:25:46 +0100 Subject: [PATCH 05/10] Fix ResolveTimestampBoundsAware running before ResolveFunctions Move the rule after ResolveFunctions in the Initialize batch so that TBucket expressions already exist when timestamp bounds are injected. --- .../java/org/elasticsearch/xpack/esql/analysis/Analyzer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java index 9cbd78d726733..149427b59d1fe 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Analyzer.java @@ -229,13 +229,13 @@ public class Analyzer extends ParameterizedRuleExecutor Date: Thu, 12 Mar 2026 16:27:53 +0100 Subject: [PATCH 06/10] Defer TBucket from/to validation to postAnalysisVerification The numeric bucket null from/to check in resolveType() ran before ResolveTimestampBoundsAware could fill in bounds from the query filter. Move the check to postAnalysisVerification and wire up expression-level PostAnalysisVerificationAware in the Verifier. --- .../elasticsearch/xpack/esql/analysis/Verifier.java | 5 +++++ .../esql/expression/function/grouping/TBucket.java | 6 ++---- .../xpack/esql/analysis/VerifierTests.java | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java index c21d72b71e8bd..b8d2e63d50a74 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java @@ -113,6 +113,11 @@ Collection verify(LogicalPlan plan, BitSet partialMetrics) { } planCheckers.forEach(c -> c.accept(p, failures)); + p.forEachExpression(e -> { + if (e instanceof PostAnalysisVerificationAware va) { + va.postAnalysisVerification(failures); + } + }); checkOperationsOnUnsignedLong(p, failures); checkBinaryComparison(p, failures); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java index 0265b9067a4c9..8d0783e2de24d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java @@ -202,7 +202,8 @@ public void postAnalysisVerification(Failures failures) { @Override public Expression surrogate() { if (buckets.resolved() && buckets.dataType().isWholeNumber()) { - assert from != null && to != null : "numeric bucket count requires [from] and [to]; resolveType should have caught this"; + assert from != null && to != null + : "numeric bucket count requires [from] and [to]; postAnalysisVerification should have caught this"; return new Bucket(source(), timestamp, buckets, from, to, configuration); } return new Bucket(source(), timestamp, buckets, from, to, configuration); @@ -233,9 +234,6 @@ protected TypeResolution resolveType() { if (resolution.unresolved()) { return resolution; } - if (buckets.dataType().isWholeNumber() && (from == null || to == null)) { - return new TypeResolution("numeric bucket count in [" + sourceText() + "] requires [from] and [to] parameters"); - } if (DataType.isTemporalAmount(buckets.dataType()) && (from != null || to != null)) { return new TypeResolution("[from] and [to] in [" + sourceText() + "] cannot be used with a duration or period bucket size"); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java index 77b7eb0cb4483..14655c59c5996 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/VerifierTests.java @@ -2976,11 +2976,14 @@ public void testInvalidTBucketCalls() { ); assertThat( error("from test | stats max(event_duration) by tbucket(3)", sampleDataAnalyzer), - equalTo("1:42: numeric bucket count in [tbucket(3)] requires [from] and [to] parameters") + equalTo( + "1:42: numeric bucket count in [tbucket(3)] requires [from] and [to] parameters" + + " or a `@timestamp` range in the query filter" + ) ); assertThat( error("from test | stats max(event_duration) by tbucket(3, \"2023-01-01T00:00:00Z\")", sampleDataAnalyzer), - equalTo("1:42: numeric bucket count in [tbucket(3, \"2023-01-01T00:00:00Z\")] requires [from] and [to] parameters") + equalTo("1:42: [from] and [to] in [tbucket(3, \"2023-01-01T00:00:00Z\")] must both be provided or both omitted") ); assertThat( error( @@ -3012,8 +3015,8 @@ public void testInvalidTBucketCalls() { assertThat( error("from test | stats max(event_duration) by tbucket(100)", sampleDataAnalyzer), equalTo( - "1:42: [tbucket(100)] requires a time range;" - + " provide explicit from/to parameters or add a @timestamp range to the query filter" + "1:42: numeric bucket count in [tbucket(100)] requires [from] and [to] parameters" + + " or a `@timestamp` range in the query filter" ) ); } From e1b66b04eddf9ac67c171cb418fe75b0e5ef2ca0 Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 16:30:04 +0100 Subject: [PATCH 07/10] Update 144057.yaml --- docs/changelog/144057.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/144057.yaml b/docs/changelog/144057.yaml index b0fe7a93e5103..6b373697869d1 100644 --- a/docs/changelog/144057.yaml +++ b/docs/changelog/144057.yaml @@ -1,5 +1,5 @@ area: ES|QL issues: [] pr: 144057 -summary: Wire TBUCKET into `TimestampBoundsAware` +summary: Allow TBUCKET to skip the from/to parameters when Kibana adds a timestamp range filter. Exmaple: `TBUCKET(100)` type: enhancement From 626c043cd02fb37e32c4b0010a744cf8d5d11afc Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 17:54:43 +0100 Subject: [PATCH 08/10] Fix changelog --- docs/changelog/144057.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog/144057.yaml b/docs/changelog/144057.yaml index 6b373697869d1..ccbdbbf8ef80a 100644 --- a/docs/changelog/144057.yaml +++ b/docs/changelog/144057.yaml @@ -1,5 +1,5 @@ area: ES|QL issues: [] pr: 144057 -summary: Allow TBUCKET to skip the from/to parameters when Kibana adds a timestamp range filter. Exmaple: `TBUCKET(100)` +summary: "Allow TBUCKET to skip the from/to parameters when Kibana adds a timestamp range filter. Exmaple: `TBUCKET(100)`" type: enhancement From 9c49ef779e7052617609ee1b2ba905991b70614e Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Thu, 12 Mar 2026 18:01:28 +0100 Subject: [PATCH 09/10] Fix needsTimestampBounds to require both from and to absent --- .../xpack/esql/expression/function/grouping/TBucket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java index 8d0783e2de24d..70fb2cd1fafe4 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/grouping/TBucket.java @@ -178,7 +178,7 @@ public ExpressionEvaluator.Factory toEvaluator(ToEvaluator toEvaluator) { @Override public boolean needsTimestampBounds() { - return buckets.resolved() && buckets.dataType().isWholeNumber() && (from == null || to == null); + return buckets.resolved() && buckets.dataType().isWholeNumber() && from == null && to == null; } @Override From e0c01bff19d04a70dfab24d23bceca89ac0bf3ee Mon Sep 17 00:00:00 2001 From: Felix Barnsteiner Date: Fri, 13 Mar 2026 08:51:57 +0100 Subject: [PATCH 10/10] Add missing EsqlFunctionRegistry import in AnalyzerTests --- .../org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java index 5c50a7769d29a..ea5d95444d29a 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTests.java @@ -55,6 +55,7 @@ import org.elasticsearch.xpack.esql.datasources.spi.StoragePath; import org.elasticsearch.xpack.esql.enrich.ResolvedEnrichPolicy; import org.elasticsearch.xpack.esql.expression.Order; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute; import org.elasticsearch.xpack.esql.expression.function.aggregate.Count; import org.elasticsearch.xpack.esql.expression.function.aggregate.FilteredExpression;