/* -*- coding: utf-8; mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Copyright (C) 2009-2015 Kouhei Sutou Copyright (C) 2014 Masafumi Yokoyama 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 "rb-grn.h" #define SELF(object) ((RbGrnExpression *)DATA_PTR(object)) VALUE rb_cGrnExpression; /* * Document-class: Groonga::Expression < Groonga::Object * * 検索条件やデータベースへの操作を表現するオブジェクト。 * */ void rb_grn_expression_finalizer (grn_ctx *context, grn_obj *object, RbGrnExpression *rb_grn_expression) { if (context && rb_grn_expression->value) grn_obj_unlink(context, rb_grn_expression->value); rb_grn_context_unregister_floating_object(RB_GRN_OBJECT(rb_grn_expression)); rb_grn_expression->value = NULL; } void rb_grn_expression_bind (RbGrnExpression *rb_grn_expression, grn_ctx *context, grn_obj *expression) { RbGrnObject *rb_grn_object; rb_grn_object = RB_GRN_OBJECT(rb_grn_expression); rb_grn_expression->value = grn_obj_open(context, GRN_BULK, 0, rb_grn_object->range_id); } void rb_grn_expression_deconstruct (RbGrnExpression *rb_grn_expression, grn_obj **expression, grn_ctx **context, grn_id *domain_id, grn_obj **domain, grn_obj **value, grn_id *range_id, grn_obj **range) { RbGrnObject *rb_grn_object; rb_grn_object = RB_GRN_OBJECT(rb_grn_expression); rb_grn_object_deconstruct(rb_grn_object, expression, context, domain_id, domain, range_id, range); if (value) *value = rb_grn_expression->value; } static VALUE rb_grn_expression_initialize (int argc, VALUE *argv, VALUE self) { grn_ctx *context = NULL; grn_obj *expression; VALUE options, rb_context, rb_name; char *name = NULL; unsigned name_size = 0; rb_scan_args(argc, argv, "01", &options); rb_grn_scan_options(options, "context", &rb_context, "name", &rb_name, NULL); context = rb_grn_context_ensure(&rb_context); if (!NIL_P(rb_name)) { name = StringValuePtr(rb_name); name_size = RSTRING_LEN(rb_name); } expression = grn_expr_create(context, name, name_size); rb_grn_context_check(context, self); rb_grn_object_assign(Qnil, self, rb_context, context, expression); rb_grn_context_register_floating_object(DATA_PTR(self)); rb_iv_set(self, "@objects", rb_ary_new()); return Qnil; } static VALUE rb_grn_expression_inspect (VALUE self) { grn_ctx *context = NULL; grn_obj *expression; grn_obj inspected; VALUE rb_inspected; rb_grn_expression_deconstruct(SELF(self), &expression, &context, NULL, NULL, NULL, NULL, NULL); rb_inspected = rb_str_new_cstr(""); rb_grn_object_inspect_header(self, rb_inspected); GRN_TEXT_INIT(&inspected, 0); grn_inspect(context, &inspected, expression); grn_bulk_truncate(context, &inspected, GRN_TEXT_LEN(&inspected) - 2); { size_t prefix_length; const char *content; size_t content_length; prefix_length = strlen("# * !!!ruby * [ * ["キーワード前に挿入する文字列1", "キーワード後に挿入する文字列1"], * ["キーワード前に挿入する文字列2", "キーワード後に挿入する文字列2"], * # ..., * ] * * * もし、1つのスニペットの中に _tags_ で指定したタグより多くの * キーワードが含まれている場合は、以下のように、また、先頭 * のタグから順番に使われる。 * *
 * !!!ruby
 *   expression.parse("Ruby groonga 検索")
 *   tags = [["", ""], ["", ""]]
 *   snippet = expression.snippet(tags)
 *   p snippet.execute("Rubyでgroonga使って全文検索、高速検索。")
 *      # => ["Rubygroonga"
 *      # =>  "使って全文検索、高速検索。"]
 * 
* * @overload snippet(tags, options) * @param tags [Array] キーワードの前後に挿入するタグの配列 * (詳細は上記を参照) * @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_new_from_args(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_new_from_args(2, self, rb_ary_new_from_values(argc, argv)); rb_grn_context_check(context, related_object); return GRNOBJECT2RVAL(Qnil, context, snippet, GRN_TRUE); } /* * Extracts keywords from _expression_. The keywords order isn't * guaranteed. * * @example * expression.parse("Ruby OR Groonga") * expression.keywords #=> ["Groonga", "Ruby"] * * @overload keywords * @return [::Array] the extracted keywords * * @since 4.0.6 */ static VALUE rb_grn_expression_get_keywords (VALUE self) { grn_ctx *context = NULL; grn_obj *expression; grn_obj keywords; VALUE rb_keywords = rb_ary_new(); rb_grn_expression_deconstruct(SELF(self), &expression, &context, NULL, NULL, NULL, NULL, NULL); GRN_PTR_INIT(&keywords, GRN_OBJ_VECTOR, GRN_ID_NIL); grn_expr_get_keywords(context, expression, &keywords); { int i, n_keywords; n_keywords = GRN_BULK_VSIZE(&keywords) / sizeof(grn_obj *); for (i = 0; i < n_keywords; i++) { grn_obj *keyword = GRN_PTR_VALUE_AT(&keywords, i); rb_ary_push(rb_keywords, GRNBULK2RVAL(context, keyword, NULL, self)); } } GRN_OBJ_FIN(context, &keywords); return rb_keywords; } /* * Estimates the number of matched records when `expression` is * executed. * * Note that the estimated size isn't correct value. It's just * estimated size. * * @example * expression.parse("Ruby OR Groonga") * expression.estimate_size # => 10 * * @overload estimate_size * @return [Integer] the estimated number of matched records when * `expression` is executed. * * @since 5.0.1 */ static VALUE rb_grn_expression_estimate_size (VALUE self) { grn_ctx *context = NULL; grn_obj *expression; unsigned int size; rb_grn_expression_deconstruct(SELF(self), &expression, &context, NULL, NULL, NULL, NULL, NULL); size = grn_expr_estimate_size(context, expression); return UINT2NUM(size); } void rb_grn_init_expression (VALUE mGrn) { rb_cGrnExpression = rb_define_class_under(mGrn, "Expression", rb_cGrnObject); rb_define_method(rb_cGrnExpression, "initialize", rb_grn_expression_initialize, -1); rb_define_method(rb_cGrnExpression, "inspect", rb_grn_expression_inspect, 0); rb_define_method(rb_cGrnExpression, "define_variable", rb_grn_expression_define_variable, -1); rb_define_method(rb_cGrnExpression, "append_object", rb_grn_expression_append_object, -1); rb_define_method(rb_cGrnExpression, "append_constant", rb_grn_expression_append_constant, -1); rb_define_method(rb_cGrnExpression, "append_operation", rb_grn_expression_append_operation, 2); rb_define_method(rb_cGrnExpression, "parse", rb_grn_expression_parse, -1); rb_define_method(rb_cGrnExpression, "execute", rb_grn_expression_execute, 0); rb_define_method(rb_cGrnExpression, "compile", rb_grn_expression_compile, 0); rb_define_method(rb_cGrnExpression, "dump_plan", rb_grn_expression_dump_plan, 0); rb_define_method(rb_cGrnExpression, "[]", rb_grn_expression_array_reference, 1); rb_define_method(rb_cGrnExpression, "snippet", rb_grn_expression_snippet, -1); rb_define_method(rb_cGrnExpression, "keywords", rb_grn_expression_get_keywords, 0); rb_define_method(rb_cGrnExpression, "estimate_size", rb_grn_expression_estimate_size, 0); }