inventory_refresh 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +47 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +4 -0
  5. data/.rspec_ci +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_cc.yml +5 -0
  8. data/.rubocop_local.yml +2 -0
  9. data/.travis.yml +12 -0
  10. data/.yamllint +12 -0
  11. data/CHANGELOG.md +0 -0
  12. data/Gemfile +6 -0
  13. data/LICENSE +202 -0
  14. data/README.md +35 -0
  15. data/Rakefile +47 -0
  16. data/bin/console +14 -0
  17. data/bin/setup +8 -0
  18. data/inventory_refresh.gemspec +34 -0
  19. data/lib/inventory_refresh.rb +11 -0
  20. data/lib/inventory_refresh/application_record_iterator.rb +56 -0
  21. data/lib/inventory_refresh/application_record_reference.rb +15 -0
  22. data/lib/inventory_refresh/graph.rb +157 -0
  23. data/lib/inventory_refresh/graph/topological_sort.rb +66 -0
  24. data/lib/inventory_refresh/inventory_collection.rb +1175 -0
  25. data/lib/inventory_refresh/inventory_collection/data_storage.rb +178 -0
  26. data/lib/inventory_refresh/inventory_collection/graph.rb +170 -0
  27. data/lib/inventory_refresh/inventory_collection/index/proxy.rb +230 -0
  28. data/lib/inventory_refresh/inventory_collection/index/type/base.rb +80 -0
  29. data/lib/inventory_refresh/inventory_collection/index/type/data.rb +26 -0
  30. data/lib/inventory_refresh/inventory_collection/index/type/local_db.rb +286 -0
  31. data/lib/inventory_refresh/inventory_collection/index/type/skeletal.rb +116 -0
  32. data/lib/inventory_refresh/inventory_collection/reference.rb +96 -0
  33. data/lib/inventory_refresh/inventory_collection/references_storage.rb +106 -0
  34. data/lib/inventory_refresh/inventory_collection/scanner.rb +117 -0
  35. data/lib/inventory_refresh/inventory_collection/serialization.rb +140 -0
  36. data/lib/inventory_refresh/inventory_object.rb +303 -0
  37. data/lib/inventory_refresh/inventory_object_lazy.rb +151 -0
  38. data/lib/inventory_refresh/save_collection/base.rb +38 -0
  39. data/lib/inventory_refresh/save_collection/recursive.rb +52 -0
  40. data/lib/inventory_refresh/save_collection/saver/base.rb +390 -0
  41. data/lib/inventory_refresh/save_collection/saver/batch.rb +17 -0
  42. data/lib/inventory_refresh/save_collection/saver/concurrent_safe.rb +71 -0
  43. data/lib/inventory_refresh/save_collection/saver/concurrent_safe_batch.rb +632 -0
  44. data/lib/inventory_refresh/save_collection/saver/default.rb +57 -0
  45. data/lib/inventory_refresh/save_collection/saver/sql_helper.rb +85 -0
  46. data/lib/inventory_refresh/save_collection/saver/sql_helper_update.rb +120 -0
  47. data/lib/inventory_refresh/save_collection/saver/sql_helper_upsert.rb +196 -0
  48. data/lib/inventory_refresh/save_collection/topological_sort.rb +38 -0
  49. data/lib/inventory_refresh/save_inventory.rb +38 -0
  50. data/lib/inventory_refresh/target.rb +73 -0
  51. data/lib/inventory_refresh/target_collection.rb +80 -0
  52. data/lib/inventory_refresh/version.rb +3 -0
  53. data/tools/ci/create_db_user.sh +3 -0
  54. metadata +207 -0
@@ -0,0 +1,80 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ module Index
6
+ module Type
7
+ class Base
8
+ # @param inventory_collection [InventoryRefresh::InventoryCollection] InventoryCollection owning the index
9
+ # @param index_name
10
+ def initialize(inventory_collection, index_name, attribute_names, *_args)
11
+ @index = {}
12
+
13
+ @inventory_collection = inventory_collection
14
+ @index_name = index_name
15
+ @attribute_names = attribute_names
16
+
17
+ assert_attribute_names!
18
+ end
19
+
20
+ delegate :keys, :to => :index
21
+
22
+ # Indexes passed InventoryObject
23
+ #
24
+ # @param inventory_object [InventoryRefresh::InventoryObject] InventoryObject we want to index
25
+ # @return [InventoryRefresh::InventoryObject] InventoryObject object
26
+ def store_index_for(inventory_object)
27
+ index[build_stringified_reference(inventory_object.data, attribute_names)] = inventory_object
28
+ end
29
+
30
+ # Rebuilds the indexes for all InventoryObject objects
31
+ def reindex!
32
+ self.index = {}
33
+ data.each do |inventory_object|
34
+ store_index_for(inventory_object)
35
+ end
36
+ end
37
+
38
+ # @return [Array] Returns index data
39
+ def index_data
40
+ index.values
41
+ end
42
+
43
+ # Find value based on index_value
44
+ #
45
+ # @param _index_value [String] a index_value of the InventoryObject we search for
46
+ def find(_index_value)
47
+ raise "Implement in subclass"
48
+ end
49
+
50
+ protected
51
+
52
+ attr_reader :attribute_names, :index, :index_name, :inventory_collection
53
+
54
+ private
55
+
56
+ attr_writer :index
57
+
58
+ delegate :build_stringified_reference, :data, :model_class, :custom_save_block, :to => :inventory_collection
59
+
60
+ # Asserts that InventoryCollection model has attributes specified for index
61
+ def assert_attribute_names!
62
+ # Skip for manually defined nodes
63
+ return if model_class.nil?
64
+ # When we do custom saving, we allow any indexes to be passed, to no limit the user
65
+ return unless custom_save_block.nil?
66
+
67
+ # We cannot simply do model_class.method_defined?(attribute_name.to_sym), because e.g. db attributes seems
68
+ # to be create lazily
69
+ test_model_object = model_class.new
70
+ attribute_names.each do |attribute_name|
71
+ unless test_model_object.respond_to?(attribute_name.to_sym)
72
+ raise "Invalid definition of index :#{index_name}, there is no attribute :#{attribute_name} on model #{model_class}"
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,26 @@
1
+ require "inventory_refresh/inventory_collection/index/type/base"
2
+
3
+ module InventoryRefresh
4
+ class InventoryCollection
5
+ module Index
6
+ module Type
7
+ class Data < InventoryRefresh::InventoryCollection::Index::Type::Base
8
+ # Find value based on index_value
9
+ #
10
+ # @param index_value [String] a index_value of the InventoryObject we search in data
11
+ def find(index_value)
12
+ index[index_value]
13
+ end
14
+
15
+ # Deletes and returns the value on the index_value
16
+ #
17
+ # @param index_value [String] a index_value of the InventoryObject we search for
18
+ # @return [InventoryObject|nil] Returns found value or nil
19
+ def delete(index_value)
20
+ index.delete(index_value)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,286 @@
1
+ require "inventory_refresh/inventory_collection/index/type/base"
2
+ require "inventory_refresh/application_record_reference"
3
+ require "active_support/core_ext/module/delegation"
4
+ require "more_core_extensions/core_ext/hash"
5
+
6
+ module InventoryRefresh
7
+ class InventoryCollection
8
+ module Index
9
+ module Type
10
+ class LocalDb < InventoryRefresh::InventoryCollection::Index::Type::Base
11
+ # (see InventoryRefresh::InventoryCollection::Index::Type::Base#initialize)
12
+ # @param data_index[InventoryRefresh::InventoryCollection::Index::Type::Data] Related data index, so we can
13
+ # figure out what data we are building vs. what we need to fetch from the DB
14
+ def initialize(inventory_collection, index_name, attribute_names, data_index)
15
+ super
16
+
17
+ @index = nil
18
+ @loaded_references = Set.new
19
+ @data_index = data_index
20
+ @all_references_loaded = false
21
+ end
22
+
23
+ # Finds reference in the DB. Using a configured strategy we cache obtained data in the index, so the
24
+ # same find will not hit database twice. Also if we use lazy_links and this is called when
25
+ # data_collection_finalized?, we load all data from the DB, referenced by lazy_links, in one query.
26
+ #
27
+ # @param reference [InventoryRefresh::InventoryCollection::Reference] Reference we want to find
28
+ def find(reference)
29
+ # Use the cached index only data_collection_finalized?, meaning no new reference can occur
30
+ if data_collection_finalized? && all_references_loaded? && index
31
+ return index[reference.stringified_reference]
32
+ else
33
+ return index[reference.stringified_reference] if index && index[reference.stringified_reference]
34
+ # We haven't found the reference, lets add it to the list of references and load it
35
+ add_reference(reference)
36
+ end
37
+
38
+ # Put our existing data_index keys into loaded references
39
+ loaded_references.merge(data_index.keys)
40
+ # Load the rest of the references from the DB
41
+ populate_index!
42
+
43
+ self.all_references_loaded = true if data_collection_finalized?
44
+
45
+ index[reference.stringified_reference]
46
+ end
47
+
48
+ private
49
+
50
+ attr_accessor :all_references_loaded, :schema
51
+ attr_reader :data_index, :loaded_references
52
+ attr_writer :index
53
+
54
+ delegate :add_reference,
55
+ :arel,
56
+ :association,
57
+ :association_to_base_class_mapping,
58
+ :association_to_foreign_key_mapping,
59
+ :association_to_foreign_type_mapping,
60
+ :attribute_references,
61
+ :data_collection_finalized?,
62
+ :db_relation,
63
+ :inventory_object?,
64
+ :inventory_object_lazy?,
65
+ :model_class,
66
+ :new_inventory_object,
67
+ :parent,
68
+ :references,
69
+ :strategy,
70
+ :stringify_joiner,
71
+ :table_name,
72
+ :to => :inventory_collection
73
+
74
+ alias all_references_loaded? all_references_loaded
75
+
76
+ # Fills index with InventoryObjects obtained from the DB
77
+ def populate_index!
78
+ # Load only new references from the DB
79
+ new_references = index_references - loaded_references
80
+ # And store which references we've already loaded
81
+ loaded_references.merge(new_references)
82
+
83
+ # Initialize index in nil
84
+ self.index ||= {}
85
+
86
+ return if new_references.blank? # Return if all references are already loaded
87
+
88
+ # TODO(lsmola) selected need to contain also :keys used in other InventoryCollections pointing to this one, once
89
+ # we get list of all keys for each InventoryCollection ,we can uncomnent
90
+ # selected = [:id] + attribute_names.map { |x| model_class.reflect_on_association(x).try(:foreign_key) || x }
91
+ # selected << :type if model_class.new.respond_to? :type
92
+ # load_from_db.select(selected).find_each do |record|
93
+
94
+ full_references = references[index_name].select { |key, _value| new_references.include?(key) } # O(1) include on Set
95
+ full_references = full_references.values.map(&:full_reference)
96
+
97
+ schema = get_schema(full_references)
98
+ paths = schema.keys
99
+ rails_friendly_includes_schema = get_rails_friendly_includes_schema(paths)
100
+
101
+ all_values = full_references.map do |ref|
102
+ schema.map do |schema_item_path, arel_column|
103
+ arel_column.eq(fetch_hash_path(schema_item_path, ref))
104
+ end.inject(:and)
105
+ end
106
+
107
+ # Return the the correct relation based on strategy and selection&projection
108
+ projection = nil
109
+
110
+ db_relation(rails_friendly_includes_schema, all_values, projection).find_each do |record|
111
+ process_db_record!(record, paths)
112
+ end
113
+ end
114
+
115
+ # Builds a multiselection conditions like (table1.a = a1 AND table2.b = b1) OR (table1.a = a2 AND table2.b = b2)
116
+ # @param all_values [Array<Arel::Nodes::And>] nested array of arel nodes
117
+ # @return [String] A condition usable in .where of an ActiveRecord relation
118
+ def build_multi_selection_condition(all_values)
119
+ # We do pure SQL OR, since Arel is nesting every .or into another parentheses, otherwise this would be just
120
+ # all_values.inject(:or)
121
+ all_values.map { |value| "(#{value.to_sql})" }.join(" OR ")
122
+ end
123
+
124
+ # Traverses the schema_item_path e.g. [:hardware, :vm_or_template, :ems_ref] and gets the value on the path
125
+ # in hash.
126
+ # @param path [Array] path for traversing hash e.g. [:hardware, :vm_or_template, :ems_ref]
127
+ # @param hash [Hash] nested hash with data e.g. {:hardware => {:vm_or_template => {:ems_ref => "value"}}}
128
+ # @return [Object] value in the Hash on the path, in the example that would be "value"
129
+ def fetch_hash_path(path, hash)
130
+ path.inject(hash) { |x, r| x.try(:[], r) }
131
+ end
132
+
133
+ # Traverses the schema_item_path e.g. [:hardware, :vm_or_template, :ems_ref] and gets the value on the path
134
+ # in object.
135
+ # @param path [Array] path for traversing hash e.g. [:hardware, :vm_or_template, :ems_ref]
136
+ # @param object [ApplicationRecord] an ApplicationRecord fetched from the DB
137
+ # @return [Object] value in the Hash on the path, in the example that would be "value"
138
+ def fetch_object_path(path, object)
139
+ path.inject(object) { |x, r| x.public_send(r) }
140
+ end
141
+
142
+ # For full_reference {:hardware => lazy_find_hardware(lazy_find_vm_or_template(:ems_ref))}
143
+ # we get schema of
144
+ # {[:hardware, :vm_or_template, :ems_ref] => VmOrTemplate.arel_table[:ems_ref]]
145
+ #
146
+ # @param full_references [Hash] InventoryRefresh::InventoryCollection::Reference object full_reference method
147
+ # containing full reference to InventoryObject
148
+ # @return [Hash] Hash containing key representing path to record's attribute and value representing arel
149
+ # definition of column
150
+ def get_schema(full_references)
151
+ @schema ||= get_schema_recursive(attribute_names, model_class.arel_table, full_references.first, {}, [], 0)
152
+ end
153
+
154
+ # Converts an array of paths to attributes in different DB tables into rails friendly format, that can be used
155
+ # for correct DB JOIN of those tables (references&includes methods)
156
+ # @param [Array[Array]] Nested array with paths e.g. [[:hardware, :vm_or_template, :ems_ref], [:description]
157
+ # @return [Array] A rails friendly format for ActiveRecord relation .includes and .references
158
+ def get_rails_friendly_includes_schema(paths)
159
+ return @rails_friendly_includes_schema if @rails_friendly_includes_schema
160
+
161
+ nested_hashes_schema = {}
162
+ # Ignore last value in path and build nested hash from paths, e.g. paths
163
+ # [[:hardware, :vm_or_template, :ems_ref], [:description] will be transformed to
164
+ # [[:hardware, :vm_or_template]], so only relations we need for DB join and then to nested hash
165
+ # {:hardware => {:vm_or_template => {}}}
166
+ paths.map { |x| x[0..-2] }.select(&:present?).each { |x| nested_hashes_schema.store_path(x, {}) }
167
+ # Convert nested Hash to Rails friendly format, e.g. {:hardware => {:vm_or_template => {}}} will be
168
+ # transformed to [:hardware => :vm_or_template]
169
+ @rails_friendly_includes_schema = transform_hash_to_rails_friendly_array_recursive(nested_hashes_schema, [])
170
+ end
171
+
172
+ # @param hash [Hash] Nested hash representing join schema e.g. {:hardware => {:vm_or_template => {}}}
173
+ # @param current_layer [Array] One layer of the joins schema
174
+ # @return [Array] Transformed hash applicable for Rails .joins, e.g. [:hardware => [:vm_or_template]]
175
+ def transform_hash_to_rails_friendly_array_recursive(hash, current_layer)
176
+ array_attributes, hash_attributes = hash.partition { |_key, value| value.blank? }
177
+
178
+ array_attributes = array_attributes.map(&:first)
179
+ current_layer.concat(array_attributes)
180
+ # current_array.concat(array_attributes)
181
+ if hash_attributes.present?
182
+ last_hash_attr = hash_attributes.each_with_object({}) do |(key, value), obj|
183
+ obj[key] = transform_hash_to_rails_friendly_array_recursive(value, [])
184
+ end
185
+ current_layer << last_hash_attr
186
+ end
187
+
188
+ current_layer
189
+ end
190
+
191
+ # A recursive method for getting a schema out of full_reference (see #get_schema)
192
+ #
193
+ # @param attribute_names [Array<Symbol>] Array of attribute names
194
+ # @param arel_table [Arel::Table]
195
+ # @param data [Hash] The full reference layer
196
+ # @param schema [Hash] Recursively built schema
197
+ # @param path [Array] Recursively build path
198
+ # @param total_level [Integer] Guard for max recursive nesting
199
+ # @return [Hash] Recursively built schema
200
+ def get_schema_recursive(attribute_names, arel_table, data, schema, path, total_level)
201
+ raise "Nested too deep" if total_level > 100
202
+
203
+ attribute_names.each do |key|
204
+ new_path = path + [key]
205
+
206
+ value = data[key]
207
+
208
+ if inventory_object?(value)
209
+ get_schema_recursive(value.inventory_collection.manager_ref,
210
+ value.inventory_collection.model_class.arel_table,
211
+ value,
212
+ schema,
213
+ new_path,
214
+ total_level + 1)
215
+ elsif inventory_object_lazy?(value)
216
+ get_schema_recursive(value.inventory_collection.index_proxy.named_ref(value.ref),
217
+ value.inventory_collection.model_class.arel_table,
218
+ value.reference.full_reference,
219
+ schema,
220
+ new_path,
221
+ total_level + 1)
222
+ else
223
+ schema[new_path] = arel_table[key]
224
+ end
225
+ end
226
+
227
+ schema
228
+ end
229
+
230
+ # Returns keys of the reference
231
+ #
232
+ # @return [Array] Keys of the reference
233
+ def index_references
234
+ Set.new(references[index_name].try(:keys) || [])
235
+ end
236
+
237
+ # Return a Rails relation that will be used to obtain the records we need to load from the DB
238
+ #
239
+ # @param rails_friendly_includes_schema [Array] Schema usable in .includes and .references methods of
240
+ # ActiveRecord relation object
241
+ # @param all_values [Array<Array>] nested array of values in format [[a1, b1], [a2, b2]] the nested array
242
+ # values must have the order of column_names
243
+ # @param projection [Array] A projection array resulting in Project operation (in Relation algebra terms)
244
+ # @return [ActiveRecord::AssociationRelation] relation object having filtered data
245
+ def db_relation(rails_friendly_includes_schema, all_values = nil, projection = nil)
246
+ relation = if !parent.nil? && !association.nil?
247
+ parent.send(association)
248
+ elsif !arel.nil?
249
+ arel
250
+ end
251
+ relation = relation.where(build_multi_selection_condition(all_values)) if relation && all_values
252
+ relation = relation.select(projection) if relation && projection
253
+ relation = relation.includes(rails_friendly_includes_schema).references(rails_friendly_includes_schema) if rails_friendly_includes_schema.present?
254
+ relation || model_class.none
255
+ end
256
+
257
+ # Takes ApplicationRecord record, converts it to the InventoryObject and places it to index
258
+ #
259
+ # @param record [ApplicationRecord] ApplicationRecord record we want to place to the index
260
+ def process_db_record!(record, paths)
261
+ # Important fact is that the path was added as .includes in the query, so this doesn't generate n+1 queries
262
+ index_value = InventoryRefresh::InventoryCollection::Reference.stringify_reference(
263
+ paths.map { |path| fetch_object_path(path, record) }
264
+ )
265
+
266
+ attributes = record.attributes.symbolize_keys
267
+ attribute_references.each do |ref|
268
+ # We need to fill all references that are relations, we will use a InventoryRefresh::ApplicationRecordReference which
269
+ # can be used for filling a relation and we don't need to do any query here.
270
+ # TODO(lsmola) maybe loading all, not just referenced here? Otherwise this will have issue for db_cache_all
271
+ # and find used in parser
272
+ # TODO(lsmola) the last usage of this should be lazy_find_by with :key specified, maybe we can get rid of this?
273
+ next unless (foreign_key = association_to_foreign_key_mapping[ref])
274
+ base_class_name = attributes[association_to_foreign_type_mapping[ref].try(:to_sym)] || association_to_base_class_mapping[ref]
275
+ id = attributes[foreign_key.to_sym]
276
+ attributes[ref] = InventoryRefresh::ApplicationRecordReference.new(base_class_name, id)
277
+ end
278
+
279
+ index[index_value] = new_inventory_object(attributes)
280
+ index[index_value].id = record.id
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,116 @@
1
+ require "inventory_refresh/inventory_collection/index/type/base"
2
+ require "active_support/core_ext/module/delegation"
3
+
4
+ module InventoryRefresh
5
+ class InventoryCollection
6
+ module Index
7
+ module Type
8
+ class Skeletal < InventoryRefresh::InventoryCollection::Index::Type::Base
9
+ # (see InventoryRefresh::InventoryCollection::Index::Type::Base#initialize)
10
+ # @param primary_index [InventoryRefresh::InventoryCollection::Index::Type::Data] Data index of primary_index
11
+ def initialize(inventory_collection, index_name, attribute_names, primary_index)
12
+ super
13
+
14
+ @primary_index = primary_index
15
+ end
16
+
17
+ delegate :default_values,
18
+ :new_inventory_object,
19
+ :named_ref,
20
+ :to => :inventory_collection
21
+
22
+ delegate :blank?,
23
+ :each,
24
+ :each_value,
25
+ :to => :index
26
+
27
+ # Find value based on index_value
28
+ #
29
+ # @param index_value [String] a index_value of the InventoryObject we search for
30
+ # @return [InventoryObject|nil] Returns found value or nil
31
+ def find(index_value)
32
+ index[index_value]
33
+ end
34
+
35
+ # Deletes and returns the value on the index_value
36
+ #
37
+ # @param index_value [String] a index_value of the InventoryObject we search for
38
+ # @return [InventoryObject|nil] Returns found value or nil
39
+ def delete(index_value)
40
+ index.delete(index_value)
41
+ end
42
+
43
+ # Takes value from primary_index and inserts it to skeletal index
44
+ #
45
+ # @param index_value [String] a index_value of the InventoryObject we search for
46
+ # @return [InventoryObject|nil] Returns found value or nil
47
+ def skeletonize_primary_index(index_value)
48
+ inventory_object = primary_index.delete(index_value)
49
+ return unless inventory_object
50
+ fill_versions!(inventory_object.data)
51
+
52
+ index[index_value] = inventory_object
53
+ end
54
+
55
+ # Builds index record with skeletal InventoryObject and returns it. Or it returns existing InventoryObject
56
+ # that is found in primary_index or skeletal_primary_index.
57
+ #
58
+ # @param attributes [Hash] Skeletal data of the index, must contain unique index keys and everything else
59
+ # needed for creating the record in the Database
60
+ # @return [InventoryObject] Returns built InventoryObject or existing InventoryObject with new attributes
61
+ # assigned
62
+ def build(attributes)
63
+ attributes = {}.merge!(default_values).merge!(attributes)
64
+ fill_versions!(attributes)
65
+
66
+ # If the primary index is already filled, we don't want populate skeletal index
67
+ uuid = ::InventoryRefresh::InventoryCollection::Reference.build_stringified_reference(attributes, named_ref)
68
+ if (inventory_object = primary_index.find(uuid))
69
+ return inventory_object.assign_attributes(attributes)
70
+ end
71
+
72
+ # Return if skeletal index already exists
73
+ if (inventory_object = index[uuid])
74
+ return inventory_object.assign_attributes(attributes)
75
+ end
76
+
77
+ # We want to populate a new skeletal index
78
+ inventory_object = new_inventory_object(attributes)
79
+ index[inventory_object.manager_uuid] = inventory_object
80
+ end
81
+
82
+ private
83
+
84
+ attr_reader :primary_index
85
+
86
+ # Add versions columns into the passed attributes
87
+ #
88
+ # @param attributes [Hash] Attributes we want to extend with version related attributes
89
+ def fill_versions!(attributes)
90
+ if inventory_collection.supports_resource_timestamps_max? && attributes[:resource_timestamp]
91
+ fill_specific_version_attr(:resource_timestamps, :resource_timestamp, attributes)
92
+ elsif inventory_collection.supports_resource_versions_max? && attributes[:resource_version]
93
+ fill_specific_version_attr(:resource_versions, :resource_version, attributes)
94
+ end
95
+ end
96
+
97
+ # Add specific versions columns into the passed attributes
98
+ #
99
+ # @param partial_row_version_attr [Symbol] Attr name for partial rows, allowed values are
100
+ # [:resource_timestamps, :resource_versions]
101
+ # @param full_row_version_attr [Symbol] Attr name for full rows, allowed values are
102
+ # [:resource_timestamp, :resource_version]
103
+ # @param attributes [Hash] Attributes we want to extend with version related attributes
104
+ def fill_specific_version_attr(partial_row_version_attr, full_row_version_attr, attributes)
105
+ # We have to symbolize, since serializing persistor makes these strings
106
+ (attributes[partial_row_version_attr] ||= {}).symbolize_keys!
107
+
108
+ (attributes.keys - inventory_collection.base_columns).each do |key|
109
+ attributes[partial_row_version_attr][key] ||= attributes[full_row_version_attr]
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end