#:enddoc: module RailsConnector class Chain # the id of the content cache to use, or nil attr_reader :content_cache # a chain of patch operations that can be used to construct the target revision # each chain member is an array with two elements. # the first element denotes the patch type (-1, 1), where # 1 => apply the revision # -1 => revert the revision # and the second element is the revision. attr_reader :patches def self.build_for(revision, content_cache = nil) if content_cache && content_cache.revision patches = patches_to(revision, content_cache.revision) if content_cache.transfer_target if patches.present? && patches.first.second == content_cache.transfer_target # the transfer target is already included in the chain - no extra patch needed! else patches.unshift([-1, content_cache.transfer_target]) end end new(content_cache, patches) else new(nil, patches_to(revision)) end end def self.query_counter @query_counter || 0 end def self.count_query @query_counter = query_counter + 1 end def initialize(content_cache, patches) @content_cache = content_cache @patches = patches end # performs a revision aware query on the cms database. # # returns a hash of results from the query. # the keys are the criterion by which the query results are grouped (typically the OBJ ID). # the values are tuples where # - the first element denotes the diff type and # - the second the document rows def query(index, key) self.class.count_query result_map = perform_queries(index, key) version_map = result_map.merge(result_map) do |ignore, results| results.map {|row| Version.new(row) } end revision_aware_results(version_map) end private # calculate a list of patches to get to the destionation revision. # if origin is given, the list will start at origin. # otherwise the list will start at the initial revision. def self.patches_to(destination, origin = nil) if destination.nil? && origin.nil? [] elsif origin.nil? || origin.generation < destination.generation patches_to(destination.base_revision, origin) + [[1, destination]] elsif destination.nil? || origin.generation > destination.generation [[-1, origin]] + patches_to(destination, origin.base_revision) elsif origin != destination [[-1, origin]] + patches_to(destination.base_revision, origin.base_revision) + [[1, destination]] else # origin == destination [] end end def perform_queries(index, key) result_map = {} query_options = { :hash_value => key, } if content_cache.present? result_map[:content_cache] = CmsBaseModel.query_index(:obj, index, query_options.merge({ :range_value => "cc/#{content_cache.id}", })) end if patches.present? result_map[:patches] = CmsBaseModel.query_index(:obj, index, query_options.merge({ :range_value => revision_range, })) end result_map end # filter out those results that aren't on the chain. # order the remaining results according to their chain position def order_results_by_chain(result_map) results = [] if result_map[:content_cache].present? results << [1, result_map[:content_cache]] end if result_map[:patches].present? results_by_revision = result_map[:patches].group_by do |version| version.revision_id end patches.each do |(patch_diff_type, revision)| results_for_revision = results_by_revision[revision.id] results << [patch_diff_type, results_for_revision] if results_for_revision end end results end # group the results by obj id # i.e. calculate a separate chain for each obj def group_results(result_map) results_by_chain = order_results_by_chain(result_map) result_index = {} results_by_chain.each do |(patch_diff_type, versions)| versions.each do |version| id = version.obj_id result_index[id] ||= [] result_index[id] << [patch_diff_type, version] end end result_index end # apply the diff chain for each of obj # to determine which of them are included in the final revision def revision_aware_results(result_map) result_index = {} # note: using merge as a map function for hashes group_results(result_map).each do |id, apply_chain| final_existance = apply_chain.inject(0) do |temp_existance, (patch_diff_type, version)| # remember: -1 (revert) multiplied with -1 (revert) equals 1 (apply) # and: +1 (create) added to -1 (delete) equals 0 (not existant) temp_existance + patch_diff_type * version.diff end if final_existance > 0 patch_diff, version = apply_chain.last result_index[id] = version.obj_data(patch_diff) end end result_index end private def revision_range @revision_range ||= begin sorted_keys = patches.map do |(type, revision)| "#{"%010d" % revision.generation}/#{revision.id}" end.sort Range.new(sorted_keys.first, sorted_keys.last) end end end end # module RailsConnector