diff --git a/docs/changelog/136805.yaml b/docs/changelog/136805.yaml new file mode 100644 index 0000000000000..9d5122cf49255 --- /dev/null +++ b/docs/changelog/136805.yaml @@ -0,0 +1,6 @@ +pr: 136805 +summary: Allow single fork branch +area: ES|QL +type: enhancement +issues: + - 135825 diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java index c30bb4dec200b..395ba216ca878 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/ForkIT.java @@ -1005,8 +1005,14 @@ public void testOneSubQuery() { | FORK ( WHERE content:"fox" ) """; - var e = expectThrows(ParsingException.class, () -> run(query)); - assertTrue(e.getMessage().contains("Fork requires at least 2 branches")); + try (var resp = run(query)) { + assertColumnTypes(resp.columns(), List.of("text", "integer", "keyword")); + assertColumnNames(resp.columns(), List.of("content", "id", "_fork")); + Iterable> expectedValues = List.of( + Arrays.stream(new Object[] { "The quick brown fox jumps over the lazy dog", 6, "fork1" }).toList() + ); + assertValues(resp.values(), expectedValues); + } } public void testForkWithinFork() { diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/FuseIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/FuseIT.java index 4e5256d6ebfae..be58acd5370ca 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/FuseIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/FuseIT.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.plugins.Plugin; +import org.elasticsearch.xpack.esql.plan.logical.fuse.Fuse; import org.junit.Before; import java.util.Collection; @@ -163,6 +164,30 @@ public void testFuseLinearWithWeightsAndNormalizer() { } } + public void testFuseWithSingleFork() { + for (Fuse.FuseType type : Fuse.FuseType.values()) { + var query = """ + FROM test METADATA _score, _id, _index + | WHERE id > 2 + | FORK + ( WHERE content:"fox" | SORT _score, _id DESC ) + | FUSE + """ + type.name() + """ + | SORT _score DESC, _id, _index + | EVAL _fork = mv_sort(_fork) + | EVAL _score = round(_score, 4) + | KEEP id, content, _fork + """; + try (var resp = run(query)) { + assertColumnNames(resp.columns(), List.of("id", "content", "_fork")); + assertColumnTypes(resp.columns(), List.of("integer", "keyword", "keyword")); + assertThat(getValuesList(resp.values()).size(), equalTo(1)); + Iterable> expectedValues = List.of(List.of(6, "The quick brown fox jumps over the lazy dog", "fork1")); + assertValues(resp.values(), expectedValues); + } + } + } + private void createAndPopulateIndex() { var indexName = "test"; var client = client().admin().indices(); 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 f3c750b27ef21..06b07117b5328 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 @@ -848,9 +848,6 @@ private void checkForRemoteClusters(LogicalPlan plan, Source source, String comm @SuppressWarnings("unchecked") public PlanFactory visitForkCommand(EsqlBaseParser.ForkCommandContext ctx) { List subQueries = visitForkSubQueries(ctx.forkSubQueries()); - if (subQueries.size() < Fork.MIN_BRANCHES) { - throw new ParsingException(source(ctx), "Fork requires at least " + Fork.MIN_BRANCHES + " branches"); - } if (subQueries.size() > Fork.MAX_BRANCHES) { throw new ParsingException(source(ctx), "Fork supports up to " + Fork.MAX_BRANCHES + " branches"); } 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 3099e8612f9af..f49c58ca389f3 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 @@ -39,14 +39,10 @@ public class Fork extends LogicalPlan implements PostAnalysisPlanVerificationAwa public static final String FORK_FIELD = "_fork"; public static final int MAX_BRANCHES = 8; - public static final int MIN_BRANCHES = 2; private final List output; public Fork(Source source, List children, List output) { super(source, children); - if (children.size() < MIN_BRANCHES) { - throw new IllegalArgumentException("FORK requires more than " + MIN_BRANCHES + " branches, got: " + children.size()); - } if (children.size() > MAX_BRANCHES) { throw new IllegalArgumentException("FORK supports up to " + MAX_BRANCHES + " branches, got: " + children.size()); } 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 16e77290506c1..f1f9c9ca04ccc 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 @@ -3861,11 +3861,12 @@ public void testForkAllCommands() { } public void testInvalidFork() { - expectError("FROM foo* | FORK (WHERE a:\"baz\")", "line 1:13: Fork requires at least 2 branches"); - expectError("FROM foo* | FORK (LIMIT 10)", "line 1:13: Fork requires at least 2 branches"); - expectError("FROM foo* | FORK (SORT a)", "line 1:13: Fork requires at least 2 branches"); - expectError("FROM foo* | FORK (WHERE x>1 | LIMIT 5)", "line 1:13: Fork requires at least 2 branches"); - expectError("FROM foo* | WHERE x>1 | FORK (WHERE a:\"baz\")", "Fork requires at least 2 branches"); + expectError(""" + FROM foo* | FORK + """, "line 2:1: mismatched input '' expecting '('"); + expectError(""" + FROM foo* | FORK () + """, "line 1:19: mismatched input ')'"); expectError(""" FROM foo*