diff --git a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java index 38b6d716e1bc3..1cb2a6a526191 100644 --- a/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java +++ b/x-pack/plugin/esql/qa/server/src/main/java/org/elasticsearch/xpack/esql/qa/rest/RequestIndexFilteringTestCase.java @@ -225,7 +225,7 @@ public void testIndicesDontExist() throws IOException { // currently we don't support remote clusters in LOOKUP JOIN // this check happens before resolving actual indices and results in a different error message RemoteClusterAware.isRemoteIndexName(pattern) - ? allOf(containsString("parsing_exception"), containsString("remote clusters are not supported in LOOKUP JOIN")) + ? allOf(containsString("parsing_exception"), containsString("remote clusters are not supported")) : allOf(containsString("verification_exception"), containsString("Unknown index [foo]")) ); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java index c3f0771919e0a..4428577c54832 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/parser/LogicalPlanBuilder.java @@ -580,7 +580,7 @@ public PlanFactory visitJoinCommand(EsqlBaseParser.JoinCommandContext ctx) { if (RemoteClusterAware.isRemoteIndexName(rightPattern)) { throw new ParsingException( source(target), - "invalid index pattern [{}], remote clusters are not supported in LOOKUP JOIN", + "invalid index pattern [{}], remote clusters are not supported with LOOKUP JOIN", rightPattern ); } @@ -621,22 +621,26 @@ public PlanFactory visitJoinCommand(EsqlBaseParser.JoinCommandContext ctx) { } return p -> { - p.forEachUp(UnresolvedRelation.class, r -> { - for (var leftPattern : Strings.splitStringByCommaToArray(r.indexPattern().indexPattern())) { - if (RemoteClusterAware.isRemoteIndexName(leftPattern)) { - throw new ParsingException( - source(target), - "invalid index pattern [{}], remote clusters are not supported in LOOKUP JOIN", - r.indexPattern().indexPattern() - ); - } - } - }); - + checkForRemoteClusters(p, source(target), "LOOKUP JOIN"); return new LookupJoin(source, p, right, joinFields); }; } + private void checkForRemoteClusters(LogicalPlan plan, Source source, String commandName) { + plan.forEachUp(UnresolvedRelation.class, r -> { + for (var indexPattern : Strings.splitStringByCommaToArray(r.indexPattern().indexPattern())) { + if (RemoteClusterAware.isRemoteIndexName(indexPattern)) { + throw new ParsingException( + source, + "invalid index pattern [{}], remote clusters are not supported with {}", + r.indexPattern().indexPattern(), + commandName + ); + } + } + }); + } + @Override @SuppressWarnings("unchecked") public PlanFactory visitForkCommand(EsqlBaseParser.ForkCommandContext ctx) { @@ -645,6 +649,7 @@ public PlanFactory visitForkCommand(EsqlBaseParser.ForkCommandContext ctx) { throw new ParsingException(source(ctx), "Fork requires at least two branches"); } return input -> { + checkForRemoteClusters(input, source(ctx), "FORK"); List subPlans = subQueries.stream().map(planFactory -> planFactory.apply(input)).toList(); return new Fork(source(ctx), subPlans, List.of()); }; @@ -744,7 +749,10 @@ public PlanFactory visitRerankCommand(EsqlBaseParser.RerankCommandContext ctx) { ? inferenceId(ctx.inferenceId) : new Literal(source, Rerank.DEFAULT_INFERENCE_ID, KEYWORD); - return p -> new Rerank(source, p, inferenceId, queryText, visitRerankFields(ctx.rerankFields())); + return p -> { + checkForRemoteClusters(p, source, "RERANK"); + return new Rerank(source, p, inferenceId, queryText, visitRerankFields(ctx.rerankFields())); + }; } @Override @@ -756,7 +764,10 @@ public PlanFactory visitCompletionCommand(EsqlBaseParser.CompletionCommandContex ? new UnresolvedAttribute(source, Completion.DEFAULT_OUTPUT_FIELD_NAME) : visitQualifiedName(ctx.targetField); - return p -> new Completion(source, p, inferenceId, prompt, targetField); + return p -> { + checkForRemoteClusters(p, source, "COMPLETION"); + return new Completion(source, p, inferenceId, prompt, targetField); + }; } public Literal inferenceId(EsqlBaseParser.IdentifierOrParameterContext ctx) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Fork.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Fork.java index f4126a1ded2ea..c0e8b736fcb6c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Fork.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/logical/Fork.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.xpack.esql.analysis.Analyzer; import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; +import org.elasticsearch.xpack.esql.capabilities.TelemetryAware; import org.elasticsearch.xpack.esql.common.Failure; import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.Attribute; @@ -32,7 +33,7 @@ * A Fork is a n-ary {@code Plan} where each child is a sub plan, e.g. * {@code FORK [WHERE content:"fox" ] [WHERE content:"dog"] } */ -public class Fork extends LogicalPlan implements PostAnalysisPlanVerificationAware { +public class Fork extends LogicalPlan implements PostAnalysisPlanVerificationAware, TelemetryAware { public static final String FORK_FIELD = "_fork"; private final List output; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java index d8709de54c346..d16da9558d67d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/parser/StatementParserTests.java @@ -3175,7 +3175,7 @@ public void testInvalidJoinPatterns() { var joinPattern = randomIndexPattern(CROSS_CLUSTER, without(WILDCARD_PATTERN), without(INDEX_SELECTOR)); expectError( "FROM " + fromPatterns + " | LOOKUP JOIN " + joinPattern + " ON " + randomIdentifier(), - "invalid index pattern [" + unquoteIndexPattern(joinPattern) + "], remote clusters are not supported in LOOKUP JOIN" + "invalid index pattern [" + unquoteIndexPattern(joinPattern) + "], remote clusters are not supported with LOOKUP JOIN" ); } { @@ -3184,7 +3184,7 @@ public void testInvalidJoinPatterns() { var joinPattern = randomIndexPattern(without(CROSS_CLUSTER), without(WILDCARD_PATTERN), without(INDEX_SELECTOR)); expectError( "FROM " + fromPatterns + " | LOOKUP JOIN " + joinPattern + " ON " + randomIdentifier(), - "invalid index pattern [" + unquoteIndexPattern(fromPatterns) + "], remote clusters are not supported in LOOKUP JOIN" + "invalid index pattern [" + unquoteIndexPattern(fromPatterns) + "], remote clusters are not supported with LOOKUP JOIN" ); } @@ -3415,6 +3415,12 @@ public void testInvalidFork() { expectError("FROM foo* | FORK ( LIMIT 10 ) ( y+2 )", "line 1:33: mismatched input 'y+2'"); expectError("FROM foo* | FORK (where true) ()", "line 1:32: mismatched input ')'"); expectError("FROM foo* | FORK () (where true)", "line 1:19: mismatched input ')'"); + + var fromPatterns = randomIndexPatterns(CROSS_CLUSTER); + expectError( + "FROM " + fromPatterns + " | FORK (EVAL a = 1) (EVAL a = 2)", + "invalid index pattern [" + unquoteIndexPattern(fromPatterns) + "], remote clusters are not supported with FORK" + ); } public void testFieldNamesAsCommands() throws Exception { @@ -3540,6 +3546,12 @@ public void testInvalidRerank() { assumeTrue("RERANK requires corresponding capability", EsqlCapabilities.Cap.RERANK.isEnabled()); expectError("FROM foo* | RERANK ON title WITH inferenceId", "line 1:20: mismatched input 'ON' expecting {QUOTED_STRING"); expectError("FROM foo* | RERANK \"query text\" WITH inferenceId", "line 1:33: mismatched input 'WITH' expecting 'on'"); + + var fromPatterns = randomIndexPatterns(CROSS_CLUSTER); + expectError( + "FROM " + fromPatterns + " | RERANK \"query text\" ON title WITH inferenceId", + "invalid index pattern [" + unquoteIndexPattern(fromPatterns) + "], remote clusters are not supported with RERANK" + ); } public void testCompletionUsingFieldAsPrompt() { @@ -3590,6 +3602,12 @@ public void testInvalidCompletion() { expectError("FROM foo* | COMPLETION completion=prompt WITH", "line 1:46: mismatched input '' expecting {"); expectError("FROM foo* | COMPLETION completion=prompt", "line 1:41: mismatched input '' expecting {"); + + var fromPatterns = randomIndexPatterns(CROSS_CLUSTER); + expectError( + "FROM " + fromPatterns + " | COMPLETION prompt_field WITH inferenceId", + "invalid index pattern [" + unquoteIndexPattern(fromPatterns) + "], remote clusters are not supported with COMPLETION" + ); } public void testSample() {