/*
  Copyright (C) 2009-2018  Brazil
  Copyright (C) 2018-2024  Sutou Kouhei <kou@clear-code.com>
  Copyright (C) 2021  Horimoto Yasuhiro <horimoto@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 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_cache.h"
#include "grn_ctx.h"
#include "grn_ctx_impl.h"
#include "grn_db.h"
#include "grn_expr.h"
#include "grn_float.h"
#include "grn_geo.h"
#include "grn_ii.h"
#include "grn_load.h"
#include "grn_output.h"
#include "grn_pat.h"
#include "grn_posting.h"
#include "grn_proc.h"
#include "grn_report.h"
#include "grn_str.h"
#include "grn_util.h"

#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>

#ifdef WIN32
#  include <io.h>
#  include <share.h>
#endif /* WIN32 */

#ifdef GRN_WITH_APACHE_ARROW
#  include <arrow/util/config.h>
#endif

#ifndef O_NOFOLLOW
#  define O_NOFOLLOW 0
#endif

/**** globals for procs ****/
const char *grn_document_root = NULL;

#define VAR GRN_PROC_GET_VAR_BY_OFFSET

static double grn_between_too_many_index_match_ratio = 0.01;
static double grn_in_values_too_many_index_match_ratio = 0.01;
static int32_t grn_sub_filter_pre_filter_threshold = 10;

void
grn_proc_init_from_env(void)
{
  {
    char grn_between_too_many_index_match_ratio_env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_BETWEEN_TOO_MANY_INDEX_MATCH_RATIO",
               grn_between_too_many_index_match_ratio_env,
               GRN_ENV_BUFFER_SIZE);
    if (grn_between_too_many_index_match_ratio_env[0]) {
      grn_between_too_many_index_match_ratio =
        atof(grn_between_too_many_index_match_ratio_env);
    }
  }

  {
    char grn_in_values_too_many_index_match_ratio_env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_IN_VALUES_TOO_MANY_INDEX_MATCH_RATIO",
               grn_in_values_too_many_index_match_ratio_env,
               GRN_ENV_BUFFER_SIZE);
    if (grn_in_values_too_many_index_match_ratio_env[0]) {
      grn_in_values_too_many_index_match_ratio =
        atof(grn_in_values_too_many_index_match_ratio_env);
    }
  }

  {
    char env[GRN_ENV_BUFFER_SIZE];
    grn_getenv("GRN_SUB_FILTER_PRE_FILTER_THRESHOLD", env, GRN_ENV_BUFFER_SIZE);
    if (env[0]) {
      grn_sub_filter_pre_filter_threshold =
        grn_atoi(env, env + strlen(env), NULL);
    }
  }
}

/* bulk must be initialized grn_bulk or grn_msg */
static int
grn_bulk_put_from_file(grn_ctx *ctx, grn_obj *bulk, const char *path)
{
  /* FIXME: implement more smartly with grn_bulk */
  int fd, ret = 0;
  struct stat stat;
  grn_open(fd, path, O_RDONLY | O_NOFOLLOW | GRN_OPEN_FLAG_BINARY);
  if (fd == -1) {
    switch (errno) {
    case EACCES:
      ERR(GRN_OPERATION_NOT_PERMITTED, "request is not allowed: <%s>", path);
      break;
    case ENOENT:
      ERR(GRN_NO_SUCH_FILE_OR_DIRECTORY, "no such file: <%s>", path);
      break;
#ifndef WIN32
    case ELOOP:
      ERR(GRN_NO_SUCH_FILE_OR_DIRECTORY,
          "symbolic link is not allowed: <%s>",
          path);
      break;
#endif /* WIN32 */
    default:
      ERRNO_ERR("failed to open file: <%s>", path);
      break;
    }
    return 0;
  }
  if (fstat(fd, &stat) != -1) {
    char *buf, *bp;
    off_t rest = stat.st_size;
    if ((buf = GRN_MALLOC(rest))) {
      ssize_t ss;
      for (bp = buf; rest; rest -= ss, bp += ss) {
        if ((ss = grn_read(fd, bp, rest)) == -1) {
          goto exit;
        }
      }
      GRN_TEXT_PUT(ctx, bulk, buf, stat.st_size);
      ret = 1;
    }
    GRN_FREE(buf);
  } else {
    ERR(GRN_INVALID_ARGUMENT, "cannot stat file: <%s>", path);
  }
exit:
  grn_close(fd);
  return ret;
}

#ifdef stat
#  undef stat
#endif /* stat */

grn_rc
grn_proc_prefixed_options_parse(grn_ctx *ctx,
                                grn_obj *options,
                                const char *prefix,
                                const char *tag,
                                const char *name,
                                ...)
{
  va_list args;

  va_start(args, name);
  grn_rc rc =
    grn_proc_prefixed_options_parsev(ctx, options, prefix, tag, name, args);
  va_end(args);

  return rc;
}

grn_rc
grn_proc_prefixed_options_parsev(grn_ctx *ctx,
                                 grn_obj *options,
                                 const char *prefix,
                                 const char *tag,
                                 const char *name,
                                 va_list args)
{
  GRN_API_ENTER;

  grn_obj func_tag_buffer;
  GRN_TEXT_INIT(&func_tag_buffer, 0);
  grn_text_printf(ctx, &func_tag_buffer, "%s[options][parse]", tag);
  GRN_TEXT_PUTC(ctx, &func_tag_buffer, '\0');
  const char *func_tag = GRN_TEXT_VALUE(&func_tag_buffer);

  grn_obj full_name;
  GRN_TEXT_INIT(&full_name, 0);

  grn_obj used_ids;
  GRN_RECORD_INIT(&used_ids, GRN_OBJ_VECTOR, GRN_ID_NIL);

  if (options->header.type != GRN_TABLE_HASH_KEY) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, options);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s options must be a hash table: %.*s",
                     func_tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    goto exit;
  }

  const size_t n_specified_options = grn_table_size(ctx, options);
  while (name && GRN_RECORD_VECTOR_SIZE(&used_ids) < n_specified_options) {
    grn_proc_option_value_type type = va_arg(args, grn_proc_option_value_type);
    void *value_raw = NULL;
    GRN_TEXT_SETS(ctx, &full_name, prefix);
    GRN_TEXT_PUTS(ctx, &full_name, name);
    GRN_TEXT_PUTC(ctx, &full_name, '\0');
    grn_id id = grn_hash_get(ctx,
                             (grn_hash *)options,
                             GRN_TEXT_VALUE(&full_name),
                             GRN_TEXT_LEN(&full_name) - 1,
                             &value_raw);
    grn_obj *value = value_raw;
    switch (type) {
    case GRN_PROC_OPTION_VALUE_RAW:
      {
        grn_obj **raw = va_arg(args, grn_obj **);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *raw = value;
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_MODE:
      {
        grn_operator *mode = va_arg(args, grn_operator *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *mode = grn_proc_get_value_mode(ctx, value, *mode, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_OPERATOR:
      {
        grn_operator *op = va_arg(args, grn_operator *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *op = grn_proc_get_value_operator(ctx, value, *op, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_EXPR_FLAGS:
      {
        grn_expr_flags *flags = va_arg(args, grn_expr_flags *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *flags = grn_proc_expr_query_flags_parse(ctx, value, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_INT32:
      {
        int32_t *number = va_arg(args, int32_t *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *number = grn_proc_get_value_int32(ctx, value, *number, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_UINT32:
      {
        uint32_t *number = va_arg(args, uint32_t *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *number = grn_proc_get_value_uint32(ctx, value, *number, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_INT64:
      {
        int64_t *number = va_arg(args, int64_t *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *number = grn_proc_get_value_int64(ctx, value, *number, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_BOOL:
      {
        bool *b = va_arg(args, bool *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *b = grn_proc_get_value_bool(ctx, value, *b, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_FUNC:
      {
        grn_proc_option_value_parse_func func =
          va_arg(args, grn_proc_option_value_parse_func);
        void *user_data = va_arg(args, void *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          grn_rc rc =
            func(ctx, GRN_TEXT_VALUE(&full_name), value, func_tag, user_data);
          if (rc != GRN_SUCCESS) {
            if (ctx->rc == GRN_SUCCESS) {
              grn_obj inspected;
              GRN_TEXT_INIT(&inspected, 0);
              grn_inspect(ctx, &inspected, value);
              GRN_PLUGIN_ERROR(ctx,
                               rc,
                               "%s[%s] failed to parse: <%.*s>",
                               func_tag,
                               name,
                               (int)GRN_TEXT_LEN(&inspected),
                               GRN_TEXT_VALUE(&inspected));
              GRN_OBJ_FIN(ctx, &inspected);
            }
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_TOKENIZE_MODE:
      {
        grn_tokenize_mode *mode = va_arg(args, grn_tokenize_mode *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *mode = grn_proc_get_value_tokenize_mode(ctx, value, *mode, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_TOKEN_CURSOR_FLAGS:
      {
        uint32_t *flags = va_arg(args, uint32_t *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *flags =
            grn_proc_get_value_token_cursor_flags(ctx, value, *flags, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_DOUBLE:
      {
        double *number = va_arg(args, double *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *number = grn_proc_get_value_double(ctx, value, *number, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    case GRN_PROC_OPTION_VALUE_RAW_STRING:
      {
        grn_raw_string *string = va_arg(args, grn_raw_string *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *string = grn_proc_get_value_raw_string(ctx, value, string, func_tag);
          if (ctx->rc != GRN_SUCCESS) {
            goto exit;
          }
        }
      }
      break;
    default:
      GRN_PLUGIN_ERROR(ctx,
                       GRN_INVALID_ARGUMENT,
                       "%s[%s] invalid option value type: %d",
                       func_tag,
                       GRN_TEXT_VALUE(&full_name),
                       type);
      goto exit;
    }
    name = va_arg(args, const char *);
  }

  const size_t n_used_options = GRN_RECORD_VECTOR_SIZE(&used_ids);
  if (n_used_options == n_specified_options) {
    goto exit;
  }

  {
    grn_obj message;
    GRN_TEXT_INIT(&message, 0);
    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)options, cursor, id)
    {
      bool is_used_option = false;
      size_t i;
      for (i = 0; i < n_used_options; i++) {
        if (GRN_RECORD_VALUE_AT(&used_ids, i) == id) {
          is_used_option = true;
          break;
        }
      }
      if (is_used_option) {
        continue;
      }

      void *key;
      int key_size = grn_hash_cursor_get_key(ctx, cursor, &key);
      grn_raw_string key_string;
      key_string.value = key;
      key_string.length = key_size;
      if (!GRN_RAW_STRING_START_WITH_CSTRING(key_string, prefix)) {
        continue;
      }
      if (GRN_TEXT_LEN(&message) > 0) {
        GRN_TEXT_PUTS(ctx, &message, ", ");
      }
      grn_text_printf(ctx, &message, "<%.*s>", key_size, (char *)key);
    }
    GRN_HASH_EACH_END(ctx, cursor);
    if (GRN_TEXT_LEN(&message) > 0) {
      grn_obj inspected;
      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, options);
      GRN_PLUGIN_ERROR(ctx,
                       GRN_INVALID_ARGUMENT,
                       "%s unknown option names: %.*s: %.*s",
                       func_tag,
                       (int)GRN_TEXT_LEN(&message),
                       GRN_TEXT_VALUE(&message),
                       (int)GRN_TEXT_LEN(&inspected),
                       GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
    }
    GRN_OBJ_FIN(ctx, &message);
  }

exit:
  GRN_OBJ_FIN(ctx, &used_ids);
  GRN_OBJ_FIN(ctx, &full_name);
  GRN_OBJ_FIN(ctx, &func_tag_buffer);
  GRN_API_RETURN(ctx->rc);
}

grn_rc
grn_proc_options_parse(
  grn_ctx *ctx, grn_obj *options, const char *tag, const char *name, ...)
{
  va_list args;

  va_start(args, name);
  grn_rc rc = grn_proc_options_parsev(ctx, options, tag, name, args);
  va_end(args);

  return rc;
}

grn_rc
grn_proc_options_parsev(grn_ctx *ctx,
                        grn_obj *options,
                        const char *tag,
                        const char *name,
                        va_list args)
{
  return grn_proc_prefixed_options_parsev(ctx, options, "", tag, name, args);
}

static void
grn_proc_func_generate_cache_key_add(grn_ctx *ctx,
                                     grn_obj *cache_key,
                                     grn_obj *arg)
{
  switch (arg->header.type) {
  case GRN_TABLE_HASH_KEY:
    GRN_HASH_EACH_BEGIN(ctx, (grn_hash *)arg, cursor, id)
    {
      void *key;
      unsigned int key_size;
      void *value;
      int value_size =
        grn_hash_cursor_get_key_value(ctx, cursor, &key, &key_size, &value);
      GRN_TEXT_PUT(ctx, cache_key, key, key_size);
      GRN_TEXT_PUTC(ctx, cache_key, '\1');
      if (value_size == sizeof(grn_obj)) {
        grn_proc_func_generate_cache_key_add(ctx, cache_key, (grn_obj *)value);
      }
      GRN_TEXT_PUTC(ctx, cache_key, '\2');
    }
    GRN_HASH_EACH_END(ctx, cursor);
    break;
  default:
    GRN_TEXT_PUT(ctx, cache_key, GRN_BULK_HEAD(arg), GRN_BULK_VSIZE(arg));
    break;
  }
}

grn_rc
grn_proc_func_generate_cache_key(grn_ctx *ctx,
                                 const char *function_name,
                                 grn_obj **args,
                                 int n_args,
                                 grn_obj *cache_key)
{
  GRN_API_ENTER;
  GRN_TEXT_PUTS(ctx, cache_key, function_name);
  GRN_TEXT_PUTC(ctx, cache_key, '\0');
  int i;
  for (i = 0; i < n_args; i++) {
    grn_proc_func_generate_cache_key_add(ctx, cache_key, args[i]);
    GRN_TEXT_PUTC(ctx, cache_key, '\0');
  }
  GRN_API_RETURN(ctx->rc);
}

/**** procs ****/

static grn_obj *
proc_load(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_load_input input;

  input.type = grn_plugin_proc_get_var_content_type(ctx,
                                                    user_data,
                                                    "input_type",
                                                    -1,
                                                    GRN_CONTENT_JSON);
#define INIT_STRING_ARGUMENT(member_name, arg_name)                            \
  input.member_name.value =                                                    \
    grn_plugin_proc_get_var_string(ctx,                                        \
                                   user_data,                                  \
                                   arg_name,                                   \
                                   -1,                                         \
                                   &(input.member_name.length))

  INIT_STRING_ARGUMENT(table, "table");
  INIT_STRING_ARGUMENT(columns, "columns");
  INIT_STRING_ARGUMENT(values, "values");
  INIT_STRING_ARGUMENT(if_exists, "ifexists");
  INIT_STRING_ARGUMENT(each, "each");

#undef INIT_STRING_ARGUMENT

  input.output_ids =
    grn_plugin_proc_get_var_bool(ctx, user_data, "output_ids", -1, false);
  input.output_errors =
    grn_plugin_proc_get_var_bool(ctx, user_data, "output_errors", -1, false);
  input.lock_table =
    grn_plugin_proc_get_var_bool(ctx, user_data, "lock_table", -1, false);
  input.emit_level = 1;

  grn_load_internal(ctx, &input);
  if (ctx->rc == GRN_CANCEL) {
    ctx->impl->loader.stat = GRN_LOADER_END;
    ctx->impl->loader.error.rc = GRN_SUCCESS;
  }
  bool accept_more_data = true;
  if (ctx->impl->loader.stat == GRN_LOADER_END) {
    accept_more_data = false;
  }
  if (ctx->impl->command.flags & GRN_CTX_TAIL) {
    accept_more_data = false;
  }
  if (accept_more_data) {
    grn_obj *command = grn_proc_get_info(ctx, user_data, NULL, NULL, NULL);
    grn_ctx_set_keep_command(ctx, command);
    return NULL;
  }

  if (ctx->impl->loader.error.rc != GRN_SUCCESS) {
    ctx->rc = ctx->impl->loader.error.rc;
    grn_strcpy(ctx->errbuf, GRN_CTX_MSGSIZE, ctx->impl->loader.error.buffer);
    ctx->errline = ctx->impl->loader.error.line;
    ctx->errfile = ctx->impl->loader.error.file;
    ctx->errfunc = ctx->impl->loader.error.func;
  }
  {
    unsigned int n_records;
    if (ctx->impl->loader.table) {
      n_records = grn_table_size(ctx, ctx->impl->loader.table);
    } else {
      n_records = 0;
    }
    GRN_QUERY_LOG(ctx,
                  GRN_QUERY_LOG_SIZE,
                  ":",
                  "load(%d): [%d][%d][%d]",
                  ctx->impl->loader.n_records,
                  ctx->impl->loader.n_record_errors,
                  ctx->impl->loader.n_column_errors,
                  n_records);
  }
  if (grn_ctx_get_command_version(ctx) >= GRN_COMMAND_VERSION_3) {
    int n_elements = 1;
    if (ctx->impl->loader.output_ids) {
      n_elements++;
    }
    if (ctx->impl->loader.output_errors) {
      n_elements++;
    }
    GRN_OUTPUT_MAP_OPEN("result", n_elements);
    GRN_OUTPUT_CSTR("n_loaded_records");
    GRN_OUTPUT_INT64(ctx->impl->loader.n_records);
    if (ctx->impl->loader.output_ids) {
      grn_obj *ids = &(ctx->impl->loader.ids);
      int i, n_ids;

      GRN_OUTPUT_CSTR("loaded_ids");
      n_ids = GRN_BULK_VSIZE(ids) / sizeof(uint32_t);
      GRN_OUTPUT_ARRAY_OPEN("loaded_ids", n_ids);
      for (i = 0; i < n_ids; i++) {
        GRN_OUTPUT_UINT64(GRN_UINT32_VALUE_AT(ids, i));
      }
      GRN_OUTPUT_ARRAY_CLOSE();
    }
    if (ctx->impl->loader.output_errors) {
      grn_obj *return_codes = &(ctx->impl->loader.return_codes);
      grn_obj *error_messages = &(ctx->impl->loader.error_messages);
      int i, n;

      GRN_OUTPUT_CSTR("errors");
      n = GRN_BULK_VSIZE(return_codes) / sizeof(int32_t);
      GRN_OUTPUT_ARRAY_OPEN("errors", n);
      for (i = 0; i < n; i++) {
        const char *message;
        unsigned int message_size;

        message_size =
          grn_vector_get_element(ctx, error_messages, i, &message, NULL, NULL);

        GRN_OUTPUT_MAP_OPEN("error", 2);
        GRN_OUTPUT_CSTR("return_code");
        GRN_OUTPUT_INT64(GRN_INT32_VALUE_AT(return_codes, i));
        GRN_OUTPUT_CSTR("message");
        if (message_size == 0) {
          GRN_OUTPUT_NULL();
        } else {
          GRN_OUTPUT_STR(message, message_size);
        }
        GRN_OUTPUT_MAP_CLOSE();
      }
      GRN_OUTPUT_ARRAY_CLOSE();
    }
    GRN_OUTPUT_MAP_CLOSE();
  } else {
    GRN_OUTPUT_INT64(ctx->impl->loader.n_records);
  }
  if (ctx->impl->loader.table) {
    grn_db_touch(ctx, DB_OBJ(ctx->impl->loader.table)->db);
  }
  grn_ctx_loader_clear(ctx);

  return NULL;
}

static grn_obj *
proc_status(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_timeval now;
  grn_cache *cache;
  grn_cache_statistics statistics;
  int n_elements = 17;
#ifdef GRN_WITH_APACHE_ARROW
  n_elements++;
#endif

  grn_timeval_now(ctx, &now);
  cache = grn_cache_current_get(ctx);
  grn_cache_get_statistics(ctx, cache, &statistics);
  GRN_OUTPUT_MAP_OPEN("RESULT", n_elements);
  GRN_OUTPUT_CSTR("alloc_count");
  GRN_OUTPUT_INT64(grn_alloc_count());
  GRN_OUTPUT_CSTR("starttime");
  GRN_OUTPUT_INT64(grn_starttime.tv_sec);
  GRN_OUTPUT_CSTR("start_time");
  GRN_OUTPUT_INT64(grn_starttime.tv_sec);
  GRN_OUTPUT_CSTR("uptime");
  GRN_OUTPUT_INT64(now.tv_sec - grn_starttime.tv_sec);
  GRN_OUTPUT_CSTR("version");
  GRN_OUTPUT_CSTR(grn_get_version());
  GRN_OUTPUT_CSTR("n_queries");
  GRN_OUTPUT_INT64(statistics.nfetches);
  GRN_OUTPUT_CSTR("cache_hit_rate");
  if (statistics.nfetches == 0) {
    GRN_OUTPUT_FLOAT(0.0);
  } else {
    double cache_hit_rate;
    cache_hit_rate = (double)statistics.nhits / (double)statistics.nfetches;
    GRN_OUTPUT_FLOAT(cache_hit_rate * 100.0);
  }
  GRN_OUTPUT_CSTR("command_version");
  GRN_OUTPUT_INT32(grn_ctx_get_command_version(ctx));
  GRN_OUTPUT_CSTR("default_command_version");
  GRN_OUTPUT_INT32(grn_get_default_command_version());
  GRN_OUTPUT_CSTR("max_command_version");
  GRN_OUTPUT_INT32(GRN_COMMAND_VERSION_MAX);
  {
    grn_com_queue *job_queue;
    job_queue = grn_job_queue_current_get(ctx);
    GRN_OUTPUT_CSTR("n_jobs");
    if (job_queue) {
      GRN_OUTPUT_UINT64(grn_com_queue_size(ctx, job_queue));
    } else {
      GRN_OUTPUT_UINT64(0);
    }
  }
  GRN_OUTPUT_CSTR("features");
  {
    const int n_features = 19;
    GRN_OUTPUT_MAP_OPEN("features", n_features);

    GRN_OUTPUT_CSTR("nfkc");
#ifndef NO_NFKC
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("mecab");
#ifdef GRN_WITH_MECAB
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("message_pack");
#ifdef GRN_WITH_MESSAGE_PACK
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("mruby");
#ifdef GRN_WITH_MRUBY
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("onigmo");
#ifdef GRN_WITH_ONIGMO
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("zlib");
#ifdef GRN_WITH_ZLIB
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("lz4");
#ifdef GRN_WITH_LZ4
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("zstandard");
#ifdef GRN_WITH_ZSTD
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("kqueue");
#ifdef USE_KQUEUE
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("epoll");
#ifdef USE_EPOLL
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("poll");
#ifdef USE_POLL
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("rapidjson");
#ifdef GRN_WITH_RAPIDJSON
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("apache_arrow");
#ifdef GRN_WITH_APACHE_ARROW
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("xxhash");
#ifdef GRN_WITH_XXHASH
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("blosc");
#ifdef GRN_WITH_BLOSC
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("bfloat16");
#ifdef GRN_HAVE_BFLOAT16
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("h3");
#ifdef GRN_WITH_H3
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("simdjson");
#ifdef GRN_WITH_SIMDJSON
    GRN_OUTPUT_BOOL(true);
#else
    GRN_OUTPUT_BOOL(false);
#endif

    GRN_OUTPUT_CSTR("back_trace");
    GRN_OUTPUT_BOOL(grn_is_back_trace_enable());

    GRN_OUTPUT_CSTR("reference_count");
    GRN_OUTPUT_BOOL(grn_is_reference_count_enable());

    GRN_OUTPUT_MAP_CLOSE();
  }
#ifdef GRN_WITH_APACHE_ARROW
  GRN_OUTPUT_CSTR("apache_arrow");
  {
    const int n_apache_arrow_elements = 4;
    GRN_OUTPUT_MAP_OPEN("apache_arrow", n_apache_arrow_elements);

    GRN_OUTPUT_CSTR("version_major");
    GRN_OUTPUT_INT32(ARROW_VERSION_MAJOR);

    GRN_OUTPUT_CSTR("version_minor");
    GRN_OUTPUT_INT32(ARROW_VERSION_MINOR);

    GRN_OUTPUT_CSTR("version_patch");
    GRN_OUTPUT_INT32(ARROW_VERSION_PATCH);

    GRN_OUTPUT_CSTR("version");
    GRN_OUTPUT_CSTR(ARROW_VERSION_STRING);

    GRN_OUTPUT_MAP_CLOSE();
  }
#endif
  GRN_OUTPUT_CSTR("memory_map_size");
  GRN_OUTPUT_UINT64(grn_get_memory_map_size());
  GRN_OUTPUT_CSTR("n_workers");
  GRN_OUTPUT_INT32(grn_ctx_get_n_workers(ctx));
  GRN_OUTPUT_CSTR("default_n_workers");
  GRN_OUTPUT_INT32(grn_get_default_n_workers());
  GRN_OUTPUT_CSTR("os");
#ifdef HOST_OS
  GRN_OUTPUT_CSTR(HOST_OS);
#else
  GRN_OUTPUT_CSTR("Unknown");
#endif
  GRN_OUTPUT_CSTR("cpu");
#ifdef HOST_CPU
  GRN_OUTPUT_CSTR(HOST_CPU);
#else
  GRN_OUTPUT_CSTR("unknown");
#endif
  GRN_OUTPUT_MAP_CLOSE();

#ifdef GRN_WITH_MEMORY_DEBUG
  if (grn_plugin_proc_get_var_bool(ctx,
                                   user_data,
                                   "dump_alloc_info",
                                   -1,
                                   false)) {
    grn_alloc_info_dump(&grn_gctx);
  }
#endif

  return NULL;
}

#define GRN_STRLEN(s) ((s) ? strlen(s) : 0)

void
grn_proc_output_object_name(grn_ctx *ctx, grn_obj *obj)
{
  grn_obj bulk;
  int name_len;
  char name[GRN_TABLE_MAX_KEY_SIZE];

  if (obj) {
    GRN_TEXT_INIT(&bulk, GRN_OBJ_DO_SHALLOW_COPY);
    name_len = grn_obj_name(ctx, obj, name, GRN_TABLE_MAX_KEY_SIZE);
    GRN_TEXT_SET(ctx, &bulk, name, name_len);
  } else {
    GRN_VOID_INIT(&bulk);
  }

  GRN_OUTPUT_OBJ(&bulk, NULL);
  GRN_OBJ_FIN(ctx, &bulk);
}

void
grn_proc_output_object_id_name(grn_ctx *ctx, grn_id id)
{
  grn_obj *obj = NULL;

  if (id != GRN_ID_NIL) {
    obj = grn_ctx_at(ctx, id);
  }

  grn_proc_output_object_name(ctx, obj);
}

static grn_obj *
proc_missing(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  uint32_t plen;
  grn_obj *outbuf = ctx->impl->output.buf;
  static int grn_document_root_len = -1;
  if (!grn_document_root) {
    return NULL;
  }
  if (grn_document_root_len < 0) {
    size_t l;
    if ((l = strlen(grn_document_root)) > PATH_MAX) {
      return NULL;
    }
    grn_document_root_len = (int)l;
    if (l > 0 && grn_document_root[l - 1] == '/') {
      grn_document_root_len--;
    }
  }
  if ((plen = GRN_TEXT_LEN(VAR(0))) + grn_document_root_len < PATH_MAX) {
    char path[PATH_MAX];
    grn_memcpy(path, grn_document_root, grn_document_root_len);
    path[grn_document_root_len] = '/';
    grn_str_url_path_normalize(ctx,
                               GRN_TEXT_VALUE(VAR(0)),
                               GRN_TEXT_LEN(VAR(0)),
                               path + grn_document_root_len + 1,
                               PATH_MAX - grn_document_root_len - 1);
    grn_bulk_put_from_file(ctx, outbuf, path);
  } else {
    uint32_t abbrlen = 32;
    ERR(GRN_INVALID_ARGUMENT,
        "too long path name: <%s/%.*s...> %u(%u)",
        grn_document_root,
        abbrlen < plen ? abbrlen : plen,
        GRN_TEXT_VALUE(VAR(0)),
        plen + grn_document_root_len,
        PATH_MAX);
  }
  return NULL;
}

static grn_obj *
proc_quit(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  ctx->stat = GRN_CTX_QUITTING;
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_obj *
proc_shutdown(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  const char *mode;
  size_t mode_size;

  mode = grn_plugin_proc_get_var_string(ctx, user_data, "mode", -1, &mode_size);
#define MODE_EQUAL(name)                                                       \
  (mode_size == strlen(name) && memcmp(mode, name, mode_size) == 0)
  if (mode_size == 0 || MODE_EQUAL("graceful")) {
    /* Do nothing. This is the default. */
  } else if (MODE_EQUAL("immediate")) {
    grn_request_canceler_cancel_all();
    if (ctx->rc == GRN_INTERRUPTED_FUNCTION_CALL) {
      ctx->rc = GRN_SUCCESS;
    }
  } else {
    ERR(GRN_INVALID_ARGUMENT,
        "[shutdown] mode must be <graceful> or <immediate>: <%.*s>",
        (int)mode_size,
        mode);
  }
#undef MODE_EQUAL

  if (ctx->rc == GRN_SUCCESS) {
    grn_gctx.stat = GRN_CTX_QUIT;
    ctx->stat = GRN_CTX_QUITTING;
  }

  GRN_OUTPUT_BOOL(!ctx->rc);

  return NULL;
}

static grn_obj *
proc_defrag(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *obj;
  int olen, threshold;
  olen = GRN_TEXT_LEN(VAR(0));

  if (olen) {
    obj = grn_ctx_get(ctx, GRN_TEXT_VALUE(VAR(0)), olen);
  } else {
    obj = ctx->impl->db;
  }

  threshold = GRN_TEXT_LEN(VAR(1))
                ? grn_atoi(GRN_TEXT_VALUE(VAR(1)), GRN_BULK_CURR(VAR(1)), NULL)
                : 0;

  if (obj) {
    grn_obj_defrag(ctx, obj, threshold);
  } else {
    ERR(GRN_INVALID_ARGUMENT, "defrag object not found");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_rc
proc_delete_validate_selector(grn_ctx *ctx,
                              grn_obj *table,
                              grn_obj *table_name,
                              grn_obj *key,
                              grn_obj *id,
                              grn_obj *filter)
{
  grn_rc rc = GRN_SUCCESS;

  if (!table) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] table doesn't exist: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name));
    return rc;
  }

  if (GRN_TEXT_LEN(key) == 0 && GRN_TEXT_LEN(id) == 0 &&
      GRN_TEXT_LEN(filter) == 0) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] either key, id or filter must be specified: "
        "table: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name));
    return rc;
  }

  if (GRN_TEXT_LEN(key) && GRN_TEXT_LEN(id) && GRN_TEXT_LEN(filter)) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] "
        "record selector must be one of key, id and filter: "
        "table: <%.*s>, key: <%.*s>, id: <%.*s>, filter: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name),
        (int)GRN_TEXT_LEN(key),
        GRN_TEXT_VALUE(key),
        (int)GRN_TEXT_LEN(id),
        GRN_TEXT_VALUE(id),
        (int)GRN_TEXT_LEN(filter),
        GRN_TEXT_VALUE(filter));
    return rc;
  }

  if (GRN_TEXT_LEN(key) && GRN_TEXT_LEN(id) && GRN_TEXT_LEN(filter) == 0) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] "
        "can't use both key and id: table: <%.*s>, key: <%.*s>, id: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name),
        (int)GRN_TEXT_LEN(key),
        GRN_TEXT_VALUE(key),
        (int)GRN_TEXT_LEN(id),
        GRN_TEXT_VALUE(id));
    return rc;
  }

  if (GRN_TEXT_LEN(key) && GRN_TEXT_LEN(id) == 0 && GRN_TEXT_LEN(filter)) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] "
        "can't use both key and filter: "
        "table: <%.*s>, key: <%.*s>, filter: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name),
        (int)GRN_TEXT_LEN(key),
        GRN_TEXT_VALUE(key),
        (int)GRN_TEXT_LEN(filter),
        GRN_TEXT_VALUE(filter));
    return rc;
  }

  if (GRN_TEXT_LEN(key) == 0 && GRN_TEXT_LEN(id) && GRN_TEXT_LEN(filter)) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc,
        "[table][record][delete] "
        "can't use both id and filter: "
        "table: <%.*s>, id: <%.*s>, filter: <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name),
        (int)GRN_TEXT_LEN(id),
        GRN_TEXT_VALUE(id),
        (int)GRN_TEXT_LEN(filter),
        GRN_TEXT_VALUE(filter));
    return rc;
  }

  return rc;
}

static grn_obj *
proc_delete(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_rc rc = GRN_INVALID_ARGUMENT;
  grn_obj *table_name = VAR(0);
  grn_obj *key = VAR(1);
  grn_obj *id = VAR(2);
  grn_obj *filter = VAR(3);
  grn_obj *table = NULL;
  uint32_t n_deleted = 0;
  uint32_t n_errors = 0;
  int32_t limit;

  if (GRN_TEXT_LEN(table_name) == 0) {
    rc = GRN_INVALID_ARGUMENT;
    ERR(rc, "[table][record][delete] table name isn't specified");
    goto exit;
  }

  table =
    grn_ctx_get(ctx, GRN_TEXT_VALUE(table_name), GRN_TEXT_LEN(table_name));
  rc = proc_delete_validate_selector(ctx, table, table_name, key, id, filter);
  if (rc != GRN_SUCCESS) {
    goto exit;
  }

  limit = grn_plugin_proc_get_var_int32(ctx, user_data, "limit", -1, -1);
  if (limit == 0) {
    goto exit;
  }

  if (GRN_TEXT_LEN(key)) {
    grn_obj casted_key;
    if (key->header.domain != table->header.domain) {
      GRN_OBJ_INIT(&casted_key, GRN_BULK, 0, table->header.domain);
      grn_obj_cast(ctx, key, &casted_key, GRN_FALSE);
      key = &casted_key;
    }
    if (ctx->rc) {
      rc = ctx->rc;
    } else {
      if (limit < 0) {
        const int32_t n_records = 1;
        limit += n_records + 1;
      }
      if (limit >= 1) {
        rc =
          grn_table_delete(ctx, table, GRN_BULK_HEAD(key), GRN_BULK_VSIZE(key));
        if (rc == GRN_SUCCESS) {
          n_deleted++;
        } else {
          n_errors++;
        }
      }
    }
    if (key == &casted_key) {
      GRN_OBJ_FIN(ctx, &casted_key);
    }
  } else if (GRN_TEXT_LEN(id)) {
    const char *end;
    grn_id parsed_id = grn_atoui(GRN_TEXT_VALUE(id), GRN_BULK_CURR(id), &end);
    if (end == GRN_BULK_CURR(id)) {
      if (limit < 0) {
        const int32_t n_records = 1;
        limit += n_records + 1;
      }
      if (limit >= 1) {
        rc = grn_table_delete_by_id(ctx, table, parsed_id);
        if (rc == GRN_SUCCESS) {
          n_deleted++;
        } else {
          n_errors++;
        }
      }
    } else {
      rc = GRN_INVALID_ARGUMENT;
      ERR(rc,
          "[table][record][delete] id should be number: "
          "table: <%.*s>, id: <%.*s>, detail: <%.*s|%c|%.*s>",
          (int)GRN_TEXT_LEN(table_name),
          GRN_TEXT_VALUE(table_name),
          (int)GRN_TEXT_LEN(id),
          GRN_TEXT_VALUE(id),
          (int)(end - GRN_TEXT_VALUE(id)),
          GRN_TEXT_VALUE(id),
          end[0],
          (int)(GRN_TEXT_VALUE(id) - end - 1),
          end + 1);
    }
  } else if (GRN_TEXT_LEN(filter)) {
    grn_obj *cond, *v;

    GRN_EXPR_CREATE_FOR_QUERY(ctx, table, cond, v);
    grn_expr_parse(ctx,
                   cond,
                   GRN_TEXT_VALUE(filter),
                   GRN_TEXT_LEN(filter),
                   NULL,
                   GRN_OP_MATCH,
                   GRN_OP_AND,
                   GRN_EXPR_SYNTAX_SCRIPT);
    if (ctx->rc) {
      char errbuf[GRN_CTX_MSGSIZE];
      grn_strcpy(errbuf, GRN_CTX_MSGSIZE, ctx->errbuf);
      rc = ctx->rc;
      ERR(rc,
          "[table][record][delete] failed to parse filter: "
          "table: <%.*s>, filter: <%.*s>, detail: <%s>",
          (int)GRN_TEXT_LEN(table_name),
          GRN_TEXT_VALUE(table_name),
          (int)GRN_TEXT_LEN(filter),
          GRN_TEXT_VALUE(filter),
          errbuf);
    } else {
      grn_obj *records;

      records = grn_table_select(ctx, table, cond, NULL, GRN_OP_OR);
      if (records) {
        if (limit < 0) {
          limit += grn_table_size(ctx, records) + 1;
        }
        GRN_TABLE_EACH_BEGIN(ctx, records, cursor, result_id)
        {
          void *key;
          grn_id id;
          grn_rc sub_rc;

          if (grn_table_cursor_get_key(ctx, cursor, &key) == 0) {
            continue;
          }

          if (n_deleted >= (uint32_t)limit) {
            break;
          }

          id = *(grn_id *)key;
          sub_rc = grn_table_delete_by_id(ctx, table, id);
          if (sub_rc == GRN_SUCCESS) {
            n_deleted++;
          } else {
            n_errors++;
          }
          if (rc == GRN_SUCCESS) {
            rc = sub_rc;
          }
          if (ctx->rc == GRN_CANCEL) {
            break;
          }
          if (ctx->rc != GRN_SUCCESS) {
            ERRCLR(ctx);
          }
        }
        GRN_TABLE_EACH_END(ctx, cursor);
        grn_obj_unlink(ctx, records);
      }
    }
    grn_obj_unlink(ctx, cond);
  }

exit:
  {
    unsigned int n_rest_records = 0;
    if (table) {
      n_rest_records = grn_table_size(ctx, table);
    }
    GRN_QUERY_LOG(ctx,
                  GRN_QUERY_LOG_SIZE,
                  ":",
                  "delete(%u): [%u][%u]",
                  n_deleted,
                  n_errors,
                  n_rest_records);
  }
  if (table) {
    grn_obj_unlink(ctx, table);
  }
  GRN_OUTPUT_BOOL(rc == GRN_SUCCESS);
  return NULL;
}

bool
grn_proc_option_value_bool(grn_ctx *ctx, grn_obj *option, bool default_value)
{
  if (!option) {
    return default_value;
  }

  grn_raw_string value;
  GRN_RAW_STRING_SET(value, option);
  if (value.length == 0) {
    return default_value;
  }

  if (GRN_RAW_STRING_EQUAL_CSTRING(value, "yes") ||
      GRN_RAW_STRING_EQUAL_CSTRING(value, "true")) {
    return true;
  } else if (GRN_RAW_STRING_EQUAL_CSTRING(value, "no") ||
             GRN_RAW_STRING_EQUAL_CSTRING(value, "false")) {
    return false;
  } else {
    return default_value;
  }
}

int32_t
grn_proc_option_value_int32(grn_ctx *ctx,
                            grn_obj *option,
                            int32_t default_value)
{
  const char *value;
  size_t value_length;
  int32_t int32_value;
  const char *rest;

  if (!option) {
    return default_value;
  }

  value = GRN_TEXT_VALUE(option);
  value_length = GRN_TEXT_LEN(option);

  if (value_length == 0) {
    return default_value;
  }

  int32_value = grn_atoi(value, value + value_length, &rest);
  if (rest == value + value_length) {
    return int32_value;
  } else {
    return default_value;
  }
}

uint32_t
grn_proc_option_value_uint32(grn_ctx *ctx,
                             grn_obj *option,
                             uint32_t default_value)
{
  const char *value;
  size_t value_length;
  uint32_t uint32_value;
  const char *rest;

  if (!option) {
    return default_value;
  }

  value = GRN_TEXT_VALUE(option);
  value_length = GRN_TEXT_LEN(option);

  if (value_length == 0) {
    return default_value;
  }

  uint32_value = grn_atoui(value, value + value_length, &rest);
  if (rest == value + value_length) {
    return uint32_value;
  } else {
    return default_value;
  }
}

double
grn_proc_option_value_double(grn_ctx *ctx,
                             grn_obj *option,
                             double default_value)
{
  if (!option) {
    return default_value;
  }

  if (GRN_TEXT_LEN(option) == 0) {
    return default_value;
  }

  double value = default_value;
  grn_obj buffer;
  GRN_FLOAT_INIT(&buffer, 0);
  grn_rc rc = grn_obj_cast(ctx, option, &buffer, false);
  if (rc == GRN_SUCCESS) {
    value = GRN_FLOAT_VALUE(&buffer);
  }
  GRN_OBJ_FIN(ctx, &buffer);

  return value;
}

float
grn_proc_option_value_float(grn_ctx *ctx, grn_obj *option, float default_value)
{
  if (!option) {
    return default_value;
  }

  if (GRN_TEXT_LEN(option) == 0) {
    return default_value;
  }

  float value = default_value;
  grn_obj buffer;
  GRN_FLOAT_INIT(&buffer, 0);
  grn_rc rc = grn_obj_cast(ctx, option, &buffer, false);
  if (rc == GRN_SUCCESS) {
    value = GRN_FLOAT_VALUE(&buffer);
  }
  GRN_OBJ_FIN(ctx, &buffer);

  return value;
}

const char *
grn_proc_option_value_string(grn_ctx *ctx, grn_obj *option, size_t *size)
{
  const char *value;
  size_t value_length;

  if (!option) {
    if (size) {
      *size = 0;
    }
    return NULL;
  }

  value = GRN_TEXT_VALUE(option);
  value_length = GRN_TEXT_LEN(option);

  if (size) {
    *size = value_length;
  }

  if (value_length == 0) {
    return NULL;
  } else {
    return value;
  }
}

grn_content_type
grn_proc_option_value_content_type(grn_ctx *ctx,
                                   grn_obj *option,
                                   grn_content_type default_value)
{
  if (!option) {
    return default_value;
  }

  return grn_content_type_parse(ctx, option, default_value);
}

bool
grn_proc_get_value_bool(grn_ctx *ctx,
                        grn_obj *value,
                        bool default_value,
                        const char *tag)
{
  if (!value) {
    return default_value;
  }

  if (value->header.domain == GRN_DB_BOOL) {
    return GRN_BOOL_VALUE(value);
  }

  if (grn_type_id_is_text_family(ctx, value->header.domain)) {
    return grn_proc_option_value_bool(ctx, value, default_value);
  }

  grn_obj inspected;
  GRN_TEXT_INIT(&inspected, 0);
  grn_inspect(ctx, &inspected, value);
  GRN_PLUGIN_ERROR(ctx,
                   GRN_INVALID_ARGUMENT,
                   "%s value must be a bool or string: <%.*s>",
                   tag,
                   (int)GRN_TEXT_LEN(&inspected),
                   GRN_TEXT_VALUE(&inspected));
  GRN_OBJ_FIN(ctx, &inspected);
  return default_value;
}

int32_t
grn_proc_get_value_int32(grn_ctx *ctx,
                         grn_obj *value,
                         int32_t default_value_raw,
                         const char *tag)
{
  int32_t value_raw;

  if (!value) {
    return default_value_raw;
  }

  if (!grn_type_id_is_number_family(ctx, value->header.domain)) {
    grn_obj inspected;

    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s value must be a number: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_value_raw;
  }

  if (value->header.domain == GRN_DB_INT32) {
    value_raw = GRN_INT32_VALUE(value);
  } else if (value->header.domain == GRN_DB_INT64) {
    value_raw = (int32_t)GRN_INT64_VALUE(value);
  } else {
    grn_obj buffer;
    grn_rc rc;

    GRN_INT32_INIT(&buffer, 0);
    rc = grn_obj_cast(ctx, value, &buffer, GRN_FALSE);
    if (rc == GRN_SUCCESS) {
      value_raw = GRN_INT32_VALUE(&buffer);
    }
    GRN_OBJ_FIN(ctx, &buffer);

    if (rc != GRN_SUCCESS) {
      grn_obj inspected;

      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, value);
      GRN_PLUGIN_ERROR(ctx,
                       rc,
                       "%s "
                       "failed to cast value to number: <%.*s>",
                       tag,
                       (int)GRN_TEXT_LEN(&inspected),
                       GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
      value_raw = default_value_raw;
    }
  }

  return value_raw;
}

uint32_t
grn_proc_get_value_uint32(grn_ctx *ctx,
                          grn_obj *value,
                          uint32_t default_value_raw,
                          const char *tag)
{
  uint32_t value_raw;

  if (!value) {
    return default_value_raw;
  }

  if (!grn_type_id_is_number_family(ctx, value->header.domain)) {
    grn_obj inspected;

    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s value must be a number: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_value_raw;
  }

  if (value->header.domain == GRN_DB_UINT32) {
    value_raw = GRN_UINT32_VALUE(value);
  } else if (value->header.domain == GRN_DB_INT32) {
    value_raw = (uint32_t)GRN_INT32_VALUE(value);
  } else if (value->header.domain == GRN_DB_UINT64) {
    value_raw = (uint32_t)GRN_UINT64_VALUE(value);
  } else if (value->header.domain == GRN_DB_INT64) {
    value_raw = (uint32_t)GRN_INT64_VALUE(value);
  } else {
    grn_obj buffer;
    grn_rc rc;

    GRN_UINT32_INIT(&buffer, 0);
    rc = grn_obj_cast(ctx, value, &buffer, GRN_FALSE);
    if (rc == GRN_SUCCESS) {
      value_raw = GRN_UINT32_VALUE(&buffer);
    }
    GRN_OBJ_FIN(ctx, &buffer);

    if (rc != GRN_SUCCESS) {
      grn_obj inspected;

      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, value);
      GRN_PLUGIN_ERROR(ctx,
                       rc,
                       "%s "
                       "failed to cast value to number: <%.*s>",
                       tag,
                       (int)GRN_TEXT_LEN(&inspected),
                       GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
      value_raw = default_value_raw;
    }
  }

  return value_raw;
}

int64_t
grn_proc_get_value_int64(grn_ctx *ctx,
                         grn_obj *value,
                         int64_t default_value_raw,
                         const char *tag)
{
  int64_t value_raw;

  if (!value) {
    return default_value_raw;
  }

  if (!grn_type_id_is_number_family(ctx, value->header.domain)) {
    grn_obj inspected;

    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s value must be a number: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_value_raw;
  }

  if (value->header.domain == GRN_DB_INT32) {
    value_raw = GRN_INT32_VALUE(value);
  } else if (value->header.domain == GRN_DB_INT64) {
    value_raw = GRN_INT64_VALUE(value);
  } else {
    grn_obj buffer;
    grn_rc rc;

    GRN_INT64_INIT(&buffer, 0);
    rc = grn_obj_cast(ctx, value, &buffer, GRN_FALSE);
    if (rc == GRN_SUCCESS) {
      value_raw = GRN_INT64_VALUE(&buffer);
    }
    GRN_OBJ_FIN(ctx, &buffer);

    if (rc != GRN_SUCCESS) {
      grn_obj inspected;

      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, value);
      GRN_PLUGIN_ERROR(ctx,
                       rc,
                       "%s "
                       "failed to cast value to number: <%.*s>",
                       tag,
                       (int)GRN_TEXT_LEN(&inspected),
                       GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
      value_raw = default_value_raw;
    }
  }

  return value_raw;
}

grn_operator
grn_proc_get_value_mode(grn_ctx *ctx,
                        grn_obj *value,
                        grn_operator default_mode,
                        const char *tag)
{
  if (!value) {
    return default_mode;
  }

  if (value->header.domain != GRN_DB_TEXT) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s mode must be text: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_mode;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    return default_mode;
  }

#define EQUAL_MODE(name)                                                       \
  (GRN_TEXT_LEN(value) == strlen(name) &&                                      \
   memcmp(GRN_TEXT_VALUE(value), name, strlen(name)) == 0)

  if (EQUAL_MODE("==") || EQUAL_MODE("EQUAL")) {
    return GRN_OP_EQUAL;
  } else if (EQUAL_MODE("!=") || EQUAL_MODE("NOT_EQUAL")) {
    return GRN_OP_NOT_EQUAL;
  } else if (EQUAL_MODE("<") || EQUAL_MODE("LESS")) {
    return GRN_OP_LESS;
  } else if (EQUAL_MODE(">") || EQUAL_MODE("GREATER")) {
    return GRN_OP_GREATER;
  } else if (EQUAL_MODE("<=") || EQUAL_MODE("LESS_EQUAL")) {
    return GRN_OP_LESS_EQUAL;
  } else if (EQUAL_MODE(">=") || EQUAL_MODE("GREATER_EQUAL")) {
    return GRN_OP_GREATER_EQUAL;
  } else if (EQUAL_MODE("@") || EQUAL_MODE("MATCH")) {
    return GRN_OP_MATCH;
  } else if (EQUAL_MODE("*N") || EQUAL_MODE("NEAR")) {
    return GRN_OP_NEAR;
  } else if (EQUAL_MODE("*NP") || EQUAL_MODE("NEAR_PHRASE")) {
    return GRN_OP_NEAR_PHRASE;
  } else if (EQUAL_MODE("*ONP") || EQUAL_MODE("ORDERED_NEAR_PHRASE")) {
    return GRN_OP_ORDERED_NEAR_PHRASE;
  } else if (EQUAL_MODE("*NPP") || EQUAL_MODE("NEAR_PHRASE_PRODUCT")) {
    return GRN_OP_NEAR_PHRASE_PRODUCT;
  } else if (EQUAL_MODE("*ONPP") || EQUAL_MODE("ORDERED_NEAR_PHRASE_PRODUCT")) {
    return GRN_OP_ORDERED_NEAR_PHRASE_PRODUCT;
  } else if (EQUAL_MODE("*S") || EQUAL_MODE("SIMILAR")) {
    return GRN_OP_SIMILAR;
  } else if (EQUAL_MODE("^") || EQUAL_MODE("@^") || EQUAL_MODE("PREFIX")) {
    return GRN_OP_PREFIX;
  } else if (EQUAL_MODE("$") || EQUAL_MODE("@$") || EQUAL_MODE("SUFFIX")) {
    return GRN_OP_SUFFIX;
  } else if (EQUAL_MODE("~") || EQUAL_MODE("@~") || EQUAL_MODE("REGEXP")) {
    return GRN_OP_REGEXP;
  } else {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s mode must be one of them: "
                     "["
                     "\"==\", \"EQUAL\", "
                     "\"!=\", \"NOT_EQUAL\", "
                     "\"<\", \"LESS\", "
                     "\">\", \"GREATER\", "
                     "\"<=\", \"LESS_EQUAL\", "
                     "\">=\", \"GREATER_EQUAL\", "
                     "\"@\", \"MATCH\", "
                     "\"*N\", \"NEAR\", "
                     "\"*NP\", \"NEAR_PHRASE\", "
                     "\"*ONP\", \"ORDERED_NEAR_PHRASE\", "
                     "\"*NPP\", \"NEAR_PHRASE_PRODUCT\", "
                     "\"*ONPP\", \"ORDERED_NEAR_PHRASE_PRODUCT\", "
                     "\"*S\", \"SIMILAR\", "
                     "\"^\", \"@^\", \"PREFIX\", "
                     "\"$\", \"@$\", \"SUFFIX\", "
                     "\"~\", \"@~\", \"REGEXP\""
                     "]: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(value),
                     GRN_TEXT_VALUE(value));
    return default_mode;
  }

#undef EQUAL_MODE
}

grn_operator
grn_proc_get_value_operator(grn_ctx *ctx,
                            grn_obj *value,
                            grn_operator default_operator,
                            const char *tag)
{
  if (!value) {
    return default_operator;
  }

  if (value->header.domain != GRN_DB_TEXT) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s operator must be text: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_operator;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    return default_operator;
  }

  grn_raw_string operator_string;
  GRN_RAW_STRING_SET(operator_string, value);

  if (GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "&&") ||
      GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "+") ||
      GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "AND")) {
    return GRN_OP_AND;
  } else if (GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "||") ||
             GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "OR")) {
    return GRN_OP_OR;
  } else if (GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "!") ||
             GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "NOT")) {
    return GRN_OP_NOT;
  } else if (GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "&!") ||
             GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "-") ||
             GRN_RAW_STRING_EQUAL_CSTRING(operator_string, "AND_NOT")) {
    return GRN_OP_AND_NOT;
  } else {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s operator must be one of them: "
                     "["
                     "\"&&\", \"+\", \"AND\", "
                     "\"||\", \"OR\", "
                     "\"!\" \"NOT\", "
                     "\"&!\", \"-\", \"AND_NOT\""
                     "]: <%.*s>",
                     tag,
                     (int)operator_string.length,
                     operator_string.value);
    return default_operator;
  }
}

grn_tokenize_mode
grn_proc_get_value_tokenize_mode(grn_ctx *ctx,
                                 grn_obj *value,
                                 grn_tokenize_mode default_mode,
                                 const char *tag)
{
  if (!value) {
    return default_mode;
  }

  if (!grn_obj_is_text_family_bulk(ctx, value)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s tokenize mode must be text bulk: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_mode;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    return default_mode;
  }

#define EQUAL_MODE(name)                                                       \
  (GRN_TEXT_LEN(value) == strlen(name) &&                                      \
   memcmp(GRN_TEXT_VALUE(value), name, strlen(name)) == 0)

  if (EQUAL_MODE("GET")) {
    return GRN_TOKENIZE_GET;
  } else if (EQUAL_MODE("ADD")) {
    return GRN_TOKENIZE_ADD;
  } else if (EQUAL_MODE("ONLY")) {
    return GRN_TOKENIZE_ONLY;
  } else {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s tokenize mode must be one of them: "
                     "["
                     "\"GET\", "
                     "\"ADD\", "
                     "\"ONLY\""
                     "]: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(value),
                     GRN_TEXT_VALUE(value));
    return default_mode;
  }

#undef EQUAL_MODE
}

uint32_t
grn_proc_get_value_token_cursor_flags(grn_ctx *ctx,
                                      grn_obj *value,
                                      uint32_t default_flags,
                                      const char *tag)
{
  if (!value) {
    return default_flags;
  }

  if (!grn_obj_is_text_family_bulk(ctx, value)) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s token cursor flags must be text bulk: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_flags;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    return default_flags;
  }

  uint32_t flags = 0;
  const char *names, *names_end;
  names = GRN_TEXT_VALUE(value);
  names_end = names + GRN_TEXT_LEN(value);
  while (names < names_end) {
    if (*names == '|' || *names == ' ') {
      names += 1;
      continue;
    }

#define CHECK_FLAG(name)                                                       \
  if (((size_t)(names_end - names) >= (sizeof(#name) - 1)) &&                  \
      (!memcmp(names, #name, sizeof(#name) - 1))) {                            \
    flags |= GRN_TOKEN_CURSOR_##name;                                          \
    names += sizeof(#name) - 1;                                                \
    continue;                                                                  \
  }

    CHECK_FLAG(ENABLE_TOKENIZED_DELIMITER);

#define GRN_TOKEN_CURSOR_NONE 0
    CHECK_FLAG(NONE);
#undef GRN_TOKEN_CURSOR_NONE

    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s token cursor flag must be one of them: "
                     "["
                     "\"ENABLE_TOKENIZED_DELIMITER\", "
                     "\"NONE\""
                     "]: <%.*s>",
                     tag,
                     (int)(names_end - names),
                     names);
    return default_flags;
#undef CHECK_FLAG
  }

  return flags;
}

double
grn_proc_get_value_double(grn_ctx *ctx,
                          grn_obj *value,
                          double default_value_raw,
                          const char *tag)
{
  double value_raw;

  if (!value) {
    return default_value_raw;
  }

  if (!grn_type_id_is_number_family(ctx, value->header.domain)) {
    grn_obj inspected;

    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect(ctx, &inspected, value);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s value must be a number: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return default_value_raw;
  }

  if (value->header.domain == GRN_DB_FLOAT) {
    value_raw = GRN_FLOAT_VALUE(value);
  } else if (value->header.domain == GRN_DB_FLOAT32) {
    value_raw = GRN_FLOAT32_VALUE(value);
#ifdef GRN_HAVE_BFLOAT16
  } else if (value->header.domain == GRN_DB_BFLOAT16) {
    value_raw = grn_bfloat16_to_float32(GRN_BFLOAT16_VALUE(value));
#endif
  } else {
    grn_obj buffer;
    grn_rc rc;

    GRN_FLOAT_INIT(&buffer, 0);
    rc = grn_obj_cast(ctx, value, &buffer, GRN_FALSE);
    if (rc == GRN_SUCCESS) {
      value_raw = GRN_FLOAT_VALUE(&buffer);
    }
    GRN_OBJ_FIN(ctx, &buffer);

    if (rc != GRN_SUCCESS) {
      grn_obj inspected;

      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, value);
      GRN_PLUGIN_ERROR(ctx,
                       rc,
                       "%s "
                       "failed to cast value to number: <%.*s>",
                       tag,
                       (int)GRN_TEXT_LEN(&inspected),
                       GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
      value_raw = default_value_raw;
    }
  }

  return value_raw;
}

grn_obj *
grn_proc_get_value_object(grn_ctx *ctx, grn_obj *value, const char *tag)
{
  if (!value) {
    return NULL;
  }

  if (!grn_obj_is_text_family_bulk(ctx, value)) {
    return value;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s object name isn't specified",
                     tag);
    return NULL;
  }

  grn_obj *object =
    grn_ctx_get(ctx, GRN_TEXT_VALUE(value), GRN_TEXT_LEN(value));
  if (!object) {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s object doesn't exist: <%.*s>",
                     tag,
                     (int)GRN_TEXT_LEN(value),
                     GRN_TEXT_VALUE(value));
    return NULL;
  }

  return object;
}

grn_obj *
grn_proc_get_value_column(grn_ctx *ctx,
                          grn_obj *value,
                          grn_obj *table,
                          const char *tag)
{
  if (!value) {
    return NULL;
  }

  if (!grn_obj_is_text_family_bulk(ctx, value)) {
    return value;
  }

  if (GRN_TEXT_LEN(value) == 0) {
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s column name isn't specified",
                     tag);
    return NULL;
  }

  grn_obj *column =
    grn_obj_column(ctx, table, GRN_TEXT_VALUE(value), GRN_TEXT_LEN(value));
  if (!column) {
    grn_obj inspected;
    GRN_TEXT_INIT(&inspected, 0);
    grn_inspect_limited(ctx, &inspected, table);
    GRN_PLUGIN_ERROR(ctx,
                     GRN_INVALID_ARGUMENT,
                     "%s column doesn't exist: <%.*s>: %.*s",
                     tag,
                     (int)GRN_TEXT_LEN(value),
                     GRN_TEXT_VALUE(value),
                     (int)GRN_TEXT_LEN(&inspected),
                     GRN_TEXT_VALUE(&inspected));
    GRN_OBJ_FIN(ctx, &inspected);
    return NULL;
  }

  return column;
}

grn_raw_string
grn_proc_get_value_raw_string(grn_ctx *ctx,
                              grn_obj *value,
                              grn_raw_string *default_value,
                              const char *tag)
{
  if (!value) {
    return (grn_raw_string){NULL, 0};
  }

  if (!grn_obj_is_text_family_bulk(ctx, value)) {
    return *default_value;
  }

  return (grn_raw_string){GRN_TEXT_VALUE(value), GRN_TEXT_LEN(value)};
}

static grn_obj *
proc_cache_limit(grn_ctx *ctx,
                 int nargs,
                 grn_obj **args,
                 grn_user_data *user_data)
{
  grn_cache *cache;
  unsigned int current_max_n_entries;

  cache = grn_cache_current_get(ctx);
  current_max_n_entries = grn_cache_get_max_n_entries(ctx, cache);
  if (GRN_TEXT_LEN(VAR(0))) {
    const char *rest;
    uint32_t max =
      grn_atoui(GRN_TEXT_VALUE(VAR(0)), GRN_BULK_CURR(VAR(0)), &rest);
    if (GRN_BULK_CURR(VAR(0)) == rest) {
      grn_cache_set_max_n_entries(ctx, cache, max);
    } else {
      ERR(GRN_INVALID_ARGUMENT,
          "max value is invalid unsigned integer format: <%.*s>",
          (int)GRN_TEXT_LEN(VAR(0)),
          GRN_TEXT_VALUE(VAR(0)));
    }
  }
  if (ctx->rc == GRN_SUCCESS) {
    GRN_OUTPUT_INT64(current_max_n_entries);
  }
  return NULL;
}

static grn_obj *
proc_register(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  if (GRN_TEXT_LEN(VAR(0))) {
    const char *name;
    GRN_TEXT_PUTC(ctx, VAR(0), '\0');
    name = GRN_TEXT_VALUE(VAR(0));
    grn_plugin_register(ctx, name);
  } else {
    ERR(GRN_INVALID_ARGUMENT, "path is required");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

void
grn_ii_buffer_check(grn_ctx *ctx, grn_ii *ii, uint32_t seg);

static grn_obj *
proc_check(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *obj = grn_ctx_get(ctx, GRN_TEXT_VALUE(VAR(0)), GRN_TEXT_LEN(VAR(0)));
  if (!obj) {
    ERR(GRN_INVALID_ARGUMENT,
        "no such object: <%.*s>",
        (int)GRN_TEXT_LEN(VAR(0)),
        GRN_TEXT_VALUE(VAR(0)));
    GRN_OUTPUT_BOOL(!ctx->rc);
  } else {
    switch (obj->header.type) {
    case GRN_DB:
      GRN_OUTPUT_BOOL(!ctx->rc);
      break;
    case GRN_TABLE_PAT_KEY:
      grn_pat_check(ctx, (grn_pat *)obj);
      break;
    case GRN_TABLE_HASH_KEY:
      grn_hash_check(ctx, (grn_hash *)obj);
      break;
    case GRN_TABLE_DAT_KEY:
    case GRN_TABLE_NO_KEY:
    case GRN_COLUMN_FIX_SIZE:
      GRN_OUTPUT_BOOL(!ctx->rc);
      break;
    case GRN_COLUMN_VAR_SIZE:
      grn_ja_check(ctx, (grn_ja *)obj);
      break;
    case GRN_COLUMN_INDEX:
      {
        grn_ii *ii = (grn_ii *)obj;
        grn_ii_header_common *h = ii->header.common;
        char buf[8];
        GRN_OUTPUT_ARRAY_OPEN("RESULT", 8);
        {
          uint32_t i, j, g = 0, a = 0, b = 0;
          uint32_t max = 0;
          for (i = h->bgqtail; i != h->bgqhead;
               i = ((i + 1) & (GRN_II_BGQSIZE - 1))) {
            j = h->bgqbody[i];
            g++;
            if (j > max) {
              max = j;
            }
          }
          for (i = 0; i < GRN_II_MAX_LSEG; i++) {
            j = h->binfo[i];
            if (j != GRN_II_PSEG_NOT_ASSIGNED) {
              if (j > max) {
                max = j;
              }
              b++;
            }
          }
          for (i = 0; i < GRN_II_MAX_LSEG; i++) {
            j = h->ainfo[i];
            if (j != GRN_II_PSEG_NOT_ASSIGNED) {
              if (j > max) {
                max = j;
              }
              a++;
            }
          }
          GRN_OUTPUT_MAP_OPEN("SUMMARY", 12);
          GRN_OUTPUT_CSTR("flags");
          grn_itoh(h->flags, buf, 8);
          GRN_OUTPUT_STR(buf, 8);
          GRN_OUTPUT_CSTR("max sid");
          GRN_OUTPUT_INT64(h->smax);
          GRN_OUTPUT_CSTR("number of garbage segments");
          GRN_OUTPUT_INT64(g);
          GRN_OUTPUT_CSTR("number of array segments");
          GRN_OUTPUT_INT64(a);
          GRN_OUTPUT_CSTR("max id of array segment");
          GRN_OUTPUT_INT64(h->amax);
          GRN_OUTPUT_CSTR("number of buffer segments");
          GRN_OUTPUT_INT64(b);
          GRN_OUTPUT_CSTR("max id of buffer segment");
          GRN_OUTPUT_INT64(h->bmax);
          GRN_OUTPUT_CSTR("max id of physical segment in use");
          GRN_OUTPUT_INT64(max);
          GRN_OUTPUT_CSTR("number of unmanaged segments");
          GRN_OUTPUT_INT64(h->pnext - a - b - g);
          GRN_OUTPUT_CSTR("total chunk size");
          GRN_OUTPUT_INT64(h->total_chunk_size);
          for (max = 0, i = 0; i < (GRN_II_MAX_CHUNK >> 3); i++) {
            if ((j = h->chunks[i])) {
              int k;
              for (k = 0; k < 8; k++) {
                if ((j & (1 << k))) {
                  max = (i << 3) + j;
                }
              }
            }
          }
          GRN_OUTPUT_CSTR("max id of chunk segments in use");
          GRN_OUTPUT_INT64(max);
          GRN_OUTPUT_CSTR("number of garbage chunk");
          GRN_OUTPUT_ARRAY_OPEN("NGARBAGES", GRN_II_N_CHUNK_VARIATION);
          for (i = 0; i <= GRN_II_N_CHUNK_VARIATION; i++) {
            GRN_OUTPUT_INT64(h->ngarbages[i]);
          }
          GRN_OUTPUT_ARRAY_CLOSE();
          GRN_OUTPUT_MAP_CLOSE();
          const uint32_t n_logical_segments = grn_ii_n_logical_segments(ii);
          for (uint32_t lseg = 0; lseg < n_logical_segments; lseg++) {
            const uint32_t pseg = grn_ii_get_buffer_pseg(ii, lseg);
            if (pseg != GRN_II_PSEG_NOT_ASSIGNED) {
              grn_ii_buffer_check(ctx, ii, lseg);
            }
          }
        }
        GRN_OUTPUT_ARRAY_CLOSE();
      }
      break;
    }
  }
  return NULL;
}

static grn_obj *
proc_truncate(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  const char *target_name;
  int target_name_len;

  target_name_len = GRN_TEXT_LEN(VAR(0));
  if (target_name_len > 0) {
    target_name = GRN_TEXT_VALUE(VAR(0));
  } else {
    target_name_len = GRN_TEXT_LEN(VAR(1));
    if (target_name_len == 0) {
      ERR(GRN_INVALID_ARGUMENT, "[truncate] table name is missing");
      goto exit;
    }
    target_name = GRN_TEXT_VALUE(VAR(1));
  }

  {
    grn_obj *target = grn_ctx_get(ctx, target_name, target_name_len);
    if (!target) {
      ERR(GRN_INVALID_ARGUMENT,
          "[truncate] no such target: <%.*s>",
          target_name_len,
          target_name);
      goto exit;
    }

    switch (target->header.type) {
    case GRN_TABLE_HASH_KEY:
    case GRN_TABLE_PAT_KEY:
    case GRN_TABLE_DAT_KEY:
    case GRN_TABLE_NO_KEY:
      grn_table_truncate(ctx, target);
      break;
    case GRN_COLUMN_FIX_SIZE:
    case GRN_COLUMN_VAR_SIZE:
    case GRN_COLUMN_INDEX:
      grn_column_truncate(ctx, target);
      break;
    default:
      {
        grn_obj buffer;
        GRN_TEXT_INIT(&buffer, 0);
        grn_inspect(ctx, &buffer, target);
        ERR(GRN_INVALID_ARGUMENT,
            "[truncate] not a table nor column object: <%.*s>",
            (int)GRN_TEXT_LEN(&buffer),
            GRN_TEXT_VALUE(&buffer));
        GRN_OBJ_FIN(ctx, &buffer);
      }
      break;
    }
  }

exit:
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static void
list_proc(grn_ctx *ctx,
          grn_proc_type target_proc_type,
          const char *name,
          const char *plural_name)
{
  grn_obj *db;
  grn_table_cursor *cursor;
  grn_obj target_procs;

  db = grn_ctx_db(ctx);
  cursor =
    grn_table_cursor_open(ctx, db, NULL, 0, NULL, 0, 0, -1, GRN_CURSOR_BY_ID);
  if (!cursor) {
    return;
  }

  GRN_PTR_INIT(&target_procs, GRN_OBJ_VECTOR, GRN_ID_NIL);
  {
    grn_id id;

    while ((id = grn_table_cursor_next(ctx, cursor)) != GRN_ID_NIL) {
      grn_obj *obj;
      grn_proc_type proc_type;

      obj = grn_ctx_at(ctx, id);
      if (!obj) {
        continue;
      }

      if (obj->header.type != GRN_PROC) {
        grn_obj_unlink(ctx, obj);
        continue;
      }

      proc_type = grn_proc_get_type(ctx, obj);
      if (proc_type != target_proc_type) {
        grn_obj_unlink(ctx, obj);
        continue;
      }

      GRN_PTR_PUT(ctx, &target_procs, obj);
    }
    grn_table_cursor_close(ctx, cursor);

    {
      int i, n_procs;

      n_procs = GRN_BULK_VSIZE(&target_procs) / sizeof(grn_obj *);
      GRN_OUTPUT_ARRAY_OPEN(plural_name, n_procs);
      for (i = 0; i < n_procs; i++) {
        grn_obj *proc;
        char name[GRN_TABLE_MAX_KEY_SIZE];
        int name_size;

        proc = GRN_PTR_VALUE_AT(&target_procs, i);
        name_size = grn_obj_name(ctx, proc, name, GRN_TABLE_MAX_KEY_SIZE);
        GRN_OUTPUT_MAP_OPEN(name, 1);
        GRN_OUTPUT_CSTR("name");
        GRN_OUTPUT_STR(name, name_size);
        GRN_OUTPUT_MAP_CLOSE();

        grn_obj_unlink(ctx, proc);
      }
      GRN_OUTPUT_ARRAY_CLOSE();
    }

    grn_obj_unlink(ctx, &target_procs);
  }
}

static grn_obj *
proc_tokenizer_list(grn_ctx *ctx,
                    int nargs,
                    grn_obj **args,
                    grn_user_data *user_data)
{
  list_proc(ctx, GRN_PROC_TOKENIZER, "tokenizer", "tokenizers");
  return NULL;
}

static grn_obj *
proc_normalizer_list(grn_ctx *ctx,
                     int nargs,
                     grn_obj **args,
                     grn_user_data *user_data)
{
  list_proc(ctx, GRN_PROC_NORMALIZER, "normalizer", "normalizers");
  return NULL;
}

static grn_obj *
func_rand(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  int val;
  grn_obj *obj;
  if (nargs > 0) {
    int max = GRN_INT32_VALUE(args[0]);
    val = (int)(1.0 * max * rand() / (RAND_MAX + 1.0));
  } else {
    val = rand();
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_INT32, 0))) {
    GRN_INT32_SET(ctx, obj, val);
  }
  return obj;
}

static grn_obj *
func_now(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *obj;
  if ((obj = GRN_PROC_ALLOC(GRN_DB_TIME, 0))) {
    GRN_TIME_NOW(ctx, obj);
  }
  return obj;
}

static grn_inline bool
is_comparable_number_type(grn_id type)
{
  return GRN_DB_INT8 <= type && type <= GRN_DB_TIME;
}

static grn_inline grn_id
larger_number_type(grn_id type1, grn_id type2)
{
  if (type1 == type2) {
    return type1;
  }

  switch (type1) {
  case GRN_DB_BFLOAT16:
    if (type2 == GRN_DB_FLOAT32 || type2 == GRN_DB_FLOAT ||
        type2 == GRN_DB_TIME) {
      return type2;
    } else {
      return type1;
    }
  case GRN_DB_FLOAT32:
    if (type2 == GRN_DB_FLOAT || type2 == GRN_DB_TIME) {
      return type2;
    } else {
      return type1;
    }
  case GRN_DB_FLOAT:
    return type1;
  case GRN_DB_TIME:
    if (type2 == GRN_DB_FLOAT) {
      return type2;
    } else {
      return type1;
    }
  default:
    if (type2 > type1) {
      return type2;
    } else {
      return type1;
    }
  }
}

static grn_inline grn_id
smaller_number_type(grn_id type1, grn_id type2)
{
  if (type1 == type2) {
    return type1;
  }

  switch (type1) {
  case GRN_DB_BFLOAT16:
    if (type2 == GRN_DB_FLOAT32 || type2 == GRN_DB_FLOAT ||
        type2 == GRN_DB_TIME) {
      return type1;
    } else {
      return type2;
    }
  case GRN_DB_FLOAT32:
    if (type2 == GRN_DB_FLOAT || type2 == GRN_DB_TIME) {
      return type1;
    } else {
      return type2;
    }
  case GRN_DB_FLOAT:
    return type1;
  case GRN_DB_TIME:
    if (type2 == GRN_DB_FLOAT) {
      return type1;
    } else {
      return type2;
    }
  default:
    {
      grn_id smaller_number_type;
      if (type2 > type1) {
        smaller_number_type = type2;
      } else {
        smaller_number_type = type1;
      }
      switch (smaller_number_type) {
      case GRN_DB_UINT8:
        return GRN_DB_INT8;
      case GRN_DB_UINT16:
        return GRN_DB_INT16;
      case GRN_DB_UINT32:
        return GRN_DB_INT32;
      case GRN_DB_UINT64:
        return GRN_DB_INT64;
      default:
        return smaller_number_type;
      }
    }
  }
}

static grn_inline bool
is_negative_value(grn_obj *number)
{
  switch (number->header.domain) {
  case GRN_DB_INT8:
    return GRN_INT8_VALUE(number) < 0;
  case GRN_DB_INT16:
    return GRN_INT16_VALUE(number) < 0;
  case GRN_DB_INT32:
    return GRN_INT32_VALUE(number) < 0;
  case GRN_DB_INT64:
    return GRN_INT64_VALUE(number) < 0;
  case GRN_DB_TIME:
    return GRN_TIME_VALUE(number) < 0;
#ifdef GRN_HAVE_BFLOAT16
  case GRN_DB_BFLOAT16:
    return GRN_BFLOAT16_VALUE(number) < 0;
#endif
  case GRN_DB_FLOAT32:
    return GRN_FLOAT32_VALUE(number) < 0;
  case GRN_DB_FLOAT:
    return GRN_FLOAT_VALUE(number) < 0;
  default:
    return false;
  }
}

static grn_inline bool
number_safe_cast(grn_ctx *ctx, grn_obj *src, grn_obj *dest, grn_id type)
{
  grn_obj_reinit(ctx, dest, type, 0);
  if (src->header.domain == type) {
    GRN_TEXT_SET(ctx, dest, GRN_TEXT_VALUE(src), GRN_TEXT_LEN(src));
    return true;
  }

  switch (type) {
  case GRN_DB_UINT8:
    if (is_negative_value(src)) {
      GRN_UINT8_SET(ctx, dest, 0);
      return true;
    }
    break;
  case GRN_DB_UINT16:
    if (is_negative_value(src)) {
      GRN_UINT16_SET(ctx, dest, 0);
      return true;
    }
    break;
  case GRN_DB_UINT32:
    if (is_negative_value(src)) {
      GRN_UINT32_SET(ctx, dest, 0);
      return true;
    }
    break;
  case GRN_DB_UINT64:
    if (is_negative_value(src)) {
      GRN_UINT64_SET(ctx, dest, 0);
      return true;
    }
    break;
  }
  return grn_obj_cast(ctx, src, dest, GRN_FALSE) == GRN_SUCCESS;
}

static grn_inline int
compare_number(grn_ctx *ctx, grn_obj *number1, grn_obj *number2, grn_id type)
{
#define COMPARE_AND_RETURN(type, value1, value2)                               \
  {                                                                            \
    type computed_value1 = value1;                                             \
    type computed_value2 = value2;                                             \
    if (computed_value1 > computed_value2) {                                   \
      return 1;                                                                \
    } else if (computed_value1 < computed_value2) {                            \
      return -1;                                                               \
    } else {                                                                   \
      return 0;                                                                \
    }                                                                          \
  }

  switch (type) {
  case GRN_DB_INT8:
    COMPARE_AND_RETURN(int8_t,
                       GRN_INT8_VALUE(number1),
                       GRN_INT8_VALUE(number2));
  case GRN_DB_UINT8:
    COMPARE_AND_RETURN(uint8_t,
                       GRN_UINT8_VALUE(number1),
                       GRN_UINT8_VALUE(number2));
  case GRN_DB_INT16:
    COMPARE_AND_RETURN(int16_t,
                       GRN_INT16_VALUE(number1),
                       GRN_INT16_VALUE(number2));
  case GRN_DB_UINT16:
    COMPARE_AND_RETURN(uint16_t,
                       GRN_UINT16_VALUE(number1),
                       GRN_UINT16_VALUE(number2));
  case GRN_DB_INT32:
    COMPARE_AND_RETURN(int32_t,
                       GRN_INT32_VALUE(number1),
                       GRN_INT32_VALUE(number2));
  case GRN_DB_UINT32:
    COMPARE_AND_RETURN(uint32_t,
                       GRN_UINT32_VALUE(number1),
                       GRN_UINT32_VALUE(number2));
  case GRN_DB_INT64:
    COMPARE_AND_RETURN(int64_t,
                       GRN_INT64_VALUE(number1),
                       GRN_INT64_VALUE(number2));
  case GRN_DB_UINT64:
    COMPARE_AND_RETURN(uint64_t,
                       GRN_UINT64_VALUE(number1),
                       GRN_UINT64_VALUE(number2));
#ifdef GRN_HAVE_BFLOAT16
  case GRN_DB_BFLOAT16:
    COMPARE_AND_RETURN(grn_bfloat16,
                       GRN_BFLOAT16_VALUE(number1),
                       GRN_BFLOAT16_VALUE(number2));
#endif
  case GRN_DB_FLOAT32:
    COMPARE_AND_RETURN(float,
                       GRN_FLOAT32_VALUE(number1),
                       GRN_FLOAT32_VALUE(number2));
  case GRN_DB_FLOAT:
    COMPARE_AND_RETURN(double,
                       GRN_FLOAT_VALUE(number1),
                       GRN_FLOAT_VALUE(number2));
  case GRN_DB_TIME:
    COMPARE_AND_RETURN(int64_t,
                       GRN_TIME_VALUE(number1),
                       GRN_TIME_VALUE(number2));
  default:
    return 0;
  }

#undef COMPARE_AND_RETURN
}

grn_inline static void
get_number_in_grn_uvector(grn_ctx *ctx,
                          grn_obj *uvector,
                          unsigned int offset,
                          grn_obj *buf)
{
#define GET_UVECTOR_ELEMENT_AS(type)                                           \
  do {                                                                         \
    GRN_##type##_SET(ctx, buf, GRN_##type##_VALUE_AT(uvector, offset));        \
  } while (GRN_FALSE)
  switch (uvector->header.domain) {
  case GRN_DB_BOOL:
    GET_UVECTOR_ELEMENT_AS(BOOL);
    break;
  case GRN_DB_INT8:
    GET_UVECTOR_ELEMENT_AS(INT8);
    break;
  case GRN_DB_UINT8:
    GET_UVECTOR_ELEMENT_AS(UINT8);
    break;
  case GRN_DB_INT16:
    GET_UVECTOR_ELEMENT_AS(INT16);
    break;
  case GRN_DB_UINT16:
    GET_UVECTOR_ELEMENT_AS(UINT16);
    break;
  case GRN_DB_INT32:
    GET_UVECTOR_ELEMENT_AS(INT32);
    break;
  case GRN_DB_UINT32:
    GET_UVECTOR_ELEMENT_AS(UINT32);
    break;
  case GRN_DB_INT64:
    GET_UVECTOR_ELEMENT_AS(INT64);
    break;
  case GRN_DB_UINT64:
    GET_UVECTOR_ELEMENT_AS(UINT64);
    break;
#ifdef GRN_HAVE_BFLOAT16
  case GRN_DB_BFLOAT16:
    GET_UVECTOR_ELEMENT_AS(BFLOAT16);
    break;
#endif
  case GRN_DB_FLOAT32:
    GET_UVECTOR_ELEMENT_AS(FLOAT32);
    break;
  case GRN_DB_FLOAT:
    GET_UVECTOR_ELEMENT_AS(FLOAT);
    break;
  case GRN_DB_TIME:
    GET_UVECTOR_ELEMENT_AS(TIME);
    break;
  default:
    GET_UVECTOR_ELEMENT_AS(RECORD);
    break;
  }
#undef GET_UVECTOR_ELEMENT_AS
}

grn_inline static void
apply_max(grn_ctx *ctx,
          grn_obj *number,
          grn_obj *max,
          grn_obj *casted_number,
          grn_obj *casted_max,
          grn_id cast_type)
{
  grn_id domain = number->header.domain;
  if (!is_comparable_number_type(domain)) {
    return;
  }
  cast_type = larger_number_type(cast_type, domain);
  if (!number_safe_cast(ctx, number, casted_number, cast_type)) {
    return;
  }
  if (max->header.domain == GRN_DB_VOID) {
    grn_obj_reinit(ctx, max, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 max,
                 GRN_TEXT_VALUE(casted_number),
                 GRN_TEXT_LEN(casted_number));
    return;
  }

  if (max->header.domain != cast_type) {
    if (!number_safe_cast(ctx, max, casted_max, cast_type)) {
      return;
    }
    grn_obj_reinit(ctx, max, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 max,
                 GRN_TEXT_VALUE(casted_max),
                 GRN_TEXT_LEN(casted_max));
  }
  if (compare_number(ctx, casted_number, max, cast_type) > 0) {
    grn_obj_reinit(ctx, max, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 max,
                 GRN_TEXT_VALUE(casted_number),
                 GRN_TEXT_LEN(casted_number));
  }
}

static grn_obj *
func_max(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *max;
  grn_id cast_type = GRN_DB_INT8;
  grn_obj casted_max, casted_number;
  int i;

  max = GRN_PROC_ALLOC(GRN_DB_VOID, 0);
  if (!max) {
    return max;
  }

  GRN_VOID_INIT(&casted_max);
  GRN_VOID_INIT(&casted_number);

  for (i = 0; i < nargs; i++) {
    switch (args[i]->header.type) {
    case GRN_BULK:
      apply_max(ctx, args[i], max, &casted_number, &casted_max, cast_type);
      break;
    case GRN_UVECTOR:
      {
        unsigned int j;
        unsigned int n_elements;
        grn_obj number_in_uvector;
        grn_obj *domain;

        domain = grn_ctx_at(ctx, args[i]->header.domain);
        GRN_OBJ_INIT(&number_in_uvector, GRN_BULK, 0, args[i]->header.domain);
        n_elements = grn_uvector_size(ctx, args[i]);
        for (j = 0; j < n_elements; j++) {
          get_number_in_grn_uvector(ctx, args[i], j, &number_in_uvector);
          if (grn_obj_is_table(ctx, domain)) {
            grn_obj_reinit(ctx, &number_in_uvector, domain->header.domain, 0);
            grn_table_get_key2(ctx,
                               domain,
                               GRN_RECORD_VALUE(&number_in_uvector),
                               &number_in_uvector);
          }
          apply_max(ctx,
                    &number_in_uvector,
                    max,
                    &casted_number,
                    &casted_max,
                    cast_type);
        }
        GRN_OBJ_FIN(ctx, &number_in_uvector);
      }
      break;
    default:
      continue;
    }
  }
  GRN_OBJ_FIN(ctx, &casted_max);
  GRN_OBJ_FIN(ctx, &casted_number);

  return max;
}

static void
apply_min(grn_ctx *ctx,
          grn_obj *number,
          grn_obj *min,
          grn_obj *casted_number,
          grn_obj *casted_min,
          grn_id cast_type)
{
  grn_id domain = number->header.domain;
  if (!is_comparable_number_type(domain)) {
    return;
  }
  cast_type = smaller_number_type(cast_type, domain);
  if (!number_safe_cast(ctx, number, casted_number, cast_type)) {
    return;
  }
  if (min->header.domain == GRN_DB_VOID) {
    grn_obj_reinit(ctx, min, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 min,
                 GRN_TEXT_VALUE(casted_number),
                 GRN_TEXT_LEN(casted_number));
    return;
  }

  if (min->header.domain != cast_type) {
    if (!number_safe_cast(ctx, min, casted_min, cast_type)) {
      return;
    }
    grn_obj_reinit(ctx, min, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 min,
                 GRN_TEXT_VALUE(casted_min),
                 GRN_TEXT_LEN(casted_min));
  }
  if (compare_number(ctx, casted_number, min, cast_type) < 0) {
    grn_obj_reinit(ctx, min, cast_type, 0);
    GRN_TEXT_SET(ctx,
                 min,
                 GRN_TEXT_VALUE(casted_number),
                 GRN_TEXT_LEN(casted_number));
  }
}

static grn_obj *
func_min(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *min;
  grn_id cast_type = GRN_DB_INT8;
  grn_obj casted_min, casted_number;
  int i;

  min = GRN_PROC_ALLOC(GRN_DB_VOID, 0);
  if (!min) {
    return min;
  }

  GRN_VOID_INIT(&casted_min);
  GRN_VOID_INIT(&casted_number);
  for (i = 0; i < nargs; i++) {
    switch (args[i]->header.type) {
    case GRN_BULK:
      apply_min(ctx, args[i], min, &casted_number, &casted_min, cast_type);
      break;
    case GRN_UVECTOR:
      {
        unsigned int j;
        unsigned int n_elements;
        grn_obj number_in_uvector;
        grn_obj *domain;

        domain = grn_ctx_at(ctx, args[i]->header.domain);
        GRN_OBJ_INIT(&number_in_uvector, GRN_BULK, 0, args[i]->header.domain);
        n_elements = grn_uvector_size(ctx, args[i]);
        for (j = 0; j < n_elements; j++) {
          get_number_in_grn_uvector(ctx, args[i], j, &number_in_uvector);
          if (grn_obj_is_table(ctx, domain)) {
            grn_obj_reinit(ctx, &number_in_uvector, domain->header.domain, 0);
            grn_table_get_key2(ctx,
                               domain,
                               GRN_RECORD_VALUE(&number_in_uvector),
                               &number_in_uvector);
          }
          apply_min(ctx,
                    &number_in_uvector,
                    min,
                    &casted_number,
                    &casted_min,
                    cast_type);
        }
        GRN_OBJ_FIN(ctx, &number_in_uvector);
      }
      break;
    default:
      continue;
    }
  }
  GRN_OBJ_FIN(ctx, &casted_min);
  GRN_OBJ_FIN(ctx, &casted_number);

  return min;
}

static grn_obj *
func_geo_in_circle(grn_ctx *ctx,
                   int nargs,
                   grn_obj **args,
                   grn_user_data *user_data)
{
  grn_obj *obj;
  bool r = false;
  grn_geo_approximate_type type = GRN_GEO_APPROXIMATE_RECTANGLE;
  switch (nargs) {
  case 4:
    if (grn_geo_resolve_approximate_type(ctx, args[3], &type) != GRN_SUCCESS) {
      break;
    }
    /* fallthru */
  case 3:
    r = grn_geo_in_circle(ctx, args[0], args[1], args[2], type);
    break;
  default:
    break;
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_BOOL, 0))) {
    GRN_BOOL_SET(ctx, obj, r);
  }
  return obj;
}

static grn_obj *
func_geo_in_rectangle(grn_ctx *ctx,
                      int nargs,
                      grn_obj **args,
                      grn_user_data *user_data)
{
  grn_obj *obj;
  bool r = false;
  if (nargs == 3) {
    r = grn_geo_in_rectangle(ctx, args[0], args[1], args[2]);
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_BOOL, 0))) {
    GRN_BOOL_SET(ctx, obj, r);
  }
  return obj;
}

static grn_obj *
func_geo_distance(grn_ctx *ctx,
                  int nargs,
                  grn_obj **args,
                  grn_user_data *user_data)
{
  grn_obj *obj;
  double d = 0.0;
  grn_geo_approximate_type type = GRN_GEO_APPROXIMATE_RECTANGLE;
  switch (nargs) {
  case 3:
    if (grn_geo_resolve_approximate_type(ctx, args[2], &type) != GRN_SUCCESS) {
      break;
    }
    /* fallthru */
  case 2:
    d = grn_geo_distance(ctx, args[0], args[1], type);
    break;
  default:
    break;
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_FLOAT, 0))) {
    GRN_FLOAT_SET(ctx, obj, d);
  }
  return obj;
}

/* deprecated. */
static grn_obj *
func_geo_distance2(grn_ctx *ctx,
                   int nargs,
                   grn_obj **args,
                   grn_user_data *user_data)
{
  grn_obj *obj;
  double d = 0;
  if (nargs == 2) {
    d = grn_geo_distance_sphere(ctx, args[0], args[1]);
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_FLOAT, 0))) {
    GRN_FLOAT_SET(ctx, obj, d);
  }
  return obj;
}

/* deprecated. */
static grn_obj *
func_geo_distance3(grn_ctx *ctx,
                   int nargs,
                   grn_obj **args,
                   grn_user_data *user_data)
{
  grn_obj *obj;
  double d = 0;
  if (nargs == 2) {
    d = grn_geo_distance_ellipsoid(ctx, args[0], args[1]);
  }
  if ((obj = GRN_PROC_ALLOC(GRN_DB_FLOAT, 0))) {
    GRN_FLOAT_SET(ctx, obj, d);
  }
  return obj;
}

static grn_obj *
func_all_records(grn_ctx *ctx,
                 int nargs,
                 grn_obj **args,
                 grn_user_data *user_data)
{
  grn_obj *true_value;
  if ((true_value = GRN_PROC_ALLOC(GRN_DB_BOOL, 0))) {
    GRN_BOOL_SET(ctx, true_value, GRN_TRUE);
  }
  return true_value;
}

static grn_rc
selector_all_records(grn_ctx *ctx,
                     grn_obj *table,
                     grn_obj *index,
                     int nargs,
                     grn_obj **args,
                     grn_obj *res,
                     grn_operator op)
{
  const double score = 1.0;
  return grn_result_set_add_table(ctx, (grn_hash *)res, table, score, op);
}

bool
grn_proc_selector_to_function_data_init(
  grn_ctx *ctx,
  grn_proc_selector_to_function_data *data,
  grn_user_data *user_data)
{
  grn_obj *condition = NULL;
  grn_obj *variable;

  data->table = NULL;
  data->records = NULL;

  data->found = GRN_PROC_ALLOC(GRN_DB_BOOL, 0);
  if (!data->found) {
    return false;
  }
  GRN_BOOL_SET(ctx, data->found, false);

  grn_proc_get_info(ctx, user_data, NULL, NULL, &condition);
  if (!condition) {
    return false;
  }

  variable = grn_expr_get_var_by_offset(ctx, condition, 0);
  if (!variable) {
    return false;
  }

  data->table = grn_ctx_at(ctx, variable->header.domain);
  if (!data->table) {
    return false;
  }

  data->records = grn_table_create(ctx,
                                   NULL,
                                   0,
                                   NULL,
                                   GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                                   data->table,
                                   NULL);
  if (!data->records) {
    return false;
  }

  {
    grn_rset_posinfo pi;
    unsigned int key_size;
    memset(&pi, 0, sizeof(grn_rset_posinfo));
    pi.rid = GRN_RECORD_VALUE(variable);
    key_size = ((grn_hash *)(data->records))->key_size;
    if (grn_table_add(ctx, data->records, &pi, key_size, NULL) == GRN_ID_NIL) {
      return false;
    }
  }

  return true;
}

void
grn_proc_selector_to_function_data_selected(
  grn_ctx *ctx, grn_proc_selector_to_function_data *data)
{
  GRN_BOOL_SET(ctx, data->found, grn_table_size(ctx, data->records) > 0);
}

void
grn_proc_selector_to_function_data_fin(grn_ctx *ctx,
                                       grn_proc_selector_to_function_data *data)
{
  if (data->records) {
    grn_obj_unlink(ctx, data->records);
  }
  if (data->table) {
    grn_obj_unref(ctx, data->table);
  }
}

static bool
sub_filter_pre_filter_accessor_is_available(grn_ctx *ctx,
                                            grn_accessor *accessor)
{
  if (grn_obj_is_scalar_column(ctx, accessor->obj) ||
      grn_obj_is_table(ctx, accessor->obj)) {
    if (accessor->next) {
      return sub_filter_pre_filter_accessor_is_available(ctx, accessor->next);
    } else {
      return true;
    }
  } else if (grn_obj_is_vector_column(ctx, accessor->obj)) {
    if (accessor->next) {
      return sub_filter_pre_filter_accessor_is_available(ctx, accessor->next);
    } else {
      return true;
    }
  } else if (grn_obj_is_index_column(ctx, accessor->obj)) {
    if (accessor->next) {
      return false;
    } else {
      return true;
    }
  } else {
    return false;
  }
}

static void
sub_filter_pre_filter_accessor(grn_ctx *ctx,
                               grn_accessor *accessor,
                               grn_id id,
                               grn_obj *base_res)
{
  if (grn_obj_is_scalar_column(ctx, accessor->obj) ||
      grn_obj_is_table(ctx, accessor->obj)) {
    grn_posting_internal posting = {0};
    posting.weight_float = 1;
    if (grn_obj_is_scalar_column(ctx, accessor->obj)) {
      grn_obj value;
      GRN_RECORD_INIT(&value, 0, DB_OBJ(accessor->obj)->range);
      grn_obj_get_value(ctx, accessor->obj, id, &value);
      if (GRN_BULK_VSIZE(&value) > 0) {
        posting.rid = GRN_RECORD_VALUE(&value);
      }
      GRN_OBJ_FIN(ctx, &value);
    } else {
      grn_table_get_key(ctx, accessor->obj, id, &(posting.rid), sizeof(grn_id));
    }
    if (posting.rid != GRN_ID_NIL) {
      if (accessor->next) {
        sub_filter_pre_filter_accessor(ctx,
                                       accessor->next,
                                       posting.rid,
                                       base_res);
      } else {
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)base_res,
                                 GRN_OP_OR);
      }
    }
  } else if (grn_obj_is_vector_column(ctx, accessor->obj)) {
    grn_posting_internal posting = {0};
    grn_obj values;
    unsigned int i, n;

    posting.weight_float = 1;
    GRN_RECORD_INIT(&values, GRN_OBJ_VECTOR, DB_OBJ(accessor->obj)->range);
    grn_obj_get_value(ctx, accessor->obj, id, &values);
    n = grn_vector_size(ctx, &values);
    for (i = 0; i < n; i++) {
      posting.rid = grn_uvector_get_element(ctx, &values, i, &(posting.weight));
      if (accessor->next) {
        sub_filter_pre_filter_accessor(ctx,
                                       accessor->next,
                                       posting.rid,
                                       base_res);
      } else {
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)base_res,
                                 GRN_OP_OR);
      }
    }
    GRN_OBJ_FIN(ctx, &values);
  } else if (grn_obj_is_index_column(ctx, accessor->obj)) {
    grn_ii_at(ctx,
              (grn_ii *)(accessor->obj),
              id,
              (grn_hash *)base_res,
              GRN_OP_OR);
  }
}

static bool
sub_filter_need_pre_filter(grn_ctx *ctx,
                           grn_obj *res,
                           int32_t pre_filter_threshold)
{
  if (pre_filter_threshold < 0) {
    return true;
  }

  if ((int32_t)(grn_table_size(ctx, res)) <= pre_filter_threshold) {
    return true;
  }

  return false;
}

static bool
sub_filter_pre_filter(grn_ctx *ctx,
                      grn_obj *res,
                      grn_obj *scope,
                      grn_obj *base_res,
                      int32_t pre_filter_threshold)
{
  const char *action = "[sub-filter][pre-filter]";

  if (!sub_filter_need_pre_filter(ctx, res, pre_filter_threshold)) {
    return false;
  }

  if (grn_obj_is_scalar_column(ctx, scope)) {
    grn_posting_internal posting = {0};
    grn_obj value;

    posting.weight_float = 1;
    GRN_RECORD_INIT(&value, 0, grn_obj_get_range(ctx, scope));
    GRN_TABLE_EACH_BEGIN(ctx, res, cursor, id)
    {
      grn_id *matched_id;

      grn_table_cursor_get_key(ctx, cursor, (void **)&matched_id);
      GRN_BULK_REWIND(&value);
      grn_obj_get_value(ctx, scope, *matched_id, &value);
      if (GRN_BULK_VSIZE(&value) > 0) {
        posting.rid = GRN_RECORD_VALUE(&value);
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)base_res,
                                 GRN_OP_OR);
      }
    }
    GRN_TABLE_EACH_END(ctx, cursor);
    GRN_OBJ_FIN(ctx, &value);

    grn_report_column(ctx, action, "[scalar]", scope);
    return true;
  } else if (grn_obj_is_vector_column(ctx, scope)) {
    grn_posting_internal posting = {0};
    grn_obj values;

    posting.weight_float = 1;
    GRN_RECORD_INIT(&values, GRN_OBJ_VECTOR, grn_obj_get_range(ctx, scope));
    GRN_TABLE_EACH_BEGIN(ctx, res, cursor, id)
    {
      grn_id *matched_id;
      unsigned int i, n;

      grn_table_cursor_get_key(ctx, cursor, (void **)&matched_id);
      GRN_BULK_REWIND(&values);
      grn_obj_get_value(ctx, scope, *matched_id, &values);
      n = grn_vector_size(ctx, &values);
      for (i = 0; i < n; i++) {
        posting.rid =
          grn_uvector_get_element(ctx, &values, i, &(posting.weight));
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)base_res,
                                 GRN_OP_OR);
      }
    }
    GRN_TABLE_EACH_END(ctx, cursor);
    GRN_OBJ_FIN(ctx, &values);

    grn_report_column(ctx, action, "[vector]", scope);
    return true;
  } else if (grn_obj_is_index_column(ctx, scope)) {
    GRN_TABLE_EACH_BEGIN(ctx, res, cursor, id)
    {
      grn_id *matched_id;
      grn_table_cursor_get_key(ctx, cursor, (void **)&matched_id);
      grn_ii_at(ctx,
                (grn_ii *)scope,
                *matched_id,
                (grn_hash *)base_res,
                GRN_OP_OR);
    }
    GRN_TABLE_EACH_END(ctx, cursor);

    grn_report_column(ctx, action, "[index]", scope);
    return true;
  } else if (grn_obj_is_accessor(ctx, scope)) {
    grn_accessor *accessor = (grn_accessor *)scope;
    if (!sub_filter_pre_filter_accessor_is_available(ctx, accessor)) {
      return false;
    }
    GRN_TABLE_EACH_BEGIN(ctx, res, cursor, id)
    {
      grn_id *matched_id;
      grn_table_cursor_get_key(ctx, cursor, (void **)&matched_id);
      sub_filter_pre_filter_accessor(ctx, accessor, *matched_id, base_res);
    }
    GRN_TABLE_EACH_END(ctx, cursor);
    grn_report_accessor(ctx, action, "", scope);
    return true;
  } else {
    return false;
  }
}

static grn_rc
run_sub_filter(grn_ctx *ctx,
               grn_obj *table,
               int nargs,
               grn_obj **args,
               grn_obj *res,
               grn_operator op)
{
  const char *tag = "[sub-filter]";
  grn_rc rc = GRN_SUCCESS;
  grn_obj *scope;
  grn_obj *sub_filter_string;
  int32_t pre_filter_threshold = grn_sub_filter_pre_filter_threshold;
  grn_obj *scope_domain = NULL;
  grn_obj *sub_filter = NULL;
  grn_obj *dummy_variable = NULL;

  if (!(nargs >= 2 && nargs <= 3)) {
    ERR(GRN_INVALID_ARGUMENT,
        "%s wrong number of arguments (%d for 2..3)",
        tag,
        nargs);
    rc = ctx->rc;
    goto exit;
  }

  scope = args[0];
  sub_filter_string = args[1];
  if (nargs == 3) {
    grn_proc_options_parse(ctx,
                           args[2],
                           tag,
                           "pre_filter_threshold",
                           GRN_PROC_OPTION_VALUE_INT32,
                           &pre_filter_threshold,
                           NULL);
  }

  switch (scope->header.type) {
  case GRN_ACCESSOR:
  case GRN_COLUMN_FIX_SIZE:
  case GRN_COLUMN_VAR_SIZE:
  case GRN_COLUMN_INDEX:
    break;
  default:
    /* TODO: put inspected the 1st argument to message */
    ERR(GRN_INVALID_ARGUMENT,
        "%s the 1st argument must be column or accessor",
        tag);
    rc = ctx->rc;
    goto exit;
    break;
  }

  scope_domain = grn_ctx_at(ctx, grn_obj_get_range(ctx, scope));

  if (sub_filter_string->header.domain != GRN_DB_TEXT) {
    /* TODO: put inspected the 2nd argument to message */
    ERR(GRN_INVALID_ARGUMENT, "%s the 2nd argument must be String", tag);
    rc = ctx->rc;
    goto exit;
  }
  if (GRN_TEXT_LEN(sub_filter_string) == 0) {
    ERR(GRN_INVALID_ARGUMENT,
        "%s the 2nd argument must not be empty String",
        tag);
    rc = ctx->rc;
    goto exit;
  }

  GRN_EXPR_CREATE_FOR_QUERY(ctx, scope_domain, sub_filter, dummy_variable);
  if (!sub_filter) {
    rc = ctx->rc;
    goto exit;
  }

  grn_expr_parse(ctx,
                 sub_filter,
                 GRN_TEXT_VALUE(sub_filter_string),
                 GRN_TEXT_LEN(sub_filter_string),
                 NULL,
                 GRN_OP_MATCH,
                 GRN_OP_AND,
                 GRN_EXPR_SYNTAX_SCRIPT);
  if (ctx->rc != GRN_SUCCESS) {
    rc = ctx->rc;
    goto exit;
  }

  {
    grn_obj *base_res = NULL;
    grn_operator select_op = GRN_OP_OR;

    base_res = grn_table_create(ctx,
                                NULL,
                                0,
                                NULL,
                                GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                                scope_domain,
                                NULL);
    if (op == GRN_OP_AND && sub_filter_pre_filter(ctx,
                                                  res,
                                                  scope,
                                                  base_res,
                                                  pre_filter_threshold)) {
      if (base_res) {
        void *key = NULL, *value = NULL;
        uint32_t key_size = 0;

        GRN_TABLE_EACH(ctx, base_res, 0, 0, id, &key, &key_size, &value, {
          grn_rset_recinfo *ri = value;
          ri->score = 0;
        });
      }
      select_op = GRN_OP_AND;
    }
    grn_table_select(ctx, scope_domain, sub_filter, base_res, select_op);
    if (scope->header.type == GRN_ACCESSOR) {
      rc = grn_accessor_resolve(ctx, scope, -1, base_res, res, op);
    } else {
      grn_accessor accessor;
      accessor.header.type = GRN_ACCESSOR;
      accessor.obj = scope;
      accessor.action = GRN_ACCESSOR_GET_COLUMN_VALUE;
      accessor.next = NULL;
      rc =
        grn_accessor_resolve(ctx, (grn_obj *)&accessor, -1, base_res, res, op);
    }
    grn_obj_unlink(ctx, base_res);
  }

exit:
  if (scope_domain) {
    grn_obj_unlink(ctx, scope_domain);
  }
  if (sub_filter) {
    grn_obj_unlink(ctx, sub_filter);
  }

  return rc;
}

static grn_rc
selector_sub_filter(grn_ctx *ctx,
                    grn_obj *table,
                    grn_obj *index,
                    int nargs,
                    grn_obj **args,
                    grn_obj *res,
                    grn_operator op)
{
  return run_sub_filter(ctx, table, nargs - 1, args + 1, res, op);
}

static grn_obj *
func_html_untag(grn_ctx *ctx,
                int nargs,
                grn_obj **args,
                grn_user_data *user_data)
{
  grn_obj *html_arg;
  int html_arg_domain;
  grn_obj html;
  grn_obj *text;
  const char *html_raw;
  int i, length;
  bool in_tag = false;

  if (nargs != 1) {
    ERR(GRN_INVALID_ARGUMENT, "HTML is missing");
    return NULL;
  }

  html_arg = args[0];
  html_arg_domain = html_arg->header.domain;
  switch (html_arg_domain) {
  case GRN_DB_SHORT_TEXT:
  case GRN_DB_TEXT:
  case GRN_DB_LONG_TEXT:
    GRN_VALUE_VAR_SIZE_INIT(&html, GRN_OBJ_DO_SHALLOW_COPY, html_arg_domain);
    GRN_TEXT_SET(ctx, &html, GRN_TEXT_VALUE(html_arg), GRN_TEXT_LEN(html_arg));
    break;
  default:
    GRN_TEXT_INIT(&html, 0);
    if (grn_obj_cast(ctx, html_arg, &html, GRN_FALSE)) {
      grn_obj inspected;
      GRN_TEXT_INIT(&inspected, 0);
      grn_inspect(ctx, &inspected, html_arg);
      ERR(GRN_INVALID_ARGUMENT,
          "failed to cast to text: <%.*s>",
          (int)GRN_TEXT_LEN(&inspected),
          GRN_TEXT_VALUE(&inspected));
      GRN_OBJ_FIN(ctx, &inspected);
      GRN_OBJ_FIN(ctx, &html);
      return NULL;
    }
    break;
  }

  text = GRN_PROC_ALLOC(html.header.domain, 0);
  if (!text) {
    GRN_OBJ_FIN(ctx, &html);
    return NULL;
  }

  html_raw = GRN_TEXT_VALUE(&html);
  length = GRN_TEXT_LEN(&html);
  for (i = 0; i < length; i++) {
    switch (html_raw[i]) {
    case '<':
      in_tag = true;
      break;
    case '>':
      if (in_tag) {
        in_tag = false;
      } else {
        GRN_TEXT_PUTC(ctx, text, html_raw[i]);
      }
      break;
    default:
      if (!in_tag) {
        GRN_TEXT_PUTC(ctx, text, html_raw[i]);
      }
      break;
    }
  }

  GRN_OBJ_FIN(ctx, &html);

  return text;
}

static bool
grn_text_equal_cstr(grn_ctx *ctx, grn_obj *text, const char *cstr)
{
  size_t cstr_len = strlen(cstr);
  return (GRN_TEXT_LEN(text) == cstr_len &&
          strncmp(GRN_TEXT_VALUE(text), cstr, cstr_len) == 0);
}

typedef enum {
  BETWEEN_BORDER_INVALID,
  BETWEEN_BORDER_INCLUDE,
  BETWEEN_BORDER_EXCLUDE
} between_border_type;

typedef struct {
  grn_obj *value;
  grn_obj *min;
  grn_obj casted_min;
  between_border_type min_border_type;
  grn_obj *max;
  grn_obj casted_max;
  between_border_type max_border_type;
  int cursor_flags;
  double too_many_index_match_ratio;
  const char *tag;
} between_data;

static void
between_data_init(grn_ctx *ctx, between_data *data)
{
  GRN_VOID_INIT(&(data->casted_min));
  GRN_VOID_INIT(&(data->casted_max));
  data->cursor_flags = 0;
  data->too_many_index_match_ratio = grn_between_too_many_index_match_ratio;
  data->tag = "[between]";
}

static void
between_data_fin(grn_ctx *ctx, between_data *data)
{
  GRN_OBJ_FIN(ctx, &(data->casted_min));
  GRN_OBJ_FIN(ctx, &(data->casted_max));
}

static between_border_type
between_parse_border(grn_ctx *ctx,
                     grn_obj *border,
                     const char *argument_description)
{
  grn_obj inspected;

  /* TODO: support other text types */
  if (border->header.domain == GRN_DB_TEXT) {
    if (grn_text_equal_cstr(ctx, border, "include")) {
      return BETWEEN_BORDER_INCLUDE;
    } else if (grn_text_equal_cstr(ctx, border, "exclude")) {
      return BETWEEN_BORDER_EXCLUDE;
    }
  }

  GRN_TEXT_INIT(&inspected, 0);
  grn_inspect(ctx, &inspected, border);
  ERR(GRN_INVALID_ARGUMENT,
      "between(): %s must be \"include\" or \"exclude\": <%.*s>",
      argument_description,
      (int)GRN_TEXT_LEN(&inspected),
      GRN_TEXT_VALUE(&inspected));
  grn_obj_unlink(ctx, &inspected);

  return BETWEEN_BORDER_INVALID;
}

static grn_rc
between_cast(grn_ctx *ctx,
             grn_obj *source,
             grn_obj *destination,
             grn_id domain,
             const char *target_argument_name)
{
  grn_rc rc;

  GRN_OBJ_INIT(destination, GRN_BULK, 0, domain);
  rc = grn_obj_cast(ctx, source, destination, GRN_FALSE);
  if (rc != GRN_SUCCESS) {
    grn_obj inspected_source;
    grn_obj *domain_object;
    char domain_name[GRN_TABLE_MAX_KEY_SIZE];
    int domain_name_length;

    GRN_TEXT_INIT(&inspected_source, 0);
    grn_inspect(ctx, &inspected_source, source);

    domain_object = grn_ctx_at(ctx, domain);
    domain_name_length =
      grn_obj_name(ctx, domain_object, domain_name, GRN_TABLE_MAX_KEY_SIZE);

    ERR(rc,
        "between(): failed to cast %s: <%.*s> -> <%.*s>",
        target_argument_name,
        (int)GRN_TEXT_LEN(&inspected_source),
        GRN_TEXT_VALUE(&inspected_source),
        domain_name_length,
        domain_name);

    grn_obj_unlink(ctx, &inspected_source);
    grn_obj_unlink(ctx, domain_object);
  }

  return rc;
}

static void
between_parse_options(grn_ctx *ctx, grn_obj *options, between_data *data)
{
  grn_proc_options_parse(ctx,
                         options,
                         data->tag,
                         "too_many_index_match_ratio",
                         GRN_PROC_OPTION_VALUE_DOUBLE,
                         &(data->too_many_index_match_ratio),
                         NULL);
}

static grn_rc
between_parse_args(grn_ctx *ctx, int nargs, grn_obj **args, between_data *data)
{
  grn_rc rc = GRN_SUCCESS;

  data->value = args[0];
  data->min = args[1];
  switch (nargs) {
  case 3:
  case 4:
    data->min_border_type = BETWEEN_BORDER_INCLUDE;
    data->max = args[2];
    data->max_border_type = BETWEEN_BORDER_INCLUDE;
    if (nargs == 4) {
      between_parse_options(ctx, args[3], data);
      if (ctx->rc != GRN_SUCCESS) {
        rc = ctx->rc;
        goto exit;
      }
    }
    break;
  case 5:
  case 6:
    data->min_border_type =
      between_parse_border(ctx, args[2], "the 3rd argument (min_border)");
    if (data->min_border_type == BETWEEN_BORDER_INVALID) {
      rc = ctx->rc;
      goto exit;
    }
    data->max = args[3];
    data->max_border_type =
      between_parse_border(ctx, args[4], "the 5th argument (max_border)");
    if (data->max_border_type == BETWEEN_BORDER_INVALID) {
      rc = ctx->rc;
      goto exit;
    }
    if (nargs == 6) {
      between_parse_options(ctx, args[5], data);
      if (ctx->rc != GRN_SUCCESS) {
        rc = ctx->rc;
        goto exit;
      }
    }
    break;
  default:
    ERR(GRN_INVALID_ARGUMENT,
        "between(): wrong number of arguments (%d for 3 or 5)",
        nargs);
    rc = ctx->rc;
    goto exit;
  }

  {
    grn_id value_type;
    switch (data->value->header.type) {
    case GRN_BULK:
      value_type = data->value->header.domain;
      break;
    case GRN_COLUMN_INDEX:
      {
        grn_obj *domain_object;
        domain_object = grn_ctx_at(ctx, data->value->header.domain);
        value_type = domain_object->header.domain;
      }
      break;
    default:
      value_type = grn_obj_get_range(ctx, data->value);
      break;
    }
    if (value_type != data->min->header.domain) {
      rc = between_cast(ctx, data->min, &data->casted_min, value_type, "min");
      if (rc != GRN_SUCCESS) {
        goto exit;
      }
      data->min = &(data->casted_min);
    }

    if (value_type != data->max->header.domain) {
      rc = between_cast(ctx, data->max, &data->casted_max, value_type, "max");
      if (rc != GRN_SUCCESS) {
        goto exit;
      }
      data->max = &(data->casted_max);
    }
  }

exit:
  return rc;
}

static bool
between_create_expr(grn_ctx *ctx,
                    grn_obj *table,
                    between_data *data,
                    grn_obj **expr,
                    grn_obj **variable)
{
  GRN_EXPR_CREATE_FOR_QUERY(ctx, table, *expr, *variable);
  if (!*expr) {
    return false;
  }
  if (!*variable) {
    return false;
  }

  if (data->value->header.type == GRN_BULK) {
    grn_expr_append_obj(ctx, *expr, data->value, GRN_OP_PUSH, 1);
  } else {
    grn_expr_append_obj(ctx, *expr, data->value, GRN_OP_GET_VALUE, 1);
  }
  grn_expr_append_obj(ctx, *expr, data->min, GRN_OP_PUSH, 1);
  if (data->min_border_type == BETWEEN_BORDER_INCLUDE) {
    grn_expr_append_op(ctx, *expr, GRN_OP_GREATER_EQUAL, 2);
  } else {
    grn_expr_append_op(ctx, *expr, GRN_OP_GREATER, 2);
  }

  if (data->value->header.type == GRN_BULK) {
    grn_expr_append_obj(ctx, *expr, data->value, GRN_OP_PUSH, 1);
  } else {
    grn_expr_append_obj(ctx, *expr, data->value, GRN_OP_GET_VALUE, 1);
  }
  grn_expr_append_obj(ctx, *expr, data->max, GRN_OP_PUSH, 1);
  if (data->max_border_type == BETWEEN_BORDER_INCLUDE) {
    grn_expr_append_op(ctx, *expr, GRN_OP_LESS_EQUAL, 2);
  } else {
    grn_expr_append_op(ctx, *expr, GRN_OP_LESS, 2);
  }

  grn_expr_append_op(ctx, *expr, GRN_OP_AND, 2);

  return true;
}

static grn_obj *
func_between(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *found;
  between_data data;
  grn_obj *condition = NULL;
  grn_obj *variable;
  grn_obj *table = NULL;
  grn_obj *between_expr;
  grn_obj *between_variable;
  grn_obj *result;

  found = GRN_PROC_ALLOC(GRN_DB_BOOL, 0);
  if (!found) {
    return NULL;
  }
  GRN_BOOL_SET(ctx, found, GRN_FALSE);

  grn_proc_get_info(ctx, user_data, NULL, NULL, &condition);
  if (!condition) {
    return found;
  }

  variable = grn_expr_get_var_by_offset(ctx, condition, 0);
  if (!variable) {
    return found;
  }

  between_data_init(ctx, &data);
  rc = between_parse_args(ctx, nargs, args, &data);
  if (rc != GRN_SUCCESS) {
    goto exit;
  }

  table = grn_ctx_at(ctx, variable->header.domain);
  if (!table) {
    goto exit;
  }
  if (!between_create_expr(ctx,
                           table,
                           &data,
                           &between_expr,
                           &between_variable)) {
    goto exit;
  }

  GRN_RECORD_SET(ctx, between_variable, GRN_RECORD_VALUE(variable));
  result = grn_expr_exec(ctx, between_expr, 0);
  if (grn_obj_is_true(ctx, result)) {
    GRN_BOOL_SET(ctx, found, GRN_TRUE);
  }

  grn_obj_unlink(ctx, between_expr);

exit:
  between_data_fin(ctx, &data);
  if (table) {
    grn_obj_unref(ctx, table);
  }

  return found;
}

static bool
selector_between_sequential_search_should_use(grn_ctx *ctx,
                                              grn_obj *table,
                                              grn_obj *index,
                                              grn_obj *index_table,
                                              between_data *data,
                                              grn_obj *res,
                                              grn_operator op)
{
  if (data->too_many_index_match_ratio < 0.0) {
    return false;
  }

  if (op != GRN_OP_AND) {
    return false;
  }

  if (!index) {
    return false;
  }

  if (index->header.flags & GRN_OBJ_WITH_WEIGHT) {
    return false;
  }

  if (data->value->header.type == GRN_COLUMN_INDEX) {
    return false;
  }

  grn_table_cursor *cursor = grn_table_cursor_open(ctx,
                                                   index_table,
                                                   GRN_BULK_HEAD(data->min),
                                                   GRN_BULK_VSIZE(data->min),
                                                   GRN_BULK_HEAD(data->max),
                                                   GRN_BULK_VSIZE(data->max),
                                                   0,
                                                   -1,
                                                   data->cursor_flags);
  if (!cursor) {
    return false;
  }
  uint32_t estimated_size =
    grn_ii_estimate_size_for_lexicon_cursor(ctx, (grn_ii *)index, cursor);
  grn_table_cursor_close(ctx, cursor);

  if (estimated_size == 0) {
    return false;
  }

  uint32_t n_existing_records = grn_table_size(ctx, res);
  double too_many_index_match_threshold =
    (estimated_size * data->too_many_index_match_ratio);
  bool use_sequential_search =
    (n_existing_records < too_many_index_match_threshold);
  if (use_sequential_search) {
    grn_obj reason;
    GRN_TEXT_INIT(&reason, 0);
    grn_text_printf(ctx,
                    &reason,
                    "too many index match: "
                    "%d < %f (%u * %f)",
                    n_existing_records,
                    too_many_index_match_threshold,
                    estimated_size,
                    data->too_many_index_match_ratio);
    GRN_TEXT_PUTC(ctx, &reason, '\0');
    grn_report_index_not_used(ctx,
                              data->tag,
                              "",
                              index,
                              GRN_TEXT_VALUE(&reason));
    GRN_OBJ_FIN(ctx, &reason);
  }
  return use_sequential_search;
}

static grn_rc
selector_between_sequential_search(grn_ctx *ctx,
                                   grn_obj *table,
                                   between_data *data,
                                   grn_obj *res,
                                   grn_operator op)
{
  {
    int offset = 0;
    int limit = -1;
    int flags = 0;
    grn_obj *target_table;
    grn_obj *target_column;
    grn_operator_exec_func *greater;
    grn_operator_exec_func *less;
    grn_table_cursor *cursor;
    grn_id id;
    grn_obj value;

    if (op == GRN_OP_AND) {
      target_table = res;
    } else {
      target_table = table;
    }
    cursor = grn_table_cursor_open(ctx,
                                   target_table,
                                   NULL,
                                   0,
                                   NULL,
                                   0,
                                   offset,
                                   limit,
                                   flags);
    if (!cursor) {
      return ctx->rc;
    }

    if (data->value->header.type == GRN_BULK) {
      target_column = grn_obj_column(ctx,
                                     table,
                                     GRN_TEXT_VALUE(data->value),
                                     GRN_TEXT_LEN(data->value));
    } else {
      target_column = data->value;
    }
    if (data->min_border_type == BETWEEN_BORDER_INCLUDE) {
      greater = grn_operator_exec_greater_equal;
    } else {
      greater = grn_operator_exec_greater;
    }
    if (data->max_border_type == BETWEEN_BORDER_INCLUDE) {
      less = grn_operator_exec_less_equal;
    } else {
      less = grn_operator_exec_less;
    }

    GRN_VOID_INIT(&value);
    while ((id = grn_table_cursor_next(ctx, cursor)) != GRN_ID_NIL) {
      grn_id record_id;

      if (target_table == res) {
        grn_id *key;
        grn_table_cursor_get_key(ctx, cursor, (void **)&key);
        record_id = *key;
      } else {
        record_id = id;
      }

      GRN_BULK_REWIND(&value);
      grn_obj_get_value(ctx, target_column, record_id, &value);
      if (greater(ctx, &value, data->min) && less(ctx, &value, data->max)) {
        grn_posting_internal posting = {0};
        posting.rid = record_id;
        posting.sid = 1;
        posting.pos = 0;
        posting.weight_float = 1;
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)res,
                                 op);
      }
    }

    GRN_OBJ_FIN(ctx, &value);

    if (target_column != data->value &&
        target_column->header.type == GRN_ACCESSOR) {
      grn_obj_unlink(ctx, target_column);
    }

    grn_table_cursor_close(ctx, cursor);

    grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
  }

  return GRN_SUCCESS;
}

static grn_rc
selector_between(grn_ctx *ctx,
                 grn_obj *table,
                 grn_obj *index,
                 int nargs,
                 grn_obj **args,
                 grn_obj *res,
                 grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  int offset = 0;
  int limit = -1;
  between_data data;
  bool use_sequential_search;
  bool index_table_need_unref = false;
  grn_obj *index_table = NULL;
  grn_table_cursor *cursor;

  between_data_init(ctx, &data);
  data.cursor_flags = GRN_CURSOR_ASCENDING | GRN_CURSOR_BY_KEY;
  rc = between_parse_args(ctx, nargs - 1, args + 1, &data);
  if (rc != GRN_SUCCESS) {
    goto exit;
  }

  if (data.min_border_type == BETWEEN_BORDER_EXCLUDE) {
    data.cursor_flags |= GRN_CURSOR_GT;
  }
  if (data.max_border_type == BETWEEN_BORDER_EXCLUDE) {
    data.cursor_flags |= GRN_CURSOR_LT;
  }

  if (data.value->header.type == GRN_COLUMN_INDEX) {
    index = data.value;
  }

  if (index) {
    switch (index->header.type) {
    case GRN_TABLE_NO_KEY:
    case GRN_TABLE_HASH_KEY:
      break;
    case GRN_TABLE_PAT_KEY:
    case GRN_TABLE_DAT_KEY:
      index_table = index;
      index = NULL;
      break;
    default:
      index_table = grn_ctx_at(ctx, index->header.domain);
      if (index_table) {
        index_table_need_unref = true;
      }
      break;
    }
  }

  if (index_table) {
    use_sequential_search =
      selector_between_sequential_search_should_use(ctx,
                                                    table,
                                                    index,
                                                    index_table,
                                                    &data,
                                                    res,
                                                    op);
  } else {
    use_sequential_search = true;
  }
  if (use_sequential_search) {
    rc = selector_between_sequential_search(ctx, table, &data, res, op);
    goto exit;
  }

  cursor = grn_table_cursor_open(ctx,
                                 index_table,
                                 GRN_BULK_HEAD(data.min),
                                 GRN_BULK_VSIZE(data.min),
                                 GRN_BULK_HEAD(data.max),
                                 GRN_BULK_VSIZE(data.max),
                                 offset,
                                 limit,
                                 data.cursor_flags);
  if (!cursor) {
    rc = ctx->rc;
    goto exit;
  }

  if (index) {
    grn_obj *index_cursor =
      grn_index_cursor_open(ctx, cursor, index, GRN_ID_NIL, GRN_ID_MAX, 0);
    if (index_cursor) {
      grn_result_set_add_index_cursor(ctx,
                                      (grn_hash *)res,
                                      index_cursor,
                                      1,
                                      1,
                                      op);
      grn_obj_close(ctx, index_cursor);
    }
  } else {
    grn_result_set_add_table_cursor(ctx, (grn_hash *)res, cursor, 1, op);
  }
  grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
  grn_table_cursor_close(ctx, cursor);

exit:
  between_data_fin(ctx, &data);

  if (index_table_need_unref) {
    grn_obj_unref(ctx, index_table);
  }

  return rc;
}

static grn_obj *
func_in_values(grn_ctx *ctx,
               int nargs,
               grn_obj **args,
               grn_user_data *user_data)
{
  grn_obj *found;
  grn_obj *target_value;
  int i;

  found = GRN_PROC_ALLOC(GRN_DB_BOOL, 0);
  if (!found) {
    return NULL;
  }
  GRN_BOOL_SET(ctx, found, GRN_FALSE);

  if (nargs < 1) {
    ERR(GRN_INVALID_ARGUMENT,
        "in_values(): wrong number of arguments (%d for 1..)",
        nargs);
    return found;
  }

  target_value = args[0];
  for (i = 1; i < nargs; i++) {
    grn_obj *value = args[i];
    grn_bool result;

    result = grn_operator_exec_equal(ctx, target_value, value);
    if (ctx->rc) {
      break;
    }

    if (result) {
      GRN_BOOL_SET(ctx, found, GRN_TRUE);
      break;
    }
  }

  return found;
}

static bool
is_reference_type_column(grn_ctx *ctx, grn_obj *column)
{
  bool is_reference_type;
  grn_obj *range;

  range = grn_ctx_at(ctx, grn_obj_get_range(ctx, column));
  switch (range->header.type) {
  case GRN_TABLE_HASH_KEY:
  case GRN_TABLE_PAT_KEY:
  case GRN_TABLE_DAT_KEY:
    is_reference_type = true;
    break;
  default:
    is_reference_type = false;
    break;
  }
  grn_obj_unlink(ctx, range);

  return is_reference_type;
}

static grn_obj *
selector_in_values_find_source(grn_ctx *ctx, grn_obj *index, grn_obj *res)
{
  grn_id source_id = GRN_ID_NIL;
  grn_obj source_ids;
  unsigned int n_source_ids;

  GRN_UINT32_INIT(&source_ids, GRN_OBJ_VECTOR);
  grn_obj_get_info(ctx, index, GRN_INFO_SOURCE, &source_ids);
  n_source_ids = GRN_BULK_VSIZE(&source_ids) / sizeof(grn_id);
  if (n_source_ids == 1) {
    source_id = GRN_UINT32_VALUE_AT(&source_ids, 0);
  }
  GRN_OBJ_FIN(ctx, &source_ids);

  if (source_id == GRN_ID_NIL) {
    return NULL;
  } else {
    return grn_ctx_at(ctx, source_id);
  }
}

static bool
selector_in_values_sequential_search(grn_ctx *ctx,
                                     grn_obj *table,
                                     grn_obj *index,
                                     int n_values,
                                     grn_obj **values,
                                     grn_obj *res,
                                     grn_operator op,
                                     double too_many_index_match_ratio,
                                     const char *tag)
{
  grn_obj *source;
  int n_existing_records;

  if (too_many_index_match_ratio < 0.0) {
    return false;
  }

  if (op != GRN_OP_AND) {
    return false;
  }

  if (index->header.flags & GRN_OBJ_WITH_WEIGHT) {
    return false;
  }

  n_existing_records = grn_table_size(ctx, res);
  if (n_existing_records == 0) {
    return true;
  }

  source = selector_in_values_find_source(ctx, index, res);
  if (!source) {
    return false;
  }

  if (!is_reference_type_column(ctx, source)) {
    grn_obj_unlink(ctx, source);
    return false;
  }

  uint32_t n_indexed_records = 0;
  {
    grn_obj value_ids;
    int i, n_value_ids;

    {
      grn_id range_id;
      grn_obj *range;

      range_id = grn_obj_get_range(ctx, source);
      range = grn_ctx_at(ctx, range_id);
      if (!range) {
        grn_obj_unlink(ctx, source);
        return false;
      }

      GRN_RECORD_INIT(&value_ids, GRN_OBJ_VECTOR, range_id);
      for (i = 0; i < n_values; i++) {
        grn_obj *value = values[i];
        grn_id value_id;

        value_id =
          grn_table_get(ctx, range, GRN_TEXT_VALUE(value), GRN_TEXT_LEN(value));
        if (value_id == GRN_ID_NIL) {
          continue;
        }
        GRN_RECORD_PUT(ctx, &value_ids, value_id);
      }
      grn_obj_unlink(ctx, range);
    }

    n_value_ids = GRN_BULK_VSIZE(&value_ids) / sizeof(grn_id);
    for (i = 0; i < n_value_ids; i++) {
      grn_id value_id = GRN_RECORD_VALUE_AT(&value_ids, i);
      n_indexed_records += grn_ii_estimate_size(ctx, (grn_ii *)index, value_id);
    }

    /*
     * Same as:
     * ((n_existing_record / n_indexed_records) >
     *  grn_in_values_too_many_index_match_ratio)
     */
    if (n_existing_records > (n_indexed_records * too_many_index_match_ratio)) {
      grn_obj_unlink(ctx, &value_ids);
      grn_obj_unlink(ctx, source);
      return false;
    }

    {
      grn_obj *accessor;
      char local_source_name[GRN_TABLE_MAX_KEY_SIZE];
      int local_source_name_length;

      local_source_name_length =
        grn_column_name(ctx, source, local_source_name, GRN_TABLE_MAX_KEY_SIZE);
      accessor =
        grn_obj_column(ctx, res, local_source_name, local_source_name_length);
      {
        grn_table_cursor *cursor;
        grn_id id;
        grn_obj record_value;

        GRN_VOID_INIT(&record_value);
        cursor = grn_table_cursor_open(ctx,
                                       res,
                                       NULL,
                                       0,
                                       NULL,
                                       0,
                                       0,
                                       -1,
                                       GRN_CURSOR_ASCENDING);
        while ((id = grn_table_cursor_next(ctx, cursor)) != GRN_ID_NIL) {
          grn_id *record_id;
          grn_table_cursor_get_key(ctx, cursor, (void **)&record_id);
          GRN_BULK_REWIND(&record_value);
          grn_obj_get_value(ctx, accessor, id, &record_value);
          for (i = 0; i < n_value_ids; i++) {
            grn_id value_id = GRN_RECORD_VALUE_AT(&value_ids, i);
            switch (record_value.header.type) {
            case GRN_BULK:
              if (value_id == GRN_RECORD_VALUE(&record_value)) {
                grn_posting_internal posting = {0};
                posting.rid = *record_id;
                posting.sid = 1;
                posting.pos = 0;
                posting.weight_float = 1;
                grn_ii_posting_add_float(ctx,
                                         (grn_posting *)(&posting),
                                         (grn_hash *)res,
                                         op);
              }
              break;
            case GRN_UVECTOR:
              {
                int j, n_elements;
                n_elements = GRN_BULK_VSIZE(&record_value) / sizeof(grn_id);
                for (j = 0; j < n_elements; j++) {
                  if (value_id == GRN_RECORD_VALUE_AT(&record_value, j)) {
                    grn_posting_internal posting = {0};
                    posting.rid = *record_id;
                    posting.sid = 1;
                    posting.pos = 0;
                    posting.weight_float = 1;
                    grn_ii_posting_add_float(ctx,
                                             (grn_posting *)(&posting),
                                             (grn_hash *)res,
                                             op);
                  }
                }
              }
              break;
            default:
              break;
            }
          }
        }
        grn_table_cursor_close(ctx, cursor);
        grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
        GRN_OBJ_FIN(ctx, &record_value);
      }
      grn_obj_unlink(ctx, accessor);
    }
    grn_obj_unlink(ctx, &value_ids);
  }
  grn_obj_unlink(ctx, source);

  {
    grn_obj reason;
    GRN_TEXT_INIT(&reason, 0);
    grn_text_printf(ctx,
                    &reason,
                    "too many index match: "
                    "%d < %f (%u * %f)",
                    n_existing_records,
                    (n_indexed_records * too_many_index_match_ratio),
                    n_indexed_records,
                    too_many_index_match_ratio);
    GRN_TEXT_PUTC(ctx, &reason, '\0');
    grn_report_index_not_used(ctx, tag, "", index, GRN_TEXT_VALUE(&reason));
    GRN_OBJ_FIN(ctx, &reason);
  }

  return true;
}

static void
in_values_parse_options(grn_ctx *ctx,
                        grn_obj *options,
                        double *too_many_index_match_ratio,
                        const char *tag)
{
  grn_proc_options_parse(ctx,
                         options,
                         tag,
                         "too_many_index_match_ratio",
                         GRN_PROC_OPTION_VALUE_DOUBLE,
                         too_many_index_match_ratio,
                         NULL);
}

static grn_rc
selector_in_values(grn_ctx *ctx,
                   grn_obj *table,
                   grn_obj *index,
                   int nargs,
                   grn_obj **args,
                   grn_obj *res,
                   grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  int i, n_values;
  grn_obj **values;
  double too_many_index_match_ratio = grn_in_values_too_many_index_match_ratio;
  const char *tag = "[in_values]";

  if (!index) {
    return GRN_INVALID_ARGUMENT;
  }

  if (nargs < 2) {
    ERR(GRN_INVALID_ARGUMENT,
        "%s wrong number of arguments (%d for 1..)",
        tag,
        nargs);
    return ctx->rc;
  }

  n_values = nargs - 2;
  if (args[nargs - 1]->header.type == GRN_TABLE_HASH_KEY) {
    in_values_parse_options(ctx,
                            args[nargs - 1],
                            &too_many_index_match_ratio,
                            tag);
    if (ctx->rc != GRN_SUCCESS) {
      return ctx->rc;
    }
    n_values--;
  }
  values = args + 2;

  if (n_values == 0) {
    return rc;
  }

  if (selector_in_values_sequential_search(ctx,
                                           table,
                                           index,
                                           n_values,
                                           values,
                                           res,
                                           op,
                                           too_many_index_match_ratio,
                                           tag)) {
    return ctx->rc;
  }

  int original_flags = ctx->flags;
  ctx->flags |= GRN_CTX_TEMPORARY_DISABLE_II_RESOLVE_SEL_AND;
  for (i = 0; i < n_values; i++) {
    grn_obj *value = values[i];
    grn_search_optarg search_options;
    memset(&search_options, 0, sizeof(grn_search_optarg));
    search_options.mode = GRN_OP_EXACT;
    search_options.similarity_threshold = 0;
    search_options.max_interval = 0;
    search_options.additional_last_interval = 0;
    search_options.weight_vector = NULL;
    search_options.vector_size = 0;
    search_options.proc = NULL;
    search_options.max_size = 0;
    search_options.scorer = NULL;
    search_options.query_options = NULL;
    search_options.min_interval = NULL;
    search_options.start_position = NULL;
    if (i == n_values - 1) {
      ctx->flags = original_flags;
    }
    rc = grn_obj_search(ctx, index, value, res, op, &search_options);
    if (rc != GRN_SUCCESS) {
      break;
    }
  }

  return rc;
}

static grn_obj *
proc_range_filter(grn_ctx *ctx,
                  int nargs,
                  grn_obj **args,
                  grn_user_data *user_data)
{
  grn_obj *table_name = VAR(0);
  grn_obj *column_name = VAR(1);
  grn_obj *min = VAR(2);
  grn_obj *min_border = VAR(3);
  grn_obj *max = VAR(4);
  grn_obj *max_border = VAR(5);
  grn_obj *offset = VAR(6);
  grn_obj *limit = VAR(7);
  grn_obj *filter = VAR(8);
  grn_obj *output_columns = VAR(9);
  grn_obj *table;
  grn_obj *res = NULL;
  grn_obj *filter_expr = NULL;
  grn_obj *filter_variable = NULL;
  int real_offset;
  int real_limit;

  table =
    grn_ctx_get(ctx, GRN_TEXT_VALUE(table_name), GRN_TEXT_LEN(table_name));
  if (!table) {
    ERR(GRN_INVALID_ARGUMENT,
        "[range_filter] nonexistent table <%.*s>",
        (int)GRN_TEXT_LEN(table_name),
        GRN_TEXT_VALUE(table_name));
    return NULL;
  }

  if (GRN_TEXT_LEN(filter) > 0) {
    GRN_EXPR_CREATE_FOR_QUERY(ctx, table, filter_expr, filter_variable);
    if (!filter_expr) {
      ERR(GRN_INVALID_ARGUMENT, "[range_filter] failed to create expression");
      goto exit;
    }

    grn_expr_parse(ctx,
                   filter_expr,
                   GRN_TEXT_VALUE(filter),
                   GRN_TEXT_LEN(filter),
                   NULL,
                   GRN_OP_MATCH,
                   GRN_OP_AND,
                   GRN_EXPR_SYNTAX_SCRIPT);
    if (ctx->rc != GRN_SUCCESS) {
      goto exit;
    }
  }

  res = grn_table_create(ctx,
                         NULL,
                         0,
                         NULL,
                         GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
                         table,
                         NULL);
  if (!res) {
    ERR(GRN_INVALID_ARGUMENT, "[range_filter] failed to result table");
    goto exit;
  }

  {
    grn_obj int32_value;

    GRN_INT32_INIT(&int32_value, 0);

    if (GRN_TEXT_LEN(offset) > 0) {
      if (grn_obj_cast(ctx, offset, &int32_value, GRN_FALSE) != GRN_SUCCESS) {
        ERR(GRN_INVALID_ARGUMENT,
            "[range_filter] invalid offset format: <%.*s>",
            (int)GRN_TEXT_LEN(offset),
            GRN_TEXT_VALUE(offset));
        GRN_OBJ_FIN(ctx, &int32_value);
        goto exit;
      }
      real_offset = GRN_INT32_VALUE(&int32_value);
    } else {
      real_offset = 0;
    }

    GRN_BULK_REWIND(&int32_value);

    if (GRN_TEXT_LEN(limit) > 0) {
      if (grn_obj_cast(ctx, limit, &int32_value, GRN_FALSE) != GRN_SUCCESS) {
        ERR(GRN_INVALID_ARGUMENT,
            "[range_filter] invalid limit format: <%.*s>",
            (int)GRN_TEXT_LEN(limit),
            GRN_TEXT_VALUE(limit));
        GRN_OBJ_FIN(ctx, &int32_value);
        goto exit;
      }
      real_limit = GRN_INT32_VALUE(&int32_value);
    } else {
      real_limit = GRN_SELECT_DEFAULT_LIMIT;
    }

    GRN_OBJ_FIN(ctx, &int32_value);
  }
  {
    grn_rc rc;
    int original_offset = real_offset;
    int original_limit = real_limit;
    rc = grn_output_range_normalize(ctx,
                                    grn_table_size(ctx, table),
                                    &real_offset,
                                    &real_limit);
    switch (rc) {
    case GRN_TOO_SMALL_OFFSET:
      ERR(GRN_INVALID_ARGUMENT,
          "[range_filter] too small offset: <%d>",
          original_offset);
      goto exit;
    case GRN_TOO_LARGE_OFFSET:
      ERR(GRN_INVALID_ARGUMENT,
          "[range_filter] too large offset: <%d>",
          original_offset);
      goto exit;
    case GRN_TOO_SMALL_LIMIT:
      ERR(GRN_INVALID_ARGUMENT,
          "[range_filter] too small limit: <%d>",
          original_limit);
      goto exit;
    default:
      break;
    }
  }

  if (real_limit != 0) {
    grn_table_sort_key *sort_keys;
    unsigned int n_sort_keys;
    sort_keys = grn_table_sort_key_from_str(ctx,
                                            GRN_TEXT_VALUE(column_name),
                                            GRN_TEXT_LEN(column_name),
                                            table,
                                            &n_sort_keys);
    if (n_sort_keys == 1) {
      grn_table_sort_key *sort_key;
      grn_obj *index;
      int n_indexes;
      grn_operator op = GRN_OP_OR;

      sort_key = &(sort_keys[0]);
      n_indexes =
        grn_column_index(ctx, sort_key->key, GRN_OP_LESS, &index, 1, NULL);
      if (n_indexes > 0) {
        grn_obj *lexicon;
        grn_table_cursor *table_cursor;
        int table_cursor_flags = 0;
        between_border_type min_border_type;
        between_border_type max_border_type;
        grn_obj real_min;
        grn_obj real_max;
        int n_records = 0;
        grn_obj *index_cursor;
        int index_cursor_flags = 0;
        grn_posting *posting;

        lexicon = grn_ctx_at(ctx, index->header.domain);
        if (sort_key->flags & GRN_TABLE_SORT_DESC) {
          table_cursor_flags |= GRN_CURSOR_DESCENDING;
        } else {
          table_cursor_flags |= GRN_CURSOR_ASCENDING;
        }
        if (GRN_TEXT_LEN(min_border) > 0) {
          min_border_type = between_parse_border(ctx, min_border, "min_border");
        } else {
          min_border_type = BETWEEN_BORDER_INCLUDE;
        }
        if (GRN_TEXT_LEN(max_border) > 0) {
          max_border_type = between_parse_border(ctx, max_border, "max_border");
        } else {
          max_border_type = BETWEEN_BORDER_INCLUDE;
        }
        if (min_border_type == BETWEEN_BORDER_EXCLUDE) {
          table_cursor_flags |= GRN_CURSOR_GT;
        }
        if (max_border_type == BETWEEN_BORDER_EXCLUDE) {
          table_cursor_flags |= GRN_CURSOR_LT;
        }
        GRN_OBJ_INIT(&real_min, GRN_BULK, 0, lexicon->header.domain);
        GRN_OBJ_INIT(&real_max, GRN_BULK, 0, lexicon->header.domain);
        if (GRN_TEXT_LEN(min) > 0) {
          grn_obj_cast(ctx, min, &real_min, GRN_FALSE);
        }
        if (GRN_TEXT_LEN(max) > 0) {
          grn_obj_cast(ctx, max, &real_max, GRN_FALSE);
        }
        table_cursor = grn_table_cursor_open(ctx,
                                             lexicon,
                                             GRN_BULK_HEAD(&real_min),
                                             GRN_BULK_VSIZE(&real_min),
                                             GRN_BULK_HEAD(&real_max),
                                             GRN_BULK_VSIZE(&real_max),
                                             0,
                                             -1,
                                             table_cursor_flags);
        index_cursor = grn_index_cursor_open(ctx,
                                             table_cursor,
                                             index,
                                             GRN_ID_NIL,
                                             GRN_ID_NIL,
                                             index_cursor_flags);
        while ((posting = grn_index_cursor_next(ctx, index_cursor, NULL))) {
          bool result_boolean = false;

          if (filter_expr) {
            grn_obj *result;
            GRN_RECORD_SET(ctx, filter_variable, posting->rid);
            result = grn_expr_exec(ctx, filter_expr, 0);
            if (ctx->rc) {
              break;
            }
            result_boolean = grn_obj_is_true(ctx, result);
          } else {
            result_boolean = true;
          }

          if (result_boolean) {
            if (n_records >= real_offset) {
              grn_posting_internal add_posting =
                *((grn_posting_internal *)posting);
              add_posting.weight_float += 1;
              grn_ii_posting_add_float(ctx,
                                       (grn_posting *)(&add_posting),
                                       (grn_hash *)res,
                                       op);
            }
            n_records++;
            if (n_records == real_limit) {
              break;
            }
          }
        }
        grn_obj_unlink(ctx, index_cursor);
        grn_table_cursor_close(ctx, table_cursor);

        GRN_OBJ_FIN(ctx, &real_min);
        GRN_OBJ_FIN(ctx, &real_max);
      }
      grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
    }
    grn_table_sort_key_close(ctx, sort_keys, n_sort_keys);
  }

  if (ctx->rc == GRN_SUCCESS) {
    const char *raw_output_columns;
    int raw_output_columns_len;

    raw_output_columns = GRN_TEXT_VALUE(output_columns);
    raw_output_columns_len = GRN_TEXT_LEN(output_columns);
    if (raw_output_columns_len == 0) {
      if (grn_obj_is_table_with_key(ctx, table)) {
        raw_output_columns = GRN_SELECT_DEFAULT_OUTPUT_COLUMNS_FOR_WITH_KEY;
      } else {
        raw_output_columns = GRN_SELECT_DEFAULT_OUTPUT_COLUMNS_FOR_NO_KEY;
      }
      raw_output_columns_len = strlen(raw_output_columns);
    }
    grn_proc_select_output_columns(ctx,
                                   res,
                                   -1,
                                   real_offset,
                                   real_limit,
                                   raw_output_columns,
                                   raw_output_columns_len,
                                   filter_expr);
  }

exit:
  if (filter_expr) {
    grn_obj_unlink(ctx, filter_expr);
  }
  if (res) {
    grn_obj_unlink(ctx, res);
  }

  return NULL;
}

static grn_obj *
proc_request_cancel(grn_ctx *ctx,
                    int nargs,
                    grn_obj **args,
                    grn_user_data *user_data)
{
  grn_obj *id = VAR(0);
  bool canceled;

  if (GRN_TEXT_LEN(id) == 0) {
    ERR(GRN_INVALID_ARGUMENT, "[request_cancel] ID is missing");
    return NULL;
  }

  canceled = grn_request_canceler_cancel(GRN_TEXT_VALUE(id), GRN_TEXT_LEN(id));

  GRN_OUTPUT_MAP_OPEN("result", 2);
  GRN_OUTPUT_CSTR("id");
  GRN_OUTPUT_STR(GRN_TEXT_VALUE(id), GRN_TEXT_LEN(id));
  GRN_OUTPUT_CSTR("canceled");
  GRN_OUTPUT_BOOL(canceled);
  GRN_OUTPUT_MAP_CLOSE();

  return NULL;
}

static grn_obj *
proc_plugin_register(grn_ctx *ctx,
                     int nargs,
                     grn_obj **args,
                     grn_user_data *user_data)
{
  if (GRN_TEXT_LEN(VAR(0))) {
    const char *name;
    GRN_TEXT_PUTC(ctx, VAR(0), '\0');
    name = GRN_TEXT_VALUE(VAR(0));
    grn_plugin_register(ctx, name);
  } else {
    ERR(GRN_INVALID_ARGUMENT, "[plugin_register] name is missing");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_obj *
proc_plugin_unregister(grn_ctx *ctx,
                       int nargs,
                       grn_obj **args,
                       grn_user_data *user_data)
{
  if (GRN_TEXT_LEN(VAR(0))) {
    const char *name;
    GRN_TEXT_PUTC(ctx, VAR(0), '\0');
    name = GRN_TEXT_VALUE(VAR(0));
    grn_plugin_unregister(ctx, name);
  } else {
    ERR(GRN_INVALID_ARGUMENT, "[plugin_unregister] name is missing");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_obj *
proc_io_flush(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *db = grn_ctx_db(ctx);

  grn_raw_string target_name;
  target_name.value = grn_plugin_proc_get_var_string(ctx,
                                                     user_data,
                                                     "target_name",
                                                     -1,
                                                     &(target_name.length));
  grn_obj *target;
  if (target_name.length > 0) {
    target = grn_ctx_get(ctx, target_name.value, target_name.length);
    if (!target) {
      ERR(GRN_INVALID_ARGUMENT,
          "[io_flush] unknown target: <%.*s>",
          (int)target_name.length,
          target_name.value);
      GRN_OUTPUT_BOOL(GRN_FALSE);
      return NULL;
    }
  } else {
    target = db;
  }

  grn_raw_string recursive;
  recursive.value = grn_plugin_proc_get_var_string(ctx,
                                                   user_data,
                                                   "recursive",
                                                   -1,
                                                   &(recursive.length));
  bool is_only_opened =
    grn_plugin_proc_get_var_bool(ctx, user_data, "only_opened", -1, false);
  grn_rc rc = GRN_SUCCESS;
  if (is_only_opened) {
    rc = grn_obj_flush_only_opened(ctx, target);
  } else {
    if (GRN_RAW_STRING_EQUAL_CSTRING(recursive, "dependent")) {
      rc = grn_obj_flush_recursive_dependent(ctx, target);
    } else if (GRN_RAW_STRING_EQUAL_CSTRING(recursive, "no")) {
      rc = grn_obj_flush(ctx, target);
    } else {
      rc = grn_obj_flush_recursive(ctx, target);
    }
  }

  if (target->header.type != GRN_DB) {
    grn_obj_unlink(ctx, target);
  }

  GRN_OUTPUT_BOOL(rc == GRN_SUCCESS);

  return NULL;
}

static grn_obj *
proc_database_unmap(grn_ctx *ctx,
                    int nargs,
                    grn_obj **args,
                    grn_user_data *user_data)
{
  grn_rc rc;
  uint32_t current_limit;

  current_limit = grn_thread_get_limit_with_ctx(ctx);
  if (current_limit != 1) {
    ERR(GRN_OPERATION_NOT_PERMITTED,
        "[database_unmap] the max number of threads must be 1: <%u>",
        current_limit);
    GRN_OUTPUT_BOOL(GRN_FALSE);
    return NULL;
  }

  rc = grn_db_unmap(ctx, grn_ctx_db(ctx));
  GRN_OUTPUT_BOOL(rc == GRN_SUCCESS);

  return NULL;
}

static grn_obj *
proc_reindex(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *target_name;
  grn_obj *target;

  target_name = VAR(0);
  if (GRN_TEXT_LEN(target_name) == 0) {
    target = grn_ctx_db(ctx);
  } else {
    target =
      grn_ctx_get(ctx, GRN_TEXT_VALUE(target_name), GRN_TEXT_LEN(target_name));
    if (!target) {
      ERR(GRN_INVALID_ARGUMENT,
          "[reindex] nonexistent target: <%.*s>",
          (int)GRN_TEXT_LEN(target_name),
          GRN_TEXT_VALUE(target_name));
      GRN_OUTPUT_BOOL(GRN_FALSE);
      return NULL;
    }
  }

  grn_obj_reindex(ctx, target);

  GRN_OUTPUT_BOOL(ctx->rc == GRN_SUCCESS);

  return NULL;
}

static grn_rc
selector_prefix_rk_search_key(
  grn_ctx *ctx, grn_obj *table, grn_obj *query, grn_obj *res, grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;

  if (table->header.type != GRN_TABLE_PAT_KEY) {
    grn_obj inspected_table;
    GRN_TEXT_INIT(&inspected_table, 0);
    grn_inspect(ctx, &inspected_table, table);
    ERR(GRN_INVALID_ARGUMENT,
        "prefix_rk_serach(): table of _key must TABLE_PAT_KEY: %.*s",
        (int)GRN_TEXT_LEN(&inspected_table),
        GRN_TEXT_VALUE(&inspected_table));
    rc = ctx->rc;
    GRN_OBJ_FIN(ctx, &inspected_table);
    goto exit;
  }

  GRN_TABLE_EACH_BEGIN_MIN(ctx,
                           table,
                           cursor,
                           id,
                           GRN_TEXT_VALUE(query),
                           GRN_TEXT_LEN(query),
                           GRN_CURSOR_PREFIX | GRN_CURSOR_RK)
  {
    grn_posting_internal posting = {0};
    posting.rid = id;
    posting.sid = 1;
    posting.pos = 0;
    posting.weight_float = 1;
    grn_ii_posting_add_float(ctx,
                             (grn_posting *)(&posting),
                             (grn_hash *)res,
                             op);
  }
  GRN_TABLE_EACH_END(ctx, cursor);
  grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);

exit:
  return rc;
}

static grn_rc
selector_prefix_rk_search_index(
  grn_ctx *ctx, grn_obj *index, grn_obj *query, grn_obj *res, grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *table;

  table = grn_column_table(ctx, index);

  GRN_TABLE_EACH_BEGIN_MIN(ctx,
                           table,
                           cursor,
                           id,
                           GRN_TEXT_VALUE(query),
                           GRN_TEXT_LEN(query),
                           GRN_CURSOR_PREFIX | GRN_CURSOR_RK)
  {
    grn_ii_at(ctx, (grn_ii *)index, id, (grn_hash *)res, op);
  }
  GRN_TABLE_EACH_END(ctx, cursor);
  grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);

  return rc;
}

static grn_rc
selector_prefix_rk_search(grn_ctx *ctx,
                          grn_obj *table,
                          grn_obj *index,
                          int nargs,
                          grn_obj **args,
                          grn_obj *res,
                          grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *column;
  grn_obj *query;

  if ((nargs - 1) != 2) {
    ERR(GRN_INVALID_ARGUMENT,
        "prefix_rk_serach(): wrong number of arguments (%d for 2)",
        nargs - 1);
    return ctx->rc;
  }

  column = args[1];
  query = args[2];

  if (grn_obj_is_table(ctx, index)) {
    rc = selector_prefix_rk_search_key(ctx, index, query, res, op);
  } else {
    rc = selector_prefix_rk_search_index(ctx, index, query, res, op);
  }
  return rc;
}

#define DEF_VAR(v, name_str)                                                   \
  do {                                                                         \
    (v).name = (name_str);                                                     \
    (v).name_size = GRN_STRLEN(name_str);                                      \
    GRN_TEXT_INIT(&(v).value, 0);                                              \
  } while (0)

#define DEF_COMMAND(name, func, nvars, vars)                                   \
  (grn_proc_create(ctx,                                                        \
                   (name),                                                     \
                   (sizeof(name) - 1),                                         \
                   GRN_PROC_COMMAND,                                           \
                   (func),                                                     \
                   NULL,                                                       \
                   NULL,                                                       \
                   (nvars),                                                    \
                   (vars)))

void
grn_db_init_builtin_commands(grn_ctx *ctx)
{
  grn_expr_var vars[10];

  grn_proc_init_define_selector(ctx);
  grn_proc_init_select(ctx);

  DEF_VAR(vars[0], "values");
  DEF_VAR(vars[1], "table");
  DEF_VAR(vars[2], "columns");
  DEF_VAR(vars[3], "ifexists");
  DEF_VAR(vars[4], "input_type");
  DEF_VAR(vars[5], "each");
  DEF_VAR(vars[6], "output_ids");
  DEF_VAR(vars[7], "output_errors");
  DEF_VAR(vars[8], "lock_table");
  DEF_COMMAND("load", proc_load, 9, vars);

  DEF_COMMAND("status", proc_status, 0, vars);

  grn_proc_init_table_list(ctx);

  grn_proc_init_column_list(ctx);

  grn_proc_init_table_create(ctx);

  grn_proc_init_table_remove(ctx);

  grn_proc_init_table_rename(ctx);

  grn_proc_init_column_create(ctx);

  grn_proc_init_column_remove(ctx);

  grn_proc_init_column_rename(ctx);

  DEF_VAR(vars[0], "path");
  DEF_COMMAND(GRN_EXPR_MISSING_NAME, proc_missing, 1, vars);

  DEF_COMMAND("quit", proc_quit, 0, vars);

  DEF_VAR(vars[0], "mode");
  DEF_COMMAND("shutdown", proc_shutdown, 1, vars);

  grn_proc_init_clearlock(ctx);
  grn_proc_init_lock_clear(ctx);

  DEF_VAR(vars[0], "target_name");
  DEF_VAR(vars[1], "threshold");
  DEF_COMMAND("defrag", proc_defrag, 2, vars);

  grn_proc_init_log_level(ctx);
  grn_proc_init_log_put(ctx);
  grn_proc_init_log_reopen(ctx);

  DEF_VAR(vars[0], "table");
  DEF_VAR(vars[1], "key");
  DEF_VAR(vars[2], "id");
  DEF_VAR(vars[3], "filter");
  DEF_COMMAND("delete", proc_delete, 4, vars);

  DEF_VAR(vars[0], "max");
  DEF_COMMAND("cache_limit", proc_cache_limit, 1, vars);

  grn_proc_init_dump(ctx);

  /* Deprecated. Use "plugin_register" instead. */
  DEF_VAR(vars[0], "path");
  DEF_COMMAND("register", proc_register, 1, vars);

  DEF_VAR(vars[0], "obj");
  DEF_COMMAND("check", proc_check, 1, vars);

  DEF_VAR(vars[0], "target_name");
  DEF_VAR(vars[1], "table");
  DEF_COMMAND("truncate", proc_truncate, 2, vars);

  grn_proc_init_normalize(ctx);

  grn_proc_init_tokenize(ctx);
  grn_proc_init_table_tokenize(ctx);

  DEF_COMMAND("tokenizer_list", proc_tokenizer_list, 0, vars);

  DEF_COMMAND("normalizer_list", proc_normalizer_list, 0, vars);

  {
    grn_obj *proc;
    proc = grn_proc_create(ctx,
                           "rand",
                           -1,
                           GRN_PROC_FUNCTION,
                           func_rand,
                           NULL,
                           NULL,
                           0,
                           NULL);
    grn_proc_set_is_stable(ctx, proc, GRN_FALSE);
  }

  {
    grn_obj *proc;
    proc = grn_proc_create(ctx,
                           "now",
                           -1,
                           GRN_PROC_FUNCTION,
                           func_now,
                           NULL,
                           NULL,
                           0,
                           NULL);
    grn_proc_set_is_stable(ctx, proc, GRN_FALSE);
  }

  grn_proc_create(ctx,
                  "max",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_max,
                  NULL,
                  NULL,
                  0,
                  NULL);
  grn_proc_create(ctx,
                  "min",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_min,
                  NULL,
                  NULL,
                  0,
                  NULL);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "geo_in_circle",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    func_geo_in_circle,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, grn_selector_geo_in_circle);
    /* We may need GRN_OP_GEO_IN_CIRCLE. */
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_MATCH);

    selector_proc = grn_proc_create(ctx,
                                    "geo_in_rectangle",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    func_geo_in_rectangle,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, grn_selector_geo_in_rectangle);
    /* We may need GRN_OP_GEO_IN_RECTANGLE. */
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_MATCH);
  }

  grn_proc_create(ctx,
                  "geo_distance",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_geo_distance,
                  NULL,
                  NULL,
                  0,
                  NULL);

  /* deprecated. */
  grn_proc_create(ctx,
                  "geo_distance2",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_geo_distance2,
                  NULL,
                  NULL,
                  0,
                  NULL);

  /* deprecated. */
  grn_proc_create(ctx,
                  "geo_distance3",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_geo_distance3,
                  NULL,
                  NULL,
                  0,
                  NULL);

  grn_proc_init_edit_distance(ctx);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "all_records",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    func_all_records,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_all_records);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_NOP);
  }

  /* experimental */
  grn_proc_init_snippet_html(ctx);

  grn_proc_init_query(ctx);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "sub_filter",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    NULL,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_sub_filter);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_NOP);
  }

  grn_proc_create(ctx,
                  "html_untag",
                  -1,
                  GRN_PROC_FUNCTION,
                  func_html_untag,
                  NULL,
                  NULL,
                  0,
                  NULL);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "between",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    func_between,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_between);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_LESS);
  }

  grn_proc_init_highlight_html(ctx);
  grn_proc_init_highlight_full(ctx);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "in_values",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    func_in_values,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_in_values);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_EQUAL);
  }

  DEF_VAR(vars[0], "table");
  DEF_VAR(vars[1], "column");
  DEF_VAR(vars[2], "min");
  DEF_VAR(vars[3], "min_border");
  DEF_VAR(vars[4], "max");
  DEF_VAR(vars[5], "max_border");
  DEF_VAR(vars[6], "offset");
  DEF_VAR(vars[7], "limit");
  DEF_VAR(vars[8], "filter");
  DEF_VAR(vars[9], "output_columns");
  DEF_COMMAND("range_filter", proc_range_filter, 10, vars);

  DEF_VAR(vars[0], "id");
  DEF_COMMAND("request_cancel", proc_request_cancel, 1, vars);

  DEF_VAR(vars[0], "name");
  DEF_COMMAND("plugin_register", proc_plugin_register, 1, vars);

  DEF_VAR(vars[0], "name");
  DEF_COMMAND("plugin_unregister", proc_plugin_unregister, 1, vars);

  DEF_VAR(vars[0], "target_name");
  DEF_VAR(vars[1], "recursive");
  DEF_VAR(vars[2], "only_opened");
  DEF_COMMAND("io_flush", proc_io_flush, 3, vars);

  grn_proc_init_object_exist(ctx);

  grn_proc_init_thread_limit(ctx);

  DEF_COMMAND("database_unmap", proc_database_unmap, 0, vars);

  grn_proc_init_column_copy(ctx);

  grn_proc_init_schema(ctx);

  DEF_VAR(vars[0], "target_name");
  DEF_COMMAND("reindex", proc_reindex, 1, vars);

  {
    grn_obj *selector_proc;

    selector_proc = grn_proc_create(ctx,
                                    "prefix_rk_search",
                                    -1,
                                    GRN_PROC_FUNCTION,
                                    NULL,
                                    NULL,
                                    NULL,
                                    0,
                                    NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_prefix_rk_search);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_PREFIX);
  }

  grn_proc_init_config_get(ctx);
  grn_proc_init_config_set(ctx);
  grn_proc_init_config_delete(ctx);

  grn_proc_init_lock_acquire(ctx);
  grn_proc_init_lock_release(ctx);

  grn_proc_init_object_inspect(ctx);

  grn_proc_init_fuzzy_search(ctx);

  grn_proc_init_object_remove(ctx);

  grn_proc_init_snippet(ctx);
  grn_proc_init_highlight(ctx);

  grn_proc_init_query_expand(ctx);

  grn_proc_init_object_list(ctx);

  grn_proc_init_table_copy(ctx);

  grn_proc_init_in_records(ctx);

  grn_proc_init_query_log_flags_get(ctx);
  grn_proc_init_query_log_flags_set(ctx);
  grn_proc_init_query_log_flags_add(ctx);
  grn_proc_init_query_log_flags_remove(ctx);

  grn_proc_init_cast_loose(ctx);

  grn_proc_init_index_column_diff(ctx);

  grn_proc_init_object_set_visibility(ctx);

  grn_proc_init_reference_acquire(ctx);
  grn_proc_init_reference_release(ctx);

  grn_proc_init_query_parallel_or(ctx);

  grn_proc_init_object_warm(ctx);

  grn_proc_init_table_create_similar(ctx);

  grn_proc_init_column_create_similar(ctx);

  grn_proc_init_thread_dump(ctx);

  grn_proc_init_escalate(ctx);

  grn_proc_init_sleep(ctx);

  grn_proc_init_distance_cosine(ctx);
  grn_proc_init_distance_inner_product(ctx);
  grn_proc_init_distance_l1_norm(ctx);
  grn_proc_init_distance_l2_norm_squared(ctx);
}
