Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Support pretty option for _explain request
Browse files Browse the repository at this point in the history
* Added a formatter to print explain response in pretty format of json.

* Added pretty option for _explain request.

* Added integration test.

* Added unittest.
  • Loading branch information
chloe-zh authored Oct 7, 2019
1 parent 407357a commit 473a42d
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.amazon.opendistroforelasticsearch.sql.executor.ActionRequestRestExecutorFactory;
import com.amazon.opendistroforelasticsearch.sql.executor.RestExecutor;
import com.amazon.opendistroforelasticsearch.sql.executor.format.ErrorMessage;
import com.amazon.opendistroforelasticsearch.sql.utils.JsonPrettyFormatter;
import com.amazon.opendistroforelasticsearch.sql.metrics.MetricName;
import com.amazon.opendistroforelasticsearch.sql.metrics.Metrics;
import com.amazon.opendistroforelasticsearch.sql.query.QueryAction;
Expand Down Expand Up @@ -139,11 +140,18 @@ private static QueryAction explainRequest(final NodeClient client, final SqlRequ

private void executeSqlRequest(final RestRequest request, final QueryAction queryAction,
final Client client, final RestChannel channel) throws Exception {
Map<String, String> params = request.params();
if (isExplainRequest(request)) {
final String jsonExplanation = queryAction.explain().explain();
channel.sendResponse(new BytesRestResponse(OK, "application/json; charset=UTF-8", jsonExplanation));
String result;
if (params.containsKey("pretty")
&& ("".equals(params.get("pretty")) || "true".equals(params.get("pretty")))) {
result = JsonPrettyFormatter.format(jsonExplanation);
} else {
result = jsonExplanation;
}
channel.sendResponse(new BytesRestResponse(OK, "application/json; charset=UTF-8", result));
} else {
Map<String, String> params = request.params();
RestExecutor restExecutor = ActionRequestRestExecutorFactory.createExecutor(params.get("format"),
queryAction);
//doing this hack because elasticsearch throws exception for un-consumed props
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright 2019 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.utils;

import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;

import java.io.IOException;

/**
* Utility Class for formatting Json string pretty.
*/
public class JsonPrettyFormatter {

/**
* @param jsonString Json string without/with pretty format
* @return A standard and pretty formatted json string
* @throws IOException
*/
public static String format(String jsonString) throws IOException {
//turn _explain response into pretty formatted Json
XContentBuilder contentBuilder = XContentFactory.jsonBuilder().prettyPrint();
try (
XContentParser contentParser = XContentFactory.xContent(XContentType.JSON)
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, jsonString)
){
contentBuilder.copyCurrentStructure(contentParser);
}
return Strings.toString(contentBuilder);
}

private JsonPrettyFormatter() {
throw new AssertionError(getClass().getCanonicalName() + " is a utility class and must not be initialized");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2019 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.esintgtest;

import com.amazon.opendistroforelasticsearch.sql.utils.StringUtils;
import com.google.common.io.Files;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static com.amazon.opendistroforelasticsearch.sql.esintgtest.TestsConstants.TEST_INDEX_ACCOUNT;
import static org.hamcrest.Matchers.equalTo;

public class PrettyFormatterIT extends SQLIntegTestCase{

@Override
protected void init() throws Exception {
loadIndex(Index.ACCOUNT);
}

@Test
public void assertExplainPrettyFormatted() throws IOException {
String query = StringUtils.format("SELECT firstname FROM %s", TEST_INDEX_ACCOUNT);

String notPrettyExplainOutputFilePath = TestUtils.getResourceFilePath(
"src/test/resources/expectedOutput/explainIT_format_not_pretty.json");
String notPrettyExplainOutput = Files.toString(new File(notPrettyExplainOutputFilePath), StandardCharsets.UTF_8);

assertThat(executeExplainRequest(query, ""), equalTo(notPrettyExplainOutput));
assertThat(executeExplainRequest(query, "pretty=false"), equalTo(notPrettyExplainOutput));

String prettyExplainOutputFilePath = TestUtils.getResourceFilePath(
"src/test/resources/expectedOutput/explainIT_format_pretty.json");
String prettyExplainOutput = Files.toString(new File(prettyExplainOutputFilePath), StandardCharsets.UTF_8);

assertThat(executeExplainRequest(query, "pretty"), equalTo(prettyExplainOutput));
assertThat(executeExplainRequest(query, "pretty=true"), equalTo(prettyExplainOutput));
}

private String executeExplainRequest(String query, String explainParam) throws IOException {
String endpoint = "/_opendistro/_sql/_explain?" + explainParam;
String request = makeRequest(query);

Request sqlRequest = new Request("POST", endpoint);
sqlRequest.setJsonEntity(request);

RestClient restClient = ESIntegTestCase.getRestClient();
Response response = restClient.performRequest(sqlRequest);
String responseString = TestUtils.getResponseBody(response, true);

return responseString;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright 2019 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.unittest;

import com.amazon.opendistroforelasticsearch.sql.esintgtest.TestUtils;
import com.amazon.opendistroforelasticsearch.sql.utils.JsonPrettyFormatter;
import com.google.common.io.Files;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class PrettyFormatterTest {

@Test
public void assertFormatterWithoutContentInside() throws IOException {
String noContentInput = "{ }";
String expectedOutput = "{ }";
String result = JsonPrettyFormatter.format(noContentInput);
assertThat(result, equalTo(expectedOutput));
}

@Test
public void assertFormatterOutputsPrettyJson() throws IOException {
String explainFormattedPrettyFilePath = TestUtils.getResourceFilePath(
"/src/test/resources/expectedOutput/explain_format_pretty.json");
String explainFormattedPretty = Files.toString(new File(explainFormattedPrettyFilePath), StandardCharsets.UTF_8);

String explainFormattedOnelineFilePath = TestUtils.getResourceFilePath(
"/src/test/resources/explain_format_oneline.json");
String explainFormattedOneline = Files.toString(new File(explainFormattedOnelineFilePath), StandardCharsets.UTF_8);
String result = JsonPrettyFormatter.format(explainFormattedOneline);

assertThat(result, equalTo(explainFormattedPretty));
}

@Test(expected = IOException.class)
public void illegalInputOfNull() throws IOException {
JsonPrettyFormatter.format("");
}

@Test(expected = IOException.class)
public void illegalInputOfUnpairedBrace() throws IOException {
JsonPrettyFormatter.format("{\"key\" : \"value\"");
}

@Test(expected = IOException.class)
public void illegalInputOfWrongBraces() throws IOException {
JsonPrettyFormatter.format("<\"key\" : \"value\">");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"from":0,"size":200,"_source":{"includes":["firstname"],"excludes":[]}}
10 changes: 10 additions & 0 deletions src/test/resources/expectedOutput/explainIT_format_pretty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"from" : 0,
"size" : 200,
"_source" : {
"includes" : [
"firstname"
],
"excludes" : [ ]
}
}
10 changes: 10 additions & 0 deletions src/test/resources/expectedOutput/explain_format_pretty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"from" : 0,
"size" : 200,
"_source" : {
"includes" : [
"firstname"
],
"excludes" : [ ]
}
}
1 change: 1 addition & 0 deletions src/test/resources/explain_format_oneline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"from" : 0, "size" : 200, "_source" : {"includes" : ["firstname"], "excludes" : [ ]}}

0 comments on commit 473a42d

Please sign in to comment.