Skip to content

Commit baeb90f

Browse files
committed
Make analytics-engine an optional dependency
sql plugin previously declared analytics-engine as a hard dependency: extendedPlugins = ['opensearch-job-scheduler', 'analytics-engine'] Installing opensearch-sql on a distribution that doesn't ship analytics-engine failed with: Missing plugin [analytics-engine], dependency of [opensearch-sql] Marking the dep ;optional=true alone is not enough — TransportPPLQueryAction Guice-injects QueryPlanExecutor on its constructor, and Guice's OpenSearch fork rejects a required constructor parameter whose binding is missing at injector-build time ("constructors cannot be optional"). Move QueryPlanExecutor from a required constructor parameter to an @Inject(optional=true) setter. Guice invokes the setter only when a binding for QueryPlanExecutor<RelNode, Iterable<Object[]>> exists — i.e. when analytics-engine's createGuiceModules has run and bound DefaultPlanExecutor. Absent analytics-engine, the setter is silently skipped, unifiedQueryHandler stays null, and all PPL queries route to the v2 Calcite-to-OpenSearch path already in the sql plugin. Drop the bundlePlugin exclude list. OpenSearch's jar-hell check skips the extended-plugin cross-check when the dep is marked optional (PluginsService.java:763), so sql can bundle every jar it needs to run self-sufficiently. When analytics-engine is installed, parent-first classloader delegation still lets analytics-engine's copies win for any shared class; sql's bundled copies sit idle. Promote analytics-framework.jar from compileOnly to api so QueryPlanExecutor is reachable from sql's own classloader when the plugin is absent. analytics-engine.jar stays compileOnly (required only for OpenSearchSchemaBuilder, which lives in the engine plugin and is reached only through RestUnifiedQueryAction — a class that never loads when the setter is skipped). Validated on a live 2-node cluster in both configurations: - With analytics-engine installed: legacy and analytics PPL both return expected rows; routing to the analytics path still fires for parquet_-prefixed indices. - Without analytics-engine (only opensearch-job-scheduler + opensearch-sql installed): cluster starts cleanly, PPL and SQL queries against Lucene indices return expected rows, parquet_-prefixed lookups return a clean IndexNotFoundException instead of a NullPointerException or NoClassDefFoundError. Signed-off-by: bowenlan-amzn <bowenlan23@gmail.com>
1 parent 92d22ed commit baeb90f

2 files changed

Lines changed: 24 additions & 57 deletions

File tree

plugin/build.gradle

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -55,57 +55,11 @@ opensearchplugin {
5555
name 'opensearch-sql'
5656
description 'OpenSearch SQL'
5757
classname 'org.opensearch.sql.plugin.SQLPlugin'
58-
extendedPlugins = ['opensearch-job-scheduler', 'analytics-engine']
58+
extendedPlugins = ['opensearch-job-scheduler', 'analytics-engine;optional=true']
5959
licenseFile rootProject.file("LICENSE.txt")
6060
noticeFile rootProject.file("NOTICE")
6161
}
6262

63-
// Exclude jars provided by the analytics-engine plugin (shared via extendedPlugins classloader).
64-
// MUST match what's actually in the analytics-engine ZIP to avoid both jar hell (when analytics-engine
65-
// ships it) and ClassNotFoundException at runtime (when it doesn't). Last verified against
66-
// analytics-engine-3.7.0-SNAPSHOT — keep this list aligned when bumping versions.
67-
bundlePlugin {
68-
exclude 'calcite-core-*.jar'
69-
exclude 'calcite-linq4j-*.jar'
70-
exclude 'avatica-core-*.jar'
71-
exclude 'guava-*.jar'
72-
exclude 'failureaccess-*.jar'
73-
exclude 'slf4j-api-*.jar'
74-
exclude 'commons-codec-*.jar'
75-
exclude 'commons-compiler-*.jar'
76-
exclude 'commons-io-*.jar'
77-
exclude 'commons-lang3-*.jar'
78-
exclude 'janino-*.jar'
79-
exclude 'joou-java-6-*.jar'
80-
exclude 'json-path-*.jar'
81-
exclude 'jackson-annotations-*.jar'
82-
exclude 'jackson-databind-*.jar'
83-
exclude 'httpcore5-5*.jar'
84-
// TODO: Remove the three httpcore5/httpclient5 exclusions below — and ideally this entire
85-
// bundlePlugin exclusion block — once analytics-engine becomes an optional dependency via the
86-
// AnalyticsFrontEndExtension SPI (opensearch-project/OpenSearch#21449). The shared-classloader
87-
// jar deduplication that requires this hand-maintained list goes away with the SPI.
88-
exclude 'httpcore5-h2-*.jar'
89-
exclude 'httpcore5-reactive-*.jar'
90-
exclude 'httpclient5-*.jar'
91-
exclude 'commons-text-*.jar'
92-
// Calcite transitive deps now bundled in analytics-engine 3.7 — exclude from sql to avoid jar hell.
93-
exclude 'commons-math3-*.jar'
94-
exclude 'commons-dbcp2-*.jar'
95-
exclude 'commons-pool2-*.jar'
96-
exclude 'uzaygezen-core-*.jar'
97-
exclude 'sketches-core-*.jar'
98-
exclude 'memory-0*.jar'
99-
exclude 'jts-io-common-*.jar'
100-
exclude 'proj4j-*.jar'
101-
exclude 'json-smart-*.jar'
102-
exclude 'accessors-smart-*.jar'
103-
exclude 'asm-9*.jar'
104-
// jsr305 is also bundled by arrow-flight-rpc (an extendedPlugins parent of analytics-engine,
105-
// which we extend); both shipping it caused jar hell at plugin install time.
106-
exclude 'jsr305-*.jar'
107-
}
108-
10963
publishing {
11064
publications {
11165
pluginZip(MavenPublication) { publication ->
@@ -207,7 +161,9 @@ dependencies {
207161

208162
api project(":ppl")
209163
api project(':api')
210-
compileOnly files("${rootDir}/libs/analytics-framework-3.7.0-SNAPSHOT.jar")
164+
// Bundled: analytics-framework interfaces must resolve even when the plugin is absent.
165+
api files("${rootDir}/libs/analytics-framework-3.7.0-SNAPSHOT.jar")
166+
// Not bundled: classes here (e.g. OpenSearchSchemaBuilder) only load when the plugin is installed.
211167
compileOnly files("${rootDir}/libs/analytics-engine-3.7.0-SNAPSHOT.jar")
212168
api project(':legacy')
213169
api project(':opensearch')

plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,23 @@ public class TransportPPLQueryAction
6262

6363
private final Supplier<Boolean> pplEnabled;
6464

65-
private final RestUnifiedQueryAction unifiedQueryHandler;
65+
/** Null when analytics-engine plugin is absent; set via {@link #setQueryPlanExecutor}. */
66+
private volatile RestUnifiedQueryAction unifiedQueryHandler;
67+
68+
private final NodeClient clientRef;
69+
private final ClusterService clusterServiceRef;
6670

67-
/** Constructor of TransportPPLQueryAction. */
6871
@Inject
6972
public TransportPPLQueryAction(
7073
TransportService transportService,
7174
ActionFilters actionFilters,
7275
NodeClient client,
7376
ClusterService clusterService,
7477
DataSourceServiceImpl dataSourceService,
75-
org.opensearch.common.settings.Settings clusterSettings,
76-
QueryPlanExecutor<RelNode, Iterable<Object[]>> queryPlanExecutor) {
78+
org.opensearch.common.settings.Settings clusterSettings) {
7779
super(PPLQueryAction.NAME, transportService, actionFilters, TransportPPLQueryRequest::new);
80+
this.clientRef = client;
81+
this.clusterServiceRef = clusterService;
7882

7983
ModulesBuilder modules = new ModulesBuilder();
8084
modules.add(new OpenSearchPluginModule());
@@ -86,9 +90,6 @@ public TransportPPLQueryAction(
8690
b.bind(DataSourceService.class).toInstance(dataSourceService);
8791
});
8892
this.injector = Guice.createInjector(modules);
89-
AnalyticsExecutorHolder.set(queryPlanExecutor);
90-
this.unifiedQueryHandler =
91-
new RestUnifiedQueryAction(client, clusterService, queryPlanExecutor);
9293
this.pplEnabled =
9394
() ->
9495
MULTI_ALLOW_EXPLICIT_INDEX.get(clusterSettings)
@@ -98,6 +99,15 @@ public TransportPPLQueryAction(
9899
.getSettingValue(Settings.Key.PPL_ENABLED);
99100
}
100101

102+
/** Invoked by Guice iff analytics-engine bound {@code QueryPlanExecutor}. */
103+
@Inject(optional = true)
104+
public void setQueryPlanExecutor(
105+
QueryPlanExecutor<RelNode, Iterable<Object[]>> queryPlanExecutor) {
106+
AnalyticsExecutorHolder.set(queryPlanExecutor);
107+
this.unifiedQueryHandler =
108+
new RestUnifiedQueryAction(clientRef, clusterServiceRef, queryPlanExecutor);
109+
}
110+
101111
/**
102112
* {@inheritDoc} Transform the request and call super.doExecute() to support call from other
103113
* plugins.
@@ -134,8 +144,9 @@ protected void doExecute(
134144
QueryContext.setProfile(transformedRequest.profile());
135145
ActionListener<TransportPPLQueryResponse> clearingListener = wrapWithProfilingClear(listener);
136146

137-
// Route to analytics engine for non-Lucene (e.g., Parquet-backed) indices
138-
if (unifiedQueryHandler.isAnalyticsIndex(transformedRequest.getRequest(), QueryType.PPL)) {
147+
// Route to analytics engine for non-Lucene (e.g., Parquet-backed) indices.
148+
if (unifiedQueryHandler != null
149+
&& unifiedQueryHandler.isAnalyticsIndex(transformedRequest.getRequest(), QueryType.PPL)) {
139150
if (transformedRequest.isExplainRequest()) {
140151
unifiedQueryHandler.explain(
141152
transformedRequest.getRequest(),

0 commit comments

Comments
 (0)