/*
  Copyright (C) 2009-2018  Brazil
  Copyright (C) 2018-2023  Sutou Kouhei <kou@clear-code.com>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "grn.h"

#include "grn_ctx_impl.h"
#include "grn_db.h"
#include "grn_expr.h"
#include "grn_float.h"
#include "grn_output.h"
#include "grn_output_columns.h"
#include "grn_str.h"
#include "grn_table.h"
#include "grn_util.h"

#include <string.h>
#include <math.h>

uint32_t grn_output_auto_flush_interval = 1024;

void
grn_output_init_from_env(void)
{
  /* clang-format is broken with this additional block... */
  /* { */
  char grn_output_auto_flush_interval_env[GRN_ENV_BUFFER_SIZE];
  grn_getenv("GRN_OUTPUT_AUTO_FLUSH_INTERVAL",
             grn_output_auto_flush_interval_env,
             GRN_ENV_BUFFER_SIZE);
  if (grn_output_auto_flush_interval_env[0]) {
    size_t env_len = strlen(grn_output_auto_flush_interval_env);
    uint32_t interval = grn_atoui(grn_output_auto_flush_interval_env,
                                  grn_output_auto_flush_interval_env + env_len,
                                  NULL);
    if (interval > 0) {
      grn_output_auto_flush_interval = interval;
    }
  }
  /* } */
}

#define LEVELS        (&ctx->impl->output.levels)
#define DEPTH         (GRN_BULK_VSIZE(LEVELS) >> 2)
#define CURR_LEVEL    (DEPTH ? (GRN_UINT32_VALUE_AT(LEVELS, (DEPTH - 1))) : 0)
#define INCR_DEPTH(i) GRN_UINT32_PUT(ctx, LEVELS, i)
#define DECR_DEPTH                                                             \
  (DEPTH ? grn_bulk_truncate(ctx,                                              \
                             LEVELS,                                           \
                             GRN_BULK_VSIZE(LEVELS) - sizeof(uint32_t))        \
         : 0)
#define INCR_LENGTH                                                            \
  (DEPTH ? (GRN_UINT32_VALUE_AT(LEVELS, (DEPTH - 1)) += 2) : 0)

grn_rc
grn_output_range_normalize(grn_ctx *ctx, int size, int *offset, int *limit)
{
  int end;
  int normalized_offset = *offset;
  int normalized_limit = *limit;

  if (normalized_limit < 0) {
    normalized_limit += size + 1;
    if (normalized_limit < 0) {
      *offset = 0;
      *limit = 0;
      return GRN_TOO_SMALL_LIMIT;
    }
  } else if (normalized_limit > size) {
    normalized_limit = size;
  }

  if (normalized_offset < 0) {
    normalized_offset += size;
    if (normalized_offset < 0) {
      if (normalized_limit + normalized_offset >= 0) {
        normalized_limit += normalized_offset;
        normalized_offset = 0;
      } else {
        *offset = 0;
        *limit = 0;
        return GRN_TOO_SMALL_OFFSET;
      }
    }
  } else if (normalized_offset != 0 && normalized_offset >= size) {
    *offset = 0;
    *limit = 0;
    return GRN_TOO_LARGE_OFFSET;
  }

  /* At this point, offset and limit must be zero or positive. */
  end = normalized_offset + normalized_limit;
  if (end > size) {
    normalized_limit -= end - size;
  }
  *offset = normalized_offset;
  *limit = normalized_limit;
  return GRN_SUCCESS;
}

static void
indent(grn_ctx *ctx, grn_obj *outbuf, size_t level)
{
  size_t i;
  for (i = 0; i < level; i++) {
    GRN_TEXT_PUTS(ctx, outbuf, "  ");
  }
}

static void
json_array_open(grn_ctx *ctx, grn_obj *outbuf, size_t *indent_level)
{
  GRN_TEXT_PUTC(ctx, outbuf, '[');
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    (*indent_level)++;
    indent(ctx, outbuf, *indent_level);
  }
}

static void
json_array_close(grn_ctx *ctx, grn_obj *outbuf, size_t *indent_level)
{
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    (*indent_level)--;
    indent(ctx, outbuf, *indent_level);
  }
  GRN_TEXT_PUTC(ctx, outbuf, ']');
}

static void
json_element_end(grn_ctx *ctx, grn_obj *outbuf, size_t indent_level)
{
  GRN_TEXT_PUTC(ctx, outbuf, ',');
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    indent(ctx, outbuf, indent_level);
  }
}

static void
json_map_open(grn_ctx *ctx, grn_obj *outbuf, size_t *indent_level)
{
  GRN_TEXT_PUTC(ctx, outbuf, '{');
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    (*indent_level)++;
    indent(ctx, outbuf, *indent_level);
  }
}

static void
json_map_close(grn_ctx *ctx, grn_obj *outbuf, size_t *indent_level)
{
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    (*indent_level)--;
    indent(ctx, outbuf, *indent_level);
  }
  GRN_TEXT_PUTC(ctx, outbuf, '}');
}

static void
json_key_end(grn_ctx *ctx, grn_obj *outbuf)
{
  GRN_TEXT_PUTC(ctx, outbuf, ':');
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, ' ');
  }
}

static void
json_key(grn_ctx *ctx, grn_obj *outbuf, const char *key)
{
  grn_text_esc(ctx, outbuf, key, strlen(key));
  json_key_end(ctx, outbuf);
}

static void
json_value_end(grn_ctx *ctx, grn_obj *outbuf, size_t indent_level)
{
  GRN_TEXT_PUTC(ctx, outbuf, ',');
  if (ctx->impl->output.is_pretty) {
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    indent(ctx, outbuf, indent_level);
  }
}

static void
put_delimiter(grn_ctx *ctx, grn_obj *outbuf, grn_content_type output_type)
{
  uint32_t level = CURR_LEVEL;
  switch (output_type) {
  case GRN_CONTENT_JSON:
    if (level < 2) {
      if (DEPTH > 0 && ctx->impl->output.is_pretty) {
        GRN_TEXT_PUTC(ctx, outbuf, '\n');
        indent(ctx, outbuf, DEPTH + 1);
      }
      return;
    }
    if ((level & 3) == 3) {
      GRN_TEXT_PUTC(ctx, outbuf, ':');
      if (ctx->impl->output.is_pretty) {
        GRN_TEXT_PUTC(ctx, outbuf, ' ');
      }
    } else {
      json_element_end(ctx, outbuf, DEPTH + 1);
    }
    // if (DEPTH == 1 && ((level & 3) != 3)) { GRN_TEXT_PUTC(ctx, outbuf, '\n');
    // }
    break;
  case GRN_CONTENT_XML:
    if (!DEPTH) {
      return;
    }
    GRN_TEXT_PUTC(ctx, outbuf, '\n');
    break;
  case GRN_CONTENT_TSV:
    if (level < 2) {
      return;
    }
    if (DEPTH <= 2) {
      GRN_TEXT_PUTC(ctx, outbuf, ((level & 3) == 3) ? '\t' : '\n');
    } else {
      GRN_TEXT_PUTC(ctx, outbuf, '\t');
    }
  case GRN_CONTENT_MSGPACK:
    // do nothing
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
}

void
grn_output_array_open(grn_ctx *ctx,
                      grn_obj *outbuf,
                      grn_content_type output_type,
                      const char *name,
                      int nelements)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    GRN_TEXT_PUTC(ctx, outbuf, '[');
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTC(ctx, outbuf, '<');
    GRN_TEXT_PUTS(ctx, outbuf, name);
    GRN_TEXT_PUTC(ctx, outbuf, '>');
    grn_vector_add_element(ctx,
                           &ctx->impl->output.names,
                           name,
                           (uint32_t)strlen(name),
                           0,
                           GRN_DB_SHORT_TEXT);
    break;
  case GRN_CONTENT_TSV:
    if (DEPTH > 2) {
      GRN_TEXT_PUTS(ctx, outbuf, "[\t");
    }
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    if (nelements < 0) {
      GRN_LOG(ctx,
              GRN_LOG_DEBUG,
              "grn_output_array_open nelements (%d) for <%s>",
              nelements,
              name);
    }
    msgpack_pack_array(&ctx->impl->output.msgpacker, (size_t)nelements);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_DEPTH(0);
}

void
grn_output_array_close(grn_ctx *ctx,
                       grn_obj *outbuf,
                       grn_content_type output_type)
{
  switch (output_type) {
  case GRN_CONTENT_JSON:
    if (ctx->impl->output.is_pretty) {
      GRN_TEXT_PUTC(ctx, outbuf, '\n');
      indent(ctx, outbuf, DEPTH);
    }
    GRN_TEXT_PUTC(ctx, outbuf, ']');
    break;
  case GRN_CONTENT_TSV:
    if (DEPTH > 3) {
      if (CURR_LEVEL >= 2) {
        GRN_TEXT_PUTC(ctx, outbuf, '\t');
      }
      GRN_TEXT_PUTC(ctx, outbuf, ']');
    }
    break;
  case GRN_CONTENT_XML:
    {
      const char *name;
      unsigned int name_len;
      name_len = grn_vector_pop_element(ctx,
                                        &ctx->impl->output.names,
                                        &name,
                                        NULL,
                                        NULL);
      GRN_TEXT_PUTS(ctx, outbuf, "</");
      GRN_TEXT_PUT(ctx, outbuf, name, name_len);
      GRN_TEXT_PUTC(ctx, outbuf, '>');
    }
    break;
  case GRN_CONTENT_MSGPACK:
    // do nothing
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  DECR_DEPTH;
  INCR_LENGTH;
}

void
grn_output_map_open(grn_ctx *ctx,
                    grn_obj *outbuf,
                    grn_content_type output_type,
                    const char *name,
                    int nelements)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    GRN_TEXT_PUTS(ctx, outbuf, "{");
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTC(ctx, outbuf, '<');
    GRN_TEXT_PUTS(ctx, outbuf, name);
    GRN_TEXT_PUTC(ctx, outbuf, '>');
    grn_vector_add_element(ctx,
                           &ctx->impl->output.names,
                           name,
                           (uint32_t)strlen(name),
                           0,
                           GRN_DB_SHORT_TEXT);
    break;
  case GRN_CONTENT_TSV:
    if (DEPTH > 2) {
      GRN_TEXT_PUTS(ctx, outbuf, "{\t");
    }
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    if (nelements < 0) {
      GRN_LOG(ctx,
              GRN_LOG_DEBUG,
              "grn_output_map_open nelements (%d) for <%s>",
              nelements,
              name);
    }
    msgpack_pack_map(&ctx->impl->output.msgpacker, (size_t)nelements);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_DEPTH(1);
}

void
grn_output_map_close(grn_ctx *ctx,
                     grn_obj *outbuf,
                     grn_content_type output_type)
{
  switch (output_type) {
  case GRN_CONTENT_JSON:
    if (ctx->impl->output.is_pretty) {
      GRN_TEXT_PUTC(ctx, outbuf, '\n');
      indent(ctx, outbuf, DEPTH);
    }
    GRN_TEXT_PUTS(ctx, outbuf, "}");
    break;
  case GRN_CONTENT_TSV:
    if (DEPTH > 3) {
      if (CURR_LEVEL >= 2) {
        GRN_TEXT_PUTC(ctx, outbuf, '\t');
      }
      GRN_TEXT_PUTC(ctx, outbuf, '}');
    }
    break;
  case GRN_CONTENT_XML:
    {
      const char *name;
      unsigned int name_len;
      name_len = grn_vector_pop_element(ctx,
                                        &ctx->impl->output.names,
                                        &name,
                                        NULL,
                                        NULL);
      GRN_TEXT_PUTS(ctx, outbuf, "</");
      GRN_TEXT_PUT(ctx, outbuf, name, name_len);
      GRN_TEXT_PUTC(ctx, outbuf, '>');
    }
    break;
  case GRN_CONTENT_MSGPACK:
    // do nothing
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  DECR_DEPTH;
  INCR_LENGTH;
}

static void
grn_output_int8(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                int8_t value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_TSV:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<INT>");
    grn_text_itoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</INT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_int8(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_int8(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

static void
grn_output_uint8(grn_ctx *ctx,
                 grn_obj *outbuf,
                 grn_content_type output_type,
                 uint8_t value)
{
  /* TODO */
  grn_output_uint32(ctx, outbuf, output_type, value);
}

static void
grn_output_int16(grn_ctx *ctx,
                 grn_obj *outbuf,
                 grn_content_type output_type,
                 int16_t value)
{
  /* TODO */
  grn_output_int32(ctx, outbuf, output_type, value);
}

static void
grn_output_uint16(grn_ctx *ctx,
                  grn_obj *outbuf,
                  grn_content_type output_type,
                  uint16_t value)
{
  /* TODO */
  grn_output_uint32(ctx, outbuf, output_type, value);
}

void
grn_output_int32(grn_ctx *ctx,
                 grn_obj *outbuf,
                 grn_content_type output_type,
                 int32_t value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_TSV:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<INT>");
    grn_text_itoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</INT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_int32(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_itoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_int32(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_uint32(grn_ctx *ctx,
                  grn_obj *outbuf,
                  grn_content_type output_type,
                  uint32_t value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_TSV:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<INT>");
    grn_text_ulltoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</INT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_uint32(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_uint32(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_int64(grn_ctx *ctx,
                 grn_obj *outbuf,
                 grn_content_type output_type,
                 int64_t value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_lltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_TSV:
    grn_text_lltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<INT>");
    grn_text_lltoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</INT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_int64(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_lltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_int64(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_uint64(grn_ctx *ctx,
                  grn_obj *outbuf,
                  grn_content_type output_type,
                  uint64_t value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_TSV:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<INT>");
    grn_text_ulltoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</INT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_uint64(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_ulltoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_uint64(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

#ifdef GRN_HAVE_BFLOAT16
void
grn_output_bfloat16(grn_ctx *ctx,
                    grn_obj *outbuf,
                    grn_content_type output_type,
                    grn_bfloat16 value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    switch (fpclassify(grn_bfloat16_to_float32(value))) {
    case FP_NAN:
    case FP_INFINITE:
      GRN_TEXT_PUTS(ctx, outbuf, "null");
      break;
    default:
      grn_text_bf16toa(ctx, outbuf, value);
      break;
    }
    break;
  case GRN_CONTENT_TSV:
    grn_text_bf16toa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<BFLOAT16>");
    grn_text_bf16toa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</BFLOAT16>");
    break;
  case GRN_CONTENT_MSGPACK:
#  ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_float(&ctx->impl->output.msgpacker,
                       grn_bfloat16_to_float32(value));
#  endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_bf16toa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_bfloat16(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}
#endif

void
grn_output_float32(grn_ctx *ctx,
                   grn_obj *outbuf,
                   grn_content_type output_type,
                   float value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    switch (fpclassify(value)) {
    case FP_NAN:
    case FP_INFINITE:
      GRN_TEXT_PUTS(ctx, outbuf, "null");
      break;
    default:
      grn_text_f32toa(ctx, outbuf, value);
      break;
    }
    break;
  case GRN_CONTENT_TSV:
    grn_text_f32toa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<FLOAT32>");
    grn_text_f32toa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</FLOAT32>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_float(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_f32toa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_float32(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_float(grn_ctx *ctx,
                 grn_obj *outbuf,
                 grn_content_type output_type,
                 double value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    switch (fpclassify(value)) {
    case FP_NAN:
    case FP_INFINITE:
      GRN_TEXT_PUTS(ctx, outbuf, "null");
      break;
    default:
      grn_text_ftoa(ctx, outbuf, value);
      break;
    }
    break;
  case GRN_CONTENT_TSV:
    grn_text_ftoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<FLOAT>");
    grn_text_ftoa(ctx, outbuf, value);
    GRN_TEXT_PUTS(ctx, outbuf, "</FLOAT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_double(&ctx->impl->output.msgpacker, value);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_ftoa(ctx, outbuf, value);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_float(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_str(grn_ctx *ctx,
               grn_obj *outbuf,
               grn_content_type output_type,
               const char *value,
               size_t value_len)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_esc(ctx, outbuf, value, value_len);
    break;
  case GRN_CONTENT_TSV:
    grn_text_esc(ctx, outbuf, value, value_len);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<TEXT>");
    grn_text_escape_xml(ctx, outbuf, value, value_len);
    GRN_TEXT_PUTS(ctx, outbuf, "</TEXT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_str(&ctx->impl->output.msgpacker, value_len);
    msgpack_pack_str_body(&ctx->impl->output.msgpacker, value, value_len);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    GRN_TEXT_PUT(ctx, outbuf, value, value_len);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_text(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        value,
        value_len);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_cstr(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                const char *value)
{
  grn_output_str(ctx, outbuf, output_type, value, strlen(value));
}

void
grn_output_bool(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                bool value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    GRN_TEXT_PUTS(ctx, outbuf, value ? "true" : "false");
    break;
  case GRN_CONTENT_TSV:
    GRN_TEXT_PUTS(ctx, outbuf, value ? "true" : "false");
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<BOOL>");
    GRN_TEXT_PUTS(ctx, outbuf, value ? "true" : "false");
    GRN_TEXT_PUTS(ctx, outbuf, "</BOOL>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    if (value) {
      msgpack_pack_true(&ctx->impl->output.msgpacker);
    } else {
      msgpack_pack_false(&ctx->impl->output.msgpacker);
    }
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    GRN_TEXT_PUTS(ctx, outbuf, value ? "true" : "false");
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_null(grn_ctx *ctx, grn_obj *outbuf, grn_content_type output_type)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    GRN_TEXT_PUTS(ctx, outbuf, "null");
    break;
  case GRN_CONTENT_TSV:
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<NULL/>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_nil(&ctx->impl->output.msgpacker);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

static inline void
grn_output_bulk_void(grn_ctx *ctx,
                     grn_obj *outbuf,
                     grn_content_type output_type,
                     const char *value,
                     size_t value_len)
{
  if (value_len == sizeof(grn_id) && *(grn_id *)value == GRN_ID_NIL) {
    grn_output_null(ctx, outbuf, output_type);
  } else {
    grn_output_str(ctx, outbuf, output_type, value, value_len);
  }
}

void
grn_output_time(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                int64_t value)
{
  double dv = (double)value;
  dv /= GRN_TIME_USEC_PER_SEC_F;
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    grn_text_ftoa(ctx, outbuf, dv);
    break;
  case GRN_CONTENT_TSV:
    grn_text_ftoa(ctx, outbuf, dv);
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<DATE>");
    grn_text_ftoa(ctx, outbuf, dv);
    GRN_TEXT_PUTS(ctx, outbuf, "</DATE>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    msgpack_pack_double(&ctx->impl->output.msgpacker, dv);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    grn_text_ftoa(ctx, outbuf, dv);
    break;
  case GRN_CONTENT_APACHE_ARROW:
    if (ctx->impl->output.arrow_stream_writer) {
      grn_timeval timeval;
      timeval.tv_sec = GRN_TIME_USEC_TO_SEC(value);
      timeval.tv_nsec =
        GRN_TIME_USEC_TO_NSEC((int32_t)(value % GRN_TIME_USEC_PER_SEC));
      grn_arrow_stream_writer_add_column_timestamp(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        timeval);
    }
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

void
grn_output_geo_point(grn_ctx *ctx,
                     grn_obj *outbuf,
                     grn_content_type output_type,
                     grn_geo_point *value)
{
  put_delimiter(ctx, outbuf, output_type);
  switch (output_type) {
  case GRN_CONTENT_JSON:
    if (value) {
      GRN_TEXT_PUTC(ctx, outbuf, '"');
      grn_text_itoa(ctx, outbuf, value->latitude);
      GRN_TEXT_PUTC(ctx, outbuf, 'x');
      grn_text_itoa(ctx, outbuf, value->longitude);
      GRN_TEXT_PUTC(ctx, outbuf, '"');
    } else {
      GRN_TEXT_PUTS(ctx, outbuf, "null");
    }
    break;
  case GRN_CONTENT_TSV:
    if (value) {
      GRN_TEXT_PUTC(ctx, outbuf, '"');
      grn_text_itoa(ctx, outbuf, value->latitude);
      GRN_TEXT_PUTC(ctx, outbuf, 'x');
      grn_text_itoa(ctx, outbuf, value->longitude);
      GRN_TEXT_PUTC(ctx, outbuf, '"');
    } else {
      GRN_TEXT_PUTS(ctx, outbuf, "\"\"");
    }
    break;
  case GRN_CONTENT_XML:
    GRN_TEXT_PUTS(ctx, outbuf, "<GEO_POINT>");
    if (value) {
      grn_text_itoa(ctx, outbuf, value->latitude);
      GRN_TEXT_PUTC(ctx, outbuf, 'x');
      grn_text_itoa(ctx, outbuf, value->longitude);
    }
    GRN_TEXT_PUTS(ctx, outbuf, "</GEO_POINT>");
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    if (value) {
      grn_obj buf;
      GRN_TEXT_INIT(&buf, 0);
      grn_text_itoa(ctx, &buf, value->latitude);
      GRN_TEXT_PUTC(ctx, &buf, 'x');
      grn_text_itoa(ctx, &buf, value->longitude);
      msgpack_pack_str(&ctx->impl->output.msgpacker, GRN_TEXT_LEN(&buf));
      msgpack_pack_str_body(&ctx->impl->output.msgpacker,
                            GRN_TEXT_VALUE(&buf),
                            GRN_TEXT_LEN(&buf));
      grn_obj_close(ctx, &buf);
    } else {
      msgpack_pack_nil(&ctx->impl->output.msgpacker);
    }
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    if (value) {
      GRN_TEXT_PUTC(ctx, outbuf, '"');
      grn_text_itoa(ctx, outbuf, value->latitude);
      GRN_TEXT_PUTC(ctx, outbuf, 'x');
      grn_text_itoa(ctx, outbuf, value->longitude);
      GRN_TEXT_PUTC(ctx, outbuf, '"');
    } else {
      GRN_TEXT_PUTS(ctx, outbuf, "\"\"");
    }
    break;
  case GRN_CONTENT_APACHE_ARROW:
    break;
  case GRN_CONTENT_NONE:
    break;
  }
  INCR_LENGTH;
}

static void
grn_output_record(grn_ctx *ctx,
                  grn_obj *outbuf,
                  grn_content_type output_type,
                  grn_obj *record)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_add_column_record(
        ctx,
        ctx->impl->output.arrow_stream_writer,
        record);
    }
  } else {
    grn_obj *table = grn_ctx_at(ctx, record->header.domain);
    grn_id id = GRN_RECORD_VALUE(record);
    if (table && table->header.type != GRN_TABLE_NO_KEY) {
      grn_obj buf;
      grn_obj *accessor = grn_obj_column(ctx,
                                         table,
                                         GRN_COLUMN_NAME_KEY,
                                         GRN_COLUMN_NAME_KEY_LEN);
      GRN_VOID_INIT(&buf);
      if (accessor) {
        if (id == GRN_ID_NIL) {
          grn_obj_reinit_for(ctx, &buf, accessor);
        } else {
          grn_obj_get_value(ctx, accessor, id, &buf);
        }
        grn_obj_unlink(ctx, accessor);
      }
      grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
      GRN_OBJ_FIN(ctx, &buf);
    } else {
      grn_output_int64(ctx, outbuf, output_type, id);
    }
    if (table) {
      grn_obj_unref(ctx, table);
    }
  }
}

typedef struct {
  grn_obj bulk;
  grn_obj vector;
  grn_obj sub_ids_stack;
  size_t next_sub_ids;
  size_t n_processed_records;
  bool auto_flush;
} grn_output_table_data;

static void
grn_output_table_data_init(grn_ctx *ctx,
                           grn_output_table_data *data,
                           grn_obj_format *format)
{
  GRN_TEXT_INIT(&(data->bulk), 0);
  GRN_TEXT_INIT(&(data->vector), GRN_OBJ_VECTOR);
  GRN_TEXT_INIT(&(data->sub_ids_stack), 0);
  data->next_sub_ids = 0;
  data->n_processed_records = 0;
  data->auto_flush = (format && (format->flags & GRN_OBJ_FORMAT_AUTO_FLUSH));
}

static grn_obj *
grn_output_table_data_push_sub_ids(grn_ctx *ctx, grn_output_table_data *data)
{
  grn_obj *sub_ids = NULL;
  size_t n_sub_ids = GRN_BULK_VSIZE(&(data->sub_ids_stack)) / sizeof(grn_obj);
  bool reuse = (data->next_sub_ids < n_sub_ids);
  if (!reuse) {
    grn_obj dummy = {0};
    grn_bulk_write(ctx,
                   &(data->sub_ids_stack),
                   (void *)&dummy,
                   sizeof(grn_obj));
  }
  grn_obj *sub_ids_stack_raw = (grn_obj *)GRN_BULK_HEAD(&(data->sub_ids_stack));
  sub_ids = &(sub_ids_stack_raw[data->next_sub_ids]);
  if (reuse) {
    GRN_BULK_REWIND(sub_ids);
  } else {
    GRN_RECORD_INIT(sub_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);
  }
  data->next_sub_ids++;
  return sub_ids;
}

static void
grn_output_table_data_pop_sub_ids(grn_ctx *ctx, grn_output_table_data *data)
{
  data->next_sub_ids--;
}

static void
grn_output_table_data_open_record(grn_ctx *ctx, grn_output_table_data *data)
{
  if (!data->auto_flush) {
    return;
  }
  if (data->n_processed_records == 0) {
    return;
  }

  if ((data->n_processed_records % grn_output_auto_flush_interval) == 0) {
    const int flags = 0;
    grn_ctx_output_flush(ctx, flags);
  }
}

static void
grn_output_table_data_close_record(grn_ctx *ctx, grn_output_table_data *data)
{
  data->n_processed_records++;
}

static void
grn_output_table_data_fin(grn_ctx *ctx, grn_output_table_data *data)
{
  GRN_OBJ_FIN(ctx, &(data->bulk));
  GRN_OBJ_FIN(ctx, &(data->vector));
  size_t i;
  size_t n_sub_ids = GRN_BULK_VSIZE(&(data->sub_ids_stack)) / sizeof(grn_obj);
  grn_obj *sub_ids_stack_raw = (grn_obj *)GRN_BULK_HEAD(&(data->sub_ids_stack));
  for (i = 0; i < n_sub_ids; i++) {
    GRN_OBJ_FIN(ctx, &(sub_ids_stack_raw[i]));
  }
  GRN_OBJ_FIN(ctx, &(data->sub_ids_stack));
}

static void
grn_output_table_column_value(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              grn_obj *obj,
                              grn_id id,
                              grn_output_table_data *data)
{
  uint32_t vs;
  grn_obj *bulk = &(data->bulk);
  grn_obj *vector = &(data->vector);
  if (obj->header.type == GRN_ACCESSOR) {
    grn_accessor *a = (grn_accessor *)obj;
    grn_obj_reinit(ctx, bulk, GRN_DB_TEXT, 0);
    for (;;) {
      bulk->header.domain = grn_obj_get_range(ctx, obj);
      GRN_BULK_REWIND(bulk);
      switch (a->action) {
      case GRN_ACCESSOR_GET_ID:
        GRN_UINT32_PUT(ctx, bulk, id);
        bulk->header.domain = GRN_DB_UINT32;
        break;
      case GRN_ACCESSOR_GET_KEY:
        grn_table_get_key2(ctx, a->obj, id, bulk);
        bulk->header.domain = DB_OBJ(a->obj)->header.domain;
        break;
      case GRN_ACCESSOR_GET_VALUE:
        grn_obj_get_value(ctx, a->obj, id, bulk);
        bulk->header.domain = DB_OBJ(a->obj)->range;
        break;
      case GRN_ACCESSOR_GET_SCORE:
        {
          double score = grn_table_get_score(ctx, a->obj, id);
          if (grn_ctx_get_command_version(ctx) == GRN_COMMAND_VERSION_1) {
            int32_t int32_score = (int32_t)score;
            GRN_INT32_PUT(ctx, bulk, int32_score);
            bulk->header.domain = GRN_DB_INT32;
          } else {
            GRN_FLOAT_PUT(ctx, bulk, score);
            bulk->header.domain = GRN_DB_FLOAT;
          }
        }
        break;
      case GRN_ACCESSOR_GET_NSUBRECS:
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          GRN_INT32_PUT(ctx, bulk, ri->n_subrecs);
        }
        bulk->header.domain = GRN_DB_INT32;
        break;
      case GRN_ACCESSOR_GET_MAX:
        bulk->header.domain = DB_OBJ(a->obj)->group.aggregated_value_type_id;
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          grn_rset_aggregated_value max =
            grn_rset_recinfo_get_max(ctx, ri, a->obj);
          if (bulk->header.domain == GRN_DB_INT64) {
            GRN_INT64_PUT(ctx, bulk, max.value_int64);
          } else {
            GRN_FLOAT_PUT(ctx, bulk, max.value_double);
          }
        }
        break;
      case GRN_ACCESSOR_GET_MIN:
        bulk->header.domain = DB_OBJ(a->obj)->group.aggregated_value_type_id;
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          grn_rset_aggregated_value min =
            grn_rset_recinfo_get_min(ctx, ri, a->obj);
          if (bulk->header.domain == GRN_DB_INT64) {
            GRN_INT64_PUT(ctx, bulk, min.value_int64);
          } else {
            GRN_FLOAT_PUT(ctx, bulk, min.value_double);
          }
        }
        break;
      case GRN_ACCESSOR_GET_SUM:
        bulk->header.domain = DB_OBJ(a->obj)->group.aggregated_value_type_id;
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          grn_rset_aggregated_value sum =
            grn_rset_recinfo_get_sum(ctx, ri, a->obj);
          if (bulk->header.domain == GRN_DB_INT64) {
            GRN_INT64_PUT(ctx, bulk, sum.value_int64);
          } else {
            GRN_FLOAT_PUT(ctx, bulk, sum.value_double);
          }
        }
        break;
      case GRN_ACCESSOR_GET_AVG:
      case GRN_ACCESSOR_GET_MEAN:
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          double mean;
          mean = grn_rset_recinfo_get_mean(ctx, ri, a->obj);
          GRN_FLOAT_PUT(ctx, bulk, mean);
        }
        bulk->header.domain = GRN_DB_FLOAT;
        break;
      case GRN_ACCESSOR_GET_COLUMN_VALUE:
        switch (a->obj->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) {
        case GRN_OBJ_COLUMN_VECTOR:
          if (a->next) {
            grn_obj *sub_ids = grn_output_table_data_push_sub_ids(ctx, data);
            sub_ids->header.domain = DB_OBJ(a->obj)->range;
            grn_obj_get_value(ctx, a->obj, id, sub_ids);
            size_t n = GRN_RECORD_VECTOR_SIZE(sub_ids);
            grn_output_array_open(ctx, outbuf, output_type, "VECTOR", (int)n);
            size_t i;
            for (i = 0; i < n; i++) {
              grn_id sub_id = GRN_RECORD_VALUE_AT(sub_ids, i);
              grn_output_table_column_value(ctx,
                                            outbuf,
                                            output_type,
                                            (grn_obj *)a->next,
                                            sub_id,
                                            data);
            }
            grn_output_table_data_pop_sub_ids(ctx, data);
            grn_output_array_close(ctx, outbuf, output_type);
          } else {
            grn_output_table_column_value(ctx,
                                          outbuf,
                                          output_type,
                                          a->obj,
                                          id,
                                          data);
          }
          return;
        case GRN_OBJ_COLUMN_INDEX:
          if (a->next) {
            grn_ii *ii = (grn_ii *)(a->obj);
            grn_obj *sub_ids = grn_output_table_data_push_sub_ids(ctx, data);
            sub_ids->header.domain = DB_OBJ(ii)->range;
            grn_ii_cursor *cursor =
              grn_ii_cursor_open(ctx,
                                 ii,
                                 id,
                                 GRN_ID_NIL,
                                 GRN_ID_MAX,
                                 (int)grn_ii_get_n_elements(ctx, ii),
                                 0);
            if (cursor) {
              grn_posting *posting;
              while ((posting = grn_ii_cursor_next(ctx, cursor))) {
                GRN_RECORD_PUT(ctx, sub_ids, posting->rid);
              }
              grn_ii_cursor_close(ctx, cursor);
            }
            size_t n = GRN_RECORD_VECTOR_SIZE(sub_ids);
            grn_output_array_open(ctx, outbuf, output_type, "VECTOR", (int)n);
            size_t i;
            for (i = 0; i < n; i++) {
              grn_id sub_id = GRN_RECORD_VALUE_AT(sub_ids, i);
              grn_output_table_column_value(ctx,
                                            outbuf,
                                            output_type,
                                            (grn_obj *)(a->next),
                                            sub_id,
                                            data);
            }
            grn_output_table_data_pop_sub_ids(ctx, data);
            grn_output_array_close(ctx, outbuf, output_type);
            return;
          } else {
            grn_obj_get_value(ctx, a->obj, id, bulk);
            break;
          }
        default:
          grn_obj_get_value(ctx, a->obj, id, bulk);
          break;
        }
        break;
      case GRN_ACCESSOR_GET_DB_OBJ:
        /* todo */
        break;
      case GRN_ACCESSOR_LOOKUP:
        /* todo */
        break;
      case GRN_ACCESSOR_FUNCALL:
        /* todo */
        break;
      }
      if (a->next) {
        a = a->next;
        if (GRN_BULK_VSIZE(bulk) >= sizeof(grn_id)) {
          id = *((grn_id *)GRN_BULK_HEAD(bulk));
        } else {
          id = GRN_ID_NIL;
        }
      } else {
        break;
      }
    }
    grn_output_obj(ctx, outbuf, output_type, bulk, NULL);
  } else {
    grn_obj *buf = NULL;
    grn_obj_format *format_argument = NULL;
    grn_obj_format format;
    GRN_OBJ_FORMAT_INIT(&format, 0, 0, 0, 0);
    switch (obj->header.type) {
    case GRN_COLUMN_FIX_SIZE:
      grn_obj_reinit(ctx, bulk, DB_OBJ(obj)->range, 0);
      buf = bulk;
      break;
    case GRN_COLUMN_VAR_SIZE:
      {
        grn_column_flags flags = grn_column_get_flags(ctx, obj);
        if ((flags & GRN_OBJ_COLUMN_TYPE_MASK) == GRN_OBJ_COLUMN_VECTOR) {
          bool is_var_size_element =
            grn_type_id_is_text_family(ctx, DB_OBJ(obj)->range);
          if (is_var_size_element) {
            buf = vector;
          } else {
            buf = bulk;
          }
          grn_obj_reinit(ctx, buf, DB_OBJ(obj)->range, GRN_OBJ_VECTOR);
          if (flags & GRN_OBJ_WITH_WEIGHT) {
            format.flags |= GRN_OBJ_FORMAT_WITH_WEIGHT;
            if (flags & (GRN_OBJ_WEIGHT_FLOAT32 | GRN_OBJ_WEIGHT_BFLOAT16)) {
              format.flags |= GRN_OBJ_FORMAT_WEIGHT_FLOAT32;
            }
            format_argument = &format;
          }
        } else {
          grn_obj_reinit(ctx, bulk, DB_OBJ(obj)->range, 0);
          buf = bulk;
        }
      }
      break;
    case GRN_COLUMN_INDEX:
      grn_obj_reinit(ctx, bulk, GRN_DB_UINT32, 0);
      buf = bulk;
      break;
    default:
      grn_obj_reinit(ctx, bulk, GRN_DB_TEXT, 0);
      buf = bulk;
      break;
    }
    grn_obj_get_value(ctx, obj, id, buf);
    grn_output_obj(ctx, outbuf, output_type, buf, format_argument);
  }
}

static inline void
grn_output_void(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                grn_obj *bulk,
                grn_obj_format *format)
{
  grn_output_null(ctx, outbuf, output_type);
}

static inline void
grn_output_bulk(grn_ctx *ctx,
                grn_obj *outbuf,
                grn_content_type output_type,
                grn_obj *bulk,
                grn_obj_format *format)
{
  grn_obj buf;
  GRN_TEXT_INIT(&buf, 0);
  switch (bulk->header.domain) {
  case GRN_DB_VOID:
    grn_output_bulk_void(ctx,
                         outbuf,
                         output_type,
                         GRN_BULK_HEAD(bulk),
                         GRN_BULK_VSIZE(bulk));
    break;
  case GRN_DB_SHORT_TEXT:
  case GRN_DB_TEXT:
  case GRN_DB_LONG_TEXT:
    grn_output_str(ctx,
                   outbuf,
                   output_type,
                   GRN_BULK_HEAD(bulk),
                   GRN_BULK_VSIZE(bulk));
    break;
  case GRN_DB_BOOL:
    grn_output_bool(ctx,
                    outbuf,
                    output_type,
                    GRN_BULK_VSIZE(bulk) ? GRN_UINT8_VALUE(bulk) : 0);
    break;
  case GRN_DB_INT8:
    grn_output_int8(ctx,
                    outbuf,
                    output_type,
                    GRN_BULK_VSIZE(bulk) ? GRN_INT8_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT8:
    grn_output_uint8(ctx,
                     outbuf,
                     output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_UINT8_VALUE(bulk) : 0);
    break;
  case GRN_DB_INT16:
    grn_output_int16(ctx,
                     outbuf,
                     output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_INT16_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT16:
    grn_output_uint16(ctx,
                      outbuf,
                      output_type,
                      GRN_BULK_VSIZE(bulk) ? GRN_UINT16_VALUE(bulk) : 0);
    break;
  case GRN_DB_INT32:
    grn_output_int32(ctx,
                     outbuf,
                     output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_INT32_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT32:
    grn_output_uint32(ctx,
                      outbuf,
                      output_type,
                      GRN_BULK_VSIZE(bulk) ? GRN_UINT32_VALUE(bulk) : 0);
    break;
  case GRN_DB_INT64:
    grn_output_int64(ctx,
                     outbuf,
                     output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_INT64_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT64:
    grn_output_uint64(ctx,
                      outbuf,
                      output_type,
                      GRN_BULK_VSIZE(bulk) ? GRN_UINT64_VALUE(bulk) : 0);
    break;
#ifdef GRN_HAVE_BFLOAT16
  case GRN_DB_BFLOAT16:
    grn_output_bfloat16(ctx,
                        outbuf,
                        output_type,
                        GRN_BULK_VSIZE(bulk) > 0 ? GRN_BFLOAT16_VALUE(bulk)
                                                 : 0);
    break;
#endif
  case GRN_DB_FLOAT32:
    grn_output_float32(ctx,
                       outbuf,
                       output_type,
                       GRN_BULK_VSIZE(bulk) ? GRN_FLOAT32_VALUE(bulk) : 0);
    break;
  case GRN_DB_FLOAT:
    grn_output_float(ctx,
                     outbuf,
                     output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_FLOAT_VALUE(bulk) : 0);
    break;
  case GRN_DB_TIME:
    grn_output_time(ctx,
                    outbuf,
                    output_type,
                    GRN_BULK_VSIZE(bulk) ? GRN_INT64_VALUE(bulk) : 0);
    break;
  case GRN_DB_TOKYO_GEO_POINT:
  case GRN_DB_WGS84_GEO_POINT:
    grn_output_geo_point(
      ctx,
      outbuf,
      output_type,
      GRN_BULK_VSIZE(bulk) ? (grn_geo_point *)GRN_BULK_HEAD(bulk) : NULL);
    break;
  default:
    if (format) {
      size_t j;
      size_t ncolumns = GRN_BULK_VSIZE(&format->columns) / sizeof(grn_obj *);
      grn_id id = GRN_RECORD_VALUE(bulk);
      grn_obj **columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);
      if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
        grn_output_array_open(ctx,
                              outbuf,
                              output_type,
                              "COLUMNS",
                              (int)ncolumns);
        for (j = 0; j < ncolumns; j++) {
          grn_id range_id;
          grn_output_array_open(ctx, outbuf, output_type, "COLUMN", 2);
          GRN_BULK_REWIND(&buf);
          grn_column_name_(ctx, columns[j], &buf);
          grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
          /* column range */
          range_id = grn_obj_get_range(ctx, columns[j]);
          if (range_id == GRN_ID_NIL) {
            GRN_TEXT_PUTS(ctx, outbuf, "null");
          } else {
            int name_len;
            grn_obj *range_obj;
            char name_buf[GRN_TABLE_MAX_KEY_SIZE];

            range_obj = grn_ctx_at(ctx, range_id);
            name_len =
              grn_obj_name(ctx, range_obj, name_buf, GRN_TABLE_MAX_KEY_SIZE);
            GRN_BULK_REWIND(&buf);
            GRN_TEXT_PUT(ctx, &buf, name_buf, name_len);
            grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
          }
          grn_output_array_close(ctx, outbuf, output_type);
        }
        grn_output_array_close(ctx, outbuf, output_type);
      }
      grn_output_array_open(ctx, outbuf, output_type, "HIT", (int)ncolumns);
      {
        grn_output_table_data data;
        grn_output_table_data_init(ctx, &data, format);
        for (j = 0; j < ncolumns; j++) {
          grn_output_table_column_value(ctx,
                                        outbuf,
                                        output_type,
                                        columns[j],
                                        id,
                                        &data);
        }
        grn_output_table_data_fin(ctx, &data);
      }
      grn_output_array_close(ctx, outbuf, output_type);
    } else {
      grn_output_record(ctx, outbuf, output_type, bulk);
    }
    break;
  }
  GRN_OBJ_FIN(ctx, &buf);
}

static void
grn_output_uvector_result_set(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              grn_obj *uvector,
                              grn_obj_format *format)
{
  uint32_t i_hit, n_hits;
  size_t i_column, n_columns;
  uint32_t n_elements;
  grn_obj **columns;
  grn_obj buf;
  bool with_column_names = false;

  n_hits = grn_vector_size(ctx, uvector);

  n_columns = GRN_BULK_VSIZE(&format->columns) / sizeof(grn_obj *);
  columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);

  GRN_TEXT_INIT(&buf, 0);

  if (n_hits > 0 && format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
    with_column_names = true;
  }

  n_elements = 1; /* for NHITS */
  if (with_column_names) {
    n_elements += 1; /* for COLUMNS */
  }
  n_elements += n_hits; /* for HITS */
  grn_output_array_open(ctx, outbuf, output_type, "RESULTSET", (int)n_elements);

  grn_output_array_open(ctx, outbuf, output_type, "NHITS", 1);
  grn_text_itoa(ctx, outbuf, (int)n_hits);
  grn_output_array_close(ctx, outbuf, output_type);

  if (with_column_names) {
    grn_output_array_open(ctx, outbuf, output_type, "COLUMNS", (int)n_columns);
    for (i_column = 0; i_column < n_columns; i_column++) {
      grn_id range_id;
      grn_output_array_open(ctx, outbuf, output_type, "COLUMN", 2);

      /* name */
      GRN_BULK_REWIND(&buf);
      grn_column_name_(ctx, columns[i_column], &buf);
      grn_output_obj(ctx, outbuf, output_type, &buf, NULL);

      /* type */
      range_id = grn_obj_get_range(ctx, columns[i_column]);
      if (range_id == GRN_ID_NIL) {
        GRN_TEXT_PUTS(ctx, outbuf, "null");
      } else {
        int name_len;
        grn_obj *range_obj;
        char name_buf[GRN_TABLE_MAX_KEY_SIZE];

        range_obj = grn_ctx_at(ctx, range_id);
        name_len =
          grn_obj_name(ctx, range_obj, name_buf, GRN_TABLE_MAX_KEY_SIZE);
        GRN_BULK_REWIND(&buf);
        GRN_TEXT_PUT(ctx, &buf, name_buf, name_len);
        grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
      }

      grn_output_array_close(ctx, outbuf, output_type);
    }
    grn_output_array_close(ctx, outbuf, output_type);
  }

  for (i_hit = 0; i_hit < n_hits++; i_hit++) {
    grn_id id;

    id = grn_uvector_get_element(ctx, uvector, i_hit, NULL);
    grn_output_array_open(ctx, outbuf, output_type, "HITS", (int)n_columns);
    for (i_column = 0; i_column < n_columns; i_column++) {
      GRN_BULK_REWIND(&buf);
      grn_obj_get_value(ctx, columns[i_column], id, &buf);
      grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
    }
    grn_output_array_close(ctx, outbuf, output_type);
  }

  grn_output_array_close(ctx, outbuf, output_type);

  GRN_OBJ_FIN(ctx, &buf);
}

static inline void
grn_output_uvector_apache_arrow(grn_ctx *ctx, grn_obj *outbuf, grn_obj *uvector)
{
  if (!ctx->impl->output.arrow_stream_writer) {
    return;
  }

  grn_arrow_stream_writer_add_column_uvector(
    ctx,
    ctx->impl->output.arrow_stream_writer,
    uvector);
}

static inline void
grn_output_uvector(grn_ctx *ctx,
                   grn_obj *outbuf,
                   grn_content_type output_type,
                   grn_obj *uvector,
                   grn_obj_format *format)
{
  bool output_result_set = false;
  bool with_weight = false;
  bool is_weight_float32 = false;

  if (format) {
    if (GRN_BULK_VSIZE(&(format->columns)) > 0) {
      output_result_set = true;
    }
    if (format->flags & GRN_OBJ_FORMAT_WITH_WEIGHT) {
      with_weight = true;
    }
    if (format->flags & GRN_OBJ_FORMAT_WEIGHT_FLOAT32) {
      is_weight_float32 = true;
    }
  }

  if (output_result_set) {
    grn_output_uvector_result_set(ctx, outbuf, output_type, uvector, format);
    return;
  }

  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_output_uvector_apache_arrow(ctx, outbuf, uvector);
    return;
  }

  if (grn_type_id_is_builtin(ctx, uvector->header.domain)) {
    size_t i, n;
    char *raw_elements;
    size_t element_size;
    grn_obj element;

    raw_elements = GRN_BULK_HEAD(uvector);
    element_size = grn_type_id_size(ctx, uvector->header.domain);
    n = GRN_BULK_VSIZE(uvector) / element_size;

    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", (int)n);
    GRN_OBJ_INIT(&element, GRN_BULK, 0, uvector->header.domain);
    for (i = 0; i < n; i++) {
      GRN_BULK_REWIND(&element);
      grn_bulk_write_from(ctx,
                          &element,
                          raw_elements + (element_size * i),
                          0,
                          element_size);
      grn_output_obj(ctx, outbuf, output_type, &element, NULL);
    }
    GRN_OBJ_FIN(ctx, &element);
    grn_output_array_close(ctx, outbuf, output_type);
  } else {
    grn_obj *range = grn_ctx_at(ctx, uvector->header.domain);

    uint32_t i, n;
    grn_obj id_value;
    grn_obj key_value;

    GRN_UINT32_INIT(&id_value, 0);
    GRN_OBJ_INIT(&key_value, GRN_BULK, 0, range->header.domain);

    n = grn_vector_size(ctx, uvector);
    if (with_weight) {
      grn_output_map_open(ctx, outbuf, output_type, "WEIGHT_VECTOR", (int)n);
    } else {
      grn_output_array_open(ctx, outbuf, output_type, "VECTOR", (int)n);
    }

    for (i = 0; i < n; i++) {
      grn_id id;
      float weight;

      id = grn_uvector_get_element_record(ctx, uvector, i, &weight);
      if (range->header.type == GRN_TABLE_NO_KEY) {
        GRN_UINT32_SET(ctx, &id_value, id);
        grn_output_obj(ctx, outbuf, output_type, &id_value, NULL);
      } else {
        GRN_BULK_REWIND(&key_value);
        grn_table_get_key2(ctx, range, id, &key_value);
        grn_output_obj(ctx, outbuf, output_type, &key_value, NULL);
      }

      if (with_weight) {
        if (is_weight_float32) {
          grn_output_float32(ctx, outbuf, output_type, weight);
        } else {
          grn_output_uint64(ctx, outbuf, output_type, (uint64_t)weight);
        }
      }
    }

    if (with_weight) {
      grn_output_map_close(ctx, outbuf, output_type);
    } else {
      grn_output_array_close(ctx, outbuf, output_type);
    }

    GRN_OBJ_FIN(ctx, &id_value);
    GRN_OBJ_FIN(ctx, &key_value);

    grn_obj_unref(ctx, range);
  }
}

static inline void
grn_output_vector(grn_ctx *ctx,
                  grn_obj *outbuf,
                  grn_content_type output_type,
                  grn_obj *vector,
                  grn_obj_format *format)
{
  bool with_weight = false;
  bool is_weight_float32 = false;

  if (format) {
    if (format->flags & GRN_OBJ_FORMAT_WITH_WEIGHT) {
      with_weight = true;
    }
    if (format->flags & GRN_OBJ_FORMAT_WEIGHT_FLOAT32) {
      is_weight_float32 = true;
    }
  }

  if (with_weight) {
    uint32_t i, n;
    grn_obj value;

    GRN_VOID_INIT(&value);
    n = grn_vector_size(ctx, vector);
    grn_output_map_open(ctx, outbuf, output_type, "WEIGHT_VECTOR", (int)n);
    for (i = 0; i < n; i++) {
      const char *_value;
      uint32_t weight;
      float weight_float;
      unsigned int length;
      grn_id domain;

      if (is_weight_float32) {
        length = grn_vector_get_element_float(ctx,
                                              vector,
                                              i,
                                              &_value,
                                              &weight_float,
                                              &domain);
      } else {
        length =
          grn_vector_get_element(ctx, vector, i, &_value, &weight, &domain);
      }

      if (domain != GRN_DB_VOID) {
        grn_obj_reinit(ctx, &value, domain, 0);
      } else {
        grn_obj_reinit(ctx, &value, vector->header.domain, 0);
      }
      grn_bulk_write(ctx, &value, _value, length);
      grn_output_obj(ctx, outbuf, output_type, &value, NULL);
      if (is_weight_float32) {
        grn_output_float32(ctx, outbuf, output_type, weight_float);
      } else {
        grn_output_uint64(ctx, outbuf, output_type, weight);
      }
    }
    grn_output_map_close(ctx, outbuf, output_type);
    GRN_OBJ_FIN(ctx, &value);
  } else {
    uint32_t i, n;
    grn_obj value;
    GRN_VOID_INIT(&value);
    n = grn_vector_size(ctx, vector);
    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", (int)n);
    for (i = 0; i < n; i++) {
      const char *_value;
      uint32_t weight;
      unsigned int length;
      grn_id domain;

      length =
        grn_vector_get_element(ctx, vector, i, &_value, &weight, &domain);
      if (domain != GRN_DB_VOID) {
        grn_obj_reinit(ctx, &value, domain, 0);
      } else {
        grn_obj_reinit(ctx, &value, vector->header.domain, 0);
      }
      grn_bulk_write(ctx, &value, _value, length);
      grn_output_obj(ctx, outbuf, output_type, &value, NULL);
    }
    grn_output_array_close(ctx, outbuf, output_type);
    GRN_OBJ_FIN(ctx, &value);
  }
}

static inline void
grn_output_pvector(grn_ctx *ctx,
                   grn_obj *outbuf,
                   grn_content_type output_type,
                   grn_obj *pvector,
                   grn_obj_format *format)
{
  if (format) {
    ERR(GRN_FUNCTION_NOT_IMPLEMENTED,
        "cannot print GRN_PVECTOR using grn_obj_format");
  } else {
    size_t i, n;
    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", -1);
    n = GRN_PTR_VECTOR_SIZE(pvector);
    for (i = 0; i < n; i++) {
      grn_obj *value;

      value = GRN_PTR_VALUE_AT(pvector, i);
      grn_output_obj(ctx, outbuf, output_type, value, NULL);
    }
    grn_output_array_close(ctx, outbuf, output_type);
  }
}

static inline void
grn_output_result_set_n_hits_v1(grn_ctx *ctx,
                                grn_obj *outbuf,
                                grn_content_type output_type,
                                grn_obj_format *format)
{
  grn_output_array_open(ctx, outbuf, output_type, "NHITS", 1);
  if (output_type == GRN_CONTENT_XML) {
    grn_text_itoa(ctx, outbuf, format->nhits);
  } else {
    grn_output_int32(ctx, outbuf, output_type, format->nhits);
  }
  grn_output_array_close(ctx, outbuf, output_type);
}

static inline void
grn_output_result_set_n_hits_v3(grn_ctx *ctx,
                                grn_obj *outbuf,
                                grn_content_type output_type,
                                grn_obj_format *format)
{
  grn_output_cstr(ctx, outbuf, output_type, "n_hits");
  grn_output_int32(ctx, outbuf, output_type, format->nhits);
}

static inline void
grn_output_result_set_n_hits(grn_ctx *ctx,
                             grn_obj *outbuf,
                             grn_content_type output_type,
                             grn_obj_format *format)
{
  if (format->nhits == -1) {
    return;
  }

  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_obj buffer;
    GRN_TEXT_INIT(&buffer, 0);
    grn_text_itoa(ctx, &buffer, format->nhits);
    GRN_TEXT_PUTC(ctx, &buffer, '\0');
    grn_arrow_stream_writer_add_metadata(ctx,
                                         ctx->impl->output.arrow_stream_writer,
                                         "GROONGA:n_hits",
                                         GRN_TEXT_VALUE(&buffer));
    GRN_OBJ_FIN(ctx, &buffer);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_result_set_n_hits_v1(ctx, outbuf, output_type, format);
  } else {
    grn_output_result_set_n_hits_v3(ctx, outbuf, output_type, format);
  }
}

static inline void
grn_output_table_column_info(grn_ctx *ctx,
                             grn_obj *outbuf,
                             grn_content_type output_type,
                             const char *name,
                             const char *type,
                             grn_obj *column)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_arrow_stream_writer_add_field(ctx,
                                      ctx->impl->output.arrow_stream_writer,
                                      name,
                                      column);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_array_open(ctx, outbuf, output_type, "COLUMN", 2);
    if (name) {
      grn_output_cstr(ctx, outbuf, output_type, name);
    } else {
      grn_output_null(ctx, outbuf, output_type);
    }
    if (type) {
      grn_output_cstr(ctx, outbuf, output_type, type);
    } else {
      grn_output_null(ctx, outbuf, output_type);
    }
    grn_output_array_close(ctx, outbuf, output_type);
  } else {
    grn_output_map_open(ctx, outbuf, output_type, "column", 2);
    grn_output_cstr(ctx, outbuf, output_type, "name");
    if (name) {
      grn_output_cstr(ctx, outbuf, output_type, name);
    } else {
      grn_output_null(ctx, outbuf, output_type);
    }
    grn_output_cstr(ctx, outbuf, output_type, "type");
    if (type) {
      grn_output_cstr(ctx, outbuf, output_type, type);
    } else {
      grn_output_null(ctx, outbuf, output_type);
    }
    grn_output_map_close(ctx, outbuf, output_type);
  }
}

static bool
is_score_accessor(grn_ctx *ctx, grn_obj *obj)
{
  grn_accessor *a;

  if (obj->header.type != GRN_ACCESSOR) {
    return false;
  }

  for (a = (grn_accessor *)obj; a->next; a = a->next) {
  }
  return a->action == GRN_ACCESSOR_GET_SCORE;
}

static inline void
grn_output_table_column(grn_ctx *ctx,
                        grn_obj *outbuf,
                        grn_content_type output_type,
                        grn_obj *column,
                        grn_obj *buf)
{
  grn_id range_id = GRN_ID_NIL;

  if (!column) {
    grn_output_table_column_info(ctx, outbuf, output_type, NULL, NULL, NULL);
    return;
  }

  GRN_BULK_REWIND(buf);
  grn_column_name_(ctx, column, buf);
  GRN_TEXT_PUTC(ctx, buf, '\0');

  if (column->header.type == GRN_COLUMN_INDEX) {
    range_id = GRN_DB_UINT32;
  } else if (is_score_accessor(ctx, column)) {
    if (grn_ctx_get_command_version(ctx) == GRN_COMMAND_VERSION_1) {
      range_id = GRN_DB_INT32;
    } else {
      range_id = GRN_DB_FLOAT;
    }
  }
  if (range_id == GRN_ID_NIL) {
    range_id = grn_obj_get_range(ctx, column);
  }
  if (range_id == GRN_ID_NIL) {
    grn_output_table_column_info(ctx,
                                 outbuf,
                                 output_type,
                                 GRN_TEXT_VALUE(buf),
                                 NULL,
                                 column);
  } else {
    grn_obj *range_obj;
    char type_name[GRN_TABLE_MAX_KEY_SIZE];
    int type_name_len;

    range_obj = grn_ctx_at(ctx, range_id);
    type_name_len =
      grn_obj_name(ctx, range_obj, type_name, GRN_TABLE_MAX_KEY_SIZE);
    type_name[type_name_len] = '\0';
    grn_output_table_column_info(ctx,
                                 outbuf,
                                 output_type,
                                 GRN_TEXT_VALUE(buf),
                                 type_name,
                                 column);
    grn_obj_unref(ctx, range_obj);
  }
}

static inline void
grn_output_table_column_by_expression(grn_ctx *ctx,
                                      grn_obj *outbuf,
                                      grn_content_type output_type,
                                      grn_obj *expr,
                                      grn_obj *buf)
{
  grn_expr *e = (grn_expr *)expr;
  if (e->codes_curr == 1 && e->codes[0].op == GRN_OP_GET_VALUE) {
    grn_output_table_column(ctx, outbuf, output_type, e->codes[0].value, buf);
    return;
  } else if (e->codes_curr == 3 && e->codes[2].op == GRN_OP_GET_MEMBER) {
    GRN_BULK_REWIND(buf);
    grn_column_name_(ctx, e->codes[0].value, buf);
    GRN_TEXT_PUTC(ctx, buf, '[');
    grn_inspect(ctx, buf, e->codes[1].value);
    GRN_TEXT_PUTC(ctx, buf, ']');
    GRN_TEXT_PUTC(ctx, buf, '\0');

    grn_output_table_column_info(ctx,
                                 outbuf,
                                 output_type,
                                 GRN_TEXT_VALUE(buf),
                                 NULL,
                                 e->codes[0].value);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_obj *column = NULL;
    if (e->codes_curr > 0) {
      column = e->codes[0].value;
    }
    grn_output_table_column(ctx, outbuf, output_type, column, buf);
    return;
  }

  grn_id range_id = grn_obj_get_range(ctx, expr);
  grn_obj *range_obj = NULL;
  char type_name[GRN_TABLE_MAX_KEY_SIZE];
  int type_name_len = 0;
  grn_obj name_buffer;
  GRN_TEXT_INIT(&name_buffer, 0);
  const char *name = NULL;

  if (range_id != GRN_ID_NIL) {
    range_obj = grn_ctx_at(ctx, range_id);
    type_name_len =
      grn_obj_name(ctx, range_obj, type_name, GRN_TABLE_MAX_KEY_SIZE);
    type_name[type_name_len] = '\0';
  }
  grn_expr_to_script_syntax(ctx, expr, &name_buffer);
  if (GRN_TEXT_LEN(&name_buffer) > 0) {
    GRN_TEXT_PUTC(ctx, &name_buffer, '\0');
    name = GRN_TEXT_VALUE(&name_buffer);
  }
  grn_output_table_column_info(ctx,
                               outbuf,
                               output_type,
                               name,
                               type_name_len > 0 ? type_name : NULL,
                               expr);
  if (range_obj) {
    grn_obj_unref(ctx, range_obj);
  }
  GRN_OBJ_FIN(ctx, &name_buffer);
}

static inline void
grn_output_table_columns_open(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              int n_columns)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_array_open(ctx, outbuf, output_type, "COLUMNS", n_columns);
  } else {
    grn_output_cstr(ctx, outbuf, output_type, "columns");
    grn_output_array_open(ctx, outbuf, output_type, "columns", n_columns);
  }
}

static inline void
grn_output_table_columns_close(grn_ctx *ctx,
                               grn_obj *outbuf,
                               grn_content_type output_type)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_arrow_stream_writer_write_schema(ctx,
                                         ctx->impl->output.arrow_stream_writer);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_array_close(ctx, outbuf, output_type);
  } else {
    grn_output_array_close(ctx, outbuf, output_type);
  }
}

static inline void
grn_output_table_columns_by_expression(grn_ctx *ctx,
                                       grn_obj *outbuf,
                                       grn_content_type output_type,
                                       grn_obj *table,
                                       grn_obj_format *format,
                                       grn_obj *buf)
{
  grn_expr *expr = (grn_expr *)format->expression;
  grn_obj offsets;
  size_t i, n;

  GRN_UINT32_INIT(&offsets, GRN_OBJ_VECTOR);
  grn_output_columns_get_offsets(ctx, format->expression, &offsets);

  n = GRN_UINT32_VECTOR_SIZE(&offsets) / 2;

  grn_output_table_columns_open(ctx, outbuf, output_type, (int)n);

  grn_expr_code *codes = expr->codes;
  uint32_t codes_curr = expr->codes_curr;
  for (i = 0; i < n; i++) {
    uint32_t code_start_offset = GRN_UINT32_VALUE_AT(&offsets, i * 2);
    uint32_t code_end_offset = GRN_UINT32_VALUE_AT(&offsets, i * 2 + 1);
    expr->codes = codes + code_start_offset;
    expr->codes_curr = code_end_offset - code_start_offset;
    grn_output_table_column_by_expression(ctx,
                                          outbuf,
                                          output_type,
                                          (grn_obj *)expr,
                                          buf);
  }
  expr->codes = codes;
  expr->codes_curr = codes_curr;

  grn_output_table_columns_close(ctx, outbuf, output_type);

  GRN_OBJ_FIN(ctx, &offsets);
}

static inline void
grn_output_table_columns_by_columns(grn_ctx *ctx,
                                    grn_obj *outbuf,
                                    grn_content_type output_type,
                                    grn_obj *table,
                                    grn_obj_format *format,
                                    grn_obj *buf)
{
  size_t i;
  size_t ncolumns = GRN_PTR_VECTOR_SIZE(&format->columns);
  grn_obj **columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);

  grn_output_table_columns_open(ctx, outbuf, output_type, (int)ncolumns);
  for (i = 0; i < ncolumns; i++) {
    grn_output_table_column(ctx, outbuf, output_type, columns[i], buf);
  }
  grn_output_table_columns_close(ctx, outbuf, output_type);
}

void
grn_output_table_columns(grn_ctx *ctx,
                         grn_obj *outbuf,
                         grn_content_type output_type,
                         grn_obj *table,
                         grn_obj_format *format)
{
  grn_obj buf;

  GRN_TEXT_INIT(&buf, 0);
  if (format->expression) {
    grn_output_table_columns_by_expression(ctx,
                                           outbuf,
                                           output_type,
                                           table,
                                           format,
                                           &buf);
  } else {
    grn_output_table_columns_by_columns(ctx,
                                        outbuf,
                                        output_type,
                                        table,
                                        format,
                                        &buf);
  }
  GRN_OBJ_FIN(ctx, &buf);
}

static inline void
grn_output_table_record_open(grn_ctx *ctx,
                             grn_obj *outbuf,
                             grn_content_type output_type,
                             int n_columns)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_arrow_stream_writer_open_record(ctx,
                                        ctx->impl->output.arrow_stream_writer);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_array_open(ctx, outbuf, output_type, "HIT", n_columns);
  } else {
    grn_output_array_open(ctx, outbuf, output_type, "record", n_columns);
  }
}

static inline void
grn_output_table_record_close(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    grn_arrow_stream_writer_close_record(ctx,
                                         ctx->impl->output.arrow_stream_writer);
    return;
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_array_close(ctx, outbuf, output_type);
  } else {
    grn_output_array_close(ctx, outbuf, output_type);
  }
}

static inline void
grn_output_table_record_by_column(grn_ctx *ctx,
                                  grn_obj *outbuf,
                                  grn_content_type output_type,
                                  grn_obj *column,
                                  grn_id id,
                                  grn_output_table_data *data)
{
  grn_output_table_column_value(ctx, outbuf, output_type, column, id, data);
}

static inline void
grn_output_table_record_by_expression(grn_ctx *ctx,
                                      grn_obj *outbuf,
                                      grn_content_type output_type,
                                      grn_obj *expression,
                                      uint32_t code_start_offset,
                                      uint32_t code_end_offset,
                                      grn_obj *record,
                                      grn_output_table_data *data)
{
  grn_expr *expr = (grn_expr *)expression;
  grn_expr_code *codes = expr->codes;
  uint32_t codes_curr = expr->codes_curr;

  expr->codes += code_start_offset;
  expr->codes_curr = code_end_offset - code_start_offset;

  if (expr->codes_curr == 1 && expr->codes[0].op == GRN_OP_GET_VALUE) {
    grn_obj *column = expr->codes[0].value;
    grn_output_table_record_by_column(ctx,
                                      outbuf,
                                      output_type,
                                      column,
                                      GRN_RECORD_VALUE(record),
                                      data);
  } else {
    grn_obj *result;
    result = grn_expr_exec(ctx, expression, 0);
    if (result) {
      grn_output_obj(ctx, outbuf, output_type, result, NULL);
    } else {
      grn_output_cstr(ctx, outbuf, output_type, ctx->errbuf);
    }
  }

  expr->codes = codes;
  expr->codes_curr = codes_curr;
}

static inline void
grn_output_table_records_by_expression(grn_ctx *ctx,
                                       grn_obj *outbuf,
                                       grn_content_type output_type,
                                       grn_table_cursor *tc,
                                       grn_obj_format *format,
                                       grn_output_table_data *data)
{
  grn_obj offsets;
  size_t n;
  grn_id id;
  grn_obj *record;

  GRN_UINT32_INIT(&offsets, GRN_OBJ_VECTOR);
  grn_output_columns_get_offsets(ctx, format->expression, &offsets);
  n = GRN_UINT32_VECTOR_SIZE(&offsets) / 2;
  record = grn_expr_get_var_by_offset(ctx, format->expression, 0);
  while ((id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL) {
    grn_output_table_data_open_record(ctx, data);
    GRN_RECORD_SET(ctx, record, id);
    grn_output_table_record_open(ctx, outbuf, output_type, (int)n);
    size_t i;
    for (i = 0; i < n; i++) {
      uint32_t code_start_offset;
      uint32_t code_end_offset;

      code_start_offset = GRN_UINT32_VALUE_AT(&offsets, i * 2);
      code_end_offset = GRN_UINT32_VALUE_AT(&offsets, i * 2 + 1);
      grn_output_table_record_by_expression(ctx,
                                            outbuf,
                                            output_type,
                                            format->expression,
                                            code_start_offset,
                                            code_end_offset,
                                            record,
                                            data);
    }
    grn_output_table_record_close(ctx, outbuf, output_type);
    grn_output_table_data_close_record(ctx, data);
  }

  GRN_OBJ_FIN(ctx, &offsets);
}

static inline void
grn_output_table_records_by_columns(grn_ctx *ctx,
                                    grn_obj *outbuf,
                                    grn_content_type output_type,
                                    grn_table_cursor *tc,
                                    grn_obj_format *format,
                                    grn_output_table_data *data)
{
  size_t i;
  grn_id id;
  size_t ncolumns = GRN_PTR_VECTOR_SIZE(&format->columns);
  grn_obj **columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);
  while (ctx->rc == GRN_SUCCESS &&
         (id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL) {
    grn_output_table_data_open_record(ctx, data);
    grn_output_table_record_open(ctx, outbuf, output_type, (int)ncolumns);
    for (i = 0; i < ncolumns; i++) {
      grn_output_table_record_by_column(ctx,
                                        outbuf,
                                        output_type,
                                        columns[i],
                                        id,
                                        data);
    }
    grn_output_table_record_close(ctx, outbuf, output_type);
    grn_output_table_data_close_record(ctx, data);
  }
}

void
grn_output_table_records_open(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              int n_records)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    return;
  }

  if (grn_ctx_get_command_version(ctx) >= GRN_COMMAND_VERSION_3) {
    grn_output_cstr(ctx, outbuf, output_type, "records");
    grn_output_array_open(ctx, outbuf, output_type, "records", n_records);
  }
}

static void
grn_output_table_keys_open(grn_ctx *ctx,
                           grn_obj *outbuf,
                           grn_content_type output_type,
                           int n_records)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    return;
  }

  if (grn_ctx_get_command_version(ctx) >= GRN_COMMAND_VERSION_3) {
    grn_output_cstr(ctx, outbuf, output_type, "keys");
    grn_output_array_open(ctx, outbuf, output_type, "keys", n_records);
  }
}

void
grn_output_table_records_close(grn_ctx *ctx,
                               grn_obj *outbuf,
                               grn_content_type output_type)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    return;
  }

  if (grn_ctx_get_command_version(ctx) >= GRN_COMMAND_VERSION_3) {
    grn_output_array_close(ctx, outbuf, output_type);
  }
}

void
grn_output_table_records_content(grn_ctx *ctx,
                                 grn_obj *outbuf,
                                 grn_content_type output_type,
                                 grn_obj *table,
                                 grn_obj_format *format)
{
  if (format) {
    grn_table_cursor *tc = grn_table_cursor_open(ctx,
                                                 table,
                                                 NULL,
                                                 0,
                                                 NULL,
                                                 0,
                                                 format->offset,
                                                 format->limit,
                                                 GRN_CURSOR_ASCENDING);
    if (!tc) {
      ERRCLR(ctx);
      return;
    }
    grn_output_table_data data;
    grn_output_table_data_init(ctx, &data, format);
    if (format->expression) {
      grn_output_table_records_by_expression(ctx,
                                             outbuf,
                                             output_type,
                                             tc,
                                             format,
                                             &data);
    } else {
      grn_output_table_records_by_columns(ctx,
                                          outbuf,
                                          output_type,
                                          tc,
                                          format,
                                          &data);
    }
    grn_output_table_data_fin(ctx, &data);
    grn_table_cursor_close(ctx, tc);
  } else {
    grn_obj *column =
      grn_obj_column(ctx, table, GRN_COLUMN_NAME_KEY, GRN_COLUMN_NAME_KEY_LEN);
    grn_obj buf;
    GRN_TEXT_INIT(&buf, 0);
    GRN_TABLE_EACH_BEGIN(ctx, table, cursor, id)
    {
      GRN_BULK_REWIND(&buf);
      grn_obj_get_value(ctx, column, id, &buf);
      grn_text_esc(ctx, outbuf, GRN_BULK_HEAD(&buf), GRN_BULK_VSIZE(&buf));
    }
    GRN_TABLE_EACH_END(ctx, cursor);
    GRN_OBJ_FIN(ctx, &buf);
    grn_obj_unlink(ctx, column);
  }
}

void
grn_output_table_records(grn_ctx *ctx,
                         grn_obj *outbuf,
                         grn_content_type output_type,
                         grn_obj *table,
                         grn_obj_format *format)
{
  if (format) {
    grn_output_table_records_open(ctx, outbuf, output_type, format->limit);
  } else {
    unsigned int n_records = grn_table_size(ctx, table);
    grn_output_table_keys_open(ctx, outbuf, output_type, (int)n_records);
  }
  grn_output_table_records_content(ctx, outbuf, output_type, table, format);
  grn_output_table_records_close(ctx, outbuf, output_type);
}

static void
grn_output_result_set_open_metadata_v1(grn_ctx *ctx,
                                       grn_obj *outbuf,
                                       grn_content_type output_type,
                                       grn_obj *table,
                                       grn_obj_format *format,
                                       uint32_t n_additional_elements)
{
  if (format) {
    int resultset_size = 1;
    /* resultset: [NHITS, (COLUMNS), (HITS)] */
    if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
      resultset_size++;
    }
    resultset_size += format->limit;
    resultset_size += (int)n_additional_elements;
    grn_output_array_open(ctx,
                          outbuf,
                          output_type,
                          "RESULTSET",
                          resultset_size);
    grn_output_result_set_n_hits(ctx, outbuf, output_type, format);
    if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
      grn_output_table_columns(ctx, outbuf, output_type, table, format);
    }
  } else {
    grn_output_array_open(ctx, outbuf, output_type, "HIT", -1);
  }
}

static void
grn_output_result_set_close_v1(grn_ctx *ctx,
                               grn_obj *outbuf,
                               grn_content_type output_type,
                               grn_obj *table,
                               grn_obj_format *format)
{
  grn_output_array_close(ctx, outbuf, output_type);
}

static void
grn_output_result_set_open_metadata_v3(grn_ctx *ctx,
                                       grn_obj *outbuf,
                                       grn_content_type output_type,
                                       grn_obj *result_set,
                                       grn_obj_format *format,
                                       uint32_t n_additional_elements)
{
  int n_elements = (int)n_additional_elements;
  if (format) {
    /* result_set: {"n_hits": N, ("columns": COLUMNS,) */
    n_elements++;
    if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
      n_elements++;
    }
    grn_output_map_open(ctx, outbuf, output_type, "result_set", n_elements);
    grn_output_result_set_n_hits(ctx, outbuf, output_type, format);
    if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
      grn_output_table_columns(ctx, outbuf, output_type, result_set, format);
    }
  } else {
    grn_output_map_open(ctx, outbuf, output_type, "result_set", n_elements);
  }
}

static void
grn_output_result_set_close_v3(grn_ctx *ctx,
                               grn_obj *outbuf,
                               grn_content_type output_type,
                               grn_obj *result_set,
                               grn_obj_format *format)
{
  grn_output_map_close(ctx, outbuf, output_type);
}

void
grn_output_result_set_open_metadata(grn_ctx *ctx,
                                    grn_obj *outbuf,
                                    grn_content_type output_type,
                                    grn_obj *result_set,
                                    grn_obj_format *format,
                                    uint32_t n_additional_elements)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_close(ctx, ctx->impl->output.arrow_stream_writer);
    }
    ctx->impl->output.arrow_stream_writer =
      grn_arrow_stream_writer_open(ctx, outbuf);
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_result_set_open_metadata_v1(ctx,
                                           outbuf,
                                           output_type,
                                           result_set,
                                           format,
                                           n_additional_elements);
  } else {
    grn_output_result_set_open_metadata_v3(ctx,
                                           outbuf,
                                           output_type,
                                           result_set,
                                           format,
                                           n_additional_elements);
  }
}

void
grn_output_result_set_open(grn_ctx *ctx,
                           grn_obj *outbuf,
                           grn_content_type output_type,
                           grn_obj *result_set,
                           grn_obj_format *format,
                           uint32_t n_additional_elements)
{
  grn_output_result_set_open_metadata(ctx,
                                      outbuf,
                                      output_type,
                                      result_set,
                                      format,
                                      n_additional_elements + 1);
  grn_output_table_records(ctx, outbuf, output_type, result_set, format);
}

void
grn_output_result_set_close(grn_ctx *ctx,
                            grn_obj *outbuf,
                            grn_content_type output_type,
                            grn_obj *result_set,
                            grn_obj_format *format)
{
  if (output_type == GRN_CONTENT_APACHE_ARROW) {
    if (ctx->impl->output.arrow_stream_writer) {
      grn_arrow_stream_writer_close(ctx, ctx->impl->output.arrow_stream_writer);
      ctx->impl->output.arrow_stream_writer = NULL;
    }
  }

  if (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_result_set_close_v1(ctx,
                                   outbuf,
                                   output_type,
                                   result_set,
                                   format);
  } else {
    grn_output_result_set_close_v3(ctx,
                                   outbuf,
                                   output_type,
                                   result_set,
                                   format);
  }
}

void
grn_output_result_set(grn_ctx *ctx,
                      grn_obj *outbuf,
                      grn_content_type output_type,
                      grn_obj *result_set,
                      grn_obj_format *format)
{
  uint32_t n_additional_elements = 0;

  grn_output_result_set_open(ctx,
                             outbuf,
                             output_type,
                             result_set,
                             format,
                             n_additional_elements);
  grn_output_result_set_close(ctx, outbuf, output_type, result_set, format);
}

static void
grn_output_table_hash_key(grn_ctx *ctx,
                          grn_obj *outbuf,
                          grn_content_type output_type,
                          grn_obj *obj,
                          grn_obj_format *format)
{
  const unsigned int n_elements = grn_table_size(ctx, obj);
  grn_obj key;
  GRN_OBJ_INIT(&key, GRN_BULK, GRN_OBJ_DO_SHALLOW_COPY, obj->header.domain);
  grn_output_map_open(ctx, outbuf, output_type, "object", (int)n_elements);
  GRN_TABLE_EACH_BEGIN(ctx, obj, cursor, id)
  {
    void *raw_key;
    const int key_size = grn_table_cursor_get_key(ctx, cursor, &raw_key);
    GRN_TEXT_SET(ctx, &key, raw_key, key_size);
    grn_output_obj(ctx, outbuf, output_type, &key, format);
    void *raw_value;
    const int value_size = grn_table_cursor_get_value(ctx, cursor, &raw_value);
    if (value_size == 0) {
      grn_output_null(ctx, outbuf, output_type);
    } else {
      grn_obj *value = raw_value;
      grn_output_obj(ctx, outbuf, output_type, value, format);
    }
  }
  GRN_TABLE_EACH_END(ctx, cursor);
  grn_output_map_close(ctx, outbuf, output_type);
}

void
grn_output_obj(grn_ctx *ctx,
               grn_obj *outbuf,
               grn_content_type output_type,
               grn_obj *obj,
               grn_obj_format *format)
{
  grn_obj buf;
  GRN_TEXT_INIT(&buf, 0);
  switch (obj->header.type) {
  case GRN_VOID:
    grn_output_void(ctx, outbuf, output_type, obj, format);
    break;
  case GRN_BULK:
    grn_output_bulk(ctx, outbuf, output_type, obj, format);
    break;
  case GRN_UVECTOR:
    grn_output_uvector(ctx, outbuf, output_type, obj, format);
    break;
  case GRN_VECTOR:
    grn_output_vector(ctx, outbuf, output_type, obj, format);
    break;
  case GRN_PVECTOR:
    grn_output_pvector(ctx, outbuf, output_type, obj, format);
    break;
  case GRN_TABLE_HASH_KEY:
    if (obj->header.flags & GRN_HASH_TINY) {
      grn_output_table_hash_key(ctx, outbuf, output_type, obj, format);
    } else {
      /* Deprecated. Use grn_output_result_set() directly. */
      grn_output_result_set(ctx, outbuf, output_type, obj, format);
    }
    break;
  case GRN_TABLE_PAT_KEY:
  case GRN_TABLE_DAT_KEY:
  case GRN_TABLE_NO_KEY:
    /* Deprecated. Use grn_output_result_set() directly. */
    grn_output_result_set(ctx, outbuf, output_type, obj, format);
    break;
  }
  GRN_OBJ_FIN(ctx, &buf);
}

typedef enum {
  XML_START,
  XML_START_ELEMENT,
  XML_END_ELEMENT,
  XML_TEXT
} xml_status;

typedef enum {
  XML_PLACE_NONE,
  XML_PLACE_COLUMN,
  XML_PLACE_HIT
} xml_place;

static char *
transform_xml_next_column(grn_obj *columns, int n)
{
  char *column = GRN_TEXT_VALUE(columns);
  while (n--) {
    while (*column) {
      column++;
    }
    column++;
  }
  return column;
}

static void
transform_xml(grn_ctx *ctx, grn_obj *output, grn_obj *transformed)
{
  char *s, *e;
  xml_status status = XML_START;
  xml_place place = XML_PLACE_NONE;
  grn_obj buf, name, columns, *expr;
  int len;
  int offset = 0, limit = 0, record_n = 0;
  int column_n = 0, column_text_n = 0, result_set_n = -1;
  bool in_vector = false;
  unsigned int vector_element_n = 0;
  bool in_weight_vector = false;
  unsigned int weight_vector_item_n = 0;

  s = GRN_TEXT_VALUE(output);
  e = GRN_BULK_CURR(output);
  GRN_TEXT_INIT(&buf, 0);
  GRN_TEXT_INIT(&name, 0);
  GRN_TEXT_INIT(&columns, 0);

  expr = ctx->impl->curr_expr;

#define EQUAL_NAME_P(_name)                                                    \
  (GRN_TEXT_LEN(&name) == strlen(_name) &&                                     \
   !memcmp(GRN_TEXT_VALUE(&name), _name, strlen(_name)))

  while (s < e) {
    switch (*s) {
    case '<':
      s++;
      switch (*s) {
      case '/':
        status = XML_END_ELEMENT;
        s++;
        break;
      default:
        status = XML_START_ELEMENT;
        break;
      }
      GRN_BULK_REWIND(&name);
      break;
    case '>':
      switch (status) {
      case XML_START_ELEMENT:
        if (EQUAL_NAME_P("COLUMN")) {
          place = XML_PLACE_COLUMN;
          column_text_n = 0;
        } else if (EQUAL_NAME_P("HIT")) {
          place = XML_PLACE_HIT;
          column_n = 0;
          if (result_set_n == 0) {
            GRN_TEXT_PUTS(ctx, transformed, "<HIT NO=\"");
            grn_text_itoa(ctx, transformed, record_n++);
            GRN_TEXT_PUTS(ctx, transformed, "\">\n");
          } else {
            GRN_TEXT_PUTS(ctx, transformed, "<NAVIGATIONELEMENT ");
          }
        } else if (EQUAL_NAME_P("RESULTSET")) {
          GRN_BULK_REWIND(&columns);
          result_set_n++;
          if (result_set_n == 0) {
          } else {
            GRN_TEXT_PUTS(ctx, transformed, "<NAVIGATIONENTRY>\n");
          }
        } else if (EQUAL_NAME_P("VECTOR")) {
          char *c = transform_xml_next_column(&columns, column_n++);
          in_vector = true;
          vector_element_n = 0;
          GRN_TEXT_PUTS(ctx, transformed, "<FIELD NAME=\"");
          GRN_TEXT_PUTS(ctx, transformed, c);
          GRN_TEXT_PUTS(ctx, transformed, "\">");
        } else if (EQUAL_NAME_P("WEIGHT_VECTOR")) {
          char *c = transform_xml_next_column(&columns, column_n++);
          in_weight_vector = true;
          weight_vector_item_n = 0;
          GRN_TEXT_PUTS(ctx, transformed, "<FIELD NAME=\"");
          GRN_TEXT_PUTS(ctx, transformed, c);
          GRN_TEXT_PUTS(ctx, transformed, "\">");
        }
        break;
      case XML_END_ELEMENT:
        if (EQUAL_NAME_P("HIT")) {
          place = XML_PLACE_NONE;
          if (result_set_n == 0) {
            GRN_TEXT_PUTS(ctx, transformed, "</HIT>\n");
          } else {
            GRN_TEXT_PUTS(ctx, transformed, "/>\n");
          }
        } else if (EQUAL_NAME_P("RESULTSET")) {
          place = XML_PLACE_NONE;
          if (result_set_n == 0) {
            GRN_TEXT_PUTS(ctx, transformed, "</RESULTSET>\n");
          } else {
            GRN_TEXT_PUTS(ctx,
                          transformed,
                          "</NAVIGATIONELEMENTS>\n"
                          "</NAVIGATIONENTRY>\n");
          }
        } else if (EQUAL_NAME_P("RESULT")) {
          GRN_TEXT_PUTS(ctx,
                        transformed,
                        "</RESULTPAGE>\n"
                        "</SEGMENT>\n"
                        "</SEGMENTS>\n");
        } else if (EQUAL_NAME_P("VECTOR")) {
          in_vector = false;
          GRN_TEXT_PUTS(ctx, transformed, "</FIELD>\n");
        } else if (EQUAL_NAME_P("WEIGHT_VECTOR")) {
          in_weight_vector = false;
          GRN_TEXT_PUTS(ctx, transformed, "</FIELD>\n");
        } else {
          switch (place) {
          case XML_PLACE_HIT:
            if (result_set_n == 0) {
              if (in_vector) {
                if (vector_element_n > 0) {
                  GRN_TEXT_PUTS(ctx, transformed, ", ");
                }
                GRN_TEXT_PUT(ctx,
                             transformed,
                             GRN_TEXT_VALUE(&buf),
                             GRN_TEXT_LEN(&buf));
                vector_element_n++;
              } else if (in_weight_vector) {
                bool is_key = ((weight_vector_item_n % 2) == 0);
                if (is_key) {
                  unsigned int weight_vector_key_n;
                  weight_vector_key_n = weight_vector_item_n / 2;
                  if (weight_vector_key_n > 0) {
                    GRN_TEXT_PUTS(ctx, transformed, ", ");
                  }
                } else {
                  GRN_TEXT_PUTS(ctx, transformed, ":");
                }
                GRN_TEXT_PUT(ctx,
                             transformed,
                             GRN_TEXT_VALUE(&buf),
                             GRN_TEXT_LEN(&buf));
                weight_vector_item_n++;
              } else {
                char *c = transform_xml_next_column(&columns, column_n++);
                GRN_TEXT_PUTS(ctx, transformed, "<FIELD NAME=\"");
                GRN_TEXT_PUTS(ctx, transformed, c);
                GRN_TEXT_PUTS(ctx, transformed, "\">");
                GRN_TEXT_PUT(ctx,
                             transformed,
                             GRN_TEXT_VALUE(&buf),
                             GRN_TEXT_LEN(&buf));
                GRN_TEXT_PUTS(ctx, transformed, "</FIELD>\n");
              }
            } else {
              char *c = transform_xml_next_column(&columns, column_n++);
              GRN_TEXT_PUTS(ctx, transformed, c);
              GRN_TEXT_PUTS(ctx, transformed, "=\"");
              GRN_TEXT_PUT(ctx,
                           transformed,
                           GRN_TEXT_VALUE(&buf),
                           GRN_TEXT_LEN(&buf));
              GRN_TEXT_PUTS(ctx, transformed, "\" ");
            }
            break;
          default:
            if (EQUAL_NAME_P("NHITS")) {
              if (result_set_n == 0) {
                uint32_t nhits;
                grn_obj *offset_value, *limit_value;

                nhits =
                  grn_atoui(GRN_TEXT_VALUE(&buf), GRN_BULK_CURR(&buf), NULL);
                offset_value =
                  grn_expr_get_var(ctx, expr, "offset", strlen("offset"));
                limit_value =
                  grn_expr_get_var(ctx, expr, "limit", strlen("limit"));
                if (GRN_TEXT_LEN(offset_value)) {
                  offset = grn_atoi(GRN_TEXT_VALUE(offset_value),
                                    GRN_BULK_CURR(offset_value),
                                    NULL);
                } else {
                  offset = 0;
                }
                if (GRN_TEXT_LEN(limit_value)) {
                  limit = grn_atoi(GRN_TEXT_VALUE(limit_value),
                                   GRN_BULK_CURR(limit_value),
                                   NULL);
                } else {
#define DEFAULT_LIMIT 10
                  limit = DEFAULT_LIMIT;
#undef DEFAULT_LIMIT
                }
                grn_output_range_normalize(ctx, (int)nhits, &offset, &limit);
                record_n = offset + 1;
                GRN_TEXT_PUTS(ctx,
                              transformed,
                              "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
                              "<SEGMENTS>\n"
                              "<SEGMENT>\n"
                              "<RESULTPAGE>\n"
                              "<RESULTSET OFFSET=\"");
                grn_text_lltoa(ctx, transformed, offset);
                GRN_TEXT_PUTS(ctx, transformed, "\" LIMIT=\"");
                grn_text_lltoa(ctx, transformed, limit);
                GRN_TEXT_PUTS(ctx, transformed, "\" NHITS=\"");
                grn_text_lltoa(ctx, transformed, nhits);
                GRN_TEXT_PUTS(ctx, transformed, "\">\n");
              } else {
                GRN_TEXT_PUTS(ctx, transformed, "<NAVIGATIONELEMENTS COUNT=\"");
                GRN_TEXT_PUT(ctx,
                             transformed,
                             GRN_TEXT_VALUE(&buf),
                             GRN_TEXT_LEN(&buf));
                GRN_TEXT_PUTS(ctx, transformed, "\">\n");
              }
            } else if (EQUAL_NAME_P("TEXT")) {
              switch (place) {
              case XML_PLACE_COLUMN:
                if (column_text_n == 0) {
                  GRN_TEXT_PUT(ctx,
                               &columns,
                               GRN_TEXT_VALUE(&buf),
                               GRN_TEXT_LEN(&buf));
                  GRN_TEXT_PUTC(ctx, &columns, '\0');
                }
                column_text_n++;
                break;
              default:
                break;
              }
            }
          }
        }
      default:
        break;
      }
      s++;
      GRN_BULK_REWIND(&buf);
      status = XML_TEXT;
      break;
    default:
      len = grn_charlen(ctx, s, e);
      switch (status) {
      case XML_START_ELEMENT:
      case XML_END_ELEMENT:
        GRN_TEXT_PUT(ctx, &name, s, len);
        break;
      default:
        GRN_TEXT_PUT(ctx, &buf, s, len);
        break;
      }
      s += len;
      break;
    }
  }
#undef EQUAL_NAME_P

  GRN_OBJ_FIN(ctx, &buf);
  GRN_OBJ_FIN(ctx, &name);
  GRN_OBJ_FIN(ctx, &columns);
}

#ifdef GRN_WITH_MESSAGE_PACK
typedef struct {
  grn_ctx *ctx;
  grn_obj *buffer;
} msgpack_writer_ctx;

static inline int
msgpack_buffer_writer(void *data, const char *buf, msgpack_size_t len)
{
  msgpack_writer_ctx *writer_ctx = (msgpack_writer_ctx *)data;
  return grn_bulk_write(writer_ctx->ctx, writer_ctx->buffer, buf, len);
}
#endif

#define JSON_CALLBACK_PARAM "callback"

static void
grn_output_envelope_open_json_v1(grn_ctx *ctx,
                                 grn_obj *head,
                                 bool is_stream_mode,
                                 bool have_body)
{
  size_t indent_level = 0;

  json_array_open(ctx, head, &indent_level);
}

static void
grn_output_envelope_close_json_v1(grn_ctx *ctx,
                                  grn_obj *head,
                                  grn_obj *foot,
                                  bool is_stream_mode,
                                  bool have_body,
                                  grn_rc rc,
                                  double started,
                                  double elapsed,
                                  const char *file,
                                  int line)
{
  size_t indent_level = 0;
  if (ctx->impl->output.is_pretty) {
    indent_level++;
  }

  {
    if (is_stream_mode && have_body) {
      json_element_end(ctx, head, indent_level);
    }

    json_array_open(ctx, head, &indent_level);
    {
      grn_text_itoa(ctx, head, rc);

      json_element_end(ctx, head, indent_level);
      grn_text_ftoa(ctx, head, started);

      json_element_end(ctx, head, indent_level);
      grn_text_ftoa(ctx, head, elapsed);

      if (rc != GRN_SUCCESS) {
        json_element_end(ctx, head, indent_level);
        grn_text_esc(ctx, head, ctx->errbuf, strlen(ctx->errbuf));

        if (ctx->errfunc && ctx->errfile) {
          grn_obj *command;

          json_element_end(ctx, head, indent_level);
          json_array_open(ctx, head, &indent_level);
          {
            json_array_open(ctx, head, &indent_level);
            {
              grn_text_esc(ctx, head, ctx->errfunc, strlen(ctx->errfunc));

              json_element_end(ctx, head, indent_level);
              grn_text_esc(ctx, head, ctx->errfile, strlen(ctx->errfile));

              json_element_end(ctx, head, indent_level);
              grn_text_ulltoa(ctx, head, ctx->errline);
            }
            json_array_close(ctx, head, &indent_level);

            if (file && (command = GRN_CTX_USER_DATA(ctx)->ptr)) {
              json_element_end(ctx, head, indent_level);
              json_array_open(ctx, head, &indent_level);
              {
                grn_text_esc(ctx, head, file, strlen(file));

                json_element_end(ctx, head, indent_level);
                grn_text_itoa(ctx, head, line);

                json_element_end(ctx, head, indent_level);
                grn_text_esc(ctx,
                             head,
                             GRN_TEXT_VALUE(command),
                             GRN_TEXT_LEN(command));
              }
              json_array_close(ctx, head, &indent_level);
            }
          }
          json_array_close(ctx, head, &indent_level);
        }
      }
    }
    json_array_close(ctx, head, &indent_level);
  }

  if (!is_stream_mode && have_body) {
    json_element_end(ctx, head, indent_level);
  }

  json_array_close(ctx, foot, &indent_level);
}

static void
grn_output_envelope_json_v1(grn_ctx *ctx,
                            grn_obj *head,
                            grn_obj *body,
                            grn_obj *foot,
                            grn_rc rc,
                            double started,
                            double elapsed,
                            const char *file,
                            int line)
{
  bool is_stream_mode = false;
  bool have_body = GRN_TEXT_LEN(body) > 0;
  grn_output_envelope_open_json_v1(ctx, head, is_stream_mode, have_body);
  grn_output_envelope_close_json_v1(ctx,
                                    head,
                                    foot,
                                    is_stream_mode,
                                    have_body,
                                    rc,
                                    started,
                                    elapsed,
                                    file,
                                    line);
}

static void
grn_output_envelope_open_json(grn_ctx *ctx,
                              grn_obj *head,
                              bool is_stream_mode,
                              bool have_body)
{
  size_t indent_level = 0;

  json_map_open(ctx, head, &indent_level);
  {
    if (is_stream_mode && have_body) {
      json_key(ctx, head, "body");
    }
  }
}

static void
grn_output_envelope_close_json_header(grn_ctx *ctx,
                                      grn_obj *head,
                                      grn_rc rc,
                                      double started,
                                      double elapsed,
                                      const char *file,
                                      int line,
                                      size_t *indent_level)
{
  json_key(ctx, head, "header");
  json_map_open(ctx, head, indent_level);
  {
    json_key(ctx, head, "return_code");
    grn_text_itoa(ctx, head, rc);

    json_value_end(ctx, head, *indent_level);
    json_key(ctx, head, "start_time");
    grn_text_ftoa(ctx, head, started);

    json_value_end(ctx, head, *indent_level);
    json_key(ctx, head, "elapsed_time");
    grn_text_ftoa(ctx, head, elapsed);

    if (rc != GRN_SUCCESS) {
      json_value_end(ctx, head, *indent_level);
      json_key(ctx, head, "error");
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "message");
        grn_text_esc(ctx, head, ctx->errbuf, strlen(ctx->errbuf));

        if (ctx->errfunc && ctx->errfile) {
          json_value_end(ctx, head, *indent_level);
          json_key(ctx, head, "function");
          grn_text_esc(ctx, head, ctx->errfunc, strlen(ctx->errfunc));

          json_value_end(ctx, head, *indent_level);
          json_key(ctx, head, "file");
          grn_text_esc(ctx, head, ctx->errfile, strlen(ctx->errfile));

          json_value_end(ctx, head, *indent_level);
          json_key(ctx, head, "line");
          grn_text_ulltoa(ctx, head, ctx->errline);
        }

        if (file) {
          grn_obj *command;

          command = GRN_CTX_USER_DATA(ctx)->ptr;
          if (command) {
            json_value_end(ctx, head, *indent_level);
            json_key(ctx, head, "input");
            json_map_open(ctx, head, indent_level);
            {
              json_key(ctx, head, "file");
              grn_text_esc(ctx, head, file, strlen(file));

              json_value_end(ctx, head, *indent_level);
              json_key(ctx, head, "line");
              grn_text_itoa(ctx, head, line);

              json_value_end(ctx, head, *indent_level);
              json_key(ctx, head, "command");
              grn_text_esc(ctx,
                           head,
                           GRN_TEXT_VALUE(command),
                           GRN_TEXT_LEN(command));
            }
            json_map_close(ctx, head, indent_level);
          }
        }
      }
      json_map_close(ctx, head, indent_level);
    }
  }
  json_map_close(ctx, head, indent_level);
}

static void
grn_output_envelope_close_json_trace_log(grn_ctx *ctx,
                                         grn_obj *head,
                                         size_t *indent_level)
{
  json_value_end(ctx, head, *indent_level);
  json_key(ctx, head, "trace_log");
  json_map_open(ctx, head, indent_level);
  {
    json_key(ctx, head, "columns");
    json_array_open(ctx, head, indent_level);
    {
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "name");
        grn_text_esc(ctx, head, "depth", strlen("depth"));
      }
      json_map_close(ctx, head, indent_level);

      json_element_end(ctx, head, *indent_level);
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "name");
        grn_text_esc(ctx, head, "sequence", strlen("sequence"));
      }
      json_map_close(ctx, head, indent_level);

      json_element_end(ctx, head, *indent_level);
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "name");
        grn_text_esc(ctx, head, "name", strlen("name"));
      }
      json_map_close(ctx, head, indent_level);

      json_element_end(ctx, head, *indent_level);
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "name");
        grn_text_esc(ctx, head, "value", strlen("value"));
      }
      json_map_close(ctx, head, indent_level);

      json_element_end(ctx, head, *indent_level);
      json_map_open(ctx, head, indent_level);
      {
        json_key(ctx, head, "name");
        grn_text_esc(ctx, head, "elapsed_time", strlen("elapsed_time"));
      }
      json_map_close(ctx, head, indent_level);
    }
    json_array_close(ctx, head, indent_level);

    json_value_end(ctx, head, *indent_level);
    json_key(ctx, head, "logs");
    json_array_open(ctx, head, indent_level);
    {
      grn_obj *depths = &(ctx->impl->trace_log.depths);
      grn_obj *sequences = &(ctx->impl->trace_log.sequences);
      grn_obj *names = &(ctx->impl->trace_log.names);
      grn_obj *values = &(ctx->impl->trace_log.values);
      grn_obj *elapsed_times = &(ctx->impl->trace_log.elapsed_times);
      uint32_t n_logs = grn_vector_size(ctx, names);
      uint32_t i;
      for (i = 0; i < n_logs; i++) {
        if (i > 0) {
          json_element_end(ctx, head, *indent_level);
        }
        json_array_open(ctx, head, indent_level);
        {
          uint16_t depth = GRN_UINT16_VALUE_AT(depths, i);
          grn_text_ulltoa(ctx, head, depth);

          json_element_end(ctx, head, *indent_level);
          uint16_t sequence = GRN_UINT16_VALUE_AT(sequences, i);
          grn_text_ulltoa(ctx, head, sequence);

          json_element_end(ctx, head, *indent_level);
          const char *name;
          uint32_t name_size =
            grn_vector_get_element_float(ctx, names, i, &name, NULL, NULL);
          grn_text_esc(ctx, head, name, name_size);

          json_element_end(ctx, head, *indent_level);
          const char *value;
          grn_id value_domain;
          uint32_t value_size = grn_vector_get_element_float(ctx,
                                                             values,
                                                             i,
                                                             &value,
                                                             NULL,
                                                             &value_domain);
          switch (value_domain) {
          case GRN_DB_UINT32:
            {
              uint32_t value_uint32 =
                (value_size == 0) ? 0 : *((const uint32_t *)value);
              grn_text_ulltoa(ctx, head, value_uint32);
            }
            break;
          default:
            grn_text_esc(ctx, head, value, value_size);
            break;
          }

          json_element_end(ctx, head, *indent_level);
          uint64_t elapsed_time = GRN_UINT64_VALUE_AT(elapsed_times, i);
          grn_text_ulltoa(ctx, head, elapsed_time);
        }
        json_array_close(ctx, head, indent_level);
      }
    }
    json_array_close(ctx, head, indent_level);
  }
  json_map_close(ctx, head, indent_level);
}

static void
grn_output_envelope_close_json(grn_ctx *ctx,
                               grn_obj *head,
                               grn_obj *foot,
                               bool is_stream_mode,
                               bool have_body,
                               grn_rc rc,
                               double started,
                               double elapsed,
                               const char *file,
                               int line)
{
  size_t indent_level = 0;
  if (ctx->impl->output.is_pretty) {
    indent_level++;
  }

  {
    if (is_stream_mode && have_body) {
      json_value_end(ctx, head, indent_level);
    }

    grn_output_envelope_close_json_header(ctx,
                                          head,
                                          rc,
                                          started,
                                          elapsed,
                                          file,
                                          line,
                                          &indent_level);

    if (grn_ctx_trace_log_is_enabled(ctx)) {
      grn_output_envelope_close_json_trace_log(ctx, head, &indent_level);
    }

    if (!is_stream_mode && have_body) {
      json_value_end(ctx, head, indent_level);
      json_key(ctx, head, "body");
    }
  }
  json_map_close(ctx, foot, &indent_level);
}

static void
grn_output_envelope_json(grn_ctx *ctx,
                         grn_obj *head,
                         grn_obj *body,
                         grn_obj *foot,
                         grn_rc rc,
                         double started,
                         double elapsed,
                         const char *file,
                         int line)
{
  bool is_stream_mode = false;
  bool have_body = GRN_TEXT_LEN(body) > 0;
  grn_output_envelope_open_json(ctx, head, is_stream_mode, have_body);
  grn_output_envelope_close_json(ctx,
                                 head,
                                 foot,
                                 is_stream_mode,
                                 have_body,
                                 rc,
                                 started,
                                 elapsed,
                                 file,
                                 line);
}

#ifdef GRN_WITH_MESSAGE_PACK
static void
msgpack_pack_cstr(msgpack_packer *packer, const char *string)
{
  size_t size;

  size = strlen(string);
  msgpack_pack_str(packer, size);
  msgpack_pack_str_body(packer, string, size);
}

static void
grn_output_envelope_msgpack_v1(grn_ctx *ctx,
                               grn_rc rc,
                               grn_obj *head,
                               grn_obj *body,
                               grn_obj *foot,
                               double started,
                               double elapsed,
                               const char *file,
                               int line)
{
  msgpack_writer_ctx head_writer_ctx;
  msgpack_packer header_packer;
  size_t header_size;

  head_writer_ctx.ctx = ctx;
  head_writer_ctx.buffer = head;
  msgpack_packer_init(&header_packer, &head_writer_ctx, msgpack_buffer_writer);

  /* [HEADER, (BODY)] */
  if (GRN_TEXT_LEN(body) > 0) {
    msgpack_pack_array(&header_packer, 2);
  } else {
    msgpack_pack_array(&header_packer, 1);
  }

  /* HEADER := [rc, started, elapsed, (error, (ERROR DETAIL))] */
  header_size = 3;
  if (rc != GRN_SUCCESS) {
    header_size++;
    if (ctx->errfunc && ctx->errfile) {
      header_size++;
    }
  }
  msgpack_pack_array(&header_packer, header_size);
  msgpack_pack_int(&header_packer, rc);

  msgpack_pack_double(&header_packer, started);
  msgpack_pack_double(&header_packer, elapsed);

  if (rc != GRN_SUCCESS) {
    msgpack_pack_str(&header_packer, strlen(ctx->errbuf));
    msgpack_pack_str_body(&header_packer, ctx->errbuf, strlen(ctx->errbuf));
    if (ctx->errfunc && ctx->errfile) {
      grn_obj *command = GRN_CTX_USER_DATA(ctx)->ptr;
      size_t error_detail_size;

      /* ERROR DETAIL := [[errfunc, errfile, errline, (file, line, command)]] */
      /* TODO: output backtrace */
      msgpack_pack_array(&header_packer, 1);
      error_detail_size = 3;
      if (command) {
        error_detail_size += 3;
      }
      msgpack_pack_array(&header_packer, error_detail_size);

      msgpack_pack_str(&header_packer, strlen(ctx->errfunc));
      msgpack_pack_str_body(&header_packer, ctx->errfunc, strlen(ctx->errfunc));

      msgpack_pack_str(&header_packer, strlen(ctx->errfile));
      msgpack_pack_str_body(&header_packer, ctx->errfile, strlen(ctx->errfile));

      msgpack_pack_unsigned_int(&header_packer, ctx->errline);

      if (command) {
        if (file) {
          msgpack_pack_str(&header_packer, strlen(file));
          msgpack_pack_str_body(&header_packer, file, strlen(file));
        } else {
          msgpack_pack_str(&header_packer, 7);
          msgpack_pack_str_body(&header_packer, "(stdin)", 7);
        }

        msgpack_pack_int(&header_packer, line);

        msgpack_pack_str(&header_packer, GRN_TEXT_LEN(command));
        msgpack_pack_str_body(&header_packer,
                              GRN_TEXT_VALUE(command),
                              GRN_TEXT_LEN(command));
      }
    }
  }
}

static void
grn_output_envelope_msgpack(grn_ctx *ctx,
                            grn_rc rc,
                            grn_obj *head,
                            grn_obj *body,
                            grn_obj *foot,
                            double started,
                            double elapsed,
                            const char *file,
                            int line)
{
  msgpack_writer_ctx writer_ctx;
  msgpack_packer packer;
  size_t n_elements;

  writer_ctx.ctx = ctx;
  writer_ctx.buffer = head;
  msgpack_packer_init(&packer, &writer_ctx, msgpack_buffer_writer);

  /*
   * ENVELOPE := {
   *   "header": HEADER,
   *   "body":   BODY    (optional)
   * }
   */
  if (GRN_TEXT_LEN(body) > 0) {
    n_elements = 2;
  } else {
    n_elements = 1;
  }

  msgpack_pack_map(&packer, n_elements);
  {
    size_t n_header_elements = 3;

    /*
     * HEADER := {
     *   "return_code":  rc,
     *   "start_time":   started,
     *   "elapsed_time": elapsed,
     "   "error": {                   (optional)
     *      "message":  errbuf,
     *      "function": errfunc,
     *      "file":     errfile,
     *      "line":     errline,
     *      "input": {                (optional)
     *        "file":    input_file,
     *        "line":    line,
     *        "command": command
     *      }
     *   }
     * }
     */

    if (rc != GRN_SUCCESS) {
      n_header_elements++;
    }

    msgpack_pack_cstr(&packer, "header");
    msgpack_pack_map(&packer, n_header_elements);
    {
      msgpack_pack_cstr(&packer, "return_code");
      msgpack_pack_int(&packer, rc);

      msgpack_pack_cstr(&packer, "start_time");
      msgpack_pack_double(&packer, started);

      msgpack_pack_cstr(&packer, "elapsed_time");
      msgpack_pack_double(&packer, elapsed);

      if (rc != GRN_SUCCESS) {
        size_t n_error_elements = 1;
        grn_obj *command;

        if (ctx->errfunc) {
          n_error_elements++;
        }
        if (ctx->errfile) {
          n_error_elements += 2;
        }

        command = GRN_CTX_USER_DATA(ctx)->ptr;
        if (file || command) {
          n_error_elements++;
        }

        msgpack_pack_cstr(&packer, "error");
        msgpack_pack_map(&packer, n_error_elements);
        {
          msgpack_pack_cstr(&packer, "message");
          msgpack_pack_cstr(&packer, ctx->errbuf);

          if (ctx->errfunc) {
            msgpack_pack_cstr(&packer, "function");
            msgpack_pack_cstr(&packer, ctx->errfunc);
          }

          if (ctx->errfile) {
            msgpack_pack_cstr(&packer, "file");
            msgpack_pack_cstr(&packer, ctx->errfile);

            msgpack_pack_cstr(&packer, "line");
            msgpack_pack_unsigned_int(&packer, ctx->errline);
          }

          if (file || command) {
            size_t n_input_elements = 0;

            if (file) {
              n_input_elements += 2;
            }
            if (command) {
              n_input_elements++;
            }

            msgpack_pack_cstr(&packer, "input");
            msgpack_pack_map(&packer, n_input_elements);

            if (file) {
              msgpack_pack_cstr(&packer, "file");
              msgpack_pack_cstr(&packer, file);

              msgpack_pack_cstr(&packer, "line");
              msgpack_pack_int(&packer, line);
            }

            if (command) {
              msgpack_pack_cstr(&packer, "command");
              msgpack_pack_str(&packer, GRN_TEXT_LEN(command));
              msgpack_pack_str_body(&packer,
                                    GRN_TEXT_VALUE(command),
                                    GRN_TEXT_LEN(command));
            }
          }
        }
      }
    }

    if (GRN_TEXT_LEN(body) > 0) {
      msgpack_pack_cstr(&packer, "body");
    }
  }
}
#endif /* GRN_WITH_MESSAGE_PACK */

static void
grn_output_envelope_close_apache_arrow_metadata(grn_ctx *ctx,
                                                grn_obj *output,
                                                grn_rc rc,
                                                double started,
                                                double elapsed,
                                                const char *file,
                                                int line)
{
  grn_arrow_stream_writer *writer = grn_arrow_stream_writer_open(ctx, output);
  if (!writer) {
    return;
  }

  grn_arrow_stream_writer_add_metadata(ctx,
                                       writer,
                                       "GROONGA:data_type",
                                       "metadata");
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "return_code",
                                    grn_ctx_at(ctx, GRN_DB_INT32));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "start_time",
                                    grn_ctx_at(ctx, GRN_DB_TIME));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "elapsed_time",
                                    grn_ctx_at(ctx, GRN_DB_FLOAT));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_message",
                                    grn_ctx_at(ctx, GRN_DB_SHORT_TEXT));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_file",
                                    grn_ctx_at(ctx, GRN_DB_SHORT_TEXT));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_line",
                                    grn_ctx_at(ctx, GRN_DB_UINT32));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_function",
                                    grn_ctx_at(ctx, GRN_DB_SHORT_TEXT));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_input_file",
                                    grn_ctx_at(ctx, GRN_DB_SHORT_TEXT));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_input_line",
                                    grn_ctx_at(ctx, GRN_DB_INT32));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "error_input_command",
                                    grn_ctx_at(ctx, GRN_DB_SHORT_TEXT));
  grn_arrow_stream_writer_write_schema(ctx, writer);

  grn_arrow_stream_writer_open_record(ctx, writer);
  grn_arrow_stream_writer_add_column_int32(ctx, writer, rc);
  grn_arrow_stream_writer_add_column_timestamp(
    ctx,
    writer,
    grn_timeval_from_double(ctx, started));
  grn_arrow_stream_writer_add_column_float(ctx, writer, elapsed);
  if (rc == GRN_SUCCESS) {
    /* error_message */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_file */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_line */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_function */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_input_file */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_input_line */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
    /* error_input_command */
    grn_arrow_stream_writer_add_column_null(ctx, writer);
  } else {
    grn_arrow_stream_writer_add_column_text(ctx,
                                            writer,
                                            ctx->errbuf,
                                            strlen(ctx->errbuf));
    if (ctx->errfile && ctx->errfunc) {
      grn_arrow_stream_writer_add_column_text(ctx,
                                              writer,
                                              ctx->errfile,
                                              strlen(ctx->errfile));
      grn_arrow_stream_writer_add_column_uint32(ctx, writer, ctx->errline);
      grn_arrow_stream_writer_add_column_text(ctx,
                                              writer,
                                              ctx->errfunc,
                                              strlen(ctx->errfunc));
    } else {
      grn_arrow_stream_writer_add_column_null(ctx, writer);
      grn_arrow_stream_writer_add_column_null(ctx, writer);
      grn_arrow_stream_writer_add_column_null(ctx, writer);
    }
    grn_obj *command = GRN_CTX_USER_DATA(ctx)->ptr;
    if (file && command) {
      grn_arrow_stream_writer_add_column_text(ctx, writer, file, strlen(file));
      grn_arrow_stream_writer_add_column_int32(ctx, writer, line);
      grn_arrow_stream_writer_add_column_text(ctx,
                                              writer,
                                              GRN_TEXT_VALUE(command),
                                              GRN_TEXT_LEN(command));
    } else {
      grn_arrow_stream_writer_add_column_null(ctx, writer);
      grn_arrow_stream_writer_add_column_null(ctx, writer);
      grn_arrow_stream_writer_add_column_null(ctx, writer);
    }
  }
  grn_arrow_stream_writer_close_record(ctx, writer);
  grn_arrow_stream_writer_close(ctx, writer);
}

static void
grn_output_envelope_close_apache_arrow_trace_log(grn_ctx *ctx, grn_obj *output)
{
  grn_arrow_stream_writer *writer = grn_arrow_stream_writer_open(ctx, output);
  if (!writer) {
    return;
  }

  grn_arrow_stream_writer_add_metadata(ctx,
                                       writer,
                                       "GROONGA:data_type",
                                       "trace_log");
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "depth",
                                    grn_ctx_at(ctx, GRN_DB_UINT16));
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "sequence",
                                    grn_ctx_at(ctx, GRN_DB_UINT16));
  grn_arrow_stream_writer_add_field_text_dictionary(
    ctx,
    writer,
    "name",
    grn_ctx_at(ctx, GRN_DB_INT16));
  enum value_type {
    VALUE_TYPE_UINT32,
    VALUE_TYPE_SHORT_TEXT,
    N_VALUE_TYPES,
  };
  grn_obj *value_types[N_VALUE_TYPES];
  value_types[VALUE_TYPE_UINT32] = grn_ctx_at(ctx, GRN_DB_UINT32);
  value_types[VALUE_TYPE_SHORT_TEXT] = grn_ctx_at(ctx, GRN_DB_SHORT_TEXT);
  grn_arrow_stream_writer_add_field_union(ctx,
                                          writer,
                                          "value",
                                          value_types,
                                          N_VALUE_TYPES);
  grn_arrow_stream_writer_add_field(ctx,
                                    writer,
                                    "elapsed_time",
                                    grn_ctx_at(ctx, GRN_DB_UINT64));
  grn_arrow_stream_writer_write_schema(ctx, writer);

  grn_obj *depths = &(ctx->impl->trace_log.depths);
  grn_obj *sequences = &(ctx->impl->trace_log.sequences);
  grn_obj *names = &(ctx->impl->trace_log.names);
  grn_obj *values = &(ctx->impl->trace_log.values);
  grn_obj *elapsed_times = &(ctx->impl->trace_log.elapsed_times);
  uint32_t n_logs = grn_vector_size(ctx, names);
  uint32_t i;
  for (i = 0; i < n_logs; i++) {
    grn_arrow_stream_writer_open_record(ctx, writer);

    uint16_t depth = GRN_UINT16_VALUE_AT(depths, i);
    grn_arrow_stream_writer_add_column_uint16(ctx, writer, depth);

    uint16_t sequence = GRN_UINT16_VALUE_AT(sequences, i);
    grn_arrow_stream_writer_add_column_uint16(ctx, writer, sequence);

    const char *name;
    uint32_t name_size =
      grn_vector_get_element_float(ctx, names, i, &name, NULL, NULL);
    grn_arrow_stream_writer_add_column_text_dictionary(ctx,
                                                       writer,
                                                       name,
                                                       name_size);

    const char *value;
    grn_id value_domain;
    uint32_t value_size =
      grn_vector_get_element_float(ctx, values, i, &value, NULL, &value_domain);
    switch (value_domain) {
    case GRN_DB_UINT32:
      {
        grn_arrow_stream_writer_add_column_union(ctx,
                                                 writer,
                                                 VALUE_TYPE_UINT32);
        uint32_t value_uint32 =
          (value_size == 0) ? 0 : *((const uint32_t *)value);
        grn_arrow_stream_writer_add_column_uint32(ctx, writer, value_uint32);
      }
      break;
    default:
      grn_arrow_stream_writer_add_column_union(ctx,
                                               writer,
                                               VALUE_TYPE_SHORT_TEXT);
      grn_arrow_stream_writer_add_column_text(ctx, writer, value, value_size);
      break;
    }

    uint64_t elapsed_time = GRN_UINT64_VALUE_AT(elapsed_times, i);
    grn_arrow_stream_writer_add_column_uint64(ctx, writer, elapsed_time);

    grn_arrow_stream_writer_close_record(ctx, writer);
  }
  grn_arrow_stream_writer_close(ctx, writer);
}

static void
grn_output_envelope_close_apache_arrow(grn_ctx *ctx,
                                       grn_obj *output,
                                       grn_rc rc,
                                       double started,
                                       double elapsed,
                                       const char *file,
                                       int line)
{
  if (ctx->impl->output.arrow_stream_writer) {
    grn_arrow_stream_writer_close(ctx, ctx->impl->output.arrow_stream_writer);
    ctx->impl->output.arrow_stream_writer = NULL;
    if (ctx->rc != GRN_SUCCESS) {
      return;
    }
  }

  grn_output_envelope_close_apache_arrow_metadata(ctx,
                                                  output,
                                                  rc,
                                                  started,
                                                  elapsed,
                                                  file,
                                                  line);
  if (grn_ctx_trace_log_is_enabled(ctx)) {
    grn_output_envelope_close_apache_arrow_trace_log(ctx, output);
  }
}

static void
grn_output_envelope_apache_arrow(grn_ctx *ctx,
                                 grn_obj *head,
                                 grn_obj *body,
                                 grn_obj *foot,
                                 grn_rc rc,
                                 double started,
                                 double elapsed,
                                 const char *file,
                                 int line)
{
  grn_output_envelope_close_apache_arrow(ctx,
                                         head,
                                         rc,
                                         started,
                                         elapsed,
                                         file,
                                         line);
}

void
grn_output_envelope(grn_ctx *ctx,
                    grn_rc rc,
                    grn_obj *head,
                    grn_obj *body,
                    grn_obj *foot,
                    const char *file,
                    int line)
{
  double started, finished, elapsed;

  grn_timeval tv_now;
  grn_timeval_now(ctx, &tv_now);
  started = (double)(ctx->impl->tv.tv_sec);
  started += ctx->impl->tv.tv_nsec / GRN_TIME_NSEC_PER_SEC_F;
  finished = (double)(tv_now.tv_sec);
  finished += tv_now.tv_nsec / GRN_TIME_NSEC_PER_SEC_F;
  elapsed = finished - started;

  switch (ctx->impl->output.type) {
  case GRN_CONTENT_JSON:
    {
      grn_obj *expr;
      grn_obj *jsonp_func = NULL;

      expr = ctx->impl->curr_expr;
      if (expr) {
        jsonp_func = grn_expr_get_var(ctx,
                                      expr,
                                      JSON_CALLBACK_PARAM,
                                      strlen(JSON_CALLBACK_PARAM));
      }
      if (jsonp_func && GRN_TEXT_LEN(jsonp_func)) {
        GRN_TEXT_PUT(ctx,
                     head,
                     GRN_TEXT_VALUE(jsonp_func),
                     GRN_TEXT_LEN(jsonp_func));
        GRN_TEXT_PUTC(ctx, head, '(');
      }

      if (grn_ctx_get_command_version(ctx) <= GRN_COMMAND_VERSION_2) {
        grn_output_envelope_json_v1(ctx,
                                    head,
                                    body,
                                    foot,
                                    rc,
                                    started,
                                    elapsed,
                                    file,
                                    line);
      } else {
        grn_output_envelope_json(ctx,
                                 head,
                                 body,
                                 foot,
                                 rc,
                                 started,
                                 elapsed,
                                 file,
                                 line);
      }

      if (jsonp_func && GRN_TEXT_LEN(jsonp_func)) {
        GRN_TEXT_PUTS(ctx, foot, ");");
      }
    }
    break;
  case GRN_CONTENT_TSV:
    grn_text_itoa(ctx, head, rc);
    GRN_TEXT_PUTC(ctx, head, '\t');
    grn_text_ftoa(ctx, head, started);
    GRN_TEXT_PUTC(ctx, head, '\t');
    grn_text_ftoa(ctx, head, elapsed);
    if (rc != GRN_SUCCESS) {
      GRN_TEXT_PUTC(ctx, head, '\t');
      grn_text_esc(ctx, head, ctx->errbuf, strlen(ctx->errbuf));
      if (ctx->errfunc && ctx->errfile) {
        /* TODO: output backtrace */
        GRN_TEXT_PUTC(ctx, head, '\t');
        grn_text_esc(ctx, head, ctx->errfunc, strlen(ctx->errfunc));
        GRN_TEXT_PUTC(ctx, head, '\t');
        grn_text_esc(ctx, head, ctx->errfile, strlen(ctx->errfile));
        GRN_TEXT_PUTC(ctx, head, '\t');
        grn_text_ulltoa(ctx, head, ctx->errline);
      }
    }
    GRN_TEXT_PUTS(ctx, head, "\n");
    GRN_TEXT_PUTS(ctx, foot, "\nEND");
    break;
  case GRN_CONTENT_XML:
    {
      char buf[GRN_TABLE_MAX_KEY_SIZE];
      int is_select = 0;
      if (!rc && ctx->impl->curr_expr) {
        int len =
          grn_obj_name(ctx, ctx->impl->curr_expr, buf, GRN_TABLE_MAX_KEY_SIZE);
        buf[len] = '\0';
        is_select = strcmp(buf, "select") == 0;
      }
      if (is_select) {
        grn_obj transformed;
        GRN_TEXT_INIT(&transformed, 0);
        transform_xml(ctx, body, &transformed);
        if (body->header.impl_flags & GRN_OBJ_REFER) {
          body->header.impl_flags =
            (uint8_t)(body->header.impl_flags &
                      ~((uint8_t)GRN_OBJ_DO_SHALLOW_COPY));
        }
        GRN_TEXT_SET(ctx,
                     body,
                     GRN_TEXT_VALUE(&transformed),
                     GRN_TEXT_LEN(&transformed));
        GRN_OBJ_FIN(ctx, &transformed);
      } else {
        GRN_TEXT_PUTS(
          ctx,
          head,
          "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RESULT CODE=\"");
        grn_text_itoa(ctx, head, rc);
        GRN_TEXT_PUTS(ctx, head, "\" UP=\"");
        grn_text_ftoa(ctx, head, started);
        GRN_TEXT_PUTS(ctx, head, "\" ELAPSED=\"");
        grn_text_ftoa(ctx, head, elapsed);
        GRN_TEXT_PUTS(ctx, head, "\">\n");
        if (rc != GRN_SUCCESS) {
          GRN_TEXT_PUTS(ctx, head, "<ERROR>");
          grn_text_escape_xml(ctx, head, ctx->errbuf, strlen(ctx->errbuf));
          if (ctx->errfunc && ctx->errfile) {
            /* TODO: output backtrace */
            GRN_TEXT_PUTS(ctx, head, "<INFO FUNC=\"");
            grn_text_escape_xml(ctx, head, ctx->errfunc, strlen(ctx->errfunc));
            GRN_TEXT_PUTS(ctx, head, "\" FILE=\"");
            grn_text_escape_xml(ctx, head, ctx->errfile, strlen(ctx->errfile));
            GRN_TEXT_PUTS(ctx, head, "\" LINE=\"");
            grn_text_ulltoa(ctx, head, ctx->errline);
            GRN_TEXT_PUTS(ctx, head, "\"/>");
          }
          GRN_TEXT_PUTS(ctx, head, "</ERROR>");
        }
        GRN_TEXT_PUTS(ctx, foot, "\n</RESULT>");
      }
    }
    break;
  case GRN_CONTENT_MSGPACK:
#ifdef GRN_WITH_MESSAGE_PACK
    if (grn_ctx_get_command_version(ctx) <= GRN_COMMAND_VERSION_2) {
      grn_output_envelope_msgpack_v1(ctx,
                                     rc,
                                     head,
                                     body,
                                     foot,
                                     started,
                                     elapsed,
                                     file,
                                     line);
    } else {
      grn_output_envelope_msgpack(ctx,
                                  rc,
                                  head,
                                  body,
                                  foot,
                                  started,
                                  elapsed,
                                  file,
                                  line);
    }
#endif /* GRN_WITH_MESSAGE_PACK */
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    grn_output_envelope_apache_arrow(ctx,
                                     head,
                                     body,
                                     foot,
                                     rc,
                                     started,
                                     elapsed,
                                     file,
                                     line);
    break;
  case GRN_CONTENT_NONE:
    break;
  }
}

void
grn_output_envelope_open(grn_ctx *ctx, grn_obj *output)
{
  switch (ctx->impl->output.type) {
  case GRN_CONTENT_JSON:
    {
      grn_obj *expr;
      grn_obj *jsonp_func = NULL;

      expr = ctx->impl->curr_expr;
      if (expr) {
        jsonp_func = grn_expr_get_var(ctx,
                                      expr,
                                      JSON_CALLBACK_PARAM,
                                      strlen(JSON_CALLBACK_PARAM));
      }
      if (jsonp_func && GRN_TEXT_LEN(jsonp_func) > 0) {
        GRN_TEXT_PUT(ctx,
                     output,
                     GRN_TEXT_VALUE(jsonp_func),
                     GRN_TEXT_LEN(jsonp_func));
        GRN_TEXT_PUTC(ctx, output, '(');
      }

      bool is_stream_mode = true;
      bool have_body = true;
      if (grn_ctx_get_command_version(ctx) <= GRN_COMMAND_VERSION_2) {
        grn_output_envelope_open_json_v1(ctx,
                                         output,
                                         is_stream_mode,
                                         have_body);
      } else {
        grn_output_envelope_open_json(ctx, output, is_stream_mode, have_body);
      }
    }
    break;
  case GRN_CONTENT_TSV:
  case GRN_CONTENT_XML:
  case GRN_CONTENT_MSGPACK:
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
  case GRN_CONTENT_APACHE_ARROW:
  case GRN_CONTENT_NONE:
    break;
  }
}

void
grn_output_envelope_close(
  grn_ctx *ctx, grn_obj *output, grn_rc rc, const char *file, int line)
{
  double started, finished, elapsed;

  grn_timeval tv_now;
  grn_timeval_now(ctx, &tv_now);
  started = (double)(ctx->impl->tv.tv_sec);
  started += ctx->impl->tv.tv_nsec / GRN_TIME_NSEC_PER_SEC_F;
  finished = (double)(tv_now.tv_sec);
  finished += tv_now.tv_nsec / GRN_TIME_NSEC_PER_SEC_F;
  elapsed = finished - started;

  switch (ctx->impl->output.type) {
  case GRN_CONTENT_JSON:
    {
      grn_obj *expr;
      grn_obj *jsonp_func = NULL;

      expr = ctx->impl->curr_expr;
      if (expr) {
        jsonp_func = grn_expr_get_var(ctx,
                                      expr,
                                      JSON_CALLBACK_PARAM,
                                      strlen(JSON_CALLBACK_PARAM));
      }

      bool is_stream_mode = true;
      bool have_body = true;
      if (grn_ctx_get_command_version(ctx) <= GRN_COMMAND_VERSION_2) {
        grn_output_envelope_close_json_v1(ctx,
                                          output,
                                          output,
                                          is_stream_mode,
                                          have_body,
                                          rc,
                                          started,
                                          elapsed,
                                          file,
                                          line);
      } else {
        grn_output_envelope_close_json(ctx,
                                       output,
                                       output,
                                       is_stream_mode,
                                       have_body,
                                       rc,
                                       started,
                                       elapsed,
                                       file,
                                       line);
      }

      if (jsonp_func && GRN_TEXT_LEN(jsonp_func) > 0) {
        GRN_TEXT_PUTS(ctx, output, ");");
      }
    }
    break;
  case GRN_CONTENT_TSV:
  case GRN_CONTENT_XML:
  case GRN_CONTENT_MSGPACK:
  case GRN_CONTENT_GROONGA_COMMAND_LIST:
    break;
  case GRN_CONTENT_APACHE_ARROW:
    grn_output_envelope_close_apache_arrow(ctx,
                                           output,
                                           rc,
                                           started,
                                           elapsed,
                                           file,
                                           line);
    break;
  case GRN_CONTENT_NONE:
    break;
  }
}

grn_rc
grn_obj_format_fin(grn_ctx *ctx, grn_obj_format *format)
{
  GRN_API_ENTER;

  size_t n_columns = GRN_PTR_VECTOR_SIZE(&(format->columns));
  size_t i;
  for (i = 0; i < n_columns; i++) {
    grn_obj *column = GRN_PTR_VALUE_AT(&(format->columns), i);
    if (grn_is_reference_count_enable() ||
        column->header.type == GRN_ACCESSOR) {
      grn_obj_unlink(ctx, column);
    }
  }
  GRN_OBJ_FIN(ctx, &(format->columns));
  if (format->expression) {
    GRN_OBJ_FIN(ctx, format->expression);
  }

  GRN_API_RETURN(ctx->rc);
}

grn_rc
grn_obj_format_set_columns(grn_ctx *ctx,
                           grn_obj_format *format,
                           grn_obj *table,
                           const char *columns,
                           unsigned int columns_len)
{
  grn_rc rc;

  if (grn_expr_is_v1_format(ctx,
                            columns,
                            columns_len,
                            GRN_EXPR_V1_FORMAT_TYPE_OUTPUT_COLUMNS)) {
    rc = grn_obj_columns(ctx, table, columns, columns_len, &(format->columns));
  } else {
    format->expression =
      grn_output_columns_parse(ctx, table, columns, columns_len);
    rc = ctx->rc;
  }

  return rc;
}
