diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 18:07:14 +0000 |
commit | a175314c3e5827eb193872241446f2f8f5c9d33c (patch) | |
tree | cd3d60ca99ae00829c52a6ca79150a5b6e62528b /storage/mroonga/vendor/groonga/lib/mrb/scripts | |
parent | Initial commit. (diff) | |
download | mariadb-10.5-upstream.tar.xz mariadb-10.5-upstream.zip |
Adding upstream version 1:10.5.12.upstream/1%10.5.12upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'storage/mroonga/vendor/groonga/lib/mrb/scripts')
66 files changed, 3582 insertions, 0 deletions
diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/Makefile.am new file mode 100644 index 00000000..835d881b --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/Makefile.am @@ -0,0 +1,16 @@ +SUBDIRS = \ + command_line \ + context \ + expression_tree \ + initialize \ + logger \ + query_logger + +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/accessor.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/accessor.rb new file mode 100644 index 00000000..3ae08fb6 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/accessor.rb @@ -0,0 +1,5 @@ +module Groonga + class Accessor + include Indexable + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/backtrace_entry.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/backtrace_entry.rb new file mode 100644 index 00000000..34f95e96 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/backtrace_entry.rb @@ -0,0 +1,28 @@ +module Groonga + class BacktraceEntry + class << self + def parse(entry) + match_data = /:(\d+):?/.match(entry) + file = match_data.pre_match + line = match_data[1].to_i + detail_match_data = /\A(in )?(\S+)\s*/.match(match_data.post_match) + if detail_match_data[1] + method = detail_match_data[2] + message = detail_match_data.post_match + else + method = "" + message = match_data.post_match + end + new(file, line, method, message) + end + end + + attr_reader :file, :line, :method, :message + def initialize(file, line, method, message) + @file = file + @line = line + @method = method + @message = message + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command.rb new file mode 100644 index 00000000..26dfe68c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command.rb @@ -0,0 +1,64 @@ +module Groonga + class Command + @@classes = {} + class << self + def register_class(name, klass) + @@classes[name] = klass + end + + def find_class(name) + @@classes[name] + end + end + + private + def context + @context ||= Context.instance + end + + def writer + @writer ||= context.writer + end + + def query_logger + @query_logger ||= context.query_logger + end + + def cache_key(input) + nil + end + + def cache_output(key, options={}) + if key.nil? + yield + else + cache = Cache.current + cached_value = cache.fetch(key) + if cached_value + context.output = cached_value + query_logger.log(:cache, ":", "cache(#{cached_value.bytesize})") + else + yield + cache.update(key, context.output) if options[:update] + end + end + end + + def run_internal(input) + begin + options = { + :update => (input["cache"] != "no"), + } + cache_output(cache_key(input), options) do + run_body(input) + end + rescue GroongaError => groonga_error + context.set_groonga_error(groonga_error) + nil + rescue => error + context.record_error(:command_error, error) + nil + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_input.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_input.rb new file mode 100644 index 00000000..779edb47 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_input.rb @@ -0,0 +1,15 @@ +module Groonga + class CommandInput + include Enumerable + + def each + args = arguments + arg = Record.new(args, nil) + args.each do |id| + arg.id = id + key = arg.key + yield(key, self[key]) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/Makefile.am new file mode 100644 index 00000000..8d580810 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_command_linedir = $(ruby_scriptsdir)/command_line +ruby_scripts_command_line_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/grndb.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/grndb.rb new file mode 100644 index 00000000..edd16d58 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/grndb.rb @@ -0,0 +1,452 @@ +module Groonga + module CommandLine + class Grndb + def initialize(argv) + @program_path, *@arguments = argv + @succeeded = true + @database_path = nil + end + + def run + command_line_parser = create_command_line_parser + options = nil + begin + options = command_line_parser.parse(@arguments) + rescue Slop::Error => error + $stderr.puts(error.message) + $stderr.puts + $stderr.puts(command_line_parser.help_message) + return false + end + @succeeded + end + + private + def create_command_line_parser + program_name = File.basename(@program_path) + parser = CommandLineParser.new(program_name) + + parser.add_command("check") do |command| + command.description = "Check database" + + options = command.options + options.banner += " DB_PATH" + options.string("--target", "Check only the target object.") + + command.add_action do |options| + open_database(command, options) do |database, rest_arguments| + check(database, options, rest_arguments) + end + end + end + + parser.add_command("recover") do |command| + command.description = "Recover database" + + options = command.options + options.banner += " DB_PATH" + options.boolean("--force-truncate", "Force to truncate corrupted objects.") + + command.add_action do |options| + open_database(command, options) do |database, rest_arguments| + recover(database, options, rest_arguments) + end + end + end + + parser + end + + def open_database(command, options) + arguments = options.arguments + if arguments.empty? + $stderr.puts("Database path is missing") + $stderr.puts + $stderr.puts(command.help_message) + @succeesed = false + return + end + + database = nil + @database_path, *rest_arguments = arguments + begin + database = Database.open(@database_path) + rescue Error => error + $stderr.puts("Failed to open database: <#{@database_path}>") + $stderr.puts(error.message) + @succeeded = false + return + end + + begin + yield(database, rest_arguments) + ensure + database.close + end + end + + def failed(*messages) + messages.each do |message| + $stderr.puts(message) + end + @succeeded = false + end + + def recover(database, options, arguments) + recoverer = Recoverer.new + recoverer.database = database + recoverer.force_truncate = options[:force_truncate] + begin + recoverer.recover + rescue Error => error + failed("Failed to recover database: <#{@database_path}>", + error.message) + end + end + + def check(database, options, arguments) + checker = Checker.new + checker.program_path = @program_path + checker.database_path = @database_path + checker.database = database + checker.on_failure = lambda do |message| + failed(message) + end + + checker.check_database + + target_name = options[:target] + if target_name + checker.check_one(target_name) + else + checker.check_all + end + end + + class Checker + attr_writer :program_path + attr_writer :database_path + attr_writer :database + attr_writer :on_failure + + def initialize + @context = Context.instance + @checked = {} + end + + def check_database + check_database_orphan_inspect + check_database_locked + check_database_corrupt + check_database_dirty + end + + def check_one(target_name) + target = @context[target_name] + if target.nil? + exist_p = open_database_cursor do |cursor| + cursor.any? do + cursor.key == target_name + end + end + if exist_p + failed_to_open(target_name) + else + message = "[#{target_name}] Not exist." + failed(message) + end + return + end + + check_object_recursive(target) + end + + def check_all + open_database_cursor do |cursor| + cursor.each do |id| + next if ID.builtin?(id) + next if builtin_object_name?(cursor.key) + next if @context[id] + failed_to_open(cursor.key) + end + end + + @database.each do |object| + check_object(object) + end + end + + private + def check_database_orphan_inspect + open_database_cursor do |cursor| + cursor.each do |id| + if cursor.key == "inspect" and @context[id].nil? + message = + "Database has orphan 'inspect' object. " + + "Remove it by '#{@program_path} recover #{@database_path}'." + failed(message) + break + end + end + end + end + + def check_database_locked + return unless @database.locked? + + message = + "Database is locked. " + + "It may be broken. " + + "Re-create the database." + failed(message) + end + + def check_database_corrupt + return unless @database.corrupt? + + message = + "Database is corrupt. " + + "Re-create the database." + failed(message) + end + + def check_database_dirty + return unless @database.dirty? + + last_modified = @database.last_modified + if File.stat(@database.path).mtime > last_modified + return + end + + open_database_cursor do |cursor| + cursor.each do |id| + next if ID.builtin?(id) + path = "%s.%07x" % [@database.path, id] + next unless File.exist?(path) + return if File.stat(path).mtime > last_modified + end + end + + message = + "Database wasn't closed successfully. " + + "It may be broken. " + + "Re-create the database." + failed(message) + end + + def check_object(object) + return if @checked.key?(object.id) + @checked[object.id] = true + + check_object_locked(object) + check_object_corrupt(object) + end + + def check_object_locked(object) + case object + when IndexColumn + return unless object.locked? + message = + "[#{object.name}] Index column is locked. " + + "It may be broken. " + + "Re-create index by '#{@program_path} recover #{@database_path}'." + failed(message) + when Column + return unless object.locked? + name = object.name + message = + "[#{name}] Data column is locked. " + + "It may be broken. " + + "(1) Truncate the column (truncate #{name}) or " + + "clear lock of the column (lock_clear #{name}) " + + "and (2) load data again." + failed(message) + when Table + return unless object.locked? + name = object.name + message = + "[#{name}] Table is locked. " + + "It may be broken. " + + "(1) Truncate the table (truncate #{name}) or " + + "clear lock of the table (lock_clear #{name}) " + + "and (2) load data again." + failed(message) + end + end + + def check_object_corrupt(object) + case object + when IndexColumn + return unless object.corrupt? + message = + "[#{object.name}] Index column is corrupt. " + + "Re-create index by '#{@program_path} recover #{@database_path}'." + failed(message) + when Column + return unless object.corrupt? + name = object.name + message = + "[#{name}] Data column is corrupt. " + + "(1) Truncate the column (truncate #{name} or " + + "'#{@program_path} recover --force-truncate #{@database_path}') " + + "and (2) load data again." + failed(message) + when Table + return unless object.corrupt? + name = object.name + message = + "[#{name}] Table is corrupt. " + + "(1) Truncate the table (truncate #{name} or " + + "'#{@program_path} recover --force-truncate #{@database_path}') " + + "and (2) load data again." + failed(message) + end + end + + def check_object_recursive(target) + return if @checked.key?(target.id) + + check_object(target) + case target + when Table + unless target.is_a?(Groonga::Array) + domain_id = target.domain_id + domain = @context[domain_id] + if domain.nil? + record = Record.new(@database, domain_id) + failed_to_open(record.key) + elsif domain.is_a?(Table) + check_object_recursive(domain) + end + end + + target.column_ids.each do |column_id| + column = @context[column_id] + if column.nil? + record = Record.new(@database, column_id) + failed_to_open(record.key) + else + check_object_recursive(column) + end + end + when FixedSizeColumn, VariableSizeColumn + range_id = target.range_id + range = @context[range_id] + if range.nil? + record = Record.new(@database, range_id) + failed_to_open(record.key) + elsif range.is_a?(Table) + check_object_recursive(range) + end + + lexicon_ids = [] + target.indexes.each do |index_info| + index = index_info.index + lexicon_ids << index.domain_id + check_object(index) + end + lexicon_ids.uniq.each do |lexicon_id| + lexicon = @context[lexicon_id] + if lexicon.nil? + record = Record.new(@database, lexicon_id) + failed_to_open(record.key) + else + check_object(lexicon) + end + end + when IndexColumn + range_id = target.range_id + range = @context[range_id] + if range.nil? + record = Record.new(@database, range_id) + failed_to_open(record.key) + return + end + check_object(range) + + target.source_ids.each do |source_id| + source = @context[source_id] + if source.nil? + record = Record.new(database, source_id) + failed_to_open(record.key) + elsif source.is_a?(Column) + check_object_recursive(source) + end + end + end + end + + def open_database_cursor(&block) + flags = + TableCursorFlags::ASCENDING | + TableCursorFlags::BY_ID + TableCursor.open(@database, :flags => flags, &block) + end + + def builtin_object_name?(name) + case name + when "inspect" + # Just for compatibility. It's needed for users who used + # Groonga master at between 2016-02-03 and 2016-02-26. + true + else + false + end + end + + def failed(message) + @on_failure.call(message) + end + + def failed_to_open(name) + message = + "[#{name}] Can't open object. " + + "It's broken. " + + "Re-create the object or the database." + failed(message) + end + end + + class Recoverer + attr_writer :database + attr_writer :force_truncate + + def initialize + @context = Context.instance + end + + def recover + if @force_truncate + truncate_corrupt_objects + end + @database.recover + end + + def truncate_corrupt_objects + @database.each do |object| + next unless object.corrupt? + logger = @context.logger + object_path = object.path + object_dirname = File.dirname(object_path) + object_basename = File.basename(object_path) + object.truncate + Dir.foreach(object_dirname) do |path| + if path.start_with?("#{object_basename}.") + begin + File.unlink("#{object_dirname}/#{path}") + message = "Corrupted <#{object_path}> related file is removed: <#{path}>" + $stdout.puts(message) + logger.log(Logger::Level::INFO.to_i, __FILE__, __LINE__, "truncate_corrupt_objects", message) + rescue Error => error + message = "Failed to remove file which is related to corrupted <#{object_path}>: <#{path}>" + $stderr.puts(message) + logger.log_error(message) + end + end + end + end + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/sources.am new file mode 100644 index 00000000..759948ee --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line/sources.am @@ -0,0 +1,2 @@ +RUBY_SCRIPT_FILES = \ + grndb.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line_parser.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line_parser.rb new file mode 100644 index 00000000..7dad55a7 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/command_line_parser.rb @@ -0,0 +1,168 @@ +module Slop + class MissingCommand < Error + end + + class UnknownCommand < Error + attr_reader :name + + def initialize(msg, name) + super(msg) + @name = name + end + end +end + +module Groonga + class CommandLineParser + def initialize(program_name=nil) + $0 ||= nil + @program_name = program_name || $0 + @commands = [] + @action_runner = ActionRunner.new + @options = Slop::Options.new + setup_options + end + + def options + yield(@options) if block_given? + @options + end + + def add_command(name) + command = Command.new(@program_name, name) + setup_common_options(command.options) + yield(command) + @commands << command + end + + def add_action(&action) + @action_runner.add(&action) + end + + def parse(command_line) + if @commands.empty? + result = @options.parse(command_line) + apply_actions(result) + return result + end + + if command_line.empty? + message = "Command is missing" + raise Slop::MissingCommand.new(message) + end + + first_argument = command_line.first + if first_argument.start_with?("-") + result = @options.parse(command_line) + apply_actions(result) + return result + end + + command_name = first_argument + command = find_command(command_name) + if command.nil? + message = "Unknown command: <#{command_name}>" + raise Slop::UnknownCommand.new(message, command_name) + end + + command.parse(command_line[1..-1]) + end + + def help_message + message = @options.to_s + @commands.each do |command| + message << "\n" + indent = " " * 4 + message << "#{command.description}:\n" if command.description + message << "#{indent}#{command.options.banner}\n" + end + message + end + + private + def setup_options + @options.banner = "Usage: #{@program_name} [OPTIONS]" + setup_common_options(@options) + @options.on("-h", "--help", "Display this help message.", + :tail => true) do + $stdout.puts(help_message) + end + end + + def setup_common_options(options) + options.string("--log-path", + "Change log path (#{Logger.default_path})", + default: Logger.default_path) + default_log_level = Logger.default_level + options.string("--log-level", + "Change log level (#{default_log_level.name})", + default: default_log_level) + end + + def find_command(name) + @commands.find do |command| + command.name == name + end + end + + def apply_actions(result) + @action_runner.run(result) unless result.help? + end + + class ActionRunner + def initialize + @actions = [] + end + + def add(&action) + @actions << action + end + + def run(*args) + @actions.each do |action| + action.call(*args) + end + end + end + + class Command + attr_reader :name + attr_reader :options + attr_accessor :description + def initialize(program_name, name) + @program_name = program_name + @name = name + @options = Slop::Options.new + setup_options + @description = nil + @action_runner = ActionRunner.new + end + + def add_action(&action) + @action_runner.add(&action) + end + + def parse(command_line) + result = @options.parse(command_line) + @action_runner.run(result) unless result.help? + result + end + + def help_message + message = "" + message << "Description: #{@description}\n" if @description + message << @options.to_s + message + end + + private + def setup_options + @options.banner = "Usage: #{@program_name} #{@name} [OPTIONS]" + @options.on("-h", "--help", "Display this help message.", + :tail => true) do + $stdout.puts(help_message) + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/context.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context.rb new file mode 100644 index 00000000..b6f59dc3 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context.rb @@ -0,0 +1,85 @@ +require "context/error_level" +require "context/rc" + +module Groonga + class Context + def guard(fallback=nil) + begin + yield + rescue => error + logger.log_error(error) + fallback + end + end + + def logger + @logger ||= Logger.new + end + + def query_logger + @query_logger ||= QueryLogger.new + end + + def writer + @writer ||= Writer.new + end + + def set_groonga_error(groonga_error) + set_error_raw(groonga_error.class.rc, + ErrorLevel::ERROR, + groonga_error.message, + groonga_error.backtrace) + end + + def record_error(rc, error) + rc = RC.find(rc) if rc.is_a?(Symbol) + set_error_raw(rc, ErrorLevel::ERROR, error.message, error.backtrace) + + logger.log_error(error) + end + + def with_command_version(version) + old_version = command_version + begin + self.command_version = version + yield + ensure + self.command_version = old_version + end + end + + def open_temporary(id) + if Thread.limit == 1 + need_close = !opened?(id) + else + need_close = false + end + object = self[id] + begin + yield(object) + ensure + if need_close and object and !object.closed? + case object + when Table, Column + object.close + end + end + end + end + + private + def set_error_raw(rc, error_level, message, backtrace) + self.rc = rc.to_i + self.error_level = error_level.to_i + + self.error_message = message + + if backtrace + entry = BacktraceEntry.parse(backtrace.first) + self.error_file = entry.file + self.error_line = entry.line + self.error_method = entry.method + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/Makefile.am new file mode 100644 index 00000000..8d862082 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_contextdir = $(ruby_scriptsdir)/context +ruby_scripts_context_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/error_level.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/error_level.rb new file mode 100644 index 00000000..c0685f16 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/error_level.rb @@ -0,0 +1,30 @@ +module Groonga + class Context + class ErrorLevel + @@names = {} + + class << self + def find(name) + @@names[name] + end + end + + attr_reader :name + def initialize(name, level) + @@names[name] = self + @name = name + @level = level + end + + def to_i + @level + end + + EMERGENCY = new(:emergency, 1) + ALERT = new(:alert, 2) + CRITICAL = new(:critical, 3) + ERROR = new(:error, 4) + WARNING = new(:warning, 5) + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/rc.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/rc.rb new file mode 100644 index 00000000..45187c22 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/rc.rb @@ -0,0 +1,205 @@ +module Groonga + class Context + class RC + @@codes = {} + @@names = {} + + class << self + def find(name_or_code) + if name_or_code.is_a?(Symbol) + @@names[name_or_code] || UNKNOWN_ERROR + else + @@codes[name_or_code] || UNKNOWN_ERROR + end + end + + def register(name, code, error_class) + rc = new(name, code, error_class) + @@codes[code] = rc + @@names[name] = rc + error_class.rc = rc if error_class + rc + end + end + + attr_reader :name + attr_reader :error_class + def initialize(name, code, error_class) + @name = name + @code = code + @error_class = error_class + end + + def to_i + @code + end + + SUCCESS = + register(:success, 0, nil) + END_OF_DATA = + register(:end_of_data, 1, EndOfData) + UNKNOWN_ERROR = + register(:unknown_error, -1, UnknownError) + OPERATION_NOT_PERMITTED = + register(:operation_not_permitted, -2, OperationNotPermitted) + NO_SUCH_FILE_OR_DIRECTORY = + register(:no_such_file_or_directory, -3, NoSuchFileOrDirectory) + NO_SUCH_PROCESS = + register(:no_such_process, -4, NoSuchProcess) + INTERRUPTED_FUNCTION_CALL = + register(:interrupted_function_call, -5, InterruptedFunctionCall) + INPUT_OUTPUT_ERROR = + register(:input_output_error, -6, InputOutputError) + NO_SUCH_DEVICE_OR_ADDRESS = + register(:no_such_device_or_address, -7, NoSuchDeviceOrAddress) + ARG_LIST_TOO_LONG = + register(:arg_list_too_long, -8, ArgListTooLong) + EXEC_FORMAT_ERROR = + register(:exec_format_error, -9, ExecFormatError) + BAD_FILE_DESCRIPTOR = + register(:bad_file_descriptor, -10, BadFileDescriptor) + NO_CHILD_PROCESSES = + register(:no_child_processes, -11, NoChildProcesses) + RESOURCE_TEMPORARILY_UNAVAILABLE = + register(:resource_temporarily_unavailable, -12, + ResourceTemporarilyUnavailable) + NOT_ENOUGH_SPACE = + register(:not_enough_space, -13, NotEnoughSpace) + PERMISSION_DENIED = + register(:permission_denied, -14, PermissionDenied) + BAD_ADDRESS = + register(:bad_address, -15, BadAddress) + RESOURCE_BUSY = + register(:resource_busy, -16, ResourceBusy) + FILE_EXISTS = + register(:file_exists, -17, FileExists) + IMPROPER_LINK = + register(:improper_link, -18, ImproperLink) + NO_SUCH_DEVICE = + register(:no_such_device, -19, NoSuchDevice) + NOT_DIRECTORY = + register(:not_directory, -20, NotDirectory) + IS_DIRECTORY = + register(:is_directory, -21, IsDirectory) + INVALID_ARGUMENT = + register(:invalid_argument, -22, InvalidArgument) + TOO_MANY_OPEN_FILES_IN_SYSTEM = + register(:too_many_open_files_in_system, -23, TooManyOpenFilesInSystem) + TOO_MANY_OPEN_FILES = + register(:too_many_open_files, -24, TooManyOpenFiles) + INAPPROPRIATE_IO_CONTROL_OPERATION = + register(:inappropriate_io_control_operation, -25, + InappropriateIOControlOperation) + FILE_TOO_LARGE = + register(:file_too_large, -26, FileTooLarge) + NO_SPACE_LEFT_ON_DEVICE = + register(:no_space_left_on_device, -27, NoSpaceLeftOnDevice) + INVALID_SEEK = + register(:invalid_seek, -28, InvalidSeek) + READ_ONLY_FILE_SYSTEM = + register(:read_only_file_system, -29, ReadOnlyFileSystem) + TOO_MANY_LINKS = + register(:too_many_links, -30, TooManyLinks) + BROKEN_PIPE = + register(:broken_pipe, -31, BrokenPipe) + DOMAIN_ERROR = + register(:domain_error, -32, DomainError) + RESULT_TOO_LARGE = + register(:result_too_large, -33, ResultTooLarge) + RESOURCE_DEADLOCK_AVOIDED = + register(:resource_deadlock_avoided, -34, ResourceDeadlockAvoided) + NO_MEMORY_AVAILABLE = + register(:no_memory_available, -35, NoMemoryAvailable) + FILENAME_TOO_LONG = + register(:filename_too_long, -36, FilenameTooLong) + NO_LOCKS_AVAILABLE = + register(:no_locks_available, -37, NoLocksAvailable) + FUNCTION_NOT_IMPLEMENTED = + register(:function_not_implemented, -38, FunctionNotImplemented) + DIRECTORY_NOT_EMPTY = + register(:directory_not_empty, -39, DirectoryNotEmpty) + ILLEGAL_BYTE_SEQUENCE = + register(:illegal_byte_sequence, -40, IllegalByteSequence) + SOCKET_NOT_INITIALIZED = + register(:socket_not_initialized, -41, SocketNotInitialized) + OPERATION_WOULD_BLOCK = + register(:operation_would_block, -42, OperationWouldBlock) + ADDRESS_IS_NOT_AVAILABLE = + register(:address_is_not_available, -43, AddressIsNotAvailable) + NETWORK_IS_DOWN = + register(:network_is_down, -44, NetworkIsDown) + NO_BUFFER = + register(:no_buffer, -45, NoBuffer) + SOCKET_IS_ALREADY_CONNECTED = + register(:socket_is_already_connected, -46, SocketIsAlreadyConnected) + SOCKET_IS_NOT_CONNECTED = + register(:socket_is_not_connected, -47, SocketIsNotConnected) + SOCKET_IS_ALREADY_SHUTDOWNED = + register(:socket_is_already_shutdowned, -48, SocketIsAlreadyShutdowned) + OPERATION_TIMEOUT = + register(:operation_timeout, -49, OperationTimeout) + CONNECTION_REFUSED = + register(:connection_refused, -50, ConnectionRefused) + RANGE_ERROR = + register(:range_error, -51, RangeError) + TOKENIZER_ERROR = + register(:tokenizer_error, -52, TokenizerError) + FILE_CORRUPT = + register(:file_corrupt, -53, FileCorrupt) + INVALID_FORMAT = + register(:invalid_format, -54, InvalidFormat) + OBJECT_CORRUPT = + register(:object_corrupt, -55, ObjectCorrupt) + TOO_MANY_SYMBOLIC_LINKS = + register(:too_many_symbolic_links, -56, TooManySymbolicLinks) + NOT_SOCKET = + register(:not_socket, -57, NotSocket) + OPERATION_NOT_SUPPORTED = + register(:operation_not_supported, -58, OperationNotSupported) + ADDRESS_IS_IN_USE = + register(:address_is_in_use, -59, AddressIsInUse) + ZLIB_ERROR = + register(:zlib_error, -60, ZlibError) + LZ4_ERROR = + register(:lz4_error, -61, LZ4Error) + STACK_OVER_FLOW = + register(:stack_over_flow, -62, StackOverFlow) + SYNTAX_ERROR = + register(:syntax_error, -63, SyntaxError) + RETRY_MAX = + register(:retry_max, -64, RetryMax) + INCOMPATIBLE_FILE_FORMAT = + register(:incompatible_file_format, -65, IncompatibleFileFormat) + UPDATE_NOT_ALLOWED = + register(:update_not_allowed, -66, UpdateNotAllowed) + TOO_SMALL_OFFSET = + register(:too_small_offset, -67, TooSmallOffset) + TOO_LARGE_OFFSET = + register(:too_large_offset, -68, TooLargeOffset) + TOO_SMALL_LIMIT = + register(:too_small_limit, -69, TooSmallLimit) + CAS_ERROR = + register(:cas_error, -70, CASError) + UNSUPPORTED_COMMAND_VERSION = + register(:unsupported_command_version, -71, UnsupportedCommandVersion) + NORMALIZER_ERROR = + register(:normalizer_error, -72, NormalizerError) + TOKEN_FILTER_ERROR = + register(:token_filter_error, -73, TokenFilterError) + COMMAND_ERROR = + register(:command_error, -74, CommandError) + PLUGIN_ERROR = + register(:plugin_error, -75, PluginError) + SCORER_ERROR = + register(:scorer_error, -76, ScorerError) + CANCEL = + register(:cancel, -77, Cancel) + WINDOW_FUNCTION_ERROR = + register(:window_function_error, -78, WindowFunctionError) + ZSTD_ERROR = + register(:zstd_error, -79, ZstdError) + + GroongaError.rc = UNKNOWN_ERROR + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/sources.am new file mode 100644 index 00000000..1f0d1624 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/context/sources.am @@ -0,0 +1,3 @@ +RUBY_SCRIPT_FILES = \ + error_level.rb \ + rc.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/database.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/database.rb new file mode 100644 index 00000000..e0c6b4a0 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/database.rb @@ -0,0 +1,65 @@ +module Groonga + class Database + def each_raw(options=nil) + return to_enum(__method__, options) unless block_given? + + if options + min = options[:prefix] + order = options[:order] + order_by = options[:order_by] + else + min = nil + order = :ascending + order_by = :id + end + flags = 0 + + if order == :descending + flags |= TableCursorFlags::DESCENDING + else + flags |= TableCursorFlags::ASCENDING + end + if order_by == :id + flags |= TableCursorFlags::BY_ID + else + flags |= TableCursorFlags::BY_KEY + end + flags |= TableCursorFlags::PREFIX if min + TableCursor.open(self, :min => min, :flags => flags) do |cursor| + cursor.each do |id| + yield(id, cursor) + end + end + end + + def each(options=nil) + return to_enum(__method__, options) unless block_given? + + context = Context.instance + each_raw(options) do |id, cursor| + object = context[id] + yield(object) if object + end + end + + def each_name(options=nil) + return to_enum(__method__, options) unless block_given? + + each_raw(options) do |id, cursor| + name = cursor.key + yield(name) + end + end + + def each_table(options={}) + return to_enum(__method__, options) unless block_given? + + context = Context.instance + each_name(options) do |name| + next if name.include?(".") + object = context[name] + yield(object) if object.is_a?(Table) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/error.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/error.rb new file mode 100644 index 00000000..e39c9045 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/error.rb @@ -0,0 +1,16 @@ +module Groonga + class GroongaError + class << self + def rc + @rc + end + + def rc=(rc) + @rc = rc + end + end + end + + class ErrorMessage < Error + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/eval_context.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/eval_context.rb new file mode 100644 index 00000000..05c7ee8d --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/eval_context.rb @@ -0,0 +1,18 @@ +module Groonga + class EvalContext + def eval(script) + proc = compile(script) + instance_eval(&proc) + end + + def method_missing(id, *args, &block) + return super unless args.empty? + return super if block_given? + + object = Context.instance[id.to_s] + return super if object.nil? + + object + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression.rb new file mode 100644 index 00000000..27dadcb7 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression.rb @@ -0,0 +1,68 @@ +require "expression_rewriter" +require "expression_rewriters" + +require "expression_tree_builder" + +require "scan_info" +require "scan_info_builder" + +require "scan_info_data_size_estimator" +require "expression_size_estimator" + +module Groonga + class Expression + def rewrite + rewritten = nil + begin + return nil unless ExpressionRewriters.enabled? + source = self + ExpressionRewriters.classes.each do |rewriter_class| + rewriter = rewriter_class.new(source) + new_rewritten = rewriter.rewrite + if new_rewritten + rewritten.close if rewritten + rewritten = new_rewritten + source = rewritten + end + end + rescue GroongaError => groonga_error + context.set_groonga_error(groonga_error) + rewritten.close if rewritten + rewritten = nil + rescue => error + context.record_error(:invalid_argument, error) + rewritten.close if rewritten + rewritten = nil + end + rewritten + end + + def build_scan_info(op, record_exist) + begin + builder = ScanInfoBuilder.new(self, op, record_exist) + builder.build + rescue => error + context.record_error(:invalid_argument, error) + nil + end + end + + def estimate_size(table) + begin + estimator = ExpressionSizeEstimator.new(self, table) + estimator.estimate + rescue GroongaError => groonga_error + context.set_groonga_error(groonga_error) + table.size + rescue => error + context.record_error(:unknown_error, error) + table.size + end + end + + private + def context + @context ||= Context.instance + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriter.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriter.rb new file mode 100644 index 00000000..8f56ca7c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriter.rb @@ -0,0 +1,22 @@ +module Groonga + class ExpressionRewriter + class << self + def register(name) + ExpressionRewriters.register(name, self) + end + end + + def initialize(expression) + @expression = expression + end + + def rewrite + nil + end + + private + def context + @context ||= Context.instance + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriters.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriters.rb new file mode 100644 index 00000000..ae773541 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_rewriters.rb @@ -0,0 +1,41 @@ +module Groonga + module ExpressionRewriters + @rewriters = {} + + class << self + def register(name, rewriter_class) + @rewriters[name] = rewriter_class + end + + def enabled? + rewriters_table_name = + Config["expression_rewriter.table"] || "expression_rewriters" + rewriters_table = Context.instance[rewriters_table_name] + return false if rewriters_table.nil? + return false if rewriters_table.empty? + + true + end + + def classes + rewriters_table_name = + Config["expression_rewriter.table"] || "expression_rewriters" + rewriters_table = Context.instance[rewriters_table_name] + return [] if rewriters_table.nil? + + rewriters_table.collect do |id| + record = Record.new(rewriters_table, id) + name = record.key + rewriter = @rewriters[name] + if rewriter.nil? + plugin_name = record.plugin_name.value + require plugin_name + rewriter = @rewriters[name] + raise "unknown rewriter: <#{name}>:<#{plugin_name}>" if rewriter.nil? + end + rewriter + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_size_estimator.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_size_estimator.rb new file mode 100644 index 00000000..597b56f9 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_size_estimator.rb @@ -0,0 +1,54 @@ +module Groonga + class ExpressionSizeEstimator + def initialize(expression, table) + @expression = expression + @table = table + @table_size = @table.size + end + + def estimate + builder = ScanInfoBuilder.new(@expression, Operator::OR, false) + data_list = builder.build + return @table_size if data_list.nil? + + current_size = 0 + sizes = [] + data_list.each do |data| + if (data.flags & ScanInfo::Flags::POP) != 0 + size = sizes.pop + case data.logical_op + when Operator::AND, Operator::AND_NOT + current_size = size if size < current_size + when Operator::OR + current_size = size if size > current_size + else + message = "invalid logical operator: <#{data.logical_op.inspect}>" + raise InvalidArgument, message + end + else + if (data.flags & ScanInfo::Flags::PUSH) != 0 + sizes.push(current_size) + current_size = 0 + end + + estimator = ScanInfoDataSizeEstimator.new(data, @table) + size = estimator.estimate + case data.logical_op + when Operator::AND + current_size = size if size < current_size + when Operator::AND_NOT + size = @table_size - size + size = 0 if size < 0 + current_size = size if size < current_size + when Operator::OR + current_size = size if size > current_size + else + message = "invalid logical operator: <#{data.logical_op.inspect}>" + raise InvalidArgument, message + end + end + end + current_size + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree.rb new file mode 100644 index 00000000..124ace0e --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree.rb @@ -0,0 +1,9 @@ +require "expression_tree/accessor" +require "expression_tree/constant" +require "expression_tree/binary_operation" +require "expression_tree/function_call" +require "expression_tree/index_column" +require "expression_tree/logical_operation" +require "expression_tree/options" +require "expression_tree/procedure" +require "expression_tree/variable" diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/Makefile.am new file mode 100644 index 00000000..1235932f --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_expression_treedir = $(ruby_scriptsdir)/expression_tree +ruby_scripts_expression_tree_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/accessor.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/accessor.rb new file mode 100644 index 00000000..ac4b122f --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/accessor.rb @@ -0,0 +1,14 @@ +module Groonga + module ExpressionTree + class Accessor + attr_reader :object + def initialize(object) + @object = object + end + + def build(expression) + expression.append_object(@object, Operator::PUSH, 1) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/binary_operation.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/binary_operation.rb new file mode 100644 index 00000000..d59c65c7 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/binary_operation.rb @@ -0,0 +1,67 @@ +module Groonga + module ExpressionTree + class BinaryOperation + attr_reader :operator + attr_reader :left + attr_reader :right + def initialize(operator, left, right) + @operator = operator + @left = left + @right = right + end + + def build(expression) + @left.build(expression) + @right.build(expression) + expression.append_operator(@operator, 2) + end + + RANGE_OPERATORS = [ + Operator::LESS, + Operator::GREATER, + Operator::LESS_EQUAL, + Operator::GREATER_EQUAL, + ] + def estimate_size(table) + case @operator + when *RANGE_OPERATORS + estimate_size_range(table) + else + table.size + end + end + + private + def estimate_size_range(table) + return table.size unless @left.is_a?(Variable) + return table.size unless @right.is_a?(Constant) + + column = @left.column + value = @right.value + index_info = column.find_index(@operator) + return table.size if index_info.nil? + + index_column = index_info.index + lexicon = index_column.lexicon + options = {} + case @operator + when Operator::LESS + options[:max] = value + options[:flags] = TableCursorFlags::LT + when Operator::LESS_EQUAL + options[:max] = value + options[:flags] = TableCursorFlags::LE + when Operator::GREATER + options[:min] = value + options[:flags] = TableCursorFlags::GT + when Operator::GREATER_EQUAL + options[:min] = value + options[:flags] = TableCursorFlags::GE + end + TableCursor.open(lexicon, options) do |cursor| + index_column.estimate_size(:lexicon_cursor => cursor) + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/constant.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/constant.rb new file mode 100644 index 00000000..228a1fc8 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/constant.rb @@ -0,0 +1,22 @@ +module Groonga + module ExpressionTree + class Constant + attr_reader :value + def initialize(value) + @value = value + end + + def build(expression) + expression.append_constant(@value, Operator::PUSH, 1) + end + + def estimate_size(table) + if Bulk.true?(@value) + table.size + else + 0 + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/function_call.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/function_call.rb new file mode 100644 index 00000000..0c7438d6 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/function_call.rb @@ -0,0 +1,66 @@ +module Groonga + module ExpressionTree + class FunctionCall + attr_reader :procedure + attr_reader :arguments + def initialize(procedure, arguments) + @procedure = procedure + @arguments = arguments + end + + def build(expression) + @procedure.build(expression) + @arguments.each do |argument| + argument.build(expression) + end + expression.append_operator(Operator::CALL, @arguments.size) + end + + def estimate_size(table) + return table.size unless @procedure.name == "between" + + column, min, min_border, max, max_border = @arguments + + if column.is_a?(Groonga::ExpressionTree::IndexColumn) + index_column = column.object + else + index_info = column.column.find_index(Operator::CALL) + return table.size if index_info.nil? + index_column = index_info.index + end + + while index_column.is_a?(Groonga::Accessor) + if index_column.have_next? + index_column = index_column.next + else + index_column = index_column.object + index_info = index_column.find_index(Operator::CALL) + return table.size if index_info.nil? + index_column = index_info.index + end + end + + lexicon = index_column.lexicon + options = { + :min => min.value, + :max => max.value, + :flags => 0, + } + if min_border.value == "include" + options[:flags] |= TableCursorFlags::LT + else + options[:flags] |= TableCursorFlags::LE + end + if max_border.value == "include" + options[:flags] |= TableCursorFlags::GT + else + options[:flags] |= TableCursorFlags::GE + end + + TableCursor.open(lexicon, options) do |cursor| + index_column.estimate_size(:lexicon_cursor => cursor) + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/index_column.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/index_column.rb new file mode 100644 index 00000000..c62a8d19 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/index_column.rb @@ -0,0 +1,14 @@ +module Groonga + module ExpressionTree + class IndexColumn + attr_reader :object + def initialize(object) + @object = object + end + + def build(expression) + expression.append_object(@object, Operator::PUSH, 1) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/logical_operation.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/logical_operation.rb new file mode 100644 index 00000000..e8d494f8 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/logical_operation.rb @@ -0,0 +1,33 @@ +module Groonga + module ExpressionTree + class LogicalOperation + attr_reader :operator + attr_reader :nodes + def initialize(operator, nodes) + @operator = operator + @nodes = nodes + end + + def build(expression) + @nodes.each_with_index do |node, i| + node.build(expression) + expression.append_operator(@operator, 2) if i > 0 + end + end + + def estimate_size(table) + estimated_sizes = @nodes.collect do |node| + node.estimate_size(table) + end + case @operator + when Operator::AND + estimated_sizes.min + when Operator::OR + estimated_sizes.max + else + estimated_sizes.first + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/options.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/options.rb new file mode 100644 index 00000000..1504b57a --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/options.rb @@ -0,0 +1,14 @@ +module Groonga + module ExpressionTree + class Options + attr_reader :object + def initialize(object) + @object = object + end + + def build(expression) + expression.append_object(@object, Operator::PUSH, 1) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/procedure.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/procedure.rb new file mode 100644 index 00000000..1d63149c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/procedure.rb @@ -0,0 +1,18 @@ +module Groonga + module ExpressionTree + class Procedure + attr_reader :object + def initialize(object) + @object = object + end + + def name + @object.name + end + + def build(expression) + expression.append_object(@object, Operator::PUSH, 1) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/sources.am new file mode 100644 index 00000000..d8a776bf --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/sources.am @@ -0,0 +1,10 @@ +RUBY_SCRIPT_FILES = \ + accessor.rb \ + binary_operation.rb \ + constant.rb \ + function_call.rb \ + index_column.rb \ + logical_operation.rb \ + options.rb \ + procedure.rb \ + variable.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/variable.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/variable.rb new file mode 100644 index 00000000..e99ad9a8 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree/variable.rb @@ -0,0 +1,18 @@ +module Groonga + module ExpressionTree + class Variable + attr_reader :column + def initialize(column) + @column = column + end + + def build(expression) + expression.append_object(@column, Operator::GET_VALUE, 1) + end + + def estimate_size(table) + table.size + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree_builder.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree_builder.rb new file mode 100644 index 00000000..f2cc297c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/expression_tree_builder.rb @@ -0,0 +1,111 @@ +require "expression_tree" + +module Groonga + class ExpressionTreeBuilder + RELATION_OPERATORS = [ + Operator::MATCH, + Operator::NEAR, + Operator::NEAR2, + Operator::SIMILAR, + Operator::PREFIX, + Operator::SUFFIX, + Operator::EQUAL, + Operator::NOT_EQUAL, + Operator::LESS, + Operator::GREATER, + Operator::LESS_EQUAL, + Operator::GREATER_EQUAL, + Operator::GEO_WITHINP5, + Operator::GEO_WITHINP6, + Operator::GEO_WITHINP8, + Operator::TERM_EXTRACT, + Operator::REGEXP, + Operator::FUZZY, + ] + + ARITHMETIC_OPERATORS = [ + Operator::BITWISE_OR, + Operator::BITWISE_XOR, + Operator::BITWISE_AND, + Operator::BITWISE_NOT, + Operator::SHIFTL, + Operator::SHIFTR, + Operator::SHIFTRR, + Operator::PLUS, + Operator::MINUS, + Operator::STAR, + Operator::MOD, + ] + + LOGICAL_OPERATORS = [ + Operator::AND, + Operator::OR, + Operator::AND_NOT, + Operator::ADJUST, + ] + + def initialize(expression) + @expression = expression + end + + def build + stack = [] + codes = @expression.codes + codes.each do |code| + case code.op + when *LOGICAL_OPERATORS + right = stack.pop + left = stack.pop + nodes = [] + add_logical_operation_node(code.op, nodes, left) + add_logical_operation_node(code.op, nodes, right) + node = ExpressionTree::LogicalOperation.new(code.op, nodes) + stack.push(node) + when *RELATION_OPERATORS, *ARITHMETIC_OPERATORS + right = stack.pop + left = stack.pop + node = ExpressionTree::BinaryOperation.new(code.op, left, right) + stack.push(node) + when Operator::GET_VALUE + node = ExpressionTree::Variable.new(code.value) + stack.push(node) + when Operator::PUSH + case code.value + when Procedure + node = ExpressionTree::Procedure.new(code.value) + when IndexColumn + node = ExpressionTree::IndexColumn.new(code.value) + when Accessor + node = ExpressionTree::Accessor.new(code.value) + when HashTable + node = ExpressionTree::Options.new(code.value) + else + node = ExpressionTree::Constant.new(code.value.value) + end + stack.push(node) + when Operator::CALL + arguments = [] + (code.n_args - 1).times do + arguments.unshift(stack.pop) + end + procedure = stack.pop + node = ExpressionTree::FunctionCall.new(procedure, arguments) + stack.push(node) + else + raise "unknown operator: #{code.inspect}" + end + end + stack.pop + end + + private + def add_logical_operation_node(operator, nodes, node) + if node.is_a?(ExpressionTree::LogicalOperation) and + node.operator == operator + nodes.concat(node.nodes) + else + nodes << node + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/fixed_size_column.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/fixed_size_column.rb new file mode 100644 index 00000000..4fab9fb7 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/fixed_size_column.rb @@ -0,0 +1,5 @@ +module Groonga + class FixedSizeColumn + include Indexable + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/id.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/id.rb new file mode 100644 index 00000000..a49e4fde --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/id.rb @@ -0,0 +1,12 @@ +module Groonga + module ID + # TODO: Should we bind GRN_N_RESERVED_TYPES? + N_RESERVED_TYPES = 256 + + class << self + def builtin?(id) + id < N_RESERVED_TYPES + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_column.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_column.rb new file mode 100644 index 00000000..25ebc149 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_column.rb @@ -0,0 +1,39 @@ +module Groonga + class IndexColumn + private :estimate_size_for_term_id + private :estimate_size_for_query + private :estimate_size_for_lexicon_cursor + + # Estimate the number of matched records for term ID or query. + # + # @overload estimate_size(:term_id => term_id) + # @return [Integer] the number of matched records for the term ID. + # + # @overload estimate_size(:query => query) + # @return [Integer] the number of matched records for the query. + # + # @overload estimate_size(:lexicon_cursor => lexicon_cursor) + # @return [Integer] the number of matched records for the lexicon cursor. + # + def estimate_size(parameters) + term_id = parameters[:term_id] + if term_id + return estimate_size_for_term_id(term_id) + end + + query = parameters[:query] + if query + return estimate_size_for_query(query, parameters) + end + + lexicon_cursor = parameters[:lexicon_cursor] + if lexicon_cursor + return estimate_size_for_lexicon_cursor(lexicon_cursor) + end + + message = + "must specify :term_id, :query, :lexicon_cursor: #{parameters.inspect}" + raise InvalidArgument, message + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_cursor.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_cursor.rb new file mode 100644 index 00000000..80440669 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_cursor.rb @@ -0,0 +1,18 @@ +module Groonga + class IndexCursor + class << self + def open(*arguments) + cursor = open_raw(*arguments) + if block_given? + begin + yield(cursor) + ensure + cursor.close + end + else + cursor + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_info.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_info.rb new file mode 100644 index 00000000..cf8336e5 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/index_info.rb @@ -0,0 +1,10 @@ +module Groonga + class IndexInfo + attr_reader :index + attr_reader :section_id + def initialize(index, section_id) + @index = index + @section_id = section_id + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/Makefile.am new file mode 100644 index 00000000..e7531fdc --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_initializedir = $(ruby_scriptsdir)/initialize +ruby_scripts_initialize_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/post.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/post.rb new file mode 100644 index 00000000..c1215f3e --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/post.rb @@ -0,0 +1,28 @@ +require "error" + +require "context" + +require "writer" + +require "id" + +require "object" +require "database" +require "table" +require "record" +require "fixed_size_column" +require "variable_size_column" +require "index_column" +require "accessor" +require "command" +require "command_input" +require "table_cursor" +require "index_cursor" + +require "expression" + +require "plugin_loader" + +require "eval_context" + +require "command_line_parser" diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/pre.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/pre.rb new file mode 100644 index 00000000..99300d11 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/pre.rb @@ -0,0 +1,3 @@ +require "backtrace_entry" + +require "operator" diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/sources.am new file mode 100644 index 00000000..3c26e19b --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/initialize/sources.am @@ -0,0 +1,3 @@ +RUBY_SCRIPT_FILES = \ + pre.rb \ + post.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger.rb new file mode 100644 index 00000000..95f86974 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger.rb @@ -0,0 +1,34 @@ +module Groonga + class Logger + def log_error(error) + log_level = Level::ERROR.to_i + + if error.is_a?(Error) + message = error.message + else + message = "#{error.class}: #{error.message}" + end + backtrace = error.backtrace + first_raw_entry = backtrace.first + if first_raw_entry + first_entry = BacktraceEntry.parse(first_raw_entry) + file = first_entry.file + line = first_entry.line + method = first_entry.method + # message = "#{file}:#{line}:#{method}: #{message}" + else + file = "" + line = 0 + method = "" + end + log(log_level, file, line, method, message) + + backtrace.each_with_index do |raw_entry, i| + entry = BacktraceEntry.parse(raw_entry) + message = entry.message + message = raw_entry if message.empty? + log(log_level, entry.file, entry.line, entry.method, raw_entry) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/Makefile.am new file mode 100644 index 00000000..448e72ca --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_loggerdir = $(ruby_scriptsdir)/logger +ruby_scripts_logger_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/level.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/level.rb new file mode 100644 index 00000000..4b8afa2c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/level.rb @@ -0,0 +1,40 @@ +module Groonga + class Logger + class Level + @@names = {} + @@levels = {} + class << self + def find(name_or_level) + if name_or_level.is_a?(Integer) + @@levels[name_or_level] + else + @@names[name_or_level] + end + end + end + + attr_reader :name + def initialize(name, level) + @@names[name] = self + @@levels[level] = self + @name = name + @level = level + end + + def to_i + @level + end + + NONE = new(:none, 0) + EMERG = new(:emerg, 1) + ALERT = new(:alert, 2) + CRIT = new(:crit, 3) + ERROR = new(:error, 4) + WARNING = new(:warning, 5) + NOTICE = new(:notice, 6) + INFO = new(:info, 7) + DEBUG = new(:debug, 8) + DUMP = new(:dump, 9) + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/sources.am new file mode 100644 index 00000000..7231ee4e --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/logger/sources.am @@ -0,0 +1,2 @@ +RUBY_SCRIPT_FILES = \ + level.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/object.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/object.rb new file mode 100644 index 00000000..aa8e9e65 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/object.rb @@ -0,0 +1,18 @@ +module Groonga + class Object + def domain + Context.instance[domain_id] + end + + def range + Context.instance[range_id] + end + + def corrupt? + check_corrupt + false + rescue + true + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/operator.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/operator.rb new file mode 100644 index 00000000..349695e1 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/operator.rb @@ -0,0 +1,22 @@ +module Groonga + class Operator + @values = {} + class << self + def register(operator) + const_set(operator.name, operator) + @values[operator.value] = operator + end + + def find(value) + @values[value] + end + end + + attr_reader :name + attr_reader :value + def initialize(name, value) + @name = name + @value = value + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/plugin_loader.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/plugin_loader.rb new file mode 100644 index 00000000..09b972f1 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/plugin_loader.rb @@ -0,0 +1,14 @@ +module Groonga + class PluginLoader + class << self + def load_file(path) + begin + load(path) + rescue => error + Context.instance.record_error(:plugin_error, error) + nil + end + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger.rb new file mode 100644 index 00000000..386ec0e2 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger.rb @@ -0,0 +1,9 @@ +module Groonga + class QueryLogger + def log(flag, mark, message) + flag = Flag.find(flag) if flag.is_a?(Symbol) + flag = flag.to_i if flag.is_a?(Flag) + log_raw(flag, mark, message) + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/Makefile.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/Makefile.am new file mode 100644 index 00000000..14b4f61c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/Makefile.am @@ -0,0 +1,9 @@ +include sources.am + +EXTRA_DIST = \ + $(RUBY_SCRIPT_FILES) + +if WITH_MRUBY +ruby_scripts_query_loggerdir = $(ruby_scriptsdir)/query_logger +ruby_scripts_query_logger_DATA = $(RUBY_SCRIPT_FILES) +endif diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/flag.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/flag.rb new file mode 100644 index 00000000..659570f6 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/flag.rb @@ -0,0 +1,39 @@ +module Groonga + class QueryLogger + class Flag + @@names = {} + class << self + def find(name) + @@names[name] + end + end + + attr_reader :name + def initialize(name, flag) + @@names[name] = self + @name = name + @flag = flag + end + + def to_i + @flag + end + + NONE = new(:none, 0x00) + COMMAND = new(:command, 0x01 << 0) + RESULT_CODE = new(:result_code, 0x01 << 1) + DESTINATION = new(:destination, 0x01 << 2) + CACHE = new(:cache, 0x01 << 3) + SIZE = new(:size, 0x01 << 4) + SCORE = new(:score, 0x01 << 5) + + all_flags = COMMAND.to_i | + RESULT_CODE.to_i | + DESTINATION.to_i | + CACHE.to_i | + SIZE.to_i | + SCORE.to_i + ALL = new(:all, all_flags) + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/sources.am new file mode 100644 index 00000000..44871a85 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/query_logger/sources.am @@ -0,0 +1,2 @@ +RUBY_SCRIPT_FILES = \ + flag.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/record.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/record.rb new file mode 100644 index 00000000..cd7d1a4c --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/record.rb @@ -0,0 +1,38 @@ +module Groonga + class Record + attr_reader :table + attr_reader :id + + def inspect + super.gsub(/>\z/) do + "@id=#{@id.inspect}, @table=#{@table.inspect}>" + end + end + + def [](name) + column = find_column(name) + if column.nil? + raise InvalidArgument, "unknown column: <#{absolute_column_name(name)}>" + end + column[@id] + end + + def method_missing(name, *args, &block) + return super unless args.empty? + + column = find_column(name) + return super if column.nil? + + column[@id] + end + + private + def absolute_column_name(name) + "#{@table.name}.#{name}" + end + + def find_column(name) + Context.instance[absolute_column_name(name)] + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/require.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/require.rb new file mode 100644 index 00000000..1352b327 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/require.rb @@ -0,0 +1,71 @@ +$" = [__FILE__] + +class ScriptLoader + @@loading_paths = {} + + def initialize(path) + @base_path = path + end + + def load_once + if absolute_path?(@base_path) + loaded = load_once_path(@base_path) + if loaded.nil? + raise LoadError, error_message + else + loaded + end + else + $LOAD_PATH.each do |load_path| + unless absolute_path?(load_path) + load_path = File.expand_path(load_path) + if File::ALT_SEPARATOR + load_path = load_path.gsub(File::ALT_SEPARATOR, "/") + end + end + loaded = load_once_path(File.join(load_path, @base_path)) + return loaded unless loaded.nil? + end + raise LoadError, error_message + end + end + + private + def error_message + "cannot load such file -- #{@base_path}" + end + + def absolute_path?(path) + path.start_with?("/") or (/\A[a-z]:\\/i === path) + end + + def load_once_path(path) + loaded = load_once_absolute_path(path) + return loaded unless loaded.nil? + + return nil unless File.extname(path).empty? + + load_once_absolute_path("#{path}.rb") + end + + def load_once_absolute_path(path) + return false if $".include?(path) + return false if @@loading_paths.key?(path) + + return nil unless File.file?(path) + + @@loading_paths[path] = true + load(path) + $" << path + @@loading_paths.delete(path) + + true + end +end + +module Kernel + def require(path) + loader = ScriptLoader.new(path) + loader.load_once + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info.rb new file mode 100644 index 00000000..acdb2eeb --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info.rb @@ -0,0 +1,38 @@ +module Groonga + class ScanInfo + module Flags + ACCESSOR = 0x01 + PUSH = 0x02 + POP = 0x04 + PRE_CONST = 0x08 + end + + def apply(data) + self.op = data.op + self.logical_op = data.logical_op + self.end = data.end + self.query = data.query + self.flags = data.flags + if data.max_interval + self.max_interval = data.max_interval + end + if data.similarity_threshold + self.similarity_threshold = data.similarity_threshold + end + data.args.each do |arg| + push_arg(arg) + end + data.search_indexes.each do |search_index| + put_index(search_index.index_column, + search_index.section_id, + search_index.weight, + search_index.scorer, + search_index.scorer_args_expr, + search_index.scorer_args_expr_offset || 0) + end + if data.start_position + self.start_position = data.start_position + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_builder.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_builder.rb new file mode 100644 index 00000000..66dad9ea --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_builder.rb @@ -0,0 +1,577 @@ +require "scan_info_data" + +module Groonga + class ScanInfoBuilder + def initialize(expression, operator, record_exist) + @data_list = [] + @expression = expression + @operator = operator + @record_exist = record_exist + @variable = @expression[0] + @table = Context.instance[@variable.domain] + end + + RELATION_OPERATORS = [ + Operator::MATCH, + Operator::NEAR, + Operator::NEAR2, + Operator::SIMILAR, + Operator::PREFIX, + Operator::SUFFIX, + Operator::EQUAL, + Operator::NOT_EQUAL, + Operator::LESS, + Operator::GREATER, + Operator::LESS_EQUAL, + Operator::GREATER_EQUAL, + Operator::GEO_WITHINP5, + Operator::GEO_WITHINP6, + Operator::GEO_WITHINP8, + Operator::TERM_EXTRACT, + Operator::REGEXP, + Operator::FUZZY, + ] + + ARITHMETIC_OPERATORS = [ + Operator::BITWISE_OR, + Operator::BITWISE_XOR, + Operator::BITWISE_AND, + Operator::BITWISE_NOT, + Operator::SHIFTL, + Operator::SHIFTR, + Operator::SHIFTRR, + Operator::PLUS, + Operator::MINUS, + Operator::STAR, + Operator::MOD, + ] + + LOGICAL_OPERATORS = [ + Operator::AND, + Operator::OR, + Operator::AND_NOT, + Operator::ADJUST, + ] + def build + return nil unless valid? + + context = BuildContext.new(@expression) + codes = context.codes + while context.have_next? + code = context.code + code_op = context.code_op + i = context.i + context.next + + case code_op + when *RELATION_OPERATORS + context.status = :start + data = context.data + data.op = code_op + data.end = i + data.weight = code.value.value if code.value + data.match_resolve_index + @data_list << data + context.data = nil + when *LOGICAL_OPERATORS + if context.status == :const + data = context.data + data.op = Operator::PUSH + data.end = data.start + @data_list << data + context.data = nil + end + put_logical_op(code_op, i) + # TODO: rescue and return nil + context.status = :start + when Operator::PUSH + context.data ||= ScanInfoData.new(i) + data = context.data + if code.value == @variable + context.status = :var + else + data.args << code.value + if context.status == :start + data.flags |= ScanInfo::Flags::PRE_CONST + end + context.status = :const + end + if code.modify > 0 and + LOGICAL_OPERATORS.include?(codes[i + code.modify].op) + data.op = Operator::PUSH + data.end = data.start + @data_list << data + context.data = nil + context.status = :start + end + when Operator::GET_VALUE + case context.status + when :start + context.data ||= ScanInfoData.new(i) + data = context.data + context.status = :column1 + data.args << code.value + when :const, :var + context.status = :column1 + data.args << code.value + when :column1 + message = "invalid expression: can't use column as a value: " + message << "<#{code.value.name}>: <#{@expression.grn_inspect}>" + raise ErrorMessage, message + when :column2 + # Do nothing + else + message = "internal expression parsing error: unknown status: " + message << "<#{context.status.inspect}>: " + message << "<#{@expression.grn_inspect}>" + raise ErrorMessage, message + end + when Operator::CALL + context.data ||= ScanInfoData.new(i) + data = context.data + if (code.flags & ExpressionCode::Flags::RELATIONAL_EXPRESSION) != 0 or + (not context.have_next?) + context.status = :start + data.op = code_op + data.end = i + data.call_relational_resolve_indexes + @data_list << data + context.data = nil + else + context.status = :column2 + end + when Operator::GET_REF + context.data ||= ScanInfoData.new(i) + case context.status + when :start + data = context.data + context.status = :column1 + data.args << code.value + end + when Operator::GET_MEMBER + data = context.data + index = data.args.pop + data.start_position = index.value + context.status = :column1 + when Operator::NOT + success = build_not(context, code, i) + return nil unless success + end + end + + if @operator == Operator::OR and !@record_exist + first_data = @data_list.first + if (first_data.flags & ScanInfo::Flags::PUSH) == 0 or + first_data.logical_op != @operator + raise ErrorMessage, "invalid expr" + else + first_data.flags &= ~ScanInfo::Flags::PUSH + first_data.logical_op = @operator + end + else + put_logical_op(@operator, context.n_codes) + end + + optimize + end + + private + def valid? + n_relation_expressions = 0 + n_logical_expressions = 0 + status = :start + codes = @expression.codes + codes.each_with_index do |code, i| + case code.op + when *RELATION_OPERATORS + if status == :start || status == :var + return false + end + status = :start + n_relation_expressions += 1 + when *ARITHMETIC_OPERATORS + if status == :start || status == :var + return false + end + status = :start + return false if n_relation_expressions != (n_logical_expressions + 1) + when *LOGICAL_OPERATORS + case status + when :start + n_logical_expressions += 1 + return false if n_logical_expressions >= n_relation_expressions + when :const + n_logical_expressions += 1 + n_relation_expressions += 1 + return false if n_logical_expressions >= n_relation_expressions + status = :start + else + return false + end + when Operator::PUSH + if code.modify > 0 and + LOGICAL_OPERATORS.include?(codes[i + code.modify].op) + n_relation_expressions += 1 + status = :start + else + if code.value == @variable + status = :var + else + status = :const + end + end + when Operator::GET_VALUE + case status + when :start, :const, :var + status = :column1 + when :column1 + status = :column2 + when :column2 + # Do nothing + else + return false + end + when Operator::CALL + if (code.flags & ExpressionCode::Flags::RELATIONAL_EXPRESSION) != 0 or + code == codes.last + status = :start + n_relation_expressions += 1 + else + status = :column2 + end + when Operator::GET_REF + case status + when :start + status = :column1 + else + return false + end + when Operator::GET_MEMBER + case status + when :const + return false unless codes[i - 1].value.value.is_a?(Integer) + status = :column1 + else + return false + end + when Operator::NOT + # Do nothing + else + return false + end + end + + return false if status != :start + return false if n_relation_expressions != (n_logical_expressions + 1) + + true + end + + def put_logical_op(operator, start) + n_parens = 1 + n_dif_ops = 0 + r = 0 + j = @data_list.size + while j > 0 + j -= 1 + data = @data_list[j] + if (data.flags & ScanInfo::Flags::POP) != 0 + n_dif_ops += 1 + n_parens += 1 + else + if (data.flags & ScanInfo::Flags::PUSH) != 0 + n_parens -= 1 + if n_parens == 0 + if r == 0 + if n_dif_ops > 0 + if j > 0 and operator != Operator::AND_NOT + n_parens = 1 + n_dif_ops = 0 + r = j + else + new_data = ScanInfoData.new(start) + new_data.flags = ScanInfo::Flags::POP + new_data.logical_op = operator + @data_list << new_data + break + end + else + data.flags &= ~ScanInfo::Flags::PUSH + data.logical_op = operator + break + end + else + if n_dif_ops > 0 + new_data = ScanInfoData.new(start) + new_data.flags = ScanInfo::Flags::POP + new_data.logical_op = operator + @data_list << new_data + else + data.flags &= ~ScanInfo::Flags::PUSH + data.logical_op = operator + @data_list = + @data_list[0...j] + + @data_list[r..-1] + + @data_list[j...r] + end + break + end + end + else + if operator == Operator::AND_NOT or operator != data.logical_op + n_dif_ops += 1 + end + end + end + + if j < 0 + raise ErrorMessage, "unmatched nesting level" + end + end + end + + def build_not(context, code, i) + last_data = @data_list.last + return false if last_data.nil? + + case last_data.op + when Operator::LESS + last_data.op = Operator::GREATER_EQUAL + last_data.end += 1 + when Operator::LESS_EQUAL + last_data.op = Operator::GREATER + last_data.end += 1 + when Operator::GREATER + last_data.op = Operator::LESS_EQUAL + last_data.end += 1 + when Operator::GREATER_EQUAL + last_data.op = Operator::LESS + last_data.end += 1 + when Operator::NOT_EQUAL + last_data.op = Operator::EQUAL + last_data.end += 1 + else + if @data_list.size == 1 + if last_data.search_indexes.empty? + if last_data.op == Operator::EQUAL + last_data.op = Operator::NOT_EQUAL + last_data.end += 1 + else + return false + end + else + last_data.logical_op = Operator::AND_NOT + last_data.flags &= ~ScanInfo::Flags::PUSH + @data_list.unshift(create_all_match_data) + end + else + next_code = context.code + return false if next_code.nil? + + case next_code.op + when Operator::AND + context.code_op = Operator::AND_NOT + when Operator::AND_NOT + context.code_op = Operator::AND + when Operator::OR + @data_list[-1, 0] = create_all_match_data + put_logical_op(Operator::AND_NOT, i) + else + return false + end + end + end + + true + end + + def optimize + optimized_data_list = [] + i = 0 + n = @data_list.size + while i < n + data = @data_list[i] + next_data = @data_list[i + 1] + i += 1 + if next_data.nil? + optimized_data_list << data + next + end + if range_operations?(data, next_data) + between_data = create_between_data(data, next_data) + optimized_data_list << between_data + i += 1 + next + end + optimized_data_list << data + end + + optimize_by_estimated_size(optimized_data_list) + end + + def optimize_by_estimated_size(data_list) + return data_list unless Groonga::ORDER_BY_ESTIMATED_SIZE + + start_index = nil + data_list.size.times do |i| + data = data_list[i] + if data.logical_op != Operator::AND + if start_index.nil? + start_index = i + else + sort_by_estimated_size!(data_list, start_index...i) + start_index = nil + end + end + end + unless start_index.nil? + sort_by_estimated_size!(data_list, start_index...data_list.size) + end + data_list + end + + def sort_by_estimated_size!(data_list, range) + target_data_list = data_list[range] + return if target_data_list.size < 2 + + start_logical_op = target_data_list.first.logical_op + sorted_data_list = target_data_list.sort_by do |data| + estimator = ScanInfoDataSizeEstimator.new(data, @table) + estimator.estimate + end + sorted_data_list.each do |sorted_data| + sorted_data.logical_op = Operator::AND + end + sorted_data_list.first.logical_op = start_logical_op + data_list[range] = sorted_data_list + end + + def range_operations?(data, next_data) + return false unless next_data.logical_op == Operator::AND + + op, next_op = data.op, next_data.op + return false if !(lower_condition?(op) or lower_condition?(next_op)) + return false if !(upper_condition?(op) or upper_condition?(next_op)) + + return false if data.args[0] != next_data.args[0] + + data_search_indexes = data.search_indexes + return false if data_search_indexes.empty? + + data_search_indexes == next_data.search_indexes + end + + def lower_condition?(operator) + case operator + when Operator::GREATER, Operator::GREATER_EQUAL + true + else + false + end + end + + def upper_condition?(operator) + case operator + when Operator::LESS, Operator::LESS_EQUAL + true + else + false + end + end + + def create_all_match_data + data = ScanInfoData.new(0) + data.end = 0 + data.flags = ScanInfo::Flags::PUSH + data.op = Operator::CALL + data.logical_op = Operator::OR + data.args = [Context.instance["all_records"]] + data.search_indexes = [] + data + end + + def create_between_data(data, next_data) + between_data = ScanInfoData.new(data.start) + between_data.end = next_data.end + 1 + between_data.flags = data.flags + between_data.op = Operator::CALL + between_data.logical_op = data.logical_op + between_data.args = create_between_data_args(data, next_data) + between_data.search_indexes = data.search_indexes + between_data + end + + def create_between_data_args(data, next_data) + between = Context.instance["between"] + @expression.take_object(between) + column = data.args[0] + op, next_op = data.op, next_data.op + if lower_condition?(op) + min = data.args[1] + min_operator = op + max = next_data.args[1] + max_operator = next_op + else + min = next_data.args[1] + min_operator = next_op + max = data.args[1] + max_operator = op + end + if min_operator == Operator::GREATER + min_border = "exclude" + else + min_border = "include" + end + if max_operator == Operator::LESS + max_border = "exclude" + else + max_border = "include" + end + + [ + between, + column, + min, + @expression.allocate_constant(min_border), + max, + @expression.allocate_constant(max_border), + ] + end + + class BuildContext + attr_accessor :status + attr_reader :codes + attr_reader :n_codes + attr_reader :i + attr_writer :code_op + attr_accessor :data + def initialize(expression) + @expression = expression + @status = :start + @current_data = nil + @codes = @expression.codes + @n_codes = @codes.size + @i = 0 + @code_op = nil + @data = nil + end + + def have_next? + @i < @n_codes + end + + def next + @i += 1 + @code_op = nil + end + + def code + @codes[@i] + end + + def code_op + @code_op || code.op + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data.rb new file mode 100644 index 00000000..342f7a7a --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data.rb @@ -0,0 +1,324 @@ +require "scan_info_search_index" + +module Groonga + class ScanInfoData + attr_accessor :start + attr_accessor :end + attr_accessor :op + attr_accessor :logical_op + attr_accessor :query + attr_accessor :args + attr_accessor :search_indexes + attr_accessor :flags + attr_accessor :max_interval + attr_accessor :similarity_threshold + attr_accessor :start_position + attr_accessor :weight + def initialize(start) + @start = start + @end = 0 + @op = Operator::NOP + @logical_op = Operator::OR + @query = nil + @args = [] + @search_indexes = [] + @flags = ScanInfo::Flags::PUSH + @max_interval = nil + @similarity_threshold = nil + @start_position = nil + @weight = 0 + end + + def match_resolve_index + if near_search? + match_near_resolve_index + elsif similar_search? + match_similar_resolve_index + else + match_generic_resolve_index + end + end + + def call_relational_resolve_indexes + procedure, *args = *@args + return unless procedure.selector? + + selector_op = procedure.selector_operator + args.each do |arg| + call_relational_resolve_index(arg, selector_op) + end + end + + private + def near_search? + (@op == Operator::NEAR or @op == Operator::NEAR2) and @args.size == 3 + end + + def match_near_resolve_index + arg = @args[0] + case arg + when Expression + match_resolve_index_expression(arg) + when Accessor + match_resolve_index_accessor(arg) + when Indexable + match_resolve_index_indexable(arg) + else + message = + "The first argument of NEAR/NEAR2 must be Expression, Accessor or Indexable: #{arg.class}" + raise ErrorMessage, message + end + + self.query = @args[1] + self.max_interval = @args[2].value + end + + def similar_search? + @op == Operator::SIMILAR and @args.size == 3 + end + + def match_similar_resolve_index + arg = @args[0] + case arg + when Expression + match_resolve_index_expression(arg) + when Accessor + match_resolve_index_accessor(arg) + when Indexable + match_resolve_index_indexable(arg) + else + message = + "The first argument of SIMILAR must be Expression, Accessor or Indexable: #{arg.class}" + raise ErrorMesesage, message + end + + self.query = @args[1] + self.similarity_threshold = @args[2].value + end + + def match_generic_resolve_index + @args.each do |arg| + case arg + when Expression + match_resolve_index_expression(arg) + when Accessor + match_resolve_index_accessor(arg) + when IndexColumn + match_resolve_index_index_column(arg) + when Indexable + match_resolve_index_indexable(arg) + when Procedure + break + else + self.query = arg + end + end + if @op == Operator::REGEXP and not index_searchable_regexp?(@query) + @search_indexes.clear + end + end + + def index_searchable_regexp?(pattern) + return false if pattern.nil? + + previous_char = nil + pattern.value.each_char do |char| + if previous_char == "\\" + case char + when "Z" + return false + when "b", "B" + return false + when "d", "D", "h", "H", "p", "s", "S", "w", "W" + return false + when "X" + return false + when "k", "g", "1", "2", "3", "4", "5", "6", "7", "8", "9" + return false + when "\\" + previous_char = nil + next + end + else + case char + when ".", "[", "]", "|", "?", "+", "*", "{", "}", "^", "$", "(", ")" + return false + end + end + previous_char = char + end + true + end + + def match_resolve_index_expression(expression) + codes = expression.codes + n_codes = codes.size + i = 0 + while i < n_codes + i = match_resolve_index_expression_codes(expression, codes, i, n_codes) + end + end + + def match_resolve_index_expression_codes(expression, codes, i, n_codes) + code = codes[i] + value = code.value + return i + 1 if value.nil? + + case value + when Accessor, Column + index_info, offset = + match_resolve_index_expression_find_index(expression, + codes, i, n_codes) + i += offset - 1 + if index_info + if value.is_a?(Accessor) + self.flags |= ScanInfo::Flags::ACCESSOR + end + weight, offset = codes[i].weight + i += offset + put_search_index(index_info.index, index_info.section_id, weight) + end + when Procedure + unless value.scorer? + message = "procedure must be scorer: #{scorer.name}>" + raise ErrorMessage, message + end + scorer = value + i += 1 + index_info, offset = + match_resolve_index_expression_find_index(expression, + codes, i, n_codes) + i += offset + if index_info + scorer_args_expr_offset = 0 + if codes[i].op != Operator::CALL + scorer_args_expr_offset = i + end + while i < n_codes and codes[i].op != Operator::CALL + i += 1 + end + weight, offset = codes[i].weight + i += offset + put_search_index(index_info.index, + index_info.section_id, + weight, + scorer, + expression, + scorer_args_expr_offset) + end + when Table + raise ErrorMessage, "invalid match target: <#{value.name}>" + end + i + 1 + end + + def match_resolve_index_expression_find_index(expression, codes, i, n_codes) + code = codes[i] + value = code.value + index_info = nil + offset = 1 + case value + when Accessor + accessor = value + index_info = accessor.find_index(@op) + if index_info + if accessor.have_next? and index_info.index != accessor.object + index_info = IndexInfo.new(accessor, index_info.section_id) + end + end + when FixedSizeColumn, VariableSizeColumn + index_info = value.find_index(@op) + when IndexColumn + index = value + section_id = 0 + rest_n_codes = n_codes - i + if rest_n_codes >= 2 and + codes[i + 1].value.is_a?(Bulk) and + (codes[i + 1].value.domain == ID::UINT32 or + codes[i + 1].value.domain == ID::INT32) and + codes[i + 2].op == Operator::GET_MEMBER + section_id = codes[i + 1].value.value + 1 + offset += 2 + end + index_info = IndexInfo.new(index, section_id) + end + + [index_info, offset] + end + + def match_resolve_index_expression_accessor(expr_code) + accessor = expr_code.value + self.flags |= ScanInfo::Flags::ACCESSOR + index_info = accessor.find_index(op) + return if index_info.nil? + + section_id = index_info.section_id + weight = expr_code.weight + if accessor.next + put_search_index(accessor, section_id, weight) + else + put_search_index(index_info.index, section_id, weight) + end + end + + def match_resolve_index_expression_data_column(expr_code) + column = expr_code.value + index_info = column.find_index(op) + return if index_info.nil? + put_search_index(index_info.index, index_info.section_id, expr_code.weight) + end + + def match_resolve_index_index_column(index) + put_search_index(index, 0, 1) + end + + def match_resolve_index_indexable(indexable) + index_info = indexable.find_index(op) + return if index_info.nil? + put_search_index(index_info.index, index_info.section_id, 1) + end + + def match_resolve_index_accessor(accessor) + self.flags |= ScanInfo::Flags::ACCESSOR + index_info = accessor.find_index(op) + return if index_info.nil? + if accessor.next + put_search_index(accessor, index_info.section_id, 1) + else + put_search_index(index_info.index, index_info.section_id, 1) + end + end + + def call_relational_resolve_index(object, selector_op) + case object + when Accessor + call_relational_resolve_index_accessor(object, selector_op) + when Bulk + self.query = object + when Indexable + call_relational_resolve_index_indexable(object, selector_op) + end + end + + def call_relational_resolve_index_indexable(indexable, selector_op) + index_info = indexable.find_index(selector_op) + return if index_info.nil? + put_search_index(index_info.index, index_info.section_id, 1) + end + + def call_relational_resolve_index_accessor(accessor, selector_op) + self.flags |= ScanInfo::Flags::ACCESSOR + index_info = accessor.find_index(selector_op) + return if index_info.nil? + put_search_index(index_info.index, index_info.section_id, 1) + end + + def put_search_index(index, section_id, weight, *args) + search_index = ScanInfoSearchIndex.new(index, + section_id, + weight + @weight, + *args) + @search_indexes << search_index + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data_size_estimator.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data_size_estimator.rb new file mode 100644 index 00000000..5d3dc4e0 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_data_size_estimator.rb @@ -0,0 +1,185 @@ +module Groonga + class ScanInfoDataSizeEstimator + def initialize(data, table) + @data = data + @table = table + @table_size = @table.size + end + + def estimate + search_index = @data.search_indexes.first + return @table_size if search_index.nil? + + index_column = resolve_index_column(search_index.index_column) + return @table_size if index_column.nil? + + size = nil + case @data.op + when Operator::MATCH, + Operator::FUZZY + size = estimate_match(index_column) + when Operator::REGEXP + size = estimate_regexp(index_column) + when Operator::EQUAL + size = estimate_equal(index_column) + when Operator::LESS, + Operator::LESS_EQUAL, + Operator::GREATER, + Operator::GREATER_EQUAL + size = estimate_range(index_column) + when Operator::PREFIX + size = estimate_prefix(index_column) + when Operator::CALL + size = estimate_call(index_column) + end + size || @table_size + end + + private + def resolve_index_column(index_column) + while index_column.is_a?(Accessor) + index_info = index_column.find_index(@data.op) + return nil if index_info.nil? + break if index_info.index == index_column + index_column = index_info.index + end + + index_column + end + + def sampling_cursor_limit(n_terms) + limit = n_terms * 0.01 + if limit < 10 + 10 + elsif limit > 1000 + 1000 + else + limit.to_i + end + end + + def estimate_match(index_column) + index_column.estimate_size(:query => @data.query.value) + end + + def estimate_regexp(index_column) + index_column.estimate_size(:query => @data.query.value, + :mode => @data.op) + end + + def estimate_equal(index_column) + query = @data.query + if index_column.is_a?(Accessor) + table = index_column.object + if index_column.name == "_id" + if table.id?(query.value) + 1 + else + 0 + end + else + if table[query.value] + 1 + else + 0 + end + end + else + lexicon = index_column.lexicon + if query.domain == lexicon.id + term_id = query.value + else + term_id = lexicon[query] + end + return 0 if term_id.nil? + + index_column.estimate_size(:term_id => term_id) + end + end + + def estimate_range(index_column) + if index_column.is_a?(Table) + is_table_search = true + lexicon = index_column + elsif index_column.is_a?(Groonga::Accessor) + is_table_search = true + lexicon = index_column.object + else + is_table_search = false + lexicon = index_column.lexicon + end + n_terms = lexicon.size + return 0 if n_terms.zero? + + value = @data.query.value + options = { + :limit => sampling_cursor_limit(n_terms), + } + case @data.op + when Operator::LESS + options[:max] = value + options[:flags] = TableCursorFlags::LT + when Operator::LESS_EQUAL + options[:max] = value + options[:flags] = TableCursorFlags::LE + when Operator::GREATER + options[:min] = value + options[:flags] = TableCursorFlags::GT + when Operator::GREATER_EQUAL + options[:min] = value + options[:flags] = TableCursorFlags::GE + end + TableCursor.open(lexicon, options) do |cursor| + if is_table_search + size = 1 + else + size = index_column.estimate_size(:lexicon_cursor => cursor) + end + size += 1 if cursor.next != ID::NIL + size + end + end + + def estimate_prefix(index_column) + is_table_search = + (index_column.is_a?(Accessor) and + index_column.name == "_key") + if is_table_search + lexicon = index_column.object + else + lexicon = index_column.lexicon + end + n_terms = lexicon.size + return 0 if n_terms.zero? + + value = @data.query.value + options = { + :min => value, + :limit => sampling_cursor_limit(n_terms), + :flags => TableCursorFlags::PREFIX, + } + TableCursor.open(lexicon, options) do |cursor| + if is_table_search + size = 1 + else + size = index_column.estimate_size(:lexicon_cursor => cursor) + end + size += 1 if cursor.next != ID::NIL + size + end + end + + def estimate_call(index_column) + procedure = @data.args[0] + arguments = @data.args[1..-1].collect do |arg| + if arg.is_a?(::Groonga::Object) + ExpressionTree::Variable.new(arg) + else + ExpressionTree::Constant.new(arg) + end + end + node = ExpressionTree::FunctionCall.new(procedure, arguments) + node.estimate_size(@table) + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_search_index.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_search_index.rb new file mode 100644 index 00000000..a2818160 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/scan_info_search_index.rb @@ -0,0 +1,9 @@ +module Groonga + class ScanInfoSearchIndex < Struct.new(:index_column, + :section_id, + :weight, + :scorer, + :scorer_args_expr, + :scorer_args_expr_offset) + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/sources.am b/storage/mroonga/vendor/groonga/lib/mrb/scripts/sources.am new file mode 100644 index 00000000..9a9e2bae --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/sources.am @@ -0,0 +1,37 @@ +RUBY_SCRIPT_FILES = \ + accessor.rb \ + backtrace_entry.rb \ + command.rb \ + command_input.rb \ + command_line_parser.rb \ + context.rb \ + database.rb \ + error.rb \ + eval_context.rb \ + expression.rb \ + expression_rewriter.rb \ + expression_rewriters.rb \ + expression_size_estimator.rb \ + expression_tree.rb \ + expression_tree_builder.rb \ + fixed_size_column.rb \ + id.rb \ + index_column.rb \ + index_cursor.rb \ + index_info.rb \ + logger.rb \ + object.rb \ + operator.rb \ + plugin_loader.rb \ + query_logger.rb \ + record.rb \ + require.rb \ + scan_info.rb \ + scan_info_builder.rb \ + scan_info_data.rb \ + scan_info_data_size_estimator.rb \ + scan_info_search_index.rb \ + table.rb \ + table_cursor.rb \ + variable_size_column.rb \ + writer.rb diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/table.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/table.rb new file mode 100644 index 00000000..75c91894 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/table.rb @@ -0,0 +1,144 @@ +module Groonga + class Table + include Enumerable + include Indexable + + def columns + context = Context.instance + column_ids.collect do |id| + context[id] + end + end + + def each + flags = + TableCursorFlags::ASCENDING | + TableCursorFlags::BY_ID + TableCursor.open(self, :flags => flags) do |cursor| + cursor.each do |id| + yield(id) + end + end + end + + def sort(keys, options={}) + offset = options[:offset] || 0 + limit = options[:limit] || -1 + ensure_sort_keys(keys) do |sort_keys| + sorted = Array.create("", self) + begin + sort_raw(sort_keys, offset, limit, sorted) + rescue Exception + sorted.close + raise + end + sorted + end + end + + def group(keys, result) + ensure_sort_keys(keys) do |sort_keys| + group_raw(sort_keys, result) + end + end + + def apply_window_function(output_column, + window_function_call, + options={}) + ensure_sort_keys_accept_nil(options[:sort_keys]) do |sort_keys| + ensure_sort_keys_accept_nil(options[:group_keys]) do |group_keys| + window_definition = WindowDefinition.new + begin + window_definition.sort_keys = sort_keys + window_definition.group_keys = group_keys + apply_window_function_raw(output_column, + window_definition, + window_function_call) + ensure + window_definition.close + end + end + end + end + + private + def ensure_sort_keys_accept_nil(keys, &block) + return yield(nil) if keys.nil? + + ensure_sort_keys(keys, &block) + end + + def ensure_sort_keys(keys) + if keys.is_a?(::Array) and keys.all? {|key| key.is_a?(TableSortKey)} + return yield(keys) + end + + converted_keys = [] + + begin + keys = [keys] unless keys.is_a?(::Array) + sort_keys = keys.collect do |key| + ensure_sort_key(key, converted_keys) + end + yield(sort_keys) + ensure + converted_keys.each do |converted_key| + converted_key.close + end + end + end + + def ensure_sort_key(key, converted_keys) + return key if key.is_a?(TableSortKey) + + sort_key = TableSortKey.new + converted_keys << sort_key + + key_name = nil + order = :ascending + offset = 0 + if key.is_a?(::Hash) + key_name = key[:key] + order = key[:order] || order + offset = key[:offset] || offset + else + key_name = key + end + + case key_name + when String + # Do nothing + when Symbol + key_name = key_name.to_s + else + message = "sort key name must be String or Symbol: " + + "#{key_name.inspect}: #{key.inspect}" + raise ArgumentError, message + end + + if key_name.start_with?("-") + key_name[0] = "" + order = :descending + elsif key_name.start_with?("+") + key_name[0] = "" + end + + key = find_column(key_name) + if key.nil? + table_name = name || "(temporary)" + message = "unknown key: #{key_name.inspect}: " + message << "#{table_name}(#{size})" + raise ArgumentError, message + end + + sort_key.key = key + if order == :ascending + sort_key.flags = Groonga::TableSortFlags::ASCENDING + else + sort_key.flags = Groonga::TableSortFlags::DESCENDING + end + sort_key.offset = offset + sort_key + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/table_cursor.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/table_cursor.rb new file mode 100644 index 00000000..45949b71 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/table_cursor.rb @@ -0,0 +1,28 @@ +module Groonga + class TableCursor + include Enumerable + + class << self + def open(*arguments) + cursor = open_raw(*arguments) + if block_given? + begin + yield(cursor) + ensure + cursor.close + end + else + cursor + end + end + end + + def each + loop do + id = self.next + return if id == Groonga::ID::NIL + yield(id) + end + end + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/variable_size_column.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/variable_size_column.rb new file mode 100644 index 00000000..3d75502f --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/variable_size_column.rb @@ -0,0 +1,5 @@ +module Groonga + class VariableSizeColumn + include Indexable + end +end diff --git a/storage/mroonga/vendor/groonga/lib/mrb/scripts/writer.rb b/storage/mroonga/vendor/groonga/lib/mrb/scripts/writer.rb new file mode 100644 index 00000000..de2bc261 --- /dev/null +++ b/storage/mroonga/vendor/groonga/lib/mrb/scripts/writer.rb @@ -0,0 +1,21 @@ +module Groonga + class Writer + def array(name, n_elements) + open_array(name, n_elements) + begin + yield + ensure + close_array + end + end + + def map(name, n_elements) + open_map(name, n_elements) + begin + yield + ensure + close_map + end + end + end +end |