diff --git a/docs/changelog/138965.yaml b/docs/changelog/138965.yaml
new file mode 100644
index 0000000000000..4d9b14e3cccab
--- /dev/null
+++ b/docs/changelog/138965.yaml
@@ -0,0 +1,5 @@
+pr: 138965
+summary: Add `project_routing` to CLI
+area: SQL
+type: enhancement
+issues: []
diff --git a/x-pack/plugin/sql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/multi_node/CliProjectRoutingIT.java b/x-pack/plugin/sql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/multi_node/CliProjectRoutingIT.java
new file mode 100644
index 0000000000000..ee1e2876f3e4e
--- /dev/null
+++ b/x-pack/plugin/sql/qa/server/multi-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/multi_node/CliProjectRoutingIT.java
@@ -0,0 +1,21 @@
+/*
+ * 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.sql.qa.multi_node;
+
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.xpack.sql.qa.cli.ProjectRoutingTestCase;
+import org.junit.ClassRule;
+
+public class CliProjectRoutingIT extends ProjectRoutingTestCase {
+ @ClassRule
+ public static final ElasticsearchCluster cluster = SqlTestCluster.getCluster();
+
+ @Override
+ protected String getTestRestCluster() {
+ return cluster.getHttpAddresses();
+ }
+}
diff --git a/x-pack/plugin/sql/qa/server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security/CliProjectRoutingIT.java b/x-pack/plugin/sql/qa/server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security/CliProjectRoutingIT.java
new file mode 100644
index 0000000000000..0a5a733a8bd4d
--- /dev/null
+++ b/x-pack/plugin/sql/qa/server/security/src/test/java/org/elasticsearch/xpack/sql/qa/security/CliProjectRoutingIT.java
@@ -0,0 +1,38 @@
+/*
+ * 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.sql.qa.security;
+
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.xpack.sql.qa.cli.EmbeddedCli.SecurityConfig;
+import org.elasticsearch.xpack.sql.qa.cli.ProjectRoutingTestCase;
+import org.junit.ClassRule;
+
+public class CliProjectRoutingIT extends ProjectRoutingTestCase {
+ @ClassRule
+ public static ElasticsearchCluster cluster = SqlSecurityTestCluster.getCluster();
+
+ @Override
+ protected String getTestRestCluster() {
+ return cluster.getHttpAddresses();
+ }
+
+ @Override
+ protected Settings restClientSettings() {
+ return RestSqlIT.securitySettings();
+ }
+
+ @Override
+ protected String getProtocol() {
+ return RestSqlIT.SSL_ENABLED ? "https" : "http";
+ }
+
+ @Override
+ protected SecurityConfig securityConfig() {
+ return CliSecurityIT.adminSecurityConfig();
+ }
+}
diff --git a/x-pack/plugin/sql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/single_node/CliProjectRoutingIT.java b/x-pack/plugin/sql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/single_node/CliProjectRoutingIT.java
new file mode 100644
index 0000000000000..935623b48b106
--- /dev/null
+++ b/x-pack/plugin/sql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/sql/qa/single_node/CliProjectRoutingIT.java
@@ -0,0 +1,21 @@
+/*
+ * 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.sql.qa.single_node;
+
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
+import org.elasticsearch.xpack.sql.qa.cli.ProjectRoutingTestCase;
+import org.junit.ClassRule;
+
+public class CliProjectRoutingIT extends ProjectRoutingTestCase {
+ @ClassRule
+ public static final ElasticsearchCluster cluster = SqlTestCluster.getCluster();
+
+ @Override
+ protected String getTestRestCluster() {
+ return cluster.getHttpAddresses();
+ }
+}
diff --git a/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ProjectRoutingTestCase.java b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ProjectRoutingTestCase.java
new file mode 100644
index 0000000000000..e3609950f1d7e
--- /dev/null
+++ b/x-pack/plugin/sql/qa/server/src/main/java/org/elasticsearch/xpack/sql/qa/cli/ProjectRoutingTestCase.java
@@ -0,0 +1,34 @@
+/*
+ * 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.sql.qa.cli;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.matchesRegex;
+
+public abstract class ProjectRoutingTestCase extends CliIntegrationTestCase {
+
+ public void testProjectRoutingCommand() throws IOException {
+ index("test", body -> body.field("name", "foo"));
+ assertEquals("[?1l>[?1000l[?2004lproject_routing set to [90m_alias:foo[0m", command("project_routing = _alias:foo"));
+ assertThat(
+ command("SELECT * FROM test"),
+ matchesRegex(".*\\s*\\[project_routing\\] is only allowed when cross-project search is enabled\\s*.*")
+ );
+ }
+
+ public void testProjectRoutingResetCommand() throws IOException {
+ index("test", body -> body.field("name", "foo"));
+ assertEquals("[?1l>[?1000l[?2004lproject_routing set to [90m_alias:foo[0m", command("project_routing = _alias:foo"));
+ assertEquals("[?1l>[?1000l[?2004lproject_routing set to [90mnull[0m", command("project_routing = null"));
+ assertThat(command("SELECT * FROM test"), matchesRegex(".*\\s*name\\s*\\.*"));
+ assertThat(readLine(), containsString("----------"));
+ assertThat(readLine(), matchesRegex(".*\\s*foo\\s*\\.*"));
+ assertEquals("", readLine());
+ }
+}
diff --git a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/Cli.java b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/Cli.java
index 5c44afe101a58..af53e7679a07a 100644
--- a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/Cli.java
+++ b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/Cli.java
@@ -23,6 +23,7 @@
import org.elasticsearch.xpack.sql.cli.command.FetchSizeCliCommand;
import org.elasticsearch.xpack.sql.cli.command.LenientCliCommand;
import org.elasticsearch.xpack.sql.cli.command.PrintLogoCommand;
+import org.elasticsearch.xpack.sql.cli.command.ProjectRoutingCliCommand;
import org.elasticsearch.xpack.sql.cli.command.ServerInfoCliCommand;
import org.elasticsearch.xpack.sql.cli.command.ServerQueryCliCommand;
import org.elasticsearch.xpack.sql.client.ClientException;
@@ -146,6 +147,7 @@ private void execute(String uri, boolean debug, boolean binary, String keystoreL
new FetchSizeCliCommand(),
new LenientCliCommand(),
new AllowPartialResultsCliCommand(),
+ new ProjectRoutingCliCommand(),
new FetchSeparatorCliCommand(),
new ServerInfoCliCommand(),
new ServerQueryCliCommand()
diff --git a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/CliSessionConfiguration.java b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/CliSessionConfiguration.java
index 928d51c93abcd..f7bb4c180c228 100644
--- a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/CliSessionConfiguration.java
+++ b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/CliSessionConfiguration.java
@@ -18,6 +18,7 @@ public class CliSessionConfiguration {
private boolean debug;
private boolean lenient;
private boolean allowPartialResults;
+ private String projectRouting;
public CliSessionConfiguration() {
this.fetchSize = CoreProtocol.FETCH_SIZE;
@@ -67,4 +68,12 @@ public boolean allowPartialResults() {
public void setAllowPartialResults(boolean allowPartialResults) {
this.allowPartialResults = allowPartialResults;
}
+
+ public String projectRouting() {
+ return projectRouting;
+ }
+
+ public void setProjectRouting(String projectRouting) {
+ this.projectRouting = projectRouting;
+ }
}
diff --git a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ProjectRoutingCliCommand.java b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ProjectRoutingCliCommand.java
new file mode 100644
index 0000000000000..c901db1f1f4d8
--- /dev/null
+++ b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ProjectRoutingCliCommand.java
@@ -0,0 +1,34 @@
+/*
+ * 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.sql.cli.command;
+
+import org.elasticsearch.xpack.sql.cli.CliTerminal;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * allows to set project routing for cross-project search
+ */
+public class ProjectRoutingCliCommand extends AbstractCliCommand {
+
+ public ProjectRoutingCliCommand() {
+ super(Pattern.compile("project(?: |_)routing *= *(.+)", Pattern.CASE_INSENSITIVE));
+ }
+
+ @Override
+ protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
+ String val = m.group(1);
+ if (val == null || val.equals("null") || val.trim().isEmpty()) {
+ cliSession.cfg().setProjectRouting(null);
+ } else {
+ cliSession.cfg().setProjectRouting(val.trim());
+ }
+ terminal.line().text("project_routing set to ").em("" + cliSession.cfg().projectRouting()).end();
+ return true;
+ }
+}
diff --git a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommand.java b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommand.java
index 8efc920bdbc16..fb1af322c7693 100644
--- a/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommand.java
+++ b/x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommand.java
@@ -27,7 +27,7 @@ protected boolean doHandle(CliTerminal terminal, CliSession cliSession, String l
String data;
try {
CliSessionConfiguration cfg = cliSession.cfg();
- response = cliClient.basicQuery(line, cfg.getFetchSize(), cfg.isLenient(), cfg.allowPartialResults());
+ response = cliClient.basicQuery(line, cfg.getFetchSize(), cfg.isLenient(), cfg.allowPartialResults(), cfg.projectRouting());
formatter = new SimpleFormatter(response.columns(), response.rows(), CLI);
data = formatter.formatWithHeader(response.columns(), response.rows());
while (true) {
diff --git a/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/BuiltinCommandTests.java b/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/BuiltinCommandTests.java
index 1d2e86173d3a4..d810df18b5f3f 100644
--- a/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/BuiltinCommandTests.java
+++ b/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/BuiltinCommandTests.java
@@ -132,4 +132,17 @@ public void testAllowPartialSearchResults() {
testTerminal.clear();
}
+ public void testProjectRouting() {
+ TestTerminal testTerminal = new TestTerminal();
+ HttpClient httpClient = mock(HttpClient.class);
+ CliSession cliSession = new CliSession(httpClient);
+ ProjectRoutingCliCommand cliCommand = new ProjectRoutingCliCommand();
+ assertFalse(cliCommand.handle(testTerminal, cliSession, "project_routing"));
+ assertNull(cliSession.cfg().projectRouting());
+ assertTrue(cliCommand.handle(testTerminal, cliSession, "project_routing = _alias:_origin"));
+ assertEquals("_alias:_origin", cliSession.cfg().projectRouting());
+ assertEquals("project_routing set to _alias:_origin", testTerminal.toString());
+ testTerminal.clear();
+ }
+
}
diff --git a/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommandTests.java b/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommandTests.java
index 4a75a72984f43..542f352523842 100644
--- a/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommandTests.java
+++ b/x-pack/plugin/sql/sql-cli/src/test/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommandTests.java
@@ -32,11 +32,11 @@ public void testExceptionHandling() throws Exception {
TestTerminal testTerminal = new TestTerminal();
HttpClient client = mock(HttpClient.class);
CliSession cliSession = new CliSession(client);
- when(client.basicQuery("blah", 1000, false, false)).thenThrow(new SQLException("test exception"));
+ when(client.basicQuery("blah", 1000, false, false, null)).thenThrow(new SQLException("test exception"));
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
assertTrue(cliCommand.handle(testTerminal, cliSession, "blah"));
assertEquals("Bad request [test exception]\n", testTerminal.toString());
- verify(client, times(1)).basicQuery(eq("blah"), eq(1000), eq(false), eq(false));
+ verify(client, times(1)).basicQuery(eq("blah"), eq(1000), eq(false), eq(false), any());
verifyNoMoreInteractions(client);
}
@@ -45,7 +45,7 @@ public void testOnePageQuery() throws Exception {
HttpClient client = mock(HttpClient.class);
CliSession cliSession = new CliSession(client);
cliSession.cfg().setFetchSize(10);
- when(client.basicQuery("test query", 10, false, false)).thenReturn(fakeResponse("", true, "foo"));
+ when(client.basicQuery("test query", 10, false, false, null)).thenReturn(fakeResponse("", true, "foo"));
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
assertTrue(cliCommand.handle(testTerminal, cliSession, "test query"));
assertEquals("""
@@ -53,7 +53,7 @@ public void testOnePageQuery() throws Exception {
---------------
foo \s
""", testTerminal.toString());
- verify(client, times(1)).basicQuery(eq("test query"), eq(10), eq(false), eq(false));
+ verify(client, times(1)).basicQuery(eq("test query"), eq(10), eq(false), eq(false), any());
verifyNoMoreInteractions(client);
}
@@ -62,7 +62,7 @@ public void testThreePageQuery() throws Exception {
HttpClient client = mock(HttpClient.class);
CliSession cliSession = new CliSession(client);
cliSession.cfg().setFetchSize(10);
- when(client.basicQuery("test query", 10, false, false)).thenReturn(fakeResponse("my_cursor1", true, "first"));
+ when(client.basicQuery("test query", 10, false, false, null)).thenReturn(fakeResponse("my_cursor1", true, "first"));
when(client.nextPage("my_cursor1")).thenReturn(fakeResponse("my_cursor2", false, "second"));
when(client.nextPage("my_cursor2")).thenReturn(fakeResponse("", false, "third"));
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
@@ -74,7 +74,7 @@ public void testThreePageQuery() throws Exception {
second \s
third \s
""", testTerminal.toString());
- verify(client, times(1)).basicQuery(eq("test query"), eq(10), eq(false), eq(false));
+ verify(client, times(1)).basicQuery(eq("test query"), eq(10), eq(false), eq(false), any());
verify(client, times(2)).nextPage(any());
verifyNoMoreInteractions(client);
}
@@ -86,7 +86,7 @@ public void testTwoPageQueryWithSeparator() throws Exception {
cliSession.cfg().setFetchSize(15);
// Set a separator
cliSession.cfg().setFetchSeparator("-----");
- when(client.basicQuery("test query", 15, false, false)).thenReturn(fakeResponse("my_cursor1", true, "first"));
+ when(client.basicQuery("test query", 15, false, false, null)).thenReturn(fakeResponse("my_cursor1", true, "first"));
when(client.nextPage("my_cursor1")).thenReturn(fakeResponse("", false, "second"));
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
assertTrue(cliCommand.handle(testTerminal, cliSession, "test query"));
@@ -97,7 +97,7 @@ public void testTwoPageQueryWithSeparator() throws Exception {
-----
second \s
""", testTerminal.toString());
- verify(client, times(1)).basicQuery(eq("test query"), eq(15), eq(false), eq(false));
+ verify(client, times(1)).basicQuery(eq("test query"), eq(15), eq(false), eq(false), any());
verify(client, times(1)).nextPage(any());
verifyNoMoreInteractions(client);
}
@@ -107,7 +107,7 @@ public void testCursorCleanupOnError() throws Exception {
HttpClient client = mock(HttpClient.class);
CliSession cliSession = new CliSession(client);
cliSession.cfg().setFetchSize(15);
- when(client.basicQuery("test query", 15, false, false)).thenReturn(fakeResponse("my_cursor1", true, "first"));
+ when(client.basicQuery("test query", 15, false, false, null)).thenReturn(fakeResponse("my_cursor1", true, "first"));
when(client.nextPage("my_cursor1")).thenThrow(new SQLException("test exception"));
when(client.queryClose("my_cursor1", Mode.CLI)).thenReturn(true);
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
@@ -118,7 +118,7 @@ public void testCursorCleanupOnError() throws Exception {
first \s
Bad request [test exception]
""", testTerminal.toString());
- verify(client, times(1)).basicQuery(eq("test query"), eq(15), eq(false), eq(false));
+ verify(client, times(1)).basicQuery(eq("test query"), eq(15), eq(false), eq(false), any());
verify(client, times(1)).nextPage(any());
verify(client, times(1)).queryClose(eq("my_cursor1"), eq(Mode.CLI));
verifyNoMoreInteractions(client);
diff --git a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java
index cc0b6271c5912..c1e01d0f97745 100644
--- a/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java
+++ b/x-pack/plugin/sql/sql-client/src/main/java/org/elasticsearch/xpack/sql/client/HttpClient.java
@@ -85,12 +85,12 @@ public SqlQueryResponse basicQuery(String query, int fetchSize) throws SQLExcept
}
public SqlQueryResponse basicQuery(String query, int fetchSize, boolean fieldMultiValueLeniency) throws SQLException {
- return basicQuery(query, fetchSize, fieldMultiValueLeniency, cfg.allowPartialSearchResults(), cfg.projectRouting());
+ return basicQuery(query, fetchSize, fieldMultiValueLeniency, cfg.allowPartialSearchResults());
}
public SqlQueryResponse basicQuery(String query, int fetchSize, boolean fieldMultiValueLeniency, boolean allowPartialSearchResults)
throws SQLException {
- return basicQuery(query, fetchSize, fieldMultiValueLeniency, allowPartialSearchResults, cfg.projectRouting());
+ return basicQuery(query, fetchSize, fieldMultiValueLeniency, allowPartialSearchResults, null);
}
public SqlQueryResponse basicQuery(
@@ -102,6 +102,10 @@ public SqlQueryResponse basicQuery(
) throws SQLException {
// TODO allow customizing the time zone - this is what session set/reset/get should be about
// method called only from CLI
+
+ if (projectRouting == null) {
+ projectRouting = cfg.projectRouting();
+ }
SqlQueryRequest sqlRequest = new SqlQueryRequest(
query,
emptyList(),
@@ -119,6 +123,7 @@ public SqlQueryResponse basicQuery(
allowPartialSearchResults,
projectRouting
);
+
return query(sqlRequest).response();
}