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(); }