diff --git a/api/src/main/java/org/opensearch/sql/api/UnifiedQueryContext.java b/api/src/main/java/org/opensearch/sql/api/UnifiedQueryContext.java
index 52b840388df..8df9c519f50 100644
--- a/api/src/main/java/org/opensearch/sql/api/UnifiedQueryContext.java
+++ b/api/src/main/java/org/opensearch/sql/api/UnifiedQueryContext.java
@@ -7,6 +7,7 @@
import static org.opensearch.sql.common.setting.Settings.Key.CALCITE_ENGINE_ENABLED;
import static org.opensearch.sql.common.setting.Settings.Key.PPL_JOIN_SUBSEARCH_MAXOUT;
+import static org.opensearch.sql.common.setting.Settings.Key.PPL_REX_MAX_MATCH_LIMIT;
import static org.opensearch.sql.common.setting.Settings.Key.PPL_SUBSEARCH_MAXOUT;
import static org.opensearch.sql.common.setting.Settings.Key.QUERY_SIZE_LIMIT;
@@ -127,6 +128,15 @@ public static class Builder {
* org.opensearch.sql.api.parser.PPLQueryParser} reuses the v2 {@code AstBuilder}, which gates
* Calcite-only commands (e.g. {@code visitTableCommand}) on this setting; without the default,
* those commands fail at parse time even when the cluster setting is true.
+ *
+ *
{@link Settings.Key#PPL_REX_MAX_MATCH_LIMIT} defaults to {@code 10} here because {@code
+ * AstBuilder.visitRexCommand} reads it unconditionally and unboxes to {@code int} — a {@code
+ * null} return from {@code getSettingValue} NPEs the planner before any operator-level
+ * capability check runs. The value mirrors the cluster-side default of {@code 10} registered by
+ * {@code OpenSearchSettings.PPL_REX_MAX_MATCH_LIMIT_SETTING}. Cluster-side overrides reach this
+ * map via {@link #setting(String, Object)} — the REST handler reads the live value from {@code
+ * OpenSearchSettings} and routes it through that existing API, keeping {@link
+ * UnifiedQueryContext} decoupled from any specific {@link Settings} implementation.
*/
private final Map settings =
new HashMap(
@@ -134,7 +144,8 @@ public static class Builder {
QUERY_SIZE_LIMIT, SysLimit.DEFAULT.querySizeLimit(),
PPL_SUBSEARCH_MAXOUT, SysLimit.DEFAULT.subsearchLimit(),
PPL_JOIN_SUBSEARCH_MAXOUT, SysLimit.DEFAULT.joinSubsearchLimit(),
- CALCITE_ENGINE_ENABLED, true));
+ CALCITE_ENGINE_ENABLED, true,
+ PPL_REX_MAX_MATCH_LIMIT, 10));
/**
* Sets the query language frontend to be used.
@@ -223,6 +234,7 @@ public UnifiedQueryContext build() {
case SQL -> UnifiedSqlSpec.extended();
case PPL -> UnifiedPplSpec.create();
};
+
Settings settings = buildSettings();
CalcitePlanContext planContext =
CalcitePlanContext.create(
diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryContextTest.java b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryContextTest.java
index ad2eba0fea5..f0111d06363 100644
--- a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryContextTest.java
+++ b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryContextTest.java
@@ -33,6 +33,10 @@ public void testContextCreationWithDefaults() {
"Settings should have default system limits",
SysLimit.DEFAULT,
SysLimit.fromSettings(context.getSettings()));
+ assertEquals(
+ "PPL_REX_MAX_MATCH_LIMIT default should be 10",
+ Integer.valueOf(10),
+ context.getSettings().getSettingValue(PPL_REX_MAX_MATCH_LIMIT));
}
@Test
@@ -43,10 +47,15 @@ public void testContextCreationWithCustomConfig() {
.catalog("opensearch", testSchema)
.cacheMetadata(true)
.setting("plugins.query.size_limit", 200)
+ .setting("plugins.ppl.rex.max_match.limit", 5)
.build();
Integer querySizeLimit = context.getSettings().getSettingValue(QUERY_SIZE_LIMIT);
assertEquals("Custom setting should be applied", Integer.valueOf(200), querySizeLimit);
+ assertEquals(
+ "Cluster-side override for PPL_REX_MAX_MATCH_LIMIT should reach the unified path",
+ Integer.valueOf(5),
+ context.getSettings().getSettingValue(PPL_REX_MAX_MATCH_LIMIT));
}
@Test(expected = IllegalArgumentException.class)
diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java b/plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java
index 41a6d8c486e..e7dd3dbc776 100644
--- a/plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java
+++ b/plugin/src/main/java/org/opensearch/sql/plugin/SQLPlugin.java
@@ -208,7 +208,8 @@ private BiFunction createSqlAnalyticsRout
if (executor == null) {
return null;
}
- cached[0] = new RestUnifiedQueryAction(client, clusterService, executor);
+ cached[0] =
+ new RestUnifiedQueryAction(client, clusterService, executor, pluginSettings);
}
return cached[0];
};
diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryAction.java b/plugin/src/main/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryAction.java
index acd50ac8b1f..0531cbde516 100644
--- a/plugin/src/main/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryAction.java
+++ b/plugin/src/main/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryAction.java
@@ -59,14 +59,17 @@ public class RestUnifiedQueryAction {
private final AnalyticsExecutionEngine analyticsEngine;
private final NodeClient client;
private final ClusterService clusterService;
+ private final org.opensearch.sql.common.setting.Settings pluginSettings;
public RestUnifiedQueryAction(
NodeClient client,
ClusterService clusterService,
- QueryPlanExecutor> planExecutor) {
+ QueryPlanExecutor> planExecutor,
+ org.opensearch.sql.common.setting.Settings pluginSettings) {
this.client = client;
this.clusterService = clusterService;
this.analyticsEngine = new AnalyticsExecutionEngine(planExecutor);
+ this.pluginSettings = pluginSettings;
}
/**
@@ -154,19 +157,42 @@ public void explain(
* Build a lightweight context for parsing only (index name extraction). Does not require cluster
* state or catalog schema.
*/
- private static UnifiedQueryContext buildParsingContext(QueryType queryType) {
- return UnifiedQueryContext.builder().language(queryType).build();
+ private UnifiedQueryContext buildParsingContext(QueryType queryType) {
+ return applyClusterOverrides(UnifiedQueryContext.builder().language(queryType)).build();
}
private UnifiedQueryContext buildContext(QueryType queryType, boolean profiling) {
- return UnifiedQueryContext.builder()
- .language(queryType)
- .catalog(SCHEMA_NAME, OpenSearchSchemaBuilder.buildSchema(clusterService.state()))
- .defaultNamespace(SCHEMA_NAME)
- .profiling(profiling)
+ return applyClusterOverrides(
+ UnifiedQueryContext.builder()
+ .language(queryType)
+ .catalog(SCHEMA_NAME, OpenSearchSchemaBuilder.buildSchema(clusterService.state()))
+ .defaultNamespace(SCHEMA_NAME)
+ .profiling(profiling))
.build();
}
+ /**
+ * Routes operator-configured cluster overrides into the builder via the existing {@code
+ * setting(String, Object)} API, keeping {@link UnifiedQueryContext} decoupled from any specific
+ * {@link org.opensearch.sql.common.setting.Settings} implementation.
+ *
+ * Currently scoped to {@code plugins.ppl.rex.max_match.limit} — required so the unified path
+ * honors {@code _cluster/settings} updates for {@code rex max_match} (CalciteRexCommandIT's
+ * testRexMaxMatchConfigurableLimit). Add keys here if a future PR / IT depends on cluster-side
+ * fidelity for one of the other planning settings.
+ */
+ private UnifiedQueryContext.Builder applyClusterOverrides(UnifiedQueryContext.Builder builder) {
+ Object rexLimit =
+ pluginSettings.getSettingValue(
+ org.opensearch.sql.common.setting.Settings.Key.PPL_REX_MAX_MATCH_LIMIT);
+ if (rexLimit != null) {
+ builder.setting(
+ org.opensearch.sql.common.setting.Settings.Key.PPL_REX_MAX_MATCH_LIMIT.getKeyValue(),
+ rexLimit);
+ }
+ return builder;
+ }
+
/**
* Extract the source index name by parsing the query and visiting the AST to find the Relation
* node. Uses the context's parser which supports both PPL and SQL.
diff --git a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java
index c148e836d86..365f1b26815 100644
--- a/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java
+++ b/plugin/src/main/java/org/opensearch/sql/plugin/transport/TransportPPLQueryAction.java
@@ -67,6 +67,7 @@ public class TransportPPLQueryAction
private final NodeClient clientRef;
private final ClusterService clusterServiceRef;
+ private final org.opensearch.sql.common.setting.Settings pluginSettingsRef;
@Inject
public TransportPPLQueryAction(
@@ -82,11 +83,13 @@ public TransportPPLQueryAction(
ModulesBuilder modules = new ModulesBuilder();
modules.add(new OpenSearchPluginModule());
+ org.opensearch.sql.common.setting.Settings pluginSettings =
+ new OpenSearchSettings(clusterService.getClusterSettings());
+ this.pluginSettingsRef = pluginSettings;
modules.add(
b -> {
b.bind(NodeClient.class).toInstance(client);
- b.bind(org.opensearch.sql.common.setting.Settings.class)
- .toInstance(new OpenSearchSettings(clusterService.getClusterSettings()));
+ b.bind(org.opensearch.sql.common.setting.Settings.class).toInstance(pluginSettings);
b.bind(DataSourceService.class).toInstance(dataSourceService);
});
this.injector = Guice.createInjector(modules);
@@ -105,7 +108,8 @@ public void setQueryPlanExecutor(
QueryPlanExecutor> queryPlanExecutor) {
AnalyticsExecutorHolder.set(queryPlanExecutor);
this.unifiedQueryHandler =
- new RestUnifiedQueryAction(clientRef, clusterServiceRef, queryPlanExecutor);
+ new RestUnifiedQueryAction(
+ clientRef, clusterServiceRef, queryPlanExecutor, pluginSettingsRef);
}
/**
diff --git a/plugin/src/test/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryActionTest.java b/plugin/src/test/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryActionTest.java
index c05a34128d6..60e0b0bf767 100644
--- a/plugin/src/test/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryActionTest.java
+++ b/plugin/src/test/java/org/opensearch/sql/plugin/rest/RestUnifiedQueryActionTest.java
@@ -14,6 +14,7 @@
import org.junit.Test;
import org.opensearch.analytics.exec.QueryPlanExecutor;
import org.opensearch.cluster.service.ClusterService;
+import org.opensearch.sql.common.setting.Settings;
import org.opensearch.sql.executor.QueryType;
import org.opensearch.transport.client.node.NodeClient;
@@ -30,7 +31,8 @@ public void setUp() {
@SuppressWarnings("unchecked")
QueryPlanExecutor> executor = mock(QueryPlanExecutor.class);
action =
- new RestUnifiedQueryAction(mock(NodeClient.class), mock(ClusterService.class), executor);
+ new RestUnifiedQueryAction(
+ mock(NodeClient.class), mock(ClusterService.class), executor, mock(Settings.class));
}
@Test