/* * IDが _id_ であるレコードを高速に全文検索するため転置索引を作 * 成する。多くの場合、 {Groonga::Table#define_index_column} で * +:source+ オプションを指定することにより、自動的に全文検索 * 用の索引は更新されるので、明示的にこのメソッドを使うこと * は少ない。 * * @example 記事の段落毎に索引を作成する。 * articles = Groonga::Array.create(:name => "<articles>") * articles.define_column("title", "ShortText") * articles.define_column("content", "Text") * * terms = Groonga::Hash.create(:name => "<terms>", * :default_tokenizer => "TokenBigram") * content_index = terms.define_index_column("content", articles, * :with_section => true) * * content = <<-EOC * groonga は組み込み型の全文検索エンジンライブラリです。 * DBMSやスクリプト言語処理系等に組み込むことによって、その * 全文検索機能を強化することができます。また、リレーショナ * ルモデルに基づくデータストア機能を内包しており、groonga * 単体でも高速なデータストアサーバとして使用することができ * ます。 * * ■全文検索方式 * 転置索引型の全文検索エンジンです。転置索引は圧縮されてファ * イルに格納され、検索時のディスク読み出し量を小さく、かつ * 局所的に抑えるように設計されています。用途に応じて以下の * 索引タイプを選択できます。 * EOC * * groonga = articles.add(:title => "groonga", :content => content) * * content.split(/\n{2,}/).each_with_index do |sentence, i| * content_index[groonga] = {:value => sentence, :section => i + 1} * end * * content_index.search("エンジン").collect do |record| * p record.key["title"] # -> "groonga" * end * * @overload []=(id, value) * @param [String] value 新しい値 * @overload []=(id, options) * _options_ を指定することにより、 _value_ を指定したときよりも索引の作 * 成を制御できる。 * @param [::Hash] options The name and value * pairs. Omitted names are initialized as the default value * @option options :section * 段落番号を指定する。省略した場合は1を指定したとみなされ * る。 * {Groonga::Table#define_index_column} で * @{:with_section => true}@ を指定していなければい * けない。 * @option options :old_value * 以前の値を指定する。省略した場合は現在の値が用いられる。 * 通常は指定する必要はない。 * @option options :value * 新しい値を指定する。 _value_ を指定した場合と _options_ で * @{:value => value}@ を指定した場合は同じ動作とな * る。 * * @deprecated Since 3.0.2. Use {#add}, {#delete} or {#update} instead. */ static VALUE rb_grn_index_column_array_set (VALUE self, VALUE rb_id, VALUE rb_value) { grn_ctx *context = NULL; grn_obj *column, *range; grn_rc rc; grn_id id; unsigned int section; grn_obj *old_value, *new_value; VALUE original_rb_value, rb_section, rb_old_value, rb_new_value; original_rb_value = rb_value; rb_grn_index_column_deconstruct(SELF(self), &column, &context, NULL, NULL, &new_value, &old_value, NULL, &range, NULL, NULL); id = RVAL2GRNID(rb_id, context, range, self); if (!RVAL2CBOOL(rb_obj_is_kind_of(rb_value, rb_cHash))) { VALUE hash_value; hash_value = rb_hash_new(); rb_hash_aset(hash_value, RB_GRN_INTERN("value"), rb_value); rb_value = hash_value; } rb_grn_scan_options(rb_value, "section", &rb_section, "old_value", &rb_old_value, "value", &rb_new_value, NULL); if (NIL_P(rb_section)) section = 1; else section = NUM2UINT(rb_section); if (NIL_P(rb_old_value)) { old_value = NULL; } else { GRN_BULK_REWIND(old_value); RVAL2GRNBULK(rb_old_value, context, old_value); } if (NIL_P(rb_new_value)) { new_value = NULL; } else { GRN_BULK_REWIND(new_value); RVAL2GRNBULK(rb_new_value, context, new_value); } rc = grn_column_index_update(context, column, id, section, old_value, new_value); rb_grn_context_check(context, self); rb_grn_rc_check(rc, self); return original_rb_value; }
/* * Executes a less operation. * * @example Executes less operations with the default context * Groonga::Operator::LESS.exec(1, 2) # => true * Groonga::Operator::LESS.exec(2, 1) # => false * * @example Executes less operations with the specified context * context = Groonga::Context.new * Groonga::Operator::LESS.exec(1, 2, * :context => context) # => true * Groonga::Operator::LESS.exec(2, 1, * :context => context) # => false * * @overload exec(x, y, options={}) * @param x [::Object] The left hand side value. * @param y [::Object] The right hand side value. * @param options [::Hash] The options. * @option options [Groonga::Context] (Groonga::Context.default) * The context to executes the operation. * @return [Boolean] `true` if `x` is less than `y`, `false` * otherwise. */ static VALUE rb_grn_less_operator_exec (int argc, VALUE *argv, VALUE self) { grn_bool less; VALUE rb_x; VALUE rb_y; VALUE rb_options; VALUE rb_context; grn_ctx *context; grn_obj x; grn_obj y; rb_scan_args(argc, argv, "21", &rb_x, &rb_y, &rb_options); rb_grn_scan_options(rb_options, "context", &rb_context, NULL); context = rb_grn_context_ensure(&rb_context); GRN_VOID_INIT(&x); GRN_VOID_INIT(&y); RVAL2GRNBULK(rb_x, context, &x); RVAL2GRNBULK(rb_y, context, &y); less = grn_operator_exec_less(context, &x, &y); GRN_OBJ_FIN(context, &x); GRN_OBJ_FIN(context, &y); return CBOOL2RVAL(less); }
/* * Executes a prefix-search operation. Prefix-serach operation checks * whether `text` starts with `prefix` or not. * * @example Executes prefix-search operations with the default context * Groonga::Operator::PREFIX.exec("Hello Rroonga", "Hello") # => true * Groonga::Operator::PREFIX.exec("Hello Rroonga", "Rroonga") # => false * * @example Executes prefix-search operations with the specified context * context = Groonga::Context.new * Groonga::Operator::PREFIX.exec("Hello Rroonga", "Hello", * :context => context) # => true * Groonga::Operator::PREFIX.exec("Hello Rroonga", "Rroonga", * :context => context) # => false * * @overload exec(text, prefix, options={}) * @param text [String] The text to be searched. * @param prefix [String] The prefix to be contained. * @param options [::Hash] The options. * @option options [Groonga::Context] (Groonga::Context.default) * The context to executes the operation. * @return [Boolean] `true` if `text` starts with `prefix`, `false` * otherwise. */ static VALUE rb_grn_prefix_operator_exec (int argc, VALUE *argv, VALUE self) { grn_bool have_prefix; VALUE rb_text; VALUE rb_prefix; VALUE rb_options; VALUE rb_context; grn_ctx *context; grn_obj text; grn_obj prefix; rb_scan_args(argc, argv, "21", &rb_text, &rb_prefix, &rb_options); rb_grn_scan_options(rb_options, "context", &rb_context, NULL); context = rb_grn_context_ensure(&rb_context); GRN_VOID_INIT(&text); GRN_VOID_INIT(&prefix); RVAL2GRNBULK(rb_text, context, &text); RVAL2GRNBULK(rb_prefix, context, &prefix); have_prefix = grn_operator_exec_prefix(context, &text, &prefix); GRN_OBJ_FIN(context, &text); GRN_OBJ_FIN(context, &prefix); return CBOOL2RVAL(have_prefix); }
/* * Updates a record that has @new_value@ as new content and * @old_value@ as old content in inverted index. Normally, this method * is not used explicitly. Inverted index for fulltext search is * updated automatically by using @:source@ option of * {Groonga::Table#define_index_column}. * * @example Updates sentences of an article in index * articles = Groonga::Array.create(:name => "Articles") * articles.define_column("title", "ShortText") * articles.define_column("content", "Text") * * terms = Groonga::Hash.create(:name => "Terms", * :key_type => "ShortText", * :default_tokenizer => "TokenBigram") * content_index = terms.define_index_column("content", articles, * :with_position => true, * :with_section => true) * * old_sentence = <<-SENTENCE * Groonga is a fast and accurate full text search engine based on * inverted index. One of the characteristics of groonga is that a * newly registered document instantly appears in search * results. Also, groonga allows updates without read locks. These * characteristics result in superior performance on real-time * applications. * SENTENCE * * new_sentence = <<-SENTENCE * Groonga is also a column-oriented database management system * (DBMS). Compared with well-known row-oriented systems, such as * MySQL and PostgreSQL, column-oriented systems are more suited for * aggregate queries. Due to this advantage, groonga can cover * weakness of row-oriented systems. * SENTENCE * * groonga = articles.add(:title => "groonga", :content => old_sentence) * * content_index.add(groonga, old_sentence, :section => 1) * p content_index.search("engine").size # -> 1 * p content_index.search("MySQL").size # -> 0 * * groonga[:content] = new_sentence * content_index.update(groonga, old_sentence, new_sentence, :section => 1) * p content_index.search("engine").size # -> 0 * p content_index.search("MySQL").size # -> 1 * * @overload update(record, old_value, new_value, options={}) * @param [Groonga::Record, Integer] record * The record that has a @new_value@ as its new value and * @old_value@ as its old value. It can be Integer as record id. * @param [String] old_value * The old value of the @record@. * @param [String] new_value * The new value of the @record@. * @param [::Hash] options * The options. * @option options [Integer] :section (1) * The section number. It is one-origin. * * You must specify @{:with_section => true}@ in * {Groonga::Table#define_index_column} to use this option. * @return [void] * * @since 3.0.2 */ static VALUE rb_grn_index_column_update (int argc, VALUE *argv, VALUE self) { grn_ctx *context = NULL; grn_obj *column, *range; grn_rc rc; grn_id id; unsigned int section; grn_obj *old_value, *new_value; VALUE rb_record, rb_old_value, rb_new_value, rb_options, rb_section; rb_scan_args(argc, argv, "31", &rb_record, &rb_old_value, &rb_new_value, &rb_options); rb_grn_index_column_deconstruct(SELF(self), &column, &context, NULL, NULL, &new_value, &old_value, NULL, &range, NULL, NULL); id = RVAL2GRNID(rb_record, context, range, self); if (NIL_P(rb_old_value)) { old_value = NULL; } else { GRN_BULK_REWIND(old_value); RVAL2GRNBULK(rb_old_value, context, old_value); } if (NIL_P(rb_new_value)) { new_value = NULL; } else { GRN_BULK_REWIND(new_value); RVAL2GRNBULK(rb_new_value, context, new_value); } rb_grn_scan_options(rb_options, "section", &rb_section, NULL); if (NIL_P(rb_section)) { section = 1; } else { section = NUM2UINT(rb_section); } rc = grn_column_index_update(context, column, id, section, old_value, new_value); rb_grn_context_check(context, self); rb_grn_rc_check(rc, self); return self; }
static void rb_grn_uvector_from_ruby_object_type (UVectorFromRubyData *data) { VALUE object; grn_ctx *context; grn_obj *uvector; grn_obj *type; VALUE *rb_values; int i, n; grn_obj *grn_value; int value_size; object = data->object; context = data->context; uvector = data->uvector; type = data->domain; grn_value = &(data->element_buffer); n = RARRAY_LEN(object); rb_values = RARRAY_PTR(object); value_size = grn_obj_get_range(context, type); for (i = 0; i < n; i++) { GRN_BULK_REWIND(grn_value); RVAL2GRNBULK(rb_values[i], context, grn_value); grn_bulk_write(context, uvector, GRN_BULK_HEAD(grn_value), value_size); } data->succeeded = GRN_TRUE; }
grn_obj * rb_grn_value_from_ruby_object (VALUE object, grn_ctx *context, grn_obj *value, grn_id type_id, grn_obj *type) { grn_bool string_p, table_type_p; string_p = rb_type(object) == T_STRING; table_type_p = (GRN_TABLE_HASH_KEY <= type->header.type && type->header.type <= GRN_TABLE_NO_KEY); if (!string_p) { return RVAL2GRNBULK_WITH_TYPE(object, context, value, type_id, type); } if (table_type_p && RSTRING_LEN(object) == 0) { if (value) { if (value->header.domain != type_id) { grn_obj_reinit(context, value, type_id, 0); } } else { value = grn_obj_open(context, GRN_BULK, 0, type_id); rb_grn_context_check(context, object); } GRN_RECORD_SET(context, value, GRN_ID_NIL); return value; } return RVAL2GRNBULK(object, context, value); }
grn_obj * rb_grn_key_from_ruby_object (VALUE rb_key, grn_ctx *context, grn_obj *key, grn_id domain_id, grn_obj *domain, VALUE related_object) { grn_id id; if (!domain) return RVAL2GRNBULK(rb_key, context, key); switch (domain->header.type) { case GRN_TYPE: return RVAL2GRNBULK_WITH_TYPE(rb_key, context, key, domain_id, domain); break; case GRN_TABLE_HASH_KEY: case GRN_TABLE_PAT_KEY: case GRN_TABLE_DAT_KEY: case GRN_TABLE_NO_KEY: id = RVAL2GRNID(rb_key, context, domain, related_object); break; default: if (!RVAL2CBOOL(rb_obj_is_kind_of(rb_key, rb_cInteger))) rb_raise(rb_eGrnError, "should be unsigned integer: <%s>: <%s>", rb_grn_inspect(rb_key), rb_grn_inspect(related_object)); id = NUM2UINT(rb_key); break; } GRN_TEXT_SET(context, key, &id, sizeof(id)); return key; }
static int hash_element_to_vector_element(VALUE key, VALUE value, VALUE user_data) { HashElementToVectorElementData *data = (HashElementToVectorElementData *)user_data; unsigned int weight; weight = NUM2UINT(value); if (data->vector->header.type == GRN_UVECTOR) { grn_id id = RVAL2GRNID(key, data->context, data->range, data->self); grn_uvector_add_element(data->context, data->vector, id, weight); } else { GRN_BULK_REWIND(data->element_value); RVAL2GRNBULK(key, data->context, data->element_value); grn_vector_add_element(data->context, data->vector, GRN_BULK_HEAD(data->element_value), GRN_BULK_VSIZE(data->element_value), weight, data->element_value->header.domain); } return ST_CONTINUE; }
static int rb_grn_hash_from_ruby_hash_body (VALUE rb_key, VALUE rb_value, VALUE user_data) { RbGrnHashFromRubyHashData *data = (RbGrnHashFromRubyHashData *)user_data; grn_obj *value; int added; rb_key = rb_grn_convert_to_string(rb_key); grn_hash_add(data->context, data->hash, RSTRING_PTR(rb_key), RSTRING_LEN(rb_key), (void **)&value, &added); rb_grn_context_check(data->context, data->rb_hash); if (added) { GRN_VOID_INIT(value); } RVAL2GRNBULK(rb_value, data->context, value); return ST_CONTINUE; }
grn_obj * rb_grn_obj_from_ruby_object (VALUE rb_object, grn_ctx *context, grn_obj **_obj) { if (RVAL2CBOOL(rb_obj_is_kind_of(rb_object, rb_cGrnObject))) { if (*_obj) { grn_obj_unlink(context, *_obj); /* TODO: reduce memory allocation */ } *_obj = RVAL2GRNOBJECT(rb_object, &context); } else { *_obj = RVAL2GRNBULK(rb_object, context, *_obj); } return *_obj; }
static VALUE rb_grn_table_key_support_set_value_by_key (VALUE self, VALUE rb_key, VALUE rb_value) { grn_ctx *context; grn_obj *table; grn_id id; grn_obj *value; grn_rc rc; if (NIL_P(rb_key)) { rb_raise(rb_eArgError, "key should not be nil: <%s>", rb_grn_inspect(self)); } rb_grn_table_key_support_deconstruct(SELF(self), &table, &context, NULL, NULL, NULL, &value, NULL, NULL, NULL); id = rb_grn_table_key_support_add_raw(self, rb_key, NULL); if (GRN_ID_NIL == id) { rb_raise(rb_eGrnError, "failed to add new record with key: <%s>: <%s>", rb_grn_inspect(rb_key), rb_grn_inspect(self)); } GRN_BULK_REWIND(value); RVAL2GRNBULK(rb_value, context, value); rc = grn_obj_set_value(context, table, id, value, GRN_OBJ_SET); rb_grn_context_check(context, self); rb_grn_rc_check(rc, self); return rb_value; }
/* * 文字列 _query_ をパースする。 * @overload parse(query, options={}) * @param [String] query パースする文字列 * @param [::Hash] options The name and value * pairs. Omitted names are initialized as the default value. * @option options :default_column * "column_name:hoge"ではなく"hoge"のようにcolumn_nameが指 * 定されない条件の検索対象となるカラムを指定する。 * @option options :default_operator (Groonga::Operator::AND) * "+"や"OR"で繋がれず、ただ列挙された複数の条件があった時、 * _expression_ 全体として各レコードをヒットとみなすかの論理 * 条件を指定する。省略した場合はGroonga::Operator::AND。 * * - Groonga::Operator::OR := * レコードはいずれかの条件にマッチすればいい。 =: * - Groonga::Operator::AND := * レコードは全ての条件にマッチしなければならない。 =: * - Groonga::Operator::AND_NOT := * 最初の条件にレコードはマッチし、残りの条件にレコードは * マッチしてはならない。 =: * * @option options :default_mode (Groonga::Operator::MATCH) * 検索時のモードを指定する。省略した場合はGroonga::Operator::MATCH。 * (FIXME: モードによってどういう動作になるかを書く。) * @option options :syntax (:query) * _query_ の構文を指定する。指定可能な値は以下の通り。省略 * した場合は +:query+ 。 * * - +:query+ := * 「文字列1 OR 文字列2」で「"文字列1"あるいは"文字列2" * にマッチという検索エンジンで利用できるような構文を使 * う。 * 参考: "Groongaのクエリ構文のドキュメント":http://groonga.org/ja/docs/reference/grn_expr/query_syntax.html =: * - +nil+ := * +:query+と同様 =: * - +:script+ := * 「[カラム名] == [値]」というようにECMAScript風の構文を使う。 * 参考: "Groongaのscript構文のドキュメント":http://groonga.org/ja/docs/reference/grn_expr/script_syntax.html =: * @option options :allow_pragma * _query_ の構文に query を用いているとき( +:syntax+ * オプション参照)、「*E-1」というようにクエリの先頭で * pragmaを利用できるようにする。script構文を用いている * ときはこのオプションを利用できない。 * * デフォルトではプラグマを利用できる。 * * 参考: "Groongaのクエリ構文のドキュメント":http://groonga.org/ja/docs/reference/grn_expr/query_syntax.html * @option options :allow_column * _query_ の構文にqueryを用いているとき( +:syntax+ オプショ * ン参照)、「カラム名:値」というようにカラム名を指定した * 条件式を利用できるようにする。script構文を用いていると * きはこのオプションを利用できない。 * * デフォルトではカラム名を指定した条件式を利用できる。 * * 参考: "Groongaのクエリ構文のドキュメント":http://groonga.org/ja/docs/reference/grn_expr/query_syntax.html * @option options :allow_update * _query_ の構文にscriptを用いているとき( +:syntax+ オプショ * ン参照)、「カラム名 = 値」というように更新操作を利用で * きるようにする。query構文を用いているときはこのオプショ * ンを利用できない。 * * デフォルトでは更新操作を利用できる。 * * 参考: "Groongaのクエリ構文のドキュメント":http://groonga.org/ja/docs/reference/grn_expr/query_syntax.html */ static VALUE rb_grn_expression_parse (int argc, VALUE *argv, VALUE self) { grn_ctx *context = NULL; grn_obj *expression, *default_column; grn_bool default_column_is_created = GRN_FALSE; grn_operator default_operator = GRN_OP_AND; grn_operator default_mode = GRN_OP_MATCH; grn_rc rc; char *query = NULL; unsigned query_size = 0; grn_expr_flags flags = 0; VALUE options, rb_query, rb_default_column, rb_default_operator; VALUE rb_default_mode, rb_syntax; VALUE rb_allow_pragma, rb_allow_column, rb_allow_update, rb_allow_leading_not; VALUE exception = Qnil; rb_scan_args(argc, argv, "11", &rb_query, &options); rb_grn_scan_options(options, "default_column", &rb_default_column, "default_operator", &rb_default_operator, "default_mode", &rb_default_mode, "syntax", &rb_syntax, "allow_pragma", &rb_allow_pragma, "allow_column", &rb_allow_column, "allow_update", &rb_allow_update, "allow_leading_not", &rb_allow_leading_not, NULL); query = StringValuePtr(rb_query); query_size = RSTRING_LEN(rb_query); rb_grn_expression_deconstruct(SELF(self), &expression, &context, NULL, NULL, NULL, NULL, NULL); if (NIL_P(rb_default_column)) { default_column = NULL; } else if (RVAL2CBOOL(rb_obj_is_kind_of(rb_default_column, rb_cGrnObject))) { default_column = RVAL2GRNOBJECT(rb_default_column, &context); } else { default_column = RVAL2GRNBULK(rb_default_column, context, NULL); default_column_is_created = GRN_TRUE; } if (!NIL_P(rb_default_mode)) default_mode = RVAL2GRNOPERATOR(rb_default_mode); if (!NIL_P(rb_default_operator)) default_operator = RVAL2GRNSETOPERATOR(rb_default_operator); if (NIL_P(rb_syntax) || rb_grn_equal_option(rb_syntax, "query")) { flags = GRN_EXPR_SYNTAX_QUERY; } else if (rb_grn_equal_option(rb_syntax, "script")) { flags = GRN_EXPR_SYNTAX_SCRIPT; } else { rb_raise(rb_eArgError, "syntax should be one of " "[nil, :query, :script]: %s", rb_grn_inspect(rb_syntax)); } if (NIL_P(rb_allow_pragma)) { if (!(flags & GRN_EXPR_SYNTAX_SCRIPT)) flags |= GRN_EXPR_ALLOW_PRAGMA; } else { if ((flags & GRN_EXPR_SYNTAX_SCRIPT)) rb_raise(rb_eArgError, ":allow_pragma isn't allowed in script syntax"); if (RVAL2CBOOL(rb_allow_pragma)) flags |= GRN_EXPR_ALLOW_PRAGMA; } if (NIL_P(rb_allow_column)) { if (!(flags & GRN_EXPR_SYNTAX_SCRIPT)) flags |= GRN_EXPR_ALLOW_COLUMN; } else { if ((flags & GRN_EXPR_SYNTAX_SCRIPT)) rb_raise(rb_eArgError, ":allow_column isn't allowed in script syntax"); if (RVAL2CBOOL(rb_allow_column)) flags |= GRN_EXPR_ALLOW_COLUMN; } if (NIL_P(rb_allow_update)) { flags |= GRN_EXPR_ALLOW_UPDATE; } else { if (RVAL2CBOOL(rb_allow_update)) flags |= GRN_EXPR_ALLOW_UPDATE; } if (!NIL_P(rb_allow_leading_not)) { if (RVAL2CBOOL(rb_allow_leading_not)) flags |= GRN_EXPR_ALLOW_LEADING_NOT; } rc = grn_expr_parse(context, expression, query, query_size, default_column, default_mode, default_operator, flags); if (rc != GRN_SUCCESS) { VALUE related_object; related_object = rb_ary_new_from_args(2, self, rb_ary_new_from_values(argc, argv)); exception = rb_grn_context_to_exception(context, related_object); } if (default_column_is_created) grn_obj_unlink(context, default_column); if (!NIL_P(exception)) rb_exc_raise(exception); return Qnil; }
grn_obj * rb_grn_bulk_from_ruby_object_with_type (VALUE object, grn_ctx *context, grn_obj *bulk, grn_id type_id, grn_obj *type) { const char *string; unsigned int size; union { int8_t int8_value; uint8_t uint8_value; int16_t int16_value; uint16_t uint16_value; int32_t int32_value; uint32_t uint32_value; int64_t int64_value; uint64_t uint64_value; int64_t time_value; double double_value; grn_geo_point geo_point_value; grn_id record_id; } value; grn_id range; VALUE rb_type_object; grn_obj_flags flags = 0; grn_bool string_p, table_type_p; string_p = rb_type(object) == T_STRING; table_type_p = (GRN_TABLE_HASH_KEY <= type->header.type && type->header.type <= GRN_TABLE_NO_KEY); switch (type_id) { case GRN_DB_INT8: value.int8_value = NUM2SHORT(object); string = (const char *)&(value.int8_value); size = sizeof(value.int8_value); break; case GRN_DB_UINT8: value.uint8_value = NUM2USHORT(object); string = (const char *)&(value.uint8_value); size = sizeof(value.uint8_value); break; case GRN_DB_INT16: value.int16_value = NUM2SHORT(object); string = (const char *)&(value.int16_value); size = sizeof(value.int16_value); break; case GRN_DB_UINT16: value.uint16_value = NUM2USHORT(object); string = (const char *)&(value.uint16_value); size = sizeof(value.uint16_value); break; case GRN_DB_INT32: value.int32_value = NUM2INT(object); string = (const char *)&(value.int32_value); size = sizeof(value.int32_value); break; case GRN_DB_UINT32: value.uint32_value = NUM2UINT(object); string = (const char *)&(value.uint32_value); size = sizeof(value.uint32_value); break; case GRN_DB_INT64: value.int64_value = NUM2LL(object); string = (const char *)&(value.int64_value); size = sizeof(value.int64_value); break; case GRN_DB_UINT64: value.uint64_value = NUM2ULL(object); string = (const char *)&(value.uint64_value); size = sizeof(value.uint64_value); break; case GRN_DB_FLOAT: value.double_value = NUM2DBL(object); string = (const char *)&(value.double_value); size = sizeof(value.double_value); break; case GRN_DB_TIME: { VALUE rb_sec, rb_usec; int64_t sec; int32_t usec; if (string_p) { ID id_parse; CONST_ID(id_parse, "parse"); object = rb_funcall(rb_cTime, id_parse, 1, object); } switch (TYPE(object)) { case T_FIXNUM: case T_BIGNUM: sec = NUM2LL(object); usec = 0; break; case T_FLOAT: rb_sec = rb_funcall(object, rb_intern("to_i"), 0); rb_usec = rb_funcall(object, rb_intern("remainder"), 1, INT2NUM(1)); sec = NUM2LL(rb_sec); usec = (int32_t)(NUM2DBL(rb_usec) * 1000000); break; case T_NIL: sec = 0; usec = 0; break; default: sec = NUM2LL(rb_funcall(object, rb_intern("to_i"), 0)); usec = NUM2INT(rb_funcall(object, rb_intern("usec"), 0)); break; } value.time_value = GRN_TIME_PACK(sec, usec); string = (const char *)&(value.time_value); size = sizeof(value.time_value); break; } case GRN_DB_SHORT_TEXT: case GRN_DB_TEXT: case GRN_DB_LONG_TEXT: string = StringValuePtr(object); size = RSTRING_LEN(object); range = grn_obj_get_range(context, type); if (size > range) rb_raise(rb_eArgError, "string is too large: expected: %u <= %u", size, range); flags |= GRN_OBJ_DO_SHALLOW_COPY; break; case GRN_DB_TOKYO_GEO_POINT: case GRN_DB_WGS84_GEO_POINT: { VALUE rb_geo_point; VALUE rb_latitude, rb_longitude; if (type_id == GRN_DB_TOKYO_GEO_POINT) { rb_geo_point = rb_funcall(rb_cGrnTokyoGeoPoint, rb_intern("new"), 1, object); } else { rb_geo_point = rb_funcall(rb_cGrnWGS84GeoPoint, rb_intern("new"), 1, object); } rb_geo_point = rb_funcall(rb_geo_point, rb_intern("to_msec"), 0); rb_latitude = rb_funcall(rb_geo_point, rb_intern("latitude"), 0); rb_longitude = rb_funcall(rb_geo_point, rb_intern("longitude"), 0); value.geo_point_value.latitude = NUM2INT(rb_latitude); value.geo_point_value.longitude = NUM2INT(rb_longitude); string = (const char *)&(value.geo_point_value); size = sizeof(value.geo_point_value); break; } case GRN_DB_VOID: case GRN_DB_DELIMIT: case GRN_DB_UNIGRAM: case GRN_DB_BIGRAM: case GRN_DB_TRIGRAM: case GRN_DB_MECAB: rb_type_object = GRNOBJECT2RVAL(Qnil, context, type, GRN_FALSE); rb_raise(rb_eArgError, "unbulkable type: %s", rb_grn_inspect(rb_type_object)); break; default: if (table_type_p && (NIL_P(object) || (string_p && RSTRING_LEN(object) == 0))) { value.record_id = GRN_ID_NIL; string = (const char *)&(value.record_id); size = sizeof(value.record_id); if (bulk && bulk->header.domain != type_id) { grn_obj_reinit(context, bulk, type_id, 0); } } else { return RVAL2GRNBULK(object, context, bulk); } break; } if (!bulk) { bulk = grn_obj_open(context, GRN_BULK, flags, GRN_ID_NIL); rb_grn_context_check(context, object); } GRN_TEXT_SET(context, bulk, string, size); return bulk; }
grn_obj * rb_grn_bulk_from_ruby_object_with_type (VALUE object, grn_ctx *context, grn_obj *bulk, grn_id type_id, grn_obj *type) { const char *string; unsigned int size; int32_t int32_value; uint32_t uint32_value; int64_t int64_value; uint64_t uint64_value; int64_t time_value; double double_value; grn_id record_id, range; VALUE rb_type_object; grn_obj_flags flags = 0; grn_bool string_p, table_type_p; string_p = rb_type(object) == T_STRING; table_type_p = (GRN_TABLE_HASH_KEY <= type->header.type && type->header.type <= GRN_TABLE_VIEW); if (string_p && !table_type_p) { return RVAL2GRNBULK(object, context, bulk); } switch (type_id) { case GRN_DB_INT32: int32_value = NUM2INT(object); string = (const char *)&int32_value; size = sizeof(int32_value); break; case GRN_DB_UINT32: uint32_value = NUM2UINT(object); string = (const char *)&uint32_value; size = sizeof(uint32_value); break; case GRN_DB_INT64: int64_value = NUM2LL(object); string = (const char *)&int64_value; size = sizeof(int64_value); break; case GRN_DB_UINT64: uint64_value = NUM2ULL(object); string = (const char *)&uint64_value; size = sizeof(uint64_value); break; case GRN_DB_FLOAT: double_value = NUM2DBL(object); string = (const char *)&double_value; size = sizeof(double_value); break; case GRN_DB_TIME: { VALUE rb_sec, rb_usec; int64_t sec; int32_t usec; switch (TYPE(object)) { case T_FIXNUM: case T_BIGNUM: sec = NUM2LL(object); usec = 0; break; case T_FLOAT: rb_sec = rb_funcall(object, rb_intern("to_i"), 0); rb_usec = rb_funcall(object, rb_intern("remainder"), 1, INT2NUM(1)); sec = NUM2LL(rb_sec); usec = (int32_t)(NUM2DBL(rb_usec) * 1000000); break; case T_NIL: sec = 0; usec = 0; break; default: sec = NUM2LL(rb_funcall(object, rb_intern("to_i"), 0)); usec = NUM2INT(rb_funcall(object, rb_intern("usec"), 0)); break; } time_value = GRN_TIME_PACK(sec, usec); } string = (const char *)&time_value; size = sizeof(time_value); break; case GRN_DB_SHORT_TEXT: case GRN_DB_TEXT: case GRN_DB_LONG_TEXT: string = StringValuePtr(object); size = RSTRING_LEN(object); range = grn_obj_get_range(context, type); if (size > range) rb_raise(rb_eArgError, "string is too large: expected: %u <= %u", size, range); flags |= GRN_OBJ_DO_SHALLOW_COPY; break; case GRN_DB_VOID: case GRN_DB_DELIMIT: case GRN_DB_UNIGRAM: case GRN_DB_BIGRAM: case GRN_DB_TRIGRAM: case GRN_DB_MECAB: rb_type_object = GRNOBJECT2RVAL(Qnil, context, type, GRN_FALSE); rb_raise(rb_eArgError, "unbulkable type: %s", rb_grn_inspect(rb_type_object)); break; default: if (table_type_p && (NIL_P(object) || (string_p && RSTRING_LEN(object) == 0))) { record_id = GRN_ID_NIL; string = (const char *)&record_id; size = sizeof(record_id); if (bulk && bulk->header.domain != type_id) { grn_obj_reinit(context, bulk, type_id, 0); } } else { return RVAL2GRNBULK(object, context, bulk); } break; } if (!bulk) { bulk = grn_obj_open(context, GRN_BULK, flags, GRN_ID_NIL); rb_grn_context_check(context, object); } GRN_TEXT_SET(context, bulk, string, size); return bulk; }
/* * It updates a value of variable size column value for the record * that ID is _id_. * * Weight vector column is a special variable size column. This * description describes only weight vector column. Other variable * size column works what you think. * * @example Use weight vector as matrix search result weight * Groonga::Schema.define do |schema| * schema.create_table("Products", * :type => :patricia_trie, * :key_type => "ShortText") do |table| * # This is weight vector. * # ":with_weight => true" is important for matrix search result weight. * table.short_text("tags", * :type => :vector, * :with_weight => true) * end * * schema.create_table("Tags", * :type => :hash, * :key_type => "ShortText") do |table| * # This is inverted index. It also needs ":with_weight => true". * table.index("Products.tags", :with_weight => true) * end * end * * products = Groonga["Products"] * groonga = products.add("Groonga") * groonga.tags = [ * { * :value => "groonga", * :weight => 100, * }, * ] * rroonga = products.add("Rroonga") * rroonga.tags = [ * { * :value => "ruby", * :weight => 100, * }, * { * :value => "groonga", * :weight => 10, * }, * ] * * result = products.select do |record| * # Search by "groonga" * record.match("groonga") do |match_target| * match_target.tags * end * end * * result.each do |record| * p [record.key.key, record.score] * end * # Matches all records with weight. * # => ["Groonga", 101] * # ["Rroonga", 11] * * # Increases score for "ruby" 10 times * products.select(# The previous search result. Required. * :result => result, * # It just adds score to existing records in the result. Required. * :operator => Groonga::Operator::ADJUST) do |record| * record.match("ruby") do |target| * target.tags * 10 # 10 times * end * end * * result.each do |record| * p [record.key.key, record.score] * end * # Weight is used for increasing score. * # => ["Groonga", 101] <- Not changed. * # ["Rroonga", 1021] <- 1021 (= 101 * 10 + 1) increased. * * @overload []=(id, elements) * This description is for weight vector column. * * @param [Integer, Record] id The record ID. * @param [Array<Hash<Symbol, String>>] elements An array of values * for weight vector. * Each value is a Hash like the following form: * * <pre> * { * :value => [KEY], * :weight => [WEIGHT], * } * </pre> * * @[KEY]@ must be the same type of the key of the table that is * specified as range on creating the weight vector. * * @[WEIGHT]@ must be an positive integer. Note that search * becomes @weight + 1@. It means that You want to get 10 as * score, you should set 9 as weight. * * @overload []=(id, value) * This description is for variable size columns except weight * vector column. * * @param [Integer, Record] id The record ID. * @param [::Object] value A new value. * @see Groonga::Object#[]= * * @since 4.0.1 */ static VALUE rb_grn_variable_size_column_array_set (VALUE self, VALUE rb_id, VALUE rb_value) { grn_ctx *context = NULL; grn_obj *column, *range; grn_rc rc; grn_id id; grn_obj *value, *element_value; int flags = GRN_OBJ_SET; rb_grn_variable_size_column_deconstruct(SELF(self), &column, &context, NULL, NULL, &value, &element_value, NULL, &range); if (!(column->header.flags & GRN_OBJ_WITH_WEIGHT)) { VALUE args[2]; args[0] = rb_id; args[1] = rb_value; return rb_call_super(2, args); } id = RVAL2GRNID(rb_id, context, range, self); grn_obj_reinit(context, value, value->header.domain, value->header.flags | GRN_OBJ_VECTOR); value->header.flags |= GRN_OBJ_WITH_WEIGHT; if (RVAL2CBOOL(rb_obj_is_kind_of(rb_value, rb_cArray))) { int i, n; n = RARRAY_LEN(rb_value); for (i = 0; i < n; i++) { unsigned int weight = 0; VALUE rb_element_value, rb_weight; rb_grn_scan_options(RARRAY_PTR(rb_value)[i], "value", &rb_element_value, "weight", &rb_weight, NULL); if (!NIL_P(rb_weight)) { weight = NUM2UINT(rb_weight); } if (value->header.type == GRN_UVECTOR) { grn_id id = RVAL2GRNID(rb_element_value, context, range, self); grn_uvector_add_element(context, value, id, weight); } else { GRN_BULK_REWIND(element_value); if (!NIL_P(rb_element_value)) { RVAL2GRNBULK(rb_element_value, context, element_value); } grn_vector_add_element(context, value, GRN_BULK_HEAD(element_value), GRN_BULK_VSIZE(element_value), weight, element_value->header.domain); } } } else if (RVAL2CBOOL(rb_obj_is_kind_of(rb_value, rb_cHash))) { HashElementToVectorElementData data; data.self = self; data.context = context; data.vector = value; data.element_value = element_value; data.range = range; rb_hash_foreach(rb_value, hash_element_to_vector_element, (VALUE)&data); } else { rb_raise(rb_eArgError, "<%s>: " "weight vector value must be an array of index value or " "a hash that key is vector value and value is vector weight: " "<%s>", rb_grn_inspect(self), rb_grn_inspect(rb_value)); } rc = grn_obj_set_value(context, column, id, value, flags); rb_grn_context_check(context, self); rb_grn_rc_check(rc, self); return rb_value; }