Skip to content

Commit e6abd9f

Browse files
QL: Add leniency option to SQL CLI (#83795)
by default the query behaviour from SQL CLI is strict (ie. non-lenient), so queries that return multi-value fields return an error. We now add an option to allow lenient behaviour (ie. in case of multi-value fields, return the first value). This behaviour can be enabled with the following command: lenient = true
1 parent d1bd822 commit e6abd9f

File tree

17 files changed

+255
-73
lines changed

17 files changed

+255
-73
lines changed

docs/changelog/83795.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 83795
2+
summary: Add leniency option to SQL CLI
3+
area: SQL
4+
type: enhancement
5+
issues:
6+
- 67436
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.sql.qa.multi_node;
8+
9+
import org.elasticsearch.xpack.sql.qa.cli.LenientTestCase;
10+
11+
public class CliLenientIT extends LenientTestCase {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.sql.qa.security;
8+
9+
import org.elasticsearch.common.settings.Settings;
10+
import org.elasticsearch.xpack.sql.qa.cli.EmbeddedCli.SecurityConfig;
11+
import org.elasticsearch.xpack.sql.qa.cli.LenientTestCase;
12+
13+
public class CliLenientIT extends LenientTestCase {
14+
@Override
15+
protected Settings restClientSettings() {
16+
return RestSqlIT.securitySettings();
17+
}
18+
19+
@Override
20+
protected String getProtocol() {
21+
return RestSqlIT.SSL_ENABLED ? "https" : "http";
22+
}
23+
24+
@Override
25+
protected SecurityConfig securityConfig() {
26+
return CliSecurityIT.adminSecurityConfig();
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.sql.qa.single_node;
8+
9+
import org.elasticsearch.xpack.sql.qa.cli.LenientTestCase;
10+
11+
public class CliLenientIT extends LenientTestCase {}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.sql.qa.cli;
8+
9+
import org.elasticsearch.test.hamcrest.RegexMatcher;
10+
11+
import java.io.IOException;
12+
13+
import static org.hamcrest.Matchers.containsString;
14+
15+
public abstract class LenientTestCase extends CliIntegrationTestCase {
16+
17+
public void testLenientCommand() throws IOException {
18+
index("test", body -> body.field("name", "foo").field("tags", new String[] { "bar", "bar" }));
19+
assertEquals("[?1l>[?1000l[?2004llenient set to [90mtrue[0m", command("lenient = true"));
20+
assertThat(command("SELECT * FROM test"), RegexMatcher.matches("\\s*name\\s*\\|\\s*tags\\s*"));
21+
assertThat(readLine(), containsString("----------"));
22+
assertThat(readLine(), RegexMatcher.matches("\\s*foo\\s*\\|\\s*bar\\s*"));
23+
assertEquals("", readLine());
24+
}
25+
26+
public void testDefaultNoLenient() throws IOException {
27+
index("test", body -> body.field("name", "foo").field("tags", new String[] { "bar", "bar" }));
28+
assertThat(
29+
command("SELECT * FROM test"),
30+
containsString("Server encountered an error [Arrays (returned by [tags]) are not supported]")
31+
);
32+
while ("][23;31;1m][0m".equals(readLine()) == false)
33+
; // clean console to avoid failures on shutdown
34+
}
35+
36+
public void testExplicitNoLenient() throws IOException {
37+
index("test", body -> body.field("name", "foo").field("tags", new String[] { "bar", "bar" }));
38+
assertEquals("[?1l>[?1000l[?2004llenient set to [90mfalse[0m", command("lenient = false"));
39+
assertThat(
40+
command("SELECT * FROM test"),
41+
containsString("Server encountered an error [Arrays (returned by [tags]) are not supported]")
42+
);
43+
while ("][23;31;1m][0m".equals(readLine()) == false)
44+
; // clean console to avoid failures on shutdown
45+
}
46+
}

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/Cli.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.elasticsearch.xpack.sql.cli.command.CliSession;
2020
import org.elasticsearch.xpack.sql.cli.command.FetchSeparatorCliCommand;
2121
import org.elasticsearch.xpack.sql.cli.command.FetchSizeCliCommand;
22+
import org.elasticsearch.xpack.sql.cli.command.LenientCliCommand;
2223
import org.elasticsearch.xpack.sql.cli.command.PrintLogoCommand;
2324
import org.elasticsearch.xpack.sql.cli.command.ServerInfoCliCommand;
2425
import org.elasticsearch.xpack.sql.cli.command.ServerQueryCliCommand;
@@ -128,6 +129,7 @@ private void execute(String uri, boolean debug, boolean binary, String keystoreL
128129
new PrintLogoCommand(),
129130
new ClearScreenCliCommand(),
130131
new FetchSizeCliCommand(),
132+
new LenientCliCommand(),
131133
new FetchSeparatorCliCommand(),
132134
new ServerInfoCliCommand(),
133135
new ServerQueryCliCommand()
@@ -136,7 +138,7 @@ private void execute(String uri, boolean debug, boolean binary, String keystoreL
136138
ConnectionBuilder connectionBuilder = new ConnectionBuilder(cliTerminal);
137139
ConnectionConfiguration con = connectionBuilder.buildConnection(uri, keystoreLocation, binary);
138140
CliSession cliSession = new CliSession(new HttpClient(con));
139-
cliSession.setDebug(debug);
141+
cliSession.cfg().setDebug(debug);
140142
if (checkConnection) {
141143
checkConnection(cliSession, cliTerminal, con);
142144
}
@@ -150,7 +152,7 @@ private void checkConnection(CliSession cliSession, CliTerminal cliTerminal, Con
150152
try {
151153
cliSession.checkConnection();
152154
} catch (ClientException ex) {
153-
if (cliSession.isDebug()) {
155+
if (cliSession.cfg().isDebug()) {
154156
cliTerminal.error("Client Exception", ex.getMessage());
155157
cliTerminal.println();
156158
cliTerminal.printStackTrace(ex);

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/AbstractServerCliCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ protected void handleExceptionWhileCommunicatingWithServer(CliTerminal terminal,
3434
.param(e.getMessage() == null ? e.getClass().getName() : e.getMessage())
3535
.error("]")
3636
.ln();
37-
if (cliSession.isDebug()) {
37+
if (cliSession.cfg().isDebug()) {
3838
terminal.printStackTrace(e);
3939
}
4040
}

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/CliSession.java

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
import org.elasticsearch.xpack.sql.client.ClientException;
1010
import org.elasticsearch.xpack.sql.client.ClientVersion;
1111
import org.elasticsearch.xpack.sql.client.HttpClient;
12-
import org.elasticsearch.xpack.sql.proto.CoreProtocol;
1312
import org.elasticsearch.xpack.sql.proto.MainResponse;
1413
import org.elasticsearch.xpack.sql.proto.SqlVersion;
1514

@@ -20,52 +19,19 @@
2019
*/
2120
public class CliSession {
2221
private final HttpClient httpClient;
23-
private int fetchSize = CoreProtocol.FETCH_SIZE;
24-
private String fetchSeparator = "";
25-
private boolean debug;
26-
private boolean binary;
22+
private final CliSessionConfiguration configuration;
2723

2824
public CliSession(HttpClient httpClient) {
2925
this.httpClient = httpClient;
26+
this.configuration = new CliSessionConfiguration();
3027
}
3128

3229
public HttpClient getClient() {
3330
return httpClient;
3431
}
3532

36-
public void setFetchSize(int fetchSize) {
37-
if (fetchSize <= 0) {
38-
throw new IllegalArgumentException("Must be > 0.");
39-
}
40-
this.fetchSize = fetchSize;
41-
}
42-
43-
public int getFetchSize() {
44-
return fetchSize;
45-
}
46-
47-
public void setFetchSeparator(String fetchSeparator) {
48-
this.fetchSeparator = fetchSeparator;
49-
}
50-
51-
public String getFetchSeparator() {
52-
return fetchSeparator;
53-
}
54-
55-
public void setDebug(boolean debug) {
56-
this.debug = debug;
57-
}
58-
59-
public boolean isDebug() {
60-
return debug;
61-
}
62-
63-
public void setBinary(boolean binary) {
64-
this.binary = binary;
65-
}
66-
67-
public boolean isBinary() {
68-
return binary;
33+
public CliSessionConfiguration cfg() {
34+
return configuration;
6935
}
7036

7137
public void checkConnection() throws ClientException {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.sql.cli.command;
9+
10+
import org.elasticsearch.xpack.sql.proto.CoreProtocol;
11+
12+
/**
13+
* Configuration for CLI session
14+
*/
15+
public class CliSessionConfiguration {
16+
private int fetchSize;
17+
private String fetchSeparator = "";
18+
private boolean debug;
19+
private boolean lenient;
20+
21+
public CliSessionConfiguration() {
22+
this.fetchSize = CoreProtocol.FETCH_SIZE;
23+
this.lenient = CoreProtocol.FIELD_MULTI_VALUE_LENIENCY;
24+
}
25+
26+
public void setFetchSize(int fetchSize) {
27+
if (fetchSize <= 0) {
28+
throw new IllegalArgumentException("Must be > 0.");
29+
}
30+
this.fetchSize = fetchSize;
31+
}
32+
33+
public int getFetchSize() {
34+
return fetchSize;
35+
}
36+
37+
public void setFetchSeparator(String fetchSeparator) {
38+
this.fetchSeparator = fetchSeparator;
39+
}
40+
41+
public String getFetchSeparator() {
42+
return fetchSeparator;
43+
}
44+
45+
public void setDebug(boolean debug) {
46+
this.debug = debug;
47+
}
48+
49+
public boolean isDebug() {
50+
return debug;
51+
}
52+
53+
public boolean isLenient() {
54+
return lenient;
55+
}
56+
57+
public void setLenient(boolean lenient) {
58+
this.lenient = lenient;
59+
}
60+
}

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/FetchSeparatorCliCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ public FetchSeparatorCliCommand() {
2222

2323
@Override
2424
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
25-
cliSession.setFetchSeparator(m.group(1));
26-
terminal.line().text("fetch separator set to \"").em(cliSession.getFetchSeparator()).text("\"").end();
25+
cliSession.cfg().setFetchSeparator(m.group(1));
26+
terminal.line().text("fetch separator set to \"").em(cliSession.cfg().getFetchSeparator()).text("\"").end();
2727
return true;
2828
}
2929
}

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/FetchSizeCliCommand.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ public FetchSizeCliCommand() {
2323
@Override
2424
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
2525
try {
26-
cliSession.setFetchSize(Integer.parseInt(m.group(1)));
26+
cliSession.cfg().setFetchSize(Integer.parseInt(m.group(1)));
2727
} catch (NumberFormatException e) {
2828
terminal.line().error("Invalid fetch size [").param(m.group(1)).error("]").end();
2929
return true;
3030
} catch (IllegalArgumentException e) {
3131
terminal.line().error("Invalid fetch size [").param(m.group(1)).error("]. " + e.getMessage()).end();
3232
return true;
3333
}
34-
terminal.line().text("fetch size set to ").em(Integer.toString(cliSession.getFetchSize())).end();
34+
terminal.line().text("fetch size set to ").em(Integer.toString(cliSession.cfg().getFetchSize())).end();
3535
return true;
3636
}
3737
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
package org.elasticsearch.xpack.sql.cli.command;
8+
9+
import org.elasticsearch.xpack.sql.cli.CliTerminal;
10+
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
13+
14+
/**
15+
* lenient command, enables/disables fields multi-value leniency.
16+
* ie. with lenient = true, in case of array values, return the first value, with no guarantee of consistent results.
17+
*
18+
*/
19+
public class LenientCliCommand extends AbstractCliCommand {
20+
21+
public LenientCliCommand() {
22+
super(Pattern.compile("lenient *= *(.+)", Pattern.CASE_INSENSITIVE));
23+
}
24+
25+
@Override
26+
protected boolean doHandle(CliTerminal terminal, CliSession cliSession, Matcher m, String line) {
27+
cliSession.cfg().setLenient(Boolean.parseBoolean(m.group(1)));
28+
terminal.line().text("lenient set to ").em(Boolean.toString(cliSession.cfg().isLenient())).end();
29+
return true;
30+
}
31+
}

x-pack/plugin/sql/sql-cli/src/main/java/org/elasticsearch/xpack/sql/cli/command/ServerQueryCliCommand.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ protected boolean doHandle(CliTerminal terminal, CliSession cliSession, String l
2626
SimpleFormatter formatter;
2727
String data;
2828
try {
29-
response = cliClient.basicQuery(line, cliSession.getFetchSize());
29+
response = cliClient.basicQuery(line, cliSession.cfg().getFetchSize(), cliSession.cfg().isLenient());
3030
formatter = new SimpleFormatter(response.columns(), response.rows(), CLI);
3131
data = formatter.formatWithHeader(response.columns(), response.rows());
3232
while (true) {
@@ -36,8 +36,8 @@ protected boolean doHandle(CliTerminal terminal, CliSession cliSession, String l
3636
terminal.flush();
3737
return true;
3838
}
39-
if (false == cliSession.getFetchSeparator().equals("")) {
40-
terminal.println(cliSession.getFetchSeparator());
39+
if (false == cliSession.cfg().getFetchSeparator().equals("")) {
40+
terminal.println(cliSession.cfg().getFetchSeparator());
4141
}
4242
response = cliSession.getClient().nextPage(response.cursor());
4343
data = formatter.formatWithoutHeader(response.rows());

0 commit comments

Comments
 (0)