module Groonga module Sharding class LogicalTableRemoveCommand < Command register("logical_table_remove", [ "logical_table", "shard_key", "min", "min_border", "max", "max_border", "dependent", "force", ]) def run_body(input) @dependent = (input[:dependent] == "yes") @force = (input[:force] == "yes") enumerator = LogicalEnumerator.new("logical_table_remove", input) success = true enumerator.each do |shard, shard_range| remove_shard(shard, shard_range, enumerator.target_range) end writer.write(success) end private def remove_shard(shard, shard_range, target_range) cover_type = target_range.cover_type(shard_range) return if cover_type == :none shard_key = shard.key if shard_key.nil? and !@force message = "[logical_table_remove] shard_key doesn't exist: " + "<#{shard.key_name}>" raise InvalidArgument, message end table = shard.table if cover_type == :all or ((table.nil? or shard_key.nil?) and @force) remove_table(shard, table) return end expression_builder = RangeExpressionBuilder.new(shard_key, target_range, nil) case cover_type when :partial_min remove_records(table) do |expression| expression_builder.build_partial_min(expression) end remove_table(shard, table) if table.empty? when :partial_max remove_records(table) do |expression| expression_builder.build_partial_max(expression) end remove_table(shard, table) if table.empty? when :partial_min_and_max remove_records(table) do |expression| expression_builder.build_partial_min_and_max(expression) end remove_table(shard, table) if table.empty? end end def collect_referenced_table_ids_from_index_ids(index_ids, referenced_table_ids) database = context.database index_ids.each do |index_id| index = context[index_id] if index.nil? context.clear_error index_name = database[index_id] lexicon_name = index_name.split(".", 2)[0] lexicon_id = database[lexicon_name] referenced_table_ids << lexicon_id if lexicon_id else referenced_table_ids << index.domain_id end end end def collect_referenced_table_ids_from_column_name(column_name, referenced_table_ids) database = context.database column_id = database[column_name] database.each_raw do |id, cursor| next if ID.builtin?(id) next if id == column_id context.open_temporary(id) do |object| if object.nil? context.clear_error next end case object when IndexColumn if object.source_ids.include?(column_id) collect_referenced_table_ids_from_index_ids([id], referenced_table_ids) end end end end end def collect_referenced_table_ids_from_column(column, referenced_table_ids) range = column.range case range when nil context.clear_error when Table referenced_table_ids << range.id collect_referenced_table_ids_from_index_ids(range.index_ids, referenced_table_ids) end collect_referenced_table_ids_from_index_ids(column.index_ids, referenced_table_ids) end def collect_referenced_table_ids_from_column_names(column_names) referenced_table_ids = [] column_names.each do |column_name| column = context[column_name] if column.nil? context.clear_error collect_referenced_table_ids_from_column_name(column_name, referenced_table_ids) else collect_referenced_table_ids_from_column(column, referenced_table_ids) end end referenced_table_ids end def collect_referenced_table_ids(shard, table) return [] unless @dependent column_names = nil if table begin column_names = table.columns.collect(&:name) rescue context.clear_error end end if column_names.nil? prefix = "#{shard.table_name}." column_names = [] context.database.each_name(:prefix => prefix) do |column_name| column_names << column_name end end collect_referenced_table_ids_from_column_names(column_names) end def remove_table(shard, table) if table.nil? unless @force if context.rc == Context::RC::SUCCESS.to_i error_class = InvalidArgument else rc = Context::RC.find(context.rc) error_class = rc.error_class end message = "[logical_table_remove] table is broken: " + "<#{shard.table_name}>: #{context.error_message}" raise error_class, message end context.clear_error end referenced_table_ids = collect_referenced_table_ids(shard, table) if table.nil? remove_table_force(shard.table_name) else options = {:dependent => @dependent} if @force begin table.remove(options) rescue context.clear_error table.close remove_table_force(shard.table_name) end else table.remove(options) end end remove_referenced_tables(shard, referenced_table_ids) end def remove_table_force(table_name) database = context.database prefix = "#{table_name}." database.each_raw(:prefix => prefix) do |id, cursor| column = context[id] if column.nil? context.clear_error column_name = cursor.key remove_column_force(column_name) table = context[table_name] if table.nil? context.clear_error else table.close end else remove_column(column) end end table_id = database[table_name] return if table_id.nil? database.each_raw do |id, cursor| next if ID.builtin?(id) next if id == table_id context.open_temporary(id) do |object| if object.nil? context.clear_error next end case object when Table if object.domain_id == table_id begin object.remove(:dependent => @dependent) rescue context.clear_error reference_table_name = object.name object.close remove_table_force(reference_table_name) end end when Column if object.range_id == table_id remove_column(object) end end end end Object.remove_force(table_name) end def remove_column(column) begin column.remove(:dependent => @dependent) rescue context.clear_error column_name = column.name column.close remove_column_force(column_name) end end def remove_column_force(column_name) database = context.database column_id = database[column_name] column = context[column_id] if column.nil? context.clear_error else column.index_ids.each do |id| index_column = context[id] if index_column.nil? context.clear_error index_column_name = database[id] remove_column_force(index_column_name) else remove_column(index_column) end end column.close end Object.remove_force(column_name) end def remove_referenced_tables(shard, referenced_table_ids) return if referenced_table_ids.empty? database = context.database shard_suffix = shard.range_data.to_suffix referenced_table_ids.each do |referenced_table_id| referenced_table_name = database[referenced_table_id] next unless referenced_table_name.end_with?(shard_suffix) referenced_table = context[referenced_table_id] if referenced_table.nil? context.clear_error if @force Object.remove_force(referenced_table_name) end next end if @force begin referenced_table.remove(:dependent => @dependent) rescue context.clear_error referenced_table.close remove_table_force(referenced_table_name) end else referenced_table.remove(:dependent => @dependent) end end end def remove_records(table) expression = nil begin expression = Expression.create(table) yield(expression) table.delete(:expression => expression) ensure expression.close if expression end end end end end