From 10c0f85b903801333cdccf151e4226de02c58558 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Tue, 25 Nov 2025 14:37:10 +0100 Subject: [PATCH 1/3] ES|QL: Add project routing to the configuration --- .../_nightly/esql/QueryPlanningBenchmark.java | 3 ++- .../benchmark/compute/operator/EvalBenchmark.java | 3 ++- .../xpack/esql/ConfigurationBuilder.java | 11 ++++++++++- .../xpack/esql/ConfigurationTestUtils.java | 3 ++- .../elasticsearch/xpack/esql/EsqlTestUtils.java | 3 ++- .../esql/capabilities/ConfigurationAware.java | 3 ++- .../xpack/esql/session/Configuration.java | 15 +++++++++++++-- .../xpack/esql/session/EsqlSession.java | 3 ++- .../function/scalar/date/DayNameTests.java | 3 ++- .../function/scalar/date/MonthNameTests.java | 3 ++- .../xpack/esql/planner/EvalMapperTests.java | 3 ++- .../esql/planner/LocalExecutionPlannerTests.java | 3 ++- .../session/ConfigurationSerializationTests.java | 3 ++- 13 files changed, 45 insertions(+), 14 deletions(-) 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 dd12ce85b3bd8..ebbb12f33d009 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 @@ -92,7 +92,8 @@ public void setup() { System.nanoTime(), false, AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), - AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.get(Settings.EMPTY), + null ); var fields = 10_000; 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 4c0995bae53c5..cc678b65cf37c 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 @@ -373,7 +373,8 @@ private static Configuration configuration() { 0, false, AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + null ); } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationBuilder.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationBuilder.java index 611b16ad52bea..88b6bf0f147b1 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationBuilder.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationBuilder.java @@ -43,6 +43,8 @@ public class ConfigurationBuilder { private Map> tables; private long queryStartTimeNanos; + private String projectRouting; + public ConfigurationBuilder(Configuration configuration) { clusterName = configuration.clusterName(); username = configuration.username(); @@ -58,6 +60,7 @@ public ConfigurationBuilder(Configuration configuration) { allowPartialResults = configuration.allowPartialResults(); tables = configuration.tables(); queryStartTimeNanos = configuration.queryStartTimeNanos(); + projectRouting = configuration.projectRouting(); } public ConfigurationBuilder clusterName(String clusterName) { @@ -130,6 +133,11 @@ public ConfigurationBuilder queryStartTimeNanos(long queryStartTimeNanos) { return this; } + public ConfigurationBuilder projectRouting(String projectRouting) { + this.projectRouting = projectRouting; + return this; + } + public Configuration build() { return new Configuration( zoneId, @@ -145,7 +153,8 @@ public Configuration build() { queryStartTimeNanos, allowPartialResults, resultTruncationMaxSizeTimeseries, - resultTruncationDefaultSizeTimeseries + resultTruncationDefaultSizeTimeseries, + projectRouting ); } } diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationTestUtils.java b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationTestUtils.java index af784827be003..8a57e74fa5035 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationTestUtils.java +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/java/org/elasticsearch/xpack/esql/ConfigurationTestUtils.java @@ -76,7 +76,8 @@ public static Configuration randomConfiguration(String query, Map> tables; private final long queryStartTimeNanos; + private final String projectRouting; public Configuration( ZoneId zi, @@ -75,7 +76,8 @@ public Configuration( long queryStartTimeNanos, boolean allowPartialResults, int resultTruncationMaxSizeTimeseries, - int resultTruncationDefaultSizeTimeseries + int resultTruncationDefaultSizeTimeseries, + String projectRouting ) { this.zoneId = zi.normalized(); this.now = ZonedDateTime.now(Clock.tick(Clock.system(zoneId), Duration.ofNanos(1))); @@ -93,6 +95,7 @@ public Configuration( assert tables != null; this.queryStartTimeNanos = queryStartTimeNanos; this.allowPartialResults = allowPartialResults; + this.projectRouting = projectRouting; } public Configuration(BlockStreamInput in) throws IOException { @@ -120,6 +123,9 @@ public Configuration(BlockStreamInput in) throws IOException { this.resultTruncationMaxSizeTimeseries = this.resultTruncationMaxSizeRegular; this.resultTruncationDefaultSizeTimeseries = this.resultTruncationDefaultSizeRegular; } + + // not needed on the data nodes for now + this.projectRouting = null; } @Override @@ -233,7 +239,8 @@ public Configuration withoutTables() { queryStartTimeNanos, allowPartialResults, resultTruncationMaxSizeTimeseries, - resultTruncationDefaultSizeTimeseries + resultTruncationDefaultSizeTimeseries, + projectRouting ); } @@ -252,6 +259,10 @@ public boolean allowPartialResults() { return allowPartialResults; } + public String projectRouting() { + return projectRouting; + } + private static void writeQuery(StreamOutput out, String query) throws IOException { if (query.length() > QUERY_COMPRESS_THRESHOLD_CHARS) { // compare on chars to avoid UTF-8 encoding unless actually required out.writeBoolean(true); 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 38df20aa07277..bad0ca2bd7eb7 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 @@ -202,7 +202,8 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P System.nanoTime(), request.allowPartialResults(), clusterSettings.timeseriesResultTruncationMaxSize(), - clusterSettings.timeseriesResultTruncationDefaultSize() + clusterSettings.timeseriesResultTruncationDefaultSize(), + statement.setting(QuerySettings.PROJECT_ROUTING) ); FoldContext foldContext = configuration.newFoldContext(); 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 4fe528a12216c..7adb91e571403 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 @@ -154,7 +154,8 @@ private Configuration configWithZoneAndLocale(ZoneId zone, Locale locale) { System.nanoTime(), randomBoolean(), AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + null ); } } 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 698d36232f665..4889fb8db4a83 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 @@ -154,7 +154,8 @@ private Configuration configWithZoneAndLocale(ZoneId zone, Locale locale) { System.nanoTime(), randomBoolean(), AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(Settings.EMPTY), - AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(Settings.EMPTY), + null ); } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java index 92622106d7c3c..b60a9867630d6 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/planner/EvalMapperTests.java @@ -82,7 +82,8 @@ public class EvalMapperTests extends ESTestCase { System.nanoTime(), false, 10000000, - 100000 + 100000, + null ); @ParametersFactory(argumentFormatting = "%1$s") 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 d773c070af50e..d86d6747e2981 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 @@ -370,7 +370,8 @@ private Configuration config() { System.nanoTime(), randomBoolean(), AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_MAX_SIZE.getDefault(null), - AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null) + AnalyzerSettings.QUERY_TIMESERIES_RESULT_TRUNCATION_DEFAULT_SIZE.getDefault(null), + null ); } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/ConfigurationSerializationTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/ConfigurationSerializationTests.java index 18cc5970aafa9..3a2d6e619ea69 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/ConfigurationSerializationTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/session/ConfigurationSerializationTests.java @@ -104,7 +104,8 @@ protected Configuration mutateInstance(Configuration in) { System.nanoTime(), randomBoolean(), in.resultTruncationMaxSize(true), - in.resultTruncationDefaultSize(true) + in.resultTruncationDefaultSize(true), + null ); } } From b5fc9e73fa5a618eb27f18005e895bcab31f94a0 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Wed, 26 Nov 2025 10:42:38 +0100 Subject: [PATCH 2/3] Add project_routing to the request body --- .../xpack/esql/action/EsqlQueryRequest.java | 9 +++++++++ .../xpack/esql/action/RequestXContent.java | 2 ++ .../xpack/esql/session/EsqlSession.java | 13 ++++++++++++- .../xpack/esql/action/EsqlQueryRequestTests.java | 11 +++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java index abe36554d3d13..fcbd2eeb1ba98 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequest.java @@ -59,6 +59,7 @@ public class EsqlQueryRequest extends org.elasticsearch.xpack.core.esql.action.E private boolean onSnapshotBuild = Build.current().isSnapshot(); private boolean acceptedPragmaRisks = false; private Boolean allowPartialResults = null; + private String projectRouting; /** * "Tables" provided in the request for use with things like {@code LOOKUP}. @@ -317,4 +318,12 @@ void onSnapshotBuild(boolean onSnapshotBuild) { void acceptedPragmaRisks(boolean accepted) { this.acceptedPragmaRisks = accepted; } + + public void projectRouting(String projectRouting) { + this.projectRouting = projectRouting; + } + + public String projectRouting() { + return projectRouting; + } } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RequestXContent.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RequestXContent.java index f5b6e4da75fbe..8f62f760f5cdd 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RequestXContent.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/action/RequestXContent.java @@ -90,6 +90,7 @@ String fields() { static final ParseField WAIT_FOR_COMPLETION_TIMEOUT = new ParseField("wait_for_completion_timeout"); static final ParseField KEEP_ALIVE = new ParseField("keep_alive"); static final ParseField KEEP_ON_COMPLETION = new ParseField("keep_on_completion"); + static final ParseField PROJECT_ROUTING = new ParseField("project_routing"); private static final ObjectParser SYNC_PARSER = objectParserSync(() -> syncEsqlQueryRequest(null)); private static final ObjectParser ASYNC_PARSER = objectParserAsync(() -> asyncEsqlQueryRequest(null)); @@ -121,6 +122,7 @@ private static void objectParserCommon(ObjectParser parser) parser.declareString((request, localeTag) -> request.locale(Locale.forLanguageTag(localeTag)), LOCALE_FIELD); parser.declareBoolean(EsqlQueryRequest::profile, PROFILE_FIELD); parser.declareField((p, r, c) -> new ParseTables(r, p).parseTables(), TABLES_FIELD, ObjectParser.ValueType.OBJECT); + parser.declareString(EsqlQueryRequest::projectRouting, PROJECT_ROUTING); } private static ObjectParser objectParserSync(Supplier supplier) { 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 bad0ca2bd7eb7..341897c51f210 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 @@ -203,7 +203,7 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P request.allowPartialResults(), clusterSettings.timeseriesResultTruncationMaxSize(), clusterSettings.timeseriesResultTruncationDefaultSize(), - statement.setting(QuerySettings.PROJECT_ROUTING) + projectRouting(request, executionInfo, statement) ); FoldContext foldContext = configuration.newFoldContext(); @@ -259,6 +259,17 @@ public void onResponse(Versioned analyzedPlan) { ); } + private static String projectRouting(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, EsqlStatement statement) { + String projectRouting = statement.setting(QuerySettings.PROJECT_ROUTING); + if (projectRouting == null) { + projectRouting = request.projectRouting(); + } + if (projectRouting != null && executionInfo.isCrossClusterSearch() == false) { + throw new VerificationException("[project_routing] is only allowed when cross-project search is enabled"); + } + return projectRouting; + } + /** * Execute an analyzed plan. Most code should prefer calling {@link #execute} but * this is public for testing. diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java index b295d04242043..3f655958820cc 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/action/EsqlQueryRequestTests.java @@ -64,6 +64,7 @@ import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasSize; +import static org.hamcrest.Matchers.is; public class EsqlQueryRequestTests extends ESTestCase { @@ -877,6 +878,16 @@ public void testTask() throws IOException { assertThat(json, equalTo(expected)); } + public void testProjectRouting() throws IOException { + String json = """ + { + "query": "FROM test", + "project_routing": "_alias:_origin" + }"""; + EsqlQueryRequest request = parseEsqlQueryRequest(json, randomBoolean()); + assertThat(request.projectRouting(), is("_alias:_origin")); + } + private List randomParameters() { if (randomBoolean()) { return Collections.emptyList(); From 75bab616720cb469231d5a516104f5ac4f84fc44 Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Wed, 26 Nov 2025 17:42:42 +0100 Subject: [PATCH 3/3] Fix cps detection --- .../org/elasticsearch/xpack/esql/session/EsqlSession.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 341897c51f210..87cae2e561c47 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 @@ -203,7 +203,7 @@ public void execute(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, P request.allowPartialResults(), clusterSettings.timeseriesResultTruncationMaxSize(), clusterSettings.timeseriesResultTruncationDefaultSize(), - projectRouting(request, executionInfo, statement) + projectRouting(request, statement) ); FoldContext foldContext = configuration.newFoldContext(); @@ -259,12 +259,13 @@ public void onResponse(Versioned analyzedPlan) { ); } - private static String projectRouting(EsqlQueryRequest request, EsqlExecutionInfo executionInfo, EsqlStatement statement) { + private String projectRouting(EsqlQueryRequest request, EsqlStatement statement) { String projectRouting = statement.setting(QuerySettings.PROJECT_ROUTING); if (projectRouting == null) { projectRouting = request.projectRouting(); } - if (projectRouting != null && executionInfo.isCrossClusterSearch() == false) { + + if (projectRouting != null && crossProjectModeDecider.crossProjectEnabled() == false) { throw new VerificationException("[project_routing] is only allowed when cross-project search is enabled"); } return projectRouting;