Ejemplo n.º 1
0
/*
 * 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;
}
Ejemplo n.º 2
0
/*
 * call-seq:
 *   column.sources -> Groonga::Columnの配列
 *
 * インデックス対象となっているカラムの配列を返す。
 */
static VALUE
rb_grn_index_column_get_sources (VALUE self)
{
    grn_ctx *context = NULL;
    grn_obj *column;
    grn_obj sources;
    grn_id *source_ids;
    VALUE rb_sources;
    int i, n;

    rb_grn_index_column_deconstruct(SELF(self), &column, &context,
				    NULL, NULL,
				    NULL, NULL, NULL, NULL,
				    NULL, NULL);

    GRN_OBJ_INIT(&sources, GRN_BULK, 0, GRN_ID_NIL);
    grn_obj_get_info(context, column, GRN_INFO_SOURCE, &sources);
    rb_grn_context_check(context, self);

    n = GRN_BULK_VSIZE(&sources) / sizeof(grn_id);
    source_ids = (grn_id *)GRN_BULK_HEAD(&sources);
    rb_sources = rb_ary_new2(n);
    for (i = 0; i < n; i++) {
	grn_obj *source;
	VALUE rb_source;

	source = grn_ctx_at(context, *source_ids);
	rb_source = GRNOBJECT2RVAL(Qnil, context, source, RB_GRN_FALSE);
	rb_ary_push(rb_sources, rb_source);
	source_ids++;
    }
    grn_obj_unlink(context, &sources);

    return rb_sources;
}
Ejemplo n.º 3
0
/*
 * Document-method: search
 *
 * call-seq:
 *   column.search(query, options={}) -> Groonga::Hash
 *
 * _object_から_query_に対応するオブジェクトを検索し、見つかっ
 * たオブジェクトのIDがキーになっているGroonga::Hashを返す。
 *
 * 利用可能なオプションは以下の通り。
 *
 * [_:result_]
 *   結果を格納するGroonga::Hash。指定しない場合は新しく
 *   Groonga::Hashを生成し、それに結果を格納して返す。
 * [_:operator_]
 *   以下のどれかの値を指定する。+nil+, <tt>"or"</tt>, <tt>"||"</tt>,
 *   <tt>"and"</tt>, <tt>"+"</tt>, <tt>"&&"</tt>, <tt>"but"</tt>,
 *   <tt>"not"</tt>, <tt>"-"</tt>, <tt>"adjust"</tt>, <tt>">"</tt>。
 *   それぞれ以下のようになる。(FIXME: 「以下」)
 * [_:exact_]
 *   +true+を指定すると完全一致で検索する
 * [_:longest_common_prefix_]
 *   +true+を指定すると_query_と同じ接頭辞をもつエントリのう
 *   ち、もっとも長いエントリを検索する
 * [_:suffix_]
 *   +true+を指定すると_query_が後方一致するエントリを検索す
 *   る
 * [_:prefix_]
 *   +true+を指定すると_query_が前方一致するレコードを検索す
 *   る
 * [_:near_]
 *   +true+を指定すると_query_に指定した複数の語が近傍に含ま
 *   れるレコードを検索する
 * [...]
 *   ...
 */
static VALUE
rb_grn_index_column_search (int argc, VALUE *argv, VALUE self)
{
    grn_ctx *context;
    grn_obj *column;
    grn_obj *range;
    grn_obj *query = NULL, *id_query = NULL, *string_query = NULL;
    grn_obj *result;
    grn_operator operator;
    grn_rc rc;
    VALUE rb_query, options, rb_result, rb_operator;

    rb_grn_index_column_deconstruct(SELF(self), &column, &context,
				    NULL, NULL,
				    NULL, NULL, NULL, &range,
				    &id_query, &string_query);

    rb_scan_args(argc, argv, "11", &rb_query, &options);

    if (CBOOL2RVAL(rb_obj_is_kind_of(rb_query, rb_cGrnQuery))) {
	grn_query *_query;
	_query = RVAL2GRNQUERY(rb_query);
	query = (grn_obj *)_query;
    } else if (CBOOL2RVAL(rb_obj_is_kind_of(rb_query, rb_cInteger))) {
	grn_id id;
	id = NUM2UINT(rb_query);
	GRN_TEXT_SET(context, id_query, &id, sizeof(grn_id));
	query = id_query;
    } else {
	const char *_query;
	_query = StringValuePtr(rb_query);
	GRN_TEXT_SET(context, string_query, _query, RSTRING_LEN(rb_query));
	query = string_query;
    }

    rb_grn_scan_options(options,
			"result", &rb_result,
			"operator", &rb_operator,
			NULL);

    if (NIL_P(rb_result)) {
	result = grn_table_create(context, NULL, 0, NULL,
				  GRN_OBJ_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC,
				  range, 0);
	rb_grn_context_check(context, self);
	rb_result = GRNOBJECT2RVAL(Qnil, context, result, RB_GRN_TRUE);
    } else {
	result = RVAL2GRNOBJECT(rb_result, &context);
    }

    operator = RVAL2GRNOPERATOR(rb_operator);

    rc = grn_obj_search(context, column, query, result, operator, NULL);
    rb_grn_rc_check(rc, self);

    return rb_result;
}
Ejemplo n.º 4
0
/*
 * _column_ が位置情報も格納する場合は +true+ を返します。
 *
 * @overload with_position?
 */
static VALUE
rb_grn_index_column_with_position_p (VALUE self)
{
    grn_obj *column;

    rb_grn_index_column_deconstruct(SELF(self), &column, NULL,
                                    NULL, NULL,
                                    NULL, NULL, NULL, NULL,
                                    NULL, NULL);

    return CBOOL2RVAL(column->header.flags & GRN_OBJ_WITH_POSITION);
}
Ejemplo n.º 5
0
/*
 * _column_ がウェイト情報も格納する場合は +true+ を返します。
 *
 * @overload with_weight?
 */
static VALUE
rb_grn_index_column_with_weight_p (VALUE self)
{
    grn_obj *column;

    rb_grn_index_column_deconstruct(SELF(self), &column, NULL,
                                    NULL, NULL,
                                    NULL, NULL, NULL, NULL,
                                    NULL, NULL);

    return CBOOL2RVAL(column->header.flags & GRN_OBJ_WITH_WEIGHT);
}
Ejemplo n.º 6
0
/*
 * 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;
}
Ejemplo n.º 7
0
/*
 * インデックス対象となる複数のカラムを配列で設定する。
 *
 * @overload sources=(columns)
 *   @param [::Array<Groonga::Column>] columns インデックス対象となるカラムの配列
 */
static VALUE
rb_grn_index_column_set_sources (VALUE self, VALUE rb_sources)
{
    VALUE exception;
    grn_ctx *context = NULL;
    grn_obj *column;
    int i, n;
    VALUE *rb_source_values;
    grn_id range_id;
    grn_id *sources;
    grn_rc rc;

    rb_grn_index_column_deconstruct(SELF(self), &column, &context,
                                    NULL, NULL,
                                    NULL, NULL,
                                    &range_id, NULL,
                                    NULL, NULL);

    n = RARRAY_LEN(rb_sources);
    rb_source_values = RARRAY_PTR(rb_sources);
    sources = ALLOCA_N(grn_id, n);
    for (i = 0; i < n; i++) {
        sources[i] = resolve_source_id(context, column, range_id,
                                       rb_source_values[i]);
    }

    {
        grn_obj bulk_sources;
        GRN_OBJ_INIT(&bulk_sources, GRN_BULK, 0, GRN_ID_NIL);
        GRN_TEXT_SET(context, &bulk_sources, sources, n * sizeof(grn_id));
        rc = grn_obj_set_info(context, column, GRN_INFO_SOURCE, &bulk_sources);
        exception = rb_grn_context_to_exception(context, self);
        grn_obj_unlink(context, &bulk_sources);
    }

    if (!NIL_P(exception))
        rb_exc_raise(exception);
    rb_grn_rc_check(rc, self);

    return Qnil;
}
Ejemplo n.º 8
0
/*
 * Opens cursor to iterate posting in the index column.
 *
 * @example
 *   # TODO
 *
 * @overload open_cursor(table_cursor, options={})
 *   @param [TableCursor] The table cursor for table of the index column.
 *   @param [::Hash] options
 *   @option options [Boolean] :with_section (nil)
 *      Includes section info the posting. It is enabled by default if
 *      the index column is created with @:with_section@ flag.
 *   @option options [Boolean] :with_weight (nil)
 *      Includes weight info the posting. It is enabled by default if
 *      the index column is created with @:with_weight@ flag.
 *   @option options [Boolean] :with_position (nil)
 *      Includes position info the posting. It is enabled by default if
 *      the index column is created with @:with_position@ flag.
 */
static VALUE
rb_grn_index_column_open_cursor (int argc, VALUE *argv, VALUE self)
{
    grn_ctx *context;
    grn_obj *column;
    grn_obj *range_object;
    grn_table_cursor *table_cursor;
    grn_id rid_min = GRN_ID_NIL;
    grn_id rid_max = GRN_ID_MAX;
    int flags = 0;
    grn_obj *index_cursor;
    VALUE rb_table_cursor;
    VALUE options;
    VALUE rb_with_section, rb_with_weight, rb_with_position;
    VALUE rb_table;
    VALUE rb_lexicon;
    VALUE rb_cursor;

    rb_grn_index_column_deconstruct(SELF(self), &column, &context,
                                    NULL, NULL,
                                    NULL, NULL,
                                    NULL, &range_object,
                                    NULL, NULL);

    rb_scan_args(argc, argv, "11", &rb_table_cursor, &options);
    rb_grn_scan_options(options,
                        "with_section", &rb_with_section,
                        "with_weight", &rb_with_weight,
                        "with_position", &rb_with_position,
                        NULL);

    table_cursor = RVAL2GRNTABLECURSOR(rb_table_cursor, NULL);
    rb_table = GRNOBJECT2RVAL(Qnil, context, range_object, GRN_FALSE);
    rb_lexicon = rb_iv_get(rb_table_cursor, "@table");

    if (NIL_P(rb_with_section)) {
        flags |= column->header.flags & GRN_OBJ_WITH_SECTION;
    } else if (RVAL2CBOOL(rb_with_section)) {
        flags |= GRN_OBJ_WITH_SECTION;
    }

    if (NIL_P(rb_with_weight)) {
        flags |= column->header.flags & GRN_OBJ_WITH_WEIGHT;
    } else if (RVAL2CBOOL(rb_with_weight)) {
        flags |= GRN_OBJ_WITH_WEIGHT;
    }

    if (NIL_P(rb_with_position)) {
        flags |= column->header.flags & GRN_OBJ_WITH_POSITION;
    } else if (RVAL2CBOOL(rb_with_position)) {
        flags |= GRN_OBJ_WITH_POSITION;
    }

    index_cursor = grn_index_cursor_open(context, table_cursor,
                                         column, rid_min, rid_max, flags);

    rb_cursor = GRNINDEXCURSOR2RVAL(context, index_cursor, rb_table, rb_lexicon);

    if (rb_block_given_p())
        return rb_ensure(rb_yield, rb_cursor, rb_grn_object_close, rb_cursor);
    else
        return rb_cursor;
}