diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java index 8c4aa2582355e..4c9e51aa6b961 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/_nightly/esql/QueryPlanningBenchmark.java @@ -9,14 +9,12 @@ package org.elasticsearch.benchmark._nightly.esql; +import org.elasticsearch.TransportVersion; import org.elasticsearch.common.logging.LogConfigurator; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.IndexMode; import org.elasticsearch.license.XPackLicenseState; -import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; -import org.elasticsearch.xpack.esql.analysis.EnrichResolution; -import org.elasticsearch.xpack.esql.analysis.Verifier; +import org.elasticsearch.xpack.esql.analysis.*; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.core.util.DateUtils; @@ -29,7 +27,6 @@ import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.parser.QueryParams; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; import org.elasticsearch.xpack.esql.telemetry.Metrics; @@ -80,15 +77,15 @@ public void setup() { null, null, new QueryPragmas(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), "", false, Map.of(), System.nanoTime(), false, - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY) ); var fields = 10_000; @@ -102,6 +99,9 @@ public void setup() { var functionRegistry = new EsqlFunctionRegistry(); + // Assume all nodes are on the current version for the benchmark. + TransportVersion minimumVersion = TransportVersion.current(); + telemetry = new PlanTelemetry(functionRegistry); defaultParser = new EsqlParser(); manyFieldsAnalyzer = new Analyzer( @@ -111,11 +111,12 @@ public void setup() { IndexResolution.valid(esIndex), Map.of(), new EnrichResolution(), - InferenceResolution.EMPTY + InferenceResolution.EMPTY, + minimumVersion ), new Verifier(new Metrics(functionRegistry), new XPackLicenseState(() -> 0L)) ); - defaultOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(config, FoldContext.small())); + defaultOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(config, FoldContext.small(), minimumVersion)); } private LogicalPlan plan(EsqlParser parser, Analyzer analyzer, LogicalPlanOptimizer optimizer, String query) { diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java index 4eed678b29728..112c5e7fc6861 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java @@ -33,6 +33,7 @@ import org.elasticsearch.core.TimeValue; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; @@ -55,7 +56,6 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.Equals; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThan; import org.elasticsearch.xpack.esql.planner.Layout; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.session.Configuration; import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.BenchmarkMode; @@ -359,15 +359,15 @@ private static Configuration configuration() { null, null, null, - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.get(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.get(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY), null, false, Map.of(), 0, false, - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } diff --git a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java index afbd6e0c55978..837139eb6d552 100644 --- a/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java +++ b/x-pack/plugin/esql-core/src/main/java/org/elasticsearch/xpack/esql/core/expression/FieldAttribute.java @@ -48,7 +48,8 @@ public record FieldName(String string) {}; FieldAttribute::readFrom ); - private static final TransportVersion ESQL_FIELD_ATTRIBUTE_DROP_TYPE = TransportVersion.fromName("esql_field_attribute_drop_type"); + // Only public for testing + public static final TransportVersion ESQL_FIELD_ATTRIBUTE_DROP_TYPE = TransportVersion.fromName("esql_field_attribute_drop_type"); private final String parentName; private final EsField field; 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 1735083435ce7..88469a8d19e8c 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 @@ -12,6 +12,7 @@ import org.apache.lucene.sandbox.document.HalfFloatPoint; import org.apache.lucene.util.BytesRef; import org.elasticsearch.ExceptionsHelper; +import org.elasticsearch.TransportVersion; import org.elasticsearch.client.internal.Client; import org.elasticsearch.cluster.ClusterName; import org.elasticsearch.cluster.RemoteException; @@ -54,12 +55,15 @@ import org.elasticsearch.search.aggregations.bucket.geogrid.GeoTileUtils; import org.elasticsearch.tasks.TaskCancelledException; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.TransportVersionUtils; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.RemoteTransportException; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xcontent.XContentType; import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.esql.action.EsqlQueryResponse; +import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.core.expression.Attribute; @@ -90,6 +94,7 @@ import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.LessThanOrEqual; import org.elasticsearch.xpack.esql.expression.predicate.operator.comparison.NotEquals; import org.elasticsearch.xpack.esql.index.EsIndex; +import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.inference.InferenceResolution; import org.elasticsearch.xpack.esql.inference.InferenceService; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; @@ -103,8 +108,6 @@ import org.elasticsearch.xpack.esql.plan.logical.local.LocalSupplier; import org.elasticsearch.xpack.esql.planner.PlannerSettings; import org.elasticsearch.xpack.esql.planner.PlannerUtils; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; -import org.elasticsearch.xpack.esql.plugin.EsqlQueryClusterSettings; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; import org.elasticsearch.xpack.esql.session.Configuration; @@ -430,8 +433,45 @@ public Object max(FieldName field) { public static final Configuration TEST_CFG = configuration(new QueryPragmas(Settings.EMPTY)); + public static TransportVersion randomMinimumVersion() { + return TransportVersionUtils.randomCompatibleVersion(ESTestCase.random()); + } + + // TODO: make this even simpler, remove the enrichResolution for tests that do not require it (most tests) + public static AnalyzerContext testAnalyzerContext( + Configuration configuration, + EsqlFunctionRegistry functionRegistry, + IndexResolution indexResolution, + EnrichResolution enrichResolution, + InferenceResolution inferenceResolution + ) { + return testAnalyzerContext(configuration, functionRegistry, indexResolution, Map.of(), enrichResolution, inferenceResolution); + } + + /** + * Analyzer context for a random (but compatible) minimum transport version. + */ + public static AnalyzerContext testAnalyzerContext( + Configuration configuration, + EsqlFunctionRegistry functionRegistry, + IndexResolution indexResolution, + Map lookupResolution, + EnrichResolution enrichResolution, + InferenceResolution inferenceResolution + ) { + return new AnalyzerContext( + configuration, + functionRegistry, + indexResolution, + lookupResolution, + enrichResolution, + inferenceResolution, + randomMinimumVersion() + ); + } + public static LogicalOptimizerContext unboundLogicalOptimizerContext() { - return new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small()); + return new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small(), randomMinimumVersion()); } public static final Verifier TEST_VERIFIER = new Verifier(new Metrics(new EsqlFunctionRegistry()), new XPackLicenseState(() -> 0L)); @@ -483,15 +523,15 @@ public static Configuration configuration(QueryPragmas pragmas, String query) { null, null, pragmas, - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), query, false, TABLES, System.nanoTime(), false, - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } @@ -503,12 +543,12 @@ public static Configuration configuration(String query) { return configuration(new QueryPragmas(Settings.EMPTY), query); } - public static EsqlQueryClusterSettings queryClusterSettings() { - return new EsqlQueryClusterSettings( - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + public static AnalyzerSettings queryClusterSettings() { + return new AnalyzerSettings( + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java index 4c6c69f979466..9682e8f8b2a2a 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java @@ -51,9 +51,9 @@ import org.elasticsearch.xcontent.json.JsonXContent; import org.elasticsearch.xpack.core.esql.action.ColumnInfo; import org.elasticsearch.xpack.esql.VerificationException; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.parser.ParsingException; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.junit.Before; @@ -1905,7 +1905,7 @@ private void createAndPopulateIndex(String indexName, Settings additionalSetting public void testDefaultTruncationSizeSetting() { ClusterAdminClient client = admin().cluster(); - Settings settings = Settings.builder().put(EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getKey(), 1).build(); + Settings settings = Settings.builder().put(AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getKey(), 1).build(); ClusterUpdateSettingsRequest settingsRequest = new ClusterUpdateSettingsRequest(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT) .persistentSettings(settings); @@ -1915,14 +1915,14 @@ public void testDefaultTruncationSizeSetting() { logger.info(results); assertEquals(1, getValuesList(results).size()); } finally { - clearPersistentSettings(EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE); + clearPersistentSettings(AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE); } } public void testMaxTruncationSizeSetting() { ClusterAdminClient client = admin().cluster(); - Settings settings = Settings.builder().put(EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getKey(), 10).build(); + Settings settings = Settings.builder().put(AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getKey(), 10).build(); ClusterUpdateSettingsRequest settingsRequest = new ClusterUpdateSettingsRequest(TEST_REQUEST_TIMEOUT, TEST_REQUEST_TIMEOUT) .persistentSettings(settings); @@ -1932,7 +1932,7 @@ public void testMaxTruncationSizeSetting() { logger.info(results); assertEquals(10, getValuesList(results).size()); } finally { - clearPersistentSettings(EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE); + clearPersistentSettings(AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE); } } 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 b941bce429579..da74cd2bd779c 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 @@ -7,10 +7,12 @@ package org.elasticsearch.xpack.esql.analysis; +import org.elasticsearch.TransportVersion; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.IndexResolution; import org.elasticsearch.xpack.esql.inference.InferenceResolution; import org.elasticsearch.xpack.esql.session.Configuration; +import org.elasticsearch.xpack.esql.session.EsqlSession; import java.util.Map; @@ -20,17 +22,41 @@ public record AnalyzerContext( IndexResolution indexResolution, Map lookupResolution, EnrichResolution enrichResolution, - InferenceResolution inferenceResolution + InferenceResolution inferenceResolution, + TransportVersion minimumVersion ) { - // Currently for tests only, since most do not test lookups - // TODO: make this even simpler, remove the enrichResolution for tests that do not require it (most tests) + public AnalyzerContext( Configuration configuration, EsqlFunctionRegistry functionRegistry, IndexResolution indexResolution, + Map lookupResolution, EnrichResolution enrichResolution, - InferenceResolution inferenceResolution + InferenceResolution inferenceResolution, + TransportVersion minimumVersion ) { - this(configuration, functionRegistry, indexResolution, Map.of(), enrichResolution, inferenceResolution); + this.configuration = configuration; + this.functionRegistry = functionRegistry; + this.indexResolution = indexResolution; + this.lookupResolution = lookupResolution; + this.enrichResolution = enrichResolution; + this.inferenceResolution = inferenceResolution; + this.minimumVersion = minimumVersion; + + assert minimumVersion != null : "AnalyzerContext must have a minimum transport version"; + assert minimumVersion.onOrBefore(TransportVersion.current()) + : "AnalyzerContext [" + minimumVersion + "] is not on or before current transport version [" + TransportVersion.current() + "]"; + } + + public AnalyzerContext(Configuration configuration, EsqlFunctionRegistry functionRegistry, EsqlSession.PreAnalysisResult result) { + this( + configuration, + functionRegistry, + result.indices(), + result.lookupIndices(), + result.enrichResolution(), + result.inferenceResolution(), + result.minimumTransportVersion() + ); } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerSettings.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerSettings.java new file mode 100644 index 0000000000000..5a9bc2f9f7347 --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/AnalyzerSettings.java @@ -0,0 +1,53 @@ +/* + * 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; + +import org.elasticsearch.common.settings.Setting; + +/** + * Values for cluster level settings used during query analysis. + */ +public record AnalyzerSettings( + int resultTruncationMaxSize, + int resultTruncationDefaultSize, + int timeseriesResultTruncationMaxSize, + int timeseriesResultTruncationDefaultSize +) { + public static final Setting QUERY_RESULT_TRUNCATION_MAX_SIZE = Setting.intSetting( + "esql.query.result_truncation_max_size", + 10000, + 1, + 1000000, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ); + public static final Setting QUERY_RESULT_TRUNCATION_DEFAULT_SIZE = Setting.intSetting( + "esql.query.result_truncation_default_size", + 1000, + 1, + 10000, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ); + public static final Setting QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE = Setting.intSetting( + "esql.query.timeseries_result_truncation_default_size", + 1_000_000, + 1, + 10_000_000, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ); + public static final Setting QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE = Setting.intSetting( + "esql.query.timeseries_result_truncation_max_size", + 10_000_000, + 1, + 1_000_000_000, + Setting.Property.NodeScope, + Setting.Property.Dynamic + ); +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java index 685979672af76..90a0f5a3a88fe 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/execution/PlanExecutor.java @@ -13,6 +13,7 @@ import org.elasticsearch.telemetry.metric.MeterRegistry; import org.elasticsearch.xpack.esql.action.EsqlExecutionInfo; import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.analysis.PreAnalyzer; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.common.Failures; @@ -20,7 +21,6 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.planner.mapper.Mapper; -import org.elasticsearch.xpack.esql.plugin.EsqlQueryClusterSettings; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog; import org.elasticsearch.xpack.esql.session.EsqlSession; @@ -67,7 +67,7 @@ public PlanExecutor( public void esql( EsqlQueryRequest request, String sessionId, - EsqlQueryClusterSettings esqlQueryClusterSettings, + AnalyzerSettings analyzerSettings, EnrichPolicyResolver enrichPolicyResolver, EsqlExecutionInfo executionInfo, IndicesExpressionGrouper indicesExpressionGrouper, @@ -78,7 +78,7 @@ public void esql( final PlanTelemetry planTelemetry = new PlanTelemetry(functionRegistry); final var session = new EsqlSession( sessionId, - esqlQueryClusterSettings, + analyzerSettings, indexResolver, enrichPolicyResolver, preAnalyzer, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalOptimizerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalOptimizerContext.java index 183008f900c5d..2daca48a4a8ee 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalOptimizerContext.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LocalLogicalOptimizerContext.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.optimizer; +import org.elasticsearch.TransportVersion; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.session.Configuration; import org.elasticsearch.xpack.esql.stats.SearchStats; @@ -17,7 +18,7 @@ public final class LocalLogicalOptimizerContext extends LogicalOptimizerContext private final SearchStats searchStats; public LocalLogicalOptimizerContext(Configuration configuration, FoldContext foldCtx, SearchStats searchStats) { - super(configuration, foldCtx); + super(configuration, foldCtx, null); this.searchStats = searchStats; } @@ -25,6 +26,17 @@ public SearchStats searchStats() { return searchStats; } + /** + * The minimum transport version is not sent to data nodes, so this is currently unsupported. + *

+ * This can be changed in the future if e.g. lookup joins need to become aware of the minimum transport version. + * (Lookup joins are remote, and for now we have to plan as if the remote node was on the oldest compatible version, which is limiting.) + */ + @Override + public TransportVersion minimumVersion() { + throw new UnsupportedOperationException("Minimum version is not sent to data nodes"); + } + @Override public boolean equals(Object obj) { if (super.equals(obj)) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalOptimizerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalOptimizerContext.java index da2d583674a90..93dded6be2191 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalOptimizerContext.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalOptimizerContext.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.optimizer; +import org.elasticsearch.TransportVersion; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.session.Configuration; @@ -15,10 +16,12 @@ public class LogicalOptimizerContext { private final Configuration configuration; private final FoldContext foldCtx; + private final TransportVersion minimumVersion; - public LogicalOptimizerContext(Configuration configuration, FoldContext foldCtx) { + public LogicalOptimizerContext(Configuration configuration, FoldContext foldCtx, TransportVersion minimumVersion) { this.configuration = configuration; this.foldCtx = foldCtx; + this.minimumVersion = minimumVersion; } public Configuration configuration() { @@ -29,22 +32,34 @@ public FoldContext foldCtx() { return foldCtx; } + public TransportVersion minimumVersion() { + return minimumVersion; + } + @Override public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; var that = (LogicalOptimizerContext) obj; - return this.configuration.equals(that.configuration) && this.foldCtx.equals(that.foldCtx); + return this.configuration.equals(that.configuration) + && this.foldCtx.equals(that.foldCtx) + && Objects.equals(this.minimumVersion, that.minimumVersion); } @Override public int hashCode() { - return Objects.hash(configuration, foldCtx); + return Objects.hash(configuration, foldCtx, minimumVersion); } @Override public String toString() { - return "LogicalOptimizerContext[configuration=" + configuration + ", foldCtx=" + foldCtx + ']'; + return "LogicalOptimizerContext[configuration=" + + configuration + + ", foldCtx=" + + foldCtx + + ", minimumVersion=" + + minimumVersion + + ']'; } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPreOptimizerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPreOptimizerContext.java index d08c639d0470b..ef67b3383be7b 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPreOptimizerContext.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/LogicalPreOptimizerContext.java @@ -7,12 +7,13 @@ package org.elasticsearch.xpack.esql.optimizer; +import org.elasticsearch.TransportVersion; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.inference.InferenceService; /** * Context passed to logical pre-optimizer rules. */ -public record LogicalPreOptimizerContext(FoldContext foldCtx, InferenceService inferenceService) { +public record LogicalPreOptimizerContext(FoldContext foldCtx, InferenceService inferenceService, TransportVersion minimumVersion) { } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalOptimizerContext.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalOptimizerContext.java index 7c94935844d6b..cc954721e61f3 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalOptimizerContext.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/optimizer/PhysicalOptimizerContext.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.optimizer; +import org.elasticsearch.TransportVersion; import org.elasticsearch.xpack.esql.session.Configuration; -public record PhysicalOptimizerContext(Configuration configuration) {} +public record PhysicalOptimizerContext(Configuration configuration, TransportVersion minimumVersion) {} 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 49192983b30e6..b32589b5282f9 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 @@ -41,6 +41,7 @@ import org.elasticsearch.xpack.esql.plan.physical.TopNExec; import org.elasticsearch.xpack.esql.plan.physical.UnaryExec; import org.elasticsearch.xpack.esql.plan.physical.inference.RerankExec; +import org.elasticsearch.xpack.esql.session.Versioned; import java.util.List; @@ -54,8 +55,13 @@ */ public class Mapper { - public PhysicalPlan map(LogicalPlan p) { + public PhysicalPlan map(Versioned versionedPlan) { + // We ignore the version for now, but it's fine to use later for plans that work + // differently from some version and up. + return mapInner(versionedPlan.inner()); + } + private PhysicalPlan mapInner(LogicalPlan p) { if (p instanceof LeafPlan leaf) { return mapLeaf(leaf); } @@ -84,7 +90,7 @@ private PhysicalPlan mapLeaf(LeafPlan leaf) { } private PhysicalPlan mapUnary(UnaryPlan unary) { - PhysicalPlan mappedChild = map(unary.child()); + PhysicalPlan mappedChild = mapInner(unary.child()); // // TODO - this is hard to follow, causes bugs and needs reworking @@ -217,14 +223,14 @@ private PhysicalPlan mapBinary(BinaryPlan bp) { return new FragmentExec(bp); } - PhysicalPlan left = map(bp.left()); + PhysicalPlan left = mapInner(bp.left()); // only broadcast joins supported for now - hence push down as a streaming operator if (left instanceof FragmentExec) { return new FragmentExec(bp); } - PhysicalPlan right = map(bp.right()); + PhysicalPlan right = mapInner(bp.right()); // if the right is data we can use a hash join directly if (right instanceof LocalSourceExec localData) { return new HashJoinExec( @@ -267,7 +273,7 @@ private static boolean isIndexModeLookup(FragmentExec fragment) { } private PhysicalPlan mapFork(Fork fork) { - return new MergeExec(fork.source(), fork.children().stream().map(child -> map(child)).toList(), fork.output()); + return new MergeExec(fork.source(), fork.children().stream().map(this::mapInner).toList(), fork.output()); } private PhysicalPlan addExchangeForFragment(LogicalPlan logical, PhysicalPlan child) { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/premapper/PreMapper.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/premapper/PreMapper.java index 87008204491a6..fd0ce9d5eaafe 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/premapper/PreMapper.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/planner/premapper/PreMapper.java @@ -13,6 +13,7 @@ import org.elasticsearch.xpack.esql.expression.function.fulltext.QueryBuilderResolver; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; +import org.elasticsearch.xpack.esql.session.Versioned; import java.util.concurrent.Executor; @@ -33,8 +34,8 @@ public PreMapper(TransportActionServices services) { /** * Invokes any premapping steps that need to be applied to the logical plan, before this is being mapped to a physical one. */ - public void preMapper(LogicalPlan plan, ActionListener listener) { - queryRewrite(plan, listener.delegateFailureAndWrap((l, p) -> { + public void preMapper(Versioned plan, ActionListener listener) { + queryRewrite(plan.inner(), listener.delegateFailureAndWrap((l, p) -> { p.setOptimized(); l.onResponse(p); })); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java index d833772529752..7f98ba0e1ed98 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlPlugin.java @@ -67,6 +67,7 @@ import org.elasticsearch.xpack.esql.action.RestEsqlListQueriesAction; import org.elasticsearch.xpack.esql.action.RestEsqlQueryAction; import org.elasticsearch.xpack.esql.action.RestEsqlStopAsyncAction; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.analysis.PlanCheckerProvider; import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.enrich.EnrichLookupOperator; @@ -95,42 +96,6 @@ public class EsqlPlugin extends Plugin implements ActionPlugin, ExtensiblePlugin public static final String ESQL_WORKER_THREAD_POOL_NAME = "esql_worker"; - public static final Setting QUERY_RESULT_TRUNCATION_MAX_SIZE = Setting.intSetting( - "esql.query.result_truncation_max_size", - 10000, - 1, - 1000000, - Setting.Property.NodeScope, - Setting.Property.Dynamic - ); - - public static final Setting QUERY_RESULT_TRUNCATION_DEFAULT_SIZE = Setting.intSetting( - "esql.query.result_truncation_default_size", - 1000, - 1, - 10000, - Setting.Property.NodeScope, - Setting.Property.Dynamic - ); - - public static final Setting QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE = Setting.intSetting( - "esql.query.timeseries_result_truncation_default_size", - 1_000_000, - 1, - 10_000_000, - Setting.Property.NodeScope, - Setting.Property.Dynamic - ); - - public static final Setting QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE = Setting.intSetting( - "esql.query.timeseries_result_truncation_max_size", - 10_000_000, - 1, - 1_000_000_000, - Setting.Property.NodeScope, - Setting.Property.Dynamic - ); - public static final Setting QUERY_ALLOW_PARTIAL_RESULTS = Setting.boolSetting( "esql.query.allow_partial_results", true, @@ -266,10 +231,10 @@ protected XPackLicenseState getLicenseState() { @Override public List> getSettings() { return List.of( - QUERY_RESULT_TRUNCATION_DEFAULT_SIZE, - QUERY_RESULT_TRUNCATION_MAX_SIZE, - QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE, - QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE, + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE, + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE, + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE, + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE, QUERY_ALLOW_PARTIAL_RESULTS, ESQL_QUERYLOG_THRESHOLD_TRACE_SETTING, ESQL_QUERYLOG_THRESHOLD_DEBUG_SETTING, diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryClusterSettings.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryClusterSettings.java deleted file mode 100644 index 1c0a300ca620d..0000000000000 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/EsqlQueryClusterSettings.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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.plugin; - -public record EsqlQueryClusterSettings( - int resultTruncationMaxSize, - int resultTruncationDefaultSize, - int timeseriesResultTruncationMaxSize, - int timeseriesResultTruncationDefaultSize -) {} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java index 03b86ac1b1ccd..f8d24720231e5 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/TransportEsqlQueryAction.java @@ -43,6 +43,7 @@ import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; import org.elasticsearch.xpack.esql.action.EsqlQueryResponse; import org.elasticsearch.xpack.esql.action.EsqlQueryTask; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.async.AsyncTaskManagementService; import org.elasticsearch.xpack.esql.enrich.AbstractLookupService; import org.elasticsearch.xpack.esql.enrich.EnrichLookupService; @@ -184,24 +185,25 @@ public TransportEsqlQueryAction( defaultAllowPartialResults = EsqlPlugin.QUERY_ALLOW_PARTIAL_RESULTS.get(clusterService.getSettings()); clusterService.getClusterSettings() .addSettingsUpdateConsumer(EsqlPlugin.QUERY_ALLOW_PARTIAL_RESULTS, v -> defaultAllowPartialResults = v); - resultTruncationMaxSize = EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.get(clusterService.getSettings()); - resultTruncationDefaultSize = EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.get(clusterService.getSettings()); - timeseriesResultTruncationMaxSize = EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.get(clusterService.getSettings()); - timeseriesResultTruncationDefaultSize = EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get( + resultTruncationMaxSize = AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.get(clusterService.getSettings()); + resultTruncationDefaultSize = AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.get(clusterService.getSettings()); + timeseriesResultTruncationMaxSize = AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.get(clusterService.getSettings()); + timeseriesResultTruncationDefaultSize = AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get( clusterService.getSettings() ); - clusterService.getClusterSettings().addSettingsUpdateConsumer(EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE, v -> { + clusterService.getClusterSettings().addSettingsUpdateConsumer(AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE, v -> { resultTruncationMaxSize = v; }); - clusterService.getClusterSettings().addSettingsUpdateConsumer(EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE, v -> { + clusterService.getClusterSettings().addSettingsUpdateConsumer(AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE, v -> { resultTruncationDefaultSize = v; }); - clusterService.getClusterSettings().addSettingsUpdateConsumer(EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE, v -> { + clusterService.getClusterSettings().addSettingsUpdateConsumer(AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE, v -> { timeseriesResultTruncationMaxSize = v; }); - clusterService.getClusterSettings().addSettingsUpdateConsumer(EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE, v -> { - timeseriesResultTruncationDefaultSize = v; - }); + clusterService.getClusterSettings() + .addSettingsUpdateConsumer(AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE, v -> { + timeseriesResultTruncationDefaultSize = v; + }); } @Override @@ -253,7 +255,7 @@ private void innerExecute(Task task, EsqlQueryRequest request, ActionListener determineUnavailableRemoteClusters( * Any Exception sent to onFailure stops processing, but not all are fatal (return a 4xx or 5xx), so * the onFailure handler determines whether to return an empty successful result or a 4xx/5xx error. */ - abstract static class CssPartialErrorsActionListener implements ActionListener { + abstract static class CssPartialErrorsActionListener implements ActionListener> { private final EsqlExecutionInfo executionInfo; private final ActionListener listener; 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 b26d33c67af55..c0d0f35150c27 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 @@ -41,6 +41,7 @@ import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; import org.elasticsearch.xpack.esql.analysis.Analyzer; import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.analysis.PreAnalyzer; import org.elasticsearch.xpack.esql.analysis.Verifier; @@ -80,7 +81,6 @@ import org.elasticsearch.xpack.esql.planner.PlannerUtils; import org.elasticsearch.xpack.esql.planner.mapper.Mapper; import org.elasticsearch.xpack.esql.planner.premapper.PreMapper; -import org.elasticsearch.xpack.esql.plugin.EsqlQueryClusterSettings; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; import org.elasticsearch.xpack.esql.telemetry.PlanTelemetry; @@ -118,7 +118,7 @@ public interface PlanRunner { private static final TransportVersion LOOKUP_JOIN_CCS = TransportVersion.fromName("lookup_join_ccs"); private final String sessionId; - private final EsqlQueryClusterSettings clusterSettings; + private final AnalyzerSettings clusterSettings; private final IndexResolver indexResolver; private final EnrichPolicyResolver enrichPolicyResolver; @@ -142,7 +142,7 @@ public interface PlanRunner { public EsqlSession( String sessionId, - EsqlQueryClusterSettings clusterSettings, + AnalyzerSettings clusterSettings, IndexResolver indexResolver, EnrichPolicyResolver enrichPolicyResolver, PreAnalyzer preAnalyzer, @@ -201,9 +201,6 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P clusterSettings.timeseriesResultTruncationDefaultSize() ); FoldContext foldContext = configuration.newFoldContext(); - var logicalPlanPreOptimizer = new LogicalPlanPreOptimizer(new LogicalPreOptimizerContext(foldContext, inferenceService)); - var logicalPlanOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(configuration, foldContext)); - var physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(configuration)); LogicalPlan plan = statement.plan(); if (plan instanceof Explain explain) { @@ -218,14 +215,27 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P request.filter(), new EsqlCCSUtils.CssPartialErrorsActionListener(executionInfo, listener) { @Override - public void onResponse(LogicalPlan analyzedPlan) { + public void onResponse(Versioned analyzedPlan) { assert ThreadPool.assertCurrentThreadPool( ThreadPool.Names.SEARCH, ThreadPool.Names.SEARCH_COORDINATION, ThreadPool.Names.SYSTEM_READ ); - SubscribableListener.newForked(l -> preOptimizedPlan(analyzedPlan, logicalPlanPreOptimizer, l)) - .andThen((l, p) -> preMapper.preMapper(optimizedPlan(p, logicalPlanOptimizer), l)) + + LogicalPlan plan = analyzedPlan.inner(); + TransportVersion minimumVersion = analyzedPlan.minimumVersion(); + + var logicalPlanPreOptimizer = new LogicalPlanPreOptimizer( + new LogicalPreOptimizerContext(foldContext, inferenceService, minimumVersion) + ); + var logicalPlanOptimizer = new LogicalPlanOptimizer( + new LogicalOptimizerContext(configuration, foldContext, minimumVersion) + ); + + SubscribableListener.newForked(l -> preOptimizedPlan(plan, logicalPlanPreOptimizer, l)) + .andThen( + (l, p) -> preMapper.preMapper(new Versioned<>(optimizedPlan(p, logicalPlanOptimizer), minimumVersion), l) + ) .andThen( (l, p) -> executeOptimizedPlan( request, @@ -234,7 +244,7 @@ public void onResponse(LogicalPlan analyzedPlan) { p, configuration, foldContext, - physicalPlanOptimizer, + minimumVersion, l ) ) @@ -255,7 +265,7 @@ public void executeOptimizedPlan( LogicalPlan optimizedPlan, Configuration configuration, FoldContext foldContext, - PhysicalPlanOptimizer physicalPlanOptimizer, + TransportVersion minimumVersion, ActionListener listener ) { assert ThreadPool.assertCurrentThreadPool( @@ -263,6 +273,8 @@ public void executeOptimizedPlan( ThreadPool.Names.SEARCH_COORDINATION, ThreadPool.Names.SYSTEM_READ ); + var physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(configuration, minimumVersion)); + if (explainMode) {// TODO: INLINE STATS come back to the explain mode branch and reevaluate PhysicalPlan physicalPlan = logicalPlanToPhysicalPlan(optimizedPlan, request, physicalPlanOptimizer); String physicalPlanString = physicalPlan.toString(); @@ -489,22 +501,27 @@ public void analyzedPlan( Configuration configuration, EsqlExecutionInfo executionInfo, QueryBuilder requestFilter, - ActionListener logicalPlanListener + ActionListener> logicalPlanListener ) { assert ThreadPool.assertCurrentThreadPool(ThreadPool.Names.SEARCH); - if (parsed.analyzed()) { - logicalPlanListener.onResponse(parsed); - return; - } - var preAnalysis = preAnalyzer.preAnalyze(parsed); - var result = FieldNameUtils.resolveFieldNames(parsed, preAnalysis.enriches().isEmpty() == false); - var description = requestFilter == null ? "the only attempt without filter" : "first attempt with filter"; + PreAnalyzer.PreAnalysis preAnalysis = preAnalyzer.preAnalyze(parsed); + PreAnalysisResult result = FieldNameUtils.resolveFieldNames(parsed, preAnalysis.enriches().isEmpty() == false); + String description = requestFilter == null ? "the only attempt without filter" : "first attempt with filter"; - resolveIndices(parsed, configuration, executionInfo, description, requestFilter, preAnalysis, result, logicalPlanListener); + resolveIndicesAndAnalyze( + parsed, + configuration, + executionInfo, + description, + requestFilter, + preAnalysis, + result, + logicalPlanListener + ); } - private void resolveIndices( + private void resolveIndicesAndAnalyze( LogicalPlan parsed, Configuration configuration, EsqlExecutionInfo executionInfo, @@ -512,20 +529,23 @@ private void resolveIndices( QueryBuilder requestFilter, PreAnalyzer.PreAnalysis preAnalysis, PreAnalysisResult result, - ActionListener logicalPlanListener + ActionListener> logicalPlanListener ) { EsqlCCSUtils.initCrossClusterState(indicesExpressionGrouper, verifier.licenseState(), preAnalysis.indexPattern(), executionInfo); - SubscribableListener.newForked(l -> preAnalyzeMainIndices(preAnalysis, executionInfo, result, requestFilter, l)) - .andThenApply(r -> { - if (r.indices.isValid() - && executionInfo.isCrossClusterSearch() - && executionInfo.getRunningClusterAliases().findAny().isEmpty()) { - LOGGER.debug("No more clusters to search, ending analysis stage"); - throw new NoClustersToSearchException(); - } - return r; - }) + // The main index pattern dictates on which nodes the query can be executed, so we use the minimum transport version from this field + // caps request. + SubscribableListener.newForked( + l -> preAnalyzeMainIndicesAndRetrieveMinTransportVersion(preAnalysis, executionInfo, result, requestFilter, l) + ).andThenApply(r -> { + if (r.indices.isValid() + && executionInfo.isCrossClusterSearch() + && executionInfo.getRunningClusterAliases().findAny().isEmpty()) { + LOGGER.debug("No more clusters to search, ending analysis stage"); + throw new NoClustersToSearchException(); + } + return r; + }) .andThen((l, r) -> preAnalyzeLookupIndices(preAnalysis.lookupIndices().iterator(), r, executionInfo, l)) .andThen((l, r) -> { enrichPolicyResolver.resolvePolicies(preAnalysis.enriches(), executionInfo, l.map(r::withEnrichResolution)); @@ -533,7 +553,7 @@ private void resolveIndices( .andThen((l, r) -> { inferenceService.inferenceResolver(functionRegistry).resolveInferenceIds(parsed, l.map(r::withInferenceResolution)); }) - .andThen( + .>andThen( (l, r) -> analyzeWithRetry(parsed, configuration, executionInfo, description, requestFilter, preAnalysis, r, l) ) .addListener(logicalPlanListener); @@ -758,7 +778,7 @@ private void validateRemoteVersions(EsqlExecutionInfo executionInfo) { }); } - private void preAnalyzeMainIndices( + private void preAnalyzeMainIndicesAndRetrieveMinTransportVersion( PreAnalyzer.PreAnalysis preAnalysis, EsqlExecutionInfo executionInfo, PreAnalysisResult result, @@ -777,7 +797,7 @@ private void preAnalyzeMainIndices( result.withIndices(IndexResolution.valid(new EsIndex(preAnalysis.indexPattern().indexPattern(), Map.of(), Map.of()))) ); } else { - indexResolver.resolveAsMergedMapping( + indexResolver.resolveAsMergedMappingAndRetrieveMinimumVersion( preAnalysis.indexPattern().indexPattern(), result.fieldNames, // Maybe if no indices are returned, retry without index mode and provide a clearer error message. @@ -794,14 +814,18 @@ private void preAnalyzeMainIndices( preAnalysis.supportsAggregateMetricDouble(), preAnalysis.supportsDenseVector(), listener.delegateFailureAndWrap((l, indexResolution) -> { - EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, indexResolution.failures()); - l.onResponse(result.withIndices(indexResolution)); + EsqlCCSUtils.updateExecutionInfoWithUnavailableClusters(executionInfo, indexResolution.inner().failures()); + l.onResponse( + result.withIndices(indexResolution.inner()).withMinimumTransportVersion(indexResolution.minimumVersion()) + ); }) ); } } else { // occurs when dealing with local relations (row a = 1) - listener.onResponse(result.withIndices(IndexResolution.invalid("[none specified]"))); + listener.onResponse( + result.withIndices(IndexResolution.invalid("[none specified]")).withMinimumTransportVersion(TransportVersion.current()) + ); } } @@ -813,7 +837,7 @@ private void analyzeWithRetry( QueryBuilder requestFilter, PreAnalyzer.PreAnalysis preAnalysis, PreAnalysisResult result, - ActionListener listener + ActionListener> listener ) { LOGGER.debug("Analyzing the plan ({})", description); try { @@ -825,7 +849,7 @@ private void analyzeWithRetry( LogicalPlan plan = analyzedPlan(parsed, configuration, result, executionInfo); 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 - listener.onResponse(plan); + listener.onResponse(new Versioned<>(plan, result.minimumTransportVersion())); } catch (VerificationException ve) { LOGGER.debug("Analyzing the plan ({}) failed with {}", description, ve.getDetailedMessage()); if (requestFilter == null) { @@ -834,7 +858,16 @@ private void analyzeWithRetry( } else { // retrying the index resolution without index filtering. executionInfo.clusterInfo.clear(); - resolveIndices(parsed, configuration, executionInfo, "second attempt, without filter", null, preAnalysis, result, listener); + resolveIndicesAndAnalyze( + parsed, + configuration, + executionInfo, + "second attempt, without filter", + null, + preAnalysis, + result, + listener + ); } } catch (Exception e) { listener.onFailure(e); @@ -866,10 +899,7 @@ private PhysicalPlan logicalPlanToPhysicalPlan( private LogicalPlan analyzedPlan(LogicalPlan parsed, Configuration configuration, PreAnalysisResult r, EsqlExecutionInfo executionInfo) throws Exception { handleFieldCapsFailures(configuration.allowPartialResults(), executionInfo, r.indices.failures()); - Analyzer analyzer = new Analyzer( - new AnalyzerContext(configuration, functionRegistry, r.indices, r.lookupIndices, r.enrichResolution, r.inferenceResolution), - verifier - ); + Analyzer analyzer = new Analyzer(new AnalyzerContext(configuration, functionRegistry, r), verifier); LogicalPlan plan = analyzer.analyze(parsed); plan.setAnalyzed(); return plan; @@ -892,8 +922,8 @@ public void preOptimizedPlan( logicalPlanPreOptimizer.preOptimize(logicalPlan, listener); } - private PhysicalPlan physicalPlan(LogicalPlan optimizedPlan) { - if (optimizedPlan.optimized() == false) { + private PhysicalPlan physicalPlan(Versioned optimizedPlan) { + if (optimizedPlan.inner().optimized() == false) { throw new IllegalStateException("Expected optimized plan"); } optimizedLogicalPlanString = optimizedPlan.toString(); @@ -903,7 +933,9 @@ private PhysicalPlan physicalPlan(LogicalPlan optimizedPlan) { } private PhysicalPlan optimizedPhysicalPlan(LogicalPlan optimizedPlan, PhysicalPlanOptimizer physicalPlanOptimizer) { - var plan = physicalPlanOptimizer.optimize(physicalPlan(optimizedPlan)); + var plan = physicalPlanOptimizer.optimize( + physicalPlan(new Versioned<>(optimizedPlan, physicalPlanOptimizer.context().minimumVersion())) + ); LOGGER.debug("Optimized physical plan:\n{}", plan); return plan; } @@ -914,15 +946,24 @@ public record PreAnalysisResult( IndexResolution indices, Map lookupIndices, EnrichResolution enrichResolution, - InferenceResolution inferenceResolution + InferenceResolution inferenceResolution, + TransportVersion minimumTransportVersion ) { public PreAnalysisResult(Set fieldNames, Set wildcardJoinIndices) { - this(fieldNames, wildcardJoinIndices, null, new HashMap<>(), null, InferenceResolution.EMPTY); + this(fieldNames, wildcardJoinIndices, null, new HashMap<>(), null, InferenceResolution.EMPTY, null); } PreAnalysisResult withIndices(IndexResolution indices) { - return new PreAnalysisResult(fieldNames, wildcardJoinIndices, indices, lookupIndices, enrichResolution, inferenceResolution); + return new PreAnalysisResult( + fieldNames, + wildcardJoinIndices, + indices, + lookupIndices, + enrichResolution, + inferenceResolution, + minimumTransportVersion + ); } PreAnalysisResult addLookupIndexResolution(String index, IndexResolution indexResolution) { @@ -931,11 +972,39 @@ PreAnalysisResult addLookupIndexResolution(String index, IndexResolution indexRe } PreAnalysisResult withEnrichResolution(EnrichResolution enrichResolution) { - return new PreAnalysisResult(fieldNames, wildcardJoinIndices, indices, lookupIndices, enrichResolution, inferenceResolution); + return new PreAnalysisResult( + fieldNames, + wildcardJoinIndices, + indices, + lookupIndices, + enrichResolution, + inferenceResolution, + minimumTransportVersion + ); } PreAnalysisResult withInferenceResolution(InferenceResolution inferenceResolution) { - return new PreAnalysisResult(fieldNames, wildcardJoinIndices, indices, lookupIndices, enrichResolution, inferenceResolution); + return new PreAnalysisResult( + fieldNames, + wildcardJoinIndices, + indices, + lookupIndices, + enrichResolution, + inferenceResolution, + minimumTransportVersion + ); + } + + PreAnalysisResult withMinimumTransportVersion(TransportVersion minimumTransportVersion) { + return new PreAnalysisResult( + fieldNames, + wildcardJoinIndices, + indices, + lookupIndices, + enrichResolution, + inferenceResolution, + minimumTransportVersion + ); } } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java index 20936e6fa86f4..49d9476126d4d 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/IndexResolver.java @@ -6,6 +6,7 @@ */ package org.elasticsearch.xpack.esql.session; +import org.elasticsearch.TransportVersion; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesIndexResponse; import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; @@ -91,14 +92,49 @@ public void resolveAsMergedMapping( boolean supportsAggregateMetricDouble, boolean supportsDenseVector, ActionListener listener + ) { + ActionListener> ignoreVersion = listener.delegateFailureAndWrap( + (l, versionedResolution) -> l.onResponse(versionedResolution.inner()) + ); + + resolveAsMergedMappingAndRetrieveMinimumVersion( + indexWildcard, + fieldNames, + requestFilter, + includeAllDimensions, + supportsAggregateMetricDouble, + supportsDenseVector, + ignoreVersion + ); + } + + /** + * Resolves a pattern to one (potentially compound meaning that spawns multiple indices) mapping. Also retrieves the minimum transport + * version available in the cluster (and remotes). + */ + public void resolveAsMergedMappingAndRetrieveMinimumVersion( + String indexWildcard, + Set fieldNames, + QueryBuilder requestFilter, + boolean includeAllDimensions, + boolean supportsAggregateMetricDouble, + boolean supportsDenseVector, + ActionListener> listener ) { client.execute( EsqlResolveFieldsAction.TYPE, createFieldCapsRequest(indexWildcard, fieldNames, requestFilter, includeAllDimensions), listener.delegateFailureAndWrap((l, response) -> { - LOGGER.debug("minimum transport version {}", response.minTransportVersion()); + TransportVersion minimumVersion = response.minTransportVersion(); + + LOGGER.debug("minimum transport version {}", minimumVersion); l.onResponse( - mergedMappings(indexWildcard, new FieldsInfo(response.caps(), supportsAggregateMetricDouble, supportsDenseVector)) + new Versioned<>( + mergedMappings(indexWildcard, new FieldsInfo(response.caps(), supportsAggregateMetricDouble, supportsDenseVector)), + // The minimum transport version was added to the field caps response in 9.2.1; in clusters with older nodes, + // we don't have that information and need to assume the oldest supported version. + minimumVersion == null ? TransportVersion.minimumCompatible() : minimumVersion + ) ); }) ); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/Versioned.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/Versioned.java new file mode 100644 index 0000000000000..8d1121993f21b --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/session/Versioned.java @@ -0,0 +1,17 @@ +/* + * 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.session; + +import org.elasticsearch.TransportVersion; + +/** + * A wrapper for objects to pass along the minimum transport version available in the cluster (and remotes), + * esp. when dealing with listeners. Where this object gets consumed, we generally need to assume that all nodes in the cluster + * (and remotes) are at the minimum transport version, so that we don't use features not supported everywhere. + */ +public record Versioned(T inner, TransportVersion minimumVersion) {} diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java index 33226f799abdd..f90547f57c0ff 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/CsvTests.java @@ -10,6 +10,7 @@ import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; import org.elasticsearch.Build; +import org.elasticsearch.TransportVersion; import org.elasticsearch.Version; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.PlainActionFuture; @@ -76,8 +77,6 @@ import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanPreOptimizer; import org.elasticsearch.xpack.esql.optimizer.LogicalPreOptimizerContext; -import org.elasticsearch.xpack.esql.optimizer.PhysicalOptimizerContext; -import org.elasticsearch.xpack.esql.optimizer.PhysicalPlanOptimizer; import org.elasticsearch.xpack.esql.optimizer.TestLocalPhysicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.plan.logical.Enrich; @@ -525,11 +524,23 @@ private static EnrichPolicy loadEnrichPolicyMapping(String policyFileName) { } } - private LogicalPlan analyzedPlan(LogicalPlan parsed, CsvTestsDataLoader.MultiIndexTestDataset datasets) { + private LogicalPlan analyzedPlan( + LogicalPlan parsed, + CsvTestsDataLoader.MultiIndexTestDataset datasets, + TransportVersion minimumVersion + ) { var indexResolution = loadIndexResolution(datasets); var enrichPolicies = loadEnrichPolicies(); var analyzer = new Analyzer( - new AnalyzerContext(configuration, functionRegistry, indexResolution, enrichPolicies, emptyInferenceResolution()), + new AnalyzerContext( + configuration, + functionRegistry, + indexResolution, + Map.of(), + enrichPolicies, + emptyInferenceResolution(), + minimumVersion + ), TEST_VERIFIER ); LogicalPlan plan = analyzer.analyze(parsed); @@ -585,7 +596,9 @@ private static TestPhysicalOperationProviders testOperationProviders( private ActualResults executePlan(BigArrays bigArrays) throws Exception { LogicalPlan parsed = parser.createStatement(testCase.query); var testDatasets = testDatasets(parsed); - LogicalPlan analyzed = analyzedPlan(parsed, testDatasets); + // Specifically use the newest transport version; the csv tests correspond to a single node cluster on the current version. + TransportVersion minimumVersion = TransportVersion.current(); + LogicalPlan analyzed = analyzedPlan(parsed, testDatasets, minimumVersion); FoldContext foldCtx = FoldContext.small(); EsqlSession session = new EsqlSession( @@ -604,9 +617,10 @@ private ActualResults executePlan(BigArrays bigArrays) throws Exception { TestPhysicalOperationProviders physicalOperationProviders = testOperationProviders(foldCtx, testDatasets); PlainActionFuture listener = new PlainActionFuture<>(); - var logicalPlanPreOptimizer = new LogicalPlanPreOptimizer(new LogicalPreOptimizerContext(foldCtx, mock(InferenceService.class))); - var logicalPlanOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(configuration, foldCtx)); - var physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(configuration)); + var logicalPlanPreOptimizer = new LogicalPlanPreOptimizer( + new LogicalPreOptimizerContext(foldCtx, mock(InferenceService.class), minimumVersion) + ); + var logicalPlanOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(configuration, foldCtx, minimumVersion)); session.preOptimizedPlan(analyzed, logicalPlanPreOptimizer, listener.delegateFailureAndWrap((l, preOptimized) -> { session.executeOptimizedPlan( new EsqlQueryRequest(), @@ -615,7 +629,7 @@ private ActualResults executePlan(BigArrays bigArrays) throws Exception { session.optimizedPlan(preOptimized, logicalPlanOptimizer), configuration, foldCtx, - physicalPlanOptimizer, + minimumVersion, listener.delegateFailureAndWrap( // Wrap so we can capture the warnings in the calling thread (next, result) -> next.onResponse( 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 5dcf4ead1d9ba..a501ee2100bbc 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 @@ -41,6 +41,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_CFG; import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; import static org.elasticsearch.xpack.esql.EsqlTestUtils.configuration; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; public final class AnalyzerTestUtils { @@ -87,7 +88,7 @@ public static Analyzer analyzer( Configuration config ) { return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( config, new EsqlFunctionRegistry(), indexResolution, @@ -105,7 +106,7 @@ public static Analyzer analyzer(IndexResolution indexResolution, Verifier verifi public static Analyzer analyzer(Verifier verifier) { return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), analyzerDefaultMapping(), 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 1a096df1b0648..1423699ab7b45 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 @@ -95,7 +95,6 @@ import org.elasticsearch.xpack.esql.plan.logical.inference.Completion; import org.elasticsearch.xpack.esql.plan.logical.inference.Rerank; import org.elasticsearch.xpack.esql.plan.logical.local.EsqlProject; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.session.IndexResolver; import java.io.IOException; @@ -122,6 +121,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.paramAsIdentifier; import static org.elasticsearch.xpack.esql.EsqlTestUtils.paramAsPattern; import static org.elasticsearch.xpack.esql.EsqlTestUtils.referenceAttribute; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.Analyzer.NO_FIELDS; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.TEXT_EMBEDDING_INFERENCE_ID; @@ -171,9 +171,9 @@ public class AnalyzerTests extends ESTestCase { "FROM" ); - private static final int MAX_LIMIT = EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY); - private static final int DEFAULT_LIMIT = EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY); - private static final int DEFAULT_TIMESERIES_LIMIT = EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault( + private static final int MAX_LIMIT = AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY); + private static final int DEFAULT_LIMIT = AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY); + private static final int DEFAULT_TIMESERIES_LIMIT = AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault( Settings.EMPTY ); @@ -1677,7 +1677,7 @@ public void testEnrichPolicyWithError() { enrichResolution.addError("languages", Enrich.Mode.ANY, "error-2"); enrichResolution.addError("foo", Enrich.Mode.ANY, "foo-error-101"); - AnalyzerContext context = new AnalyzerContext( + AnalyzerContext context = testAnalyzerContext( configuration("from test"), new EsqlFunctionRegistry(), testIndex, @@ -1833,7 +1833,7 @@ public void testEnrichFieldsIncludeMatchField() { languageIndex.get().mapping() ) ); - AnalyzerContext context = new AnalyzerContext( + AnalyzerContext context = testAnalyzerContext( configuration(query), new EsqlFunctionRegistry(), testIndex, @@ -2224,7 +2224,7 @@ public void testLookupJoinUnknownIndex() { IndexResolution missingLookupIndex = IndexResolution.invalid(errorMessage); Analyzer analyzerMissingLookupIndex = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), analyzerDefaultMapping(), @@ -4623,7 +4623,7 @@ public void testImplicitCastingForAggregateMetricDouble() { ); var indexResolution = IndexResolution.valid(esIndex); var analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), indexResolution, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java index 899d3d0647efa..606db6fb8fb2c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/analysis/ParsingTests.java @@ -44,6 +44,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; @@ -55,7 +56,7 @@ public class ParsingTests extends ESTestCase { private final IndexResolution defaultIndex = loadIndexResolution("mapping-basic.json"); private final Analyzer defaultAnalyzer = new Analyzer( - new AnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), defaultIndex, emptyPolicyResolution(), emptyInferenceResolution()), + testAnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), defaultIndex, emptyPolicyResolution(), emptyInferenceResolution()), TEST_VERIFIER ); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java index 68a6f38cdd69a..733c23af12966 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/CheckLicenseTests.java @@ -18,7 +18,6 @@ import org.elasticsearch.xpack.esql.LicenseAware; import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; @@ -35,6 +34,7 @@ import java.util.Objects; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyzerDefaultMapping; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultEnrichResolution; import static org.hamcrest.Matchers.containsString; @@ -91,7 +91,7 @@ public EsqlFunctionRegistry snapshotRegistry() { private static Analyzer analyzer(EsqlFunctionRegistry registry, License.OperationMode operationMode) { return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, registry, analyzerDefaultMapping(), diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java index 9daf0c9b30cfa..56e2197ce52ec 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/AbstractConfigurationFunctionTestCase.java @@ -8,11 +8,11 @@ package org.elasticsearch.xpack.esql.expression.function.scalar; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.core.util.StringUtils; import org.elasticsearch.xpack.esql.expression.function.AbstractScalarFunctionTestCase; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; @@ -49,15 +49,15 @@ private static Configuration randomConfiguration() { randomBoolean() ? null : randomAlphaOfLength(randomInt(64)), randomBoolean() ? null : randomAlphaOfLength(randomInt(64)), QueryPragmas.EMPTY, - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), StringUtils.EMPTY, randomBoolean(), Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java index 4f002f0a878c5..a385fc36443f9 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/DayNameTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.time.DateUtils; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; @@ -21,7 +22,6 @@ import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; import org.hamcrest.Matchers; @@ -133,15 +133,15 @@ private Configuration configWithZoneAndLocale(ZoneId zone, Locale locale) { null, null, new QueryPragmas(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), "", false, Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/MonthNameTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/MonthNameTests.java index 62bf6dd5448eb..88fa109b36800 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/MonthNameTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/date/MonthNameTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.time.DateUtils; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; @@ -21,7 +22,6 @@ import org.elasticsearch.xpack.esql.core.type.DataType; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; import org.hamcrest.Matchers; @@ -135,15 +135,15 @@ private Configuration configWithZoneAndLocale(ZoneId zone, Locale locale) { null, null, new QueryPragmas(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), "", false, Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java index 8838ea3c12709..9701e03d5a1f5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToLowerTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.Page; import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; @@ -24,7 +25,6 @@ import org.elasticsearch.xpack.esql.core.util.DateUtils; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; @@ -65,15 +65,15 @@ private Configuration randomLocaleConfig() { null, null, new QueryPragmas(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), "", false, Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java index e48c9b351aa55..d301f72b45539 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/string/ToUpperTests.java @@ -16,6 +16,7 @@ import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.Page; import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Expression; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.expression.Literal; @@ -24,7 +25,6 @@ import org.elasticsearch.xpack.esql.core.util.DateUtils; import org.elasticsearch.xpack.esql.expression.function.TestCaseSupplier; import org.elasticsearch.xpack.esql.expression.function.scalar.AbstractConfigurationFunctionTestCase; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; @@ -65,15 +65,15 @@ private Configuration randomLocaleConfig() { null, null, new QueryPragmas(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), "", false, Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java index 7b111e50890c5..0f1939aabd8cb 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLocalPhysicalPlanOptimizerTests.java @@ -19,7 +19,6 @@ import org.elasticsearch.xpack.core.enrich.EnrichPolicy; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.core.tree.Source; @@ -43,6 +42,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.configuration; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; @@ -96,7 +96,7 @@ public void init() { var timeSeriesMapping = loadMapping("k8s-mappings.json"); var timeSeriesIndex = IndexResolution.valid(new EsIndex("k8s", timeSeriesMapping, Map.of("k8s", IndexMode.TIME_SERIES))); timeSeriesAnalyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), timeSeriesIndex, @@ -114,7 +114,7 @@ private Analyzer makeAnalyzer(String mappingFileName, EnrichResolution enrichRes IndexResolution getIndexResult = IndexResolution.valid(test); return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( config, new EsqlFunctionRegistry(), getIndexResult, @@ -132,7 +132,7 @@ protected Analyzer makeAnalyzer(String mappingFileName) { protected Analyzer makeAnalyzer(IndexResolution indexResolution) { return new Analyzer( - new AnalyzerContext(config, new EsqlFunctionRegistry(), indexResolution, new EnrichResolution(), emptyInferenceResolution()), + testAnalyzerContext(config, new EsqlFunctionRegistry(), indexResolution, new EnrichResolution(), emptyInferenceResolution()), new Verifier(new Metrics(new EsqlFunctionRegistry()), new XPackLicenseState(() -> 0L)) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java index fc61454f05060..f84a5eb4afaa5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/AbstractLogicalPlanOptimizerTests.java @@ -11,7 +11,6 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.core.type.EsField; @@ -30,6 +29,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution; @@ -56,11 +56,11 @@ public abstract class AbstractLogicalPlanOptimizerTests extends ESTestCase { protected static EnrichResolution enrichResolution; - public static class SubstitutionOnlyOptimizer extends LogicalPlanOptimizer { - public static SubstitutionOnlyOptimizer INSTANCE = new SubstitutionOnlyOptimizer(unboundLogicalOptimizerContext()); + public static class TestSubstitutionOnlyOptimizer extends LogicalPlanOptimizer { + // A static instance of this would break the EsqlNodeSubclassTests because its initialization requires a Random instance. - SubstitutionOnlyOptimizer(LogicalOptimizerContext optimizerContext) { - super(optimizerContext); + public TestSubstitutionOnlyOptimizer() { + super(unboundLogicalOptimizerContext()); } @Override @@ -82,7 +82,7 @@ public static void init() { EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(test); analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, @@ -98,7 +98,7 @@ public static void init() { EsIndex airports = new EsIndex("airports", mappingAirports, Map.of("airports", IndexMode.STANDARD)); IndexResolution getIndexResultAirports = IndexResolution.valid(airports); analyzerAirports = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResultAirports, @@ -114,7 +114,7 @@ public static void init() { EsIndex types = new EsIndex("types", mappingTypes, Map.of("types", IndexMode.STANDARD)); IndexResolution getIndexResultTypes = IndexResolution.valid(types); analyzerTypes = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResultTypes, @@ -129,7 +129,7 @@ public static void init() { EsIndex extra = new EsIndex("extra", mappingExtra, Map.of("extra", IndexMode.STANDARD)); IndexResolution getIndexResultExtra = IndexResolution.valid(extra); analyzerExtra = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResultExtra, @@ -142,7 +142,7 @@ public static void init() { metricMapping = loadMapping("k8s-mappings.json"); var metricsIndex = IndexResolution.valid(new EsIndex("k8s", metricMapping, Map.of("k8s", IndexMode.TIME_SERIES))); metricsAnalyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), metricsIndex, @@ -166,7 +166,7 @@ public static void init() { ) ); multiIndexAnalyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), multiIndex, @@ -181,7 +181,7 @@ public static void init() { new EsIndex("sample_data", sampleDataMapping, Map.of("sample_data", IndexMode.STANDARD)) ); sampleDataIndexAnalyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), sampleDataIndex, 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 ca334fecc2165..1ef512a070d14 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 @@ -15,7 +15,6 @@ import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; import org.elasticsearch.xpack.esql.core.expression.AttributeSet; @@ -95,6 +94,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.EsqlTestUtils.statsForExistingField; import static org.elasticsearch.xpack.esql.EsqlTestUtils.statsForMissingField; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.core.tree.Source.EMPTY; @@ -128,7 +128,7 @@ public static void init() { logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, @@ -522,7 +522,7 @@ public void testSparseDocument() throws Exception { var logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); var analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, @@ -1117,7 +1117,7 @@ private static Analyzer analyzerWithUnionTypeMapping() { IndexResolution getIndexResult = IndexResolution.valid(test); return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, 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 dbf6c7b81b766..cf5753e379f61 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 @@ -9,6 +9,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.Build; +import org.elasticsearch.TransportVersion; import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.compute.aggregation.QuantileStates; @@ -22,7 +23,6 @@ import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; @@ -171,6 +171,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.referenceAttribute; import static org.elasticsearch.xpack.esql.EsqlTestUtils.relation; import static org.elasticsearch.xpack.esql.EsqlTestUtils.singleValue; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.Analyzer.NO_FIELDS; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyze; @@ -4989,7 +4990,7 @@ public void testCountOfLiteral() { s_null = count(null) by w = emp_no % 2 | keep s, s_expr, s_null, w - """, SubstitutionOnlyOptimizer.INSTANCE); + """, new TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var esqlProject = as(limit.child(), EsqlProject.class); @@ -5061,7 +5062,7 @@ public void testSumOfLiteral() { s_null = sum(null) by w = emp_no % 2 | keep s, s_expr, s_null, w - """, SubstitutionOnlyOptimizer.INSTANCE); + """, new TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var esqlProject = as(limit.child(), EsqlProject.class); @@ -5168,7 +5169,7 @@ public void testAggOfLiteral() { ); String query = LoggerMessageFormat.format(null, queryWithoutValues, "[1,2]", "314.0/100", "null"); - var plan = plan(query, SubstitutionOnlyOptimizer.INSTANCE); + var plan = plan(query, new TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var esqlProject = as(limit.child(), EsqlProject.class); @@ -5216,7 +5217,7 @@ public void testAggOfLiteralGrouped() { ); String query = LoggerMessageFormat.format(null, queryWithoutValues, "[1,2]", "314.0/100", "null"); - var plan = plan(query, SubstitutionOnlyOptimizer.INSTANCE); + var plan = plan(query, new TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var esqlProject = as(limit.child(), EsqlProject.class); @@ -5284,7 +5285,7 @@ public void testEmptyMappingIndex() { EsIndex empty = new EsIndex("empty_test", emptyMap(), Map.of()); IndexResolution getIndexResultAirports = IndexResolution.valid(empty); var analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResultAirports, @@ -8934,8 +8935,11 @@ public void testKnnWithRerankAmdLimit() { assertThat(knn.k(), equalTo(100)); } - private LogicalPlanOptimizer getCustomRulesLogicalPlanOptimizer(List> batches) { - LogicalOptimizerContext context = new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small()); + private LogicalPlanOptimizer getCustomRulesLogicalPlanOptimizer( + List> batches, + TransportVersion minimumVersion + ) { + LogicalOptimizerContext context = new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small(), minimumVersion); LogicalPlanOptimizer customOptimizer = new LogicalPlanOptimizer(context) { @Override protected List> batches() { @@ -8976,7 +8980,10 @@ protected LogicalPlan rule(Aggregate plan, LogicalOptimizerContext context) { } ); - LogicalPlanOptimizer customRulesLogicalPlanOptimizer = getCustomRulesLogicalPlanOptimizer(List.of(customRuleBatch)); + LogicalPlanOptimizer customRulesLogicalPlanOptimizer = getCustomRulesLogicalPlanOptimizer( + List.of(customRuleBatch), + logicalOptimizer.context().minimumVersion() + ); Exception e = expectThrows(VerificationException.class, () -> customRulesLogicalPlanOptimizer.optimize(plan)); assertThat(e.getMessage(), containsString("Output has changed from")); assertThat(e.getMessage(), containsString("additionalAttribute")); @@ -9021,7 +9028,10 @@ public List output() { } ); - LogicalPlanOptimizer customRulesLogicalPlanOptimizer = getCustomRulesLogicalPlanOptimizer(List.of(customRuleBatch)); + LogicalPlanOptimizer customRulesLogicalPlanOptimizer = getCustomRulesLogicalPlanOptimizer( + List.of(customRuleBatch), + logicalOptimizerCtx.minimumVersion() + ); Exception e = expectThrows(VerificationException.class, () -> customRulesLogicalPlanOptimizer.optimize(plan)); assertThat(e.getMessage(), containsString("Output has changed from")); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanPreOptimizerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanPreOptimizerTests.java index ad7c1dbea1a50..86582923b7af6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanPreOptimizerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/LogicalPlanPreOptimizerTests.java @@ -28,6 +28,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; import static org.elasticsearch.xpack.esql.EsqlTestUtils.fieldAttribute; import static org.elasticsearch.xpack.esql.EsqlTestUtils.of; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomMinimumVersion; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.mockito.Mockito.mock; @@ -74,7 +75,11 @@ public LogicalPlan preOptimizedPlan(LogicalPlan plan) throws Exception { private LogicalPlanPreOptimizer preOptimizer() { var inferenceService = mock(org.elasticsearch.xpack.esql.inference.InferenceService.class); - LogicalPreOptimizerContext preOptimizerContext = new LogicalPreOptimizerContext(FoldContext.small(), inferenceService); + LogicalPreOptimizerContext preOptimizerContext = new LogicalPreOptimizerContext( + FoldContext.small(), + inferenceService, + randomMinimumVersion() + ); return new LogicalPlanPreOptimizer(preOptimizerContext); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRulesTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRulesTests.java index b36cb3f6c6a42..6a720936c8180 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRulesTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/OptimizerRulesTests.java @@ -28,6 +28,7 @@ import java.util.Collections; import java.util.List; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomMinimumVersion; import static org.elasticsearch.xpack.esql.EsqlTestUtils.rangeOf; import static org.elasticsearch.xpack.esql.core.type.DataType.BOOLEAN; import static org.elasticsearch.xpack.esql.core.util.TestUtils.getFieldAttribute; @@ -129,7 +130,7 @@ protected Expression rule(Expression e, LogicalOptimizerContext ctx) { rule.apply( new EsqlParser().createStatement("FROM index | EVAL x=f1+1 | KEEP x, f2 | LIMIT 1"), - new LogicalOptimizerContext(null, FoldContext.small()) + new LogicalOptimizerContext(null, FoldContext.small(), randomMinimumVersion()) ); var literal = new Literal(new Source(1, 25, "1"), 1, DataType.INTEGER); 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 3c7ee590c8653..5fdaa2ca2692b 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 @@ -11,6 +11,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.Build; +import org.elasticsearch.TransportVersion; import org.elasticsearch.common.geo.ShapeRelation; import org.elasticsearch.common.lucene.BytesRefs; import org.elasticsearch.common.settings.Settings; @@ -46,7 +47,6 @@ import org.elasticsearch.xpack.esql.VerificationException; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.Attribute; @@ -147,6 +147,7 @@ import org.elasticsearch.xpack.esql.querydsl.query.SpatialRelatesQuery; import org.elasticsearch.xpack.esql.rule.RuleExecutor; import org.elasticsearch.xpack.esql.session.Configuration; +import org.elasticsearch.xpack.esql.session.Versioned; import org.elasticsearch.xpack.esql.stats.SearchStats; import org.junit.Before; @@ -180,7 +181,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; import static org.elasticsearch.xpack.esql.EsqlTestUtils.statsForMissingField; -import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.SerializationTestUtils.assertSerialization; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.analyze; @@ -228,8 +229,6 @@ public class PhysicalPlanOptimizerTests extends ESTestCase { private static final int KEYWORD_EST = EstimatesRowSize.estimateSize(DataType.KEYWORD); private EsqlParser parser; - private LogicalPlanOptimizer logicalOptimizer; - private PhysicalPlanOptimizer physicalPlanOptimizer; private Mapper mapper; private TestDataSource testData; private TestDataSource testDataLimitedRaw; @@ -251,7 +250,25 @@ public class PhysicalPlanOptimizerTests extends ESTestCase { private final Configuration config; private PlannerSettings plannerSettings; - private record TestDataSource(Map mapping, EsIndex index, Analyzer analyzer, SearchStats stats) {} + private record TestDataSource(Map mapping, EsIndex index, Analyzer analyzer, SearchStats stats) { + TransportVersion minimumVersion() { + return analyzer.context().minimumVersion(); + } + + /** + * A logical optimizer configured for the same minimum transport version as the analyzer. + */ + LogicalPlanOptimizer logicalOptimizer() { + return new LogicalPlanOptimizer(new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, FoldContext.small(), minimumVersion())); + } + + /** + * A physical optimizer configured for the same minimum transport version as the analyzer. + */ + PhysicalPlanOptimizer physicalOptimizer() { + return new PhysicalPlanOptimizer(new PhysicalOptimizerContext(analyzer.context().configuration(), minimumVersion())); + } + } @ParametersFactory(argumentFormatting = PARAM_FORMATTING) public static List params() { @@ -272,8 +289,6 @@ public PhysicalPlanOptimizerTests(String name, Configuration config) { @Before public void init() { parser = new EsqlParser(); - logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); - physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(config)); EsqlFunctionRegistry functionRegistry = new EsqlFunctionRegistry(); mapper = new Mapper(); var enrichResolution = setupEnrichResolution(); @@ -378,7 +393,7 @@ TestDataSource makeTestDataSource( EsIndex index = new EsIndex(indexName, mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(index); Analyzer analyzer = new Analyzer( - new AnalyzerContext(config, functionRegistry, getIndexResult, lookupResolution, enrichResolution, emptyInferenceResolution()), + testAnalyzerContext(config, functionRegistry, getIndexResult, lookupResolution, enrichResolution, emptyInferenceResolution()), TEST_VERIFIER ); return new TestDataSource(mapping, index, analyzer, stats); @@ -2884,7 +2899,7 @@ public void testFieldExtractWithoutSourceAttributes() { ) ); - var e = expectThrows(VerificationException.class, () -> physicalPlanOptimizer.verify(badPlan, verifiedPlan.output())); + var e = expectThrows(VerificationException.class, () -> testData.physicalOptimizer().verify(badPlan, verifiedPlan.output())); assertThat( e.getMessage(), containsString( @@ -2913,7 +2928,10 @@ public void testVerifierOnMissingReferences() { ) ); final var finalPlan = plan; - var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan, planBeforeModification.output())); + var e = expectThrows( + IllegalStateException.class, + () -> testData.physicalOptimizer().verify(finalPlan, planBeforeModification.output()) + ); assertThat(e.getMessage(), containsString(" > 10[INTEGER]]] optimized incorrectly due to missing references [emp_no{f}#")); } @@ -2929,7 +2947,10 @@ public void testVerifierOnMissingReferencesWithBinaryPlans() throws Exception { var planWithInvalidJoinLeftSide = plan.transformUp(LookupJoinExec.class, join -> join.replaceChildren(join.right(), join.right())); - var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinLeftSide, plan.output())); + var e = expectThrows( + IllegalStateException.class, + () -> testData.physicalOptimizer().verify(planWithInvalidJoinLeftSide, plan.output()) + ); assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references from left hand side [languages")); var planWithInvalidJoinRightSide = plan.transformUp( @@ -2938,7 +2959,10 @@ public void testVerifierOnMissingReferencesWithBinaryPlans() throws Exception { join -> new HashJoinExec(join.source(), join.left(), join.left(), join.leftFields(), join.rightFields(), join.output()) ); - e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(planWithInvalidJoinRightSide, plan.output())); + e = expectThrows( + IllegalStateException.class, + () -> testData.physicalOptimizer().verify(planWithInvalidJoinRightSide, plan.output()) + ); assertThat(e.getMessage(), containsString(" optimized incorrectly due to missing references from right hand side [language_code")); } @@ -2963,7 +2987,10 @@ public void testVerifierOnDuplicateOutputAttributes() { ); }); final var finalPlan = plan; - var e = expectThrows(IllegalStateException.class, () -> physicalPlanOptimizer.verify(finalPlan, planBeforeModification.output())); + var e = expectThrows( + IllegalStateException.class, + () -> testData.physicalOptimizer().verify(finalPlan, planBeforeModification.output()) + ); assertThat( e.getMessage(), containsString("Plan [LimitExec[1000[INTEGER],null]] optimized incorrectly due to duplicate output attribute emp_no{f}#") @@ -7554,6 +7581,7 @@ public void testLookupThenTopN() { assertThat(e.getMessage(), containsString("line 3:3: mismatched input 'LOOKUP_🐔' expecting {")); return; } + var plan = physicalPlan(query); ProjectExec outerProject = as(plan, ProjectExec.class); @@ -7561,7 +7589,7 @@ public void testLookupThenTopN() { ExchangeExec exchange = as(outerTopN.child(), ExchangeExec.class); FragmentExec frag = as(exchange.child(), FragmentExec.class); - LogicalPlan opt = logicalOptimizer.optimize(frag.fragment()); + LogicalPlan opt = logicalOptimizer().optimize(frag.fragment()); TopN innerTopN = as(opt, TopN.class); assertMap( innerTopN.order().stream().map(o -> o.child().toString()).toList(), @@ -7794,7 +7822,7 @@ public void testSingleModeAggregate() { | STATS first_name = VALUES(first_name), last_name = VALUES(last_name) BY emp_no | STATS count(first_name) BY last_name"""; PhysicalPlan plan = physicalPlan(q); - PhysicalPlan optimized = physicalPlanOptimizer.optimize(plan); + PhysicalPlan optimized = testData.physicalOptimizer().optimize(plan); LimitExec limit = as(optimized, LimitExec.class); AggregateExec second = as(limit.child(), AggregateExec.class); assertThat(second.getMode(), equalTo(SINGLE)); @@ -7923,7 +7951,7 @@ public void testScore() { ExchangeExec exchange = as(limitExec.child(), ExchangeExec.class); FragmentExec frag = as(exchange.child(), FragmentExec.class); - LogicalPlan opt = logicalOptimizer.optimize(frag.fragment()); + LogicalPlan opt = logicalOptimizer().optimize(frag.fragment()); Limit limit = as(opt, Limit.class); Filter filter = as(limit.child(), Filter.class); @@ -7950,7 +7978,7 @@ public void testScoreTopN() { ExchangeExec exchange = as(topNExec.child(), ExchangeExec.class); FragmentExec frag = as(exchange.child(), FragmentExec.class); - LogicalPlan opt = logicalOptimizer.optimize(frag.fragment()); + LogicalPlan opt = logicalOptimizer().optimize(frag.fragment()); TopN topN = as(opt, TopN.class); List order = topN.order(); Order scoreOrer = order.getFirst(); @@ -8221,12 +8249,20 @@ private static EsQueryExec source(PhysicalPlan plan) { } private PhysicalPlan optimizedPlan(PhysicalPlan plan) { - return optimizedPlan(plan, EsqlTestUtils.TEST_SEARCH_STATS); + return optimizedPlan(plan, testData); + } + + private PhysicalPlan optimizedPlan(PhysicalPlan plan, TestDataSource data) { + return optimizedPlan(plan, data, data.stats()); + } + + private PhysicalPlan optimizedPlan(PhysicalPlan plan, SearchStats stats) { + return optimizedPlan(plan, testData, stats); } - private PhysicalPlan optimizedPlan(PhysicalPlan plan, SearchStats searchStats) { + private PhysicalPlan optimizedPlan(PhysicalPlan plan, TestDataSource data, SearchStats stats) { // System.out.println("* Physical Before\n" + plan); - var p = EstimatesRowSize.estimateRowSize(0, physicalPlanOptimizer.optimize(plan)); + var p = EstimatesRowSize.estimateRowSize(0, data.physicalOptimizer().optimize(plan)); // System.out.println("* Physical After\n" + p); // the real execution breaks the plan at the exchange and then decouples the plan // this is of no use in the unit tests, which checks the plan as a whole instead of each @@ -8234,7 +8270,7 @@ private PhysicalPlan optimizedPlan(PhysicalPlan plan, SearchStats searchStats) { var l = p.transformUp(FragmentExec.class, fragment -> { var flags = new EsqlFlags(true); - var localPlan = PlannerUtils.localPlan(TEST_PLANNER_SETTINGS, flags, config, FoldContext.small(), fragment, searchStats); + var localPlan = PlannerUtils.localPlan(TEST_PLANNER_SETTINGS, flags, config, FoldContext.small(), fragment, stats); return EstimatesRowSize.estimateRowSize(fragment.estimatedRowSize(), localPlan); }); @@ -8272,9 +8308,10 @@ private PhysicalPlan physicalPlan(String query, TestDataSource dataSource) { } private PhysicalPlan physicalPlan(String query, TestDataSource dataSource, boolean assertSerialization) { + var logicalOptimizer = dataSource.logicalOptimizer(); var logical = logicalOptimizer.optimize(dataSource.analyzer.analyze(parser.createStatement(query))); // System.out.println("Logical\n" + logical); - var physical = mapper.map(logical); + var physical = mapper.map(new Versioned<>(logical, dataSource.minimumVersion())); // System.out.println("Physical\n" + physical); if (assertSerialization) { assertSerialization(physical, config); @@ -8282,6 +8319,10 @@ private PhysicalPlan physicalPlan(String query, TestDataSource dataSource, boole return physical; } + private LogicalPlanOptimizer logicalOptimizer() { + return testData.logicalOptimizer(); + } + private List fieldSorts(List orders) { return orders.stream().map(o -> new FieldSort((FieldAttribute) o.child(), o.direction(), o.nullsPosition())).toList(); } @@ -8302,7 +8343,8 @@ private QueryBuilder sv(QueryBuilder builder, String fieldName) { } private PhysicalPlanOptimizer getCustomRulesPhysicalPlanOptimizer(List> batches) { - PhysicalOptimizerContext context = new PhysicalOptimizerContext(config); + var analyzerContext = testData.analyzer.context(); + PhysicalOptimizerContext context = new PhysicalOptimizerContext(analyzerContext.configuration(), analyzerContext.minimumVersion()); PhysicalPlanOptimizer PhysicalPlanOptimizer = new PhysicalPlanOptimizer(context) { @Override protected List> batches() { @@ -8313,7 +8355,6 @@ protected List> batches() { } public void testVerifierOnAdditionalAttributeAdded() throws Exception { - PhysicalPlan plan = physicalPlan(""" from test | stats a = min(salary) by emp_no @@ -8354,7 +8395,6 @@ public PhysicalPlan rule(PhysicalPlan plan, PhysicalOptimizerContext context) { } public void testVerifierOnAttributeDatatypeChanged() throws Exception { - PhysicalPlan plan = physicalPlan(""" from test | stats a = min(salary) by emp_no diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/TestPlannerOptimizer.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/TestPlannerOptimizer.java index cdb793f7efd61..713ae3eb2f063 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/TestPlannerOptimizer.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/TestPlannerOptimizer.java @@ -18,6 +18,7 @@ import org.elasticsearch.xpack.esql.planner.mapper.Mapper; import org.elasticsearch.xpack.esql.plugin.EsqlFlags; import org.elasticsearch.xpack.esql.session.Configuration; +import org.elasticsearch.xpack.esql.session.Versioned; import org.elasticsearch.xpack.esql.stats.SearchStats; import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_PLANNER_SETTINGS; @@ -31,7 +32,11 @@ public class TestPlannerOptimizer { private final Configuration config; public TestPlannerOptimizer(Configuration config, Analyzer analyzer) { - this(config, analyzer, new LogicalPlanOptimizer(new LogicalOptimizerContext(config, FoldContext.small()))); + this( + config, + analyzer, + new LogicalPlanOptimizer(new LogicalOptimizerContext(config, FoldContext.small(), analyzer.context().minimumVersion())) + ); } public TestPlannerOptimizer(Configuration config, Analyzer analyzer, LogicalPlanOptimizer logicalOptimizer) { @@ -40,7 +45,7 @@ public TestPlannerOptimizer(Configuration config, Analyzer analyzer, LogicalPlan this.logicalOptimizer = logicalOptimizer; parser = new EsqlParser(); - physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(config)); + physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(config, analyzer.context().minimumVersion())); mapper = new Mapper(); } @@ -93,7 +98,6 @@ private PhysicalPlan optimizedPlan(PhysicalPlan plan, SearchStats searchStats, E private PhysicalPlan physicalPlan(String query, Analyzer analyzer) { LogicalPlan logical = logicalOptimizer.optimize(analyzer.analyze(parser.createStatement(query))); // System.out.println("Logical\n" + logical); - PhysicalPlan physical = mapper.map(logical); - return physical; + return mapper.map(new Versioned<>(logical, analyzer.context().minimumVersion())); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/LogicalOptimizerContextTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/LogicalOptimizerContextTests.java index 5d2fec0fc8181..57b75a14e168d 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/LogicalOptimizerContextTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/LogicalOptimizerContextTests.java @@ -7,25 +7,35 @@ package org.elasticsearch.xpack.esql.optimizer.rules; +import org.elasticsearch.TransportVersion; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.EqualsHashCodeTestUtils; import org.elasticsearch.xpack.esql.ConfigurationTestUtils; import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.tree.Source; import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; import org.elasticsearch.xpack.esql.session.Configuration; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomMinimumVersion; import static org.hamcrest.Matchers.equalTo; public class LogicalOptimizerContextTests extends ESTestCase { public void testToString() { // Random looking numbers for FoldContext are indeed random. Just so we have consistent numbers to assert on in toString. - LogicalOptimizerContext ctx = new LogicalOptimizerContext(EsqlTestUtils.TEST_CFG, new FoldContext(102)); + // Same for the transport version. + LogicalOptimizerContext ctx = new LogicalOptimizerContext( + EsqlTestUtils.TEST_CFG, + new FoldContext(102), + FieldAttribute.ESQL_FIELD_ATTRIBUTE_DROP_TYPE + ); ctx.foldCtx().trackAllocation(Source.EMPTY, 99); assertThat( ctx.toString(), - equalTo("LogicalOptimizerContext[configuration=" + EsqlTestUtils.TEST_CFG + ", foldCtx=FoldContext[3/102]]") + equalTo( + "LogicalOptimizerContext[configuration=" + EsqlTestUtils.TEST_CFG + ", foldCtx=FoldContext[3/102], minimumVersion=9075000]" + ) ); } @@ -34,22 +44,23 @@ public void testEqualsAndHashCode() { } private LogicalOptimizerContext randomLogicalOptimizerContext() { - return new LogicalOptimizerContext(ConfigurationTestUtils.randomConfiguration(), randomFoldContext()); + return new LogicalOptimizerContext(ConfigurationTestUtils.randomConfiguration(), randomFoldContext(), randomMinimumVersion()); } private LogicalOptimizerContext copy(LogicalOptimizerContext c) { - return new LogicalOptimizerContext(c.configuration(), c.foldCtx()); + return new LogicalOptimizerContext(c.configuration(), c.foldCtx(), c.minimumVersion()); } private LogicalOptimizerContext mutate(LogicalOptimizerContext c) { Configuration configuration = c.configuration(); FoldContext foldCtx = c.foldCtx(); - if (randomBoolean()) { - configuration = randomValueOtherThan(configuration, ConfigurationTestUtils::randomConfiguration); - } else { - foldCtx = randomValueOtherThan(foldCtx, this::randomFoldContext); + TransportVersion minVersion = c.minimumVersion(); + switch (randomIntBetween(0, 2)) { + case 0 -> configuration = randomValueOtherThan(configuration, ConfigurationTestUtils::randomConfiguration); + case 1 -> foldCtx = randomValueOtherThan(foldCtx, this::randomFoldContext); + case 2 -> minVersion = randomValueOtherThan(minVersion, EsqlTestUtils::randomMinimumVersion); } - return new LogicalOptimizerContext(configuration, foldCtx); + return new LogicalOptimizerContext(configuration, foldCtx, minVersion); } private FoldContext randomFoldContext() { 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 c8714fbdb035a..eedad20dcc42f 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 @@ -12,7 +12,6 @@ import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.action.EsqlCapabilities; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.core.expression.Expressions; import org.elasticsearch.xpack.esql.core.expression.ReferenceAttribute; @@ -20,8 +19,8 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; import org.elasticsearch.xpack.esql.index.IndexResolution; +import org.elasticsearch.xpack.esql.optimizer.AbstractLogicalPlanOptimizerTests; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; -import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizerTests; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.plan.logical.Aggregate; import org.elasticsearch.xpack.esql.plan.logical.EsRelation; @@ -39,6 +38,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultInferenceResolution; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; @@ -59,7 +59,7 @@ public static void init() { EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(test); analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, @@ -88,7 +88,7 @@ public void testGroupingAliasingMoved_To_LeftSideOfJoin() { from test | keep emp_no, languages, gender | inline stats max_lang = MAX(languages) BY y = gender - """, LogicalPlanOptimizerTests.SubstitutionOnlyOptimizer.INSTANCE); + """, new AbstractLogicalPlanOptimizerTests.TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var inline = as(limit.child(), InlineJoin.class); @@ -132,7 +132,7 @@ public void testGroupingAliasingMoved_To_LeftSideOfJoin_WithExpression() { | keep emp_no, languages, gender, last_name, first_name | eval first_name_l = left(first_name, 1) | inline stats max_lang = MAX(languages), min_lang = MIN(languages) BY f = left(last_name, 1), g = gender, first_name_l - """, LogicalPlanOptimizerTests.SubstitutionOnlyOptimizer.INSTANCE); + """, new AbstractLogicalPlanOptimizerTests.TestSubstitutionOnlyOptimizer()); var limit = as(plan, Limit.class); var inline = as(limit.child(), InlineJoin.class); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java index e1797d6c2c16a..ecd12af404108 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/PushDownAndCombineFiltersTests.java @@ -69,6 +69,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.greaterThanOrEqualOf; import static org.elasticsearch.xpack.esql.EsqlTestUtils.lessThanOf; import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomLiteral; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.randomMinimumVersion; import static org.elasticsearch.xpack.esql.EsqlTestUtils.referenceAttribute; import static org.elasticsearch.xpack.esql.EsqlTestUtils.rlike; import static org.elasticsearch.xpack.esql.EsqlTestUtils.wildcardLike; @@ -79,7 +80,7 @@ public class PushDownAndCombineFiltersTests extends AbstractLogicalPlanOptimizerTests { - private final LogicalOptimizerContext optimizerContext = new LogicalOptimizerContext(null, FoldContext.small()); + private final LogicalOptimizerContext optimizerContext = new LogicalOptimizerContext(null, FoldContext.small(), randomMinimumVersion()); public void testCombineFilters() { EsRelation relation = relation(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java index 24045e888ab05..6f927dae662d8 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/optimizer/rules/logical/local/IgnoreNullMetricsTests.java @@ -11,7 +11,6 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils; import org.elasticsearch.xpack.esql.analysis.EnrichResolution; import org.elasticsearch.xpack.esql.core.expression.Alias; @@ -47,6 +46,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; import static org.elasticsearch.xpack.esql.EsqlTestUtils.as; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.unboundLogicalOptimizerContext; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.defaultLookupResolution; import static org.hamcrest.Matchers.hasSize; @@ -86,7 +86,7 @@ private static void init() { EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.TIME_SERIES)); IndexResolution getIndexResult = IndexResolution.valid(test); analyzer = new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java index 0b77af3436f02..96507bf9615a1 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/FilterTests.java @@ -43,6 +43,7 @@ import org.elasticsearch.xpack.esql.plugin.EsqlFlags; import org.elasticsearch.xpack.esql.querydsl.query.SingleValueQuery; import org.elasticsearch.xpack.esql.session.Configuration; +import org.elasticsearch.xpack.esql.session.Versioned; import org.junit.BeforeClass; import java.io.IOException; @@ -87,7 +88,8 @@ public static void init() { EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(test); logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); - physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(EsqlTestUtils.TEST_CFG)); + TransportVersion minimumVersion = logicalOptimizer.context().minimumVersion(); + physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(EsqlTestUtils.TEST_CFG, minimumVersion)); mapper = new Mapper(); analyzer = new Analyzer( @@ -95,8 +97,10 @@ public static void init() { EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, + Map.of(), EsqlTestUtils.emptyPolicyResolution(), - emptyInferenceResolution() + emptyInferenceResolution(), + minimumVersion ), TEST_VERIFIER ); @@ -372,7 +376,7 @@ public static QueryBuilder singleValueQuery(String query, QueryBuilder inner, St private PhysicalPlan plan(String query, QueryBuilder restFilter) { var logical = logicalOptimizer.optimize(analyzer.analyze(parser.createStatement(query))); // System.out.println("Logical\n" + logical); - var physical = mapper.map(logical); + var physical = mapper.map(new Versioned<>(logical, logicalOptimizer.context().minimumVersion())); // System.out.println("physical\n" + physical); physical = physical.transformUp( FragmentExec.class, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java index 102779a0a1ca5..be9a3d547c58c 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/LocalExecutionPlannerTests.java @@ -47,6 +47,7 @@ import org.elasticsearch.plugins.Plugin; import org.elasticsearch.search.internal.AliasFilter; import org.elasticsearch.search.internal.ContextIndexSearcher; +import org.elasticsearch.xpack.esql.analysis.AnalyzerSettings; import org.elasticsearch.xpack.esql.core.expression.Alias; import org.elasticsearch.xpack.esql.core.expression.FieldAttribute; import org.elasticsearch.xpack.esql.core.expression.FoldContext; @@ -62,7 +63,6 @@ import org.elasticsearch.xpack.esql.plan.physical.EsQueryExec; import org.elasticsearch.xpack.esql.plan.physical.FieldExtractExec; import org.elasticsearch.xpack.esql.plan.physical.TimeSeriesAggregateExec; -import org.elasticsearch.xpack.esql.plugin.EsqlPlugin; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; import org.elasticsearch.xpack.spatial.SpatialPlugin; @@ -368,15 +368,15 @@ private Configuration config() { "test_user", "test_cluster", pragmas, - EsqlPlugin.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(null), - EsqlPlugin.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_MAX_SIZE.getDefault(null), + AnalyzerSettings.QUERY_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null), StringUtils.EMPTY, false, Map.of(), System.nanoTime(), randomBoolean(), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(null), - EsqlPlugin.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(null), + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null) ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java index da36b42d1241b..5212abf4270f7 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/PlanConcurrencyCalculatorTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.planner; +import org.elasticsearch.TransportVersion; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.analysis.Analyzer; @@ -21,6 +22,7 @@ import org.elasticsearch.xpack.esql.planner.mapper.Mapper; import org.elasticsearch.xpack.esql.plugin.QueryPragmas; import org.elasticsearch.xpack.esql.session.Configuration; +import org.elasticsearch.xpack.esql.session.Versioned; import java.util.List; @@ -238,11 +240,14 @@ private void assertConcurrency(String query, Integer concurrencyPragmaValue, Int ); Analyzer analyzer = analyzer(analyzerDefaultMapping(), TEST_VERIFIER, configuration); + TransportVersion minimumVersion = analyzer.context().minimumVersion(); LogicalPlan logicalPlan = AnalyzerTestUtils.analyze(query, analyzer); - logicalPlan = new LogicalPlanOptimizer(new LogicalOptimizerContext(configuration, FoldContext.small())).optimize(logicalPlan); + logicalPlan = new LogicalPlanOptimizer(new LogicalOptimizerContext(configuration, FoldContext.small(), minimumVersion)).optimize( + logicalPlan + ); - PhysicalPlan physicalPlan = new Mapper().map(logicalPlan); - physicalPlan = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(configuration)).optimize(physicalPlan); + PhysicalPlan physicalPlan = new Mapper().map(new Versioned<>(logicalPlan, minimumVersion)); + physicalPlan = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(configuration, minimumVersion)).optimize(physicalPlan); PhysicalPlan dataNodePlan = PlannerUtils.breakPlanBetweenCoordinatorAndDataNode(physicalPlan, configuration).v2(); diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java index 720cd77dd2c8a..4fd28e8897ee7 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/QueryTranslatorTests.java @@ -12,7 +12,6 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.esql.EsqlTestUtils; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.analysis.Verifier; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; @@ -31,6 +30,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; import static org.elasticsearch.xpack.esql.analysis.AnalyzerTestUtils.indexWithDateDateNanosUnionType; import static org.hamcrest.Matchers.containsString; @@ -51,7 +51,7 @@ private static Analyzer makeAnalyzer(String mappingFileName) { IndexResolution getIndexResult = IndexResolution.valid(test); return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, @@ -64,7 +64,7 @@ private static Analyzer makeAnalyzer(String mappingFileName) { public static Analyzer makeAnalyzer(IndexResolution indexResolution) { return new Analyzer( - new AnalyzerContext( + testAnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), indexResolution, diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java index f9dff219b8632..b25fba6f1dc48 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.plugin; +import org.elasticsearch.TransportVersion; import org.elasticsearch.action.OriginalIndices; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -23,10 +24,12 @@ import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; import org.elasticsearch.xpack.esql.index.EsIndex; import org.elasticsearch.xpack.esql.index.IndexResolution; +import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; import org.elasticsearch.xpack.esql.parser.EsqlParser; import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; +import org.elasticsearch.xpack.esql.session.Versioned; import java.io.IOException; import java.util.ArrayList; @@ -164,22 +167,26 @@ private static String randomQuery() { """); } - static LogicalPlan parse(String query) { + static Versioned parse(String query) { Map mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(test); - var logicalOptimizer = new LogicalPlanOptimizer(unboundLogicalOptimizerContext()); + LogicalOptimizerContext context = unboundLogicalOptimizerContext(); + TransportVersion minimumVersion = context.minimumVersion(); + var logicalOptimizer = new LogicalPlanOptimizer(context); var analyzer = new Analyzer( new AnalyzerContext( EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, + Map.of(), emptyPolicyResolution(), - emptyInferenceResolution() + emptyInferenceResolution(), + minimumVersion ), TEST_VERIFIER ); - return logicalOptimizer.optimize(analyzer.analyze(new EsqlParser().createStatement(query))); + return new Versioned<>(logicalOptimizer.optimize(analyzer.analyze(new EsqlParser().createStatement(query))), minimumVersion); } @Override diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java index 666ce63082d75..a9faac859f0da 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequestSerializationTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.plugin; +import org.elasticsearch.TransportVersion; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; import org.elasticsearch.common.io.stream.Writeable; @@ -19,7 +20,6 @@ import org.elasticsearch.search.internal.AliasFilter; import org.elasticsearch.test.AbstractWireSerializingTestCase; import org.elasticsearch.xpack.esql.analysis.Analyzer; -import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.core.type.EsField; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; @@ -33,6 +33,7 @@ import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; import org.elasticsearch.xpack.esql.planner.mapper.Mapper; +import org.elasticsearch.xpack.esql.session.Versioned; import java.io.IOException; import java.util.ArrayList; @@ -46,6 +47,7 @@ import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyInferenceResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution; import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.testAnalyzerContext; import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; public class DataNodeRequestSerializationTests extends AbstractWireSerializingTestCase { @@ -297,20 +299,21 @@ protected DataNodeRequest mutateInstance(DataNodeRequest in) throws IOException }; } - static LogicalPlan parse(String query) { + static Versioned parse(String query) { Map mapping = loadMapping("mapping-basic.json"); EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); IndexResolution getIndexResult = IndexResolution.valid(test); - var logicalOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(TEST_CFG, FoldContext.small())); var analyzer = new Analyzer( - new AnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, emptyPolicyResolution(), emptyInferenceResolution()), + testAnalyzerContext(TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, emptyPolicyResolution(), emptyInferenceResolution()), TEST_VERIFIER ); - return logicalOptimizer.optimize(analyzer.analyze(new EsqlParser().createStatement(query))); + TransportVersion minimumVersion = analyzer.context().minimumVersion(); + var logicalOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(TEST_CFG, FoldContext.small(), minimumVersion)); + return new Versioned<>(logicalOptimizer.optimize(analyzer.analyze(new EsqlParser().createStatement(query))), minimumVersion); } - static PhysicalPlan mapAndMaybeOptimize(LogicalPlan logicalPlan) { - var physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(TEST_CFG)); + static PhysicalPlan mapAndMaybeOptimize(Versioned logicalPlan) { + var physicalPlanOptimizer = new PhysicalPlanOptimizer(new PhysicalOptimizerContext(TEST_CFG, logicalPlan.minimumVersion())); var mapper = new Mapper(); var physical = mapper.map(logicalPlan); if (randomBoolean()) {