/* * スニペットを作成する。 * * @overload new(options={}) * @param options [::Hash] The name and value * pairs. Omitted names are initialized as the default value. * @option options :context (Groonga::Context.default) * スキーマ作成時に使用するGroonga::Contextを指定する。 * @option options :normalize * キーワード文字列・スニペット元の文字列を正規化するかどうか。 * 省略した場合は +false+ で正規化しない。 * @option options :skip_leading_spaces (false) * 先頭の空白を無視するかどうか。省略した場合は +false+ で無視しない。 * @option options :width (100) * スニペット文字列の長さ。省略した場合は100文字。 * @option options :max_results (3) * 生成するスニペットの最大数。省略した場合は3。 * @option options :html_escape (false) * スニペット内の +<+ , +>+ , +&+ , +"+ をHTMLエスケープするかどうか。 * 省略した場合は +false+ で、HTMLエスケープしない。 * @option options :default_open_tag ("") * デフォルトの開始タグ。省略した場合は""(空文字列) * @option options :default_close_tag ("") * デフォルトの終了タグ。省略した場合は""(空文字列) */ static VALUE rb_grn_snippet_initialize (int argc, VALUE *argv, VALUE self) { grn_ctx *context = NULL; grn_snip *snippet = NULL; VALUE options; VALUE rb_context, rb_normalize, rb_skip_leading_spaces; VALUE rb_width, rb_max_results, rb_default_open_tag, rb_default_close_tag; VALUE rb_html_escape; int flags = GRN_SNIP_COPY_TAG; unsigned int width = 100; unsigned int max_results = 3; char *default_open_tag = NULL; unsigned int default_open_tag_length = 0; char *default_close_tag = NULL; unsigned int default_close_tag_length = 0; grn_snip_mapping *mapping = NULL; rb_scan_args(argc, argv, "01", &options); rb_grn_scan_options(options, "context", &rb_context, "normalize", &rb_normalize, "skip_leading_spaces", &rb_skip_leading_spaces, "width", &rb_width, "max_results", &rb_max_results, "default_open_tag", &rb_default_open_tag, "default_close_tag", &rb_default_close_tag, "html_escape", &rb_html_escape, NULL); context = rb_grn_context_ensure(&rb_context); if (!grn_ctx_db(context)) { rb_raise(rb_eArgError, "Groonga::Context should be associated with a database by " "Groonga::Database#open or #create: %s", rb_grn_inspect(rb_context)); } if (RVAL2CBOOL(rb_normalize)) flags |= GRN_SNIP_NORMALIZE; if (RVAL2CBOOL(rb_skip_leading_spaces)) flags |= GRN_SNIP_SKIP_LEADING_SPACES; if (!NIL_P(rb_width)) width = NUM2UINT(rb_width); if (!NIL_P(rb_max_results)) max_results = NUM2UINT(rb_max_results); if (!NIL_P(rb_default_open_tag)) { default_open_tag = StringValuePtr(rb_default_open_tag); default_open_tag_length = RSTRING_LEN(rb_default_open_tag); } if (!NIL_P(rb_default_close_tag)) { default_close_tag = StringValuePtr(rb_default_close_tag); default_close_tag_length = RSTRING_LEN(rb_default_close_tag); } if (RVAL2CBOOL(rb_html_escape)) mapping = (grn_snip_mapping *)-1; snippet = grn_snip_open(context, flags, width, max_results, default_open_tag, default_open_tag_length, default_close_tag, default_close_tag_length, mapping); rb_grn_context_check(context, rb_ary_new4(argc, argv)); rb_grn_object_assign(Qnil, self, rb_context, context, (grn_obj *)snippet); rb_grn_context_register_floating_object(DATA_PTR(self)); rb_iv_set(self, "@context", rb_context); return Qnil; }
/* * Logs a message. * * @overload log(message, options={}) * @param message [String] The log message. * @param options [::Hash] * @option options :context [Groonga::Context] (Groonga::Context.default) * The context for the message. * @option options :level [nil, :none, :emergency, :alert, :critical, * :error, :warning, :notice, :info, :debug, :dump] (:notice) * The level for the message. * * `nil` equals to `:notice`. * @option options :file [nil, String] (nil) * The file name where the message is occurred. * * If all of `:file`, `:line` and `:function` are nil, these * values are guessed from `Kernel.#caller_locations` result. * @option options :line [nil, Integer] (nil) * The line number where the message is occurred. * @option options :function [nil, String] (nil) * The function or related name such as method name where the * message is occurred. * @return [void] * * @since 5.0.2 */ static VALUE rb_grn_logger_s_log (int argc, VALUE *argv, VALUE klass) { VALUE rb_message; const char *message; VALUE rb_context = Qnil; grn_ctx *context; VALUE rb_level; grn_log_level level = GRN_LOG_DEFAULT_LEVEL; VALUE rb_file; const char *file = NULL; VALUE rb_line; int line = 0; VALUE rb_function; const char *function = NULL; VALUE rb_options; rb_scan_args(argc, argv, "11", &rb_message, &rb_options); message = StringValueCStr(rb_message); rb_grn_scan_options(rb_options, "context", &rb_context, "level", &rb_level, "file", &rb_file, "line", &rb_line, "function", &rb_function, NULL); context = rb_grn_context_ensure(&rb_context); if (!NIL_P(rb_level)) { level = RVAL2GRNLOGLEVEL(rb_level); } if (NIL_P(rb_file) && NIL_P(rb_line) && NIL_P(rb_function)) { VALUE rb_locations; VALUE rb_location; rb_locations = rb_funcall(rb_cObject, id_caller_locations, 2, INT2NUM(1), INT2NUM(1)); rb_location = RARRAY_PTR(rb_locations)[0]; rb_file = rb_funcall(rb_location, id_path, 0); rb_line = rb_funcall(rb_location, id_lineno, 0); rb_function = rb_funcall(rb_location, id_label, 0); } if (!NIL_P(rb_file)) { file = StringValueCStr(rb_file); } if (!NIL_P(rb_line)) { line = NUM2INT(rb_line); } if (!NIL_P(rb_function)) { function = StringValueCStr(rb_function); } grn_logger_put(context, level, file, line, function, "%s", message); return Qnil; }
/* * groongaがログを出力する度に呼び出されるブロックを登録する。 * * @overload register(options={}) * @yield [event, level, time, title, message, location] * _event_ と _level_ はSymbol、それ以外は全て文字列で渡される。 * _event_ と _level_ 以外 * の4つについては _options_ で +false+ を指定することでブロックに * 渡さないようにすることができ、その場合は空文字列が実際には渡される。 * @param options [::Hash] The name and value * pairs. Omitted names are initialized as the default value. * @option options :max_level (:notice) * ログのレベルを +:none+ , +:emergency+ , +:alert+ , * +:critical+ , +:error+ , +:warning+ , +:notice+ , +:info+ , * +:debug+ , +:dump+ のいずれかで指定する。それより重要度が * 低いログはブロックに渡されなくなる。デフォルトでは +:notice+ 。 * @option options :time * ログが出力された時間をブロックに渡したいなら +true+ を指 * 定する。デフォルトでは渡す。 * @option options :title * ログのタイトルをブロックに渡したいなら +true+ を指定す * る。デフォルトでは渡す。 * (FIXME: groongaで実装されていない?) * @option options :message * ログのメッセージをブロックに渡したいなら +true+ を指定す * る。デフォルトでは渡す。 * @option options :location * ログの発生元のプロセスIDとgroongaのソースコードのファイ * ル名、行番号、関数名をブロックに渡したいなら +true+ を指 * 定する。デフォルトでは渡す。 */ static VALUE rb_grn_logger_s_register (int argc, VALUE *argv, VALUE klass) { VALUE rb_context = Qnil; grn_ctx *context; VALUE rb_logger, rb_callback; VALUE rb_options, rb_max_level; VALUE rb_time, rb_title, rb_message, rb_location; VALUE rb_flags; grn_log_level max_level = GRN_LOG_DEFAULT_LEVEL; int flags = 0; rb_scan_args(argc, argv, "02&", &rb_logger, &rb_options, &rb_callback); if (rb_block_given_p()) { if (!NIL_P(rb_logger)) { rb_options = rb_logger; } rb_logger = rb_funcall(rb_cGrnCallbackLogger, id_new, 1, rb_callback); } rb_grn_scan_options(rb_options, "max_level", &rb_max_level, "time", &rb_time, "title", &rb_title, "message", &rb_message, "location", &rb_location, "flags", &rb_flags, NULL); if (!NIL_P(rb_max_level)) { max_level = RVAL2GRNLOGLEVEL(rb_max_level); } if (NIL_P(rb_time) || CBOOL2RVAL(rb_time)) { flags |= GRN_LOG_TIME; } if (NIL_P(rb_title) || CBOOL2RVAL(rb_title)) { flags |= GRN_LOG_TITLE; } if (NIL_P(rb_message) || CBOOL2RVAL(rb_message)) { flags |= GRN_LOG_MESSAGE; } if (NIL_P(rb_location) || CBOOL2RVAL(rb_location)) { flags |= GRN_LOG_LOCATION; } if (!NIL_P(rb_flags)) { flags = rb_funcall(rb_mGrnLoggerFlags, id_parse, 2, INT2NUM(flags), rb_flags); } rb_grn_logger.max_level = max_level; rb_grn_logger.flags = flags; rb_grn_logger.user_data = (void *)rb_logger; context = rb_grn_context_ensure(&rb_context); grn_logger_set(context, &rb_grn_logger); rb_grn_context_check(context, rb_logger); rb_cv_set(klass, "@@current_logger", rb_logger); return Qnil; }
/* * 各レコードをキーで管理するテーブルを生成する。ブロックを指 * 定すると、そのブロックに生成したテーブルが渡され、ブロック * を抜けると自動的にテーブルが破棄される。 * * @example * #無名一時テーブルを生成する。 * Groonga::Hash.create * * #無名永続テーブルを生成する。 * Groonga::Hash.create(:path => "/tmp/hash.grn") * * #名前付き永続テーブルを生成する。ただし、ファイル名は気にしない。 * Groonga::Hash.create(:name => "Bookmarks", * :persistent => true) * * #それぞれのレコードに512バイトの値を格納できる無名一時テーブルを生成する。 * Groonga::Hash.create(:value => 512) * * #キーとして文字列を使用する無名一時テーブルを生成する。 * Groonga::Hash.create(:key_type => Groonga::Type::SHORT_TEXT) * * #キーとして文字列を使用する無名一時テーブルを生成する。 * (キーの種類を表すオブジェクトは文字列で指定。) * Groonga::Hash.create(:key_type => "ShortText") * * #キーとしてBookmarksテーブルのレコードを使用す * る無名一時テーブルを生成する。 * bookmarks = Groonga::Hash.create(:name => "Bookmarks") * Groonga::Hash.create(:key_type => bookmarks) * * #キーとしてBookmarksテーブルのレコードを使用す * #る無名一時テーブルを生成する。(テーブルは文字列で指定。) * Groonga::Hash.create(:name => "Bookmarks") * Groonga::Hash.create(:key_type => "Bookmarks") * * #全文検索用のトークンをバイグラムで切り出す無名一時テーブ * #ルを生成する。 * bookmarks = Groonga::Hash.create(:name => "Bookmarks") * bookmarks.define_column("comment", "Text") * terms = Groonga::Hash.create(:name => "Terms", * :default_tokenizer => "TokenBigram") * terms.define_index_column("content", bookmarks, * :source => "Bookmarks.comment") * * @overload create(options={}) * @return [Groonga::Hash] * @!macro hash.create.options * @param [::Hash] options The name and value * pairs. Omitted names are initialized as the default value * @option options [Groonga::Context] :context (Groonga::Context.default) * テーブルが利用する {Groonga::Context} 。省略すると * {Groonga::Context.default} を用いる。 * @option options [Groonga::Context#[]] :name * テーブルの名前。名前をつけると、 {Groonga::Context#[]} に名 * 前を指定してテーブルを取得することができる。省略すると * 無名テーブルになり、テーブルIDでのみ取得できる。 * @option options [Groonga::Context#[]] :path * テーブルを保存するパス。パスを指定すると永続テーブルとな * り、プロセス終了後もレコードは保持される。次回起動時に * {Groonga::Context#[]} で保存されたレコードを利用することが * できる。省略すると一時テーブルになり、プロセスが終了する * とレコードは破棄される。 * @option options :persistent * +true+ を指定すると永続テーブルとなる。 +path+ を省略した * 場合は自動的にパスが付加される。 +:context+ で指定した * {Groonga::Context} に結びついているデータベースが一時デー * タベースの場合は例外が発生する。 * * @option options :key_normalize (false) Keys are normalized * if this value is @true@. * * @deprecated Use @:normalizer => "NormalizerAuto"@ instead. * * @option options :key_type * キーの種類を示すオブジェクトを指定する。キーの種類には型 * 名("Int32"や"ShortText"など)または {Groonga::Type} または * テーブル( {Groonga::Array} 、 {Groonga::Hash} 、 * {Groonga::PatriciaTrie} のどれか)を指定する。 * * {Groonga::Type} を指定した場合は、その型が示す範囲の値をキー * として使用する。ただし、キーの最大サイズは4096バイトで * あるため、 {Groonga::Type::TEXT} や {Groonga::Type::LONG_TEXT} * は使用できない。 * * テーブルを指定した場合はレコードIDをキーとして使用する。 * 指定したテーブルの {Groonga::Record} をキーとして使用するこ * ともでき、その場合は自動的に {Groonga::Record} からレコード * IDを取得する。 * * 省略した場合はShortText型をキーとして使用する。この場合、 * 4096バイトまで使用可能である。 * @option options :value_type * 値の型を指定する。省略すると値のための領域を確保しない。 * 値を保存したい場合は必ず指定すること。 * * 参考: {Groonga::Type.new} * @option options [Groonga::IndexColumn] :default_tokenizer * {Groonga::IndexColumn} で使用するトークナイザを指定する。 * デフォルトでは何も設定されていないので、テーブルに * {Groonga::IndexColumn} を定義する場合は * @"TokenBigram"@ などを指定する必要がある。 * * @option options [::Array<String, Groonga::Procedure>, nil] * :token_filters (nil) The token filters to be used in the * table. * * @option options [Groonga::Record#n_sub_records] :sub_records * +true+ を指定すると {#group} でグループ化したときに、 * {Groonga::Record#n_sub_records} でグループに含まれるレコー * ドの件数を取得できる。 * * @option options [String, Groonga::Procedure, nil] :normalizer * The normalizer that is used by {Groonga::IndexColumn}. You * can specify this by normalizer name as String such as * @"NormalizerAuto"@ or normalizer object. * * @!macro hash.create.options * @overload create(options={}) * @yield [table] * @!macro hash.create.options */ static VALUE rb_grn_hash_s_create (int argc, VALUE *argv, VALUE klass) { grn_ctx *context; grn_obj *key_type = NULL, *value_type = NULL, *table; const char *name = NULL, *path = NULL; unsigned name_size = 0; grn_obj_flags flags = GRN_OBJ_TABLE_HASH_KEY; VALUE rb_table; VALUE options, rb_context, rb_name, rb_path, rb_persistent; VALUE rb_key_normalize, rb_key_type, rb_value_type, rb_default_tokenizer; VALUE rb_token_filters; VALUE rb_sub_records; VALUE rb_normalizer; rb_scan_args(argc, argv, "01", &options); rb_grn_scan_options(options, "context", &rb_context, "name", &rb_name, "path", &rb_path, "persistent", &rb_persistent, "key_normalize", &rb_key_normalize, "key_type", &rb_key_type, "value_type", &rb_value_type, "default_tokenizer", &rb_default_tokenizer, "token_filters", &rb_token_filters, "sub_records", &rb_sub_records, "normalizer", &rb_normalizer, NULL); context = rb_grn_context_ensure(&rb_context); if (!NIL_P(rb_name)) { name = StringValuePtr(rb_name); name_size = RSTRING_LEN(rb_name); flags |= GRN_OBJ_PERSISTENT; } if (!NIL_P(rb_path)) { path = StringValueCStr(rb_path); flags |= GRN_OBJ_PERSISTENT; } if (RVAL2CBOOL(rb_persistent)) flags |= GRN_OBJ_PERSISTENT; if (RVAL2CBOOL(rb_key_normalize)) flags |= GRN_OBJ_KEY_NORMALIZE; if (NIL_P(rb_key_type)) { key_type = grn_ctx_at(context, GRN_DB_SHORT_TEXT); } else { key_type = RVAL2GRNOBJECT(rb_key_type, &context); } if (!NIL_P(rb_value_type)) value_type = RVAL2GRNOBJECT(rb_value_type, &context); if (RVAL2CBOOL(rb_sub_records)) flags |= GRN_OBJ_WITH_SUBREC; table = grn_table_create(context, name, name_size, path, flags, key_type, value_type); if (!table) rb_grn_context_check(context, rb_ary_new_from_values(argc, argv)); rb_table = GRNOBJECT2RVAL(klass, context, table, GRN_TRUE); if (!NIL_P(rb_default_tokenizer)) rb_funcall(rb_table, rb_intern("default_tokenizer="), 1, rb_default_tokenizer); if (!NIL_P(rb_token_filters)) rb_funcall(rb_table, rb_intern("token_filters="), 1, rb_token_filters); if (!NIL_P(rb_normalizer)) rb_funcall(rb_table, rb_intern("normalizer="), 1, rb_normalizer); if (rb_block_given_p()) return rb_ensure(rb_yield, rb_table, rb_grn_object_close, rb_table); else return rb_table; }
/* FIXME: DON'T WORK!!! */ static VALUE rb_grn_view_sort (int argc, VALUE *argv, VALUE self) { VALUE rb_result = Qnil; #ifdef WIN32 rb_raise(rb_eNotImpError, "grn_view_add() isn't available on Windows."); #else grn_ctx *context = NULL; grn_obj *view; grn_obj *result; grn_table_sort_key *keys; int i, n_keys; int offset = 0, limit = -1; VALUE rb_keys, options; VALUE rb_offset, rb_limit; VALUE *rb_sort_keys; grn_table_cursor *cursor; VALUE exception; grn_obj id; rb_grn_table_deconstruct(SELF(self), &view, &context, NULL, NULL, NULL, NULL, NULL, NULL); rb_scan_args(argc, argv, "11", &rb_keys, &options); if (!RVAL2CBOOL(rb_obj_is_kind_of(rb_keys, rb_cArray))) rb_raise(rb_eArgError, "keys should be an array of key: <%s>", rb_grn_inspect(rb_keys)); n_keys = RARRAY_LEN(rb_keys); rb_sort_keys = RARRAY_PTR(rb_keys); keys = ALLOCA_N(grn_table_sort_key, n_keys); for (i = 0; i < n_keys; i++) { VALUE rb_sort_options, rb_key, rb_resolved_key, rb_order; if (RVAL2CBOOL(rb_obj_is_kind_of(rb_sort_keys[i], rb_cHash))) { rb_sort_options = rb_sort_keys[i]; } else if (RVAL2CBOOL(rb_obj_is_kind_of(rb_sort_keys[i], rb_cArray))) { rb_sort_options = rb_hash_new(); rb_hash_aset(rb_sort_options, RB_GRN_INTERN("key"), rb_ary_entry(rb_sort_keys[i], 0)); rb_hash_aset(rb_sort_options, RB_GRN_INTERN("order"), rb_ary_entry(rb_sort_keys[i], 1)); } else { rb_sort_options = rb_hash_new(); rb_hash_aset(rb_sort_options, RB_GRN_INTERN("key"), rb_sort_keys[i]); } rb_grn_scan_options(rb_sort_options, "key", &rb_key, "order", &rb_order, NULL); if (RVAL2CBOOL(rb_obj_is_kind_of(rb_key, rb_cString))) { rb_resolved_key = rb_grn_table_get_column(self, rb_key); } else { rb_resolved_key = rb_key; } keys[i].key = RVAL2GRNOBJECT(rb_resolved_key, &context); if (!keys[i].key) { rb_raise(rb_eGrnNoSuchColumn, "no such column: <%s>: <%s>", rb_grn_inspect(rb_key), rb_grn_inspect(self)); } if (NIL_P(rb_order)) { keys[i].flags = 0; } else if (rb_grn_equal_option(rb_order, "desc") || rb_grn_equal_option(rb_order, "descending")) { keys[i].flags = GRN_TABLE_SORT_DESC; } else if (rb_grn_equal_option(rb_order, "asc") || rb_grn_equal_option(rb_order, "ascending")) { keys[i].flags = GRN_TABLE_SORT_ASC; } else { rb_raise(rb_eArgError, "order should be one of " "[nil, :desc, :descending, :asc, :ascending]: %s", rb_grn_inspect(rb_order)); } } rb_grn_scan_options(options, "offset", &rb_offset, "limit", &rb_limit, NULL); if (!NIL_P(rb_offset)) offset = NUM2INT(rb_offset); if (!NIL_P(rb_limit)) limit = NUM2INT(rb_limit); result = grn_table_create(context, NULL, 0, NULL, GRN_TABLE_VIEW, NULL, NULL); /* use n_records that is return value from grn_table_sort() when rroonga user become specifying output table. */ grn_table_sort(context, view, offset, limit, result, keys, n_keys); exception = rb_grn_context_to_exception(context, self); if (!NIL_P(exception)) { grn_obj_unlink(context, result); rb_exc_raise(exception); } rb_result = rb_ary_new(); cursor = grn_table_cursor_open(context, result, NULL, 0, NULL, 0, 0, -1, GRN_CURSOR_ASCENDING); GRN_TEXT_INIT(&id, 0); while (grn_table_cursor_next_o(context, cursor, &id) == GRN_SUCCESS) { rb_ary_push(rb_result, rb_grn_view_record_new(self, &id)); } GRN_OBJ_FIN(context, &id); grn_table_cursor_close(context, cursor); grn_obj_unlink(context, result); #endif return rb_result; }
/* * call-seq: * column.select(options) {|record| ...} -> Groonga::Hash * column.select(query, options) -> Groonga::Hash * column.select(expression, options) -> Groonga::Hash * * カラムが所属するテーブルからブロックまたは文字列で指定し * た条件にマッチするレコードを返す。返されたテーブルには * +expression+という特異メソッドがあり、指定した条件を表し * ているGroonga::Expressionを取得できる。 * Groonga::Expression#snippetを使うことにより、指定した条件 * 用のスニペットを簡単に生成できる。 * * results = description_column.select do |column| * column =~ "groonga" * end * snippet = results.expression.snippet([["<em>", "</em>"]]) * results.each do |record| * puts "#{record['name']}の説明文の中で「groonga」が含まれる部分" * snippet.execute(record["description"].each do |snippet| * puts "---" * puts "#{snippet}..." * puts "---" * end * end * * 出力例 * Ruby/groongaの説明文の中で「groonga」が含まれる部分 * --- * Ruby/<em>groonga</em>は<em>groonga</em>のいわゆるDB-APIの層の... * --- * * _query_には「[カラム名]:[演算子][値]」という書式で条件を * 指定する。演算子は以下の通り。 * * [なし] * [カラム値] == [値] * [<tt>!</tt>] * [カラム値] != [値] * [<tt><</tt>] * [カラム値] < [値] * [<tt>></tt>] * [カラム値] > [値] * [<tt><=</tt>] * [カラム値] <= [値] * [<tt>>=</tt>] * [カラム値] >= [値] * [<tt>@</tt>] * [カラム値]が[値]を含んでいるかどうか * * 例: * "groonga" # _column_カラムの値が"groonga"のレコードにマッチ * "name:daijiro" # _column_カラムが属しているテーブルの * # "name"カラムの値が"daijiro"のレコードにマッチ * "description:@groonga" # _column_カラムが属しているテーブルの * # "description"カラムが * # "groonga"を含んでいるレコードにマッチ * * _expression_には既に作成済みのGroonga::Expressionを渡す * * ブロックで条件を指定する場合は * Groonga::ColumnExpressionBuilderを参照。 * * _options_に指定可能な値は以下の通り。 * * [+:operator+] * マッチしたレコードをどのように扱うか。指定可能な値は以 * 下の通り。省略した場合はGroonga::Operator::OR。 * * [Groonga::Operator::OR] * マッチしたレコードを追加。すでにレコードが追加され * ている場合は何もしない。 * [Groonga::Operator::AND] * マッチしたレコードのスコアを増加。マッチしなかった * レコードを削除。 * [Groonga::Operator::BUT] * マッチしたレコードを削除。 * [Groonga::Operator::ADJUST] * マッチしたレコードのスコアを増加。 * * [+:result+] * 検索結果を格納するテーブル。マッチしたレコードが追加さ * れていく。省略した場合は新しくテーブルを作成して返す。 * * [+:name+] * 条件の名前。省略した場合は名前を付けない。 * * [+:syntax+] * _query_の構文。省略した場合は+:query+。 * * 参考: Groonga::Expression#parse. * * [+:allow_pragma+] * query構文時にプラグマを利用するかどうか。省略した場合は * 利用する。 * * 参考: Groonga::Expression#parse. * * [+:allow_column+] * query構文時にカラム指定を利用するかどうか。省略した場合 * は利用する。 * * 参考: Groonga::Expression#parse. * * [+:allow_update+] * script構文時に更新操作を利用するかどうか。省略した場合 * は利用する。 * * 参考: Groonga::Expression#parse. */ static VALUE rb_grn_column_select (int argc, VALUE *argv, VALUE self) { grn_ctx *context; grn_obj *table, *column, *result, *expression; grn_operator operator = GRN_OP_OR; VALUE options; VALUE rb_query, condition_or_options; VALUE rb_name, rb_operator, rb_result, rb_syntax; VALUE rb_allow_pragma, rb_allow_column, rb_allow_update; VALUE builder; VALUE rb_expression = Qnil; rb_query = Qnil; rb_scan_args(argc, argv, "02", &condition_or_options, &options); rb_grn_column_deconstruct(SELF(self), &column, &context, NULL, NULL, NULL, NULL, NULL); table = grn_column_table(context, column); if (RVAL2CBOOL(rb_obj_is_kind_of(condition_or_options, rb_cString))) { rb_query = condition_or_options; } else if (RVAL2CBOOL(rb_obj_is_kind_of(condition_or_options, rb_cGrnExpression))) { rb_expression = condition_or_options; } else { if (!NIL_P(options)) rb_raise(rb_eArgError, "should be [query_string, option_hash], " "[expression, option_hash] " "or [option_hash]: %s", rb_grn_inspect(rb_ary_new4(argc, argv))); options = condition_or_options; } rb_grn_scan_options(options, "operator", &rb_operator, "result", &rb_result, "name", &rb_name, "syntax", &rb_syntax, "allow_pragma", &rb_allow_pragma, "allow_column", &rb_allow_column, "allow_update", &rb_allow_update, NULL); if (!NIL_P(rb_operator)) operator = NUM2INT(rb_operator); if (NIL_P(rb_result)) { result = grn_table_create(context, NULL, 0, NULL, GRN_TABLE_HASH_KEY | GRN_OBJ_WITH_SUBREC, table, 0); rb_result = GRNTABLE2RVAL(context, result, GRN_TRUE); } else { result = RVAL2GRNTABLE(rb_result, &context); } if (NIL_P(rb_expression)) { builder = rb_grn_column_expression_builder_new(self, rb_name, rb_query); rb_funcall(builder, rb_intern("syntax="), 1, rb_syntax); rb_funcall(builder, rb_intern("allow_pragma="), 1, rb_allow_pragma); rb_funcall(builder, rb_intern("allow_column="), 1, rb_allow_column); rb_funcall(builder, rb_intern("allow_update="), 1, rb_allow_update); rb_expression = rb_grn_column_expression_builder_build(builder); } rb_grn_object_deconstruct(RB_GRN_OBJECT(DATA_PTR(rb_expression)), &expression, NULL, NULL, NULL, NULL, NULL); grn_table_select(context, table, expression, result, operator); rb_grn_context_check(context, self); rb_attr(rb_singleton_class(rb_result), rb_intern("expression"), GRN_TRUE, GRN_FALSE, GRN_FALSE); rb_iv_set(rb_result, "@expression", rb_expression); return rb_result; }
/* * _expression_ から {Groonga::Snippet} を生成する。 _tags_ には * キーワードの前後に挿入するタグの配列を以下のような形式で指定 * する。 * * <pre> * !!!ruby * [ * ["キーワード前に挿入する文字列1", "キーワード後に挿入する文字列1"], * ["キーワード前に挿入する文字列2", "キーワード後に挿入する文字列2"], * # ..., * ] * </pre> * * もし、1つのスニペットの中に _tags_ で指定したタグより多くの * キーワードが含まれている場合は、以下のように、また、先頭 * のタグから順番に使われる。 * * <pre> * !!!ruby * expression.parse("Ruby groonga 検索") * tags = [["<tag1>", "</tag1>"], ["<tag2>", "</tag2>"]] * snippet = expression.snippet(tags) * p snippet.execute("Rubyでgroonga使って全文検索、高速検索。") * # => ["<tag1>Ruby</tag1>で<tag2>groonga</tag2>" * # => "使って全文<tag1>検索</tag1>、高速<tag2>検索</tag2>。"] * </pre> * * @overload snippet(tags, options) * @param tags [Array<string>] キーワードの前後に挿入するタグの配列 * (詳細は上記を参照) * @param options [::Hash] The name and value * pairs. Omitted names are initialized as the default value. * @option options :normalize (false) * キーワード文字列・スニペット元の文字列を正規化するかど * うか。省略した場合は +false+ で正規化しない。 * @option options :skip_leading_spaces (false) * 先頭の空白を無視するかどうか。省略した場合は +false+ で無 * 視しない。 * @option options :width (100) * スニペット文字列の長さ。省略した場合は100文字。 * @option options :max_results (3) * 生成するスニペットの最大数。省略した場合は3。 * @option options :html_escape (false) * スニペット内の +<+, +>+, +&+, +"+ をHTMLエスケープするか * どうか。省略した場合は +false+ で、HTMLエスケープしない。 * @return [Groonga::Snippet] */ static VALUE rb_grn_expression_snippet (int argc, VALUE *argv, VALUE self) { grn_ctx *context = NULL; grn_obj *expression; grn_obj *snippet; VALUE options; VALUE rb_normalize, rb_skip_leading_spaces; VALUE rb_width, rb_max_results, rb_tags; VALUE rb_html_escape; VALUE *rb_tag_values; VALUE related_object; unsigned int i; int flags = GRN_SNIP_COPY_TAG; unsigned int width = 100; unsigned int max_results = 3; unsigned int n_tags = 0; char **open_tags = NULL; unsigned int *open_tag_lengths = NULL; char **close_tags = NULL; unsigned int *close_tag_lengths = NULL; grn_snip_mapping *mapping = NULL; rb_grn_expression_deconstruct(SELF(self), &expression, &context, NULL, NULL, NULL, NULL, NULL); rb_scan_args(argc, argv, "11", &rb_tags, &options); rb_grn_scan_options(options, "normalize", &rb_normalize, "skip_leading_spaces", &rb_skip_leading_spaces, "width", &rb_width, "max_results", &rb_max_results, "html_escape", &rb_html_escape, NULL); if (TYPE(rb_tags) != T_ARRAY) { rb_raise(rb_eArgError, "tags should be " "[\"open_tag\", \"close_tag\"] or " "[[\"open_tag1\", \"close_tag1\"], ...]: %s", rb_grn_inspect(rb_tags)); } if (TYPE(RARRAY_PTR(rb_tags)[0]) == T_STRING) { rb_tags = rb_ary_new3(1, rb_tags); } rb_tag_values = RARRAY_PTR(rb_tags); n_tags = RARRAY_LEN(rb_tags); open_tags = ALLOCA_N(char *, n_tags); open_tag_lengths = ALLOCA_N(unsigned int, n_tags); close_tags = ALLOCA_N(char *, n_tags); close_tag_lengths = ALLOCA_N(unsigned int, n_tags); for (i = 0; i < n_tags; i++) { VALUE *tag_pair; if (TYPE(rb_tag_values[i]) != T_ARRAY || RARRAY_LEN(rb_tag_values[i]) != 2) { rb_raise(rb_eArgError, "tags should be " "[\"open_tag\", \"close_tag\"] or" "[[\"open_tag1\", \"close_tag1\"], ...]: %s", rb_grn_inspect(rb_tags)); } tag_pair = RARRAY_PTR(rb_tag_values[i]); open_tags[i] = StringValuePtr(tag_pair[0]); open_tag_lengths[i] = RSTRING_LEN(tag_pair[0]); close_tags[i] = StringValuePtr(tag_pair[1]); close_tag_lengths[i] = RSTRING_LEN(tag_pair[1]); } if (RVAL2CBOOL(rb_normalize)) flags |= GRN_SNIP_NORMALIZE; if (RVAL2CBOOL(rb_skip_leading_spaces)) flags |= GRN_SNIP_SKIP_LEADING_SPACES; if (!NIL_P(rb_width)) width = NUM2UINT(rb_width); if (!NIL_P(rb_max_results)) max_results = NUM2UINT(rb_max_results); if (RVAL2CBOOL(rb_html_escape)) mapping = (grn_snip_mapping *)-1; snippet = grn_expr_snip(context, expression, flags, width, max_results, n_tags, (const char **)open_tags, open_tag_lengths, (const char **)close_tags, close_tag_lengths, mapping); related_object = rb_ary_new3(2, self, rb_ary_new4(argc, argv)); rb_grn_context_check(context, related_object); return GRNOBJECT2RVAL(Qnil, context, snippet, GRN_TRUE); }
/* * 文字列 _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_new3(2, self, rb_ary_new4(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; }
/* * 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; }
static grn_table_cursor * rb_grn_patricia_trie_open_grn_prefix_cursor (int argc, VALUE *argv, VALUE self, grn_ctx **context) { grn_obj *table; grn_table_cursor *cursor; void *prefix = NULL; unsigned prefix_size = 0; int offset = 0, limit = -1; int flags = GRN_CURSOR_PREFIX; VALUE options, rb_prefix, rb_key_bytes, rb_key_bits; VALUE rb_order, rb_order_by; VALUE rb_greater_than, rb_less_than, rb_offset, rb_limit; rb_grn_table_deconstruct((RbGrnTable *)SELF(self), &table, context, NULL, NULL, NULL, NULL, NULL, NULL); rb_scan_args(argc, argv, "11", &rb_prefix, &options); rb_grn_scan_options(options, "key_bytes", &rb_key_bytes, "key_bites", &rb_key_bits, "offset", &rb_offset, "limit", &rb_limit, "order", &rb_order, "order_by", &rb_order_by, "greater_than", &rb_greater_than, "less_than", &rb_less_than, NULL); prefix = StringValuePtr(rb_prefix); if (!NIL_P(rb_key_bytes) && !NIL_P(rb_key_bits)) { rb_raise(rb_eArgError, "should not specify both :key_bytes and :key_bits once: %s", rb_grn_inspect(rb_ary_new4(argc, argv))); } else if (!NIL_P(rb_key_bytes)) { prefix_size = NUM2UINT(rb_key_bytes); } else if (!NIL_P(rb_key_bits)) { prefix_size = NUM2UINT(rb_key_bits); flags |= GRN_CURSOR_SIZE_BY_BIT; } else { prefix_size = RSTRING_LEN(rb_prefix); } if (!NIL_P(rb_offset)) offset = NUM2INT(rb_offset); if (!NIL_P(rb_limit)) limit = NUM2INT(rb_limit); if (NIL_P(rb_order)) { } else if (rb_grn_equal_option(rb_order, "asc") || rb_grn_equal_option(rb_order, "ascending")) { flags |= GRN_CURSOR_ASCENDING; } else if (rb_grn_equal_option(rb_order, "desc") || rb_grn_equal_option(rb_order, "descending")) { flags |= GRN_CURSOR_DESCENDING; } else { rb_raise(rb_eArgError, "order should be one of " "[:asc, :ascending, :desc, :descending]: %s", rb_grn_inspect(rb_order)); } if (NIL_P(rb_order_by)) { } else if (rb_grn_equal_option(rb_order_by, "id")) { flags |= GRN_CURSOR_BY_ID; } else if (rb_grn_equal_option(rb_order_by, "key")) { if (table->header.type != GRN_TABLE_PAT_KEY) { rb_raise(rb_eArgError, "order_by => :key is available " "only for Groonga::PatriciaTrie: %s", rb_grn_inspect(self)); } flags |= GRN_CURSOR_BY_KEY; } else { rb_raise(rb_eArgError, "order_by should be one of [:id%s]: %s", table->header.type == GRN_TABLE_PAT_KEY ? ", :key" : "", rb_grn_inspect(rb_order_by)); } if (RVAL2CBOOL(rb_greater_than)) flags |= GRN_CURSOR_GT; if (RVAL2CBOOL(rb_less_than)) flags |= GRN_CURSOR_LT; cursor = grn_table_cursor_open(*context, table, prefix, prefix_size, NULL, 0, offset, limit, flags); rb_grn_context_check(*context, self); return cursor; }