@POST
 @Path("data/explore/queries/{id}/preview")
 public void getQueryResultPreview(
     HttpRequest request, HttpResponder responder, @PathParam("id") String id) {
   // NOTE: this call is a POST because it is not idempotent: cursor of results is moved
   try {
     QueryHandle handle = QueryHandle.fromId(id);
     List<QueryResult> results;
     if (handle.equals(QueryHandle.NO_OP)) {
       results = Lists.newArrayList();
     } else {
       results = exploreService.previewResults(handle);
     }
     responder.sendJson(HttpResponseStatus.OK, results);
   } catch (IllegalArgumentException e) {
     LOG.debug("Got exception:", e);
     responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
   } catch (SQLException e) {
     LOG.debug("Got exception:", e);
     responder.sendString(
         HttpResponseStatus.BAD_REQUEST,
         String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
   } catch (HandleNotFoundException e) {
     if (e.isInactive()) {
       responder.sendString(
           HttpResponseStatus.CONFLICT, "Preview is unavailable for inactive queries.");
       return;
     }
     responder.sendStatus(HttpResponseStatus.NOT_FOUND);
   } catch (Throwable e) {
     LOG.error("Got exception:", e);
     responder.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
   }
 }
  @POST
  @Path("data/explore/queries/{id}/download")
  public void downloadQueryResults(
      HttpRequest request, HttpResponder responder, @PathParam("id") final String id) {
    // NOTE: this call is a POST because it is not idempotent: cursor of results is moved
    boolean responseStarted = false;
    try {
      QueryHandle handle = QueryHandle.fromId(id);
      if (handle.equals(QueryHandle.NO_OP)
          || !exploreService.getStatus(handle).getStatus().equals(QueryStatus.OpStatus.FINISHED)) {
        responder.sendStatus(HttpResponseStatus.CONFLICT);
        return;
      }

      StringBuffer sb = new StringBuffer();
      sb.append(getCSVHeaders(exploreService.getResultSchema(handle)));
      sb.append('\n');

      List<QueryResult> results;
      results = exploreService.previewResults(handle);
      if (results.isEmpty()) {
        results = exploreService.nextResults(handle, DOWNLOAD_FETCH_CHUNK_SIZE);
      }

      ChunkResponder chunkResponder = responder.sendChunkStart(HttpResponseStatus.OK, null);
      responseStarted = true;
      while (!results.isEmpty()) {
        for (QueryResult result : results) {
          appendCSVRow(sb, result);
          sb.append('\n');
        }
        // If failed to send to client, just propagate the IOException and let netty-http to handle
        chunkResponder.sendChunk(ChannelBuffers.wrappedBuffer(sb.toString().getBytes("UTF-8")));
        sb.delete(0, sb.length());
        results = exploreService.nextResults(handle, DOWNLOAD_FETCH_CHUNK_SIZE);
      }
      Closeables.closeQuietly(chunkResponder);

    } catch (IllegalArgumentException e) {
      LOG.debug("Got exception:", e);
      // We can't send another response if sendChunkStart has been called
      if (!responseStarted) {
        responder.sendString(HttpResponseStatus.BAD_REQUEST, e.getMessage());
      }
    } catch (SQLException e) {
      LOG.debug("Got exception:", e);
      if (!responseStarted) {
        responder.sendString(
            HttpResponseStatus.BAD_REQUEST,
            String.format("[SQLState %s] %s", e.getSQLState(), e.getMessage()));
      }
    } catch (HandleNotFoundException e) {
      if (!responseStarted) {
        if (e.isInactive()) {
          responder.sendString(HttpResponseStatus.CONFLICT, "Query is inactive");
        } else {
          responder.sendStatus(HttpResponseStatus.NOT_FOUND);
        }
      }
    } catch (Throwable e) {
      LOG.error("Got exception:", e);
      if (!responseStarted) {
        responder.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
      }
    }
  }