/*
  Copyright (C) 2009-2018  Brazil
  Copyright (C) 2019-2025  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_arrow.h"
#include "grn_request_canceler.h"
#include "grn_request_timer.h"
#include "grn_tokenizers.h"
#include "grn_ctx_impl.h"
#include "grn_distance.h"
#include "grn_encoding.h"
#include "grn_hash.h"
#include "grn_ii.h"
#include "grn_language_model.hpp"
#include "grn_pat.h"
#include "grn_index_column.h"
#include "grn_proc.h"
#include "grn_plugin.h"
#include "grn_snip.h"
#include "grn_output.h"
#include "grn_normalizer.h"
#include "grn_mrb.h"
#include "grn_ctx_impl_mrb.h"
#include "grn_ctx_impl_lua.h"
#include "grn_logger.h"
#include "grn_cache.h"
#include "grn_expr.h"
#include "grn_table_selector.h"
#include "grn_task_executor.hpp"
#include "grn_token_column.h"
#include "grn_window_function_executor.h"
#include "grn_windows.h"
#include "grn_group.h"
#include "grn_slow_log.h"

#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

#ifdef GRN_SUPPORT_REGEXP
#  include <onigmo.h>
#endif /* GRN_SUPPORT_REGEXP */

#ifdef GRN_WITH_BLOSC
#  include <blosc2.h>
#endif

#ifdef WIN32
#  include <share.h>
#  ifndef MAXUSHORT
#    define MAXUSHORT 0xffff
#  endif
#else /* WIN32 */
#  include <netinet/in.h>
#  ifdef HAVE_EXECINFO_H
#    include <execinfo.h>
#  endif /* HAVE_EXECINFO_H */
#endif   /* WIN32 */

/* TODO: Is there configuration for this style? */
/* clang-format off */
#define GRN_CTX_INITIALIZER(enc)                \
  {                                             \
    /* rc */ GRN_SUCCESS,                       \
    /* flags */ 0,                              \
    /* encoding */ enc,                         \
    /* ntrace */ 0,                             \
    /* errlvl */ GRN_LOG_NOTICE,                \
    /* stat */ GRN_CTX_FIN,                     \
    /* seqno */ 0,                              \
    /* subno */ 0,                              \
    /* seqno2 */ 0,                             \
    /* errline */ 0,                            \
    /* user_data */ {0},                        \
    /* prev */ NULL,                            \
    /* next */ NULL,                            \
    /* errfile */ NULL,                         \
    /* errfunc */ NULL,                         \
    /* impl */ NULL,                            \
    /* trace */ {0},                            \
    /* errbuf */ {0}                            \
  }
/* clang-format on */

#define GRN_CTX_CLOSED(ctx) ((ctx)->stat == GRN_CTX_FIN)

extern "C" {
grn_ctx grn_gctx = GRN_CTX_INITIALIZER(GRN_ENC_DEFAULT);
int grn_pagesize;
grn_critical_section grn_glock;
uint32_t grn_gtick;
int grn_lock_timeout = GRN_LOCK_TIMEOUT;

#ifdef USE_UYIELD
int grn_uyield_count = 0;
#endif

static bool grn_ctx_per_db = false;
static bool grn_back_trace_enable = true;
static int32_t grn_n_workers_default = 0;

static void
grn_init_from_env(void)
{
  {
    char grn_ctx_per_db_env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_CTX_PER_DB", grn_ctx_per_db_env, GRN_ENV_BUFFER_SIZE);
    if (grn_ctx_per_db_env[0] && strcmp(grn_ctx_per_db_env, "yes") == 0) {
      grn_ctx_per_db = true;
    }
  }

  {
    char grn_back_trace_enable_env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_BACK_TRACE_ENABLE",
               grn_back_trace_enable_env,
               GRN_ENV_BUFFER_SIZE);
    if (grn_back_trace_enable_env[0] &&
        strcmp(grn_back_trace_enable_env, "no") == 0) {
      grn_back_trace_enable = false;
    }
  }

  /* For backward compatibility. */
  {
    char env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_SELECT_N_WORKERS_DEFAULT", env, GRN_ENV_BUFFER_SIZE);
    if (env[0]) {
      grn_n_workers_default = atoi(env);
    }
  }
  {
    char env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_N_WORKERS_DEFAULT", env, GRN_ENV_BUFFER_SIZE);
    if (env[0]) {
      grn_n_workers_default = atoi(env);
    }
  }

  grn_error_init_from_env();

  grn_alloc_init_from_env();
  grn_mrb_init_from_env();
  grn_ctx_impl_mrb_init_from_env();
  grn_distance_init_from_env();
  grn_hash_init_from_env();
  grn_io_init_from_env();
  grn_ii_init_from_env();
  grn_ja_init_from_env();
  grn::language_model::init_from_env();
  grn_db_init_from_env();
  grn_expr_init_from_env();
  grn_index_column_init_from_env();
  grn_output_init_from_env();
  grn_proc_init_from_env();
  grn_proc_query_init_from_env();
  grn_plugin_init_from_env();
  grn_token_column_init_from_env();
  grn_group_init_from_env();
  grn_window_function_executor_init_from_env();
  grn_table_selector_init_from_env();
  grn_slow_log_init_from_env();
}

static void
grn_init_external_libraries(void)
{
#ifdef GRN_SUPPORT_REGEXP
  onig_init();
#endif /*  GRN_SUPPORT_REGEXP */
#ifdef GRN_WITH_BLOSC
  blosc2_init();
#endif
  grn_arrow_init_external_libraries();
  grn_distance_init_external_libraries();
}

static void
grn_fin_external_libraries(void)
{
#ifdef GRN_SUPPORT_REGEXP
  onig_end();
#endif /*  GRN_SUPPORT_REGEXP */
#ifdef GRN_WITH_BLOSC
  blosc2_destroy();
#endif
  grn::language_model::fin_external_libraries();
}

void
grn_sleep(uint32_t seconds)
{
#ifdef WIN32
  Sleep(seconds * 1000);
#else  // WIN32
  sleep(seconds);
#endif // WIN32
}

void
grn_nanosleep(uint64_t nanoseconds)
{
#ifdef WIN32
  Sleep((DWORD)(nanoseconds / 1000000));
#else  // WIN32
  struct timespec interval;
  interval.tv_sec = (time_t)(nanoseconds / 1000000000);
  interval.tv_nsec = (long)(nanoseconds % 1000000000);
  nanosleep(&interval, NULL);
#endif // WIN32
}

const char *
grn_get_global_error_message(void)
{
  return grn_gctx.errbuf;
}

static void
grn_loader_init(grn_loader *loader)
{
  GRN_TEXT_INIT(&loader->values, 0);
  GRN_UINT32_INIT(&loader->level, GRN_OBJ_VECTOR);
  loader->columns = NULL;
  GRN_PTR_INIT(&loader->ranges, GRN_OBJ_VECTOR, GRN_ID_NIL);
  GRN_PTR_INIT(&loader->indexes, GRN_OBJ_VECTOR, GRN_ID_NIL);
  GRN_UINT32_INIT(&loader->ids, GRN_OBJ_VECTOR);
  GRN_INT32_INIT(&loader->return_codes, GRN_OBJ_VECTOR);
  GRN_TEXT_INIT(&loader->error_messages, GRN_OBJ_VECTOR);
  loader->id_offset = -1;
  loader->key_offset = -1;
  loader->last = NULL;
  loader->table = NULL;
  loader->ifexists = NULL;
  loader->each = NULL;
  loader->values_size = 0;
  loader->n_records = 0;
  loader->n_record_errors = 0;
  loader->n_column_errors = 0;
  loader->stat = GRN_LOADER_BEGIN;
  loader->columns_status = GRN_LOADER_COLUMNS_UNSET;
  loader->error.rc = GRN_SUCCESS;
  loader->error.buffer[0] = '\0';
  loader->error.line = 0;
  loader->error.file = NULL;
  loader->error.func = NULL;
  loader->output_ids = false;
  loader->output_errors = false;
}

void
grn_ctx_loader_clear(grn_ctx *ctx)
{
  grn_loader *loader = &ctx->impl->loader;
  grn_obj *v = (grn_obj *)(GRN_BULK_HEAD(&loader->values));
  grn_obj *ve = (grn_obj *)(GRN_BULK_CURR(&loader->values));
  if (ctx->impl->db) {
    size_t i;
    size_t n_ranges = GRN_PTR_VECTOR_SIZE(&(loader->ranges));
    for (i = 0; i < n_ranges; i++) {
      grn_obj_unlink(ctx, GRN_PTR_VALUE_AT(&(loader->ranges), i));
    }
    size_t n_indexes = GRN_PTR_VECTOR_SIZE(&(loader->indexes));
    for (i = 0; i < n_indexes; i++) {
      grn_obj_unlink(ctx, GRN_PTR_VALUE_AT(&(loader->indexes), i));
    }
    if (loader->columns) {
      GRN_HASH_EACH_BEGIN(ctx, loader->columns, cursor, id)
      {
        void *value;
        grn_hash_cursor_get_value(ctx, cursor, &value);
        if (value) {
          grn_obj *column = *((grn_obj **)value);
          grn_obj_unlink(ctx, column);
        }
      }
      GRN_HASH_EACH_END(ctx, cursor);
    }
    if (loader->table) {
      grn_obj_unlink(ctx, loader->table);
    }
  }
  if (loader->ifexists) {
    grn_obj_unlink(ctx, loader->ifexists);
  }
  if (loader->each) {
    grn_obj_unlink(ctx, loader->each);
  }
  while (v < ve) {
    GRN_OBJ_FIN(ctx, v++);
  }
  GRN_OBJ_FIN(ctx, &loader->values);
  GRN_OBJ_FIN(ctx, &loader->level);
  if (loader->columns) {
    grn_hash_close(ctx, loader->columns);
  }
  GRN_OBJ_FIN(ctx, &loader->ranges);
  GRN_OBJ_FIN(ctx, &loader->indexes);
  GRN_OBJ_FIN(ctx, &loader->ids);
  GRN_OBJ_FIN(ctx, &loader->return_codes);
  GRN_OBJ_FIN(ctx, &loader->error_messages);
#ifdef GRN_WITH_APACHE_ARROW
  if (ctx->impl->arrow_stream_loader) {
    grn_arrow_stream_loader_close(ctx, ctx->impl->arrow_stream_loader);
    ctx->impl->arrow_stream_loader = NULL;
  }
#endif
  grn_loader_init(loader);
}

#define IMPL_SIZE                                                              \
  (size_t)((((int)sizeof(struct _grn_ctx_impl)) + (grn_pagesize - 1)) &        \
           ~(grn_pagesize - 1))

#ifdef GRN_WITH_MESSAGE_PACK
static int
grn_msgpack_buffer_write(void *data, const char *buf, msgpack_size_t len)
{
  grn_ctx *ctx = (grn_ctx *)data;
  return grn_bulk_write(ctx, ctx->impl->output.buf, buf, len);
}
#endif

typedef struct {
  void *data;
  grn_close_func close_func;
} grn_ctx_local_variable;

static grn_rc
grn_ctx_impl_init(grn_ctx *ctx)
{
  grn_io_mapinfo mi;
  if (!(ctx->impl =
          static_cast<_grn_ctx_impl *>(grn_io_anon_map(ctx, &mi, IMPL_SIZE)))) {
    return ctx->rc;
  }
  grn_alloc_init_ctx_impl(ctx);
  ctx->impl->encoding = ctx->encoding;
  ctx->impl->lifoseg = -1;
  ctx->impl->currseg = -1;

  ctx->impl->values = NULL;
  ctx->impl->temporary_columns = NULL;
  ctx->impl->temporary_options = NULL;
  ctx->impl->columns_cache = NULL;
  ctx->impl->ios = NULL;
  ctx->impl->expr_vars = NULL;
  ctx->impl->stack = NULL;
  ctx->impl->current_selector_data = NULL;

  CRITICAL_SECTION_INIT(ctx->impl->lock);
  CRITICAL_SECTION_INIT(ctx->impl->columns_cache_lock);
  if (!(ctx->impl->values =
          grn_array_create(ctx, NULL, sizeof(grn_db_obj *), GRN_ARRAY_TINY))) {
    goto exit;
  }
  if (!(ctx->impl->temporary_columns = grn_pat_create(ctx,
                                                      NULL,
                                                      GRN_TABLE_MAX_KEY_SIZE,
                                                      sizeof(grn_obj *),
                                                      0))) {
    goto exit;
  }
  ctx->impl->temporary_options =
    grn_options_create(ctx, NULL, "[ctx][impl][create]");
  if (!ctx->impl->temporary_options) {
    goto exit;
  }
  if (!(ctx->impl->columns_cache =
          grn_hash_create(ctx,
                          NULL,
                          sizeof(grn_id),
                          sizeof(grn_hash *),
                          GRN_OBJ_TABLE_HASH_KEY | GRN_HASH_TINY))) {
    goto exit;
  }
  if (!(ctx->impl->ios =
          grn_hash_create(ctx,
                          NULL,
                          GRN_TABLE_MAX_KEY_SIZE,
                          sizeof(grn_io *),
                          GRN_OBJ_KEY_VAR_SIZE | GRN_HASH_TINY))) {
    goto exit;
  }
  ctx->impl->db = NULL;

  if (!(ctx->impl->expr_vars =
          grn_hash_create(ctx, NULL, sizeof(grn_id), sizeof(grn_obj *), 0))) {
    goto exit;
  }
  if (!(ctx->impl->stack = GRN_MALLOCN(grn_obj *, GRN_STACK_SIZE))) {
    goto exit;
  }
  ctx->impl->stack_curr = 0;
  ctx->impl->stack_size = GRN_STACK_SIZE;
  ctx->impl->curr_expr = NULL;
  GRN_TEXT_INIT(&ctx->impl->current_request_id, 0);
  ctx->impl->current_request_timer_id = NULL;
  /* grn_obj * isn't void * but sizeof(grn_obj *) and sizeof(void *)
   * is same. So we reuse GRN_PVECTOR here. */
  GRN_PTR_INIT(&(ctx->impl->expr_parsers), GRN_OBJ_VECTOR, GRN_ID_NIL);

  GRN_TEXT_INIT(&ctx->impl->output.names, GRN_OBJ_VECTOR);
  GRN_UINT32_INIT(&ctx->impl->output.levels, GRN_OBJ_VECTOR);

  ctx->impl->parallel.n_workers = grn_n_workers_default;
  ctx->impl->parallel.task_executor = nullptr;

  ctx->impl->command.flags = 0;
  if (ctx == &grn_gctx) {
    ctx->impl->command.version = GRN_COMMAND_VERSION_STABLE;
  } else {
    ctx->impl->command.version = grn_get_default_command_version();
  }
  ctx->impl->command.keep.command = NULL;
  ctx->impl->command.keep.version = ctx->impl->command.version;
  ctx->impl->command.keep.n_workers = ctx->impl->parallel.n_workers;

  if (ctx == &grn_gctx) {
    ctx->impl->match_escalation_threshold =
      GRN_DEFAULT_MATCH_ESCALATION_THRESHOLD;
  } else {
    ctx->impl->match_escalation_threshold =
      grn_get_default_match_escalation_threshold();
  }
  ctx->impl->force_match_escalation = false;

  ctx->impl->finalizer = NULL;

  ctx->impl->com = NULL;
  ctx->impl->output.buf = grn_obj_open(ctx, GRN_BULK, 0, GRN_DB_TEXT);
  ctx->impl->output.func = NULL;
  ctx->impl->output.data.ptr = NULL;
#ifdef GRN_WITH_MESSAGE_PACK
  msgpack_packer_init(&ctx->impl->output.msgpacker,
                      ctx,
                      grn_msgpack_buffer_write);
#endif
  ctx->impl->output.arrow_stream_writer = NULL;
  grn_timeval_now(ctx, &ctx->impl->tv);
#ifdef GRN_WITH_APACHE_ARROW
  ctx->impl->arrow_stream_loader = NULL;
#endif
  grn_loader_init(&ctx->impl->loader);
  ctx->impl->plugin_path = NULL;

  GRN_TEXT_INIT(&ctx->impl->query_log_buf, 0);

  ctx->impl->previous_errbuf[0] = '\0';
  ctx->impl->n_same_error_messages = 0;

  GRN_INT64_INIT(&(ctx->impl->slow_log.start_times), GRN_OBJ_VECTOR);

  grn_ctx_impl_mrb_init(ctx);
  grn_ctx_impl_lua_init(ctx);

  GRN_TEXT_INIT(&(ctx->impl->temporary_open_spaces.stack), 0);
  ctx->impl->temporary_open_spaces.current = NULL;

  ctx->impl->variables = grn_hash_create(ctx,
                                         NULL,
                                         GRN_TABLE_MAX_KEY_SIZE,
                                         sizeof(grn_ctx_local_variable),
                                         GRN_OBJ_KEY_VAR_SIZE | GRN_HASH_TINY);
  if (!ctx->impl->variables) {
    goto exit;
  }

  ctx->impl->wal.role = GRN_WAL_ROLE_NONE;

  CRITICAL_SECTION_INIT(ctx->impl->children.lock);
  /* grn_obj * isn't grn_ctx * but sizeof(grn_obj *) and
   * sizeof(grn_ctx *) is same. So we reuse GRN_PVECTOR here. */
  GRN_PTR_INIT(&(ctx->impl->children.pool), GRN_OBJ_VECTOR, GRN_ID_NIL);
  ctx->impl->parent = NULL;
  CRITICAL_SECTION_INIT(ctx->impl->temporary_objects_lock);

  ctx->impl->progress.callback = NULL;
  ctx->impl->progress.user_data = NULL;

  ctx->impl->trace_log.start_time = 0;
  GRN_UINT16_INIT(&(ctx->impl->trace_log.depths), GRN_OBJ_VECTOR);
  GRN_UINT16_INIT(&(ctx->impl->trace_log.sequence_stack), GRN_OBJ_VECTOR);
  GRN_UINT16_INIT(&(ctx->impl->trace_log.sequences), GRN_OBJ_VECTOR);
  GRN_TEXT_INIT(&(ctx->impl->trace_log.names), GRN_OBJ_VECTOR);
  GRN_TEXT_INIT(&(ctx->impl->trace_log.values), GRN_OBJ_VECTOR);
  GRN_UINT64_INIT(&(ctx->impl->trace_log.elapsed_times), GRN_OBJ_VECTOR);

exit:
  if (ctx->rc != GRN_SUCCESS) {
    if (ctx->impl->variables) {
      grn_hash_close(ctx, ctx->impl->variables);
    }
    GRN_OBJ_FIN(ctx, &(ctx->impl->temporary_open_spaces.stack));
    GRN_OBJ_FIN(ctx, &ctx->impl->query_log_buf);
    grn_obj_close(ctx, ctx->impl->output.buf);
    GRN_OBJ_FIN(ctx, &ctx->impl->output.levels);
    GRN_OBJ_FIN(ctx, &ctx->impl->output.names);
    GRN_OBJ_FIN(ctx, &ctx->impl->current_request_id);
    if (ctx->impl->stack) {
      GRN_FREE(ctx->impl->stack);
    }
    if (ctx->impl->expr_vars) {
      grn_hash_close(ctx, ctx->impl->expr_vars);
    }
    if (ctx->impl->ios) {
      grn_hash_close(ctx, ctx->impl->ios);
    }
    if (ctx->impl->columns_cache) {
      grn_hash_close(ctx, ctx->impl->columns_cache);
    }
    if (ctx->impl->temporary_options) {
      grn_options_close(ctx, ctx->impl->temporary_options);
    }
    if (ctx->impl->temporary_columns) {
      grn_pat_close(ctx, ctx->impl->temporary_columns);
    }
    if (ctx->impl->values) {
      grn_array_close(ctx, ctx->impl->values);
    }
    CRITICAL_SECTION_FIN(ctx->impl->columns_cache_lock);
    CRITICAL_SECTION_FIN(ctx->impl->lock);
    grn_io_anon_unmap(ctx, &mi, IMPL_SIZE);
    ctx->impl = NULL;
  }

  return ctx->rc;
}

void
grn_ctx_set_keep_command(grn_ctx *ctx, grn_obj *command)
{
  ctx->impl->command.keep.command = command;
  ctx->impl->command.keep.version = ctx->impl->command.version;
  ctx->impl->command.keep.n_workers = ctx->impl->parallel.n_workers;
}

grn_ctx *
grn_ctx_pull_child(grn_ctx *ctx)
{
  grn_ctx *child_ctx = NULL;
  CRITICAL_SECTION_ENTER(ctx->impl->children.lock);
  if (GRN_PTR_VECTOR_SIZE(&(ctx->impl->children.pool)) == 0) {
    child_ctx = grn_ctx_open(0);
  } else {
    grn_obj *value;
    GRN_PTR_POP(&(ctx->impl->children.pool), value);
    child_ctx = (grn_ctx *)value;
  }
  grn_ctx_use(child_ctx, grn_ctx_db(ctx));
  child_ctx->impl->parent = ctx;
  child_ctx->impl->command.version = ctx->impl->command.version;
  child_ctx->impl->match_escalation_threshold =
    ctx->impl->match_escalation_threshold;
  child_ctx->impl->force_match_escalation = ctx->impl->force_match_escalation;
  child_ctx->impl->parallel.n_workers = ctx->impl->parallel.n_workers;
  CRITICAL_SECTION_LEAVE(ctx->impl->children.lock);
  return child_ctx;
}

grn_rc
grn_ctx_release_child(grn_ctx *ctx, grn_ctx *child_ctx)
{
  if (child_ctx->impl->parent != ctx) {
    return GRN_INVALID_ARGUMENT;
  }

  CRITICAL_SECTION_ENTER(ctx->impl->children.lock);
  if (ctx->rc == GRN_SUCCESS && child_ctx->rc != GRN_SUCCESS) {
    ctx->rc = child_ctx->rc;
    ctx->errlvl = child_ctx->errlvl;
    ctx->errfile = child_ctx->errfile;
    ctx->errline = child_ctx->errline;
    ctx->errfunc = child_ctx->errfunc;
    grn_strcpy(ctx->errbuf, GRN_CTX_MSGSIZE, child_ctx->errbuf);
  }
  ERRCLR(child_ctx);
  child_ctx->impl->parent = NULL;
  grn_ctx_use(child_ctx, NULL);
  GRN_PTR_PUT(ctx, &(ctx->impl->children.pool), child_ctx);
  CRITICAL_SECTION_LEAVE(ctx->impl->children.lock);
  return ctx->rc;
}

void *
grn_ctx_expr_parser_pull(grn_ctx *ctx)
{
  void *parser = NULL;
  if (GRN_PTR_VECTOR_SIZE(&(ctx->impl->expr_parsers)) == 0) {
    parser = grn_expr_parser_open(ctx);
  } else {
    grn_obj *value;
    GRN_PTR_POP(&(ctx->impl->expr_parsers), value);
    parser = reinterpret_cast<void *>(value);
  }
  return parser;
}

grn_rc
grn_ctx_expr_parser_release(grn_ctx *ctx, void *parser)
{
  GRN_PTR_PUT(ctx, &(ctx->impl->expr_parsers), parser);
  return ctx->rc;
}

grn_rc
grn_ctx_expr_parsers_clear(grn_ctx *ctx)
{
  while (GRN_PTR_VECTOR_SIZE(&(ctx->impl->expr_parsers)) > 0) {
    grn_obj *value;
    GRN_PTR_POP(&(ctx->impl->expr_parsers), value);
    auto expr_parser = reinterpret_cast<void *>(value);
    grn_expr_parser_close(ctx, expr_parser);
  }
  return ctx->rc;
}

grn_rc
grn_ctx_set_progress_callback(grn_ctx *ctx,
                              grn_progress_callback_func func,
                              void *user_data)
{
  if (!ctx) {
    return GRN_INVALID_ARGUMENT;
  }
  if (!ctx->impl) {
    return GRN_INVALID_ARGUMENT;
  }

  ctx->impl->progress.callback = func;
  ctx->impl->progress.user_data = user_data;

  return GRN_SUCCESS;
}

grn_progress_callback_func
grn_ctx_get_progress_callback(grn_ctx *ctx)
{
  if (!ctx) {
    return NULL;
  }
  if (!ctx->impl) {
    return NULL;
  }
  return ctx->impl->progress.callback;
}

void
grn_ctx_call_progress_callback(grn_ctx *ctx, grn_progress *progress)
{
  if (!ctx) {
    return;
  }
  if (!ctx->impl) {
    return;
  }
  ctx->impl->progress.callback(ctx, progress, ctx->impl->progress.user_data);
}

static void
grn_ctx_impl_clear_n_same_error_messagges(grn_ctx *ctx)
{
  if (ctx->impl->n_same_error_messages == 0) {
    return;
  }

  GRN_LOG(ctx,
          GRN_LOG_NOTICE,
          "(%u same messages are truncated)",
          ctx->impl->n_same_error_messages);
  ctx->impl->n_same_error_messages = 0;
}

bool
grn_ctx_impl_should_log(grn_ctx *ctx)
{
  if (!ctx->impl) {
    return true;
  }

  if (strcmp(ctx->errbuf, ctx->impl->previous_errbuf) == 0) {
    ctx->impl->n_same_error_messages++;
    return false;
  }

  return true;
}

void
grn_ctx_impl_set_current_error_message(grn_ctx *ctx)
{
  if (!ctx->impl) {
    return;
  }

  grn_ctx_impl_clear_n_same_error_messagges(ctx);
  grn_strcpy(ctx->impl->previous_errbuf, GRN_CTX_MSGSIZE, ctx->errbuf);
}

void
grn_ctx_impl_columns_cache_clear(grn_ctx *ctx)
{
  CRITICAL_SECTION_ENTER(ctx->impl->columns_cache_lock);
  GRN_HASH_EACH_BEGIN(ctx, ctx->impl->columns_cache, cursor, id)
  {
    void *value;
    grn_hash_cursor_get_value(ctx, cursor, &value);
    grn_hash *columns = *((grn_hash **)value);
    grn_hash_close(ctx, columns);
    grn_hash_cursor_delete(ctx, cursor, NULL);
  }
  GRN_HASH_EACH_END(ctx, cursor);
  CRITICAL_SECTION_LEAVE(ctx->impl->columns_cache_lock);
}

void
grn_ctx_impl_columns_cache_delete(grn_ctx *ctx, grn_id table_id)
{
  CRITICAL_SECTION_ENTER(ctx->impl->columns_cache_lock);
  grn_id cache_id;
  void *value;
  cache_id = grn_hash_get(ctx,
                          ctx->impl->columns_cache,
                          &table_id,
                          sizeof(grn_id),
                          &value);
  if (cache_id != GRN_ID_NIL) {
    grn_hash *columns = *((grn_hash **)value);
    grn_hash_close(ctx, columns);
    grn_hash_delete_by_id(ctx, ctx->impl->columns_cache, cache_id, NULL);
  }
  CRITICAL_SECTION_LEAVE(ctx->impl->columns_cache_lock);
}

static grn_rc
grn_ctx_impl_fin(grn_ctx *ctx)
{
  grn_rc rc;

  grn_ctx_impl_clear_n_same_error_messagges(ctx);
  if (ctx->impl->finalizer) {
    ctx->impl->finalizer(ctx, 0, NULL, &(ctx->user_data));
  }

  ctx->impl->trace_log.start_time = 0;
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.depths));
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.sequence_stack));
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.sequences));
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.names));
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.values));
  GRN_OBJ_FIN(ctx, &(ctx->impl->trace_log.elapsed_times));

  {
    while (GRN_PTR_VECTOR_SIZE(&(ctx->impl->children.pool)) > 0) {
      grn_obj *value;
      GRN_PTR_POP(&(ctx->impl->children.pool), value);
      grn_ctx *child_ctx = (grn_ctx *)value;
      grn_ctx_close(child_ctx);
    }
    GRN_OBJ_FIN(ctx, &(ctx->impl->children.pool));
    CRITICAL_SECTION_FIN(ctx->impl->children.lock);
  }
  CRITICAL_SECTION_FIN(ctx->impl->temporary_objects_lock);

  GRN_HASH_EACH_BEGIN(ctx, ctx->impl->variables, cursor, id)
  {
    void *value;
    grn_hash_cursor_get_value(ctx, cursor, &value);
    auto variable = static_cast<grn_ctx_local_variable *>(value);
    if (variable->close_func) {
      variable->close_func(ctx, variable->data);
    }
  }
  GRN_HASH_EACH_END(ctx, cursor);
  grn_hash_close(ctx, ctx->impl->variables);
  {
    grn_obj *stack;
    grn_obj *spaces;
    size_t i, n_spaces;

    stack = &(ctx->impl->temporary_open_spaces.stack);
    spaces = (grn_obj *)GRN_BULK_HEAD(stack);
    n_spaces = GRN_BULK_VSIZE(stack) / sizeof(grn_obj);
    for (i = 0; i < n_spaces; i++) {
      grn_obj *space = spaces + (n_spaces - i - 1);
      GRN_OBJ_FIN(ctx, space);
    }
    GRN_OBJ_FIN(ctx, stack);
  }
  grn_ctx_impl_lua_fin(ctx);
  grn_ctx_impl_mrb_fin(ctx);
  GRN_OBJ_FIN(ctx, &(ctx->impl->slow_log.start_times));
  grn_ctx_loader_clear(ctx);
  grn_ctx_expr_parsers_clear(ctx);
  GRN_OBJ_FIN(ctx, &(ctx->impl->expr_parsers));
  GRN_OBJ_FIN(ctx, &ctx->impl->current_request_id);
  if (ctx->impl->values) {
#ifndef GRN_WITH_MEMORY_DEBUG
    grn_db_obj *o;
    GRN_ARRAY_EACH(ctx, ctx->impl->values, 0, 0, id, &o, {
      grn_obj_close(ctx, *((grn_obj **)o));
    });
#endif
    grn_array_close(ctx, ctx->impl->values);
  }
  if (ctx->impl->temporary_columns) {
#ifndef GRN_WITH_MEMORY_DEBUG
    grn_obj *value;
    GRN_PAT_EACH(ctx, ctx->impl->temporary_columns, id, NULL, NULL, &value, {
      grn_obj_close(ctx, *((grn_obj **)value));
    });
#endif
    grn_pat_close(ctx, ctx->impl->temporary_columns);
  }
  if (ctx->impl->temporary_options) {
    grn_options_close(ctx, ctx->impl->temporary_options);
  }
  if (ctx->impl->columns_cache) {
    grn_ctx_impl_columns_cache_clear(ctx);
    grn_hash_close(ctx, ctx->impl->columns_cache);
  }
  if (ctx->impl->ios) {
    grn_hash_close(ctx, ctx->impl->ios);
  }
  if (ctx->impl->com) {
    if (ctx->stat != GRN_CTX_QUIT) {
      int flags;
      char *str;
      unsigned int str_len;
      grn_ctx_send(ctx, "quit", 4, GRN_CTX_HEAD);
      grn_ctx_recv(ctx, &str, &str_len, &flags);
    }
    grn_ctx_send(ctx, "ACK", 3, GRN_CTX_HEAD);
    rc = grn_com_close(ctx, ctx->impl->com);
  }
  GRN_OBJ_FIN(ctx, &ctx->impl->query_log_buf);
  if (ctx->impl->parallel.task_executor) {
    delete static_cast<grn::TaskExecutor *>(ctx->impl->parallel.task_executor);
  }
  if (ctx->impl->output.arrow_stream_writer) {
    grn_arrow_stream_writer_close(ctx, ctx->impl->output.arrow_stream_writer);
  }
  GRN_OBJ_FIN(ctx, &ctx->impl->output.names);
  GRN_OBJ_FIN(ctx, &ctx->impl->output.levels);
  rc = grn_obj_close(ctx, ctx->impl->output.buf);
  if (ctx->impl->expr_vars) {
    grn_hash **vp;
    grn_obj *value;
    GRN_HASH_EACH(ctx, ctx->impl->expr_vars, eid, NULL, NULL, &vp, {
      if (*vp) {
        GRN_HASH_EACH(ctx, *vp, id, NULL, NULL, &value, {
          GRN_OBJ_FIN(ctx, value);
        });
      }
      grn_hash_close(ctx, *vp);
    });
    grn_hash_close(ctx, ctx->impl->expr_vars);
  }
  if (ctx->impl->stack) {
    GRN_FREE(ctx->impl->stack);
  }
  if (ctx->impl->db && ctx->flags & GRN_CTX_PER_DB) {
    grn_obj *db = ctx->impl->db;
    ctx->impl->db = NULL;
    grn_obj_close(ctx, db);
  }
  grn_alloc_fin_ctx_impl(ctx);
  grn_alloc_info_dump(ctx);
  grn_alloc_info_free(ctx);
  CRITICAL_SECTION_FIN(ctx->impl->columns_cache_lock);
  CRITICAL_SECTION_FIN(ctx->impl->lock);
  {
    grn_io_mapinfo mi;
    mi.map = (void *)ctx->impl;
    grn_io_anon_unmap(ctx, &mi, IMPL_SIZE);
  }

  return rc;
}

static grn_rc
grn_ctx_init_internal(grn_ctx *ctx, int flags)
{
  if (!ctx) {
    return GRN_INVALID_ARGUMENT;
  }
  // if (ctx->stat != GRN_CTX_FIN) { return GRN_INVALID_ARGUMENT; }
  ctx->rc = GRN_SUCCESS;
  ERRCLR(ctx);
  ctx->flags = flags;
  if (grn_ctx_per_db) {
    ctx->flags |= GRN_CTX_PER_DB;
  }
  ctx->stat = GRN_CTX_INITED;
  ctx->encoding = grn_gctx.encoding;
  ctx->seqno = 0;
  ctx->seqno2 = 0;
  ctx->subno = 0;
  ctx->impl = NULL;
  ctx->user_data.ptr = NULL;
  CRITICAL_SECTION_ENTER(grn_glock);
  ctx->next = grn_gctx.next;
  ctx->prev = &grn_gctx;
  grn_gctx.next->prev = ctx;
  grn_gctx.next = ctx;
  CRITICAL_SECTION_LEAVE(grn_glock);
  ctx->errline = 0;
  ctx->errfile = "";
  ctx->errfunc = "";
  ctx->trace[0] = NULL;
  ctx->errbuf[0] = '\0';
  return GRN_SUCCESS;
}

grn_rc
grn_ctx_init(grn_ctx *ctx, int flags)
{
  grn_rc rc;

  rc = grn_ctx_init_internal(ctx, flags);
  if (rc == GRN_SUCCESS) {
    grn_ctx_impl_init(ctx);
    rc = ctx->rc;
    if (rc != GRN_SUCCESS) {
      grn_ctx_fin(ctx);
      if (flags & GRN_CTX_ALLOCATED) {
        CRITICAL_SECTION_ENTER(grn_glock);
        ctx->next->prev = ctx->prev;
        ctx->prev->next = ctx->next;
        CRITICAL_SECTION_LEAVE(grn_glock);
      }
    }
  }

  return rc;
}

grn_ctx *
grn_ctx_open(int flags)
{
  grn_ctx *ctx = GRN_GMALLOCN(grn_ctx, 1);
  if (ctx) {
    grn_ctx_init(ctx, flags | GRN_CTX_ALLOCATED);
    if (ERRP(ctx, GRN_ERROR)) {
      GRN_GFREE(ctx);
      ctx = NULL;
    }
  }
  return ctx;
}

grn_rc
grn_ctx_fin(grn_ctx *ctx)
{
  grn_rc rc = GRN_SUCCESS;
  if (!ctx) {
    return GRN_INVALID_ARGUMENT;
  }
  if (ctx->stat == GRN_CTX_FIN) {
    return GRN_INVALID_ARGUMENT;
  }
  if (!(ctx->flags & GRN_CTX_ALLOCATED)) {
    CRITICAL_SECTION_ENTER(grn_glock);
    ctx->next->prev = ctx->prev;
    ctx->prev->next = ctx->next;
    CRITICAL_SECTION_LEAVE(grn_glock);
  }
  if (ctx->impl) {
    rc = grn_ctx_impl_fin(ctx);
    ctx->impl = NULL;
  }
  ctx->stat = GRN_CTX_FIN;
  return rc;
}

grn_rc
grn_ctx_set_finalizer(grn_ctx *ctx, grn_proc_func *finalizer)
{
  if (!ctx) {
    return GRN_INVALID_ARGUMENT;
  }
  if (!ctx->impl) {
    if (ERRP(ctx, GRN_ERROR)) {
      return ctx->rc;
    }
  }
  ctx->impl->finalizer = finalizer;
  return GRN_SUCCESS;
}

grn_timeval grn_starttime;

static void
check_overcommit_memory(grn_ctx *ctx)
{
  FILE *file;
  int value;
  file = grn_fopen("/proc/sys/vm/overcommit_memory", "r");
  if (!file) {
    return;
  }
  value = fgetc(file);
  if (value != '1') {
    GRN_LOG(ctx,
            GRN_LOG_NOTICE,
            "vm.overcommit_memory kernel parameter should be 1: <%c>: "
            "See INFO level log to resolve this",
            value);
    GRN_LOG(ctx,
            GRN_LOG_INFO,
            "Some processings with vm.overcommit_memory != 1 "
            "may break DB under low memory condition.");
    GRN_LOG(ctx, GRN_LOG_INFO, "To set vm.overcommit_memory to 1");
    GRN_LOG(ctx,
            GRN_LOG_INFO,
            "add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and "
            "restart your system or");
    GRN_LOG(ctx,
            GRN_LOG_INFO,
            "run 'sudo /sbin/sysctl vm.overcommit_memory=1' command.");
  }
  fclose(file);
}

grn_rc
grn_init(void)
{
  grn_rc rc;
  grn_ctx *ctx = &grn_gctx;
#ifdef WIN32
  grn_windows_init();
#endif
  grn_slow_log_init();
  grn_init_from_env();
  grn_init_external_libraries();
  grn_alloc_info_init();
  grn_logger_init();
  grn_query_logger_init();
  CRITICAL_SECTION_INIT(grn_glock);
  grn_gtick = 0;
  ctx->next = ctx;
  ctx->prev = ctx;
  rc = grn_ctx_init_internal(ctx, 0);
  if (rc) {
    goto fail_ctx_init_internal;
  }
  ctx->encoding = grn_encoding_parse(GRN_DEFAULT_ENCODING);
  rc = grn_timeval_now(ctx, &grn_starttime);
  if (rc) {
    goto fail_start_time;
  }
#ifdef WIN32
  {
    SYSTEM_INFO si;
    GetSystemInfo(&si);
    grn_pagesize = si.dwAllocationGranularity;
  }
#else  /* WIN32 */
  if ((grn_pagesize = (int)sysconf(_SC_PAGESIZE)) == -1) {
    SERR("_SC_PAGESIZE");
    rc = ctx->rc;
    goto fail_page_size;
  }
#endif /* WIN32 */
  if (grn_pagesize & (grn_pagesize - 1)) {
    GRN_LOG(ctx, GRN_LOG_CRIT, "pagesize=%x", grn_pagesize);
  }
  // expand_stack();
  if ((rc = grn_com_init())) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "grn_com_init failed (%d)", rc);
    goto fail_com;
  }
  if ((rc = grn_ctx_impl_init(ctx))) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "grn_ctx_impl_init failed (%d)", rc);
    goto fail_ctx_impl;
  }
  if ((rc = grn_plugins_init())) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "grn_plugins_init failed (%d)", rc);
    goto fail_plugins;
  }
  if ((rc = grn_normalizer_init())) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "grn_normalizer_init failed (%d)", rc);
    goto fail_normalizer;
  }
  if ((rc = grn_tokenizers_init())) {
    GRN_LOG(ctx, GRN_LOG_ALERT, "grn_tokenizers_init failed (%d)", rc);
    goto fail_tokenizer;
  }
  grn_cache_init();
  if (!grn_request_canceler_init()) {
    rc = ctx->rc;
    GRN_LOG(ctx,
            GRN_LOG_ALERT,
            "failed to initialize request canceler (%d)",
            rc);
    goto fail_request_canceler;
  }
  if (!grn_request_timer_init()) {
    rc = ctx->rc;
    GRN_LOG(ctx, GRN_LOG_ALERT, "failed to initialize request timer (%d)", rc);
    goto fail_request_timer;
  }
  GRN_LOG(ctx, GRN_LOG_NOTICE, "grn_init: <%s>", grn_get_version());
  check_overcommit_memory(ctx);
  return rc;

fail_request_timer:
  grn_request_canceler_fin();
fail_request_canceler:
  grn_cache_fin();
fail_tokenizer:
  grn_normalizer_fin();
fail_normalizer:
  grn_plugins_fin();
fail_plugins:
  grn_ctx_fin(ctx);
fail_ctx_impl:
  grn_com_fin();
fail_com:
#ifndef WIN32
fail_page_size:
#endif /* WIN32 */
fail_start_time:
fail_ctx_init_internal:
  GRN_LOG(ctx, GRN_LOG_NOTICE, "grn_init: <%s>: failed", grn_get_version());
  grn_query_logger_fin(ctx);
  grn_logger_fin(ctx);
  CRITICAL_SECTION_FIN(grn_glock);
  grn_alloc_info_fin();
  grn_fin_external_libraries();
#ifdef WIN32
  grn_windows_fin();
#endif
  return rc;
}

grn_encoding
grn_get_default_encoding(void)
{
  return grn_gctx.encoding;
}

grn_rc
grn_set_default_encoding(grn_encoding encoding)
{
  switch (encoding) {
  case GRN_ENC_DEFAULT:
    grn_gctx.encoding = grn_encoding_parse(GRN_DEFAULT_ENCODING);
    return GRN_SUCCESS;
  case GRN_ENC_NONE:
  case GRN_ENC_EUC_JP:
  case GRN_ENC_UTF8:
  case GRN_ENC_SJIS:
  case GRN_ENC_LATIN1:
  case GRN_ENC_KOI8R:
    grn_gctx.encoding = encoding;
    return GRN_SUCCESS;
  default:
    return GRN_INVALID_ARGUMENT;
  }
}

grn_command_version
grn_get_default_command_version(void)
{
  return grn_ctx_get_command_version(&grn_gctx);
}

grn_rc
grn_set_default_command_version(grn_command_version version)
{
  return grn_ctx_set_command_version(&grn_gctx, version);
}

int64_t
grn_get_default_match_escalation_threshold(void)
{
  return grn_ctx_get_match_escalation_threshold(&grn_gctx);
}

grn_rc
grn_set_default_match_escalation_threshold(int64_t threshold)
{
  return grn_ctx_set_match_escalation_threshold(&grn_gctx, threshold);
}

int32_t
grn_get_default_n_workers(void)
{
  return grn_n_workers_default;
}

grn_rc
grn_set_default_n_workers(int32_t n_workers)
{
  grn_n_workers_default = n_workers;
  return GRN_SUCCESS;
}

int
grn_get_lock_timeout(void)
{
  return grn_lock_timeout;
}

grn_rc
grn_set_lock_timeout(int timeout)
{
  grn_lock_timeout = timeout;
  return GRN_SUCCESS;
}

grn_rc
grn_fin(void)
{
  grn_ctx *ctx, *ctx_;
  if (grn_gctx.stat == GRN_CTX_FIN) {
    return GRN_INVALID_ARGUMENT;
  }
  /* Clear ctx pool to avoid double free. */
  for (ctx = grn_gctx.next; ctx != &grn_gctx; ctx = ctx->next) {
    if (!ctx->impl) {
      continue;
    }
    if (GRN_PTR_VECTOR_SIZE(&(ctx->impl->children.pool)) == 0) {
      continue;
    }
    GRN_BULK_REWIND(&(ctx->impl->children.pool));
  }
  for (ctx = grn_gctx.next; ctx != &grn_gctx; ctx = ctx_) {
    ctx_ = ctx->next;
    if (ctx->stat != GRN_CTX_FIN) {
      grn_ctx_fin(ctx);
    }
    if (ctx->flags & GRN_CTX_ALLOCATED) {
      ctx->next->prev = ctx->prev;
      ctx->prev->next = ctx->next;
      GRN_GFREE(ctx);
    }
  }
  grn_query_logger_fin(ctx);
  grn_request_timer_fin();
  grn_request_canceler_fin();
  grn_cache_fin();
  grn_tokenizers_fin();
  grn_normalizer_fin();
  grn_plugins_fin();
  grn_ctx_fin(ctx);
  grn_com_fin();
  GRN_LOG(ctx, GRN_LOG_NOTICE, "grn_fin (%u)", grn_alloc_count());
  grn_logger_fin(ctx);
  CRITICAL_SECTION_FIN(grn_glock);
  grn_alloc_info_fin();
  grn_fin_external_libraries();
#ifdef WIN32
  grn_windows_fin();
#endif
  return GRN_SUCCESS;
}

grn_rc
grn_ctx_connect(grn_ctx *ctx, const char *host, int port, int flags)
{
  GRN_API_ENTER;
  if (!ctx->impl) {
    goto exit;
  }
  {
    grn_com *com = grn_com_copen(ctx, NULL, host, port);
    if (com) {
      ctx->impl->com = com;
    }
  }
exit:
  GRN_API_RETURN(ctx->rc);
}

grn_rc
grn_ctx_close(grn_ctx *ctx)
{
  grn_rc rc = grn_ctx_fin(ctx);
  CRITICAL_SECTION_ENTER(grn_glock);
  ctx->next->prev = ctx->prev;
  ctx->prev->next = ctx->next;
  CRITICAL_SECTION_LEAVE(grn_glock);
  GRN_GFREE(ctx);
  return rc;
}

grn_command_version
grn_ctx_get_command_version(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->command.version;
  } else {
    return GRN_COMMAND_VERSION_STABLE;
  }
}

grn_rc
grn_ctx_set_command_version(grn_ctx *ctx, grn_command_version version)
{
  switch (version) {
  case GRN_COMMAND_VERSION_DEFAULT:
    ctx->impl->command.version = GRN_COMMAND_VERSION_STABLE;
    return GRN_SUCCESS;
  default:
    if (GRN_COMMAND_VERSION_MIN <= version &&
        version <= GRN_COMMAND_VERSION_MAX) {
      ctx->impl->command.version = version;
      return GRN_SUCCESS;
    } else {
      return GRN_UNSUPPORTED_COMMAND_VERSION;
    }
  }
}

grn_content_type
grn_ctx_get_output_type(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->output.type;
  } else {
    return GRN_CONTENT_NONE;
  }
}

grn_rc
grn_ctx_set_output_type(grn_ctx *ctx, grn_content_type type)
{
  grn_rc rc = GRN_SUCCESS;

  if (ctx->impl) {
    ctx->impl->output.type = type;
    switch (ctx->impl->output.type) {
    case GRN_CONTENT_NONE:
      ctx->impl->output.mime_type = "application/octet-stream";
      break;
    case GRN_CONTENT_TSV:
      ctx->impl->output.mime_type = "text/tab-separated-values";
      break;
    case GRN_CONTENT_JSON:
      ctx->impl->output.mime_type = "application/json";
      break;
    case GRN_CONTENT_XML:
      ctx->impl->output.mime_type = "text/xml";
      break;
    case GRN_CONTENT_MSGPACK:
      ctx->impl->output.mime_type = "application/x-msgpack";
      break;
    case GRN_CONTENT_GROONGA_COMMAND_LIST:
      ctx->impl->output.mime_type = "text/x-groonga-command-list";
      break;
    case GRN_CONTENT_APACHE_ARROW:
      ctx->impl->output.mime_type = "application/x-apache-arrow-streaming";
      break;
    }
  } else {
    rc = GRN_INVALID_ARGUMENT;
  }

  return rc;
}

const char *
grn_ctx_get_mime_type(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->output.mime_type;
  } else {
    return NULL;
  }
}

int64_t
grn_ctx_get_match_escalation_threshold(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->match_escalation_threshold;
  } else {
    return GRN_DEFAULT_MATCH_ESCALATION_THRESHOLD;
  }
}

grn_rc
grn_ctx_set_match_escalation_threshold(grn_ctx *ctx, int64_t threshold)
{
  ctx->impl->match_escalation_threshold = threshold;
  return GRN_SUCCESS;
}

bool
grn_ctx_get_force_match_escalation(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->force_match_escalation;
  } else {
    return false;
  }
}

grn_rc
grn_ctx_set_force_match_escalation(grn_ctx *ctx, bool force)
{
  if (ctx->impl) {
    ctx->impl->force_match_escalation = force;
    return GRN_SUCCESS;
  } else {
    return GRN_INVALID_ARGUMENT;
  }
}

int32_t
grn_ctx_get_n_workers(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->parallel.n_workers;
  } else {
    return grn_n_workers_default;
  }
}

grn_rc
grn_ctx_set_n_workers(grn_ctx *ctx, int32_t n_workers)
{
  if (ctx->impl) {
    ctx->impl->parallel.n_workers = n_workers;
    return GRN_SUCCESS;
  } else {
    return GRN_INVALID_ARGUMENT;
  }
}

grn_wal_role
grn_ctx_get_wal_role(grn_ctx *ctx)
{
  if (ctx->impl) {
    return ctx->impl->wal.role;
  } else {
    return GRN_WAL_ROLE_NONE;
  }
}

grn_rc
grn_ctx_set_wal_role(grn_ctx *ctx, grn_wal_role role)
{
  if (ctx->impl) {
    ctx->impl->wal.role = role;
    return GRN_SUCCESS;
  } else {
    return GRN_INVALID_ARGUMENT;
  }
}

grn_rc
grn_ctx_set_variable(grn_ctx *ctx,
                     const char *name,
                     int name_size,
                     void *data,
                     grn_close_func close_func)
{
  GRN_API_ENTER;
  if (!ctx->impl) {
    ERR(GRN_INVALID_ARGUMENT, "[ctx][variable][set] not initialized ctx");
    goto exit;
  }
  if (name_size < 0) {
    name_size = (int)strlen(name);
  }
  if (data) {
    void *value;
    int added;
    grn_id id = grn_hash_add(ctx,
                             ctx->impl->variables,
                             name,
                             (unsigned int)name_size,
                             &value,
                             &added);
    if (id == GRN_ID_NIL) {
      grn_rc rc = ctx->rc;
      if (rc == GRN_SUCCESS) {
        rc = GRN_UNKNOWN_ERROR;
      }
      ERR(rc, "[ctx][variable][set] failed to add variable");
      goto exit;
    }
    auto variable = static_cast<grn_ctx_local_variable *>(value);
    if (!added) {
      if (variable->close_func) {
        variable->close_func(ctx, variable->data);
      }
    }
    variable->data = data;
    variable->close_func = close_func;
  } else {
    void *value;
    grn_id id = grn_hash_get(ctx,
                             ctx->impl->variables,
                             name,
                             (unsigned int)name_size,
                             &value);
    if (id != GRN_ID_NIL) {
      auto variable = static_cast<grn_ctx_local_variable *>(value);
      if (variable->close_func) {
        variable->close_func(ctx, variable->data);
      }
      grn_hash_delete_by_id(ctx, ctx->impl->variables, id, NULL);
    }
  }
exit:
  GRN_API_RETURN(ctx->rc);
}

void *
grn_ctx_get_variable(grn_ctx *ctx, const char *name, int name_size)
{
  void *data = NULL;
  GRN_API_ENTER;
  if (!ctx->impl) {
    ERR(GRN_INVALID_ARGUMENT, "[ctx][variable][get] not initialized ctx");
    goto exit;
  }
  if (name_size < 0) {
    name_size = (int)strlen(name);
  }
  {
    void *value;
    grn_id id = grn_hash_get(ctx,
                             ctx->impl->variables,
                             name,
                             (unsigned int)name_size,
                             &value);
    if (id == GRN_ID_NIL) {
      goto exit;
    }
    {
      auto variable = static_cast<grn_ctx_local_variable *>(value);
      data = variable->data;
    }
  }
exit:
  GRN_API_RETURN(data);
}

grn_rc
grn_unset_variable(const char *name, int name_size)
{
  grn_rc rc = GRN_SUCCESS;
  grn_ctx *ctx = grn_gctx.next;
  while (ctx != &grn_gctx) {
    if (ctx->impl) {
      rc = grn_ctx_set_variable(ctx, name, name_size, NULL, NULL);
      if (rc != GRN_SUCCESS) {
        break;
      }
    }
    CRITICAL_SECTION_ENTER(grn_glock);
    ctx = ctx->next;
    CRITICAL_SECTION_LEAVE(grn_glock);
  }
  return rc;
}

grn_content_type
grn_get_ctype(grn_obj *var)
{
  return grn_content_type_parse(NULL, var, GRN_CONTENT_JSON);
}

grn_content_type
grn_content_type_parse(grn_ctx *ctx,
                       grn_obj *var,
                       grn_content_type default_value)
{
  grn_content_type ct = default_value;
  if (var->header.domain == GRN_DB_INT32) {
    ct = static_cast<grn_content_type>(GRN_INT32_VALUE(var));
  } else {
    grn_raw_string input;
    GRN_RAW_STRING_SET(input, var);
    if (GRN_RAW_STRING_EQUAL_CSTRING_CI(input, "tsv")) {
      ct = GRN_CONTENT_TSV;
    } else if (GRN_RAW_STRING_EQUAL_CSTRING_CI(input, "json")) {
      ct = GRN_CONTENT_JSON;
    } else if (GRN_RAW_STRING_EQUAL_CSTRING_CI(input, "xml")) {
      ct = GRN_CONTENT_XML;
    } else if (GRN_RAW_STRING_EQUAL_CSTRING_CI(input, "apache-arrow")) {
      ct = GRN_CONTENT_APACHE_ARROW;
    }
  }
  return ct;
}

static void
get_content_mime_type(grn_ctx *ctx, const char *p, const char *pe)
{
  ctx->impl->output.type = GRN_CONTENT_NONE;
  ctx->impl->output.mime_type = "application/octet-stream";

  grn_raw_string type;
  type.value = p;
  type.length = (size_t)(pe - p);

  if (type.length < 2) {
    return;
  }

  switch (type.value[0]) {
  case 'c':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "css")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "text/css";
    }
    break;
  case 'g':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "gif")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "image/gif";
    }
    break;
  case 'h':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "html")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "text/html";
    }
    break;
  case 'j':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "js")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "text/javascript";
    } else if (GRN_RAW_STRING_EQUAL_CSTRING(type, "json")) {
      ctx->impl->output.type = GRN_CONTENT_JSON;
      ctx->impl->output.mime_type = "application/json";
    } else if (GRN_RAW_STRING_EQUAL_CSTRING(type, "jpg") ||
               GRN_RAW_STRING_EQUAL_CSTRING(type, "jpeg")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "image/jpeg";
    }
    break;
#ifdef GRN_WITH_MESSAGE_PACK
  case 'm':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "msgpack")) {
      ctx->impl->output.type = GRN_CONTENT_MSGPACK;
      ctx->impl->output.mime_type = "application/x-msgpack";
    }
    break;
#endif
#ifdef GRN_WITH_APACHE_ARROW
  case 'a':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "arrows")) {
      ctx->impl->output.type = GRN_CONTENT_APACHE_ARROW;
      ctx->impl->output.mime_type = "application/vnd.apache.arrow.stream";
    } else if (GRN_RAW_STRING_EQUAL_CSTRING(type, "arrow") ||
               GRN_RAW_STRING_EQUAL_CSTRING(type, "apache-arrow")) {
      ctx->impl->output.type = GRN_CONTENT_APACHE_ARROW;
      ctx->impl->output.mime_type = "application/x-apache-arrow-streaming";
    }
    break;
#endif
  case 'p':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "png")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "image/png";
    }
    break;
  case 't':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "txt")) {
      ctx->impl->output.type = GRN_CONTENT_NONE;
      ctx->impl->output.mime_type = "text/plain";
    } else if (GRN_RAW_STRING_EQUAL_CSTRING(type, "tsv")) {
      ctx->impl->output.type = GRN_CONTENT_TSV;
      ctx->impl->output.mime_type = "text/tab-separated-values";
    }
    break;
  case 'x':
    if (GRN_RAW_STRING_EQUAL_CSTRING(type, "xml")) {
      ctx->impl->output.type = GRN_CONTENT_XML;
      ctx->impl->output.mime_type = "text/xml";
    }
    break;
  }
}

static void
grn_str_get_mime_type(grn_ctx *ctx,
                      const char *p,
                      const char *pe,
                      const char **key_end,
                      const char **filename_end)
{
  const char *pd = NULL;
  for (; p < pe && *p != '?' && *p != '#'; p++) {
    if (*p == '.') {
      pd = p;
    }
  }
  *filename_end = p;
  if (pd && pd < p) {
    get_content_mime_type(ctx, pd + 1, p);
    *key_end = pd;
  } else {
    *key_end = pe;
  }
}

static void
get_command_version(grn_ctx *ctx, const char *p, const char *pe)
{
  grn_command_version version;
  const char *rest;

  version = static_cast<grn_command_version>(grn_atoui(p, pe, &rest));
  if (pe == rest) {
    grn_rc rc;
    rc = grn_ctx_set_command_version(ctx, version);
    if (rc == GRN_UNSUPPORTED_COMMAND_VERSION) {
      ERR(rc,
          "unsupported command version is specified: %d: "
          "stable command version: %d: "
          "available command versions: %d-%d",
          version,
          GRN_COMMAND_VERSION_STABLE,
          GRN_COMMAND_VERSION_MIN,
          GRN_COMMAND_VERSION_MAX);
    }
  }
}

static void
get_n_workers(grn_ctx *ctx, const char *p, const char *pe)
{
  const char *rest;
  int32_t n_workers = grn_atoi(p, pe, &rest);
  if (pe == rest) {
    grn_ctx_set_n_workers(ctx, n_workers);
  }
}

#define INDEX_HTML                  "index.html"
#define OUTPUT_TYPE                 "output_type"
#define COMMAND_VERSION             "command_version"
#define REQUEST_ID                  "request_id"
#define REQUEST_TIMEOUT             "request_timeout"
#define OUTPUT_PRETTY               "output_pretty"
#define OUTPUT_TRACE_LOG            "output_trace_log"
#define N_WORKERS                   "n_workers"
#define EXPR_MISSING                "expr_missing"
#define OUTPUT_TYPE_LEN             (sizeof(OUTPUT_TYPE) - 1)
#define COMMAND_VERSION_LEN         (sizeof(COMMAND_VERSION) - 1)
#define REQUEST_ID_LEN              (sizeof(REQUEST_ID) - 1)
#define REQUEST_TIMEOUT_LEN         (sizeof(REQUEST_TIMEOUT) - 1)
#define OUTPUT_PRETTY_LEN           (sizeof(OUTPUT_PRETTY) - 1)
#define OUTPUT_TRACE_LOG_LEN        (sizeof(OUTPUT_TRACE_LOG) - 1)
#define N_WORKERS_LEN               (sizeof(N_WORKERS) - 1)

#define HTTP_QUERY_PAIR_DELIMITER   "="
#define HTTP_QUERY_PAIRS_DELIMITERS "&;"

static inline int
command_proc_p(grn_obj *expr)
{
  return (expr->header.type == GRN_PROC &&
          ((grn_proc *)expr)->type == GRN_PROC_COMMAND);
}

grn_obj *
grn_ctx_qe_exec_uri(grn_ctx *ctx, const char *path, uint32_t path_len)
{
  grn_obj buf, *expr, *val;
  grn_obj request_id;
  double request_timeout;
  const char *p = path, *e = path + path_len, *v, *key_end, *filename_end;

  request_timeout = grn_get_default_request_timeout();

  GRN_TEXT_INIT(&buf, 0);
  GRN_TEXT_INIT(&request_id, 0);
  p = grn_text_urldec(ctx, &buf, p, e, '?');
  if (!GRN_TEXT_LEN(&buf)) {
    GRN_TEXT_SETS(ctx, &buf, INDEX_HTML);
  }
  v = GRN_TEXT_VALUE(&buf);
  grn_str_get_mime_type(ctx, v, GRN_BULK_CURR(&buf), &key_end, &filename_end);
  if ((GRN_TEXT_LEN(&buf) >= 2 && v[0] == 'd' && v[1] == '/')) {
    const char *command_name = v + 2;
    ptrdiff_t command_name_size = key_end - command_name;
    expr = grn_ctx_get(ctx, command_name, (int)command_name_size);
    if (expr && command_proc_p(expr)) {
      while (p < e) {
        size_t l;
        GRN_BULK_REWIND(&buf);
        p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIR_DELIMITER);
        v = GRN_TEXT_VALUE(&buf);
        l = GRN_TEXT_LEN(&buf);
        if (l == OUTPUT_TYPE_LEN && !memcmp(v, OUTPUT_TYPE, OUTPUT_TYPE_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          v = GRN_TEXT_VALUE(&buf);
          get_content_mime_type(ctx, v, GRN_BULK_CURR(&buf));
        } else if (l == COMMAND_VERSION_LEN &&
                   !memcmp(v, COMMAND_VERSION, COMMAND_VERSION_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          get_command_version(ctx, GRN_TEXT_VALUE(&buf), GRN_BULK_CURR(&buf));
          if (ctx->rc) {
            goto exit;
          }
        } else if (l == REQUEST_ID_LEN &&
                   !memcmp(v, REQUEST_ID, REQUEST_ID_LEN)) {
          GRN_BULK_REWIND(&request_id);
          p = grn_text_cgidec(ctx,
                              &request_id,
                              p,
                              e,
                              HTTP_QUERY_PAIRS_DELIMITERS);
        } else if (l == REQUEST_TIMEOUT_LEN &&
                   !memcmp(v, REQUEST_TIMEOUT, REQUEST_TIMEOUT_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          GRN_TEXT_PUTC(ctx, &buf, '\0');
          request_timeout = strtod(GRN_TEXT_VALUE(&buf), NULL);
        } else if (l == OUTPUT_PRETTY_LEN &&
                   !memcmp(v, OUTPUT_PRETTY, OUTPUT_PRETTY_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          if (GRN_TEXT_LEN(&buf) == strlen("yes") &&
              !memcmp(GRN_TEXT_VALUE(&buf), "yes", GRN_TEXT_LEN(&buf))) {
            ctx->impl->output.is_pretty = true;
          } else {
            ctx->impl->output.is_pretty = false;
          }
        } else if (l == OUTPUT_TRACE_LOG_LEN &&
                   !memcmp(v, OUTPUT_TRACE_LOG, OUTPUT_TRACE_LOG_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          if (GRN_TEXT_LEN(&buf) == strlen("yes") &&
              !memcmp(GRN_TEXT_VALUE(&buf), "yes", GRN_TEXT_LEN(&buf))) {
            grn_ctx_trace_log_enable(ctx);
          } else {
            grn_ctx_trace_log_disable(ctx);
          }
        } else if (l == N_WORKERS_LEN && !memcmp(v, N_WORKERS, N_WORKERS_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_cgidec(ctx, &buf, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
          get_n_workers(ctx, GRN_TEXT_VALUE(&buf), GRN_BULK_CURR(&buf));
        } else {
          if (!(val = grn_expr_get_or_add_var(ctx, expr, v, (unsigned int)l))) {
            val = &buf;
          }
          grn_obj_reinit(ctx, val, GRN_DB_TEXT, 0);
          p = grn_text_cgidec(ctx, val, p, e, HTTP_QUERY_PAIRS_DELIMITERS);
        }
      }
      if (request_timeout > 0 && GRN_TEXT_LEN(&request_id) == 0) {
        grn_text_printf(ctx, &request_id, "%p", ctx);
      }
      if (GRN_TEXT_LEN(&request_id) > 0) {
        GRN_TEXT_SET(ctx,
                     &ctx->impl->current_request_id,
                     GRN_TEXT_VALUE(&request_id),
                     GRN_TEXT_LEN(&request_id));
        grn_request_canceler_register(ctx,
                                      GRN_TEXT_VALUE(&request_id),
                                      (unsigned int)GRN_TEXT_LEN(&request_id));
        if (request_timeout > 0.0) {
          ctx->impl->current_request_timer_id =
            grn_request_timer_register(GRN_TEXT_VALUE(&request_id),
                                       (unsigned int)GRN_TEXT_LEN(&request_id),
                                       request_timeout);
        }
      }
      ctx->impl->curr_expr = expr;
      grn_expr_exec(ctx, expr, 0);
    } else {
      ERR(GRN_INVALID_ARGUMENT,
          "invalid command name: %.*s",
          (int)command_name_size,
          command_name);
    }
  } else if ((expr = grn_ctx_get(ctx,
                                 GRN_EXPR_MISSING_NAME,
                                 strlen(GRN_EXPR_MISSING_NAME)))) {
    if ((val = grn_expr_get_var_by_offset(ctx, expr, 0))) {
      grn_obj_reinit(ctx, val, GRN_DB_TEXT, 0);
      GRN_TEXT_SET(ctx, val, v, filename_end - v);
    }
    ctx->impl->curr_expr = expr;
    grn_expr_exec(ctx, expr, 0);
  }
exit:
  GRN_OBJ_FIN(ctx, &request_id);
  GRN_OBJ_FIN(ctx, &buf);

  return expr;
}

grn_obj *
grn_ctx_qe_exec(grn_ctx *ctx, const char *str, uint32_t str_len)
{
  uint8_t tok_type;
  unsigned int offset = 0;
  grn_obj buf, *expr = NULL, *val = NULL;
  grn_obj request_id;
  double request_timeout;
  const char *p = str, *e = str + str_len, *v;

  request_timeout = grn_get_default_request_timeout();

  GRN_TEXT_INIT(&buf, 0);
  GRN_TEXT_INIT(&request_id, 0);
  p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
  expr = grn_ctx_get(ctx, GRN_TEXT_VALUE(&buf), (int)GRN_TEXT_LEN(&buf));
  while (p < e) {
    GRN_BULK_REWIND(&buf);
    p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
    v = GRN_TEXT_VALUE(&buf);
    switch (tok_type) {
    case GRN_TOK_VOID:
      p = e;
      break;
    case GRN_TOK_SYMBOL:
      if (GRN_TEXT_LEN(&buf) > 2 && v[0] == '-' && v[1] == '-') {
        size_t l = GRN_TEXT_LEN(&buf) - 2;
        v += 2;
        if (l == OUTPUT_TYPE_LEN && !memcmp(v, OUTPUT_TYPE, OUTPUT_TYPE_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          v = GRN_TEXT_VALUE(&buf);
          get_content_mime_type(ctx, v, GRN_BULK_CURR(&buf));
        } else if (l == COMMAND_VERSION_LEN &&
                   !memcmp(v, COMMAND_VERSION, COMMAND_VERSION_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          get_command_version(ctx, GRN_TEXT_VALUE(&buf), GRN_BULK_CURR(&buf));
          if (ctx->rc) {
            goto exit;
          }
        } else if (l == REQUEST_ID_LEN &&
                   !memcmp(v, REQUEST_ID, REQUEST_ID_LEN)) {
          GRN_BULK_REWIND(&request_id);
          p = grn_text_unesc_tok(ctx, &request_id, p, e, &tok_type);
        } else if (l == REQUEST_TIMEOUT_LEN &&
                   !memcmp(v, REQUEST_TIMEOUT, REQUEST_TIMEOUT_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          GRN_TEXT_PUTC(ctx, &buf, '\0');
          request_timeout = strtod(GRN_TEXT_VALUE(&buf), NULL);
        } else if (l == OUTPUT_PRETTY_LEN &&
                   !memcmp(v, OUTPUT_PRETTY, OUTPUT_PRETTY_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          if (GRN_TEXT_LEN(&buf) == strlen("yes") &&
              !memcmp(GRN_TEXT_VALUE(&buf), "yes", GRN_TEXT_LEN(&buf))) {
            ctx->impl->output.is_pretty = true;
          } else {
            ctx->impl->output.is_pretty = false;
          }
        } else if (l == OUTPUT_TRACE_LOG_LEN &&
                   !memcmp(v, OUTPUT_TRACE_LOG, OUTPUT_TRACE_LOG_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          if (GRN_TEXT_LEN(&buf) == strlen("yes") &&
              !memcmp(GRN_TEXT_VALUE(&buf), "yes", GRN_TEXT_LEN(&buf))) {
            grn_ctx_trace_log_enable(ctx);
          } else {
            grn_ctx_trace_log_disable(ctx);
          }
        } else if (l == N_WORKERS_LEN && !memcmp(v, N_WORKERS, N_WORKERS_LEN)) {
          GRN_BULK_REWIND(&buf);
          p = grn_text_unesc_tok(ctx, &buf, p, e, &tok_type);
          get_n_workers(ctx, GRN_TEXT_VALUE(&buf), GRN_BULK_CURR(&buf));
        } else if (expr &&
                   (val =
                      grn_expr_get_or_add_var(ctx, expr, v, (unsigned int)l))) {
          grn_obj_reinit(ctx, val, GRN_DB_TEXT, 0);
          p = grn_text_unesc_tok(ctx, val, p, e, &tok_type);
        } else {
          p = e;
        }
        break;
      }
      // fallthru
    case GRN_TOK_STRING:
    case GRN_TOK_QUOTE:
      if (expr && (val = grn_expr_get_var_by_offset(ctx, expr, offset++))) {
        grn_obj_reinit(ctx, val, GRN_DB_TEXT, 0);
        GRN_TEXT_PUT(ctx, val, GRN_TEXT_VALUE(&buf), GRN_TEXT_LEN(&buf));
      } else {
        p = e;
      }
      break;
    }
  }
  if (request_timeout > 0 && GRN_TEXT_LEN(&request_id) == 0) {
    grn_text_printf(ctx, &request_id, "%p", ctx);
  }
  if (GRN_TEXT_LEN(&request_id) > 0) {
    GRN_TEXT_SET(ctx,
                 &ctx->impl->current_request_id,
                 GRN_TEXT_VALUE(&request_id),
                 GRN_TEXT_LEN(&request_id));
    grn_request_canceler_register(ctx,
                                  GRN_TEXT_VALUE(&request_id),
                                  (unsigned int)GRN_TEXT_LEN(&request_id));
    if (request_timeout > 0.0) {
      ctx->impl->current_request_timer_id =
        grn_request_timer_register(GRN_TEXT_VALUE(&request_id),
                                   (unsigned int)GRN_TEXT_LEN(&request_id),
                                   request_timeout);
    }
  }
  ctx->impl->curr_expr = expr;
  if (expr && command_proc_p(expr)) {
    grn_expr_exec(ctx, expr, 0);
  } else {
    GRN_BULK_REWIND(&buf);
    grn_text_unesc_tok(ctx, &buf, str, str + str_len, &tok_type);
    if (GRN_TEXT_LEN(&buf)) {
      ERR(GRN_INVALID_ARGUMENT,
          "invalid command name: %.*s",
          (int)GRN_TEXT_LEN(&buf),
          GRN_TEXT_VALUE(&buf));
    }
  }
exit:
  GRN_OBJ_FIN(ctx, &request_id);
  GRN_OBJ_FIN(ctx, &buf);

  return expr;
}

grn_rc
grn_ctx_sendv(grn_ctx *ctx, int argc, char **argv, int flags)
{
  grn_obj buf;
  GRN_API_ENTER;
  GRN_TEXT_INIT(&buf, 0);
  while (argc--) {
    // todo : encode into json like syntax
    GRN_TEXT_PUTS(ctx, &buf, *argv);
    argv++;
    if (argc) {
      GRN_TEXT_PUTC(ctx, &buf, ' ');
    }
  }
  grn_ctx_send(ctx,
               GRN_TEXT_VALUE(&buf),
               (unsigned int)GRN_TEXT_LEN(&buf),
               flags);
  GRN_OBJ_FIN(ctx, &buf);
  GRN_API_RETURN(ctx->rc);
}

static bool
comment_command_p(const char *command, unsigned int length)
{
  const char *p, *e;

  e = command + length;
  for (p = command; p < e; p++) {
    switch (*p) {
    case '#':
      return true;
    case ' ':
    case '\t':
      break;
    default:
      return false;
    }
  }
  return false;
}

unsigned int
grn_ctx_send(grn_ctx *ctx, const char *str, unsigned int str_len, int flags)
{
  if (!ctx) {
    return 0;
  }
  GRN_API_ENTER;
  if (ctx->impl) {
    if ((flags & GRN_CTX_MORE)) {
      flags |= GRN_CTX_QUIET;
    }
    if (ctx->stat == GRN_CTX_QUIT) {
      flags |= GRN_CTX_QUIT;
    }

    ctx->impl->command.flags = flags;
    if (ctx->impl->com) {
      grn_rc rc;
      grn_com_header sheader;
      grn_timeval_now(ctx, &ctx->impl->tv);
      sheader.proto = GRN_COM_PROTO_GQTP;
      sheader.qtype = 0;
      sheader.keylen = 0;
      sheader.level = 0;
      sheader.flags = (uint8_t)flags;
      sheader.status = 0;
      sheader.opaque = 0;
      sheader.cas = 0;
      if ((rc = grn_com_send(ctx,
                             ctx->impl->com,
                             &sheader,
                             (char *)str,
                             str_len,
                             0))) {
        ERR(rc, "grn_com_send failed");
      }
      goto exit;
    } else {
      grn_obj *expr = NULL;
      bool is_comment = false;
      bool processed = false;

      grn_command_version command_version = grn_ctx_get_command_version(ctx);
      int32_t n_workers = grn_ctx_get_n_workers(ctx);
      if (ctx->impl->command.keep.command) {
        grn_obj *val;
        expr = ctx->impl->command.keep.command;
        ctx->impl->command.keep.command = NULL;
        grn_ctx_set_command_version(ctx, ctx->impl->command.keep.version);
        grn_ctx_set_n_workers(ctx, ctx->impl->command.keep.n_workers);
        if ((val = grn_expr_get_var_by_offset(ctx, expr, 0))) {
          grn_obj_reinit(ctx, val, GRN_DB_TEXT, 0);
          GRN_TEXT_PUT(ctx, val, str, str_len);
        }
        grn_expr_exec(ctx, expr, 0);
      } else if (comment_command_p(str, str_len)) {
        is_comment = true;
      } else {
        GRN_BULK_REWIND(ctx->impl->output.buf);
        GRN_BULK_REWIND(&(ctx->impl->output.levels));
        ctx->impl->output.type = GRN_CONTENT_JSON;
        ctx->impl->output.mime_type = "application/json";
        ctx->impl->output.is_pretty = false;
        grn_timeval_now(ctx, &ctx->impl->tv);
        GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_COMMAND, ">", "%.*s", str_len, str);
        if (str_len && *str == '/') {
          expr = grn_ctx_qe_exec_uri(ctx, str + 1, str_len - 1);
        } else {
          expr = grn_ctx_qe_exec(ctx, str, str_len);
        }
      }
      if (ctx->stat == GRN_CTX_QUITTING) {
        ctx->stat = GRN_CTX_QUIT;
      }
      if (!is_comment) {
        if (ctx->impl->command.keep.command) {
          ERRCLR(ctx);
        } else {
          if (ctx->impl->current_request_timer_id) {
            void *timer_id = ctx->impl->current_request_timer_id;
            ctx->impl->current_request_timer_id = NULL;
            grn_request_timer_unregister(timer_id);
          }
          if (GRN_TEXT_LEN(&ctx->impl->current_request_id) > 0) {
            grn_obj *request_id = &ctx->impl->current_request_id;
            grn_request_canceler_unregister(
              ctx,
              GRN_TEXT_VALUE(request_id),
              (unsigned int)GRN_TEXT_LEN(request_id));
            GRN_BULK_REWIND(&ctx->impl->current_request_id);
          }
          processed = true;
        }
      }
      if (!(ctx->impl->command.flags & GRN_CTX_QUIET) &&
          ctx->impl->output.func) {
        ctx->impl->output.func(ctx, GRN_CTX_TAIL, ctx->impl->output.data.ptr);
      }
      if (processed) {
        GRN_QUERY_LOG(ctx, GRN_QUERY_LOG_RESULT_CODE, "<", "rc=%d", ctx->rc);
        grn_ctx_trace_log_disable(ctx);
      }
      if (expr) {
        grn_expr_clear_vars(ctx, expr);
      }
      grn_ctx_set_command_version(ctx, command_version);
      grn_ctx_set_n_workers(ctx, n_workers);
      if (processed) {
        grn_db_command_processed(ctx, ctx->impl->db);
      }
      goto exit;
    }
  }
  ERR(GRN_INVALID_ARGUMENT, "invalid ctx assigned");
exit:
  GRN_API_RETURN(0);
}

grn_rc
grn_ctx_recv(grn_ctx *ctx, char **str, unsigned int *str_len, int *flags)
{
  if (!ctx) {
    return GRN_INVALID_ARGUMENT;
  }

  *flags = 0;

  if (ctx->stat == GRN_CTX_QUIT) {
    bool have_buffer = false;

    if (ctx->impl && !ctx->impl->com &&
        GRN_TEXT_LEN(ctx->impl->output.buf) > 0) {
      have_buffer = true;
    }

    *flags = GRN_CTX_QUIT;
    if (!have_buffer) {
      *str = NULL;
      *str_len = 0;
      return GRN_SUCCESS;
    }
  }

  GRN_API_ENTER;
  if (ctx->impl) {
    if (ctx->impl->com) {
      grn_com_header header;
      if (grn_com_recv(ctx, ctx->impl->com, &header, ctx->impl->output.buf)) {
        *str = NULL;
        *str_len = 0;
        *flags = 0;
      } else {
        *str = GRN_BULK_HEAD(ctx->impl->output.buf);
        *str_len = (unsigned int)GRN_BULK_VSIZE(ctx->impl->output.buf);
        if (header.flags & GRN_CTX_QUIT) {
          ctx->stat = GRN_CTX_QUIT;
          *flags = GRN_CTX_QUIT;
        } else {
          if (!(header.flags & GRN_CTX_TAIL)) {
            *flags = GRN_CTX_MORE;
          }
        }
        ctx->impl->output.type = static_cast<grn_content_type>(header.qtype);
        ctx->rc =
          static_cast<grn_rc>(static_cast<int16_t>(ntohs(header.status)));
        ctx->errbuf[0] = '\0';
        ctx->errline = 0;
        ctx->errfile = NULL;
        ctx->errfunc = NULL;
      }
      goto exit;
    } else {
      grn_obj *buf = ctx->impl->output.buf;
      *str = GRN_BULK_HEAD(buf);
      *str_len = (unsigned int)GRN_BULK_VSIZE(buf);
      GRN_BULK_REWIND(ctx->impl->output.buf);
      goto exit;
    }
  }
  ERR(GRN_INVALID_ARGUMENT, "invalid ctx assigned");
exit:
  GRN_API_RETURN(GRN_SUCCESS);
}

void
grn_ctx_stream_out_func(grn_ctx *ctx, int flags, void *stream)
{
  if (ctx && ctx->impl) {
    grn_obj *buf = ctx->impl->output.buf;
    size_t size = GRN_BULK_VSIZE(buf);
    if (size) {
      if (fwrite(GRN_BULK_HEAD(buf), 1, size, (FILE *)stream)) {
        fputc('\n', (FILE *)stream);
        fflush((FILE *)stream);
      }
      GRN_BULK_REWIND(buf);
    }
  }
}

void
grn_ctx_recv_handler_set(grn_ctx *ctx,
                         grn_recv_handler_func func,
                         void *func_arg)
{
  if (ctx && ctx->impl) {
    ctx->impl->output.func = func;
    ctx->impl->output.data.ptr = func_arg;
  }
}

grn_rc
grn_ctx_info_get(grn_ctx *ctx, grn_ctx_info *info)
{
  if (!ctx || !ctx->impl) {
    return GRN_INVALID_ARGUMENT;
  }
  if (ctx->impl->com) {
    info->fd = ctx->impl->com->fd;
    info->com_status = ctx->impl->com_status;
    info->outbuf = ctx->impl->output.buf;
    info->stat = ctx->stat;
  } else {
    info->fd = -1;
    info->com_status = 0;
    info->outbuf = ctx->impl->output.buf;
    info->stat = ctx->stat;
  }
  return GRN_SUCCESS;
}

#define DB_P(s) ((s) && (s)->header.type == GRN_DB)

grn_rc
grn_ctx_use(grn_ctx *ctx, grn_obj *db)
{
  GRN_API_ENTER;
  if (db && !DB_P(db)) {
    ctx->rc = GRN_INVALID_ARGUMENT;
  } else {
    if (!ctx->rc) {
      ctx->impl->db = db;
      if (db) {
        grn_obj buf;
        GRN_TEXT_INIT(&buf, 0);
        grn_obj_get_info(ctx, db, GRN_INFO_ENCODING, &buf);
        ctx->encoding = *(grn_encoding *)GRN_BULK_HEAD(&buf);
        grn_obj_close(ctx, &buf);
      }
    }
  }
  GRN_API_RETURN(ctx->rc);
}

/* don't handle error inside logger functions */

void
grn_ctx_log(grn_ctx *ctx, const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  grn_ctx_logv(ctx, fmt, ap);
  va_end(ap);
}

void
grn_ctx_logv(grn_ctx *ctx, const char *fmt, va_list ap)
{
  char buffer[GRN_CTX_MSGSIZE];
  grn_vsnprintf(buffer, GRN_CTX_MSGSIZE, fmt, ap);
  grn_strcpy(ctx->errbuf, GRN_CTX_MSGSIZE, buffer);
}

#ifdef WIN32
/* This is for clang-format.
 * clang-format can't work with "static __declspec(noinline) void". */
#  define grn_noinline __declspec(noinline)
static grn_noinline void
grn_ctx_log_back_trace_windows(grn_ctx *ctx, grn_log_level level)
{
#  ifdef GRN_WITH_WINDOWS_BACK_TRACE
#    define MAX_N_FRAMES 62
  ULONG frames_to_skip = 0;
  ULONG frames_to_capture = MAX_N_FRAMES;
  void *back_trace[MAX_N_FRAMES];
#    undef MAX_N_FRAMES
  USHORT n_frames =
    CaptureStackBackTrace(frames_to_skip, frames_to_capture, back_trace, NULL);
  if (n_frames == 0) {
    return;
  }

  HANDLE process = GetCurrentProcess();
  if (!grn_windows_symbol_initialize(ctx, process)) {
    GRN_LOG(ctx,
            level,
            "[log][back-trace] failed to initialize symbol handler");
    return;
  }

  USHORT i;
  for (i = 0; i < n_frames; i++) {
    void *address = back_trace[i];
    grn_windows_log_back_trace_entry(ctx, level, process, (DWORD64)address);
  }

  grn_windows_symbol_cleanup(ctx, process);
#  endif
}
#else
#  ifdef HAVE_BACKTRACE
static void
grn_ctx_log_back_trace_backtrace(grn_ctx *ctx, grn_log_level level)
{
#    define MAX_N_TRACES 16
  void *traces[MAX_N_TRACES];
  int n_traces = backtrace(traces, MAX_N_TRACES);
#    undef MAX_N_TRACES
  char **symbols;
  symbols = backtrace_symbols(traces, n_traces);
  if (symbols) {
    int i;
    for (i = 0; i < n_traces; i++) {
      GRN_LOG(ctx, level, "%s", symbols[i]);
    }
    free(symbols);
  } else {
    GRN_LOG(ctx, level, "[log][back-trace] backtrace_symbols() is failed");
  }
}
#  endif
#endif

bool
grn_is_back_trace_enable(void)
{
  return grn_back_trace_enable;
}

grn_rc
grn_set_back_trace_enable(bool enable)
{
  grn_back_trace_enable = enable;
  return GRN_SUCCESS;
}

void
grn_ctx_log_back_trace(grn_ctx *ctx, grn_log_level level)
{
  if (!grn_back_trace_enable) {
    return;
  }
#ifdef WIN32
  grn_ctx_log_back_trace_windows(ctx, level);
#else
#  ifdef HAVE_BACKTRACE
  grn_ctx_log_back_trace_backtrace(ctx, level);
#  endif
#endif
}

void
grn_assert(grn_ctx *ctx, int cond, const char *file, int line, const char *func)
{
  if (!cond) {
    GRN_LOG(ctx, GRN_LOG_WARNING, "ASSERT fail on %s %s:%d", func, file, line);
  }
}

const char *
grn_get_version(void)
{
  return GRN_VERSION;
}

uint32_t
grn_get_version_major(void)
{
  return GRN_VERSION_MAJOR;
}

uint32_t
grn_get_version_minor(void)
{
  return GRN_VERSION_MINOR;
}

uint32_t
grn_get_version_micro(void)
{
  return GRN_VERSION_MICRO;
}

const char *
grn_get_package(void)
{
  return PACKAGE;
}

const char *
grn_get_package_label(void)
{
  return PACKAGE_LABEL;
}

#ifdef WIN32
static LONG WINAPI
grn_exception_filter(EXCEPTION_POINTERS *info)
{
  grn_ctx *ctx = &grn_gctx;
  GRN_LOG(ctx, GRN_LOG_CRIT, "-- CRASHED!!! --");
  grn_windows_log_back_trace(ctx,
                             GRN_LOG_CRIT,
                             GetCurrentProcess(),
                             GetCurrentThread(),
                             info->ContextRecord);
  GRN_LOG(ctx, GRN_LOG_CRIT, "----------------");
  return EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(HAVE_SIGNAL_H) /* WIN32 */
static bool crashed = false;
static void
grn_crash_handler(int signal_number, siginfo_t *info, void *context)
{
  grn_ctx *ctx = &grn_gctx;

  if (crashed) {
    if (signal_number == SIGSEGV) {
      GRN_LOG(ctx, GRN_LOG_CRIT, "crashed in SEGV handler.");
    } else if (signal_number == SIGABRT) {
      GRN_LOG(ctx, GRN_LOG_CRIT, "crashed in ABRT handler.");
    } else {
      GRN_LOG(ctx,
              GRN_LOG_CRIT,
              "crashed in signal(%d) handler.",
              signal_number);
    }
    return;
  }
  crashed = true;

  GRN_LOG(ctx, GRN_LOG_CRIT, "-- CRASHED!!! --");
#  ifdef HAVE_BACKTRACE
#    define N_TRACE_LEVEL 1024
  {
    static void *trace[N_TRACE_LEVEL];
    int n = backtrace(trace, N_TRACE_LEVEL);
    char **symbols = backtrace_symbols(trace, n);
    int i;

    if (symbols) {
      for (i = 0; i < n; i++) {
        GRN_LOG(ctx, GRN_LOG_CRIT, "%s", symbols[i]);
      }
      free(symbols);
    }
  }
#  else  /* HAVE_BACKTRACE */
  GRN_LOG(ctx, GRN_LOG_CRIT, "backtrace() isn't available.");
#  endif /* HAVE_BACKTRACE */
  GRN_LOG(ctx, GRN_LOG_CRIT, "----------------");

  struct sigaction default_action;
  memset(&default_action, 0, sizeof(default_action));
  sigemptyset(&default_action.sa_mask);
  default_action.sa_handler = SIG_DFL;
  default_action.sa_flags = SA_SIGINFO | SA_ONSTACK;
  if (sigaction(SIGABRT, &default_action, NULL) == 0) {
    abort();
  } else {
    GRN_LOG(ctx,
            GRN_LOG_CRIT,
            "exiting because resetting SIGABRT handler failed");
    _exit(EXIT_FAILURE);
  }
}
#endif   /* WIN32 */

grn_rc
grn_set_segv_handler(void)
{
  grn_rc rc = GRN_SUCCESS;
#ifdef WIN32
  SetUnhandledExceptionFilter(grn_exception_filter);
#elif defined(HAVE_SIGNAL_H) /* WIN32 */
  grn_ctx *ctx = &grn_gctx;
  struct sigaction action;
  memset(&action, 0, sizeof(action));
  sigemptyset(&action.sa_mask);
  action.sa_sigaction = grn_crash_handler;
  action.sa_flags = SA_SIGINFO | SA_ONSTACK;
  if (sigaction(SIGSEGV, &action, NULL)) {
    SERR("failed to set SIGSEGV action");
    rc = ctx->rc;
  }
#endif                       /* WIN32 */
  return rc;
}

grn_rc
grn_set_abrt_handler(void)
{
  grn_rc rc = GRN_SUCCESS;
#if !defined(WIN32) && defined(HAVE_SIGNAL_H)
  grn_ctx *ctx = &grn_gctx;
  struct sigaction action;
  memset(&action, 0, sizeof(action));
  sigemptyset(&action.sa_mask);
  action.sa_sigaction = grn_crash_handler;
  action.sa_flags = SA_SIGINFO | SA_ONSTACK;
  if (sigaction(SIGABRT, &action, NULL)) {
    SERR("failed to set SIGABRT action");
    rc = ctx->rc;
  }
#endif
  return rc;
}

#if defined(HAVE_SIGNAL_H) && !defined(WIN32)
static struct sigaction old_int_handler;
static void
int_handler(int signal_number, siginfo_t *info, void *context)
{
  grn_gctx.stat = GRN_CTX_QUIT;
  sigaction(signal_number, &old_int_handler, NULL);
}

static struct sigaction old_term_handler;
static void
term_handler(int signal_number, siginfo_t *info, void *context)
{
  grn_gctx.stat = GRN_CTX_QUIT;
  sigaction(signal_number, &old_term_handler, NULL);
}
#endif /* defined(HAVE_SIGNAL_H) && !defined(WIN32) */

grn_rc
grn_set_int_handler(void)
{
  grn_rc rc = GRN_SUCCESS;
#if defined(HAVE_SIGNAL_H) && !defined(WIN32)
  grn_ctx *ctx = &grn_gctx;
  struct sigaction action;

  sigemptyset(&action.sa_mask);
  action.sa_sigaction = int_handler;
  action.sa_flags = SA_SIGINFO;

  if (sigaction(SIGINT, &action, &old_int_handler)) {
    SERR("failed to set SIGINT action");
    rc = ctx->rc;
  }
#endif
  return rc;
}

grn_rc
grn_set_term_handler(void)
{
  grn_rc rc = GRN_SUCCESS;
#if defined(HAVE_SIGNAL_H) && !defined(WIN32)
  grn_ctx *ctx = &grn_gctx;
  struct sigaction action;

  sigemptyset(&action.sa_mask);
  action.sa_sigaction = term_handler;
  action.sa_flags = SA_SIGINFO;

  if (sigaction(SIGTERM, &action, &old_term_handler)) {
    SERR("failed to set SIGTERM action");
    rc = ctx->rc;
  }
#endif
  return rc;
}

void
grn_ctx_output_flush(grn_ctx *ctx, int flags)
{
  if (flags & GRN_CTX_QUIET) {
    return;
  }
  if (!ctx->impl->output.func) {
    return;
  }
  if (ctx->impl->output.arrow_stream_writer) {
    grn_arrow_stream_writer_flush(ctx, ctx->impl->output.arrow_stream_writer);
  }
  ctx->impl->output.func(ctx, flags, ctx->impl->output.data.ptr);
}

void
grn_ctx_output_array_open(grn_ctx *ctx, const char *name, int nelements)
{
  grn_output_array_open(ctx,
                        ctx->impl->output.buf,
                        ctx->impl->output.type,
                        name,
                        nelements);
}

void
grn_ctx_output_array_close(grn_ctx *ctx)
{
  grn_output_array_close(ctx, ctx->impl->output.buf, ctx->impl->output.type);
}

void
grn_ctx_output_map_open(grn_ctx *ctx, const char *name, int nelements)
{
  grn_output_map_open(ctx,
                      ctx->impl->output.buf,
                      ctx->impl->output.type,
                      name,
                      nelements);
}

void
grn_ctx_output_map_close(grn_ctx *ctx)
{
  grn_output_map_close(ctx, ctx->impl->output.buf, ctx->impl->output.type);
}

void
grn_ctx_output_null(grn_ctx *ctx)
{
  grn_output_null(ctx, ctx->impl->output.buf, ctx->impl->output.type);
}

void
grn_ctx_output_int32(grn_ctx *ctx, int32_t value)
{
  grn_output_int32(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_uint32(grn_ctx *ctx, uint32_t value)
{
  grn_output_uint32(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_int64(grn_ctx *ctx, int64_t value)
{
  grn_output_int64(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_uint64(grn_ctx *ctx, uint64_t value)
{
  grn_output_uint64(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_float32(grn_ctx *ctx, float value)
{
  grn_output_float32(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_float(grn_ctx *ctx, double value)
{
  grn_output_float(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_cstr(grn_ctx *ctx, const char *value)
{
  grn_output_cstr(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_str(grn_ctx *ctx, const char *value, size_t value_len)
{
  grn_output_str(ctx,
                 ctx->impl->output.buf,
                 ctx->impl->output.type,
                 value,
                 value_len);
}

void
grn_ctx_output_bool(grn_ctx *ctx, bool value)
{
  grn_output_bool(ctx, ctx->impl->output.buf, ctx->impl->output.type, value);
}

void
grn_ctx_output_obj(grn_ctx *ctx, grn_obj *value, grn_obj_format *format)
{
  grn_output_obj(ctx,
                 ctx->impl->output.buf,
                 ctx->impl->output.type,
                 value,
                 format);
}

void
grn_ctx_output_result_set_open_metadata(grn_ctx *ctx,
                                        grn_obj *result_set,
                                        grn_obj_format *format,
                                        uint32_t n_additional_elements)
{
  grn_output_result_set_open_metadata(ctx,
                                      ctx->impl->output.buf,
                                      ctx->impl->output.type,
                                      result_set,
                                      format,
                                      n_additional_elements);
}

void
grn_ctx_output_result_set_open(grn_ctx *ctx,
                               grn_obj *result_set,
                               grn_obj_format *format,
                               uint32_t n_additional_elements)
{
  grn_output_result_set_open(ctx,
                             ctx->impl->output.buf,
                             ctx->impl->output.type,
                             result_set,
                             format,
                             n_additional_elements);
}

void
grn_ctx_output_result_set_close(grn_ctx *ctx,
                                grn_obj *result_set,
                                grn_obj_format *format)
{
  grn_output_result_set_close(ctx,
                              ctx->impl->output.buf,
                              ctx->impl->output.type,
                              result_set,
                              format);
}

void
grn_ctx_output_result_set(grn_ctx *ctx,
                          grn_obj *result_set,
                          grn_obj_format *format)
{
  grn_output_result_set(ctx,
                        ctx->impl->output.buf,
                        ctx->impl->output.type,
                        result_set,
                        format);
}

void
grn_ctx_output_table_columns(grn_ctx *ctx,
                             grn_obj *table,
                             grn_obj_format *format)
{
  grn_output_table_columns(ctx,
                           ctx->impl->output.buf,
                           ctx->impl->output.type,
                           table,
                           format);
}

void
grn_ctx_output_table_records_open(grn_ctx *ctx, int n_records)
{
  grn_output_table_records_open(ctx,
                                ctx->impl->output.buf,
                                ctx->impl->output.type,
                                n_records);
}

void
grn_ctx_output_table_records_close(grn_ctx *ctx)
{
  grn_output_table_records_close(ctx,
                                 ctx->impl->output.buf,
                                 ctx->impl->output.type);
}

void
grn_ctx_output_table_records_content(grn_ctx *ctx,
                                     grn_obj *table,
                                     grn_obj_format *format)
{
  grn_output_table_records_content(ctx,
                                   ctx->impl->output.buf,
                                   ctx->impl->output.type,
                                   table,
                                   format);
}

void
grn_ctx_output_table_records(grn_ctx *ctx,
                             grn_obj *table,
                             grn_obj_format *format)
{
  grn_output_table_records(ctx,
                           ctx->impl->output.buf,
                           ctx->impl->output.type,
                           table,
                           format);
}
}

grn::TaskExecutor *
grn_ctx_get_task_executor(grn_ctx *ctx)
{
  if (!ctx->impl) {
    return nullptr;
  }
  if (!ctx->impl->parallel.task_executor) {
    ctx->impl->parallel.task_executor = new grn::TaskExecutor(ctx);
  }
  auto task_executor =
    static_cast<grn::TaskExecutor *>(ctx->impl->parallel.task_executor);
  task_executor->set_n_workers(ctx->impl->parallel.n_workers);
  return task_executor;
}
