Skip to content

Commit cbd9d4c

Browse files
authored
SQL: add pretty printing to JSON format (#43756)
1 parent 85cacff commit cbd9d4c

File tree

5 files changed

+104
-2
lines changed

5 files changed

+104
-2
lines changed

server/src/main/java/org/elasticsearch/rest/AbstractRestChannel.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,22 @@ public XContentBuilder newErrorBuilder() throws IOException {
8484
*/
8585
@Override
8686
public XContentBuilder newBuilder(@Nullable XContentType requestContentType, boolean useFiltering) throws IOException {
87+
return newBuilder(requestContentType, null, useFiltering);
88+
}
89+
90+
/**
91+
* Creates a new {@link XContentBuilder} for a response to be sent using this channel. The builder's type can be sent as a parameter,
92+
* through {@code responseContentType} or it can fallback to {@link #newBuilder(XContentType, boolean)} logic if the sent type value
93+
* is {@code null}.
94+
*/
95+
@Override
96+
public XContentBuilder newBuilder(@Nullable XContentType requestContentType, @Nullable XContentType responseContentType,
97+
boolean useFiltering) throws IOException {
98+
if (responseContentType == null) {
99+
responseContentType = XContentType.fromMediaTypeOrFormat(format);
100+
}
87101
// try to determine the response content type from the media type or the format query string parameter, with the format parameter
88102
// taking precedence over the Accept header
89-
XContentType responseContentType = XContentType.fromMediaTypeOrFormat(format);
90103
if (responseContentType == null) {
91104
if (requestContentType != null) {
92105
// if there was a parsed content-type for the incoming request use that since no format was specified using the query

server/src/main/java/org/elasticsearch/rest/RestChannel.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public interface RestChannel {
3636
XContentBuilder newErrorBuilder() throws IOException;
3737

3838
XContentBuilder newBuilder(@Nullable XContentType xContentType, boolean useFiltering) throws IOException;
39+
40+
XContentBuilder newBuilder(@Nullable XContentType xContentType, @Nullable XContentType responseContentType,
41+
boolean useFiltering) throws IOException;
3942

4043
BytesStreamOutput bytesOutput();
4144

server/src/main/java/org/elasticsearch/rest/RestController.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,12 @@ public XContentBuilder newBuilder(@Nullable XContentType xContentType, boolean u
508508
return delegate.newBuilder(xContentType, useFiltering);
509509
}
510510

511+
@Override
512+
public XContentBuilder newBuilder(XContentType xContentType, XContentType responseContentType, boolean useFiltering)
513+
throws IOException {
514+
return delegate.newBuilder(xContentType, responseContentType, useFiltering);
515+
}
516+
511517
@Override
512518
public BytesStreamOutput bytesOutput() {
513519
return delegate.bytesOutput();

x-pack/plugin/sql/qa/src/main/java/org/elasticsearch/xpack/sql/qa/rest/RestSqlTestCase.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.elasticsearch.client.ResponseException;
1616
import org.elasticsearch.common.CheckedSupplier;
1717
import org.elasticsearch.common.Strings;
18+
import org.elasticsearch.common.bytes.BytesArray;
1819
import org.elasticsearch.common.collect.Tuple;
1920
import org.elasticsearch.common.io.Streams;
2021
import org.elasticsearch.common.xcontent.XContentHelper;
@@ -414,6 +415,85 @@ protected Map<String, Object> runSql(HttpEntity sql, String suffix) throws IOExc
414415
}
415416
}
416417

418+
public void testPrettyPrintingEnabled() throws IOException {
419+
boolean columnar = randomBoolean();
420+
String expected = "";
421+
if (columnar) {
422+
expected = "{\n" +
423+
" \"columns\" : [\n" +
424+
" {\n" +
425+
" \"name\" : \"test1\",\n" +
426+
" \"type\" : \"text\"\n" +
427+
" }\n" +
428+
" ],\n" +
429+
" \"values\" : [\n" +
430+
" [\n" +
431+
" \"test1\",\n" +
432+
" \"test2\"\n" +
433+
" ]\n" +
434+
" ]\n" +
435+
"}\n";
436+
} else {
437+
expected = "{\n" +
438+
" \"columns\" : [\n" +
439+
" {\n" +
440+
" \"name\" : \"test1\",\n" +
441+
" \"type\" : \"text\"\n" +
442+
" }\n" +
443+
" ],\n" +
444+
" \"rows\" : [\n" +
445+
" [\n" +
446+
" \"test1\"\n" +
447+
" ],\n" +
448+
" [\n" +
449+
" \"test2\"\n" +
450+
" ]\n" +
451+
" ]\n" +
452+
"}\n";
453+
}
454+
executeAndAssertPrettyPrinting(expected, "true", columnar);
455+
}
456+
457+
public void testPrettyPrintingDisabled() throws IOException {
458+
boolean columnar = randomBoolean();
459+
String expected = "";
460+
if (columnar) {
461+
expected = "{\"columns\":[{\"name\":\"test1\",\"type\":\"text\"}],\"values\":[[\"test1\",\"test2\"]]}";
462+
} else {
463+
expected = "{\"columns\":[{\"name\":\"test1\",\"type\":\"text\"}],\"rows\":[[\"test1\"],[\"test2\"]]}";
464+
}
465+
executeAndAssertPrettyPrinting(expected, randomFrom("false", null), columnar);
466+
}
467+
468+
private void executeAndAssertPrettyPrinting(String expectedJson, String prettyParameter, boolean columnar)
469+
throws IOException {
470+
index("{\"test1\":\"test1\"}",
471+
"{\"test1\":\"test2\"}");
472+
473+
Request request = new Request("POST", SQL_QUERY_REST_ENDPOINT);
474+
if (prettyParameter != null) {
475+
request.addParameter("pretty", prettyParameter);
476+
}
477+
if (randomBoolean()) {
478+
// We default to JSON but we force it randomly for extra coverage
479+
request.addParameter("format", "json");
480+
}
481+
if (randomBoolean()) {
482+
// JSON is the default but randomly set it sometime for extra coverage
483+
RequestOptions.Builder options = request.getOptions().toBuilder();
484+
options.addHeader("Accept", randomFrom("*/*", "application/json"));
485+
request.setOptions(options);
486+
}
487+
request.setEntity(new StringEntity("{\"query\":\"SELECT * FROM test\"" + mode("plain") + columnarParameter(columnar) + "}",
488+
ContentType.APPLICATION_JSON));
489+
490+
Response response = client().performRequest(request);
491+
try (InputStream content = response.getEntity().getContent()) {
492+
String actualJson = new BytesArray(content.readAllBytes()).utf8ToString();
493+
assertEquals(expectedJson, actualJson);
494+
}
495+
}
496+
417497
public void testBasicTranslateQuery() throws IOException {
418498
index("{\"test\":\"test\"}", "{\"test\":\"test\"}");
419499

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
9595
return channel -> client.execute(SqlQueryAction.INSTANCE, sqlRequest, new RestResponseListener<SqlQueryResponse>(channel) {
9696
@Override
9797
public RestResponse buildResponse(SqlQueryResponse response) throws Exception {
98-
XContentBuilder builder = XContentBuilder.builder(xContentType.xContent());
98+
XContentBuilder builder = channel.newBuilder(request.getXContentType(), xContentType, true);
9999
response.toXContent(builder, request);
100100
return new BytesRestResponse(RestStatus.OK, builder);
101101
}

0 commit comments

Comments
 (0)