Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL: binary communication implementation for drivers and the CLI #48261

Merged
merged 12 commits into from
Oct 31, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Tests updates
astefan committed Oct 18, 2019
commit 92a2b9d0ab1b858ba1e10f979630cfa315bfe642
Original file line number Diff line number Diff line change
@@ -11,13 +11,11 @@
import org.elasticsearch.client.RestClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.NotEqualMessageBuilder;
import org.elasticsearch.test.rest.ESRestTestCase;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.UnsupportedCharsetException;
import java.sql.JDBCType;
import java.util.HashMap;
@@ -27,6 +25,7 @@
import static java.util.Collections.singletonList;
import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.mode;
import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.randomMode;
import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.toMap;
import static org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase.SQL_QUERY_REST_ENDPOINT;
import static org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase.columnInfo;

@@ -101,9 +100,7 @@ private void createTestData(int documents) throws UnsupportedCharsetException, I
}

private Map<String, Object> responseToMap(Response response) throws IOException {
try (InputStream content = response.getEntity().getContent()) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
}
return toMap(response, "plain");
}

private void assertCount(RestClient client, int count) throws IOException {
@@ -114,7 +111,7 @@ private void assertCount(RestClient client, int count) throws IOException {

Request request = new Request("POST", SQL_QUERY_REST_ENDPOINT);
request.setJsonEntity("{\"query\": \"SELECT COUNT(*) FROM test\"" + mode(mode) + "}");
Map<String, Object> actual = responseToMap(client.performRequest(request));
Map<String, Object> actual = toMap(client.performRequest(request), mode);

if (false == expected.equals(actual)) {
NotEqualMessageBuilder message = new NotEqualMessageBuilder();
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@
import org.elasticsearch.client.ResponseException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.cbor.CborXContent;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.NotEqualMessageBuilder;
import org.hamcrest.Matcher;
@@ -70,10 +71,10 @@ public void expectScrollMatchesAdmin(String adminSql, String user, String userSq
String mode = randomMode();
Map<String, Object> adminResponse = runSql(null,
new StringEntity("{\"query\": \"" + adminSql + "\", \"fetch_size\": 1" + mode(mode) + "}",
ContentType.APPLICATION_JSON));
ContentType.APPLICATION_JSON), mode);
Map<String, Object> otherResponse = runSql(user,
new StringEntity("{\"query\": \"" + adminSql + "\", \"fetch_size\": 1" + mode(mode) + "}",
ContentType.APPLICATION_JSON));
ContentType.APPLICATION_JSON), mode);

String adminCursor = (String) adminResponse.remove("cursor");
String otherCursor = (String) otherResponse.remove("cursor");
@@ -82,9 +83,9 @@ public void expectScrollMatchesAdmin(String adminSql, String user, String userSq
assertResponse(adminResponse, otherResponse);
while (true) {
adminResponse = runSql(null,
new StringEntity("{\"cursor\": \"" + adminCursor + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON));
new StringEntity("{\"cursor\": \"" + adminCursor + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON), mode);
otherResponse = runSql(user,
new StringEntity("{\"cursor\": \"" + otherCursor + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON));
new StringEntity("{\"cursor\": \"" + otherCursor + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON), mode);
adminCursor = (String) adminResponse.remove("cursor");
otherCursor = (String) otherResponse.remove("cursor");
assertResponse(adminResponse, otherResponse);
@@ -179,18 +180,18 @@ public void checkNoMonitorMain(String user) throws Exception {
}

private static Map<String, Object> runSql(@Nullable String asUser, String mode, String sql) throws IOException {
return runSql(asUser, new StringEntity("{\"query\": \"" + sql + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON));
return runSql(asUser, new StringEntity("{\"query\": \"" + sql + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON), mode);
}

private static Map<String, Object> runSql(@Nullable String asUser, HttpEntity entity) throws IOException {
private static Map<String, Object> runSql(@Nullable String asUser, HttpEntity entity, String mode) throws IOException {
Request request = new Request("POST", SQL_QUERY_REST_ENDPOINT);
if (asUser != null) {
RequestOptions.Builder options = request.getOptions().toBuilder();
options.addHeader("es-security-runas-user", asUser);
request.setOptions(options);
}
request.setEntity(entity);
return toMap(client().performRequest(request));
return toMap(client().performRequest(request), mode);
}

private static void assertResponse(Map<String, Object> expected, Map<String, Object> actual) {
@@ -201,9 +202,13 @@ private static void assertResponse(Map<String, Object> expected, Map<String, Obj
}
}

private static Map<String, Object> toMap(Response response) throws IOException {
private static Map<String, Object> toMap(Response response, String mode) throws IOException {
try (InputStream content = response.getEntity().getContent()) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
if (mode.equalsIgnoreCase("jdbc")) {
astefan marked this conversation as resolved.
Show resolved Hide resolved
return XContentHelper.convertToMap(CborXContent.cborXContent, content, false);
} else {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
}
}
}
}
@@ -226,15 +231,17 @@ protected AuditLogAsserter createAuditLogAsserter() {
public void testHijackScrollFails() throws Exception {
createUser("full_access", "rest_minimal");

String mode = randomMode();
Map<String, Object> adminResponse = RestActions.runSql(null,
new StringEntity("{\"query\": \"SELECT * FROM test\", \"fetch_size\": 1" + mode(randomMode()) + "}",
ContentType.APPLICATION_JSON));
new StringEntity("{\"query\": \"SELECT * FROM test\", \"fetch_size\": 1" + mode(mode) + "}",
ContentType.APPLICATION_JSON), mode);

String cursor = (String) adminResponse.remove("cursor");
assertNotNull(cursor);

final String m = randomMode();
ResponseException e = expectThrows(ResponseException.class, () -> RestActions.runSql("full_access",
new StringEntity("{\"cursor\":\"" + cursor + "\"" + mode(randomMode()) + "}", ContentType.APPLICATION_JSON)));
new StringEntity("{\"cursor\":\"" + cursor + "\"" + mode(m) + "}", ContentType.APPLICATION_JSON), m));
// TODO return a better error message for bad scrolls
assertThat(e.getMessage(), containsString("No search context found for id"));
assertEquals(404, e.getResponse().getStatusLine().getStatusCode());
Original file line number Diff line number Diff line change
@@ -6,16 +6,13 @@

package org.elasticsearch.xpack.sql.qa.security;

import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.NotEqualMessageBuilder;
import org.elasticsearch.test.rest.ESRestTestCase;
@@ -25,7 +22,6 @@
import org.junit.rules.TestName;

import java.io.IOException;
import java.io.InputStream;
import java.sql.JDBCType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,6 +32,7 @@

import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.mode;
import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.randomMode;
import static org.elasticsearch.xpack.sql.qa.rest.BaseRestSqlTestCase.toMap;
import static org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase.SQL_QUERY_REST_ENDPOINT;
import static org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase.columnInfo;

@@ -174,18 +171,14 @@ private void deleteUser(String name) throws IOException {
}

private Map<String, Object> runSql(String asUser, String mode, String sql) throws IOException {
return runSql(asUser, new StringEntity("{\"query\": \"" + sql + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON));
}

private Map<String, Object> runSql(String asUser, HttpEntity entity) throws IOException {
Request request = new Request("POST", SQL_QUERY_REST_ENDPOINT);
if (asUser != null) {
RequestOptions.Builder options = request.getOptions().toBuilder();
options.addHeader("es-security-runas-user", asUser);
request.setOptions(options);
}
request.setEntity(entity);
return toMap(client().performRequest(request));
request.setEntity(new StringEntity("{\"query\": \"" + sql + "\"" + mode(mode) + "}", ContentType.APPLICATION_JSON));
return toMap(client().performRequest(request), mode);
}

private void assertResponse(Map<String, Object> expected, Map<String, Object> actual) {
@@ -195,12 +188,6 @@ private void assertResponse(Map<String, Object> expected, Map<String, Object> ac
fail("Response does not match:\n" + message.toString());
}
}

private static Map<String, Object> toMap(Response response) throws IOException {
try (InputStream content = response.getEntity().getContent()) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
}
}

private void index(String... docs) throws IOException {
Request request = new Request("POST", "/test/_bulk");
Original file line number Diff line number Diff line change
@@ -5,8 +5,6 @@
*/
package org.elasticsearch.xpack.sql.qa.single_node;

import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.elasticsearch.xpack.sql.qa.rest.RestSqlTestCase;

import java.io.IOException;
@@ -22,28 +20,22 @@ public class RestSqlIT extends RestSqlTestCase {

public void testErrorMessageForTranslatingQueryWithWhereEvaluatingToFalse() throws IOException {
index("{\"foo\":1}");
expectBadRequest(() -> runSql(
new StringEntity("{\"query\":\"SELECT * FROM test WHERE foo = 1 AND foo = 2\"}",
ContentType.APPLICATION_JSON), "/translate/"),
expectBadRequest(() -> runTranslateSql("{\"query\":\"SELECT * FROM test WHERE foo = 1 AND foo = 2\"}"),
containsString("Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates " +
"to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: " +
"[SELECT * FROM test WHERE foo = 1 AND foo = 2]"));
}

public void testErrorMessageForTranslatingQueryWithLocalExecution() throws IOException {
index("{\"foo\":1}");
expectBadRequest(() -> runSql(
new StringEntity("{\"query\":\"SELECT SIN(PI())\"}",
ContentType.APPLICATION_JSON), "/translate/"),
expectBadRequest(() -> runTranslateSql("{\"query\":\"SELECT SIN(PI())\"}"),
containsString("Cannot generate a query DSL for an SQL query that either its WHERE clause evaluates " +
"to FALSE or doesn't operate on a table (missing a FROM clause), sql statement: [SELECT SIN(PI())]"));
}

public void testErrorMessageForTranslatingSQLCommandStatement() throws IOException {
index("{\"foo\":1}");
expectBadRequest(() -> runSql(
new StringEntity("{\"query\":\"SHOW FUNCTIONS\"}",
ContentType.APPLICATION_JSON), "/translate/"),
expectBadRequest(() -> runTranslateSql("{\"query\":\"SHOW FUNCTIONS\"}"),
containsString("Cannot generate a query DSL for a special SQL command " +
"(e.g.: DESCRIBE, SHOW), sql statement: [SHOW FUNCTIONS]"));
}
Original file line number Diff line number Diff line change
@@ -7,11 +7,18 @@
package org.elasticsearch.xpack.sql.qa.rest;

import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.cbor.CborXContent;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.xpack.sql.proto.Mode;
import org.elasticsearch.xpack.sql.proto.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

public abstract class BaseRestSqlTestCase extends ESRestTestCase {

@@ -34,4 +41,27 @@ public static String mode(String mode) {
public static String randomMode() {
return randomFrom(StringUtils.EMPTY, "jdbc", "plain");
}

/**
* CBOR XContent parser returns floating point numbers as Floats, while JSON parser as Doubles.
astefan marked this conversation as resolved.
Show resolved Hide resolved
* To have the tests compare the correct data type, the floating point numbers types should be passed accordingly, to the comparators.
*/
public static Number xContentDependentFloatingNumberValue(String mode, Number value) {
Mode m = Mode.fromString(mode);
if (Mode.isDriver(m) || m == Mode.CLI) {
return value.floatValue();
} else {
return value.doubleValue();
}
}

public static Map<String, Object> toMap(Response response, String mode) throws IOException {
try (InputStream content = response.getEntity().getContent()) {
if (Mode.fromString(mode) == Mode.JDBC) {
astefan marked this conversation as resolved.
Show resolved Hide resolved
return XContentHelper.convertToMap(CborXContent.cborXContent, content, false);
} else {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
package org.elasticsearch.xpack.sql.qa.rest;

import com.fasterxml.jackson.core.io.JsonStringEncoder;

import org.apache.http.HttpEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
@@ -20,6 +21,7 @@
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.NotEqualMessageBuilder;
import org.elasticsearch.xpack.sql.proto.Mode;
import org.elasticsearch.xpack.sql.proto.StringUtils;
import org.elasticsearch.xpack.sql.qa.ErrorsTestCase;
import org.hamcrest.Matcher;
@@ -102,15 +104,16 @@ public void testNextPage() throws IOException {
+ "\"mode\":\"" + mode + "\", "
+ "\"fetch_size\":2" + columnarParameter(columnar) + "}";

Number value = xContentDependentFloatingNumberValue(mode, 1);
astefan marked this conversation as resolved.
Show resolved Hide resolved
String cursor = null;
for (int i = 0; i < 20; i += 2) {
Map<String, Object> response;
if (i == 0) {
response = runSql(new StringEntity(sqlRequest, ContentType.APPLICATION_JSON), "");
response = runSql(new StringEntity(sqlRequest, ContentType.APPLICATION_JSON), "", mode);
} else {
columnar = randomBoolean();
response = runSql(new StringEntity("{\"cursor\":\"" + cursor + "\"" + mode(mode) + columnarParameter(columnar) + "}",
ContentType.APPLICATION_JSON), StringUtils.EMPTY);
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode);
}

Map<String, Object> expected = new HashMap<>();
@@ -127,11 +130,11 @@ public void testNextPage() throws IOException {
Arrays.asList("text" + i, "text" + (i + 1)),
Arrays.asList(i, i + 1),
Arrays.asList(Math.sqrt(i), Math.sqrt(i + 1)),
Arrays.asList(1.0, 1.0)));
Arrays.asList(value, value)));
} else {
expected.put("rows", Arrays.asList(
Arrays.asList("text" + i, i, Math.sqrt(i), 1.0),
Arrays.asList("text" + (i + 1), i + 1, Math.sqrt(i + 1), 1.0)));
Arrays.asList("text" + i, i, Math.sqrt(i), value),
Arrays.asList("text" + (i + 1), i + 1, Math.sqrt(i + 1), value)));
}
cursor = (String) response.remove("cursor");
assertResponse(expected, response);
@@ -145,7 +148,7 @@ public void testNextPage() throws IOException {
expected.put("rows", emptyList());
}
assertResponse(expected, runSql(new StringEntity("{ \"cursor\":\"" + cursor + "\"" + mode(mode) + columnarParameter(columnar) + "}",
ContentType.APPLICATION_JSON), StringUtils.EMPTY));
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode));
}

@AwaitsFix(bugUrl = "Unclear status, https://github.com/elastic/x-pack-elasticsearch/issues/2074")
@@ -185,10 +188,11 @@ public void testScoreWithFieldNamedScore() throws IOException {
columnInfo(mode, "name", "text", JDBCType.VARCHAR, Integer.MAX_VALUE),
columnInfo(mode, "score", "long", JDBCType.BIGINT, 20),
columnInfo(mode, "SCORE()", "float", JDBCType.REAL, 15)));
Number value = xContentDependentFloatingNumberValue(mode, 1.0);
astefan marked this conversation as resolved.
Show resolved Hide resolved
if (columnar) {
expected.put("values", Arrays.asList(singletonList("test"), singletonList(10), singletonList(1.0)));
expected.put("values", Arrays.asList(singletonList("test"), singletonList(10), singletonList(value)));
} else {
expected.put("rows", singletonList(Arrays.asList("test", 10, 1.0)));
expected.put("rows", singletonList(Arrays.asList("test", 10, value)));
}

assertResponse(expected, runSql(mode, "SELECT *, SCORE() FROM test ORDER BY SCORE()", columnar));
@@ -328,7 +332,8 @@ public void testUseColumnarForUnsupportedFormats() throws Exception {
request.addParameter("error_trace", "true");
request.addParameter("pretty", "true");
request.addParameter("format", format);
request.setEntity(new StringEntity("{\"columnar\":true,\"query\":\"SELECT * FROM test\"" + mode(randomMode()) + "}",
request.setEntity(new StringEntity("{\"columnar\":true,\"query\":\"SELECT * FROM test\""
+ mode(randomValueOtherThan("jdbc", () -> randomMode())) + "}",
ContentType.APPLICATION_JSON));
expectBadRequest(() -> {
client().performRequest(request);
@@ -380,7 +385,11 @@ private Map<String, Object> runSql(String mode, String sql, boolean columnar) th
private Map<String, Object> runSql(String mode, String sql, String suffix, boolean columnar) throws IOException {
// put an explicit "columnar": false parameter or omit it altogether, it should make no difference
return runSql(new StringEntity("{\"query\":\"" + sql + "\"" + mode(mode) + columnarParameter(columnar) + "}",
ContentType.APPLICATION_JSON), suffix);
ContentType.APPLICATION_JSON), suffix, mode);
}

protected Map<String, Object> runTranslateSql(String sql) throws IOException {
return runSql(new StringEntity(sql, ContentType.APPLICATION_JSON), "/translate/", Mode.PLAIN.toString());
}

private String columnarParameter(boolean columnar) {
@@ -391,7 +400,7 @@ private String columnarParameter(boolean columnar) {
}
}

protected Map<String, Object> runSql(HttpEntity sql, String suffix) throws IOException {
protected Map<String, Object> runSql(HttpEntity sql, String suffix, String mode) throws IOException {
Request request = new Request("POST", SQL_QUERY_REST_ENDPOINT + suffix);
request.addParameter("error_trace", "true"); // Helps with debugging in case something crazy happens on the server.
request.addParameter("pretty", "true"); // Improves error reporting readability
@@ -406,11 +415,7 @@ protected Map<String, Object> runSql(HttpEntity sql, String suffix) throws IOExc
request.setOptions(options);
}
request.setEntity(sql);

Response response = client().performRequest(request);
try (InputStream content = response.getEntity().getContent()) {
return XContentHelper.convertToMap(JsonXContent.jsonXContent, content, false);
}
return toMap(client().performRequest(request), mode);
}

public void testPrettyPrintingEnabled() throws IOException {
@@ -495,8 +500,7 @@ private void executeAndAssertPrettyPrinting(String expectedJson, String prettyPa
public void testBasicTranslateQuery() throws IOException {
index("{\"test\":\"test\"}", "{\"test\":\"test\"}");

Map<String, Object> response = runSql(new StringEntity("{\"query\":\"SELECT * FROM test\"" + mode(randomMode()) + "}",
ContentType.APPLICATION_JSON), "/translate/");
Map<String, Object> response = runTranslateSql("{\"query\":\"SELECT * FROM test\"}");
assertEquals(1000, response.get("size"));
@SuppressWarnings("unchecked")
Map<String, Object> source = (Map<String, Object>) response.get("_source");
@@ -515,7 +519,7 @@ public void testBasicQueryWithFilter() throws IOException {
expected.put("rows", singletonList(singletonList("foo")));
assertResponse(expected, runSql(new StringEntity("{\"query\":\"SELECT * FROM test\", " +
"\"filter\":{\"match\": {\"test\": \"foo\"}}" + mode(mode) + "}",
ContentType.APPLICATION_JSON), StringUtils.EMPTY));
ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode));
}

public void testBasicQueryWithParameters() throws IOException {
@@ -536,17 +540,14 @@ public void testBasicQueryWithParameters() throws IOException {
}
assertResponse(expected, runSql(new StringEntity("{\"query\":\"SELECT test, ? param FROM test WHERE test = ?\", " +
"\"params\":[{\"type\": \"integer\", \"value\": 10}, {\"type\": \"keyword\", \"value\": \"foo\"}]"
+ mode(mode) + columnarParameter(columnar) + "}", ContentType.APPLICATION_JSON), StringUtils.EMPTY));
+ mode(mode) + columnarParameter(columnar) + "}", ContentType.APPLICATION_JSON), StringUtils.EMPTY, mode));
}

public void testBasicTranslateQueryWithFilter() throws IOException {
index("{\"test\":\"foo\"}",
"{\"test\":\"bar\"}");

Map<String, Object> response = runSql(
new StringEntity("{\"query\":\"SELECT * FROM test\", \"filter\":{\"match\": {\"test\": \"foo\"}}}",
ContentType.APPLICATION_JSON), "/translate/"
);
Map<String, Object> response = runTranslateSql("{\"query\":\"SELECT * FROM test\", \"filter\":{\"match\": {\"test\": \"foo\"}}}");

assertEquals(response.get("size"), 1000);
@SuppressWarnings("unchecked")
@@ -585,10 +586,8 @@ public void testTranslateQueryWithGroupByAndHaving() throws IOException {
index("{\"salary\":100}",
"{\"age\":20}");

Map<String, Object> response = runSql(
new StringEntity("{\"query\":\"SELECT avg(salary) FROM test GROUP BY abs(age) HAVING avg(salary) > 50 LIMIT 10\"}",
ContentType.APPLICATION_JSON), "/translate/"
);
Map<String, Object> response = runTranslateSql("{\"query\":\"SELECT avg(salary) FROM test GROUP BY abs(age) "
+ "HAVING avg(salary) > 50 LIMIT 10\"}");

assertEquals(response.get("size"), 0);
assertEquals(false, response.get("_source"));
@@ -804,10 +803,10 @@ private void executeQueryWithNextPage(String format, String expectedHeader, Stri
Map<String, Object> expected = new HashMap<>();
expected.put("rows", emptyList());
assertResponse(expected, runSql(new StringEntity("{\"cursor\":\"" + cursor + "\"}", ContentType.APPLICATION_JSON),
StringUtils.EMPTY));
StringUtils.EMPTY, Mode.PLAIN.toString()));

Map<String, Object> response = runSql(new StringEntity("{\"cursor\":\"" + cursor + "\"}", ContentType.APPLICATION_JSON),
"/close");
"/close", Mode.PLAIN.toString());
assertEquals(true, response.get("succeeded"));

assertEquals(0, getNumberOfSearchContexts("test"));