diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java index a25ccdc58cf27..a54d87336a663 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizer.java @@ -117,10 +117,10 @@ public LogicalPlanOptimizer(LogicalOptimizerContext optimizerContext) { public LogicalPlan optimize(LogicalPlan verified) { var optimized = execute(verified); -// Failures failures = verifier.verify(optimized, verified.output()); -// if (failures.hasFailures()) { -// throw new VerificationException(failures); -// } + // Failures failures = verifier.verify(optimized, verified.output()); + // if (failures.hasFailures()) { + // throw new VerificationException(failures); + // } optimized.setOptimized(); return optimized; } 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 64dbbbff5dbcc..a62bfa615aeaa 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 @@ -8,23 +8,29 @@ package org.elasticsearch.xpack.esql.plan.logical.promql; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.core.TimeValue; +import org.elasticsearch.xpack.esql.capabilities.PostAnalysisVerificationAware; import org.elasticsearch.xpack.esql.capabilities.TelemetryAware; +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.tree.NodeInfo; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan; +import org.elasticsearch.xpack.esql.plan.logical.promql.selector.Selector; import java.io.IOException; import java.time.Duration; import java.util.Objects; +import static org.elasticsearch.xpack.esql.common.Failure.fail; + /** * Container plan for embedded PromQL queries. * Gets eliminated by the analyzer once the query is validated. */ -public class PromqlCommand extends UnaryPlan implements TelemetryAware { +public class PromqlCommand extends UnaryPlan implements TelemetryAware, PostAnalysisVerificationAware { private final LogicalPlan promqlPlan; private final Expression start, end, step; @@ -102,9 +108,9 @@ public int hashCode() { public boolean equals(Object obj) { if (super.equals(obj)) { - PromqlCommand other = (PromqlCommand) obj; - return Objects.equals(child(), other.child()) && Objects.equals(promqlPlan, other.promqlPlan); - } + PromqlCommand other = (PromqlCommand) obj; + return Objects.equals(child(), other.child()) && Objects.equals(promqlPlan, other.promqlPlan); + } return false; } @@ -123,4 +129,38 @@ public String nodeString() { sb.append("\n<>]]"); return sb.toString(); } + + @Override + public void postAnalysisVerification(Failures failures) { + promqlPlan().forEachDownMayReturnEarly((lp, breakEarly) -> { + if (lp instanceof PromqlFunctionCall fc) { + if (fc instanceof AcrossSeriesAggregate) { + breakEarly.set(true); + fc.forEachDown((childLp -> verifyNonFunctionCall(failures, childLp))); + } else if (fc instanceof WithinSeriesAggregate withinSeriesAggregate) { + failures.add( + fail( + withinSeriesAggregate, + "within time series aggregate function [{}] " + + "can only be used inside an across time series aggregate function at this time", + withinSeriesAggregate.sourceText() + ) + ); + } + } else { + verifyNonFunctionCall(failures, lp); + } + }); + } + + private void verifyNonFunctionCall(Failures failures, LogicalPlan logicalPlan) { + if (logicalPlan instanceof Selector s) { + if (s.labelMatchers().nameLabel().matcher().isRegex()) { + failures.add(fail(s, "regex label selectors on __name__ are not supported at this time [{}]", s.sourceText())); + } + if (s.evaluation().offset() != null && s.evaluation().offset() != TimeValue.ZERO) { + failures.add(fail(s, "offset modifiers are not supported at this time [{}]", s.sourceText())); + } + } + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/selector/LabelMatcher.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/selector/LabelMatcher.java index 4d7c44ca7f8f5..17468e78108ed 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/selector/LabelMatcher.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/promql/selector/LabelMatcher.java @@ -60,6 +60,10 @@ public static Matcher from(String value) { this.value = value; this.isRegex = isRegex; } + + public boolean isRegex() { + return isRegex; + } } private final String name; 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 9d05b33d81c01..12543af85bd92 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 @@ -3086,7 +3086,7 @@ private String error(String query, Object... params) { return error(query, defaultAnalyzer, VerificationException.class, params); } - private String error(String query, Analyzer analyzer, Object... params) { + public static String error(String query, Analyzer analyzer, Object... params) { return error(query, analyzer, VerificationException.class, params); } @@ -3094,7 +3094,7 @@ private String error(String query, TransportVersion transportVersion, Object... return error(query, transportVersion, VerificationException.class, params); } - private String error(String query, Analyzer analyzer, Class exception, Object... params) { + public static String error(String query, Analyzer analyzer, Class exception, Object... params) { List parameters = new ArrayList<>(); for (Object param : params) { if (param == null) { 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 new file mode 100644 index 0000000000000..f97b7c37f6d42 --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/promql/PromqlVerifierTests.java @@ -0,0 +1,122 @@ +/* + * 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.analysis.promql; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.xpack.esql.analysis.Analyzer; +import org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils; +import org.junit.Ignore; + +import java.util.List; + +import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.elasticsearch.xpack.esql.analysis.VerifierTests.error; +import static org.hamcrest.Matchers.equalTo; + +public class PromqlVerifierTests extends ESTestCase { + + private final Analyzer tsdb = AnalyzerTestUtils.analyzer(AnalyzerTestUtils.tsdbIndexResolution()); + + public void testPromqlMissingAcrossSeriesAggregation() { + assertThat( + error(""" + TS test | PROMQL step 5m ( + rate(network.bytes_in[5m]) + )""", tsdb), + equalTo( + "2:3: within time series aggregate function [rate(network.bytes_in[5m])] can only be used " + + "inside an across time series aggregate function at this time" + ) + ); + } + + public void testPromqlIllegalNameLabelMatcher() { + assertThat( + error("TS test | PROMQL step 5m ({__name__=~\"*.foo.*\"})", tsdb), + equalTo("1:27: regex label selectors on __name__ are not supported at this time [{__name__=~\"*.foo.*\"}]") + ); + } + + @Ignore + public void testPromqlSubquery() { + // TODO doesn't parse + // line 1:36: Invalid query 'network.bytes_in'[ValueExpressionContext] given; expected Expression but found + // InstantSelector + assertThat(error("TS test | PROMQL step 5m (avg(rate(network.bytes_in[5m:])))", tsdb), equalTo("")); + assertThat(error("TS test | PROMQL step 5m (avg(rate(network.bytes_in[5m:1m])))", tsdb), equalTo("")); + } + + @Ignore + public void testPromqlArithmetricOperators() { + // TODO doesn't parse + // line 1:27: Invalid query '1+1'[ArithmeticBinaryContext] given; expected LogicalPlan but found VectorBinaryArithmetic + assertThat( + error("TS test | PROMQL step 5m (1+1)", tsdb), + equalTo("1:27: arithmetic operators are not supported at this time [foo]") + ); + assertThat( + error("TS test | PROMQL step 5m (foo+1)", tsdb), + equalTo("1:27: arithmetic operators are not supported at this time [foo]") + ); + assertThat( + error("TS test | PROMQL step 5m (1+foo)", tsdb), + equalTo("1:27: arithmetic operators are not supported at this time [foo]") + ); + assertThat( + error("TS test | PROMQL step 5m (foo+bar)", tsdb), + equalTo("1:27: arithmetic operators are not supported at this time [foo]") + ); + } + + @Ignore + public void testPromqlVectorMatching() { + // TODO doesn't parse + // line 1:27: Invalid query 'method_code_http_errors_rate5m{code="500"}'[ValueExpressionContext] given; expected Expression but + // found InstantSelector + assertThat( + error( + "TS test | PROMQL step 5m (method_code_http_errors_rate5m{code=\"500\"} / ignoring(code) method_http_requests_rate5m)", + tsdb + ), + equalTo("") + ); + assertThat( + error( + "TS test | PROMQL step 5m (method_code_http_errors_rate5m / ignoring(code) group_left method_http_requests_rate5m)", + tsdb + ), + equalTo("") + ); + } + + public void testPromqlModifier() { + assertThat( + error("TS test | PROMQL step 5m (foo offset 5m)", tsdb), + equalTo("1:27: offset modifiers are not supported at this time [foo offset 5m]") + ); + /* TODO + assertThat( + error("TS test | PROMQL step 5m (foo @ start())", tsdb), + equalTo("1:27: @ modifiers are not supported at this time [foo @ start()]") + );*/ + } + + @Ignore + public void testLogicalSetBinaryOperators() { + // TODO doesn't parse + // line 1:27: Invalid query 'foo'[ValueExpressionContext] given; expected Expression but found InstantSelector + assertThat(error("TS test | PROMQL step 5m (foo and bar)", tsdb), equalTo("")); + assertThat(error("TS test | PROMQL step 5m (foo or bar)", tsdb), equalTo("")); + assertThat(error("TS test | PROMQL step 5m (foo unless bar)", tsdb), equalTo("")); + } + + @Override + protected List filteredWarnings() { + return withDefaultLimitWarning(super.filteredWarnings()); + } +} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/promql/PromqlLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/promql/PromqlLogicalPlanOptimizerTests.java index a6539c6189638..0bac9c8ba15e5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/promql/PromqlLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/promql/PromqlLogicalPlanOptimizerTests.java @@ -9,7 +9,6 @@ import org.elasticsearch.TransportVersion; import org.elasticsearch.index.IndexMode; -import org.elasticsearch.transport.Transport; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.analysis.Analyzer; @@ -23,7 +22,6 @@ import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.junit.BeforeClass; -import java.util.Collections; import java.util.Map; import static java.util.Collections.emptyMap;