diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionBreakerIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionBreakerIT.java index a8b687cde48ca..7a2cccdf680b3 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionBreakerIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionBreakerIT.java @@ -87,10 +87,6 @@ protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) { } public static class EsqlTestPluginWithMockBlockFactory extends EsqlPlugin { - public EsqlTestPluginWithMockBlockFactory(Settings settings) { - super(settings); - } - @Override protected BlockFactoryProvider blockFactoryProvider( CircuitBreaker breaker, diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithEnterpriseOrTrialLicense.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithEnterpriseOrTrialLicense.java index 79359229e2b16..34d09fc541572 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithEnterpriseOrTrialLicense.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithEnterpriseOrTrialLicense.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.esql.action; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.internal.XPackLicenseStatus; @@ -20,10 +19,6 @@ * that require an Enteprise (or Trial) license. */ public class EsqlPluginWithEnterpriseOrTrialLicense extends EsqlPlugin { - public EsqlPluginWithEnterpriseOrTrialLicense(Settings settings) { - super(settings); - } - protected XPackLicenseState getLicenseState() { License.OperationMode operationMode = randomFrom(License.OperationMode.ENTERPRISE, License.OperationMode.TRIAL); return new XPackLicenseState(() -> System.currentTimeMillis(), new XPackLicenseStatus(operationMode, true, "Test license expired")); diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithNonEnterpriseOrExpiredLicense.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithNonEnterpriseOrExpiredLicense.java index 4f942173a1b26..46c3f3f6204cd 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithNonEnterpriseOrExpiredLicense.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlPluginWithNonEnterpriseOrExpiredLicense.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.esql.action; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.internal.XPackLicenseStatus; @@ -23,10 +22,6 @@ * - an expired enterprise or trial license */ public class EsqlPluginWithNonEnterpriseOrExpiredLicense extends EsqlPlugin { - public EsqlPluginWithNonEnterpriseOrExpiredLicense(Settings settings) { - super(settings); - } - protected XPackLicenseState getLicenseState() { License.OperationMode operationMode; boolean active; diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialNoLicenseTestCase.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialNoLicenseTestCase.java index 4ccbf4dd9164e..c3a770ed375e7 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialNoLicenseTestCase.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/spatial/SpatialNoLicenseTestCase.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.esql.spatial; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.License; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.license.internal.XPackLicenseStatus; @@ -50,10 +49,6 @@ private static XPackLicenseState getLicenseState() { * This is used to test the behavior of spatial functions when no valid license is present. */ public static class TestEsqlPlugin extends EsqlPlugin { - public TestEsqlPlugin(Settings settings) { - super(settings); - } - protected XPackLicenseState getLicenseState() { return SpatialNoLicenseTestCase.getLicenseState(); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PlanCheckerProvider.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PlanCheckerProvider.java new file mode 100644 index 0000000000000..04096a8db7e6e --- /dev/null +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/PlanCheckerProvider.java @@ -0,0 +1,27 @@ +/* + * 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.cluster.project.ProjectResolver; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.xpack.esql.common.Failures; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; + +import java.util.List; +import java.util.function.BiConsumer; + +/** + * SPI provider interface for supplying additional ESQL plan checks to be performed during verification. + */ +public interface PlanCheckerProvider { + /** + * Build a list of checks to perform on the plan. Each one is called once per + * {@link LogicalPlan} node in the plan. + */ + List> checkers(ProjectResolver projectResolver, ClusterService clusterService); +} diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java index 9db9e1cabfa14..30fa9e8609b3c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/analysis/Verifier.java @@ -7,7 +7,6 @@ package org.elasticsearch.xpack.esql.analysis; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.xpack.esql.LicenseAware; import org.elasticsearch.xpack.esql.capabilities.PostAnalysisPlanVerificationAware; @@ -56,31 +55,22 @@ * step does type resolution and fails queries based on invalid type expressions. */ public class Verifier { - public interface ExtraCheckers { - /** - * Build a list of checks to perform on the plan. Each one is called once per - * {@link LogicalPlan} node in the plan. - */ - List> extra(Settings settings); - } /** * Extra plan verification checks defined in plugins. */ - private final List extraCheckers; + private final List> extraCheckers; private final Metrics metrics; private final XPackLicenseState licenseState; - private final Settings settings; public Verifier(Metrics metrics, XPackLicenseState licenseState) { - this(metrics, licenseState, Collections.emptyList(), Settings.EMPTY); + this(metrics, licenseState, Collections.emptyList()); } - public Verifier(Metrics metrics, XPackLicenseState licenseState, List extraCheckers, Settings settings) { + public Verifier(Metrics metrics, XPackLicenseState licenseState, List> extraCheckers) { this.metrics = metrics; this.licenseState = licenseState; this.extraCheckers = extraCheckers; - this.settings = settings; } /** @@ -104,9 +94,7 @@ Collection verify(LogicalPlan plan, BitSet partialMetrics) { // collect plan checkers var planCheckers = planCheckers(plan); - for (ExtraCheckers e : extraCheckers) { - planCheckers.addAll(e.extra(settings)); - } + planCheckers.addAll(extraCheckers); // Concrete verifications plan.forEachDown(p -> { 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 414e1f372ea3f..2043176f24a29 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 @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.esql.execution; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.indices.IndicesExpressionGrouper; import org.elasticsearch.license.XPackLicenseState; import org.elasticsearch.telemetry.metric.MeterRegistry; @@ -16,6 +15,7 @@ import org.elasticsearch.xpack.esql.action.EsqlQueryRequest; import org.elasticsearch.xpack.esql.analysis.PreAnalyzer; import org.elasticsearch.xpack.esql.analysis.Verifier; +import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.core.expression.FoldContext; import org.elasticsearch.xpack.esql.enrich.EnrichPolicyResolver; import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; @@ -23,6 +23,7 @@ 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.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.planner.mapper.Mapper; import org.elasticsearch.xpack.esql.plugin.TransportActionServices; import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog; @@ -36,6 +37,7 @@ import org.elasticsearch.xpack.esql.telemetry.QueryMetric; import java.util.List; +import java.util.function.BiConsumer; import static org.elasticsearch.action.ActionListener.wrap; @@ -55,15 +57,14 @@ public PlanExecutor( MeterRegistry meterRegistry, XPackLicenseState licenseState, EsqlQueryLog queryLog, - List extraCheckers, - Settings settings + List> extraCheckers ) { this.indexResolver = indexResolver; this.preAnalyzer = new PreAnalyzer(); this.functionRegistry = new EsqlFunctionRegistry(); this.mapper = new Mapper(); this.metrics = new Metrics(functionRegistry); - this.verifier = new Verifier(metrics, licenseState, extraCheckers, settings); + this.verifier = new Verifier(metrics, licenseState, extraCheckers); this.planTelemetryManager = new PlanTelemetryManager(meterRegistry); this.queryLog = queryLog; } 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 776874fbf90f6..64e205e68d6fe 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,7 +67,8 @@ 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.Verifier; +import org.elasticsearch.xpack.esql.analysis.PlanCheckerProvider; +import org.elasticsearch.xpack.esql.common.Failures; import org.elasticsearch.xpack.esql.enrich.EnrichLookupOperator; import org.elasticsearch.xpack.esql.enrich.LookupFromIndexOperator; import org.elasticsearch.xpack.esql.execution.PlanExecutor; @@ -75,6 +76,7 @@ import org.elasticsearch.xpack.esql.io.stream.ExpressionQueryBuilder; import org.elasticsearch.xpack.esql.io.stream.PlanStreamWrapperQueryBuilder; import org.elasticsearch.xpack.esql.plan.PlanWritables; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; import org.elasticsearch.xpack.esql.planner.PhysicalSettings; import org.elasticsearch.xpack.esql.querydsl.query.SingleValueQuery; import org.elasticsearch.xpack.esql.querylog.EsqlQueryLog; @@ -85,6 +87,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -184,12 +187,7 @@ public class EsqlPlugin extends Plugin implements ActionPlugin, ExtensiblePlugin Setting.Property.Dynamic ); - private final List extraCheckers = new ArrayList<>(); - private final Settings settings; - - public EsqlPlugin(Settings settings) { - this.settings = settings; - } + private final List extraCheckerProviders = new ArrayList<>(); @Override public Collection createComponents(PluginServices services) { @@ -203,14 +201,17 @@ public Collection createComponents(PluginServices services) { BigArrays bigArrays = services.indicesService().getBigArrays().withCircuitBreaking(); var blockFactoryProvider = blockFactoryProvider(circuitBreaker, bigArrays, maxPrimitiveArrayBlockSize); setupSharedSecrets(); + List> extraCheckers = extraCheckerProviders.stream() + .flatMap(p -> p.checkers(services.projectResolver(), services.clusterService()).stream()) + .toList(); + return List.of( new PlanExecutor( new IndexResolver(services.client()), services.telemetryProvider().getMeterRegistry(), getLicenseState(), new EsqlQueryLog(services.clusterService().getClusterSettings(), services.slowLogFieldProvider()), - extraCheckers, - settings + extraCheckers ), new ExchangeService( services.clusterService().getSettings(), @@ -349,6 +350,6 @@ public List> getExecutorBuilders(Settings settings) { @Override public void loadExtensions(ExtensionLoader loader) { - extraCheckers.addAll(loader.loadExtensions(Verifier.ExtraCheckers.class)); + extraCheckerProviders.addAll(loader.loadExtensions(PlanCheckerProvider.class)); } } 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 65587cf4d6876..cba3c3a7556e0 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 @@ -57,7 +57,7 @@ protected Writeable.Reader instanceReader() { protected NamedWriteableRegistry getNamedWriteableRegistry() { List writeables = new ArrayList<>(); writeables.addAll(new SearchModule(Settings.EMPTY, List.of()).getNamedWriteables()); - writeables.addAll(new EsqlPlugin(Settings.EMPTY).getNamedWriteables()); + writeables.addAll(new EsqlPlugin().getNamedWriteables()); return new NamedWriteableRegistry(writeables); } 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 fe0e028db4c9c..1a1d981ca0ba1 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 @@ -60,7 +60,7 @@ protected Writeable.Reader instanceReader() { protected NamedWriteableRegistry getNamedWriteableRegistry() { List writeables = new ArrayList<>(); writeables.addAll(new SearchModule(Settings.EMPTY, List.of()).getNamedWriteables()); - writeables.addAll(new EsqlPlugin(Settings.EMPTY).getNamedWriteables()); + writeables.addAll(new EsqlPlugin().getNamedWriteables()); return new NamedWriteableRegistry(writeables); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java index 2d8151d8fc2a1..752e61c240cd5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/telemetry/PlanExecutorMetricsTests.java @@ -150,14 +150,7 @@ public void testFailedMetric() { return null; }).when(esqlClient).execute(eq(EsqlResolveFieldsAction.TYPE), any(), any()); - var planExecutor = new PlanExecutor( - indexResolver, - MeterRegistry.NOOP, - new XPackLicenseState(() -> 0L), - mockQueryLog(), - List.of(), - Settings.EMPTY - ); + var planExecutor = new PlanExecutor(indexResolver, MeterRegistry.NOOP, new XPackLicenseState(() -> 0L), mockQueryLog(), List.of()); var enrichResolver = mockEnrichResolver(); var request = new EsqlQueryRequest();