/* -*- c-basic-offset: 2 -*- */
/*
  Copyright(C) 2009-2018 Brazil
  Copyright(C) 2018-2020 Sutou Kouhei <kou@clear-code.com>

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License 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_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 */

#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 unsigned int 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_atoui(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_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)
{
  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 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_id id = grn_hash_get(ctx,
                             (grn_hash *)options,
                             name, strlen(name),
                             &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 *operator = va_arg(args, grn_operator *);
        if (id != GRN_ID_NIL) {
          GRN_RECORD_PUT(ctx, &used_ids, id);
          *operator = grn_proc_get_value_operator(ctx,
                                                  value,
                                                  *operator,
                                                  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_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, 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;
    default :
      GRN_PLUGIN_ERROR(ctx,
                       GRN_INVALID_ARGUMENT,
                       "%s[%s] invalid option value type: %d",
                       func_tag,
                       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; 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);
      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);
    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, &message);
    GRN_OBJ_FIN(ctx, &inspected);
  }

exit :
  GRN_OBJ_FIN(ctx, &used_ids);
  GRN_OBJ_FIN(ctx, &func_tag_buffer);
  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,
                                                  GRN_FALSE);
  input.output_errors = grn_plugin_proc_get_var_bool(ctx,
                                                     user_data,
                                                     "output_errors", -1,
                                                     GRN_FALSE);
  input.lock_table = grn_plugin_proc_get_var_bool(ctx,
                                                  user_data,
                                                  "lock_table", -1,
                                                  GRN_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.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.rc != GRN_SUCCESS) {
    ctx->rc = ctx->impl->loader.rc;
    grn_strcpy(ctx->errbuf, GRN_CTX_MSGSIZE, ctx->impl->loader.errbuf);
  }
  {
    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;
  const int n_elements = 11;

  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_INT32(grn_alloc_count());
  GRN_OUTPUT_CSTR("starttime");
  GRN_OUTPUT_INT32(grn_starttime.tv_sec);
  GRN_OUTPUT_CSTR("start_time");
  GRN_OUTPUT_INT32(grn_starttime.tv_sec);
  GRN_OUTPUT_CSTR("uptime");
  GRN_OUTPUT_INT32(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_MAP_CLOSE();

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

  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_obj *
proc_log_level(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *level_name = VAR(0);
  if (GRN_TEXT_LEN(level_name) > 0) {
    grn_log_level max_level;
    GRN_TEXT_PUTC(ctx, level_name, '\0');
    if (grn_log_level_parse(GRN_TEXT_VALUE(level_name), &max_level)) {
      grn_logger_set_max_level(ctx, max_level);
    } else {
      ERR(GRN_INVALID_ARGUMENT,
          "invalid log level: <%s>", GRN_TEXT_VALUE(level_name));
    }
  } else {
    ERR(GRN_INVALID_ARGUMENT, "log level is missing");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_obj *
proc_log_put(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *level_name = VAR(0);
  grn_obj *message = VAR(1);
  if (GRN_TEXT_LEN(level_name) > 0) {
    grn_log_level level;
    GRN_TEXT_PUTC(ctx, level_name, '\0');
    if (grn_log_level_parse(GRN_TEXT_VALUE(level_name), &level)) {
      GRN_LOG(ctx, level, "%.*s",
              (int)GRN_TEXT_LEN(message),
              GRN_TEXT_VALUE(message));
    } else {
      ERR(GRN_INVALID_ARGUMENT,
          "invalid log level: <%s>", GRN_TEXT_VALUE(level_name));
    }
  } else {
    ERR(GRN_INVALID_ARGUMENT, "log level is missing");
  }
  GRN_OUTPUT_BOOL(!ctx->rc);
  return NULL;
}

static grn_obj *
proc_log_reopen(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_log_reopen(ctx);
  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;
}

grn_bool
grn_proc_option_value_bool(grn_ctx *ctx,
                           grn_obj *option,
                           grn_bool default_value)
{
  const char *value;
  size_t value_length;

  if (!option) {
    return default_value;
  }

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

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

  if (value_length == strlen("yes") &&
      strncmp(value, "yes", value_length) == 0) {
    return GRN_TRUE;
  } else if (value_length == strlen("no") &&
             strncmp(value, "no", value_length) == 0) {
    return GRN_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;
  }
}

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

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("*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\", "
                     "\"*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, "-") ||
             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, "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 (((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 on of them: "
                     "["
                     "\"ENABLE_TOKENIZED_DELIMITER\", "
                     "\"NONE\""
                     "]: <%.*s>",
                     tag,
                     (int)(names_end - names), names);
    return default_flags;
#undef CHECK_FLAG
  }

  return flags;
}

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 grn_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_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_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 grn_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;
  case GRN_DB_FLOAT32 :
    return GRN_FLOAT32_VALUE(number) < 0;
  case GRN_DB_FLOAT :
    return GRN_FLOAT_VALUE(number) < 0;
  default :
    return GRN_FALSE;
  }
}

static grn_inline grn_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 GRN_TRUE;
  }

  switch (type) {
  case GRN_DB_UINT8 :
    if (is_negative_value(src)) {
      GRN_UINT8_SET(ctx, dest, 0);
      return GRN_TRUE;
    }
    break;
  case GRN_DB_UINT16 :
    if (is_negative_value(src)) {
      GRN_UINT16_SET(ctx, dest, 0);
      return GRN_TRUE;
    }
    break;
  case GRN_DB_UINT32 :
    if (is_negative_value(src)) {
      GRN_UINT32_SET(ctx, dest, 0);
      return GRN_TRUE;
    }
    break;
  case GRN_DB_UINT64 :
    if (is_negative_value(src)) {
      GRN_UINT64_SET(ctx, dest, 0);
      return GRN_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));
  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;
  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;
  grn_bool r = GRN_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;
  grn_bool r = GRN_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);
}

typedef struct {
  grn_obj *found;
  grn_obj *table;
  grn_obj *records;
} selector_to_function_data;

static grn_bool
selector_to_function_data_init(grn_ctx *ctx,
                               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 GRN_FALSE;
  }
  GRN_BOOL_SET(ctx, data->found, GRN_FALSE);

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

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

  data->table = grn_ctx_at(ctx, variable->header.domain);
  if (!data->table) {
    return GRN_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 GRN_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 GRN_FALSE;
    }
  }

  return GRN_TRUE;
}

static void
selector_to_function_data_selected(grn_ctx *ctx,
                                   selector_to_function_data *data)
{
  GRN_BOOL_SET(ctx, data->found, grn_table_size(ctx, data->records) > 0);
}

static void
selector_to_function_data_fin(grn_ctx *ctx,
                              selector_to_function_data *data)
{
  if (data->records) {
    grn_obj_unlink(ctx, data->records);
  }
  if (data->table) {
    grn_obj_unref(ctx, data->table);
  }
}

static grn_rc
run_query(grn_ctx *ctx,
          grn_obj *table,
          int nargs,
          grn_obj **args,
          grn_obj *res,
          grn_operator op,
          grn_selector_data *selector_data)
{
  grn_rc rc = GRN_SUCCESS;
  const char *tag = "[query]";
  grn_obj *match_columns_string;
  grn_obj *query;
  grn_obj *query_expander_name = NULL;
  grn_operator default_mode = GRN_OP_MATCH;
  grn_operator default_operator = GRN_OP_AND;
  grn_expr_flags flags = GRN_EXPR_SYNTAX_QUERY;
  grn_expr_flags flags_specified = -1;
  grn_obj *match_columns = NULL;
  grn_obj *condition = NULL;
  grn_obj *dummy_variable;

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

  match_columns_string = args[0];
  query = args[1];
  if (nargs > 2) {
    grn_obj *options = args[2];

    switch (options->header.type) {
    case GRN_BULK :
      query_expander_name = options;
      break;
    case GRN_TABLE_HASH_KEY :
#define OPTIONS                                                         \
      "expander",                                                       \
        GRN_PROC_OPTION_VALUE_RAW,                                      \
        &query_expander_name,                                           \
        "default_mode",                                                 \
        GRN_PROC_OPTION_VALUE_MODE,                                     \
        &default_mode,                                                  \
        "default_operator",                                             \
        GRN_PROC_OPTION_VALUE_OPERATOR,                                 \
        &default_operator,                                              \
        "flags",                                                        \
        GRN_PROC_OPTION_VALUE_EXPR_FLAGS,                               \
        &flags_specified
      if (selector_data) {
        rc = grn_selector_data_parse_options(ctx,
                                             selector_data,
                                             options,
                                             tag,
                                             OPTIONS,
                                             NULL);
      } else {
        rc = grn_proc_options_parse(ctx,
                                    options,
                                    tag,
                                    OPTIONS,
                                    NULL);
      }
#undef OPTIONS
      if (rc != GRN_SUCCESS) {
        goto exit;
      }
      break;
    default :
      {
        grn_obj inspected;
        GRN_TEXT_INIT(&inspected, 0);
        grn_inspect(ctx, &inspected, options);
        GRN_PLUGIN_ERROR(ctx, GRN_INVALID_ARGUMENT,
                         "query(): "
                         "3rd argument must be string or object literal: <%.*s>",
                         (int)GRN_TEXT_LEN(&inspected),
                         GRN_TEXT_VALUE(&inspected));
        GRN_OBJ_FIN(ctx, &inspected);
      }
      rc = ctx->rc;
      goto exit;
    }
  }

  if (flags_specified == -1) {
    flags |= GRN_EXPR_ALLOW_PRAGMA | GRN_EXPR_ALLOW_COLUMN;
  } else {
    flags |= flags_specified;
  }

  if (match_columns_string->header.domain == GRN_DB_TEXT &&
      GRN_TEXT_LEN(match_columns_string) > 0) {
    GRN_EXPR_CREATE_FOR_QUERY(ctx, table, match_columns, dummy_variable);
    if (!match_columns) {
      rc = ctx->rc;
      goto exit;
    }

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

  if (query->header.domain == GRN_DB_TEXT && GRN_TEXT_LEN(query) > 0) {
    const char *query_string;
    unsigned int query_string_len;
    grn_obj expanded_query;

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

    query_string = GRN_TEXT_VALUE(query);
    query_string_len = GRN_TEXT_LEN(query);

    GRN_TEXT_INIT(&expanded_query, 0);
    if (query_expander_name &&
        query_expander_name->header.domain == GRN_DB_TEXT &&
        GRN_TEXT_LEN(query_expander_name) > 0) {
      rc = grn_proc_syntax_expand_query(ctx,
                                        query_string, query_string_len,
                                        flags,
                                        GRN_TEXT_VALUE(query_expander_name),
                                        GRN_TEXT_LEN(query_expander_name),
                                        NULL, 0,
                                        NULL, 0,
                                        &expanded_query,
                                        "[query]");
      if (rc != GRN_SUCCESS) {
        GRN_OBJ_FIN(ctx, &expanded_query);
        goto exit;
      }
      query_string = GRN_TEXT_VALUE(&expanded_query);
      query_string_len = GRN_TEXT_LEN(&expanded_query);
    }
    grn_expr_parse(ctx,
                   condition,
                   query_string,
                   query_string_len,
                   match_columns,
                   default_mode,
                   default_operator,
                   flags);
    rc = ctx->rc;
    GRN_OBJ_FIN(ctx, &expanded_query);
    if (rc != GRN_SUCCESS) {
      goto exit;
    }
    grn_table_select(ctx, table, condition, res, op);
    rc = ctx->rc;
  }

exit :
  if (match_columns) {
    grn_obj_unlink(ctx, match_columns);
  }
  if (condition) {
    grn_obj_unlink(ctx, condition);
  }

  return rc;
}

static grn_obj *
func_query(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  selector_to_function_data data;

  if (selector_to_function_data_init(ctx, &data, user_data)) {
    grn_rc rc;
    rc = run_query(ctx, data.table, nargs, args, data.records, GRN_OP_AND, NULL);
    if (rc == GRN_SUCCESS) {
      selector_to_function_data_selected(ctx, &data);
    }
  }
  selector_to_function_data_fin(ctx, &data);

  return data.found;
}

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

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 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) {
      if (accessor->next) {
        sub_filter_pre_filter_accessor(ctx,
                                       accessor->next,
                                       GRN_RECORD_VALUE(&value),
                                       base_res);
      } else {
        grn_posting_internal posting = {0};
        posting.rid = GRN_RECORD_VALUE(&value);
        posting.weight_float = 1;
        grn_ii_posting_add_float(ctx,
                                 (grn_posting *)(&posting),
                                 (grn_hash *)base_res,
                                 GRN_OP_OR);
      }
    }
    GRN_OBJ_FIN(ctx, &value);
  } 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)) {
    if (accessor->next) {
      /* TODO: Report not supported. */
    } else {
      grn_ii_at(ctx,
                (grn_ii *)(accessor->obj),
                id,
                (grn_hash *)base_res,
                GRN_OP_OR);
    }
  }
}

static grn_bool
sub_filter_pre_filter(grn_ctx *ctx,
                      grn_obj *res,
                      grn_obj *scope,
                      grn_obj *base_res)
{
  if (grn_table_size(ctx, res) > grn_sub_filter_pre_filter_threshold) {
    return GRN_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,
                      "[sub_filter][pre-filter]",
                      "[scalar]",
                      scope);
    return GRN_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,
                      "[sub_filter][pre-filter]",
                      "[vector]",
                      scope);
    return GRN_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,
                      "[sub_filter][pre-filter]",
                      "[index]",
                      scope);
    return GRN_TRUE;
  } else if (grn_obj_is_accessor(ctx, scope)) {
    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,
                                     (grn_accessor *)scope,
                                     *matched_id,
                                     base_res);
    } GRN_TABLE_EACH_END(ctx, cursor);
    grn_report_accessor(ctx,
                        "[sub_filter][pre-filter]",
                        "",
                        scope);
    return GRN_TRUE;
  } else {
    return GRN_FALSE;
  }
}

static grn_rc
run_sub_filter(grn_ctx *ctx, grn_obj *table,
               int nargs, grn_obj **args,
               grn_obj *res, grn_operator op)
{
  grn_rc rc = GRN_SUCCESS;
  grn_obj *scope;
  grn_obj *sub_filter_string;
  grn_obj *scope_domain = NULL;
  grn_obj *sub_filter = NULL;
  grn_obj *dummy_variable = NULL;

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

  scope = args[0];
  sub_filter_string = args[1];

  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,
        "sub_filter(): the 1st argument must be column or accessor");
    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,
        "sub_filter(): the 2nd argument must be String");
    rc = ctx->rc;
    goto exit;
  }
  if (GRN_TEXT_LEN(sub_filter_string) == 0) {
    ERR(GRN_INVALID_ARGUMENT,
        "sub_filter(): the 2nd argument must not be empty String");
    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)) {
      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;
  grn_bool in_tag = GRN_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 = GRN_TRUE;
      break;
    case '>' :
      if (in_tag) {
        in_tag = GRN_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 grn_bool
grn_text_equal_cstr(grn_ctx *ctx, grn_obj *text, const char *cstr)
{
  int cstr_len;

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

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 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 :
    data->min_border_type = BETWEEN_BORDER_INCLUDE;
    data->max = args[2];
    data->max_border_type = BETWEEN_BORDER_INCLUDE;
    break;
  case 5 :
    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;
    }
    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 grn_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 GRN_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 GRN_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 grn_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,
                                              double too_many_index_match_ratio)
{
  int n_index_keys;

  if (too_many_index_match_ratio < 0.0) {
    return GRN_FALSE;
  }

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

  if (!index) {
    return GRN_FALSE;
  }

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

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

  n_index_keys = grn_table_size(ctx, index_table);
  if (n_index_keys == 0) {
    return GRN_FALSE;
  }

  switch (index_table->header.domain) {
  /* TODO: */
  /* case GRN_DB_INT8 : */
  /* case GRN_DB_UINT8 : */
  /* case GRN_DB_INT16 : */
  /* case GRN_DB_UINT16 : */
  /* case GRN_DB_INT32 : */
  /* case GRN_DB_UINT32 : */
  /* case GRN_DB_INT64 : */
  /* case GRN_DB_UINT64 : */
  /* case GRN_DB_FLOAT : */
  case GRN_DB_TIME :
    break;
  default :
    return GRN_FALSE;
  }

  {
    grn_table_cursor *cursor;
    long long int all_min;
    long long int all_max;
    cursor = grn_table_cursor_open(ctx, index_table,
                                   NULL, -1,
                                   NULL, -1,
                                   0, 1,
                                   GRN_CURSOR_BY_KEY | GRN_CURSOR_ASCENDING);
    if (!cursor) {
      return GRN_FALSE;
    }
    if (grn_table_cursor_next(ctx, cursor) == GRN_ID_NIL) {
      grn_table_cursor_close(ctx, cursor);
      return GRN_FALSE;
    }
    {
      long long int *key;
      grn_table_cursor_get_key(ctx, cursor, (void **)&key);
      all_min = *key;
    }
    grn_table_cursor_close(ctx, cursor);

    cursor = grn_table_cursor_open(ctx, index_table,
                                   NULL, 0, NULL, 0,
                                   0, 1,
                                   GRN_CURSOR_BY_KEY | GRN_CURSOR_DESCENDING);
    if (!cursor) {
      return GRN_FALSE;
    }
    if (grn_table_cursor_next(ctx, cursor) == GRN_ID_NIL) {
      grn_table_cursor_close(ctx, cursor);
      return GRN_FALSE;
    }
    {
      long long int *key;
      grn_table_cursor_get_key(ctx, cursor, (void **)&key);
      all_max = *key;
    }
    grn_table_cursor_close(ctx, cursor);

    /*
     * We assume the following:
     *   * homogeneous index key distribution.
     *   * each index key matches only 1 record.
     * TODO: Improve me.
     */
    {
      int n_existing_records;
      int n_indexed_records;
      long long int all_difference;
      long long int argument_difference;

      n_existing_records = grn_table_size(ctx, res);

      all_difference = all_max - all_min;
      if (all_difference <= 0) {
        return GRN_FALSE;
      }
      argument_difference =
        GRN_TIME_VALUE(data->max) - GRN_TIME_VALUE(data->min);
      if (argument_difference <= 0) {
        return GRN_FALSE;
      }
      n_indexed_records =
        n_index_keys * ((double)argument_difference / (double)all_difference);

      /*
       * Same as:
       * ((n_existing_record / n_indexed_records) > too_many_index_match_ratio)
       */
      if (n_existing_records > (n_indexed_records * too_many_index_match_ratio)) {
        return GRN_FALSE;
      }
    }
  }

  return GRN_TRUE;
}

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;
  int flags = GRN_CURSOR_ASCENDING | GRN_CURSOR_BY_KEY;
  between_data data;
  grn_bool use_sequential_search;
  grn_obj *index_table = NULL;
  grn_table_cursor *cursor;
  grn_id id;

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

  if (data.min_border_type == BETWEEN_BORDER_EXCLUDE) {
    flags |= GRN_CURSOR_GT;
  }
  if (data.max_border_type == BETWEEN_BORDER_EXCLUDE) {
    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);
      break;
    }
  }

  if (index_table) {
    double ratio = grn_between_too_many_index_match_ratio;
    use_sequential_search =
      selector_between_sequential_search_should_use(ctx,
                                                    table,
                                                    index,
                                                    index_table,
                                                    &data,
                                                    res,
                                                    op,
                                                    ratio);
  } else {
    use_sequential_search = GRN_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, flags);
  if (!cursor) {
    rc = ctx->rc;
    goto exit;
  }

  if (index) {
    while ((id = grn_table_cursor_next(ctx, cursor))) {
      grn_ii_at(ctx, (grn_ii *)index, id, (grn_hash *)res, op);
    }
  } else {
    grn_posting_internal posting = {0};
    posting.sid = 1;
    posting.pos = 0;
    posting.weight_float = 1;
    while ((id = grn_table_cursor_next(ctx, cursor))) {
      posting.rid = id;
      grn_ii_posting_add_float(ctx,
                               (grn_posting *)(&posting),
                               (grn_hash *)res,
                               op);
    }
  }
  grn_ii_resolve_sel_and(ctx, (grn_hash *)res, op);
  grn_table_cursor_close(ctx, cursor);

exit :
  between_data_fin(ctx, &data);

  if (grn_enable_reference_count && index_table) {
    grn_obj_unlink(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 grn_bool
is_reference_type_column(grn_ctx *ctx, grn_obj *column)
{
  grn_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 = GRN_TRUE;
    break;
  default :
    is_reference_type = GRN_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 grn_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)
{
  grn_obj *source;
  int n_existing_records;

  if (grn_in_values_too_many_index_match_ratio < 0.0) {
    return GRN_FALSE;
  }

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

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

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

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

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

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

    {
      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 GRN_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 * grn_in_values_too_many_index_match_ratio)) {
      grn_obj_unlink(ctx, &value_ids);
      grn_obj_unlink(ctx, source);
      return GRN_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);

  return GRN_TRUE;
}

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;

  if (!index) {
    return GRN_INVALID_ARGUMENT;
  }

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

  n_values = nargs - 2;
  values = args + 2;

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

  if (selector_in_values_sequential_search(ctx, table, index,
                                           n_values, values,
                                           res, op)) {
    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;
    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))) {
          grn_bool result_boolean = GRN_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 = GRN_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) {
      raw_output_columns = GRN_SELECT_DEFAULT_OUTPUT_COLUMNS;
      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);
  grn_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_rc rc = grn_obj_lock(ctx, db, GRN_ID_NIL, grn_lock_timeout);
  if (rc != GRN_SUCCESS) {
    char errbuf[GRN_CTX_MSGSIZE];
    grn_strcpy(errbuf, GRN_CTX_MSGSIZE, ctx->errbuf);
    ERR(rc, "[io_flush] failed to lock DB: %s", errbuf);
    GRN_OUTPUT_BOOL(GRN_FALSE);
    return NULL;
  }

  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,
                                                     GRN_FALSE);
  if (target->header.type == GRN_DB && is_only_opened) {
    GRN_TABLE_EACH_BEGIN_FLAGS(ctx, target, cursor, id, GRN_CURSOR_BY_ID) {
      if (id < GRN_N_RESERVED_TYPES) {
        continue;
      }

      if (!grn_ctx_is_opened(ctx, id)) {
        continue;
      }

      grn_obj *sub_target = grn_ctx_at(ctx, id);
      rc = grn_obj_flush(ctx, sub_target);
      grn_obj_unref(ctx, sub_target);
      if (rc != GRN_SUCCESS) {
        break;
      }
    } GRN_TABLE_EACH_END(ctx, cursor);
    if (rc == GRN_SUCCESS) {
      rc = grn_obj_flush(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);
    }
  }

  grn_rc unlock_rc = grn_obj_unlock(ctx, db, GRN_ID_NIL);
  if (rc == GRN_SUCCESS) {
    rc = unlock_rc;
  }

  grn_rc flush_rc = grn_obj_flush(ctx, db);
  if (rc == GRN_SUCCESS) {
    rc = flush_rc;
  }

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

  GRN_OUTPUT_BOOL(rc == GRN_SUCCESS);

  return NULL;
}

static grn_obj *
proc_thread_limit(grn_ctx *ctx, int nargs, grn_obj **args, grn_user_data *user_data)
{
  grn_obj *max_bulk;
  uint32_t current_limit;

  current_limit = grn_thread_get_limit_with_ctx(ctx);
  GRN_OUTPUT_INT64(current_limit);

  max_bulk = VAR(0);
  if (GRN_TEXT_LEN(max_bulk) > 0) {
    uint32_t max;
    const char *max_text = GRN_TEXT_VALUE(max_bulk);
    const char *max_text_end;
    const char *max_text_rest;

    max_text_end = max_text + GRN_TEXT_LEN(max_bulk);
    max = grn_atoui(max_text, max_text_end, &max_text_rest);
    if (max_text_rest != max_text_end) {
      ERR(GRN_INVALID_ARGUMENT,
          "[thread_limit] max must be unsigned integer value: <%.*s>",
          (int)GRN_TEXT_LEN(max_bulk),
          max_text);
      return NULL;
    }
    if (max == 0) {
      ERR(GRN_INVALID_ARGUMENT,
          "[thread_limit] max must be 1 or larger: <%.*s>",
          (int)GRN_TEXT_LEN(max_bulk),
          max_text);
      return NULL;
    }
    grn_thread_set_limit_with_ctx(ctx, max);
  }

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

  DEF_VAR(vars[0], "level");
  DEF_COMMAND("log_level", proc_log_level, 1, vars);

  DEF_VAR(vars[0], "level");
  DEF_VAR(vars[1], "message");
  DEF_COMMAND("log_put", proc_log_put, 2, vars);

  DEF_COMMAND("log_reopen", proc_log_reopen, 0, vars);

  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_obj *selector_proc;

    selector_proc = grn_proc_create(ctx, "query", -1, GRN_PROC_FUNCTION,
                                    func_query, NULL, NULL, 0, NULL);
    grn_proc_set_selector(ctx, selector_proc, selector_query);
    grn_proc_set_selector_operator(ctx, selector_proc, GRN_OP_NOP);
  }

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

  DEF_VAR(vars[0], "max");
  DEF_COMMAND("thread_limit", proc_thread_limit, 1, vars);

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