/* -*- c-basic-offset: 2 -*- */
/* Copyright(C) 2009-2015 Brazil

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License version 2.1 as published by the Free Software Foundation.

  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 <string.h>
#include "grn_str.h"
#include "grn_db.h"
#include "grn_expr_code.h"
#include "grn_util.h"
#include "grn_output.h"

#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)

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_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, 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, nelements);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST :
    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_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, 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, nelements);
#endif
    break;
  case GRN_CONTENT_GROONGA_COMMAND_LIST :
    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_NONE:
    break;
  }
  DECR_DEPTH;
  INCR_LENGTH;
}

void
grn_output_int32(grn_ctx *ctx, grn_obj *outbuf, grn_content_type output_type, int 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_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_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_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:
    grn_text_ftoa(ctx, outbuf, value);
    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_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_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, grn_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_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_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 = value;
  dv /= 1000000.0;
  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_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_NONE:
    break;
  }
  INCR_LENGTH;
}

static void
grn_text_atoj(grn_ctx *ctx, grn_obj *outbuf, grn_content_type output_type,
              grn_obj *obj, grn_id id)
{
  uint32_t vs;
  grn_obj buf;
  if (obj->header.type == GRN_ACCESSOR) {
    grn_accessor *a = (grn_accessor *)obj;
    GRN_TEXT_INIT(&buf, 0);
    for (;;) {
      buf.header.domain = grn_obj_get_range(ctx, obj);
      GRN_BULK_REWIND(&buf);
      switch (a->action) {
      case GRN_ACCESSOR_GET_ID :
        GRN_UINT32_PUT(ctx, &buf, id);
        buf.header.domain = GRN_DB_UINT32;
        break;
      case GRN_ACCESSOR_GET_KEY :
        grn_table_get_key2(ctx, a->obj, id, &buf);
        buf.header.domain = DB_OBJ(a->obj)->header.domain;
        break;
      case GRN_ACCESSOR_GET_VALUE :
        grn_obj_get_value(ctx, a->obj, id, &buf);
        buf.header.domain = DB_OBJ(a->obj)->range;
        break;
      case GRN_ACCESSOR_GET_SCORE :
        {
          grn_rset_recinfo *ri =
            (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          if (grn_ctx_get_command_version(ctx) == GRN_COMMAND_VERSION_1) {
            int32_t int32_score = ri->score;
            GRN_INT32_PUT(ctx, &buf, int32_score);
            buf.header.domain = GRN_DB_INT32;
          } else {
            double float_score = ri->score;
            GRN_FLOAT_PUT(ctx, &buf, float_score);
            buf.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, &buf, ri->n_subrecs);
        }
        buf.header.domain = GRN_DB_INT32;
        break;
      case GRN_ACCESSOR_GET_MAX :
        {
          grn_rset_recinfo *ri = (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          int64_t max;
          max = grn_rset_recinfo_get_max(ctx, ri, a->obj);
          GRN_INT64_PUT(ctx, &buf, max);
        }
        buf.header.domain = GRN_DB_INT64;
        break;
      case GRN_ACCESSOR_GET_MIN :
        {
          grn_rset_recinfo *ri = (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          int64_t min;
          min = grn_rset_recinfo_get_min(ctx, ri, a->obj);
          GRN_INT64_PUT(ctx, &buf, min);
        }
        buf.header.domain = GRN_DB_INT64;
        break;
      case GRN_ACCESSOR_GET_SUM :
        {
          grn_rset_recinfo *ri = (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          int64_t sum;
          sum = grn_rset_recinfo_get_sum(ctx, ri, a->obj);
          GRN_INT64_PUT(ctx, &buf, sum);
        }
        buf.header.domain = GRN_DB_INT64;
        break;
      case GRN_ACCESSOR_GET_AVG :
        {
          grn_rset_recinfo *ri = (grn_rset_recinfo *)grn_obj_get_value_(ctx, a->obj, id, &vs);
          double avg;
          avg = grn_rset_recinfo_get_avg(ctx, ri, a->obj);
          GRN_FLOAT_PUT(ctx, &buf, avg);
        }
        buf.header.domain = GRN_DB_FLOAT;
        break;
      case GRN_ACCESSOR_GET_COLUMN_VALUE :
        if ((a->obj->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) == GRN_OBJ_COLUMN_VECTOR) {
          if (a->next) {
            grn_id *idp;
            grn_obj_get_value(ctx, a->obj, id, &buf);
            idp = (grn_id *)GRN_BULK_HEAD(&buf);
            vs = GRN_BULK_VSIZE(&buf) / sizeof(grn_id);
            grn_output_array_open(ctx, outbuf, output_type, "VECTOR", vs);
            for (; vs--; idp++) {
              grn_text_atoj(ctx, outbuf, output_type, (grn_obj *)a->next, *idp);
            }
            grn_output_array_close(ctx, outbuf, output_type);
          } else {
            grn_text_atoj(ctx, outbuf, output_type, a->obj, id);
          }
          goto exit;
        } else {
          grn_obj_get_value(ctx, a->obj, id, &buf);
        }
        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(&buf) >= sizeof(grn_id)) {
          id = *((grn_id *)GRN_BULK_HEAD(&buf));
        } else {
          id = GRN_ID_NIL;
        }
      } else {
        break;
      }
    }
    grn_output_obj(ctx, outbuf, output_type, &buf, NULL);
  } else {
    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_VALUE_FIX_SIZE_INIT(&buf, 0, DB_OBJ(obj)->range);
      break;
    case GRN_COLUMN_VAR_SIZE :
      if ((obj->header.flags & GRN_OBJ_COLUMN_TYPE_MASK) == GRN_OBJ_COLUMN_VECTOR) {
        grn_obj *range = grn_ctx_at(ctx, DB_OBJ(obj)->range);
        if (GRN_OBJ_TABLEP(range) ||
            (range->header.flags & GRN_OBJ_KEY_VAR_SIZE) == 0) {
          GRN_VALUE_FIX_SIZE_INIT(&buf, GRN_OBJ_VECTOR, DB_OBJ(obj)->range);
        } else {
          GRN_VALUE_VAR_SIZE_INIT(&buf, GRN_OBJ_VECTOR, DB_OBJ(obj)->range);
        }
        if (obj->header.flags & GRN_OBJ_WITH_WEIGHT) {
          format.flags |= GRN_OBJ_FORMAT_WITH_WEIGHT;
          format_argument = &format;
        }
      } else {
        GRN_VALUE_VAR_SIZE_INIT(&buf, 0, DB_OBJ(obj)->range);
      }
      break;
    case GRN_COLUMN_INDEX :
      GRN_UINT32_INIT(&buf, 0);
      break;
    default:
      GRN_TEXT_INIT(&buf, 0);
      break;
    }
    grn_obj_get_value(ctx, obj, id, &buf);
    grn_output_obj(ctx, outbuf, output_type, &buf, format_argument);
  }
exit :
  grn_obj_close(ctx, &buf);
}

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_int32(ctx, outbuf, output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_INT8_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT8 :
    grn_output_int32(ctx, outbuf, output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_UINT8_VALUE(bulk) : 0);
    break;
  case GRN_DB_INT16 :
    grn_output_int32(ctx, outbuf, output_type,
                     GRN_BULK_VSIZE(bulk) ? GRN_INT16_VALUE(bulk) : 0);
    break;
  case GRN_DB_UINT16 :
    grn_output_int32(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_int64(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;
  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) {
      int j;
      int 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", 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", ncolumns);
      for (j = 0; j < ncolumns; j++) {
        grn_text_atoj(ctx, outbuf, output_type, columns[j], id);
      }
      grn_output_array_close(ctx, outbuf, output_type);
    } else {
      grn_obj *table = grn_ctx_at(ctx, bulk->header.domain);
      grn_id id = GRN_RECORD_VALUE(bulk);
      if (table && table->header.type != GRN_TABLE_NO_KEY) {
        grn_obj *accessor = grn_obj_column(ctx, table,
                                           GRN_COLUMN_NAME_KEY,
                                           GRN_COLUMN_NAME_KEY_LEN);
        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, format);
      } else {
        grn_output_int64(ctx, outbuf, output_type, id);
      }
    }
    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)
{
  unsigned int i_hit, n_hits;
  unsigned int i_column, n_columns;
  unsigned int n_elements;
  grn_obj **columns;
  grn_obj buf;
  grn_bool with_column_names = GRN_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 = GRN_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", n_elements);

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

  if (with_column_names) {
    grn_output_array_open(ctx, outbuf, output_type, "COLUMNS", 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", 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(grn_ctx *ctx, grn_obj *outbuf, grn_content_type output_type,
                   grn_obj *uvector, grn_obj_format *format)
{
  grn_bool output_result_set = GRN_FALSE;
  grn_bool with_weight = GRN_FALSE;
  grn_obj *range;
  grn_bool range_is_type;

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

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

  range = grn_ctx_at(ctx, uvector->header.domain);
  range_is_type = (range->header.type == GRN_TYPE);
  if (range_is_type) {
    unsigned int i, n;
    char *raw_elements;
    unsigned int element_size;
    grn_obj element;

    raw_elements = GRN_BULK_HEAD(uvector);
    element_size = GRN_TYPE_SIZE(DB_OBJ(range));
    n = GRN_BULK_VSIZE(uvector) / element_size;

    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", 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 {
    unsigned int 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", n);
    } else {
      grn_output_array_open(ctx, outbuf, output_type, "VECTOR", n);
    }

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

      id = grn_uvector_get_element(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) {
        grn_output_uint64(ctx, outbuf, output_type, 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_unlink(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)
{
  grn_bool with_weight = GRN_FALSE;

  if (vector->header.domain == GRN_DB_VOID) {
    ERR(GRN_INVALID_ARGUMENT, "invalid obj->header.domain");
    return;
  }

  if (format) {
    if (format->flags & GRN_OBJ_FORMAT_WITH_WEIGHT) {
      with_weight = GRN_TRUE;
    }
  }

  if (with_weight) {
    unsigned int 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", n);
    for (i = 0; i < n; i++) {
      const char *_value;
      unsigned int weight, 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_uint64(ctx, outbuf, output_type, weight);
    }
    grn_output_map_close(ctx, outbuf, output_type);
    GRN_OBJ_FIN(ctx, &value);
  } else {
    unsigned int i, n;
    grn_obj value;
    GRN_VOID_INIT(&value);
    n = grn_vector_size(ctx, vector);
    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", n);
    for (i = 0; i < n; i++) {
      const char *_value;
      unsigned int weight, 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 {
    unsigned int i, n;
    grn_output_array_open(ctx, outbuf, output_type, "VECTOR", -1);
    n = GRN_BULK_VSIZE(pvector) / sizeof(grn_obj *);
    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 (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)
{
  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 inline int
count_n_elements_in_expression(grn_ctx *ctx, grn_obj *expression)
{
  int n_elements = 0;
  grn_bool is_first_comma = GRN_TRUE;
  grn_expr *expr = (grn_expr *)expression;
  grn_expr_code *code;
  grn_expr_code *code_end = expr->codes + expr->codes_curr;

  for (code = expr->codes; code < code_end; code++) {
    if (code->op == GRN_OP_COMMA) {
      n_elements++;
      if (is_first_comma) {
        n_elements++;
        is_first_comma = GRN_FALSE;
      }
    }
  }

  return n_elements;
}

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

  if (obj->header.type != GRN_ACCESSOR) {
    return GRN_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);
    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);
  } 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);
  }
}

static inline void
grn_output_table_column_by_expression(grn_ctx *ctx, grn_obj *outbuf,
                                      grn_content_type output_type,
                                      grn_expr_code *code,
                                      grn_expr_code *code_end,
                                      grn_obj *buf)
{
  if (code_end <= code) {
    grn_output_table_column_info(ctx,
                                 outbuf,
                                 output_type,
                                 NULL,
                                 NULL);
    return;
  }

  switch (code_end[-1].op) {
  case GRN_OP_GET_MEMBER :
    if ((code_end - code) == 3) {
      GRN_BULK_REWIND(buf);
      grn_column_name_(ctx, code[0].value, buf);
      GRN_TEXT_PUTC(ctx, buf, '[');
      grn_inspect(ctx, buf, code[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);
    } else {
      grn_output_table_column(ctx, outbuf, output_type, code->value, buf);
    }
    break;
  default :
    grn_output_table_column(ctx, outbuf, output_type, code->value, buf);
    break;
  }
}

static inline void
grn_output_table_columns_open(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              int n_columns)
{
  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 (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)
{
  int n_elements;
  int previous_comma_offset = -1;
  grn_bool is_first_comma = GRN_TRUE;
  grn_bool have_comma = GRN_FALSE;
  grn_expr *expr = (grn_expr *)format->expression;
  grn_expr_code *code;
  grn_expr_code *code_end = expr->codes + expr->codes_curr;

  n_elements = count_n_elements_in_expression(ctx, format->expression);

  grn_output_table_columns_open(ctx, outbuf, output_type, n_elements);

  for (code = expr->codes; code < code_end; code++) {
    int code_start_offset;

    if (code->op != GRN_OP_COMMA) {
      continue;
    }

    have_comma = GRN_TRUE;
    if (is_first_comma) {
      unsigned int n_used_codes;
      int code_end_offset;

      n_used_codes = grn_expr_code_n_used_codes(ctx, expr->codes, code - 1);
      code_end_offset = code - expr->codes - n_used_codes;

      grn_output_table_column_by_expression(ctx, outbuf, output_type,
                                            expr->codes,
                                            expr->codes + code_end_offset,
                                            buf);
      code_start_offset = code_end_offset;
      is_first_comma = GRN_FALSE;
    } else {
      code_start_offset = previous_comma_offset + 1;
    }

    grn_output_table_column_by_expression(ctx, outbuf, output_type,
                                          expr->codes + code_start_offset,
                                          code,
                                          buf);
    previous_comma_offset = code - expr->codes;
  }

  if (!have_comma && expr->codes_curr > 0) {
    grn_output_table_column_by_expression(ctx, outbuf, output_type,
                                          expr->codes,
                                          code_end,
                                          buf);
  }

  grn_output_table_columns_close(ctx, outbuf, output_type);
}

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)
{
  int i;
  int ncolumns = GRN_BULK_VSIZE(&format->columns)/sizeof(grn_obj *);
  grn_obj **columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);

  grn_output_table_columns_open(ctx, outbuf, output_type, 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 (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 (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_text_atoj(ctx, outbuf, output_type, column, id);
}

static inline void
grn_output_table_record_by_expression(grn_ctx *ctx,
                                      grn_obj *outbuf,
                                      grn_content_type output_type,
                                      grn_obj *expression,
                                      grn_obj *record)
{
  grn_expr *expr = (grn_expr *)expression;

  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));
  } 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);
    }
  }
}

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)
{
  int n_elements = 0;
  grn_id id;
  grn_obj *record;
  grn_expr *expr = (grn_expr *)format->expression;
  grn_expr_code *code;
  grn_expr_code *code_end = expr->codes + expr->codes_curr;

  n_elements = count_n_elements_in_expression(ctx, format->expression);
  record = grn_expr_get_var_by_offset(ctx, format->expression, 0);
  while ((id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL) {
    int previous_comma_offset = -1;
    grn_bool is_first_comma = GRN_TRUE;
    grn_bool have_comma = GRN_FALSE;
    GRN_RECORD_SET(ctx, record, id);
    grn_output_table_record_open(ctx, outbuf, output_type, n_elements);
    for (code = expr->codes; code < code_end; code++) {
      if (code->op == GRN_OP_COMMA) {
        int code_start_offset = previous_comma_offset + 1;
        int code_end_offset;
        int original_codes_curr = expr->codes_curr;

        have_comma = GRN_TRUE;
        if (is_first_comma) {
          int second_code_offset;
          unsigned int second_code_n_used_codes;
          second_code_offset = code - expr->codes - 1;
          second_code_n_used_codes =
            grn_expr_code_n_used_codes(ctx,
                                       expr->codes,
                                       expr->codes + second_code_offset);
          expr->codes_curr = second_code_offset - second_code_n_used_codes + 1;
          grn_output_table_record_by_expression(ctx,
                                                outbuf,
                                                output_type,
                                                format->expression,
                                                record);
          code_start_offset = expr->codes_curr;
          is_first_comma = GRN_FALSE;
        }
        code_end_offset = code - expr->codes - code_start_offset;
        expr->codes += code_start_offset;
        expr->codes_curr = code_end_offset;
        grn_output_table_record_by_expression(ctx,
                                              outbuf,
                                              output_type,
                                              format->expression,
                                              record);
        expr->codes -= code_start_offset;
        expr->codes_curr = original_codes_curr;
        previous_comma_offset = code - expr->codes;
      }
    }

    if (!have_comma && expr->codes_curr > 0) {
      grn_output_table_record_by_expression(ctx,
                                            outbuf,
                                            output_type,
                                            format->expression,
                                            record);
    }

    grn_output_table_record_close(ctx, outbuf, output_type);
  }
}

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)
{
  int i;
  grn_id id;
  int ncolumns = GRN_BULK_VSIZE(&format->columns)/sizeof(grn_obj *);
  grn_obj **columns = (grn_obj **)GRN_BULK_HEAD(&format->columns);
  while ((id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL) {
    grn_output_table_record_open(ctx, outbuf, output_type, ncolumns);
    for (i = 0; i < ncolumns; i++) {
      grn_output_table_record_by_column(ctx,
                                        outbuf,
                                        output_type,
                                        columns[i],
                                        id);
    }
    grn_output_table_record_close(ctx, outbuf, output_type);
  }
}

static inline void
grn_output_table_records_open(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              int n_records)
{
  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 inline void
grn_output_table_records_close(grn_ctx *ctx,
                               grn_obj *outbuf,
                               grn_content_type output_type)
{
  if (grn_ctx_get_command_version(ctx) >= GRN_COMMAND_VERSION_3) {
    grn_output_array_close(ctx, outbuf, output_type);
  }
}

void
grn_output_table_records(grn_ctx *ctx, grn_obj *outbuf,
                         grn_content_type output_type,
                         grn_obj *table, grn_obj_format *format)
{
  grn_table_cursor *tc;

  grn_output_table_records_open(ctx, outbuf, output_type, format->limit);
  tc = grn_table_cursor_open(ctx, table, NULL, 0, NULL, 0,
                             format->offset, format->limit,
                             GRN_CURSOR_ASCENDING);
  if (tc) {
    if (format->expression) {
      grn_output_table_records_by_expression(ctx, outbuf, output_type,
                                             tc, format);
    } else {
      grn_output_table_records_by_columns(ctx, outbuf, output_type,
                                          tc, format);
    }
    grn_table_cursor_close(ctx, tc);
  } else {
    ERRCLR(ctx);
  }
  grn_output_table_records_close(ctx, outbuf, output_type);
}

static void
grn_output_result_set_open_v1(grn_ctx *ctx,
                              grn_obj *outbuf,
                              grn_content_type output_type,
                              grn_obj *table,
                              grn_obj_format *format,
                              uint32_t n_additional_elements)
{
  grn_obj buf;
  GRN_TEXT_INIT(&buf, 0);
  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 += 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);
    }
    grn_output_table_records(ctx, outbuf, output_type, table, format);
  } else {
    int i;
    grn_obj *column = grn_obj_column(ctx, table,
                                     GRN_COLUMN_NAME_KEY,
                                     GRN_COLUMN_NAME_KEY_LEN);
    grn_table_cursor *tc = grn_table_cursor_open(ctx, table, NULL, 0, NULL, 0,
                                                 0, -1, GRN_CURSOR_ASCENDING);
    grn_output_array_open(ctx, outbuf, output_type, "HIT", -1);
    if (tc) {
      grn_id id;
      for (i = 0; (id = grn_table_cursor_next(ctx, tc)) != GRN_ID_NIL; i++) {
        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_cursor_close(ctx, tc);
    }
    grn_obj_unlink(ctx, column);
  }
  GRN_OBJ_FIN(ctx, &buf);
}

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_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)
{
  grn_obj buf;
  GRN_TEXT_INIT(&buf, 0);
  if (format) {
    int n_elements = 2;
    /* result_set: {"n_hits": N, ("columns": COLUMNS,) "records": records} */
    if (format->flags & GRN_OBJ_FORMAT_WITH_COLUMN_NAMES) {
      n_elements++;
    }
    n_elements += n_additional_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);
    }
    grn_output_table_records(ctx, outbuf, output_type, result_set, format);
  } else {
    grn_obj *column;
    int n_records;
    int n_elements = 1;

    column = grn_obj_column(ctx,
                            result_set,
                            GRN_COLUMN_NAME_KEY,
                            GRN_COLUMN_NAME_KEY_LEN);
    n_elements += n_additional_elements;
    grn_output_map_open(ctx, outbuf, output_type, "result_set", n_elements);
    n_records = grn_table_size(ctx, result_set);
    grn_output_cstr(ctx, outbuf, output_type, "keys");
    grn_output_array_open(ctx, outbuf, output_type, "keys", n_records);
    GRN_TABLE_EACH_BEGIN(ctx, result_set, 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_output_array_close(ctx, outbuf, output_type);
    grn_obj_unlink(ctx, column);
  }
  GRN_OBJ_FIN(ctx, &buf);
}

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(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 (grn_ctx_get_command_version(ctx) < GRN_COMMAND_VERSION_3) {
    grn_output_result_set_open_v1(ctx,
                                  outbuf,
                                  output_type,
                                  result_set,
                                  format,
                                  n_additional_elements);
  } else {
    grn_output_result_set_open_v3(ctx,
                                  outbuf,
                                  output_type,
                                  result_set,
                                  format,
                                  n_additional_elements);
  }
}

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 (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);
}

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 :
  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;
  unsigned int len;
  int offset = 0, limit = 0, record_n = 0;
  int column_n = 0, column_text_n = 0, result_set_n = -1;
  grn_bool in_vector = GRN_FALSE;
  unsigned int vector_element_n = 0;
  grn_bool in_weight_vector = GRN_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 = GRN_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 = GRN_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 = GRN_FALSE;
          GRN_TEXT_PUTS(ctx, transformed, "</FIELD>\n");
        } else if (EQUAL_NAME_P("WEIGHT_VECTOR")) {
          in_weight_vector = GRN_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) {
                grn_bool is_key;
                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_normalize_offset_and_limit(ctx, 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_json_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)
{
  size_t indent_level = 0;

  json_array_open(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_itoa(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 (GRN_TEXT_LEN(body)) {
    json_element_end(ctx, head, indent_level);
  }

  json_array_close(ctx, foot, &indent_level);
}

static void
grn_output_envelope_json(grn_ctx *ctx,
                         grn_rc rc,
                         grn_obj *head,
                         grn_obj *body,
                         grn_obj *foot,
                         double started,
                         double elapsed,
                         const char *file,
                         int line)
{
  size_t indent_level = 0;

  json_map_open(ctx, head, &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_itoa(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);

    if (GRN_TEXT_LEN(body)) {
      json_value_end(ctx, head, indent_level);
      json_key(ctx, head, "body");
    }

    json_map_close(ctx, foot, &indent_level);
  }
}

#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;
  int 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;
      int      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_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;
  int 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);
  {
    int 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) {
        int 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_int(&packer, ctx->errline);
          }

          if (file || command) {
            int 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 */

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 = ctx->impl->tv.tv_sec;
  started += ctx->impl->tv.tv_nsec / GRN_TIME_NSEC_PER_SEC_F;
  finished = 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, rc,
                                    head, body, foot,
                                    started, elapsed,
                                    file, line);
      } else {
        grn_output_envelope_json(ctx, rc,
                                 head, body, foot,
                                 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_itoa(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);
        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_itoa(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_NONE:
    break;
  }
}

static inline grn_bool
is_output_columns_format_v1(grn_ctx *ctx,
                            const char *output_columns,
                            unsigned int output_columns_len)
{
  const char *current;
  const char *end;
  grn_bool in_identifier = GRN_FALSE;

  current = output_columns;
  end = current + output_columns_len;
  while (current < end) {
    int char_length;

    char_length = grn_charlen(ctx, current, end);
    if (char_length != 1) {
      return GRN_FALSE;
    }

    switch (current[0]) {
    case ' ' :
    case ',' :
      in_identifier = GRN_FALSE;
      break;
    case '_' :
      in_identifier = GRN_TRUE;
      break;
    case '.' :
    case '-' :
    case '#' :
    case '@' :
      if (!in_identifier) {
        return GRN_FALSE;
      }
      break;
    default :
      if ('a' <= current[0] && current[0] <= 'z') {
        in_identifier = GRN_TRUE;
        break;
      } else if ('A' <= current[0] && current[0] <= 'Z') {
        in_identifier = GRN_TRUE;
        break;
      } else if ('0' <= current[0] && current[0] <= '9') {
        in_identifier = GRN_TRUE;
        break;
      } else {
        return GRN_FALSE;
      }
    }

    current += char_length;
  }

  return GRN_TRUE;
}

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

  if (is_output_columns_format_v1(ctx, columns, columns_len)) {
    rc = grn_obj_columns(ctx, table, columns, columns_len, &(format->columns));
  } else {
    grn_obj *variable;
    GRN_EXPR_CREATE_FOR_QUERY(ctx, table, format->expression, variable);
    rc = grn_expr_parse(ctx, format->expression,
                        columns, columns_len, NULL,
                        GRN_OP_MATCH, GRN_OP_AND,
                        GRN_EXPR_SYNTAX_OUTPUT_COLUMNS);
  }

  return rc;
}
