You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@druid.apache.org by fj...@apache.org on 2018/10/02 01:13:24 UTC

[incubator-druid] branch master updated: SQL: Fix too-long headers in http responses. (#6411)

This is an automated email from the ASF dual-hosted git repository.

fjy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-druid.git


The following commit(s) were added to refs/heads/master by this push:
     new 244046f  SQL: Fix too-long headers in http responses. (#6411)
244046f is described below

commit 244046fda5efb72d18044577c8ce7d741ac58df3
Author: Gian Merlino <gi...@gmail.com>
AuthorDate: Mon Oct 1 18:13:08 2018 -0700

    SQL: Fix too-long headers in http responses. (#6411)
    
    Fixes #6409 by moving column name info from HTTP headers into the
    result body.
---
 docs/content/querying/sql.md                       |  21 +++-
 .../apache/druid/sql/http/ArrayLinesWriter.java    |  13 ++
 .../org/apache/druid/sql/http/ArrayWriter.java     |  13 ++
 .../java/org/apache/druid/sql/http/CsvWriter.java  |   6 +
 .../apache/druid/sql/http/ObjectLinesWriter.java   |  15 ++-
 .../org/apache/druid/sql/http/ObjectWriter.java    |  13 ++
 .../org/apache/druid/sql/http/ResultFormat.java    |   3 +
 .../java/org/apache/druid/sql/http/SqlQuery.java   |  15 ++-
 .../org/apache/druid/sql/http/SqlResource.java     |  15 +--
 .../druid/sql/calcite/http/SqlQueryTest.java       |   2 +-
 .../druid/sql/calcite/http/SqlResourceTest.java    | 132 +++++++++++++++------
 11 files changed, 196 insertions(+), 52 deletions(-)

diff --git a/docs/content/querying/sql.md b/docs/content/querying/sql.md
index 98c2eeb..0130d6f 100644
--- a/docs/content/querying/sql.md
+++ b/docs/content/querying/sql.md
@@ -369,12 +369,7 @@ Metadata is available over the HTTP API by querying [system tables](#retrieving-
 
 #### Responses
 
-All Druid SQL HTTP responses include a "X-Druid-Column-Names" header with a JSON-encoded array of columns that
-will appear in the result rows and an "X-Druid-Column-Types" header with a JSON-encoded array of
-[types](#data-types-and-casts).
-
-For the result rows themselves, Druid SQL supports a variety of result formats. You can
-specify these by adding a "resultFormat" parameter, like:
+Druid SQL supports a variety of result formats. You can specify these by adding a "resultFormat" parameter, like:
 
 ```json
 {
@@ -393,6 +388,20 @@ The supported result formats are:
 |`arrayLines`|Like "array", but the JSON arrays are separated by newlines instead of being wrapped in a JSON array. This can make it easier to parse the entire response set as a stream, if you do not have ready access to a streaming JSON parser. To make it possible to detect a truncated response, this format includes a trailer of one blank line.|text/plain|
 |`csv`|Comma-separated values, with one row per line. Individual field values may be escaped by being surrounded in double quotes. If double quotes appear in a field value, they will be escaped by replacing them with double-double-quotes like `""this""`. To make it possible to detect a truncated response, this format includes a trailer of one blank line.|text/csv|
 
+You can additionally request a header by setting "header" to true in your request, like:
+
+```json
+{
+  "query" : "SELECT COUNT(*) FROM data_source WHERE foo = 'bar' AND __time > TIMESTAMP '2000-01-01 00:00:00'",
+  "resultFormat" : "arrayLines",
+  "header" : true
+}
+```
+
+In this case, the first result returned will be a header. For the `csv`, `array`, and `arrayLines` formats, the header
+will be a list of column names. For the `object` and `objectLines` formats, the header will be an object where the
+keys are column names, and the values are null.
+
 Errors that occur before the response body is sent will be reported in JSON, with an HTTP 500 status code, in the
 same format as [native Druid query errors](../querying/querying.html#query-errors). If an error occurs while the response body is
 being sent, at that point it is too late to change the HTTP status code or report a JSON error, so the response will
diff --git a/sql/src/main/java/org/apache/druid/sql/http/ArrayLinesWriter.java b/sql/src/main/java/org/apache/druid/sql/http/ArrayLinesWriter.java
index 01ee28d..29eae27 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/ArrayLinesWriter.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/ArrayLinesWriter.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 public class ArrayLinesWriter implements ResultFormat.Writer
 {
@@ -56,6 +57,18 @@ public class ArrayLinesWriter implements ResultFormat.Writer
   }
 
   @Override
+  public void writeHeader(final List<String> columnNames) throws IOException
+  {
+    jsonGenerator.writeStartArray();
+
+    for (String columnName : columnNames) {
+      jsonGenerator.writeString(columnName);
+    }
+
+    jsonGenerator.writeEndArray();
+  }
+
+  @Override
   public void writeRowStart() throws IOException
   {
     jsonGenerator.writeStartArray();
diff --git a/sql/src/main/java/org/apache/druid/sql/http/ArrayWriter.java b/sql/src/main/java/org/apache/druid/sql/http/ArrayWriter.java
index 9871fdd..c177cf3 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/ArrayWriter.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/ArrayWriter.java
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 public class ArrayWriter implements ResultFormat.Writer
 {
@@ -54,6 +55,18 @@ public class ArrayWriter implements ResultFormat.Writer
   }
 
   @Override
+  public void writeHeader(final List<String> columnNames) throws IOException
+  {
+    jsonGenerator.writeStartArray();
+
+    for (String columnName : columnNames) {
+      jsonGenerator.writeString(columnName);
+    }
+
+    jsonGenerator.writeEndArray();
+  }
+
+  @Override
   public void writeRowStart() throws IOException
   {
     jsonGenerator.writeStartArray();
diff --git a/sql/src/main/java/org/apache/druid/sql/http/CsvWriter.java b/sql/src/main/java/org/apache/druid/sql/http/CsvWriter.java
index a118374..d89c752 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/CsvWriter.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/CsvWriter.java
@@ -59,6 +59,12 @@ public class CsvWriter implements ResultFormat.Writer
   }
 
   @Override
+  public void writeHeader(final List<String> columnNames)
+  {
+    writer.writeNext(columnNames.toArray(new String[0]), false);
+  }
+
+  @Override
   public void writeRowStart()
   {
     // Do nothing.
diff --git a/sql/src/main/java/org/apache/druid/sql/http/ObjectLinesWriter.java b/sql/src/main/java/org/apache/druid/sql/http/ObjectLinesWriter.java
index 9b040dd..887b272 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/ObjectLinesWriter.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/ObjectLinesWriter.java
@@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 public class ObjectLinesWriter implements ResultFormat.Writer
 {
@@ -40,7 +41,7 @@ public class ObjectLinesWriter implements ResultFormat.Writer
   }
 
   @Override
-  public void writeResponseStart() throws IOException
+  public void writeResponseStart()
   {
     // Do nothing.
   }
@@ -56,6 +57,18 @@ public class ObjectLinesWriter implements ResultFormat.Writer
   }
 
   @Override
+  public void writeHeader(final List<String> columnNames) throws IOException
+  {
+    jsonGenerator.writeStartObject();
+
+    for (String columnName : columnNames) {
+      jsonGenerator.writeNullField(columnName);
+    }
+
+    jsonGenerator.writeEndObject();
+  }
+
+  @Override
   public void writeRowStart() throws IOException
   {
     jsonGenerator.writeStartObject();
diff --git a/sql/src/main/java/org/apache/druid/sql/http/ObjectWriter.java b/sql/src/main/java/org/apache/druid/sql/http/ObjectWriter.java
index 76a65f6..b1623a5 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/ObjectWriter.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/ObjectWriter.java
@@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import javax.annotation.Nullable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 public class ObjectWriter implements ResultFormat.Writer
 {
@@ -54,6 +55,18 @@ public class ObjectWriter implements ResultFormat.Writer
   }
 
   @Override
+  public void writeHeader(final List<String> columnNames) throws IOException
+  {
+    jsonGenerator.writeStartObject();
+
+    for (String columnName : columnNames) {
+      jsonGenerator.writeNullField(columnName);
+    }
+
+    jsonGenerator.writeEndObject();
+  }
+
+  @Override
   public void writeRowStart() throws IOException
   {
     jsonGenerator.writeStartObject();
diff --git a/sql/src/main/java/org/apache/druid/sql/http/ResultFormat.java b/sql/src/main/java/org/apache/druid/sql/http/ResultFormat.java
index 8d21fcb..2e95993 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/ResultFormat.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/ResultFormat.java
@@ -28,6 +28,7 @@ import javax.ws.rs.core.MediaType;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.List;
 
 public enum ResultFormat
 {
@@ -112,6 +113,8 @@ public enum ResultFormat
      */
     void writeResponseStart() throws IOException;
 
+    void writeHeader(List<String> columnNames) throws IOException;
+
     /**
      * Start of each result row.
      */
diff --git a/sql/src/main/java/org/apache/druid/sql/http/SqlQuery.java b/sql/src/main/java/org/apache/druid/sql/http/SqlQuery.java
index 86e6268..4e2c873 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/SqlQuery.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/SqlQuery.java
@@ -31,17 +31,20 @@ public class SqlQuery
 {
   private final String query;
   private final ResultFormat resultFormat;
+  private final boolean header;
   private final Map<String, Object> context;
 
   @JsonCreator
   public SqlQuery(
       @JsonProperty("query") final String query,
       @JsonProperty("resultFormat") final ResultFormat resultFormat,
+      @JsonProperty("header") final boolean header,
       @JsonProperty("context") final Map<String, Object> context
   )
   {
     this.query = Preconditions.checkNotNull(query, "query");
     this.resultFormat = resultFormat == null ? ResultFormat.OBJECT : resultFormat;
+    this.header = header;
     this.context = context == null ? ImmutableMap.of() : context;
   }
 
@@ -57,6 +60,12 @@ public class SqlQuery
     return resultFormat;
   }
 
+  @JsonProperty("header")
+  public boolean includeHeader()
+  {
+    return header;
+  }
+
   @JsonProperty
   public Map<String, Object> getContext()
   {
@@ -73,7 +82,8 @@ public class SqlQuery
       return false;
     }
     final SqlQuery sqlQuery = (SqlQuery) o;
-    return Objects.equals(query, sqlQuery.query) &&
+    return header == sqlQuery.header &&
+           Objects.equals(query, sqlQuery.query) &&
            resultFormat == sqlQuery.resultFormat &&
            Objects.equals(context, sqlQuery.context);
   }
@@ -81,7 +91,7 @@ public class SqlQuery
   @Override
   public int hashCode()
   {
-    return Objects.hash(query, resultFormat, context);
+    return Objects.hash(query, resultFormat, header, context);
   }
 
   @Override
@@ -90,6 +100,7 @@ public class SqlQuery
     return "SqlQuery{" +
            "query='" + query + '\'' +
            ", resultFormat=" + resultFormat +
+           ", header=" + header +
            ", context=" + context +
            '}';
   }
diff --git a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
index 0406855..74a3597 100644
--- a/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
+++ b/sql/src/main/java/org/apache/druid/sql/http/SqlResource.java
@@ -23,6 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Throwables;
 import com.google.inject.Inject;
+import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.druid.guice.annotations.Json;
 import org.apache.druid.java.util.common.ISE;
 import org.apache.druid.java.util.common.guava.Yielder;
@@ -34,9 +37,6 @@ import org.apache.druid.sql.calcite.planner.Calcites;
 import org.apache.druid.sql.calcite.planner.DruidPlanner;
 import org.apache.druid.sql.calcite.planner.PlannerFactory;
 import org.apache.druid.sql.calcite.planner.PlannerResult;
-import org.apache.calcite.plan.RelOptPlanner;
-import org.apache.calcite.rel.type.RelDataTypeField;
-import org.apache.calcite.sql.type.SqlTypeName;
 import org.joda.time.DateTimeZone;
 import org.joda.time.format.ISODateTimeFormat;
 
@@ -52,6 +52,7 @@ import javax.ws.rs.core.Response;
 import javax.ws.rs.core.StreamingOutput;
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Arrays;
 import java.util.List;
 
 @Path("/druid/v2/sql/")
@@ -93,14 +94,12 @@ public class SqlResource
       final boolean[] timeColumns = new boolean[fieldList.size()];
       final boolean[] dateColumns = new boolean[fieldList.size()];
       final String[] columnNames = new String[fieldList.size()];
-      final String[] columnTypes = new String[fieldList.size()];
 
       for (int i = 0; i < fieldList.size(); i++) {
         final SqlTypeName sqlTypeName = fieldList.get(i).getType().getSqlTypeName();
         timeColumns[i] = sqlTypeName == SqlTypeName.TIMESTAMP;
         dateColumns[i] = sqlTypeName == SqlTypeName.DATE;
         columnNames[i] = fieldList.get(i).getName();
-        columnTypes[i] = sqlTypeName.getName();
       }
 
       final Yielder<Object[]> yielder0 = Yielders.each(plannerResult.run());
@@ -119,6 +118,10 @@ public class SqlResource
                                                                     .createFormatter(outputStream, jsonMapper)) {
                       writer.writeResponseStart();
 
+                      if (sqlQuery.includeHeader()) {
+                        writer.writeHeader(Arrays.asList(columnNames));
+                      }
+
                       while (!yielder.isDone()) {
                         final Object[] row = yielder.get();
                         writer.writeRowStart();
@@ -151,8 +154,6 @@ public class SqlResource
                   }
                 }
             )
-            .header("X-Druid-Column-Names", jsonMapper.writeValueAsString(columnNames))
-            .header("X-Druid-Column-Types", jsonMapper.writeValueAsString(columnTypes))
             .build();
       }
       catch (Throwable e) {
diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java
index 014f039..aa85c70 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlQueryTest.java
@@ -34,7 +34,7 @@ public class SqlQueryTest extends CalciteTestBase
   public void testSerde() throws Exception
   {
     final ObjectMapper jsonMapper = TestHelper.makeJsonMapper();
-    final SqlQuery query = new SqlQuery("SELECT 1", ResultFormat.ARRAY, ImmutableMap.of("useCache", false));
+    final SqlQuery query = new SqlQuery("SELECT 1", ResultFormat.ARRAY, true, ImmutableMap.of("useCache", false));
     Assert.assertEquals(query, jsonMapper.readValue(jsonMapper.writeValueAsString(query), SqlQuery.class));
   }
 }
diff --git a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlResourceTest.java b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlResourceTest.java
index 70e275d..f898148 100644
--- a/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlResourceTest.java
+++ b/sql/src/test/java/org/apache/druid/sql/calcite/http/SqlResourceTest.java
@@ -25,6 +25,7 @@ import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import org.apache.calcite.tools.ValidationException;
 import org.apache.druid.common.config.NullHandling;
 import org.apache.druid.jackson.DefaultObjectMapper;
 import org.apache.druid.java.util.common.ISE;
@@ -49,7 +50,6 @@ import org.apache.druid.sql.calcite.util.SpecificSegmentsQuerySegmentWalker;
 import org.apache.druid.sql.http.ResultFormat;
 import org.apache.druid.sql.http.SqlQuery;
 import org.apache.druid.sql.http.SqlResource;
-import org.apache.calcite.tools.ValidationException;
 import org.easymock.EasyMock;
 import org.junit.After;
 import org.junit.AfterClass;
@@ -152,33 +152,10 @@ public class SqlResourceTest extends CalciteTestBase
   }
 
   @Test
-  public void testXDruidColumnHeaders() throws Exception
-  {
-    final Response response = resource.doPost(
-        new SqlQuery(
-            "SELECT FLOOR(__time TO DAY) as \"day\", COUNT(*) as TheCount, SUM(m1) FROM druid.foo GROUP BY 1",
-            ResultFormat.OBJECT,
-            null
-        ),
-        req
-    );
-
-    Assert.assertEquals(
-        "[\"day\",\"TheCount\",\"EXPR$2\"]",
-        response.getMetadata().getFirst("X-Druid-Column-Names")
-    );
-
-    Assert.assertEquals(
-        "[\"TIMESTAMP\",\"BIGINT\",\"DOUBLE\"]",
-        response.getMetadata().getFirst("X-Druid-Column-Types")
-    );
-  }
-
-  @Test
   public void testCountStar() throws Exception
   {
     final List<Map<String, Object>> rows = doPost(
-        new SqlQuery("SELECT COUNT(*) AS cnt, 'foo' AS TheFoo FROM druid.foo", null, null)
+        new SqlQuery("SELECT COUNT(*) AS cnt, 'foo' AS TheFoo FROM druid.foo", null, false, null)
     ).rhs;
 
     Assert.assertEquals(
@@ -196,6 +173,7 @@ public class SqlResourceTest extends CalciteTestBase
         new SqlQuery(
             "SELECT __time, CAST(__time AS DATE) AS t2 FROM druid.foo LIMIT 1",
             ResultFormat.OBJECT,
+            false,
             null
         )
     ).rhs;
@@ -215,6 +193,7 @@ public class SqlResourceTest extends CalciteTestBase
         new SqlQuery(
             "SELECT __time, CAST(__time AS DATE) AS t2 FROM druid.foo LIMIT 1",
             ResultFormat.OBJECT,
+            false,
             ImmutableMap.of(PlannerContext.CTX_SQL_TIME_ZONE, "America/Los_Angeles")
         )
     ).rhs;
@@ -231,7 +210,7 @@ public class SqlResourceTest extends CalciteTestBase
   public void testFieldAliasingSelect() throws Exception
   {
     final List<Map<String, Object>> rows = doPost(
-        new SqlQuery("SELECT dim2 \"x\", dim2 \"y\" FROM druid.foo LIMIT 1", ResultFormat.OBJECT, null)
+        new SqlQuery("SELECT dim2 \"x\", dim2 \"y\" FROM druid.foo LIMIT 1", ResultFormat.OBJECT, false, null)
     ).rhs;
 
     Assert.assertEquals(
@@ -246,7 +225,7 @@ public class SqlResourceTest extends CalciteTestBase
   public void testFieldAliasingGroupBy() throws Exception
   {
     final List<Map<String, Object>> rows = doPost(
-        new SqlQuery("SELECT dim2 \"x\", dim2 \"y\" FROM druid.foo GROUP BY dim2", ResultFormat.OBJECT, null)
+        new SqlQuery("SELECT dim2 \"x\", dim2 \"y\" FROM druid.foo GROUP BY dim2", ResultFormat.OBJECT, false, null)
     ).rhs;
 
     Assert.assertEquals(
@@ -276,9 +255,43 @@ public class SqlResourceTest extends CalciteTestBase
     Assert.assertEquals(
         ImmutableList.of(
             Arrays.asList("2000-01-01T00:00:00.000Z", 1, "", "a", 1.0, 1.0, "org.apache.druid.hll.HLLCV1", nullStr),
-            Arrays.asList("2000-01-02T00:00:00.000Z", 1, "10.1", nullStr, 2.0, 2.0, "org.apache.druid.hll.HLLCV1", nullStr)
+            Arrays.asList(
+                "2000-01-02T00:00:00.000Z",
+                1,
+                "10.1",
+                nullStr,
+                2.0,
+                2.0,
+                "org.apache.druid.hll.HLLCV1",
+                nullStr
+            )
+        ),
+        doPost(new SqlQuery(query, ResultFormat.ARRAY, false, null), new TypeReference<List<List<Object>>>() {}).rhs
+    );
+  }
+
+  @Test
+  public void testArrayResultFormatWithHeader() throws Exception
+  {
+    final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
+    final String nullStr = NullHandling.replaceWithDefault() ? "" : null;
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            Arrays.asList("__time", "cnt", "dim1", "dim2", "m1", "m2", "unique_dim1", "EXPR$7"),
+            Arrays.asList("2000-01-01T00:00:00.000Z", 1, "", "a", 1.0, 1.0, "org.apache.druid.hll.HLLCV1", nullStr),
+            Arrays.asList(
+                "2000-01-02T00:00:00.000Z",
+                1,
+                "10.1",
+                nullStr,
+                2.0,
+                2.0,
+                "org.apache.druid.hll.HLLCV1",
+                nullStr
+            )
         ),
-        doPost(new SqlQuery(query, ResultFormat.ARRAY, null), new TypeReference<List<List<Object>>>() {}).rhs
+        doPost(new SqlQuery(query, ResultFormat.ARRAY, true, null), new TypeReference<List<List<Object>>>() {}).rhs
     );
   }
 
@@ -286,7 +299,7 @@ public class SqlResourceTest extends CalciteTestBase
   public void testArrayLinesResultFormat() throws Exception
   {
     final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
-    final String response = doPostRaw(new SqlQuery(query, ResultFormat.ARRAYLINES, null)).rhs;
+    final String response = doPostRaw(new SqlQuery(query, ResultFormat.ARRAYLINES, false, null)).rhs;
     final String nullStr = NullHandling.replaceWithDefault() ? "" : null;
     final List<String> lines = Splitter.on('\n').splitToList(response);
 
@@ -304,6 +317,31 @@ public class SqlResourceTest extends CalciteTestBase
   }
 
   @Test
+  public void testArrayLinesResultFormatWithHeader() throws Exception
+  {
+    final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
+    final String response = doPostRaw(new SqlQuery(query, ResultFormat.ARRAYLINES, true, null)).rhs;
+    final String nullStr = NullHandling.replaceWithDefault() ? "" : null;
+    final List<String> lines = Splitter.on('\n').splitToList(response);
+
+    Assert.assertEquals(5, lines.size());
+    Assert.assertEquals(
+        Arrays.asList("__time", "cnt", "dim1", "dim2", "m1", "m2", "unique_dim1", "EXPR$7"),
+        JSON_MAPPER.readValue(lines.get(0), List.class)
+    );
+    Assert.assertEquals(
+        Arrays.asList("2000-01-01T00:00:00.000Z", 1, "", "a", 1.0, 1.0, "org.apache.druid.hll.HLLCV1", nullStr),
+        JSON_MAPPER.readValue(lines.get(1), List.class)
+    );
+    Assert.assertEquals(
+        Arrays.asList("2000-01-02T00:00:00.000Z", 1, "10.1", nullStr, 2.0, 2.0, "org.apache.druid.hll.HLLCV1", nullStr),
+        JSON_MAPPER.readValue(lines.get(2), List.class)
+    );
+    Assert.assertEquals("", lines.get(3));
+    Assert.assertEquals("", lines.get(4));
+  }
+
+  @Test
   public void testObjectResultFormat() throws Exception
   {
     final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo  LIMIT 2";
@@ -340,7 +378,10 @@ public class SqlResourceTest extends CalciteTestBase
                 .put("EXPR$7", "")
                 .build()
         ).stream().map(transformer).collect(Collectors.toList()),
-        doPost(new SqlQuery(query, ResultFormat.OBJECT, null), new TypeReference<List<Map<String, Object>>>() {}).rhs
+        doPost(
+            new SqlQuery(query, ResultFormat.OBJECT, false, null),
+            new TypeReference<List<Map<String, Object>>>() {}
+        ).rhs
     );
   }
 
@@ -348,7 +389,7 @@ public class SqlResourceTest extends CalciteTestBase
   public void testObjectLinesResultFormat() throws Exception
   {
     final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
-    final String response = doPostRaw(new SqlQuery(query, ResultFormat.OBJECTLINES, null)).rhs;
+    final String response = doPostRaw(new SqlQuery(query, ResultFormat.OBJECTLINES, false, null)).rhs;
     final String nullStr = NullHandling.replaceWithDefault() ? "" : null;
     final Function<Map<String, Object>, Map<String, Object>> transformer = m -> {
       return Maps.transformEntries(
@@ -399,11 +440,30 @@ public class SqlResourceTest extends CalciteTestBase
   public void testCsvResultFormat() throws Exception
   {
     final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
-    final String response = doPostRaw(new SqlQuery(query, ResultFormat.CSV, null)).rhs;
+    final String response = doPostRaw(new SqlQuery(query, ResultFormat.CSV, false, null)).rhs;
+    final List<String> lines = Splitter.on('\n').splitToList(response);
+
+    Assert.assertEquals(
+        ImmutableList.of(
+            "2000-01-01T00:00:00.000Z,1,,a,1.0,1.0,org.apache.druid.hll.HLLCV1,",
+            "2000-01-02T00:00:00.000Z,1,10.1,,2.0,2.0,org.apache.druid.hll.HLLCV1,",
+            "",
+            ""
+        ),
+        lines
+    );
+  }
+
+  @Test
+  public void testCsvResultFormatWithHeaders() throws Exception
+  {
+    final String query = "SELECT *, CASE dim2 WHEN '' THEN dim2 END FROM foo LIMIT 2";
+    final String response = doPostRaw(new SqlQuery(query, ResultFormat.CSV, true, null)).rhs;
     final List<String> lines = Splitter.on('\n').splitToList(response);
 
     Assert.assertEquals(
         ImmutableList.of(
+            "__time,cnt,dim1,dim2,m1,m2,unique_dim1,EXPR$7",
             "2000-01-01T00:00:00.000Z,1,,a,1.0,1.0,org.apache.druid.hll.HLLCV1,",
             "2000-01-02T00:00:00.000Z,1,10.1,,2.0,2.0,org.apache.druid.hll.HLLCV1,",
             "",
@@ -417,7 +477,7 @@ public class SqlResourceTest extends CalciteTestBase
   public void testExplainCountStar() throws Exception
   {
     final List<Map<String, Object>> rows = doPost(
-        new SqlQuery("EXPLAIN PLAN FOR SELECT COUNT(*) AS cnt FROM druid.foo", ResultFormat.OBJECT, null)
+        new SqlQuery("EXPLAIN PLAN FOR SELECT COUNT(*) AS cnt FROM druid.foo", ResultFormat.OBJECT, false, null)
     ).rhs;
 
     Assert.assertEquals(
@@ -438,6 +498,7 @@ public class SqlResourceTest extends CalciteTestBase
         new SqlQuery(
             "SELECT dim3 FROM druid.foo",
             ResultFormat.OBJECT,
+            false,
             null
         )
     ).lhs;
@@ -453,7 +514,7 @@ public class SqlResourceTest extends CalciteTestBase
   {
     // SELECT + ORDER unsupported
     final QueryInterruptedException exception = doPost(
-        new SqlQuery("SELECT dim1 FROM druid.foo ORDER BY dim1", ResultFormat.OBJECT, null)
+        new SqlQuery("SELECT dim1 FROM druid.foo ORDER BY dim1", ResultFormat.OBJECT, false, null)
     ).lhs;
 
     Assert.assertNotNull(exception);
@@ -472,6 +533,7 @@ public class SqlResourceTest extends CalciteTestBase
         new SqlQuery(
             "SELECT DISTINCT dim1 FROM foo",
             ResultFormat.OBJECT,
+            false,
             ImmutableMap.of("maxMergingDictionarySize", 1)
         )
     ).lhs;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@druid.apache.org
For additional commands, e-mail: commits-help@druid.apache.org