diff --git a/docs/changelog/128917.yaml b/docs/changelog/128917.yaml
new file mode 100644
index 0000000000000..2d80fb5b43354
--- /dev/null
+++ b/docs/changelog/128917.yaml
@@ -0,0 +1,6 @@
+pr: 128917
+summary: Adopt a "LogicalPlan" approach to running multiple sub-queries (with INLINESTATS
+ so far)
+area: ES|QL
+type: enhancement
+issues: []
diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java
index 03fbcbc5b18fe..fd3852d8ecca3 100644
--- a/server/src/main/java/org/elasticsearch/TransportVersions.java
+++ b/server/src/main/java/org/elasticsearch/TransportVersions.java
@@ -333,6 +333,7 @@ static TransportVersion def(int id) {
public static final TransportVersion ESQL_SERIALIZE_TIMESERIES_FIELD_TYPE = def(9_114_0_00);
public static final TransportVersion ML_INFERENCE_IBM_WATSONX_COMPLETION_ADDED = def(9_115_0_00);
public static final TransportVersion ESQL_SPLIT_ON_BIG_VALUES = def(9_116_0_00);
+ public static final TransportVersion ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS = def(9_117_0_00);
/*
* STOP! READ THIS FIRST! No, really,
diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
index be0feade7f8af..f244069aab57a 100644
--- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
+++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java
@@ -49,7 +49,7 @@
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.FORK_V9;
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS;
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS_V2;
-import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS_V7;
+import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.INLINESTATS_V8;
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_LOOKUP_V12;
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.JOIN_PLANNING_V1;
import static org.elasticsearch.xpack.esql.action.EsqlCapabilities.Cap.METADATA_FIELDS_REMOTE_TEST;
@@ -128,7 +128,7 @@ protected void shouldSkipTest(String testName) throws IOException {
assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS.capabilityName()));
assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V2.capabilityName()));
assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(JOIN_PLANNING_V1.capabilityName()));
- assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V7.capabilityName()));
+ assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains(INLINESTATS_V8.capabilityName()));
assumeFalse("LOOKUP JOIN not yet supported in CCS", testCase.requiredCapabilities.contains(JOIN_LOOKUP_V12.capabilityName()));
// Unmapped fields require a coorect capability response from every cluster, which isn't currently implemented.
assumeFalse("UNMAPPED FIELDS not yet supported in CCS", testCase.requiredCapabilities.contains(UNMAPPED_FIELDS.capabilityName()));
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 7ec9ee6344551..c304e9d9b742d 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
@@ -83,6 +83,7 @@
import org.elasticsearch.xpack.esql.plan.logical.EsRelation;
import org.elasticsearch.xpack.esql.plan.logical.Limit;
import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan;
+import org.elasticsearch.xpack.esql.plan.logical.local.EmptyLocalSupplier;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
import org.elasticsearch.xpack.esql.plugin.EsqlPlugin;
@@ -446,7 +447,7 @@ public static Literal L(Object value) {
}
public static LogicalPlan emptySource() {
- return new LocalRelation(Source.EMPTY, emptyList(), LocalSupplier.EMPTY);
+ return new LocalRelation(Source.EMPTY, emptyList(), EmptyLocalSupplier.EMPTY);
}
public static LogicalPlan localSource(BlockFactory blockFactory, List fields, List
*/
-public interface LocalSupplier extends Supplier, Writeable {
-
- LocalSupplier EMPTY = new LocalSupplier() {
- @Override
- public Block[] get() {
- return BlockUtils.NO_BLOCKS;
- }
-
- @Override
- public String toString() {
- return "EMPTY";
- }
-
- @Override
- public void writeTo(StreamOutput out) throws IOException {
- out.writeVInt(0);
- }
-
- @Override
- public boolean equals(Object obj) {
- return obj == EMPTY;
- }
-
- @Override
- public int hashCode() {
- return 0;
- }
- };
+public interface LocalSupplier extends Supplier, NamedWriteable {
static LocalSupplier of(Block[] blocks) {
return new ImmediateLocalSupplier(blocks);
}
- static LocalSupplier readFrom(PlanStreamInput in) throws IOException {
- Block[] blocks = in.readCachedBlockArray();
- return blocks.length == 0 ? EMPTY : of(blocks);
- }
}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/HashJoinExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/HashJoinExec.java
index 3818b4e5a4c32..55a34af436f8a 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/HashJoinExec.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/HashJoinExec.java
@@ -23,6 +23,8 @@
import java.util.Objects;
import java.util.Set;
+import static org.elasticsearch.xpack.esql.expression.NamedExpressions.mergeOutputAttributes;
+
public class HashJoinExec extends BinaryExec implements EstimatesRowSize {
public static final NamedWriteableRegistry.Entry ENTRY = new NamedWriteableRegistry.Entry(
PhysicalPlan.class,
@@ -107,11 +109,12 @@ public PhysicalPlan estimateRowSize(State state) {
@Override
public List output() {
if (lazyOutput == null) {
- lazyOutput = new ArrayList<>(left().output());
- var rightFieldNames = rightFields.stream().map(Attribute::name).toList();
- lazyOutput.removeIf(a -> rightFieldNames.contains(a.name()));
- lazyOutput.addAll(addedFields);
- lazyOutput.addAll(rightFields);
+ List leftOutputWithoutKeys = left().output().stream().filter(attr -> leftFields.contains(attr) == false).toList();
+ List rightWithAppendedKeys = new ArrayList<>(right().output());
+ rightWithAppendedKeys.removeAll(rightFields);
+ rightWithAppendedKeys.addAll(leftFields);
+
+ lazyOutput = mergeOutputAttributes(rightWithAppendedKeys, leftOutputWithoutKeys);
}
return lazyOutput;
}
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExec.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExec.java
index 84e09bdd643a4..5994ce813c851 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExec.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExec.java
@@ -7,13 +7,17 @@
package org.elasticsearch.xpack.esql.plan.physical;
+import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.compute.data.Block;
import org.elasticsearch.xpack.esql.core.expression.Attribute;
import org.elasticsearch.xpack.esql.core.tree.NodeInfo;
import org.elasticsearch.xpack.esql.core.tree.Source;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
+import org.elasticsearch.xpack.esql.plan.logical.local.EmptyLocalSupplier;
+import org.elasticsearch.xpack.esql.plan.logical.local.ImmediateLocalSupplier;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
import java.io.IOException;
@@ -39,14 +43,41 @@ public LocalSourceExec(Source source, List output, LocalSupplier supp
public LocalSourceExec(StreamInput in) throws IOException {
super(Source.readFrom((PlanStreamInput) in));
this.output = in.readNamedWriteableCollectionAsList(Attribute.class);
- this.supplier = LocalSupplier.readFrom((PlanStreamInput) in);
+ if (in.getTransportVersion().onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ this.supplier = in.readNamedWriteable(LocalSupplier.class);
+ } else {
+ this.supplier = readLegacyLocalSupplierFrom((PlanStreamInput) in);
+ }
+ }
+
+ /**
+ * Legacy {@link LocalSupplier} deserialization for code that didn't use {@link org.elasticsearch.common.io.stream.NamedWriteable}s
+ * and the {@link LocalSupplier} had only one implementation (the {@link ImmediateLocalSupplier}).
+ *
+ * @param in
+ * @return
+ * @throws IOException
+ */
+ public static LocalSupplier readLegacyLocalSupplierFrom(PlanStreamInput in) throws IOException {
+ Block[] blocks = in.readCachedBlockArray();
+ return blocks.length == 0 ? EmptyLocalSupplier.EMPTY : LocalSupplier.of(blocks);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
source().writeTo(out);
out.writeNamedWriteableCollection(output);
- supplier.writeTo(out);
+ if (out.getTransportVersion().onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ out.writeNamedWriteable(supplier);
+ } else {
+ if (supplier == EmptyLocalSupplier.EMPTY) {
+ out.writeVInt(0);
+ } else {
+ // here we can only have an ImmediateLocalSupplier as this was the only implementation apart from EMPTY
+ // for earlier versions
+ ((ImmediateLocalSupplier) supplier).writeTo(out);
+ }
+ }
}
@Override
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java
index 137a2118b0d54..4bdb90af10316 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/mapper/Mapper.java
@@ -25,7 +25,6 @@
import org.elasticsearch.xpack.esql.plan.logical.TopN;
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;
import org.elasticsearch.xpack.esql.plan.logical.inference.Rerank;
-import org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin;
import org.elasticsearch.xpack.esql.plan.logical.join.Join;
import org.elasticsearch.xpack.esql.plan.logical.join.JoinConfig;
import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes;
@@ -207,14 +206,10 @@ private PhysicalPlan mapBinary(BinaryPlan bp) {
throw new EsqlIllegalArgumentException("unsupported join type [" + config.type() + "]");
}
- if (join instanceof InlineJoin) {
- return new FragmentExec(bp);
- }
-
PhysicalPlan left = map(bp.left());
// only broadcast joins supported for now - hence push down as a streaming operator
- if (left instanceof FragmentExec fragment) {
+ if (left instanceof FragmentExec) {
return new FragmentExec(bp);
}
@@ -228,7 +223,7 @@ private PhysicalPlan mapBinary(BinaryPlan bp) {
config.matchFields(),
config.leftFields(),
config.rightFields(),
- join.output()
+ join.rightOutputFields()
);
}
if (right instanceof FragmentExec fragment
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 b6dd0c40f3481..090f85936a3ae 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
@@ -104,7 +104,6 @@
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -114,6 +113,7 @@
import static org.elasticsearch.index.query.QueryBuilders.boolQuery;
import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY;
import static org.elasticsearch.xpack.esql.core.util.StringUtils.WILDCARD;
+import static org.elasticsearch.xpack.esql.plan.logical.join.InlineJoin.firstSubPlan;
public class EsqlSession {
@@ -217,8 +217,8 @@ public void executeOptimizedPlan(
LogicalPlan optimizedPlan,
ActionListener listener
) {
- PhysicalPlan physicalPlan = logicalPlanToPhysicalPlan(optimizedPlan, request);
- if (explainMode) {
+ if (explainMode) {// TODO: INLINESTATS come back to the explain mode branch and reevaluate
+ PhysicalPlan physicalPlan = logicalPlanToPhysicalPlan(optimizedPlan, request);
String physicalPlanString = physicalPlan.toString();
List fields = List.of(
new ReferenceAttribute(EMPTY, "role", DataType.KEYWORD),
@@ -231,44 +231,30 @@ public void executeOptimizedPlan(
values.add(List.of("coordinator", "optimizedPhysicalPlan", physicalPlanString));
var blocks = BlockUtils.fromList(PlannerUtils.NON_BREAKING_BLOCK_FACTORY, values);
physicalPlan = new LocalSourceExec(Source.EMPTY, fields, LocalSupplier.of(blocks));
+ planRunner.run(physicalPlan, listener);
+ } else {
+ // TODO: this could be snuck into the underlying listener
+ EsqlCCSUtils.updateExecutionInfoAtEndOfPlanning(executionInfo);
+ // execute any potential subplans
+ executeSubPlans(optimizedPlan, planRunner, executionInfo, request, listener);
}
- // TODO: this could be snuck into the underlying listener
- EsqlCCSUtils.updateExecutionInfoAtEndOfPlanning(executionInfo);
- // execute any potential subplans
- executeSubPlans(physicalPlan, planRunner, executionInfo, request, listener);
}
- private record PlanTuple(PhysicalPlan physical, LogicalPlan logical) {}
-
private void executeSubPlans(
- PhysicalPlan physicalPlan,
+ LogicalPlan optimizedPlan,
PlanRunner runner,
EsqlExecutionInfo executionInfo,
EsqlQueryRequest request,
ActionListener listener
) {
- List subplans = new ArrayList<>();
-
- // Currently the inlinestats are limited and supported as streaming operators, thus present inside the fragment as logical plans
- // Below they get collected, translated into a separate, coordinator based plan and the results 'broadcasted' as a local relation
- physicalPlan.forEachUp(FragmentExec.class, f -> {
- f.fragment().forEachUp(InlineJoin.class, ij -> {
- // extract the right side of the plan and replace its source
- LogicalPlan subplan = InlineJoin.replaceStub(ij.left(), ij.right());
- // mark the new root node as optimized
- subplan.setOptimized();
- PhysicalPlan subqueryPlan = logicalPlanToPhysicalPlan(subplan, request);
- subplans.add(new PlanTuple(subqueryPlan, ij.right()));
- });
- });
-
- Iterator iterator = subplans.iterator();
+ var subPlan = firstSubPlan(optimizedPlan);
// TODO: merge into one method
- if (subplans.size() > 0) {
+ if (subPlan != null) {
// code-path to execute subplans
- executeSubPlan(new DriverCompletionInfo.Accumulator(), physicalPlan, iterator, executionInfo, runner, listener);
+ executeSubPlan(new DriverCompletionInfo.Accumulator(), optimizedPlan, subPlan, executionInfo, runner, request, listener);
} else {
+ PhysicalPlan physicalPlan = logicalPlanToPhysicalPlan(optimizedPlan, request);
// execute main plan
runner.run(physicalPlan, listener);
}
@@ -276,40 +262,47 @@ private void executeSubPlans(
private void executeSubPlan(
DriverCompletionInfo.Accumulator completionInfoAccumulator,
- PhysicalPlan plan,
- Iterator subPlanIterator,
+ LogicalPlan optimizedPlan,
+ InlineJoin.LogicalPlanTuple subPlans,
EsqlExecutionInfo executionInfo,
PlanRunner runner,
+ EsqlQueryRequest request,
ActionListener listener
) {
- PlanTuple tuple = subPlanIterator.next();
+ LOGGER.debug("Executing subplan:\n{}", subPlans.stubReplacedSubPlan());
+ // Create a physical plan out of the logical sub-plan
+ var physicalSubPlan = logicalPlanToPhysicalPlan(subPlans.stubReplacedSubPlan(), request);
- runner.run(tuple.physical, listener.delegateFailureAndWrap((next, result) -> {
+ runner.run(physicalSubPlan, listener.delegateFailureAndWrap((next, result) -> {
try {
+ // Translate the subquery into a separate, coordinator based plan and the results 'broadcasted' as a local relation
completionInfoAccumulator.accumulate(result.completionInfo());
- LocalRelation resultWrapper = resultToPlan(tuple.logical, result);
+ LocalRelation resultWrapper = resultToPlan(subPlans.stubReplacedSubPlan(), result);
// replace the original logical plan with the backing result
- PhysicalPlan newPlan = plan.transformUp(FragmentExec.class, f -> {
- LogicalPlan frag = f.fragment();
- return f.withFragment(
- frag.transformUp(
- InlineJoin.class,
- ij -> ij.right() == tuple.logical ? InlineJoin.inlineData(ij, resultWrapper) : ij
- )
- );
- });
-
- if (subPlanIterator.hasNext() == false) {
- runner.run(newPlan, next.delegateFailureAndWrap((finalListener, finalResult) -> {
+ LogicalPlan newLogicalPlan = optimizedPlan.transformUp(
+ InlineJoin.class,
+ // use object equality since the right-hand side shouldn't have changed in the optimizedPlan at this point
+ // and equals would have ignored name IDs anyway
+ ij -> ij.right() == subPlans.originalSubPlan() ? InlineJoin.inlineData(ij, resultWrapper) : ij
+ );
+ // TODO: INLINESTATS can we do better here and further optimize the plan AFTER one of the subplans executed?
+ newLogicalPlan.setOptimized();
+ LOGGER.debug("Plan after previous subplan execution:\n{}", newLogicalPlan);
+ // look for the next inlinejoin plan
+ var newSubPlan = firstSubPlan(newLogicalPlan);
+
+ if (newSubPlan == null) {// run the final "main" plan
+ LOGGER.debug("Executing final plan:\n{}", newLogicalPlan);
+ var newPhysicalPlan = logicalPlanToPhysicalPlan(newLogicalPlan, request);
+ runner.run(newPhysicalPlan, next.delegateFailureAndWrap((finalListener, finalResult) -> {
completionInfoAccumulator.accumulate(finalResult.completionInfo());
finalListener.onResponse(
new Result(finalResult.schema(), finalResult.pages(), completionInfoAccumulator.finish(), executionInfo)
);
}));
- } else {
- // continue executing the subplans
- executeSubPlan(completionInfoAccumulator, newPlan, subPlanIterator, executionInfo, runner, next);
+ } else {// continue executing the subplans
+ executeSubPlan(completionInfoAccumulator, newLogicalPlan, newSubPlan, executionInfo, runner, request, listener);
}
} finally {
Releasables.closeExpectNoException(Releasables.wrap(Iterators.map(result.pages().iterator(), p -> p::releaseBlocks)));
@@ -317,7 +310,7 @@ private void executeSubPlan(
}));
}
- private LocalRelation resultToPlan(LogicalPlan plan, Result result) {
+ private static LocalRelation resultToPlan(LogicalPlan plan, Result result) {
List pages = result.pages();
List schema = result.schema();
// if (pages.size() > 1) {
diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/SessionUtils.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/SessionUtils.java
index 85abc635967a6..294d8110fd7b2 100644
--- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/SessionUtils.java
+++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/SessionUtils.java
@@ -26,7 +26,7 @@ public static Block[] fromPages(List schema, List pages) {
// Limit ourselves to 1mb of results similar to LOOKUP for now.
long bytesUsed = pages.stream().mapToLong(Page::ramBytesUsedByBlocks).sum();
if (bytesUsed > ByteSizeValue.ofMb(1).getBytes()) {
- throw new IllegalArgumentException("first phase result too large [" + ByteSizeValue.ofBytes(bytesUsed) + "] > 1mb");
+ throw new IllegalArgumentException("sub-plan execution results too large [" + ByteSizeValue.ofBytes(bytesUsed) + "] > 1mb");
}
int positionCount = pages.stream().mapToInt(Page::getPositionCount).sum();
Block.Builder[] builders = new Block.Builder[schema.size()];
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 6ae6c6eea3b6f..0b69fc4cdd6f3 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
@@ -4120,6 +4120,21 @@ public void testImplicitCastingForDateAndDateNanosFields() {
assertEquals("test*", esRelation.indexPattern());
}
+ public void testGroupingOverridesInStats() {
+ verifyUnsupported("""
+ from test
+ | stats MIN(salary) BY x = languages, x = x + 1
+ """, "Found 1 problem\n" + "line 2:43: Unknown column [x]", "mapping-default.json");
+ }
+
+ public void testGroupingOverridesInInlinestats() {
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
+ verifyUnsupported("""
+ from test
+ | inlinestats MIN(salary) BY x = languages, x = x + 1
+ """, "Found 1 problem\n" + "line 2:49: Unknown column [x]", "mapping-default.json");
+ }
+
private void verifyNameAndType(String actualName, DataType actualType, String expectedName, DataType expectedType) {
assertEquals(expectedName, actualName);
assertEquals(expectedType, actualType);
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java
index f2b70c99253b8..02805933979a8 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalPlanOptimizerTests.java
@@ -52,9 +52,9 @@
import org.elasticsearch.xpack.esql.plan.logical.Project;
import org.elasticsearch.xpack.esql.plan.logical.Row;
import org.elasticsearch.xpack.esql.plan.logical.UnaryPlan;
+import org.elasticsearch.xpack.esql.plan.logical.local.EmptyLocalSupplier;
import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
-import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
import org.elasticsearch.xpack.esql.stats.SearchStats;
import org.hamcrest.Matchers;
import org.junit.BeforeClass;
@@ -681,7 +681,7 @@ public void testReplaceStringCasingAndRLikeWithLocalRelation() {
var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) RLIKE \"VALÜ*\"");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
}
// same plan as in testReplaceUpperStringCasingWithInsensitiveRLike, but with LIKE instead of RLIKE
@@ -720,7 +720,7 @@ public void testReplaceStringCasingAndLikeWithLocalRelation() {
var plan = localPlan("FROM test | WHERE TO_LOWER(TO_UPPER(first_name)) LIKE \"VALÜ*\"");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
}
/**
@@ -780,7 +780,7 @@ private IsNotNull isNotNull(Expression field) {
private LocalRelation asEmptyRelation(Object o) {
var empty = as(o, LocalRelation.class);
- assertThat(empty.supplier(), is(LocalSupplier.EMPTY));
+ assertThat(empty.supplier(), is(EmptyLocalSupplier.EMPTY));
return empty;
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java
index 7795bf5c2d9ff..d159f9949f2b2 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanOptimizerTests.java
@@ -128,9 +128,9 @@
import org.elasticsearch.xpack.esql.plan.logical.join.JoinConfig;
import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes;
import org.elasticsearch.xpack.esql.plan.logical.join.LookupJoin;
+import org.elasticsearch.xpack.esql.plan.logical.local.EmptyLocalSupplier;
import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
-import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
import java.time.Duration;
import java.util.ArrayList;
@@ -1238,7 +1238,7 @@ public void testPushdownLimitsPastLeftJoin() {
var rule = new PushDownAndCombineLimits();
var leftChild = emptySource();
- var rightChild = new LocalRelation(Source.EMPTY, List.of(fieldAttribute()), LocalSupplier.EMPTY);
+ var rightChild = new LocalRelation(Source.EMPTY, List.of(fieldAttribute()), EmptyLocalSupplier.EMPTY);
assertNotEquals(leftChild, rightChild);
var joinConfig = new JoinConfig(JoinTypes.LEFT, List.of(), List.of(), List.of());
@@ -3089,7 +3089,7 @@ public void testFoldInEval() {
""");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), is(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), is(EmptyLocalSupplier.EMPTY));
}
public void testFoldFromRow() {
@@ -5340,7 +5340,7 @@ public void testNoWrongIsNotNullPruning() {
""");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
assertWarnings(
"Line 2:16: evaluation of [a + b] failed, treating result as null. Only first 20 failures recorded.",
"Line 2:16: java.lang.IllegalArgumentException: single-value function encountered multi-value"
@@ -6107,7 +6107,7 @@ public void testSimplifyComparisonArithmeticSkippedOnFloats() {
public void testReplaceStringCasingWithInsensitiveEqualsUpperFalse() {
var plan = optimizedPlan("FROM test | WHERE TO_UPPER(first_name) == \"VALÜe\"");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
}
public void testReplaceStringCasingWithInsensitiveEqualsUpperTrue() {
@@ -6122,7 +6122,7 @@ public void testReplaceStringCasingWithInsensitiveEqualsUpperTrue() {
public void testReplaceStringCasingWithInsensitiveEqualsLowerFalse() {
var plan = optimizedPlan("FROM test | WHERE TO_LOWER(first_name) == \"VALÜe\"");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
}
public void testReplaceStringCasingWithInsensitiveEqualsLowerTrue() {
@@ -7318,7 +7318,7 @@ public void testWhereNull() {
| LIMIT 12
""");
var local = as(plan, LocalRelation.class);
- assertThat(local.supplier(), equalTo(LocalSupplier.EMPTY));
+ assertThat(local.supplier(), equalTo(EmptyLocalSupplier.EMPTY));
}
public void testFunctionNamedParamsAsFunctionArgument() {
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java
index 99eded20b1687..ef9887e98cdc2 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/PhysicalPlanOptimizerTests.java
@@ -109,8 +109,8 @@
import org.elasticsearch.xpack.esql.plan.logical.TopN;
import org.elasticsearch.xpack.esql.plan.logical.join.Join;
import org.elasticsearch.xpack.esql.plan.logical.join.JoinTypes;
+import org.elasticsearch.xpack.esql.plan.logical.local.EmptyLocalSupplier;
import org.elasticsearch.xpack.esql.plan.logical.local.LocalRelation;
-import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier;
import org.elasticsearch.xpack.esql.plan.physical.AggregateExec;
import org.elasticsearch.xpack.esql.plan.physical.DissectExec;
import org.elasticsearch.xpack.esql.plan.physical.EnrichExec;
@@ -2547,7 +2547,7 @@ public void testLocallyMissingField() {
var limit = as(optimized, LimitExec.class);
var exchange = as(limit.child(), ExchangeExec.class);
var source = as(exchange.child(), LocalSourceExec.class);
- assertEquals(LocalSupplier.EMPTY, source.supplier());
+ assertEquals(EmptyLocalSupplier.EMPTY, source.supplier());
}
/**
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java
index 088b5c1c9205e..148cfd7ca4fa6 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PropagateInlineEvalsTests.java
@@ -83,7 +83,7 @@ public static void init() {
* \_StubRelation[[emp_no{f}#11, languages{f}#14, gender{f}#13, y{r}#10]]
*/
public void testGroupingAliasingMoved_To_LeftSideOfJoin() {
- assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
var plan = plan("""
from test
| keep emp_no, languages, gender
@@ -126,7 +126,7 @@ public void testGroupingAliasingMoved_To_LeftSideOfJoin() {
* {r}#21]]
*/
public void testGroupingAliasingMoved_To_LeftSideOfJoin_WithExpression() {
- assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("Requires INLINESTATS", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
var plan = plan("""
from test
| keep emp_no, languages, gender, last_name, first_name
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/AbstractLogicalPlanSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/AbstractLogicalPlanSerializationTests.java
index 415edea4cd40c..4960bae3d62f4 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/AbstractLogicalPlanSerializationTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/AbstractLogicalPlanSerializationTests.java
@@ -30,6 +30,7 @@ public static LogicalPlan randomChild(int depth) {
protected final NamedWriteableRegistry getNamedWriteableRegistry() {
List entries = new ArrayList<>();
entries.addAll(PlanWritables.logical());
+ entries.addAll(PlanWritables.others());
entries.addAll(ExpressionWritables.aggregates());
entries.addAll(ExpressionWritables.allExpressions());
return new NamedWriteableRegistry(entries);
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/CopyingLocalSupplierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/CopyingLocalSupplierTests.java
new file mode 100644
index 0000000000000..b5503c5c6cc27
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/CopyingLocalSupplierTests.java
@@ -0,0 +1,33 @@
+/*
+ * 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.plan.logical.local;
+
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.TransportVersions;
+import org.elasticsearch.compute.data.Block;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class CopyingLocalSupplierTests extends LocalSupplierTests {
+
+ @Override
+ protected LocalSupplier createTestInstance() {
+ Block[] blocks = randomList(1, 10, LocalSupplierTests::randomBlock).toArray(Block[]::new);
+ return new CopyingLocalSupplier(blocks);
+ }
+
+ protected void assertOnBWCObject(LocalSupplier testInstance, LocalSupplier bwcDeserializedObject, TransportVersion version) {
+ assertNotSame(version.toString(), bwcDeserializedObject, testInstance);
+ if (version.onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ assertThat(testInstance, equalTo(bwcDeserializedObject));
+ } else {
+ assertTrue(version.toString(), bwcDeserializedObject instanceof ImmediateLocalSupplier);
+ }
+ assertEquals(version.toString(), testInstance.hashCode(), bwcDeserializedObject.hashCode());
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/EmptyLocalSupplierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/EmptyLocalSupplierTests.java
new file mode 100644
index 0000000000000..c1a12e50417df
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/EmptyLocalSupplierTests.java
@@ -0,0 +1,40 @@
+/*
+ * 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.plan.logical.local;
+
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.TransportVersions;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class EmptyLocalSupplierTests extends LocalSupplierTests {
+
+ @Override
+ protected LocalSupplier createTestInstance() {
+ return EmptyLocalSupplier.EMPTY;
+ }
+
+ protected void assertOnBWCObject(LocalSupplier testInstance, LocalSupplier bwcDeserializedObject, TransportVersion version) {
+ assertSame(version.toString(), bwcDeserializedObject, testInstance);
+ assertThat(version.toString(), bwcDeserializedObject, equalTo(EmptyLocalSupplier.EMPTY));
+ assertEquals(version.toString(), testInstance.hashCode(), bwcDeserializedObject.hashCode());
+ }
+
+ @Override
+ protected void writeTo(BytesStreamOutput output, LocalSupplier instance, TransportVersion version) throws IOException {
+ if (version.onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ new PlanStreamOutput(output, null).writeNamedWriteable(instance);
+ } else {
+ output.writeVInt(0);
+ }
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/ImmediateLocalSupplierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/ImmediateLocalSupplierTests.java
new file mode 100644
index 0000000000000..1de9581f4dbc0
--- /dev/null
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/ImmediateLocalSupplierTests.java
@@ -0,0 +1,28 @@
+/*
+ * 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.plan.logical.local;
+
+import org.elasticsearch.TransportVersion;
+import org.elasticsearch.compute.data.Block;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class ImmediateLocalSupplierTests extends LocalSupplierTests {
+
+ @Override
+ protected LocalSupplier createTestInstance() {
+ Block[] blocks = randomList(1, 10, LocalSupplierTests::randomBlock).toArray(Block[]::new);
+ return new ImmediateLocalSupplier(blocks);
+ }
+
+ protected void assertOnBWCObject(LocalSupplier testInstance, LocalSupplier bwcDeserializedObject, TransportVersion version) {
+ assertNotSame(version.toString(), bwcDeserializedObject, testInstance);
+ assertThat(version.toString(), testInstance, equalTo(bwcDeserializedObject));
+ assertEquals(version.toString(), testInstance.hashCode(), bwcDeserializedObject.hashCode());
+ }
+}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/LocalSupplierTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/LocalSupplierTests.java
index b7e934e273f00..1d144b995711f 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/LocalSupplierTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/logical/local/LocalSupplierTests.java
@@ -8,45 +8,95 @@
package org.elasticsearch.xpack.esql.plan.logical.local;
import org.elasticsearch.TransportVersion;
+import org.elasticsearch.TransportVersions;
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.compute.data.Block;
import org.elasticsearch.compute.data.BlockFactory;
import org.elasticsearch.compute.data.IntBlock;
import org.elasticsearch.test.AbstractWireTestCase;
+import org.elasticsearch.test.TransportVersionUtils;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput;
import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput;
+import org.elasticsearch.xpack.esql.plan.PlanWritables;
+import org.elasticsearch.xpack.esql.plan.physical.LocalSourceExec;
import java.io.IOException;
import java.util.Arrays;
+import java.util.NavigableSet;
+
+public abstract class LocalSupplierTests extends AbstractWireTestCase {
+
+ private static final NavigableSet DEFAULT_BWC_VERSIONS = getAllBWCVersions();
-public class LocalSupplierTests extends AbstractWireTestCase {
private static final BlockFactory BLOCK_FACTORY = BlockFactory.getInstance(
new NoopCircuitBreaker("noop-esql-breaker"),
BigArrays.NON_RECYCLING_INSTANCE
);
+ private static NavigableSet getAllBWCVersions() {
+ return TransportVersionUtils.allReleasedVersions().tailSet(TransportVersions.MINIMUM_COMPATIBLE, true);
+ }
+
+ public final void testBwcSerialization() throws IOException {
+ for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
+ LocalSupplier testInstance = createTestInstance();
+ for (TransportVersion bwcVersion : DEFAULT_BWC_VERSIONS) {
+ assertBwcSerialization(testInstance, bwcVersion);
+ }
+ }
+ }
+
+ protected final void assertBwcSerialization(LocalSupplier testInstance, TransportVersion version) throws IOException {
+ LocalSupplier deserializedInstance = copyInstance(testInstance, version);
+ assertOnBWCObject(testInstance, deserializedInstance, version);
+ }
+
+ protected abstract void assertOnBWCObject(LocalSupplier testInstance, LocalSupplier bwcDeserializedObject, TransportVersion version);
+
@Override
protected LocalSupplier copyInstance(LocalSupplier instance, TransportVersion version) throws IOException {
try (BytesStreamOutput output = new BytesStreamOutput()) {
output.setTransportVersion(version);
- instance.writeTo(new PlanStreamOutput(output, null));
+ writeTo(output, instance, version);
try (StreamInput in = output.bytes().streamInput()) {
in.setTransportVersion(version);
- return LocalSupplier.readFrom(new PlanStreamInput(in, getNamedWriteableRegistry(), null));
+ return readFrom(in, version);
}
}
}
+ protected void writeTo(BytesStreamOutput output, LocalSupplier instance, TransportVersion version) throws IOException {
+ if (version.onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ new PlanStreamOutput(output, null).writeNamedWriteable(instance);
+ } else {
+ instance.writeTo(new PlanStreamOutput(output, null));
+ }
+ }
+
+ protected LocalSupplier readFrom(StreamInput input, TransportVersion version) throws IOException {
+ if (version.onOrAfter(TransportVersions.ESQL_LOCAL_RELATION_WITH_NEW_BLOCKS)) {
+ return new PlanStreamInput(input, getNamedWriteableRegistry(), null).readNamedWriteable(LocalSupplier.class);
+ } else {
+ return LocalSourceExec.readLegacyLocalSupplierFrom(new PlanStreamInput(input, getNamedWriteableRegistry(), null));
+ }
+ }
+
@Override
protected LocalSupplier createTestInstance() {
- return randomBoolean() ? LocalSupplier.EMPTY : randomNonEmpty();
+ return randomLocalSupplier();
+ }
+
+ public static LocalSupplier randomLocalSupplier() {
+ return randomBoolean() ? EmptyLocalSupplier.EMPTY : randomNonEmpty();
}
public static LocalSupplier randomNonEmpty() {
- return LocalSupplier.of(randomList(1, 10, LocalSupplierTests::randomBlock).toArray(Block[]::new));
+ Block[] blocks = randomList(1, 10, LocalSupplierTests::randomBlock).toArray(Block[]::new);
+ return randomBoolean() ? LocalSupplier.of(blocks) : new CopyingLocalSupplier(blocks);
}
@Override
@@ -54,7 +104,7 @@ protected LocalSupplier mutateInstance(LocalSupplier instance) throws IOExceptio
Block[] blocks = instance.get();
if (blocks.length > 0 && randomBoolean()) {
if (randomBoolean()) {
- return LocalSupplier.EMPTY;
+ return EmptyLocalSupplier.EMPTY;
}
return LocalSupplier.of(Arrays.copyOf(blocks, blocks.length - 1, Block[].class));
}
@@ -63,7 +113,12 @@ protected LocalSupplier mutateInstance(LocalSupplier instance) throws IOExceptio
return LocalSupplier.of(blocks);
}
- private static Block randomBlock() {
+ @Override
+ protected NamedWriteableRegistry getNamedWriteableRegistry() {
+ return new NamedWriteableRegistry(PlanWritables.others());
+ }
+
+ static Block randomBlock() {
int len = between(1, 1000);
try (IntBlock.Builder ints = BLOCK_FACTORY.newIntBlockBuilder(len)) {
for (int i = 0; i < len; i++) {
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExecSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExecSerializationTests.java
index 2a5f844d3fee0..e77acc06e4706 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExecSerializationTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plan/physical/LocalSourceExecSerializationTests.java
@@ -19,7 +19,7 @@ public class LocalSourceExecSerializationTests extends AbstractPhysicalPlanSeria
public static LocalSourceExec randomLocalSourceExec() {
Source source = randomSource();
List output = randomFieldAttributes(1, 9, false);
- LocalSupplier supplier = randomBoolean() ? LocalSupplier.EMPTY : LocalSupplierTests.randomNonEmpty();
+ LocalSupplier supplier = LocalSupplierTests.randomLocalSupplier();
return new LocalSourceExec(source, output, supplier);
}
@@ -35,7 +35,7 @@ protected LocalSourceExec mutateInstance(LocalSourceExec instance) throws IOExce
if (randomBoolean()) {
output = randomValueOtherThan(output, () -> randomFieldAttributes(1, 9, false));
} else {
- supplier = randomValueOtherThan(supplier, () -> randomBoolean() ? LocalSupplier.EMPTY : LocalSupplierTests.randomNonEmpty());
+ supplier = randomValueOtherThan(supplier, () -> LocalSupplierTests.randomLocalSupplier());
}
return new LocalSourceExec(instance.source(), output, supplier);
}
diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
index ae66488f419e1..7dee2803231e0 100644
--- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
+++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/IndexResolverFieldNamesTests.java
@@ -30,7 +30,7 @@ public void testBasicFromCommand() {
}
public void testBasicFromCommandWithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("from test | inlinestats max(salary) by gender", ALL_FIELDS);
}
@@ -39,7 +39,7 @@ public void testBasicFromCommandWithMetadata() {
}
public void testBasicFromCommandWithMetadata_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("from test metadata _index, _id, _version | inlinestats max(salary)", ALL_FIELDS);
}
@@ -305,7 +305,7 @@ public void testLimitZero() {
}
public void testLimitZero_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
FROM employees
| INLINESTATS COUNT(*), MAX(salary) BY gender
@@ -320,7 +320,7 @@ public void testDocsDropHeight() {
}
public void testDocsDropHeight_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
FROM employees
| DROP height
@@ -336,7 +336,7 @@ public void testDocsDropHeightWithWildcard() {
}
public void testDocsDropHeightWithWildcard_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
FROM employees
| INLINESTATS MAX(salary) BY gender
@@ -503,7 +503,7 @@ public void testSortWithLimitOne_DropHeight() {
}
public void testSortWithLimitOne_DropHeight_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("from employees | inlinestats avg(salary) by languages | sort languages | limit 1 | drop height*", ALL_FIELDS);
}
@@ -803,7 +803,7 @@ public void testFilterById() {
}
public void testFilterById_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("FROM apps metadata _id | INLINESTATS max(rate) | WHERE _id == \"4\"", ALL_FIELDS);
}
@@ -1274,7 +1274,7 @@ public void testProjectDropPattern() {
}
public void testProjectDropPattern_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| inlinestats max(foo) by bar
@@ -1357,7 +1357,7 @@ public void testCountAllAndOtherStatGrouped() {
}
public void testCountAllAndOtherStatGrouped_WithInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| inlinestats c = count(*), min = min(emp_no) by languages
@@ -1396,7 +1396,7 @@ public void testCountAllWithEval() {
}
public void testCountAllWithEval_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| rename languages as l
@@ -1409,7 +1409,7 @@ public void testCountAllWithEval_AndInlinestats() {
}
public void testKeepAfterEval_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| rename languages as l
@@ -1422,7 +1422,7 @@ public void testKeepAfterEval_AndInlinestats() {
}
public void testKeepBeforeEval_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| rename languages as l
@@ -1435,7 +1435,7 @@ public void testKeepBeforeEval_AndInlinestats() {
}
public void testStatsBeforeEval_AndInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| rename languages as l
@@ -1447,7 +1447,7 @@ public void testStatsBeforeEval_AndInlinestats() {
}
public void testStatsBeforeInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| stats min = min(salary) by languages
@@ -1456,7 +1456,7 @@ public void testStatsBeforeInlinestats() {
}
public void testKeepBeforeInlinestats() {
- assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V7.isEnabled());
+ assumeTrue("INLINESTATS required", EsqlCapabilities.Cap.INLINESTATS_V8.isEnabled());
assertFieldNames("""
from test
| keep languages, salary