SQL: Switch CLI to core-cli's Command and make it testable (elastic/x-pack-elasticsearch#3232)
Switches CLI to use the standard Elasticsearch Command and refactors it to be more testable. It doesn't change any cli functionality except using the bright color while displaying query results. relates elastic/x-pack-elasticsearch#2881, elastic/x-pack-elasticsearch#3203, elastic/x-pack-elasticsearch#2990 Original commit: elastic/x-pack-elasticsearch@841f306d50
This commit is contained in:
parent
9664363575
commit
6839f99ed0
|
@ -21,20 +21,20 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith("With[{}]"));
|
assertThat(readLine(), startsWith("With[{}]"));
|
||||||
assertThat(readLine(), startsWith("\\_Project[[?*]]"));
|
assertThat(readLine(), startsWith("\\_Project[[?*]]"));
|
||||||
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test"), containsString("plan"));
|
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
assertThat(readLine(), startsWith("Project[[test_field{r}#"));
|
assertThat(readLine(), startsWith("Project[[test_field{r}#"));
|
||||||
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]"));
|
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]"));
|
||||||
assertThat(readLine(), startsWith(" \\_EsRelation[test][test_field{r}#"));
|
assertThat(readLine(), startsWith(" \\_EsRelation[test][test_field{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
assertThat(readLine(), startsWith("Project[[test_field{r}#"));
|
assertThat(readLine(), startsWith("Project[[test_field{r}#"));
|
||||||
assertThat(readLine(), startsWith("\\_EsRelation[test][test_field{r}#"));
|
assertThat(readLine(), startsWith("\\_EsRelation[test][test_field{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
// TODO in this case we should probably remove the source filtering entirely. Right? It costs but we don't need it.
|
// TODO in this case we should probably remove the source filtering entirely. Right? It costs but we don't need it.
|
||||||
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT * FROM test"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT * FROM test"), containsString("plan"));
|
||||||
|
@ -47,7 +47,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith(" \"excludes\" : [ ]"));
|
assertThat(readLine(), startsWith(" \"excludes\" : [ ]"));
|
||||||
assertThat(readLine(), startsWith(" }"));
|
assertThat(readLine(), startsWith(" }"));
|
||||||
assertThat(readLine(), startsWith("}]"));
|
assertThat(readLine(), startsWith("}]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testExplainWithWhere() throws IOException {
|
public void testExplainWithWhere() throws IOException {
|
||||||
|
@ -60,7 +60,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith("\\_Project[[?*]]"));
|
assertThat(readLine(), startsWith("\\_Project[[?*]]"));
|
||||||
assertThat(readLine(), startsWith(" \\_Filter[?i = 2]"));
|
assertThat(readLine(), startsWith(" \\_Filter[?i = 2]"));
|
||||||
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test WHERE i = 2"),
|
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT * FROM test WHERE i = 2"),
|
||||||
containsString("plan"));
|
containsString("plan"));
|
||||||
|
@ -69,14 +69,14 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith("\\_Filter[i{r}#"));
|
assertThat(readLine(), startsWith("\\_Filter[i{r}#"));
|
||||||
assertThat(readLine(), startsWith(" \\_SubQueryAlias[test]"));
|
assertThat(readLine(), startsWith(" \\_SubQueryAlias[test]"));
|
||||||
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test WHERE i = 2"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT * FROM test WHERE i = 2"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
assertThat(readLine(), startsWith("Project[[i{r}#"));
|
assertThat(readLine(), startsWith("Project[[i{r}#"));
|
||||||
assertThat(readLine(), startsWith("\\_Filter[i{r}#"));
|
assertThat(readLine(), startsWith("\\_Filter[i{r}#"));
|
||||||
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT * FROM test WHERE i = 2"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT * FROM test WHERE i = 2"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
|
@ -99,7 +99,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith(" \"i\""));
|
assertThat(readLine(), startsWith(" \"i\""));
|
||||||
assertThat(readLine(), startsWith(" ]"));
|
assertThat(readLine(), startsWith(" ]"));
|
||||||
assertThat(readLine(), startsWith("}]"));
|
assertThat(readLine(), startsWith("}]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testExplainWithCount() throws IOException {
|
public void testExplainWithCount() throws IOException {
|
||||||
|
@ -111,7 +111,7 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith("With[{}]"));
|
assertThat(readLine(), startsWith("With[{}]"));
|
||||||
assertThat(readLine(), startsWith("\\_Project[[?COUNT(?*)]]"));
|
assertThat(readLine(), startsWith("\\_Project[[?COUNT(?*)]]"));
|
||||||
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
assertThat(readLine(), startsWith(" \\_UnresolvedRelation[[index=test],null,Unknown index [test]]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT COUNT(*) FROM test"),
|
assertThat(command("EXPLAIN " + (randomBoolean() ? "" : "(PLAN ANALYZED) ") + "SELECT COUNT(*) FROM test"),
|
||||||
containsString("plan"));
|
containsString("plan"));
|
||||||
|
@ -119,13 +119,13 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#"));
|
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#"));
|
||||||
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]"));
|
assertThat(readLine(), startsWith("\\_SubQueryAlias[test]"));
|
||||||
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
assertThat(readLine(), startsWith(" \\_EsRelation[test][i{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT COUNT(*) FROM test"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN OPTIMIZED) SELECT COUNT(*) FROM test"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#"));
|
assertThat(readLine(), startsWith("Aggregate[[],[COUNT(1)#"));
|
||||||
assertThat(readLine(), startsWith("\\_EsRelation[test][i{r}#"));
|
assertThat(readLine(), startsWith("\\_EsRelation[test][i{r}#"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
|
|
||||||
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT COUNT(*) FROM test"), containsString("plan"));
|
assertThat(command("EXPLAIN (PLAN EXECUTABLE) SELECT COUNT(*) FROM test"), containsString("plan"));
|
||||||
assertThat(readLine(), startsWith("----------"));
|
assertThat(readLine(), startsWith("----------"));
|
||||||
|
@ -134,6 +134,6 @@ public class CliExplainIT extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), startsWith(" \"_source\" : false,"));
|
assertThat(readLine(), startsWith(" \"_source\" : false,"));
|
||||||
assertThat(readLine(), startsWith(" \"stored_fields\" : \"_none_\""));
|
assertThat(readLine(), startsWith(" \"stored_fields\" : \"_none_\""));
|
||||||
assertThat(readLine(), startsWith("}]"));
|
assertThat(readLine(), startsWith("}]"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
assertEquals("---------------+---------------+---------------", cli.readLine());
|
assertEquals("---------------+---------------+---------------", cli.readLine());
|
||||||
assertThat(cli.readLine(), containsString("1 |2 |3"));
|
assertThat(cli.readLine(), containsString("1 |2 |3"));
|
||||||
assertThat(cli.readLine(), containsString("4 |5 |6"));
|
assertThat(cli.readLine(), containsString("4 |5 |6"));
|
||||||
assertEquals("[0m", cli.readLine());
|
assertEquals("", cli.readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
for (Map.Entry<String, String> column : columns.entrySet()) {
|
for (Map.Entry<String, String> column : columns.entrySet()) {
|
||||||
assertThat(cli.readLine(), both(startsWith(column.getKey())).and(containsString("|" + column.getValue())));
|
assertThat(cli.readLine(), both(startsWith(column.getKey())).and(containsString("|" + column.getValue())));
|
||||||
}
|
}
|
||||||
assertEquals("[0m", cli.readLine());
|
assertEquals("", cli.readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ public class CliSecurityIT extends SqlSecurityTestCase {
|
||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
assertThat(cli.readLine(), containsString(table));
|
assertThat(cli.readLine(), containsString(table));
|
||||||
}
|
}
|
||||||
assertEquals("[0m", cli.readLine());
|
assertEquals("", cli.readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ public abstract class SelectTestCase extends CliIntegrationTestCase {
|
||||||
assertThat(command("SELECT * FROM test"), containsString("test_field"));
|
assertThat(command("SELECT * FROM test"), containsString("test_field"));
|
||||||
assertThat(readLine(), containsString("----------"));
|
assertThat(readLine(), containsString("----------"));
|
||||||
assertThat(readLine(), containsString("test_value"));
|
assertThat(readLine(), containsString("test_value"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSelectWithWhere() throws IOException {
|
public void testSelectWithWhere() throws IOException {
|
||||||
|
@ -26,6 +26,6 @@ public abstract class SelectTestCase extends CliIntegrationTestCase {
|
||||||
assertThat(command("SELECT * FROM test WHERE i = 2"), RegexMatcher.matches("\\s*i\\s*\\|\\s*test_field\\s*"));
|
assertThat(command("SELECT * FROM test WHERE i = 2"), RegexMatcher.matches("\\s*i\\s*\\|\\s*test_field\\s*"));
|
||||||
assertThat(readLine(), containsString("----------"));
|
assertThat(readLine(), containsString("----------"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*2\\s*\\|\\s*test_value2\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*2\\s*\\|\\s*test_value2\\s*"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), containsString("----------"));
|
assertThat(readLine(), containsString("----------"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*test[12]\\s*"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testShowFunctions() throws IOException {
|
public void testShowFunctions() throws IOException {
|
||||||
|
@ -39,7 +39,7 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
|
||||||
while (scalarFunction.matcher(line).matches()) {
|
while (scalarFunction.matcher(line).matches()) {
|
||||||
line = readLine();
|
line = readLine();
|
||||||
}
|
}
|
||||||
assertEquals("[0m", line);
|
assertEquals("", line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testShowFunctionsLikePrefix() throws IOException {
|
public void testShowFunctionsLikePrefix() throws IOException {
|
||||||
|
@ -47,7 +47,7 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), containsString("----------"));
|
assertThat(readLine(), containsString("----------"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*LOG\\s*\\|\\s*SCALAR\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*LOG\\s*\\|\\s*SCALAR\\s*"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*LOG10\\s*\\|\\s*SCALAR\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*LOG10\\s*\\|\\s*SCALAR\\s*"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testShowFunctionsLikeInfix() throws IOException {
|
public void testShowFunctionsLikeInfix() throws IOException {
|
||||||
|
@ -59,6 +59,6 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_YEAR\\s*\\|\\s*SCALAR\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*DAY_OF_YEAR\\s*\\|\\s*SCALAR\\s*"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*HOUR_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*HOUR_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
|
||||||
assertThat(readLine(), RegexMatcher.matches("\\s*MINUTE_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
|
assertThat(readLine(), RegexMatcher.matches("\\s*MINUTE_OF_DAY\\s*\\|\\s*SCALAR\\s*"));
|
||||||
assertEquals("[0m", readLine());
|
assertEquals("", readLine());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ dependencies {
|
||||||
compile project(':x-pack-elasticsearch:sql:shared-client')
|
compile project(':x-pack-elasticsearch:sql:shared-client')
|
||||||
compile project(':x-pack-elasticsearch:sql:cli-proto')
|
compile project(':x-pack-elasticsearch:sql:cli-proto')
|
||||||
compile project(':x-pack-elasticsearch:sql:shared-proto')
|
compile project(':x-pack-elasticsearch:sql:shared-proto')
|
||||||
|
compile project(':core:cli')
|
||||||
|
|
||||||
runtime "org.fusesource.jansi:jansi:1.16"
|
runtime "org.fusesource.jansi:jansi:1.16"
|
||||||
runtime "org.elasticsearch:jna:4.4.0-1"
|
runtime "org.elasticsearch:jna:4.4.0-1"
|
||||||
|
@ -19,10 +20,12 @@ dependencyLicenses {
|
||||||
mapping from: /cli-proto.*/, to: 'elasticsearch'
|
mapping from: /cli-proto.*/, to: 'elasticsearch'
|
||||||
mapping from: /shared-client.*/, to: 'elasticsearch'
|
mapping from: /shared-client.*/, to: 'elasticsearch'
|
||||||
mapping from: /shared-proto.*/, to: 'elasticsearch'
|
mapping from: /shared-proto.*/, to: 'elasticsearch'
|
||||||
|
mapping from: /elasticsearch-cli.*/, to: 'elasticsearch'
|
||||||
mapping from: /jackson-.*/, to: 'jackson'
|
mapping from: /jackson-.*/, to: 'jackson'
|
||||||
ignoreSha 'cli-proto'
|
ignoreSha 'cli-proto'
|
||||||
ignoreSha 'shared-client'
|
ignoreSha 'shared-client'
|
||||||
ignoreSha 'shared-proto'
|
ignoreSha 'shared-proto'
|
||||||
|
ignoreSha 'elasticsearch-cli'
|
||||||
}
|
}
|
||||||
|
|
||||||
forbiddenApisMain {
|
forbiddenApisMain {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
98cafc6081d5632b61be2c9e60650b64ddbc637c
|
|
@ -0,0 +1,24 @@
|
||||||
|
/*
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2004-2015 Paul R. Holser, Jr.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
|
@ -5,350 +5,82 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryResponse;
|
import joptsimple.OptionSet;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
import joptsimple.OptionSpec;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.SuppressForbidden;
|
import org.elasticsearch.cli.Command;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.JreHttpUrlConnection;
|
import org.elasticsearch.cli.ExitCodes;
|
||||||
import org.elasticsearch.xpack.sql.client.shared.StringUtils;
|
import org.elasticsearch.cli.Terminal;
|
||||||
import org.elasticsearch.xpack.sql.protocol.shared.AbstractQueryInitRequest;
|
import org.elasticsearch.cli.UserException;
|
||||||
import org.jline.reader.EndOfFileException;
|
import org.elasticsearch.xpack.sql.cli.command.ClearScreenCliCommand;
|
||||||
import org.jline.reader.LineReader;
|
import org.elasticsearch.xpack.sql.cli.command.CliCommand;
|
||||||
import org.jline.reader.LineReaderBuilder;
|
import org.elasticsearch.xpack.sql.cli.command.CliCommands;
|
||||||
import org.jline.reader.UserInterruptException;
|
import org.elasticsearch.xpack.sql.cli.command.CliSession;
|
||||||
import org.jline.terminal.Terminal;
|
import org.elasticsearch.xpack.sql.cli.command.FetchSeparatorCliCommand;
|
||||||
import org.jline.terminal.TerminalBuilder;
|
import org.elasticsearch.xpack.sql.cli.command.FetchSizeCliCommand;
|
||||||
import org.jline.utils.AttributedString;
|
import org.elasticsearch.xpack.sql.cli.command.PrintLogoCommand;
|
||||||
import org.jline.utils.AttributedStringBuilder;
|
import org.elasticsearch.xpack.sql.cli.command.ServerInfoCliCommand;
|
||||||
import org.jline.utils.InfoCmp.Capability;
|
import org.elasticsearch.xpack.sql.cli.command.ServerQueryCliCommand;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.util.Arrays;
|
||||||
import java.io.InputStreamReader;
|
import java.util.List;
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.logging.LogManager;
|
import java.util.logging.LogManager;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.parseURI;
|
public class Cli extends Command {
|
||||||
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.removeQuery;
|
private final OptionSpec<Boolean> debugOption;
|
||||||
import static org.jline.utils.AttributedStyle.BOLD;
|
private final OptionSpec<String> connectionString;
|
||||||
import static org.jline.utils.AttributedStyle.BRIGHT;
|
|
||||||
import static org.jline.utils.AttributedStyle.DEFAULT;
|
|
||||||
import static org.jline.utils.AttributedStyle.RED;
|
|
||||||
import static org.jline.utils.AttributedStyle.YELLOW;
|
|
||||||
|
|
||||||
public class Cli {
|
public Cli() {
|
||||||
public static String DEFAULT_CONNECTION_STRING = "http://localhost:9200/";
|
super("Elasticsearch SQL CLI", Cli::configureLogging);
|
||||||
public static URI DEFAULT_URI = URI.create(DEFAULT_CONNECTION_STRING);
|
this.debugOption = parser.acceptsAll(Arrays.asList("d", "debug"),
|
||||||
|
"Enable debug logging")
|
||||||
|
.withRequiredArg().ofType(Boolean.class)
|
||||||
|
.defaultsTo(Boolean.parseBoolean(System.getProperty("cli.debug", "false")));
|
||||||
|
this.connectionString = parser.nonOptions("uri");
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String... args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
/* Initialize the logger from the a properties file we bundle. This makes sure
|
final Cli cli = new Cli();
|
||||||
* we get useful error messages from jLine. */
|
int status = cli.main(args, Terminal.DEFAULT);
|
||||||
LogManager.getLogManager().readConfiguration(Cli.class.getResourceAsStream("/logging.properties"));
|
if (status != ExitCodes.OK) {
|
||||||
final URI uri;
|
exit(status);
|
||||||
final String connectionString;
|
|
||||||
Properties properties = new Properties();
|
|
||||||
String user = null;
|
|
||||||
String password = null;
|
|
||||||
if (args.length > 0) {
|
|
||||||
connectionString = args[0];
|
|
||||||
try {
|
|
||||||
uri = removeQuery(parseURI(connectionString, DEFAULT_URI), connectionString, DEFAULT_URI);
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
exit(ex.getMessage(), 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user = uri.getUserInfo();
|
|
||||||
if (user != null) {
|
|
||||||
int colonIndex = user.indexOf(':');
|
|
||||||
if (colonIndex >= 0) {
|
|
||||||
password = user.substring(colonIndex + 1);
|
|
||||||
user = user.substring(0, colonIndex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uri = DEFAULT_URI;
|
|
||||||
connectionString = DEFAULT_CONNECTION_STRING;
|
|
||||||
}
|
|
||||||
|
|
||||||
try (Terminal term = TerminalBuilder.builder().build()) {
|
|
||||||
try {
|
|
||||||
if (user != null) {
|
|
||||||
if (password == null) {
|
|
||||||
term.writer().print("password: ");
|
|
||||||
term.writer().flush();
|
|
||||||
term.echo(false);
|
|
||||||
password = new BufferedReader(term.reader()).readLine();
|
|
||||||
term.echo(true);
|
|
||||||
}
|
|
||||||
properties.setProperty(ConnectionConfiguration.AUTH_USER, user);
|
|
||||||
properties.setProperty(ConnectionConfiguration.AUTH_PASS, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean debug = StringUtils.parseBoolean(System.getProperty("cli.debug", "false"));
|
|
||||||
Cli console = new Cli(debug, new ConnectionConfiguration(uri, connectionString, properties), term);
|
|
||||||
console.run();
|
|
||||||
} catch (FatalException e) {
|
|
||||||
term.writer().println(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final boolean debug;
|
private static void configureLogging() {
|
||||||
private final Terminal term;
|
|
||||||
private final CliHttpClient cliClient;
|
|
||||||
private int fetchSize = AbstractQueryInitRequest.DEFAULT_FETCH_SIZE;
|
|
||||||
private String fetchSeparator = "";
|
|
||||||
|
|
||||||
Cli(boolean debug, ConnectionConfiguration cfg, Terminal terminal) {
|
|
||||||
this.debug = debug;
|
|
||||||
term = terminal;
|
|
||||||
cliClient = new CliHttpClient(cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void run() throws IOException {
|
|
||||||
PrintWriter out = term.writer();
|
|
||||||
|
|
||||||
LineReader reader = LineReaderBuilder.builder()
|
|
||||||
.terminal(term)
|
|
||||||
.completer(Completers.INSTANCE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String DEFAULT_PROMPT = new AttributedString("sql> ", DEFAULT.foreground(YELLOW)).toAnsi(term);
|
|
||||||
String MULTI_LINE_PROMPT = new AttributedString(" | ", DEFAULT.foreground(YELLOW)).toAnsi(term);
|
|
||||||
|
|
||||||
StringBuilder multiLine = new StringBuilder();
|
|
||||||
String prompt = DEFAULT_PROMPT;
|
|
||||||
|
|
||||||
out.flush();
|
|
||||||
printLogo();
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
String line = null;
|
|
||||||
try {
|
|
||||||
line = reader.readLine(prompt);
|
|
||||||
} catch (UserInterruptException ex) {
|
|
||||||
// ignore
|
|
||||||
} catch (EndOfFileException ex) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
line = line.trim();
|
|
||||||
|
|
||||||
if (!line.endsWith(";")) {
|
|
||||||
multiLine.append(" ");
|
|
||||||
multiLine.append(line);
|
|
||||||
prompt = MULTI_LINE_PROMPT;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.substring(0, line.length() - 1);
|
|
||||||
|
|
||||||
prompt = DEFAULT_PROMPT;
|
|
||||||
if (multiLine.length() > 0) {
|
|
||||||
// append the line without trailing ;
|
|
||||||
multiLine.append(line);
|
|
||||||
line = multiLine.toString().trim();
|
|
||||||
multiLine.setLength(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// special case to handle exit
|
|
||||||
if (isExit(line)) {
|
|
||||||
out.println(new AttributedString("Bye!", DEFAULT.foreground(BRIGHT)).toAnsi(term));
|
|
||||||
out.flush();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
boolean wasLocal = handleLocalCommand(line);
|
|
||||||
if (false == wasLocal) {
|
|
||||||
try {
|
|
||||||
if (isServerInfo(line)) {
|
|
||||||
executeServerInfo();
|
|
||||||
} else {
|
|
||||||
executeQuery(line);
|
|
||||||
}
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
handleExceptionWhileCommunicatingWithServer(e);
|
|
||||||
}
|
|
||||||
out.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
out.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle an exception while communication with the server. Extracted
|
|
||||||
* into a method so that tests can bubble the failure.
|
|
||||||
*/
|
|
||||||
protected void handleExceptionWhileCommunicatingWithServer(RuntimeException e) {
|
|
||||||
AttributedStringBuilder asb = new AttributedStringBuilder();
|
|
||||||
asb.append("Communication error [", BOLD.foreground(RED));
|
|
||||||
asb.append(e.getMessage(), DEFAULT.boldOff().italic().foreground(YELLOW));
|
|
||||||
asb.append("]", BOLD.underlineOff().foreground(RED));
|
|
||||||
term.writer().println(asb.toAnsi(term));
|
|
||||||
if (debug) {
|
|
||||||
e.printStackTrace(term.writer());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printLogo() {
|
|
||||||
term.puts(Capability.clear_screen);
|
|
||||||
try (InputStream in = Cli.class.getResourceAsStream("/logo.txt")) {
|
|
||||||
if (in == null) {
|
|
||||||
throw new FatalException("Could not find logo!");
|
|
||||||
}
|
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
term.writer().println(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new FatalException("Could not load logo!", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
term.writer().println();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Pattern LOGO_PATTERN = Pattern.compile("logo", Pattern.CASE_INSENSITIVE);
|
|
||||||
private static final Pattern CLEAR_PATTERN = Pattern.compile("cls", Pattern.CASE_INSENSITIVE);
|
|
||||||
private static final Pattern FETCH_SIZE_PATTERN = Pattern.compile("fetch(?: |_)size *= *(.+)", Pattern.CASE_INSENSITIVE);
|
|
||||||
private static final Pattern FETCH_SEPARATOR_PATTERN = Pattern.compile("fetch(?: |_)separator *= *\"(.+)\"", Pattern.CASE_INSENSITIVE);
|
|
||||||
private boolean handleLocalCommand(String line) {
|
|
||||||
Matcher m = LOGO_PATTERN.matcher(line);
|
|
||||||
if (m.matches()) {
|
|
||||||
printLogo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m = CLEAR_PATTERN.matcher(line);
|
|
||||||
if (m.matches()) {
|
|
||||||
term.puts(Capability.clear_screen);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m = FETCH_SIZE_PATTERN.matcher(line);
|
|
||||||
if (m.matches()) {
|
|
||||||
int proposedFetchSize;
|
|
||||||
try {
|
|
||||||
proposedFetchSize = fetchSize = Integer.parseInt(m.group(1));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
AttributedStringBuilder asb = new AttributedStringBuilder();
|
|
||||||
asb.append("Invalid fetch size [", BOLD.foreground(RED));
|
|
||||||
asb.append(m.group(1), DEFAULT.boldOff().italic().foreground(YELLOW));
|
|
||||||
asb.append("]", BOLD.underlineOff().foreground(RED));
|
|
||||||
term.writer().println(asb.toAnsi(term));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (proposedFetchSize <= 0) {
|
|
||||||
AttributedStringBuilder asb = new AttributedStringBuilder();
|
|
||||||
asb.append("Invalid fetch size [", BOLD.foreground(RED));
|
|
||||||
asb.append(m.group(1), DEFAULT.boldOff().italic().foreground(YELLOW));
|
|
||||||
asb.append("]. Must be > 0.", BOLD.underlineOff().foreground(RED));
|
|
||||||
term.writer().println(asb.toAnsi(term));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
this.fetchSize = proposedFetchSize;
|
|
||||||
AttributedStringBuilder asb = new AttributedStringBuilder();
|
|
||||||
asb.append("fetch size set to ", DEFAULT);
|
|
||||||
asb.append(Integer.toString(fetchSize), DEFAULT.foreground(BRIGHT));
|
|
||||||
term.writer().println(asb.toAnsi(term));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
m = FETCH_SEPARATOR_PATTERN.matcher(line);
|
|
||||||
if (m.matches()) {
|
|
||||||
fetchSeparator = m.group(1);
|
|
||||||
AttributedStringBuilder asb = new AttributedStringBuilder();
|
|
||||||
asb.append("fetch separator set to \"", DEFAULT);
|
|
||||||
asb.append(fetchSeparator, DEFAULT.foreground(BRIGHT));
|
|
||||||
asb.append("\"", DEFAULT);
|
|
||||||
term.writer().println(asb.toAnsi(term));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isServerInfo(String line) {
|
|
||||||
line = line.toLowerCase(Locale.ROOT);
|
|
||||||
return line.equals("info");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void executeServerInfo() {
|
|
||||||
try {
|
try {
|
||||||
term.writer().println(ResponseToString.toAnsi(cliClient.serverInfo()).toAnsi(term));
|
/* Initialize the logger from the a properties file we bundle. This makes sure
|
||||||
} catch (SQLException e) {
|
* we get useful error messages from jLine. */
|
||||||
error("Error fetching server info", e.getMessage());
|
LogManager.getLogManager().readConfiguration(Cli.class.getResourceAsStream("/logging.properties"));
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new RuntimeException("cannot setup logging", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isExit(String line) {
|
@Override
|
||||||
line = line.toLowerCase(Locale.ROOT);
|
protected void execute(org.elasticsearch.cli.Terminal terminal, OptionSet options) throws Exception {
|
||||||
return line.equals("exit") || line.equals("quit");
|
boolean debug = debugOption.value(options);
|
||||||
|
List<String> args = connectionString.values(options);
|
||||||
|
if (args.size() > 1) {
|
||||||
|
throw new UserException(ExitCodes.USAGE, "expecting a single uri");
|
||||||
|
}
|
||||||
|
execute(args.size() == 1 ? args.get(0) : null, debug);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void executeQuery(String line) throws IOException {
|
private void execute(String uri, boolean debug) throws Exception {
|
||||||
QueryResponse response;
|
CliCommand cliCommand = new CliCommands(
|
||||||
try {
|
new PrintLogoCommand(),
|
||||||
response = cliClient.queryInit(line, fetchSize);
|
new ClearScreenCliCommand(),
|
||||||
} catch (SQLException e) {
|
new FetchSizeCliCommand(),
|
||||||
if (JreHttpUrlConnection.SQL_STATE_BAD_SERVER.equals(e.getSQLState())) {
|
new FetchSeparatorCliCommand(),
|
||||||
error("Server error", e.getMessage());
|
new ServerInfoCliCommand(),
|
||||||
} else {
|
new ServerQueryCliCommand()
|
||||||
error("Bad request", e.getMessage());
|
);
|
||||||
}
|
try (CliTerminal cliTerminal = new JLineTerminal()) {
|
||||||
return;
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(cliTerminal);
|
||||||
|
CliSession cliSession = new CliSession(new CliHttpClient(connectionBuilder.buildConnection(uri)));
|
||||||
|
cliSession.setDebug(debug);
|
||||||
|
new CliRepl(cliTerminal, cliSession, cliCommand).execute();
|
||||||
}
|
}
|
||||||
while (true) {
|
|
||||||
term.writer().print(ResponseToString.toAnsi(response).toAnsi(term));
|
|
||||||
term.writer().flush();
|
|
||||||
if (response.cursor().isEmpty()) {
|
|
||||||
// Successfully finished the entire query!
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (false == fetchSeparator.equals("")) {
|
|
||||||
term.writer().println(fetchSeparator);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
response = cliClient.nextPage(response.cursor());
|
|
||||||
} catch (SQLException e) {
|
|
||||||
if (JreHttpUrlConnection.SQL_STATE_BAD_SERVER.equals(e.getSQLState())) {
|
|
||||||
error("Server error", e.getMessage());
|
|
||||||
} else {
|
|
||||||
error("Bad request", e.getMessage());
|
|
||||||
} return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void error(String type, String message) {
|
|
||||||
AttributedStringBuilder sb = new AttributedStringBuilder();
|
|
||||||
sb.append(type + " [", BOLD.foreground(RED));
|
|
||||||
sb.append(message, DEFAULT.boldOff().italic().foreground(YELLOW));
|
|
||||||
sb.append("]", BOLD.underlineOff().foreground(RED));
|
|
||||||
term.writer().print(sb.toAnsi(term));
|
|
||||||
}
|
|
||||||
|
|
||||||
static class FatalException extends RuntimeException {
|
|
||||||
FatalException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
FatalException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressForbidden(reason = "CLI application")
|
|
||||||
private static void exit(String message, int code) {
|
|
||||||
System.err.println(message);
|
|
||||||
System.exit(code);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import static java.lang.String.format;
|
|
||||||
|
|
||||||
@SuppressWarnings("serial")
|
|
||||||
public class CliException extends RuntimeException {
|
|
||||||
|
|
||||||
public CliException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CliException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CliException(String message, Object... args) {
|
|
||||||
super(format(Locale.ROOT, message, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public CliException(Throwable cause, String message, Object... args) {
|
|
||||||
super(format(Locale.ROOT, message, args), cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CliException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.command.CliCommand;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.command.CliSession;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class CliRepl {
|
||||||
|
|
||||||
|
private CliTerminal cliTerminal;
|
||||||
|
private CliCommand cliCommand;
|
||||||
|
private CliSession cliSession;
|
||||||
|
|
||||||
|
public CliRepl(CliTerminal cliTerminal, CliSession cliSession, CliCommand cliCommand) {
|
||||||
|
this.cliTerminal = cliTerminal;
|
||||||
|
this.cliCommand = cliCommand;
|
||||||
|
this.cliSession = cliSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void execute() {
|
||||||
|
String DEFAULT_PROMPT = "sql> ";
|
||||||
|
String MULTI_LINE_PROMPT = " | ";
|
||||||
|
|
||||||
|
StringBuilder multiLine = new StringBuilder();
|
||||||
|
String prompt = DEFAULT_PROMPT;
|
||||||
|
|
||||||
|
cliTerminal.flush();
|
||||||
|
cliCommand.handle(cliTerminal, cliSession, "logo");
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
String line = cliTerminal.readLine(prompt);
|
||||||
|
if (line == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
line = line.trim();
|
||||||
|
|
||||||
|
if (!line.endsWith(";")) {
|
||||||
|
multiLine.append(" ");
|
||||||
|
multiLine.append(line);
|
||||||
|
prompt = MULTI_LINE_PROMPT;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
line = line.substring(0, line.length() - 1);
|
||||||
|
|
||||||
|
prompt = DEFAULT_PROMPT;
|
||||||
|
if (multiLine.length() > 0) {
|
||||||
|
// append the line without trailing ;
|
||||||
|
multiLine.append(line);
|
||||||
|
line = multiLine.toString().trim();
|
||||||
|
multiLine.setLength(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// special case to handle exit
|
||||||
|
if (isExit(line)) {
|
||||||
|
cliTerminal.line().em("Bye!").ln();
|
||||||
|
cliTerminal.flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cliCommand.handle(cliTerminal, cliSession, line) == false) {
|
||||||
|
cliTerminal.error("Unrecognized command", line);
|
||||||
|
}
|
||||||
|
cliTerminal.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isExit(String line) {
|
||||||
|
line = line.toLowerCase(Locale.ROOT);
|
||||||
|
return line.equals("exit") || line.equals("quit");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a terminal endpoint
|
||||||
|
*/
|
||||||
|
public interface CliTerminal extends AutoCloseable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints line with plain text
|
||||||
|
*/
|
||||||
|
void print(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints line with plain text followed by a new line
|
||||||
|
*/
|
||||||
|
void println(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a formatted error message
|
||||||
|
*/
|
||||||
|
void error(String type, String message);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints a new line
|
||||||
|
*/
|
||||||
|
void println();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the terminal
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the terminal
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the stacktrace of the exception
|
||||||
|
*/
|
||||||
|
void printStackTrace(Exception ex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prompts the user to enter the password and returns it.
|
||||||
|
*/
|
||||||
|
String readPassword(String prompt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the line from the terminal.
|
||||||
|
*/
|
||||||
|
String readLine(String prompt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new line builder, which allows building a formatted lines.
|
||||||
|
*
|
||||||
|
* The line is not displayed until it is closed with ln() or end().
|
||||||
|
*/
|
||||||
|
LineBuilder line();
|
||||||
|
|
||||||
|
interface LineBuilder {
|
||||||
|
/**
|
||||||
|
* Adds a plain text to the line
|
||||||
|
*/
|
||||||
|
LineBuilder text(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a text with emphasis to the line
|
||||||
|
*/
|
||||||
|
LineBuilder em(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a text representing the error message
|
||||||
|
*/
|
||||||
|
LineBuilder error(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a text representing a parameter of the error message
|
||||||
|
*/
|
||||||
|
LineBuilder param(String text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds '\n' to the line and send it to the screen.
|
||||||
|
*/
|
||||||
|
void ln();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the line to the screen.
|
||||||
|
*/
|
||||||
|
void end();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.cli.UserException;
|
||||||
|
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.parseURI;
|
||||||
|
import static org.elasticsearch.xpack.sql.client.shared.UriUtils.removeQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection Builder. Can interactively ask users for the password if it is not provided
|
||||||
|
*/
|
||||||
|
public class ConnectionBuilder {
|
||||||
|
public static String DEFAULT_CONNECTION_STRING = "http://localhost:9200/";
|
||||||
|
public static URI DEFAULT_URI = URI.create(DEFAULT_CONNECTION_STRING);
|
||||||
|
|
||||||
|
private CliTerminal cliTerminal;
|
||||||
|
|
||||||
|
public ConnectionBuilder(CliTerminal cliTerminal) {
|
||||||
|
this.cliTerminal = cliTerminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionConfiguration buildConnection(String arg) throws UserException {
|
||||||
|
final URI uri;
|
||||||
|
final String connectionString;
|
||||||
|
Properties properties = new Properties();
|
||||||
|
String user = null;
|
||||||
|
String password = null;
|
||||||
|
if (arg != null) {
|
||||||
|
connectionString = arg;
|
||||||
|
uri = removeQuery(parseURI(connectionString, DEFAULT_URI), connectionString, DEFAULT_URI);
|
||||||
|
user = uri.getUserInfo();
|
||||||
|
if (user != null) {
|
||||||
|
int colonIndex = user.indexOf(':');
|
||||||
|
if (colonIndex >= 0) {
|
||||||
|
password = user.substring(colonIndex + 1);
|
||||||
|
user = user.substring(0, colonIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
uri = DEFAULT_URI;
|
||||||
|
connectionString = DEFAULT_CONNECTION_STRING;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
if (password == null) {
|
||||||
|
password = cliTerminal.readPassword("password: ");
|
||||||
|
}
|
||||||
|
properties.setProperty(ConnectionConfiguration.AUTH_USER, user);
|
||||||
|
properties.setProperty(ConnectionConfiguration.AUTH_PASS, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ConnectionConfiguration(uri, connectionString, properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throwing this except will cause the CLI to terminate
|
||||||
|
*/
|
||||||
|
public class FatalCliException extends RuntimeException {
|
||||||
|
public FatalCliException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FatalCliException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.jline.reader.EndOfFileException;
|
||||||
|
import org.jline.reader.LineReader;
|
||||||
|
import org.jline.reader.LineReaderBuilder;
|
||||||
|
import org.jline.reader.UserInterruptException;
|
||||||
|
import org.jline.terminal.Terminal;
|
||||||
|
import org.jline.terminal.TerminalBuilder;
|
||||||
|
import org.jline.utils.AttributedString;
|
||||||
|
import org.jline.utils.AttributedStringBuilder;
|
||||||
|
import org.jline.utils.InfoCmp;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.jline.utils.AttributedStyle.BOLD;
|
||||||
|
import static org.jline.utils.AttributedStyle.BRIGHT;
|
||||||
|
import static org.jline.utils.AttributedStyle.DEFAULT;
|
||||||
|
import static org.jline.utils.AttributedStyle.RED;
|
||||||
|
import static org.jline.utils.AttributedStyle.YELLOW;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* jline-based implementation of the terminal
|
||||||
|
*/
|
||||||
|
public class JLineTerminal implements CliTerminal {
|
||||||
|
|
||||||
|
private Terminal terminal;
|
||||||
|
private LineReader reader;
|
||||||
|
|
||||||
|
protected JLineTerminal() {
|
||||||
|
try {
|
||||||
|
this.terminal = TerminalBuilder.builder().build();
|
||||||
|
reader = LineReaderBuilder.builder()
|
||||||
|
.terminal(terminal)
|
||||||
|
.completer(Completers.INSTANCE)
|
||||||
|
.build();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalCliException("Cannot use terminal", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder line() {
|
||||||
|
return new LineBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String text) {
|
||||||
|
terminal.writer().print(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println(String text) {
|
||||||
|
terminal.writer().println(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String type, String message) {
|
||||||
|
AttributedStringBuilder sb = new AttributedStringBuilder();
|
||||||
|
sb.append(type + " [", BOLD.foreground(RED));
|
||||||
|
sb.append(message, DEFAULT.boldOff().italic().foreground(YELLOW));
|
||||||
|
sb.append("]", BOLD.underlineOff().foreground(RED));
|
||||||
|
terminal.writer().print(sb.toAnsi(terminal));
|
||||||
|
terminal.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println() {
|
||||||
|
terminal.writer().println();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
terminal.puts(InfoCmp.Capability.clear_screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
terminal.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace(Exception ex) {
|
||||||
|
ex.printStackTrace(terminal.writer());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readPassword(String prompt) {
|
||||||
|
terminal.writer().print(prompt);
|
||||||
|
terminal.writer().flush();
|
||||||
|
terminal.echo(false);
|
||||||
|
try {
|
||||||
|
return new BufferedReader(terminal.reader()).readLine();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
throw new FatalCliException("Error reading password", ex);
|
||||||
|
} finally {
|
||||||
|
terminal.echo(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine(String prompt) {
|
||||||
|
try {
|
||||||
|
String attributedString = new AttributedString(prompt, DEFAULT.foreground(YELLOW)).toAnsi(terminal);
|
||||||
|
return reader.readLine(attributedString);
|
||||||
|
} catch (UserInterruptException ex) {
|
||||||
|
return "";
|
||||||
|
} catch (EndOfFileException ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
terminal.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class LineBuilder implements CliTerminal.LineBuilder {
|
||||||
|
AttributedStringBuilder line;
|
||||||
|
|
||||||
|
private LineBuilder() {
|
||||||
|
line = new AttributedStringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineBuilder text(String text) {
|
||||||
|
line.append(text, DEFAULT);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineBuilder em(String text) {
|
||||||
|
line.append(text, DEFAULT.foreground(BRIGHT));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public LineBuilder error(String text) {
|
||||||
|
line.append(text, BOLD.foreground(RED));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LineBuilder param(String text) {
|
||||||
|
line.append(text, DEFAULT.italic().foreground(YELLOW));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ln() {
|
||||||
|
terminal.writer().println(line.toAnsi(terminal));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void end() {
|
||||||
|
terminal.writer().print(line.toAnsi(terminal));
|
||||||
|
terminal.writer().flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
|
||||||
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.InfoResponse;
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.Proto.ResponseType;
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryResponse;
|
|
||||||
import org.elasticsearch.xpack.sql.protocol.shared.Response;
|
|
||||||
import org.jline.utils.AttributedStringBuilder;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
|
|
||||||
import static org.jline.utils.AttributedStyle.BRIGHT;
|
|
||||||
import static org.jline.utils.AttributedStyle.DEFAULT;
|
|
||||||
import static org.jline.utils.AttributedStyle.WHITE;
|
|
||||||
|
|
||||||
abstract class ResponseToString {
|
|
||||||
|
|
||||||
static AttributedStringBuilder toAnsi(Response response) {
|
|
||||||
AttributedStringBuilder sb = new AttributedStringBuilder();
|
|
||||||
|
|
||||||
switch ((ResponseType) response.responseType()) {
|
|
||||||
case QUERY_INIT:
|
|
||||||
case QUERY_PAGE:
|
|
||||||
QueryResponse cmd = (QueryResponse) response;
|
|
||||||
if (cmd.data != null) {
|
|
||||||
String data = cmd.data.toString();
|
|
||||||
if (data.startsWith("digraph ")) {
|
|
||||||
sb.append(handleGraphviz(data), DEFAULT.foreground(WHITE));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
sb.append(data, DEFAULT.foreground(WHITE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb;
|
|
||||||
case INFO:
|
|
||||||
InfoResponse info = (InfoResponse) response;
|
|
||||||
sb.append("Node:", DEFAULT.foreground(BRIGHT));
|
|
||||||
sb.append(info.node, DEFAULT.foreground(WHITE));
|
|
||||||
sb.append(" Cluster:", DEFAULT.foreground(BRIGHT));
|
|
||||||
sb.append(info.cluster, DEFAULT.foreground(WHITE));
|
|
||||||
sb.append(" Version:", DEFAULT.foreground(BRIGHT));
|
|
||||||
sb.append(info.versionString, DEFAULT.foreground(WHITE));
|
|
||||||
return sb;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("Unsupported response: " + response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String handleGraphviz(String str) {
|
|
||||||
try {
|
|
||||||
// save the content to a temp file
|
|
||||||
Path dotTempFile = Files.createTempFile(Paths.get("."), "sql-gv", ".dot");
|
|
||||||
Files.write(dotTempFile, str.getBytes(StandardCharsets.UTF_8));
|
|
||||||
// run graphviz on it (dot needs to be on the file path)
|
|
||||||
//Desktop desktop = Desktop.getDesktop();
|
|
||||||
//File f = dotTempFile.toFile();
|
|
||||||
//desktop.open(f);
|
|
||||||
//f.deleteOnExit();
|
|
||||||
return "Saved graph file at " + dotTempFile;
|
|
||||||
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return "Cannot save graph file; " + ex.getMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for simple commands that match the pattern
|
||||||
|
*/
|
||||||
|
public abstract class AbstractCliCommand implements CliCommand {
|
||||||
|
|
||||||
|
protected final Pattern pattern;
|
||||||
|
|
||||||
|
AbstractCliCommand(Pattern pattern) {
|
||||||
|
this.pattern = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(CliTerminal terminal, CliSession cliSession, String line) {
|
||||||
|
Matcher matcher = pattern.matcher(line);
|
||||||
|
if (matcher.matches()) {
|
||||||
|
return doHandle(terminal, cliSession, matcher, line);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the perform the command
|
||||||
|
* returns true if the command handled the line and false otherwise
|
||||||
|
*/
|
||||||
|
protected abstract boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
public abstract class AbstractServerCliCommand implements CliCommand {
|
||||||
|
|
||||||
|
public AbstractServerCliCommand() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean handle(CliTerminal terminal, CliSession cliSession, String line) {
|
||||||
|
try {
|
||||||
|
return doHandle(terminal, cliSession, line);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
handleExceptionWhileCommunicatingWithServer(terminal, cliSession, e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean doHandle(CliTerminal cliTerminal, CliSession cliSession, String line);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an exception while communication with the server. Extracted
|
||||||
|
* into a method so that tests can bubble the failure.
|
||||||
|
*/
|
||||||
|
protected void handleExceptionWhileCommunicatingWithServer(CliTerminal terminal, CliSession cliSession, RuntimeException e) {
|
||||||
|
terminal.line().error("Communication error [").param(e.getMessage()).error("]").ln();
|
||||||
|
if (cliSession.isDebug()) {
|
||||||
|
terminal.printStackTrace(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* cls command that cleans the screen
|
||||||
|
*/
|
||||||
|
public class ClearScreenCliCommand extends AbstractCliCommand {
|
||||||
|
|
||||||
|
public ClearScreenCliCommand() {
|
||||||
|
super(Pattern.compile("cls", Pattern.CASE_INSENSITIVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
|
||||||
|
terminal.clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
public interface CliCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the command, return true if the command is handled, false otherwise
|
||||||
|
*/
|
||||||
|
boolean handle(CliTerminal terminal, CliSession cliSession, String line);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for several commands
|
||||||
|
*/
|
||||||
|
public class CliCommands implements CliCommand {
|
||||||
|
|
||||||
|
private final List<CliCommand> commands;
|
||||||
|
|
||||||
|
public CliCommands(CliCommand... commands) {
|
||||||
|
this.commands = Arrays.asList(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(CliTerminal terminal, CliSession cliSession, String line) {
|
||||||
|
for (CliCommand cliCommand : commands) {
|
||||||
|
if (cliCommand.handle(terminal, cliSession, line)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliHttpClient;
|
||||||
|
import org.elasticsearch.xpack.sql.protocol.shared.AbstractQueryInitRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores information about the current session
|
||||||
|
*/
|
||||||
|
public class CliSession {
|
||||||
|
private final CliHttpClient cliHttpClient;
|
||||||
|
private int fetchSize = AbstractQueryInitRequest.DEFAULT_FETCH_SIZE;
|
||||||
|
private String fetchSeparator = "";
|
||||||
|
private boolean debug;
|
||||||
|
|
||||||
|
public CliSession(CliHttpClient cliHttpClient) {
|
||||||
|
this.cliHttpClient = cliHttpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CliHttpClient getClient() {
|
||||||
|
return cliHttpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFetchSize(int fetchSize) {
|
||||||
|
if (fetchSize <= 0) {
|
||||||
|
throw new IllegalArgumentException("Must be > 0.");
|
||||||
|
}
|
||||||
|
this.fetchSize = fetchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getFetchSize() {
|
||||||
|
return fetchSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFetchSeparator(String fetchSeparator) {
|
||||||
|
this.fetchSeparator = fetchSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFetchSeparator() {
|
||||||
|
return fetchSeparator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDebug(boolean debug) {
|
||||||
|
this.debug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebug() {
|
||||||
|
return debug;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch_separator command that allows to change the separator string between fetches
|
||||||
|
*/
|
||||||
|
public class FetchSeparatorCliCommand extends AbstractCliCommand {
|
||||||
|
|
||||||
|
public FetchSeparatorCliCommand() {
|
||||||
|
super(Pattern.compile("fetch(?: |_)separator *= *\"(.+)\"", Pattern.CASE_INSENSITIVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
|
||||||
|
cliSession.setFetchSeparator(m.group(1));
|
||||||
|
terminal.line().text("fetch separator set to \"").em(cliSession.getFetchSeparator()).text("\"").end();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch_size command that allows to change the size of fetches
|
||||||
|
*/
|
||||||
|
public class FetchSizeCliCommand extends AbstractCliCommand {
|
||||||
|
|
||||||
|
public FetchSizeCliCommand() {
|
||||||
|
super(Pattern.compile("fetch(?: |_)size *= *(.+)", Pattern.CASE_INSENSITIVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
|
||||||
|
try {
|
||||||
|
cliSession.setFetchSize(Integer.parseInt(m.group(1)));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
terminal.line().error("Invalid fetch size [").param(m.group(1)).error("]").end();
|
||||||
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
terminal.line().error("Invalid fetch size [").param(m.group(1)).error("]. " + e.getMessage()).end();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
terminal.line().text("fetch size set to ").em(Integer.toString(cliSession.getFetchSize())).end();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.Cli;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.FatalCliException;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logo command that cleans the screen and prints the logo
|
||||||
|
*/
|
||||||
|
public class PrintLogoCommand extends AbstractCliCommand {
|
||||||
|
|
||||||
|
public PrintLogoCommand() {
|
||||||
|
super(Pattern.compile("logo", Pattern.CASE_INSENSITIVE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
|
||||||
|
printLogo(terminal);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void printLogo(CliTerminal terminal) {
|
||||||
|
terminal.clear();
|
||||||
|
try (InputStream in = Cli.class.getResourceAsStream("/logo.txt")) {
|
||||||
|
if (in == null) {
|
||||||
|
throw new FatalCliException("Could not find logo!");
|
||||||
|
}
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
terminal.println(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new FatalCliException("Could not load logo!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
terminal.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.net.protocol.InfoResponse;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class ServerInfoCliCommand extends AbstractServerCliCommand {
|
||||||
|
|
||||||
|
public ServerInfoCliCommand() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean doHandle(CliTerminal terminal, CliSession cliSession, String line) {
|
||||||
|
if (false == "info".equals(line.toLowerCase(Locale.ROOT))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
InfoResponse info;
|
||||||
|
try {
|
||||||
|
info = cliSession.getClient().serverInfo();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
terminal.error("Error fetching server info", e.getMessage());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
terminal.line()
|
||||||
|
.text("Node:").em(info.node)
|
||||||
|
.text(" Cluster:").em(info.cluster)
|
||||||
|
.text(" Version:").em(info.versionString)
|
||||||
|
.ln();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliTerminal;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryResponse;
|
||||||
|
import org.elasticsearch.xpack.sql.client.shared.JreHttpUrlConnection;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
public class ServerQueryCliCommand extends AbstractServerCliCommand {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, String line) {
|
||||||
|
QueryResponse response;
|
||||||
|
try {
|
||||||
|
response = cliSession.getClient().queryInit(line, cliSession.getFetchSize());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (JreHttpUrlConnection.SQL_STATE_BAD_SERVER.equals(e.getSQLState())) {
|
||||||
|
terminal.error("Server error", e.getMessage());
|
||||||
|
} else {
|
||||||
|
terminal.error("Bad request", e.getMessage());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (response.data.startsWith("digraph ")) {
|
||||||
|
handleGraphviz(terminal, response.data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
handleText(terminal, response.data);
|
||||||
|
if (response.cursor().isEmpty()) {
|
||||||
|
// Successfully finished the entire query!
|
||||||
|
terminal.flush();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (false == cliSession.getFetchSeparator().equals("")) {
|
||||||
|
terminal.println(cliSession.getFetchSeparator());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
response = cliSession.getClient().nextPage(response.cursor());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
if (JreHttpUrlConnection.SQL_STATE_BAD_SERVER.equals(e.getSQLState())) {
|
||||||
|
terminal.error("Server error", e.getMessage());
|
||||||
|
} else {
|
||||||
|
terminal.error("Bad request", e.getMessage());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleText(CliTerminal terminal, String str) {
|
||||||
|
terminal.print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleGraphviz(CliTerminal terminal, String str) {
|
||||||
|
try {
|
||||||
|
// save the content to a temp file
|
||||||
|
Path dotTempFile = Files.createTempFile(Paths.get("."), "sql-gv", ".dot");
|
||||||
|
Files.write(dotTempFile, str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
terminal.println("Saved graph file at " + dotTempFile);
|
||||||
|
} catch (IOException ex) {
|
||||||
|
terminal.error("Cannot save graph file ", ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.command.CliCommand;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.command.CliSession;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class CliReplTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testBasicCliFunctionality() throws Exception {
|
||||||
|
CliTerminal cliTerminal = new TestTerminal(
|
||||||
|
"test;",
|
||||||
|
"notest;",
|
||||||
|
"exit;"
|
||||||
|
);
|
||||||
|
CliSession mockSession = mock(CliSession.class);
|
||||||
|
CliCommand mockCommand = mock(CliCommand.class);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "logo")).thenReturn(true);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "test")).thenReturn(true);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "notest")).thenReturn(false);
|
||||||
|
|
||||||
|
CliRepl cli = new CliRepl(cliTerminal, mockSession, mockCommand);
|
||||||
|
cli.execute();
|
||||||
|
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "test");
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "logo");
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "notest");
|
||||||
|
verifyNoMoreInteractions(mockCommand, mockSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testFatalCliExceptionHandling() throws Exception {
|
||||||
|
CliTerminal cliTerminal = new TestTerminal(
|
||||||
|
"test;",
|
||||||
|
"fail;"
|
||||||
|
);
|
||||||
|
|
||||||
|
CliSession mockSession = mock(CliSession.class);
|
||||||
|
CliCommand mockCommand = mock(CliCommand.class);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "logo")).thenReturn(true);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "test")).thenReturn(true);
|
||||||
|
when(mockCommand.handle(cliTerminal, mockSession, "fail")).thenThrow(new FatalCliException("die"));
|
||||||
|
|
||||||
|
CliRepl cli = new CliRepl(cliTerminal, mockSession, mockCommand);
|
||||||
|
expectThrows(FatalCliException.class, cli::execute);
|
||||||
|
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "logo");
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "test");
|
||||||
|
verify(mockCommand, times(1)).handle(cliTerminal, mockSession, "fail");
|
||||||
|
verifyNoMoreInteractions(mockCommand, mockSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.client.shared.ConnectionConfiguration;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ConnectionBuilderTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testDefaultConnection() throws Exception {
|
||||||
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||||
|
ConnectionConfiguration con = connectionBuilder.buildConnection(null);
|
||||||
|
assertNull(con.authUser());
|
||||||
|
assertNull(con.authPass());
|
||||||
|
assertEquals("http://localhost:9200/", con.connectionString());
|
||||||
|
assertEquals(URI.create("http://localhost:9200/"), con.baseUri());
|
||||||
|
assertEquals(30000, con.connectTimeout());
|
||||||
|
assertEquals(60000, con.networkTimeout());
|
||||||
|
assertEquals(45000, con.pageTimeout());
|
||||||
|
assertEquals(90000, con.queryTimeout());
|
||||||
|
assertEquals(1000, con.pageSize());
|
||||||
|
verifyNoMoreInteractions(testTerminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testBasicConnection() throws Exception {
|
||||||
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||||
|
ConnectionConfiguration con = connectionBuilder.buildConnection("http://foobar:9242/");
|
||||||
|
assertNull(con.authUser());
|
||||||
|
assertNull(con.authPass());
|
||||||
|
assertEquals("http://foobar:9242/", con.connectionString());
|
||||||
|
assertEquals(URI.create("http://foobar:9242/"), con.baseUri());
|
||||||
|
verifyNoMoreInteractions(testTerminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUserAndPasswordConnection() throws Exception {
|
||||||
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||||
|
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user:pass@foobar:9242/");
|
||||||
|
assertEquals("user", con.authUser());
|
||||||
|
assertEquals("pass", con.authPass());
|
||||||
|
assertEquals("http://user:pass@foobar:9242/", con.connectionString());
|
||||||
|
assertEquals(URI.create("http://foobar:9242/"), con.baseUri());
|
||||||
|
verifyNoMoreInteractions(testTerminal);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testUserInteractiveConnection() throws Exception {
|
||||||
|
CliTerminal testTerminal = mock(CliTerminal.class);
|
||||||
|
when(testTerminal.readPassword("password: ")).thenReturn("password");
|
||||||
|
ConnectionBuilder connectionBuilder = new ConnectionBuilder(testTerminal);
|
||||||
|
ConnectionConfiguration con = connectionBuilder.buildConnection("http://user@foobar:9242/");
|
||||||
|
assertEquals("user", con.authUser());
|
||||||
|
assertEquals("password", con.authPass());
|
||||||
|
assertEquals("http://user@foobar:9242/", con.connectionString());
|
||||||
|
assertEquals(URI.create("http://foobar:9242/"), con.baseUri());
|
||||||
|
verify(testTerminal, times(1)).readPassword(any());
|
||||||
|
verifyNoMoreInteractions(testTerminal);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.sql.cli;
|
|
||||||
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryInitResponse;
|
|
||||||
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryPageResponse;
|
|
||||||
import org.jline.terminal.Terminal;
|
|
||||||
import org.jline.utils.AttributedStringBuilder;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
public class ResponseToStringTests extends ESTestCase {
|
|
||||||
public void testQueryInitResponse() {
|
|
||||||
AttributedStringBuilder s = ResponseToString.toAnsi(new QueryInitResponse(123, "", "some command response"));
|
|
||||||
assertEquals("some command response", unstyled(s));
|
|
||||||
assertEquals("[37msome command response[0m", fullyStyled(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testQueryPageResponse() {
|
|
||||||
AttributedStringBuilder s = ResponseToString.toAnsi(new QueryPageResponse(123, "", "some command response"));
|
|
||||||
assertEquals("some command response", unstyled(s));
|
|
||||||
assertEquals("[37msome command response[0m", fullyStyled(s));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String unstyled(AttributedStringBuilder s) {
|
|
||||||
Terminal dumb = mock(Terminal.class);
|
|
||||||
when(dumb.getType()).thenReturn(Terminal.TYPE_DUMB);
|
|
||||||
return s.toAnsi(dumb);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String fullyStyled(AttributedStringBuilder s) {
|
|
||||||
return s
|
|
||||||
// toAnsi without an argument returns fully styled
|
|
||||||
.toAnsi()
|
|
||||||
// replace the escape character because they do not show up in the exception message
|
|
||||||
.replace("\u001B", "");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class TestTerminal implements CliTerminal {
|
||||||
|
|
||||||
|
private StringBuilder stringBuilder = new StringBuilder();
|
||||||
|
private boolean closed = false;
|
||||||
|
private Iterator<String> inputLines;
|
||||||
|
|
||||||
|
public TestTerminal(String ... inputLines) {
|
||||||
|
this.inputLines = Arrays.asList(inputLines).iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder line() {
|
||||||
|
return new LineBuilder() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder text(String text) {
|
||||||
|
stringBuilder.append(text);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder em(String text) {
|
||||||
|
stringBuilder.append("<em>").append(text).append("</em>");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder error(String text) {
|
||||||
|
stringBuilder.append("<b>").append(text).append("</b>");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LineBuilder param(String text) {
|
||||||
|
stringBuilder.append("<i>").append(text).append("</i>");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void ln() {
|
||||||
|
stringBuilder.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void end() {
|
||||||
|
stringBuilder.append("<flush/>");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void print(String text) {
|
||||||
|
stringBuilder.append(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println(String text) {
|
||||||
|
stringBuilder.append(text);
|
||||||
|
stringBuilder.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void error(String type, String message) {
|
||||||
|
stringBuilder.append("<b>").append(type).append(" [</b>");
|
||||||
|
stringBuilder.append("<i>").append(message).append("</i>");
|
||||||
|
stringBuilder.append("<b>]</b>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void println() {
|
||||||
|
stringBuilder.append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
stringBuilder = new StringBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
stringBuilder.append("<flush/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void printStackTrace(Exception ex) {
|
||||||
|
stringBuilder.append("<stack/>");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readPassword(String prompt) {
|
||||||
|
return "password";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine(String prompt) {
|
||||||
|
assertTrue(inputLines.hasNext());
|
||||||
|
return inputLines.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
assertFalse(closed);
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return stringBuilder.toString();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliHttpClient;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.TestTerminal;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
|
||||||
|
public class BuiltinCommandTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testInvalidCommand() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
assertFalse(new ClearScreenCliCommand().handle(testTerminal, cliSession, "something"));
|
||||||
|
assertFalse(new FetchSeparatorCliCommand().handle(testTerminal, cliSession, "something"));
|
||||||
|
assertFalse(new FetchSizeCliCommand().handle(testTerminal, cliSession, "something"));
|
||||||
|
assertFalse(new PrintLogoCommand().handle(testTerminal, cliSession, "something"));
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testClearScreen() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
testTerminal.print("not clean");
|
||||||
|
assertTrue(new ClearScreenCliCommand().handle(testTerminal, cliSession, "cls"));
|
||||||
|
assertEquals("", testTerminal.toString());
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFetchSeparator() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
FetchSeparatorCliCommand cliCommand = new FetchSeparatorCliCommand();
|
||||||
|
assertFalse(cliCommand.handle(testTerminal, cliSession, "fetch"));
|
||||||
|
assertEquals("", cliSession.getFetchSeparator());
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch_separator = \"foo\""));
|
||||||
|
assertEquals("foo", cliSession.getFetchSeparator());
|
||||||
|
assertEquals("fetch separator set to \"<em>foo</em>\"<flush/>", testTerminal.toString());
|
||||||
|
testTerminal.clear();
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch_separator=\"bar\""));
|
||||||
|
assertEquals("bar", cliSession.getFetchSeparator());
|
||||||
|
assertEquals("fetch separator set to \"<em>bar</em>\"<flush/>", testTerminal.toString());
|
||||||
|
testTerminal.clear();
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch separator=\"baz\""));
|
||||||
|
assertEquals("baz", cliSession.getFetchSeparator());
|
||||||
|
assertEquals("fetch separator set to \"<em>baz</em>\"<flush/>", testTerminal.toString());
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testFetchSize() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
FetchSizeCliCommand cliCommand = new FetchSizeCliCommand();
|
||||||
|
assertFalse(cliCommand.handle(testTerminal, cliSession, "fetch"));
|
||||||
|
assertEquals(1000L, cliSession.getFetchSize());
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch_size = \"foo\""));
|
||||||
|
assertEquals(1000L, cliSession.getFetchSize());
|
||||||
|
assertEquals("<b>Invalid fetch size [</b><i>\"foo\"</i><b>]</b><flush/>", testTerminal.toString());
|
||||||
|
testTerminal.clear();
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch_size = 10"));
|
||||||
|
assertEquals(10L, cliSession.getFetchSize());
|
||||||
|
assertEquals("fetch size set to <em>10</em><flush/>", testTerminal.toString());
|
||||||
|
|
||||||
|
testTerminal.clear();
|
||||||
|
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "fetch_size = -10"));
|
||||||
|
assertEquals(10L, cliSession.getFetchSize());
|
||||||
|
assertEquals("<b>Invalid fetch size [</b><i>-10</i><b>]. Must be > 0.</b><flush/>", testTerminal.toString());
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testPrintLogo() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
testTerminal.print("not clean");
|
||||||
|
assertTrue(new PrintLogoCommand().handle(testTerminal, cliSession, "logo"));
|
||||||
|
assertThat(testTerminal.toString(), containsString("SQL"));
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliHttpClient;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.TestTerminal;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
public class CliCommandsTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testCliCommands() {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient cliHttpClient = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(cliHttpClient);
|
||||||
|
CliCommands cliCommands = new CliCommands(
|
||||||
|
(terminal, session, line) -> line.equals("foo"),
|
||||||
|
(terminal, session, line) -> line.equals("bar"),
|
||||||
|
(terminal, session, line) -> line.equals("baz")
|
||||||
|
);
|
||||||
|
|
||||||
|
assertTrue(cliCommands.handle(testTerminal, cliSession, "foo"));
|
||||||
|
assertTrue(cliCommands.handle(testTerminal, cliSession, "bar"));
|
||||||
|
assertTrue(cliCommands.handle(testTerminal, cliSession, "baz"));
|
||||||
|
assertFalse(cliCommands.handle(testTerminal, cliSession, ""));
|
||||||
|
assertFalse(cliCommands.handle(testTerminal, cliSession, "something"));
|
||||||
|
verifyNoMoreInteractions(cliHttpClient);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliHttpClient;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.TestTerminal;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.net.protocol.InfoResponse;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ServerInfoCliCommandTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testInvalidCommand() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
ServerInfoCliCommand cliCommand = new ServerInfoCliCommand();
|
||||||
|
assertFalse(cliCommand.handle(testTerminal, cliSession, "blah"));
|
||||||
|
assertEquals(testTerminal.toString(), "");
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testShowInfo() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
when(client.serverInfo()).thenReturn(new InfoResponse("my_node", "my_cluster", (byte) 1, (byte) 2, "v1.2", "1234", "Sep 1, 2017"));
|
||||||
|
ServerInfoCliCommand cliCommand = new ServerInfoCliCommand();
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "info"));
|
||||||
|
assertEquals(testTerminal.toString(), "Node:<em>my_node</em> Cluster:<em>my_cluster</em> Version:<em>v1.2</em>\n");
|
||||||
|
verify(client, times(1)).serverInfo();
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.sql.cli.command;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.CliHttpClient;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.TestTerminal;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryInitResponse;
|
||||||
|
import org.elasticsearch.xpack.sql.cli.net.protocol.QueryPageResponse;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class ServerQueryCliCommandTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testExceptionHandling() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
when(client.queryInit("blah", 1000)).thenThrow(new SQLException("test exception"));
|
||||||
|
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "blah"));
|
||||||
|
assertEquals("<b>Bad request [</b><i>test exception</i><b>]</b>\n", testTerminal.toString());
|
||||||
|
verify(client, times(1)).queryInit(eq("blah"), eq(1000));
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testOnePageQuery() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
cliSession.setFetchSize(10);
|
||||||
|
when(client.queryInit("test query", 10)).thenReturn(new QueryInitResponse(123, "", "some command response"));
|
||||||
|
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "test query"));
|
||||||
|
assertEquals("some command response<flush/>", testTerminal.toString());
|
||||||
|
verify(client, times(1)).queryInit(eq("test query"), eq(10));
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testThreePageQuery() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
cliSession.setFetchSize(10);
|
||||||
|
when(client.queryInit("test query", 10)).thenReturn(new QueryInitResponse(123, "my_cursor1", "first"));
|
||||||
|
when(client.nextPage("my_cursor1")).thenReturn(new QueryPageResponse(345, "my_cursor2", "second"));
|
||||||
|
when(client.nextPage("my_cursor2")).thenReturn(new QueryPageResponse(678, "", "third"));
|
||||||
|
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "test query"));
|
||||||
|
assertEquals("firstsecondthird<flush/>", testTerminal.toString());
|
||||||
|
verify(client, times(1)).queryInit(eq("test query"), eq(10));
|
||||||
|
verify(client, times(2)).nextPage(any());
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTwoPageQueryWithSeparator() throws Exception {
|
||||||
|
TestTerminal testTerminal = new TestTerminal();
|
||||||
|
CliHttpClient client = mock(CliHttpClient.class);
|
||||||
|
CliSession cliSession = new CliSession(client);
|
||||||
|
cliSession.setFetchSize(15);
|
||||||
|
// Set a separator
|
||||||
|
cliSession.setFetchSeparator("-----");
|
||||||
|
when(client.queryInit("test query", 15)).thenReturn(new QueryInitResponse(123, "my_cursor1", "first"));
|
||||||
|
when(client.nextPage("my_cursor1")).thenReturn(new QueryPageResponse(345, "", "second"));
|
||||||
|
ServerQueryCliCommand cliCommand = new ServerQueryCliCommand();
|
||||||
|
assertTrue(cliCommand.handle(testTerminal, cliSession, "test query"));
|
||||||
|
assertEquals("first-----\nsecond<flush/>", testTerminal.toString());
|
||||||
|
verify(client, times(1)).queryInit(eq("test query"), eq(15));
|
||||||
|
verify(client, times(1)).nextPage(any());
|
||||||
|
verifyNoMoreInteractions(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue