require "inventory_refresh/inventory_collection/index/type/data" require "inventory_refresh/inventory_collection/index/type/local_db" require "inventory_refresh/inventory_collection/index/type/skeletal" require "inventory_refresh/logging" require "active_support/core_ext/module/delegation" require "active_support/deprecation" module InventoryRefresh class InventoryCollection module Index class Proxy include InventoryRefresh::Logging attr_reader :skeletal_primary_index # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection object owning the proxy # @param secondary_refs [Hash] Secondary_refs in format {:name_of_the_ref => [:attribute1, :attribute2]} def initialize(inventory_collection, secondary_refs = {}) @inventory_collection = inventory_collection @primary_ref = {primary_index_ref => @inventory_collection.manager_ref} @secondary_refs = secondary_refs @all_refs = @primary_ref.merge(@secondary_refs) @data_indexes = {} @local_db_indexes = {} @all_refs.each do |index_name, attribute_names| @data_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::Data.new( inventory_collection, index_name, attribute_names ) @local_db_indexes[index_name] = InventoryRefresh::InventoryCollection::Index::Type::LocalDb.new( inventory_collection, index_name, attribute_names, @data_indexes[index_name] ) end @skeletal_primary_index = InventoryRefresh::InventoryCollection::Index::Type::Skeletal.new( inventory_collection, :skeletal_primary_index_ref, named_ref, primary_index ) end # Builds primary index for passed InventoryObject # # @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject we want to index # @return [InventoryRefresh::InventoryObject] Passed InventoryObject def build_primary_index_for(inventory_object) # Building the object, we need to provide all keys of a primary index assert_index(inventory_object.data, primary_index_ref) primary_index.store_index_for(inventory_object) end def build_secondary_indexes_for(inventory_object) secondary_refs.each_key do |ref| data_index(ref).store_index_for(inventory_object) end end def reindex_secondary_indexes! data_indexes.each do |ref, index| next if ref == primary_index_ref index.reindex! end end def primary_index data_index(primary_index_ref) end def find(reference, ref: primary_index_ref) # TODO(lsmola) this method should return lazy too, the rest of the finders should be deprecated return if reference.nil? assert_index(reference, ref) reference = inventory_collection.build_reference(reference, ref) case strategy when :local_db_find_references, :local_db_cache_all local_db_index_find(reference) when :local_db_find_missing_references find_in_data_or_skeletal_index(reference) || local_db_index_find(reference) else find_in_data_or_skeletal_index(reference) end end def lazy_find(manager_uuid = nil, opts = {}, ref: primary_index_ref, key: nil, default: nil, transform_nested_lazy_finds: false, **manager_uuid_hash) # TODO(lsmola) also, it should be enough to have only 1 find method, everything can be lazy, until we try to # access the data ref = opts[:ref] if opts.key?(:ref) key = opts[:key] if opts.key?(:key) default = opts[:default] if opts.key?(:default) transform_nested_lazy_finds = opts[:transform_nested_lazy_finds] if opts.key?(:transform_nested_lazy_finds) manager_uuid_hash.update(opts.except(:ref, :key, :default, :transform_nested_lazy_finds)) # Skip if no manager_uuid is provided return if manager_uuid.nil? && manager_uuid_hash.blank? raise ArgumentError, "only one of manager_uuid or manager_uuid_hash must be passed" unless !!manager_uuid ^ !!manager_uuid_hash.present? ActiveSupport::Deprecation.warn("Passing a hash for options is deprecated and will be removed in an upcoming release.") if opts.present? manager_uuid ||= manager_uuid_hash assert_index(manager_uuid, ref) ::InventoryRefresh::InventoryObjectLazy.new(inventory_collection, manager_uuid, :ref => ref, :key => key, :default => default, :transform_nested_lazy_finds => transform_nested_lazy_finds) end def named_ref(ref = primary_index_ref) all_refs[ref] end def primary_index_ref :manager_ref end private delegate :association_to_foreign_key_mapping, :build_stringified_reference, :parallel_safe?, :strategy, :to => :inventory_collection attr_reader :all_refs, :data_indexes, :inventory_collection, :primary_ref, :local_db_indexes, :secondary_refs def find_in_data_or_skeletal_index(reference) if parallel_safe? # With parallel safe strategies, we create skeletal nodes that we can look for data_index_find(reference) || skeletal_index_find(reference) else data_index_find(reference) end end def skeletal_index_find(reference) # Find in skeletal index, but we are able to create skeletal index only for primary indexes skeletal_primary_index.find(reference.stringified_reference) if reference.primary? end def data_index_find(reference) data_index(reference.ref).find(reference.stringified_reference) end def local_db_index_find(reference) local_db_index(reference.ref).find(reference) end def data_index(name) data_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}") end def local_db_index(name) local_db_indexes[name] || raise("Index :#{name} not defined for #{inventory_collection}") end def missing_keys(data_keys, ref) named_ref(ref) - data_keys end def required_index_keys_present?(data_keys, ref) missing_keys(data_keys, ref).empty? end def assert_relation_keys(data, ref) named_ref(ref).each do |key| # Skip if the key is not a foreign key next unless association_to_foreign_key_mapping[key] # Skip if data on key are nil or InventoryObject or InventoryObjectLazy next if data[key].nil? || data[key].kind_of?(InventoryRefresh::InventoryObject) || data[key].kind_of?(InventoryRefresh::InventoryObjectLazy) # Raise error since relation must be nil or InventoryObject or InventoryObjectLazy raise "Wrong index for key :#{key}, the value must be of type Nil or InventoryObject or InventoryObjectLazy, got: #{data[key]}" end end def assert_index_exists(ref) raise "Index :#{ref} doesn't exist on #{inventory_collection}" if named_ref(ref).nil? end def assert_index(manager_uuid, ref) # TODO(lsmola) do we need some production logging too? Maybe the refresh log level could drive this # Let' do this really slick development and test env, but disable for production, since the checks are pretty # slow. return unless inventory_collection.assert_graph_integrity if manager_uuid.kind_of?(InventoryRefresh::InventoryCollection::Reference) # InventoryRefresh::InventoryCollection::Reference has been already asserted, skip elsif manager_uuid.kind_of?(Hash) # Test te index exists assert_index_exists(ref) # Test we are sending all keys required for the index unless required_index_keys_present?(manager_uuid.keys, ref) raise "Finder has missing keys for index :#{ref}, missing indexes are: #{missing_keys(manager_uuid.keys, ref)}" end # Test that keys, that are relations, are nil or InventoryObject or InventoryObjectlazy class assert_relation_keys(manager_uuid, ref) else # Test te index exists assert_index_exists(ref) # Check that other value (possibly String or Integer)) has no composite index if named_ref(ref).size > 1 right_format = "collection.find(#{named_ref(ref).map { |x| ":#{x} => 'X'" }.join(", ")}" raise "The index :#{ref} has composite index, finder has to be called as: #{right_format})" end # Assert the that possible relation is nil or InventoryObject or InventoryObjectlazy class assert_relation_keys({named_ref(ref).first => manager_uuid}, ref) end rescue => e logger.error("Error when asserting index: #{manager_uuid}, with ref: #{ref} of: #{inventory_collection}") raise e end end end end end