/**
   * Get the next complete Row object from the response queue.
   *
   * @return null if end-of-stream, otherwise a complete Row.
   * @throws java.io.IOException On errors.
   */
  public synchronized Row getNextMergedRow() throws IOException {
    if (!lastResponseProcessed) {
      ResultQueueEntry<Row> queueEntry = getNext();

      if (queueEntry.isCompletionMarker()) {
        lastResponseProcessed = true;
      } else {
        return queueEntry.getResponseOrThrow();
      }
    }

    Preconditions.checkState(
        lastResponseProcessed,
        "Should only exit merge loop with by returning a complete Row or hitting end of stream.");
    return null;
  }
 /** {@inheritDoc} */
 @Override
 public void onError(Throwable t) {
   try {
     resultQueue.put(ResultQueueEntry.<Row>fromThrowable(t));
   } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
     throw new RuntimeException("Interrupted while adding a ResultQueueEntry", e);
   }
 }
 /** {@inheritDoc} */
 @Override
 public void onNext(Row row) {
   try {
     resultQueue.put(ResultQueueEntry.fromResponse(row));
   } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
     throw new RuntimeException("Interrupted while adding a ResultQueueEntry", e);
   }
 }
 /** {@inheritDoc} */
 @Override
 public void onCompleted() {
   try {
     completionMarkerFound.set(true);
     resultQueue.put(ResultQueueEntry.<Row>completionMarker());
   } catch (InterruptedException e) {
     Thread.currentThread().interrupt();
     throw new RuntimeException("Interrupted while adding a ResultQueueEntry", e);
   }
 }