From 0c58bf7686f841bd09d7650a0e63630ec58162a7 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 20 Feb 2026 09:59:24 +0100
Subject: [PATCH 1/9] Add TimestampBoundsAware analysis mechanism
Introduce timestamp-bounds-aware analysis plumbing and use it to inject
filter-derived bounds into PromqlCommand range queries during analysis.
---
.../xpack/esql/EsqlTestUtils.java | 27 +++++++++-
.../esql/analysis/MutableAnalyzerContext.java | 21 +++++++-
.../xpack/esql/analysis/Analyzer.java | 37 +++++++++++++
.../xpack/esql/analysis/AnalyzerContext.java | 52 ++++++++++++++++++-
.../xpack/esql/analysis/Verifier.java | 16 ++++++
.../function/TimestampBoundsAware.java | 41 +++++++++++++++
.../plan/logical/promql/PromqlCommand.java | 21 +++++++-
.../xpack/esql/session/EsqlSession.java | 27 +++++-----
.../esql/analysis/AnalyzerTestUtils.java | 29 ++++++++++-
.../analysis/promql/PromqlVerifierTests.java | 12 +++++
10 files changed, 262 insertions(+), 21 deletions(-)
create mode 100644 x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
index d9e18ba7ed7af..d9e749f1349d7 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
@@ -45,6 +45,7 @@
import org.elasticsearch.compute.data.LongRangeBlockBuilder;
import org.elasticsearch.compute.data.Page;
import org.elasticsearch.compute.data.TDigestHolder;
+import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.PathUtils;
import org.elasticsearch.core.SuppressForbidden;
import org.elasticsearch.core.Tuple;
@@ -85,6 +86,7 @@
import org.elasticsearch.xpack.esql.analysis.UnmappedResolution;
import org.elasticsearch.xpack.esql.analysis.Verifier;
import org.elasticsearch.xpack.esql.core.expression.Alias;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
@@ -569,6 +571,28 @@ public static MutableAnalyzerContext testAnalyzerContext(
EnrichResolution enrichResolution,
InferenceResolution inferenceResolution,
UnmappedResolution unmappedResolution
+ ) {
+ return testAnalyzerContext(
+ configuration,
+ functionRegistry,
+ indexResolutions,
+ lookupResolution,
+ enrichResolution,
+ inferenceResolution,
+ unmappedResolution,
+ null
+ );
+ }
+
+ public static MutableAnalyzerContext testAnalyzerContext(
+ Configuration configuration,
+ EsqlFunctionRegistry functionRegistry,
+ Map indexResolutions,
+ Map lookupResolution,
+ EnrichResolution enrichResolution,
+ InferenceResolution inferenceResolution,
+ UnmappedResolution unmappedResolution,
+ @Nullable TimestampBounds timestampBounds
) {
return new MutableAnalyzerContext(
configuration,
@@ -578,7 +602,8 @@ public static MutableAnalyzerContext testAnalyzerContext(
enrichResolution,
inferenceResolution,
randomMinimumVersion(),
- unmappedResolution
+ unmappedResolution,
+ timestampBounds
);
}
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
index e75956ce3ad79..d0d79d5b4b930 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
@@ -8,7 +8,9 @@
package org.elasticsearch.xpack.esql.analysis;
import org.elasticsearch.TransportVersion;
+import org.elasticsearch.core.Nullable;
import org.elasticsearch.test.TransportVersionUtils;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.index.IndexResolution;
import org.elasticsearch.xpack.esql.inference.InferenceResolution;
@@ -33,16 +35,33 @@ public MutableAnalyzerContext(
InferenceResolution inferenceResolution,
TransportVersion minimumVersion,
UnmappedResolution unmappedResolution
+ ) {
+ this(configuration, functionRegistry, indexResolution, lookupResolution, enrichResolution, inferenceResolution,
+ minimumVersion, unmappedResolution, null);
+ }
+
+ public MutableAnalyzerContext(
+ Configuration configuration,
+ EsqlFunctionRegistry functionRegistry,
+ Map indexResolution,
+ Map lookupResolution,
+ EnrichResolution enrichResolution,
+ InferenceResolution inferenceResolution,
+ TransportVersion minimumVersion,
+ UnmappedResolution unmappedResolution,
+ @Nullable TimestampBounds timestampBounds
) {
super(
configuration,
functionRegistry,
+ null,
indexResolution,
lookupResolution,
enrichResolution,
inferenceResolution,
minimumVersion,
- unmappedResolution
+ unmappedResolution,
+ timestampBounds
);
this.currentVersion = minimumVersion;
}
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 97b4f470e598b..e22b268bd9108 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
@@ -65,6 +65,7 @@
import org.elasticsearch.xpack.esql.expression.function.AggregateMetricDoubleNativeSupport;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.expression.function.FunctionDefinition;
+import org.elasticsearch.xpack.esql.expression.function.TimestampBoundsAware;
import org.elasticsearch.xpack.esql.expression.function.UnresolvedFunction;
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
import org.elasticsearch.xpack.esql.expression.function.aggregate.Absent;
@@ -225,6 +226,7 @@ public class Analyzer extends ParameterizedRuleExecutor {
+
+ @Override
+ protected boolean skipResolved() {
+ return false;
+ }
+
+ @Override
+ protected LogicalPlan rule(LogicalPlan plan, AnalyzerContext context) {
+ var bounds = context.timestampBounds();
+ if (bounds == null) {
+ return plan;
+ }
+ if (plan instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
+ @SuppressWarnings("unchecked")
+ var planAware = (TimestampBoundsAware) tba;
+ plan = planAware.withTimestampBounds(
+ Literal.dateTime(plan.source(), bounds.start()),
+ Literal.dateTime(plan.source(), bounds.end())
+ );
+ }
+ return plan.transformExpressionsUp(Expression.class, expression -> {
+ if (expression instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
+ @SuppressWarnings("unchecked")
+ var exprAware = (TimestampBoundsAware) tba;
+ return exprAware.withTimestampBounds(
+ Literal.dateTime(expression.source(), bounds.start()),
+ Literal.dateTime(expression.source(), bounds.end())
+ );
+ }
+ return expression;
+ });
+ }
+ }
+
private static class ResolveFunctions extends ParameterizedAnalyzerRule {
@Override
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
index 86c7501547d6c..27a9fd4aec1c4 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
@@ -10,7 +10,9 @@
import org.elasticsearch.TransportVersion;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.ProjectMetadata;
+import org.elasticsearch.core.Nullable;
import org.elasticsearch.xpack.esql.core.expression.MetadataAttribute;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.index.IndexResolution;
import org.elasticsearch.xpack.esql.inference.InferenceResolution;
@@ -34,6 +36,7 @@ public class AnalyzerContext {
private final ProjectMetadata projectMetadata;
private Boolean hasRemoteIndices;
private final UnmappedResolution unmappedResolution;
+ private final TimestampBounds timestampBounds;
public AnalyzerContext(
Configuration configuration,
@@ -45,6 +48,32 @@ public AnalyzerContext(
InferenceResolution inferenceResolution,
TransportVersion minimumVersion,
UnmappedResolution unmappedResolution
+ ) {
+ this(
+ configuration,
+ functionRegistry,
+ projectMetadata,
+ indexResolution,
+ lookupResolution,
+ enrichResolution,
+ inferenceResolution,
+ minimumVersion,
+ unmappedResolution,
+ null
+ );
+ }
+
+ public AnalyzerContext(
+ Configuration configuration,
+ EsqlFunctionRegistry functionRegistry,
+ ProjectMetadata projectMetadata,
+ Map indexResolution,
+ Map lookupResolution,
+ EnrichResolution enrichResolution,
+ InferenceResolution inferenceResolution,
+ TransportVersion minimumVersion,
+ UnmappedResolution unmappedResolution,
+ @Nullable TimestampBounds timestampBounds
) {
this.configuration = configuration;
this.functionRegistry = functionRegistry;
@@ -55,6 +84,7 @@ public AnalyzerContext(
this.inferenceResolution = inferenceResolution;
this.minimumVersion = minimumVersion;
this.unmappedResolution = unmappedResolution;
+ this.timestampBounds = timestampBounds;
assert minimumVersion != null : "AnalyzerContext must have a minimum transport version";
assert TransportVersion.current().supports(minimumVersion)
@@ -129,6 +159,14 @@ public UnmappedResolution unmappedResolution() {
return unmappedResolution;
}
+ /**
+ * Returns the {@code @timestamp} bounds extracted from the query DSL filter, or {@code null} if not available.
+ */
+ @Nullable
+ public TimestampBounds timestampBounds() {
+ return timestampBounds;
+ }
+
public Set allowedTags() {
Set result = new HashSet<>();
result.addAll(MetadataAttribute.ATTRIBUTES_MAP.keySet());
@@ -155,6 +193,17 @@ public AnalyzerContext(
UnmappedResolution unmappedResolution,
ProjectMetadata projectMetadata,
EsqlSession.PreAnalysisResult result
+ ) {
+ this(configuration, functionRegistry, unmappedResolution, projectMetadata, result, null);
+ }
+
+ public AnalyzerContext(
+ Configuration configuration,
+ EsqlFunctionRegistry functionRegistry,
+ UnmappedResolution unmappedResolution,
+ ProjectMetadata projectMetadata,
+ EsqlSession.PreAnalysisResult result,
+ @Nullable TimestampBounds timestampBounds
) {
this(
configuration,
@@ -165,7 +214,8 @@ public AnalyzerContext(
result.enrichResolution(),
result.inferenceResolution(),
result.minimumTransportVersion(),
- unmappedResolution
+ unmappedResolution,
+ timestampBounds
);
}
}
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..00dcaaebbd24e 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
@@ -24,6 +24,7 @@
import org.elasticsearch.xpack.esql.core.tree.Node;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.Holder;
+import org.elasticsearch.xpack.esql.expression.function.TimestampBoundsAware;
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Neg;
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
@@ -95,6 +96,7 @@ Collection verify(LogicalPlan plan, BitSet partialMetrics) {
checkUnresolvedAttributes(plan, failures);
ConfigurationAware.verifyNoMarkerConfiguration(plan, failures);
+ checkUnresolvedTimestampBounds(plan, failures);
// in case of failures bail-out as all other checks will be redundant
if (failures.hasFailures()) {
@@ -220,6 +222,20 @@ else if (p instanceof PromqlCommand promql) {
});
}
+ private static void checkUnresolvedTimestampBounds(LogicalPlan plan, Failures failures) {
+ plan.forEachDown(p -> p.forEachExpression(Expression.class, e -> {
+ if (e instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
+ failures.add(
+ fail(
+ e,
+ "[{}] requires a time range; provide explicit from/to parameters or add a @timestamp range to the query filter",
+ e.sourceText()
+ )
+ );
+ }
+ }));
+ }
+
/**
* Build a list of checkers based on the components in the plan.
*/
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
new file mode 100644
index 0000000000000..dcb307b9cc071
--- /dev/null
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import org.elasticsearch.xpack.esql.analysis.Analyzer;
+import org.elasticsearch.xpack.esql.analysis.Verifier;
+import org.elasticsearch.xpack.esql.capabilities.ConfigurationAware;
+import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
+import org.elasticsearch.xpack.esql.core.expression.Literal;
+
+/**
+ * Marker interface for nodes (expressions or plans) that require {@code @timestamp} bounds derived from the query DSL filter.
+ *
+ * Implementations are resolved during analysis by {@link Analyzer}'s {@code ResolveTimestampBoundsAware} rule,
+ * following the same pattern as {@link ConfigurationAware}.
+ *
+ *
+ * Expression implementations that still {@link #needsTimestampBounds() need bounds} after analysis are automatically
+ * rejected by the {@link Verifier} with a client error.
+ * LogicalPlan implementations are responsible for their own validation via {@link PostAnalysisVerificationAware#postAnalysisVerification}.
+ *
+ *
+ * @param the type returned by {@link #withTimestampBounds}, typically {@code Expression} or {@code LogicalPlan}
+ */
+public interface TimestampBoundsAware {
+
+ /**
+ * Returns {@code true} if this node still needs timestamp bounds to be injected.
+ */
+ boolean needsTimestampBounds();
+
+ /**
+ * Returns a copy of this node with the given timestamp bounds applied.
+ */
+ T withTimestampBounds(Literal start, Literal end);
+}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
index 560639854f7da..1a987143c8f05 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
@@ -23,6 +23,7 @@
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.Holder;
import org.elasticsearch.xpack.esql.expression.function.TimestampAware;
+import org.elasticsearch.xpack.esql.expression.function.TimestampBoundsAware;
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;
import org.elasticsearch.xpack.esql.plan.logical.promql.operator.VectorBinaryComparison;
@@ -44,7 +45,12 @@
* Container plan for embedded PromQL queries.
* Gets eliminated by the analyzer once the query is validated.
*/
-public class PromqlCommand extends UnaryPlan implements TelemetryAware, PostAnalysisVerificationAware, TimestampAware {
+public class PromqlCommand extends UnaryPlan
+ implements
+ TelemetryAware,
+ PostAnalysisVerificationAware,
+ TimestampAware,
+ TimestampBoundsAware {
/**
* The name of the column containing the step value (aka time bucket) in range queries.
@@ -162,7 +168,18 @@ public PromqlCommand withPromqlPlan(LogicalPlan newPromqlPlan) {
);
}
- public PromqlCommand withStartEnd(Literal start, Literal end) {
+ /**
+ * Bounds are only needed when {@code buckets} is specified without an explicit time range.
+ * When {@code step} alone is set, the query can proceed without start/end because the step
+ * directly defines the bucket size; {@link #postAnalysisVerification} validates that case.
+ */
+ @Override
+ public boolean needsTimestampBounds() {
+ return buckets.value() != null && hasTimeRange() == false;
+ }
+
+ @Override
+ public LogicalPlan withTimestampBounds(Literal start, Literal end) {
return new PromqlCommand(
source(),
child(),
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
index 0f38d4e2d875d..f923b0b402036 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
@@ -54,7 +54,6 @@
import org.elasticsearch.xpack.esql.approximation.Approximation;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
-import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute;
import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor;
import org.elasticsearch.xpack.esql.core.tree.NodeUtils;
@@ -82,7 +81,6 @@
import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
-import org.elasticsearch.xpack.esql.plan.logical.promql.PromqlCommand;
import org.elasticsearch.xpack.esql.plan.physical.EstimatesRowSize;
import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec;
import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan;
@@ -277,16 +275,6 @@ public void execute(
explainMode = true;
plan = explain.query();
parsedPlanString = plan.toString();
- } else if (plan instanceof PromqlCommand promqlCommand && promqlCommand.isRangeQuery() && promqlCommand.hasTimeRange() == false) {
- // infer start/end from filter if not explicitly set
- QueryDslTimestampBoundsExtractor.TimestampBounds bounds = QueryDslTimestampBoundsExtractor.extractTimestampBounds(
- request.filter()
- );
- if (bounds != null) {
- Literal startLiteral = Literal.dateTime(promqlCommand.source(), bounds.start());
- Literal endLiteral = Literal.dateTime(promqlCommand.source(), bounds.end());
- plan = promqlCommand.withStartEnd(startLiteral, endLiteral);
- }
}
final EsqlStatement statementFinal = statement;
@@ -1165,7 +1153,7 @@ private void analyzeWithRetry(
}
TimeSpanMarker analysisProfile = executionInfo.queryProfile().analysis();
analysisProfile.start();
- LogicalPlan plan = analyzedPlan(parsed, unmappedResolution, configuration, result, executionInfo);
+ LogicalPlan plan = analyzedPlan(parsed, unmappedResolution, configuration, result, executionInfo, requestFilter);
analysisProfile.stop();
LOGGER.debug("Analyzed plan ({}):\n{}", description, plan);
// the analysis succeeded from the first attempt, irrespective if it had a filter or not, just continue with the planning
@@ -1211,10 +1199,19 @@ private LogicalPlan analyzedPlan(
UnmappedResolution unmappedResolution,
Configuration configuration,
PreAnalysisResult r,
- EsqlExecutionInfo executionInfo
+ EsqlExecutionInfo executionInfo,
+ QueryBuilder requestFilter
) throws Exception {
handleFieldCapsFailures(configuration.allowPartialResults(), executionInfo, r.indexResolution());
- AnalyzerContext analyzerContext = new AnalyzerContext(configuration, functionRegistry, unmappedResolution, projectMetadata, r);
+ var timestampBounds = QueryDslTimestampBoundsExtractor.extractTimestampBounds(requestFilter);
+ AnalyzerContext analyzerContext = new AnalyzerContext(
+ configuration,
+ functionRegistry,
+ unmappedResolution,
+ projectMetadata,
+ r,
+ timestampBounds
+ );
Analyzer analyzer = new Analyzer(analyzerContext, verifier);
LogicalPlan plan = analyzer.analyze(parsed);
plan.setAnalyzed();
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java
index e3383412e9027..49b4d8d7dd3a2 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/AnalyzerTestUtils.java
@@ -14,6 +14,7 @@
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.core.enrich.EnrichPolicy;
import org.elasticsearch.xpack.esql.EsqlTestUtils;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.EsField;
import org.elasticsearch.xpack.esql.core.type.InvalidMappedField;
@@ -70,6 +71,19 @@ public static Analyzer analyzer(IndexResolution indexResolution) {
return analyzer(indexResolution, TEST_VERIFIER);
}
+ /** Analyzer with a single index and {@code @timestamp} bounds from a query DSL filter. */
+ public static Analyzer analyzer(IndexResolution indexResolution, TimestampBounds timestampBounds) {
+ return analyzer(
+ indexResolutions(indexResolution),
+ defaultLookupResolution(),
+ defaultEnrichResolution(),
+ TEST_VERIFIER,
+ TEST_CFG,
+ UNMAPPED_FIELDS.defaultValue(),
+ timestampBounds
+ );
+ }
+
/** Simple analyzer with multiple indexes, which may also be invalid */
public static Analyzer analyzer(Map indexResolutions) {
return analyzer(indexResolutions, defaultLookupResolution(), defaultEnrichResolution(), TEST_VERIFIER, TEST_CFG);
@@ -113,6 +127,18 @@ public static Analyzer analyzer(
Verifier verifier,
Configuration config,
UnmappedResolution unmappedResolution
+ ) {
+ return analyzer(indexResolutions, lookupResolution, enrichResolution, verifier, config, unmappedResolution, null);
+ }
+
+ public static Analyzer analyzer(
+ Map indexResolutions,
+ Map lookupResolution,
+ EnrichResolution enrichResolution,
+ Verifier verifier,
+ Configuration config,
+ UnmappedResolution unmappedResolution,
+ @Nullable TimestampBounds timestampBounds
) {
return new Analyzer(
testAnalyzerContext(
@@ -122,7 +148,8 @@ public static Analyzer analyzer(
lookupResolution,
enrichResolution,
defaultInferenceResolution(),
- unmappedResolution
+ unmappedResolution,
+ timestampBounds
),
verifier
);
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/promql/PromqlVerifierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/promql/PromqlVerifierTests.java
index af59fca3f74d6..d0a871a562ccc 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/promql/PromqlVerifierTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/promql/PromqlVerifierTests.java
@@ -10,7 +10,11 @@
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.esql.analysis.Analyzer;
import org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
+import org.elasticsearch.xpack.esql.parser.EsqlParser;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.List;
import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning;
@@ -113,6 +117,14 @@ public void testPromqlBucketsWithoutRange() {
);
}
+ public void testPromqlBucketsWithTimestampBoundsFromContext() {
+ var now = Instant.now();
+ var bounds = new TimestampBounds(now.minus(1, ChronoUnit.HOURS), now);
+ var analyzer = AnalyzerTestUtils.analyzer(AnalyzerTestUtils.tsdbIndexResolution(), bounds);
+ var plan = analyzer.analyze(EsqlParser.INSTANCE.parseQuery("PROMQL index=test buckets=10 avg(network.bytes_in)"));
+ assertTrue("Plan should be resolved after timestamp bounds injection", plan.resolved());
+ }
+
public void testNoMetricNameMatcherNotSupported() {
assertThat(
error("PROMQL index=test step=5m {foo=\"bar\"}", tsdb),
From 8f4eb61dea7732fa8a3a75243210084088db2bc2 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 20 Feb 2026 12:30:02 +0100
Subject: [PATCH 2/9] Fix post merge compile error
---
.../org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
index 56b1c2df407c1..f72611fad4664 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerContext.java
@@ -60,6 +60,7 @@ public AnalyzerContext(
lookupResolution,
enrichResolution,
inferenceResolution,
+ externalSourceResolution,
minimumVersion,
unmappedResolution,
null
@@ -74,6 +75,7 @@ public AnalyzerContext(
Map lookupResolution,
EnrichResolution enrichResolution,
InferenceResolution inferenceResolution,
+ ExternalSourceResolution externalSourceResolution,
TransportVersion minimumVersion,
UnmappedResolution unmappedResolution,
@Nullable TimestampBounds timestampBounds
From 6c3ddd987523bebad0562da662a9f5cb9c773d08 Mon Sep 17 00:00:00 2001
From: elasticsearchmachine
Date: Fri, 20 Feb 2026 11:36:38 +0000
Subject: [PATCH 3/9] [CI] Auto commit changes from spotless
---
.../org/elasticsearch/xpack/esql/EsqlTestUtils.java | 2 +-
.../xpack/esql/analysis/MutableAnalyzerContext.java | 13 +++++++++++--
2 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
index d9e749f1349d7..f55a15de9d902 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/EsqlTestUtils.java
@@ -86,7 +86,6 @@
import org.elasticsearch.xpack.esql.analysis.UnmappedResolution;
import org.elasticsearch.xpack.esql.analysis.Verifier;
import org.elasticsearch.xpack.esql.core.expression.Alias;
-import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FieldAttribute;
@@ -98,6 +97,7 @@
import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute;
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.RLikePattern;
import org.elasticsearch.xpack.esql.core.expression.predicate.regex.WildcardPattern;
+import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.type.EsField;
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
index d0d79d5b4b930..f6e8fcfd2e374 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
@@ -36,8 +36,17 @@ public MutableAnalyzerContext(
TransportVersion minimumVersion,
UnmappedResolution unmappedResolution
) {
- this(configuration, functionRegistry, indexResolution, lookupResolution, enrichResolution, inferenceResolution,
- minimumVersion, unmappedResolution, null);
+ this(
+ configuration,
+ functionRegistry,
+ indexResolution,
+ lookupResolution,
+ enrichResolution,
+ inferenceResolution,
+ minimumVersion,
+ unmappedResolution,
+ null
+ );
}
public MutableAnalyzerContext(
From 2fb27a8bbec721fcf747cfe8a40285bf67c12097 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 20 Feb 2026 13:56:15 +0100
Subject: [PATCH 4/9] Fix compile error
---
.../xpack/esql/analysis/MutableAnalyzerContext.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
index f6e8fcfd2e374..560e2eaa3d50f 100644
--- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
+++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/analysis/MutableAnalyzerContext.java
@@ -11,6 +11,7 @@
import org.elasticsearch.core.Nullable;
import org.elasticsearch.test.TransportVersionUtils;
import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor.TimestampBounds;
+import org.elasticsearch.xpack.esql.datasources.ExternalSourceResolution;
import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry;
import org.elasticsearch.xpack.esql.index.IndexResolution;
import org.elasticsearch.xpack.esql.inference.InferenceResolution;
@@ -68,6 +69,7 @@ public MutableAnalyzerContext(
lookupResolution,
enrichResolution,
inferenceResolution,
+ ExternalSourceResolution.EMPTY,
minimumVersion,
unmappedResolution,
timestampBounds
From 6d04e7879c58aa56b2f79aaa56be214c87636505 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Mon, 23 Feb 2026 18:18:14 +0100
Subject: [PATCH 5/9] Add test to ignore `should` matchers
---
...QueryDslTimestampBoundsExtractorTests.java | 45 +++++++++++++++++++
1 file changed, 45 insertions(+)
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/core/querydsl/QueryDslTimestampBoundsExtractorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/core/querydsl/QueryDslTimestampBoundsExtractorTests.java
index 0f9c87c933416..70c5be0d5a8b6 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/core/querydsl/QueryDslTimestampBoundsExtractorTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/core/querydsl/QueryDslTimestampBoundsExtractorTests.java
@@ -93,4 +93,49 @@ public void testExtractTimestampBoundsInvalidValueDoesNotThrow() {
assertThat(bounds, nullValue());
}
+ public void testIgnoresRangeInShouldClause() {
+ var range = new RangeQueryBuilder("@timestamp").format("strict_date_optional_time")
+ .gte("2025-01-01T00:00:00Z")
+ .lte("2025-01-02T00:00:00Z");
+ var filter = new BoolQueryBuilder().should(range);
+
+ assertThat(QueryDslTimestampBoundsExtractor.extractTimestampBounds(filter), nullValue());
+ }
+
+ public void testIgnoresRangeInMustNotClause() {
+ var range = new RangeQueryBuilder("@timestamp").format("strict_date_optional_time")
+ .gte("2025-01-01T00:00:00Z")
+ .lte("2025-01-02T00:00:00Z");
+ var filter = new BoolQueryBuilder().mustNot(range);
+
+ assertThat(QueryDslTimestampBoundsExtractor.extractTimestampBounds(filter), nullValue());
+ }
+
+ public void testFilterRangeExtractedShouldRangeIgnored() {
+ Instant filterStart = Instant.parse("2025-01-01T00:00:00Z");
+ Instant filterEnd = Instant.parse("2025-01-02T00:00:00Z");
+ var filterRange = new RangeQueryBuilder("@timestamp").format("strict_date_optional_time")
+ .gte(filterStart.toString())
+ .lte(filterEnd.toString());
+ var shouldRange = new RangeQueryBuilder("@timestamp").format("strict_date_optional_time")
+ .gte("2025-06-01T00:00:00Z")
+ .lte("2025-06-30T00:00:00Z");
+ var filter = new BoolQueryBuilder().filter(filterRange).should(shouldRange);
+
+ TimestampBounds bounds = QueryDslTimestampBoundsExtractor.extractTimestampBounds(filter);
+ assertThat(bounds, notNullValue());
+ assertThat(bounds.start(), equalTo(filterStart));
+ assertThat(bounds.end(), equalTo(filterEnd));
+ }
+
+ public void testIgnoresRangeNestedInsideShouldSubtree() {
+ var range = new RangeQueryBuilder("@timestamp").format("strict_date_optional_time")
+ .gte("2025-01-01T00:00:00Z")
+ .lte("2025-01-02T00:00:00Z");
+ var innerBool = new BoolQueryBuilder().should(range);
+ var outerBool = new BoolQueryBuilder().must(innerBool);
+
+ assertThat(QueryDslTimestampBoundsExtractor.extractTimestampBounds(outerBool), nullValue());
+ }
+
}
From 41b6c3d1f79f39016f94a762fda0a0e8f928ae88 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Mon, 2 Mar 2026 14:58:51 +0100
Subject: [PATCH 6/9] Avoid unchecked casts by creating sub-interfaces for
expressions and logical plans
---
.../xpack/esql/analysis/Analyzer.java | 12 ++++-------
.../xpack/esql/analysis/Verifier.java | 2 +-
.../function/TimestampBoundsAware.java | 20 ++++++++++++++++++-
.../plan/logical/promql/PromqlCommand.java | 4 ++--
4 files changed, 26 insertions(+), 12 deletions(-)
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 b5d083ef85392..44e20ff0cde92 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
@@ -1737,19 +1737,15 @@ protected LogicalPlan rule(LogicalPlan plan, AnalyzerContext context) {
if (bounds == null) {
return plan;
}
- if (plan instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
- @SuppressWarnings("unchecked")
- var planAware = (TimestampBoundsAware) tba;
- plan = planAware.withTimestampBounds(
+ if (plan instanceof TimestampBoundsAware.OfLogicalPlan tba && tba.needsTimestampBounds()) {
+ plan = tba.withTimestampBounds(
Literal.dateTime(plan.source(), bounds.start()),
Literal.dateTime(plan.source(), bounds.end())
);
}
return plan.transformExpressionsUp(Expression.class, expression -> {
- if (expression instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
- @SuppressWarnings("unchecked")
- var exprAware = (TimestampBoundsAware) tba;
- return exprAware.withTimestampBounds(
+ if (expression instanceof TimestampBoundsAware.OfExpression tba && tba.needsTimestampBounds()) {
+ return tba.withTimestampBounds(
Literal.dateTime(expression.source(), bounds.start()),
Literal.dateTime(expression.source(), bounds.end())
);
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 00dcaaebbd24e..dd680cf488117 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
@@ -224,7 +224,7 @@ else if (p instanceof PromqlCommand promql) {
private static void checkUnresolvedTimestampBounds(LogicalPlan plan, Failures failures) {
plan.forEachDown(p -> p.forEachExpression(Expression.class, e -> {
- if (e instanceof TimestampBoundsAware> tba && tba.needsTimestampBounds()) {
+ if (e instanceof TimestampBoundsAware.OfExpression tba && tba.needsTimestampBounds()) {
failures.add(
fail(
e,
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
index dcb307b9cc071..1e0a1f3b6cb2d 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
@@ -11,7 +11,10 @@
import org.elasticsearch.xpack.esql.analysis.Verifier;
import org.elasticsearch.xpack.esql.capabilities.ConfigurationAware;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
+import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.Literal;
+import org.elasticsearch.xpack.esql.core.tree.Node;
+import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
/**
* Marker interface for nodes (expressions or plans) that require {@code @timestamp} bounds derived from the query DSL filter.
@@ -24,10 +27,15 @@
* rejected by the {@link Verifier} with a client error.
* LogicalPlan implementations are responsible for their own validation via {@link PostAnalysisVerificationAware#postAnalysisVerification}.
*
+ *
+ * Use the sub-interfaces {@link OfExpression} and {@link OfLogicalPlan}
+ * rather than implementing this interface directly.
+ *
*
* @param the type returned by {@link #withTimestampBounds}, typically {@code Expression} or {@code LogicalPlan}
*/
-public interface TimestampBoundsAware {
+public sealed interface TimestampBoundsAware> permits TimestampBoundsAware.OfExpression,
+ TimestampBoundsAware.OfLogicalPlan {
/**
* Returns {@code true} if this node still needs timestamp bounds to be injected.
@@ -38,4 +46,14 @@ public interface TimestampBoundsAware {
* Returns a copy of this node with the given timestamp bounds applied.
*/
T withTimestampBounds(Literal start, Literal end);
+
+ /**
+ * Sub-interface for {@link Expression} nodes that require timestamp bounds.
+ */
+ non-sealed interface OfExpression extends TimestampBoundsAware {}
+
+ /**
+ * Sub-interface for {@link LogicalPlan} nodes that require timestamp bounds.
+ */
+ non-sealed interface OfLogicalPlan extends TimestampBoundsAware {}
}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
index 1a987143c8f05..32a3d9e5098c9 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/PromqlCommand.java
@@ -50,7 +50,7 @@ public class PromqlCommand extends UnaryPlan
TelemetryAware,
PostAnalysisVerificationAware,
TimestampAware,
- TimestampBoundsAware {
+ TimestampBoundsAware.OfLogicalPlan {
/**
* The name of the column containing the step value (aka time bucket) in range queries.
@@ -179,7 +179,7 @@ public boolean needsTimestampBounds() {
}
@Override
- public LogicalPlan withTimestampBounds(Literal start, Literal end) {
+ public PromqlCommand withTimestampBounds(Literal start, Literal end) {
return new PromqlCommand(
source(),
child(),
From 20778377f9230ecddf14eb9e4673fa6422ff35b0 Mon Sep 17 00:00:00 2001
From: Felix Barnsteiner
Date: Fri, 6 Mar 2026 09:31:48 +0100
Subject: [PATCH 7/9] Make implementations of TimestampBoundsAware responsible
for the validation
---
.../xpack/esql/analysis/Verifier.java | 15 ---------------
.../expression/function/TimestampBoundsAware.java | 11 ++++-------
2 files changed, 4 insertions(+), 22 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 dd680cf488117..c0b77d1674ea3 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
@@ -96,7 +96,6 @@ Collection verify(LogicalPlan plan, BitSet partialMetrics) {
checkUnresolvedAttributes(plan, failures);
ConfigurationAware.verifyNoMarkerConfiguration(plan, failures);
- checkUnresolvedTimestampBounds(plan, failures);
// in case of failures bail-out as all other checks will be redundant
if (failures.hasFailures()) {
@@ -222,20 +221,6 @@ else if (p instanceof PromqlCommand promql) {
});
}
- private static void checkUnresolvedTimestampBounds(LogicalPlan plan, Failures failures) {
- plan.forEachDown(p -> p.forEachExpression(Expression.class, e -> {
- if (e instanceof TimestampBoundsAware.OfExpression tba && tba.needsTimestampBounds()) {
- failures.add(
- fail(
- e,
- "[{}] requires a time range; provide explicit from/to parameters or add a @timestamp range to the query filter",
- e.sourceText()
- )
- );
- }
- }));
- }
-
/**
* Build a list of checkers based on the components in the plan.
*/
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
index 1e0a1f3b6cb2d..86fcc46aaac39 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
@@ -8,7 +8,6 @@
package org.elasticsearch.xpack.esql.expression.function;
import org.elasticsearch.xpack.esql.analysis.Analyzer;
-import org.elasticsearch.xpack.esql.analysis.Verifier;
import org.elasticsearch.xpack.esql.capabilities.ConfigurationAware;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
import org.elasticsearch.xpack.esql.core.expression.Expression;
@@ -19,13 +18,11 @@
/**
* Marker interface for nodes (expressions or plans) that require {@code @timestamp} bounds derived from the query DSL filter.
*
- * Implementations are resolved during analysis by {@link Analyzer}'s {@code ResolveTimestampBoundsAware} rule,
+ * Implementations are resolved during analysis by the {@code ResolveTimestampBoundsAware} analyzer rule,
* following the same pattern as {@link ConfigurationAware}.
*
*
- * Expression implementations that still {@link #needsTimestampBounds() need bounds} after analysis are automatically
- * rejected by the {@link Verifier} with a client error.
- * LogicalPlan implementations are responsible for their own validation via {@link PostAnalysisVerificationAware#postAnalysisVerification}.
+ * Implementations are responsible for their own validation via {@link PostAnalysisVerificationAware#postAnalysisVerification}.
*
*
* Use the sub-interfaces {@link OfExpression} and {@link OfLogicalPlan}
@@ -34,8 +31,8 @@
*
* @param the type returned by {@link #withTimestampBounds}, typically {@code Expression} or {@code LogicalPlan}
*/
-public sealed interface TimestampBoundsAware> permits TimestampBoundsAware.OfExpression,
- TimestampBoundsAware.OfLogicalPlan {
+public sealed interface TimestampBoundsAware> extends PostAnalysisVerificationAware permits
+ TimestampBoundsAware.OfExpression, TimestampBoundsAware.OfLogicalPlan {
/**
* Returns {@code true} if this node still needs timestamp bounds to be injected.
From ebb5b1f36fc75f7b0540a4a18b49627b9eb3736a Mon Sep 17 00:00:00 2001
From: elasticsearchmachine
Date: Fri, 6 Mar 2026 08:37:41 +0000
Subject: [PATCH 8/9] [CI] Auto commit changes from spotless
---
.../java/org/elasticsearch/xpack/esql/analysis/Verifier.java | 1 -
.../xpack/esql/expression/function/TimestampBoundsAware.java | 1 -
2 files changed, 2 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 c0b77d1674ea3..c21d72b71e8bd 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
@@ -24,7 +24,6 @@
import org.elasticsearch.xpack.esql.core.tree.Node;
import org.elasticsearch.xpack.esql.core.type.DataType;
import org.elasticsearch.xpack.esql.core.util.Holder;
-import org.elasticsearch.xpack.esql.expression.function.TimestampBoundsAware;
import org.elasticsearch.xpack.esql.expression.function.UnsupportedAttribute;
import org.elasticsearch.xpack.esql.expression.predicate.operator.arithmetic.Neg;
import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals;
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
index 86fcc46aaac39..ec375cdc46823 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/TimestampBoundsAware.java
@@ -7,7 +7,6 @@
package org.elasticsearch.xpack.esql.expression.function;
-import org.elasticsearch.xpack.esql.analysis.Analyzer;
import org.elasticsearch.xpack.esql.capabilities.ConfigurationAware;
import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware;
import org.elasticsearch.xpack.esql.core.expression.Expression;
From f2c542472eb0615203398c3226572cf8570b12ec Mon Sep 17 00:00:00 2001
From: elasticsearchmachine
Date: Wed, 11 Mar 2026 14:58:06 +0000
Subject: [PATCH 9/9] [CI] Auto commit changes from spotless
---
.../java/org/elasticsearch/xpack/esql/session/EsqlSession.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
index 47397e766a5c5..0674314c637ca 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/EsqlSession.java
@@ -56,7 +56,6 @@
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.expression.Expression;
import org.elasticsearch.xpack.esql.core.expression.FoldContext;
-import org.elasticsearch.xpack.esql.core.expression.Literal;
import org.elasticsearch.xpack.esql.core.querydsl.QueryDslTimestampBoundsExtractor;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.datasources.ExternalSourceResolution;