diff --git a/plugin/src/main/java/com/amazon/opendistroforelasticsearch/sql/plugin/rest/RestPPLQueryAction.java b/plugin/src/main/java/com/amazon/opendistroforelasticsearch/sql/plugin/rest/RestPPLQueryAction.java index b429586095..85ee6779e9 100644 --- a/plugin/src/main/java/com/amazon/opendistroforelasticsearch/sql/plugin/rest/RestPPLQueryAction.java +++ b/plugin/src/main/java/com/amazon/opendistroforelasticsearch/sql/plugin/rest/RestPPLQueryAction.java @@ -18,7 +18,7 @@ import com.amazon.opendistroforelasticsearch.sql.elasticsearch.security.SecurityAccess; import com.amazon.opendistroforelasticsearch.sql.plugin.request.PPLQueryRequestFactory; import com.amazon.opendistroforelasticsearch.sql.ppl.PPLService; -import com.amazon.opendistroforelasticsearch.sql.ppl.ResponseListener; +import com.amazon.opendistroforelasticsearch.sql.protocol.response.ResponseListener; import com.amazon.opendistroforelasticsearch.sql.ppl.config.PPLServiceConfig; import com.amazon.opendistroforelasticsearch.sql.ppl.domain.PPLQueryResponse; import org.elasticsearch.client.node.NodeClient; diff --git a/ppl/build.gradle b/ppl/build.gradle index 2dd218987d..b03012d821 100644 --- a/ppl/build.gradle +++ b/ppl/build.gradle @@ -32,6 +32,7 @@ dependencies { compile group: 'org.springframework', name: 'spring-beans', version: '5.2.5.RELEASE' compile project(':common') compile project(':core') + compile project(':protocol') testCompile group: 'junit', name: 'junit', version: '4.12' diff --git a/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLService.java b/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLService.java index 30bc26023d..cad37af917 100644 --- a/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLService.java +++ b/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLService.java @@ -15,6 +15,7 @@ package com.amazon.opendistroforelasticsearch.sql.ppl; +import com.amazon.opendistroforelasticsearch.sql.protocol.response.ResponseListener; import com.amazon.opendistroforelasticsearch.sql.ppl.antlr.PPLSyntaxParser; import com.amazon.opendistroforelasticsearch.sql.ppl.domain.PPLQueryRequest; import com.amazon.opendistroforelasticsearch.sql.ppl.domain.PPLQueryResponse; diff --git a/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/ResponseListener.java b/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/ResponseListener.java deleted file mode 100644 index d49d068d0a..0000000000 --- a/ppl/src/main/java/com/amazon/opendistroforelasticsearch/sql/ppl/ResponseListener.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amazon.opendistroforelasticsearch.sql.ppl; - -public interface ResponseListener { - - void onResponse(Response response); - - void onFailure(Exception e); -} diff --git a/ppl/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLServiceTest.java b/ppl/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLServiceTest.java index 50f52b3f1f..7e5c91863c 100644 --- a/ppl/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLServiceTest.java +++ b/ppl/src/test/java/com/amazon/opendistroforelasticsearch/sql/ppl/PPLServiceTest.java @@ -15,6 +15,7 @@ package com.amazon.opendistroforelasticsearch.sql.ppl; +import com.amazon.opendistroforelasticsearch.sql.protocol.response.ResponseListener; import com.amazon.opendistroforelasticsearch.sql.ppl.config.PPLServiceConfig; import com.amazon.opendistroforelasticsearch.sql.ppl.domain.PPLQueryRequest; import com.amazon.opendistroforelasticsearch.sql.ppl.domain.PPLQueryResponse; diff --git a/protocol/build.gradle b/protocol/build.gradle new file mode 100644 index 0000000000..b82c6608ec --- /dev/null +++ b/protocol/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'java' + id "io.freefair.lombok" + id 'jacoco' +} + +repositories { + mavenCentral() +} + +dependencies { + compile group: 'com.google.guava', name: 'guava', version:'23.0' + compile group: 'org.json', name: 'json', version:'20180813' //TODO: change to other JSON lib? + compile project(':core') + + testImplementation('org.junit.jupiter:junit-jupiter:5.6.2') + testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '2.1' + testCompile group: 'org.mockito', name: 'mockito-core', version: '3.3.3' + testCompile group: 'org.mockito', name: 'mockito-junit-jupiter', version: '3.3.3' +} + +test { + useJUnitPlatform() + testLogging { + events "passed", "skipped", "failed" + } +} + +jacoco { + toolVersion = "0.8.3" +} +jacocoTestReport { + reports { + html.enabled true + } + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it) + })) + } +} +test.finalizedBy(project.tasks.jacocoTestReport) + +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 1.0 + } + + } + } + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it) + })) + } +} +check.dependsOn jacocoTestCoverageVerification +jacocoTestCoverageVerification.dependsOn jacocoTestReport \ No newline at end of file diff --git a/protocol/lombok.config b/protocol/lombok.config new file mode 100644 index 0000000000..189c0bef98 --- /dev/null +++ b/protocol/lombok.config @@ -0,0 +1,3 @@ +# This file is generated by the 'io.freefair.lombok' Gradle plugin +config.stopBubbling = true +lombok.addLombokGeneratedAnnotation = true diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponse.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponse.java new file mode 100644 index 0000000000..58baefcb18 --- /dev/null +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponse.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response; + +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValue; +import com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils; +import lombok.RequiredArgsConstructor; + +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Query response that encapsulates query results and isolate {@link ExprValue} related from formatter implementation. + */ +@RequiredArgsConstructor +public class QueryResponse implements Iterable { + + /** + * Results which are collection of expression + */ + private final Collection exprValues; + + + /** + * @return size of results + */ + public int size() { + return exprValues.size(); + } + + /** + * Parse column name from results + * @return mapping from column names to its expression type + */ + public Map columnNameTypes() { + if (exprValues.isEmpty()) { + return Collections.emptyMap(); + } + + // TODO: Need other way to extract header than inferring from data implicitly + Map tupleValue = getFirstTupleValue(); + return populateColumnNameAndTypes(tupleValue); + } + + @Override + public Iterator iterator() { + // Any chance to avoid copy for json response generation? + return exprValues.stream(). + map(ExprValueUtils::getTupleValue). + map(Map::values). + map(this::convertExprValuesToValues). + iterator(); + } + + private Map getFirstTupleValue() { + // Assume expression is always tuple on first level + // and columns (keys) of all tuple values are exactly same + ExprValue firstValue = exprValues.iterator().next(); + return ExprValueUtils.getTupleValue(firstValue); + } + + private Map populateColumnNameAndTypes(Map tupleValue) { + // Use linked hashmap to maintain original order in tuple expression + Map colNameTypes = new LinkedHashMap<>(); + tupleValue.forEach((name, expr) -> colNameTypes.put(name, getTypeString(expr))); + return colNameTypes; + } + + private Object[] convertExprValuesToValues(Collection exprValues) { + return exprValues.stream(). + map(ExprValue::value). + toArray(Object[]::new); + } + + private String getTypeString(ExprValue exprValue) { + return exprValue.type().name().toLowerCase(); + } + +} diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/ResponseListener.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/ResponseListener.java new file mode 100644 index 0000000000..9439ed58a4 --- /dev/null +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/ResponseListener.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response; + +/** + * Response listener for response post-processing callback. + * This is necessary because execution engine may schedule and execute in different thread. + * + * @param response class + */ +public interface ResponseListener { + + /** + * Handle successful response. + * @param response successful response + */ + void onResponse(Response response); + + /** + * Handle failed response. + * @param e exception captured + */ + void onFailure(Exception e); + +} diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java new file mode 100644 index 0000000000..7730fd9233 --- /dev/null +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/JsonResponseFormatter.java @@ -0,0 +1,76 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response.format; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.json.JSONObject; + +import static com.amazon.opendistroforelasticsearch.sql.protocol.response.format.JsonResponseFormatter.Style.PRETTY; + +/** + * Abstract class for all JSON formatter. + * @param response generic type which could be DQL or DML response + */ +@RequiredArgsConstructor +public abstract class JsonResponseFormatter implements ResponseFormatter { + + /** + * JSON format styles: pretty format or compact format without indent and space + */ + public enum Style { + PRETTY, COMPACT + } + + /** + * JSON format style + */ + private final Style style; + + + @Override + public String format(Response response) { + return jsonify(buildJsonObject(response)); + } + + @Override + public String format(Throwable t) { + JsonError error = new JsonError(t.getClass().getSimpleName(), + t.getMessage()); + return jsonify(error); + } + + /** + * Build JSON object to generate response json string. + * @param response response + * @return json object for response + */ + protected abstract Object buildJsonObject(Response response); + + + private String jsonify(Object jsonObject) { + JSONObject json = new JSONObject(jsonObject); + return (style == PRETTY) ? json.toString(2) : json.toString(); + } + + @RequiredArgsConstructor + @Getter + public static class JsonError { + private final String type; + private final String reason; + } +} diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/ResponseFormatter.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/ResponseFormatter.java new file mode 100644 index 0000000000..0dae00fbcf --- /dev/null +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/ResponseFormatter.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response.format; + +/** + * Response formatter to format response to different formats. + */ +public interface ResponseFormatter { + + /** + * Format response into string in expected format. + * @param response response + * @return string with response content formatted + */ + String format(Response response); + + /** + * Format an exception into string. + * @param t exception occurred + * @return string with exception content formatted + */ + String format(Throwable t); + +} diff --git a/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java new file mode 100644 index 0000000000..af0f04c625 --- /dev/null +++ b/protocol/src/main/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatter.java @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response.format; + +import com.amazon.opendistroforelasticsearch.sql.protocol.response.QueryResponse; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Singular; + +import java.util.List; + +/** + * JSON response format with schema header and data rows. For example, + * + *
+ *  {
+ *      "schema": [
+ *          {
+ *              "name": "name",
+ *              "type": "string"
+ *          }
+ *      ],
+ *      "datarows": [
+ *          ["John"],
+ *          ["Smith"]
+ *      ],
+ *      "total": 2,
+ *      "size": 2
+ *  }
+ * 
+ */ +public class SimpleJsonResponseFormatter extends JsonResponseFormatter { + + public SimpleJsonResponseFormatter(Style style) { + super(style); + } + + @Override + public Object buildJsonObject(QueryResponse response) { + JsonResponse.JsonResponseBuilder json = JsonResponse.builder(); + + json.total(response.size()). + size(response.size()); + + response.columnNameTypes().forEach((name, type) -> json.column(new Column(name, type))); + + for (Object[] values : response) { + json.row(new DataRow(values)); + } + return json.build(); + } + + /** + * org.json requires these inner data classes be public (and static) + */ + @Builder + @Getter + public static class JsonResponse { + @Singular("column") + private final List schema; + + @Singular("row") + private final List datarows; + + private long total; + private long size; + } + + @RequiredArgsConstructor + @Getter + public static class Column { + private final String name; + private final String type; + } + + @RequiredArgsConstructor + @Getter + public static class DataRow { + private final Object[] row; + } + +} diff --git a/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponseTest.java b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponseTest.java new file mode 100644 index 0000000000..c3560f4b1a --- /dev/null +++ b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/QueryResponseTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response; + +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.tupleValue; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +class QueryResponseTest { + + @Test + void size() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("name", "John", "age", 20)), + tupleValue(ImmutableMap.of("name", "Allen", "age", 30)), + tupleValue(ImmutableMap.of("name", "Smith", "age", 40)) + )); + assertEquals(3, response.size()); + } + + @Test + void columnNameTypes() { + QueryResponse response = new QueryResponse(Collections.singletonList( + tupleValue(ImmutableMap.of("name", "John", "age", 20)) + )); + + assertEquals( + ImmutableMap.of("name", "string", "age", "integer"), + response.columnNameTypes() + ); + } + + @Test + void columnNameTypesFromEmptyExprValues() { + QueryResponse response = new QueryResponse(Collections.emptyList()); + assertTrue(response.columnNameTypes().isEmpty()); + } + + @Disabled("Need to figure out column headers in some other way than inferring from data implicitly") + @Test + void columnNameTypesFromExprValuesWithMissing() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("name", "John")), + tupleValue(ImmutableMap.of("name", "John", "age", 20)) + )); + + assertEquals( + ImmutableMap.of("name", "string", "age", "integer"), + response.columnNameTypes() + ); + } + + @Test + void iterate() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("name", "John", "age", 20)), + tupleValue(ImmutableMap.of("name", "Allen", "age", 30)) + )); + + int i = 0; + for (Object[] objects : response) { + if (i == 0) { + assertArrayEquals(new Object[]{"John", 20}, objects); + } else if (i == 1) { + assertArrayEquals(new Object[]{"Allen", 30}, objects); + } else { + fail("More rows returned than expected"); + } + i++; + } + } + +} \ No newline at end of file diff --git a/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java new file mode 100644 index 0000000000..f40f291122 --- /dev/null +++ b/protocol/src/test/java/com/amazon/opendistroforelasticsearch/sql/protocol/response/format/SimpleJsonResponseFormatterTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + */ + +package com.amazon.opendistroforelasticsearch.sql.protocol.response.format; + +import com.amazon.opendistroforelasticsearch.sql.protocol.response.QueryResponse; +import com.google.common.collect.ImmutableMap; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; + +import static com.amazon.opendistroforelasticsearch.sql.data.model.ExprValueUtils.tupleValue; +import static com.amazon.opendistroforelasticsearch.sql.protocol.response.format.JsonResponseFormatter.Style.COMPACT; +import static com.amazon.opendistroforelasticsearch.sql.protocol.response.format.JsonResponseFormatter.Style.PRETTY; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SimpleJsonResponseFormatterTest { + + @Test + void formatResponse() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("firstname", "John", "age", 20)), + tupleValue(ImmutableMap.of("firstname", "Smith", "age", 30)) + )); + SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); + assertEquals( + "{\"schema\":[{\"name\":\"firstname\",\"type\":\"string\"},{\"name\":\"age\",\"type\":\"integer\"}]," + + "\"total\":2,\"datarows\":[{\"row\":[\"John\",20]},{\"row\":[\"Smith\",30]}],\"size\":2}", + formatter.format(response) + ); + } + + @Test + void formatResponsePretty() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("firstname", "John", "age", 20)), + tupleValue(ImmutableMap.of("firstname", "Smith", "age", 30)) + )); + SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(PRETTY); + assertEquals( + "{\n" + + " \"schema\": [\n" + + " {\n" + + " \"name\": \"firstname\",\n" + + " \"type\": \"string\"\n" + + " },\n" + + " {\n" + + " \"name\": \"age\",\n" + + " \"type\": \"integer\"\n" + + " }\n" + + " ],\n" + + " \"total\": 2,\n" + + " \"datarows\": [\n" + + " {\"row\": [\n" + + " \"John\",\n" + + " 20\n" + + " ]},\n" + + " {\"row\": [\n" + + " \"Smith\",\n" + + " 30\n" + + " ]}\n" + + " ],\n" + + " \"size\": 2\n" + + "}", + formatter.format(response) + ); + } + + @Disabled("Need to figure out column headers in some other way than inferring from data implicitly") + @Test + void formatResponseWithMissingValue() { + QueryResponse response = new QueryResponse(Arrays.asList( + tupleValue(ImmutableMap.of("firstname", "John")), + tupleValue(ImmutableMap.of("firstname", "Smith", "age", 30)) + )); + SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); + assertEquals( + "{\"schema\":[{\"name\":\"firstname\",\"type\":\"string\"},{\"name\":\"age\",\"type\":\"integer\"}]," + + "\"total\":2,\"datarows\":[{\"row\":[\"John\",null]},{\"row\":[\"Smith\",30]}],\"size\":2}", + formatter.format(response) + ); + } + + @Test + void formatError() { + SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(COMPACT); + assertEquals( + "{\"reason\":\"This is an exception\",\"type\":\"RuntimeException\"}", + formatter.format(new RuntimeException("This is an exception")) + ); + } + + @Test + void formatErrorPretty() { + SimpleJsonResponseFormatter formatter = new SimpleJsonResponseFormatter(PRETTY); + assertEquals( + "{\n" + + " \"reason\": \"This is an exception\",\n" + + " \"type\": \"RuntimeException\"\n" + + "}", + formatter.format(new RuntimeException("This is an exception")) + ); + } + +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 3e8b5ba08e..cb101e1ab8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,4 +21,5 @@ include 'integ-test' include 'common' include 'elasticsearch' include 'core' +include 'protocol'