bulk_dependency_eraser 4.3.0 → 4.4.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6cc809556aa24a68e132b8b18c5107452d98a7659594c8ae60807d4a41192941
4
- data.tar.gz: e21b344baed301a3b8732d9b8b6d517fa38353de1169eb3842f752d1aec31be9
3
+ metadata.gz: 886ff4e4e736393ca417bfca554cc0f0cac8dd3d2c78233e8cfdb9c007d4b0f1
4
+ data.tar.gz: d81c392e904f999442287d77349b52659dd14621da208ce5f942ff8f4529e5a3
5
5
  SHA512:
6
- metadata.gz: 3715bf4c61448253a260e6b968c68bd4f27350f2fe960787c58c60023cade1a62af66ad611c1bf50d7c9c261c9d5f847d9fe28ebb3f31975a4dbe0122de5e56e
7
- data.tar.gz: 38dc0a80a042241fbb5964f8da4c0228e4f978cbaba9ebb328b6bd25d78133519c5573b453120cabbc899f8e8b1a041511579e9c5f946a5b65b49a2350761e92
6
+ metadata.gz: e0d7a0e10751d57d39dfc99ed6639170af4484f228b9778b8a7187c2168004738c1faf538c5417cf0037862a48635937723677367226816291696f20fad0eaae
7
+ data.tar.gz: edbefd2f654115b2566ad2b1c5b38fb6ca2970a61f0c7371e9254896c6a34c6c81044020a5fc4ac32d6bf4199c96858e059c1ed2c16578ceac3ac0383ec09901
@@ -58,6 +58,7 @@ module BulkDependencyEraser
58
58
  # Using PG system column CTID can result in a 10x deletion speed increase
59
59
  # - is used in combination with the primary key column to ensure CTID hasn't changed.
60
60
  use_pg_system_column_ctid: false,
61
+ delete_ctids_by_partions: false,
61
62
  }.freeze
62
63
 
63
64
  # write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.
@@ -177,8 +178,14 @@ module BulkDependencyEraser
177
178
  end
178
179
 
179
180
  # Only pluck CTID if we're plucking column :id (the only primary key supported)
180
- if columns == [:id] && opts_c.use_pg_system_column_ctid && skip_ctid_check == false
181
- columns << :ctid
181
+ if columns == [:id] && skip_ctid_check == false
182
+ columns << :ctid if opts_c.use_pg_system_column_ctid
183
+ columns << 'tableoid::regclass' if opts_c.delete_ctids_by_partions # to support partioned tables
184
+ end
185
+
186
+ # Gem test ENV only! Swap out SQL column value with SQLite3 mocked, compatible value
187
+ if ENV['TEST_GEM_ENV'] == 'bulk_dependency_eraser' && columns.include?('tableoid::regclass')
188
+ columns[columns.index('tableoid::regclass')] = 'tableoid'
182
189
  end
183
190
 
184
191
  set_current_klass_name(query)
@@ -222,6 +229,11 @@ module BulkDependencyEraser
222
229
  # - query_results would just be an array of IDs
223
230
  return [query_results, nil] if columns.count == 1
224
231
 
232
+ # Gem test ENV only! Swap out SQL column value with SQLite3 mocked, compatible value
233
+ if ENV['TEST_GEM_ENV'] == 'bulk_dependency_eraser' && columns.include?('tableoid')
234
+ columns[columns.index('tableoid')] = 'tableoid::regclass'
235
+ end
236
+
225
237
  transposed_results = query_results.transpose
226
238
 
227
239
  query_primary_keys = transposed_results.shift || [] # could be an empty id set
@@ -55,6 +55,10 @@ module BulkDependencyEraser
55
55
  # - 1st priority of scopes
56
56
  deletion_proc_scopes_per_class_name: {},
57
57
  use_ctid_over_primary_key: false,
58
+ # Using PG system column CTID can result in a 10x deletion speed increase
59
+ # - is used in combination with the primary key column to ensure CTID hasn't changed.
60
+ use_pg_system_column_ctid: false,
61
+ delete_ctids_by_partions: false,
58
62
  }.freeze
59
63
 
60
64
  def initialize class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}
@@ -131,63 +135,117 @@ module BulkDependencyEraser
131
135
  end
132
136
 
133
137
  # @param additional_identifiers [Hash] can be nil, if chosen to be so by implementers
138
+ # - Key: <ID> (primary_key)
139
+ # - Val: Hash of column/value pairings
134
140
  def delete_by_klass_and_ids(klass, ids, additional_identifiers:)
135
141
  puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
136
142
  query = klass.unscoped
137
143
  query = custom_scope_for_query(query)
138
144
 
145
+ # Get column-names/keys of any additional identifer columns
146
+ detected_additional_identifier_columns = additional_identifiers&.values&.flat_map(&:keys)&.uniq || []
147
+
148
+ if opts_c.use_pg_system_column_ctid && opts_c.delete_ctids_by_partions && (detected_additional_identifier_columns & ['ctid', 'tableoid::regclass']).size == 2
149
+ deletable_partioned_tables_by_ctids = additional_identifiers.values.pluck('tableoid::regclass').uniq
150
+ end
151
+
139
152
  if batching_disabled?
140
153
  puts "Deleting without batching" if opts_c.verbose
141
- # Get column-names/keys of any additional identifer columns
142
- detected_additional_identifier_columns = additional_identifiers&.values&.flat_map(&:keys)&.uniq || []
143
- delete_in_db do
144
- deletion_query = query
145
- if opts_c.use_ctid_over_primary_key && detected_additional_identifier_columns.include?('ctid')
146
- # Do nothing, query will be deleted via ctid
147
- else
148
- deletion_query = deletion_query.where(id: ids)
149
- end
150
-
151
- # Apply any additional query identifiers (i.e. :ctid column)
152
- detected_additional_identifier_columns.each do |column|
153
- deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
154
+ # Delete by partioned CTIDs
155
+ if deletable_partioned_tables_by_ctids
156
+ puts "Deleting via CTID and table partitions" if opts_c.verbose
157
+ deletable_partioned_tables_by_ctids.each do |table_partition|
158
+ identifiers_by_partition = additional_identifiers.filter { |id, identifiers| identifiers['tableoid::regclass'] == table_partition }
159
+ ctids = identifiers_by_partition.values.pluck('ctid')
160
+ ids = identifiers_by_partition.values.pluck('id')
161
+ deletion_by_partitioned_ctids(query, ids, ctids, table_partition, identifiers_by_partition)
154
162
  end
155
-
156
- # Perform Deletion
157
- deletion_result = deletion_query.delete_all
158
- # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
159
- # and have access to these objects in their proc.
160
- # - query can give them access to the klass and table_name
161
- [deletion_result, query, ids, additional_identifiers]
162
- end
163
- else
164
- puts "Deleting with batching" if opts_c.verbose
165
- ids.each_slice(batch_size) do |ids_subset|
166
- additional_identifiers_subset = additional_identifiers&.slice(*ids_subset)
167
- # Get column-names/keys of any additional identifer columns
168
- detected_additional_identifier_columns = additional_identifiers_subset&.values&.flat_map(&:keys)&.uniq || []
163
+ else
169
164
  delete_in_db do
170
165
  deletion_query = query
171
166
  if opts_c.use_ctid_over_primary_key && detected_additional_identifier_columns.include?('ctid')
172
167
  # Do nothing, query will be deleted via ctid
173
168
  else
174
- deletion_query = deletion_query.where(id: ids_subset)
169
+ deletion_query = deletion_query.where(id: ids)
175
170
  end
176
171
 
177
172
  # Apply any additional query identifiers (i.e. :ctid column)
178
173
  detected_additional_identifier_columns.each do |column|
179
- deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
174
+ deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
180
175
  end
181
176
 
182
177
  # Perform Deletion
183
178
  deletion_result = deletion_query.delete_all
184
-
185
179
  # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
186
180
  # and have access to these objects in their proc.
187
- # - query can give them access to the klass and table_name
188
- [deletion_result, query, ids_subset, additional_identifiers_subset]
181
+ # - query can give them access to the klass and table_name
182
+ [deletion_result, query, ids, additional_identifiers]
189
183
  end
190
184
  end
185
+ else
186
+ puts "Deleting with batching" if opts_c.verbose
187
+ if deletable_partioned_tables_by_ctids
188
+ puts "Deleting via CTID and table partitions" if opts_c.verbose
189
+ deletable_partioned_tables_by_ctids.each do |table_partition|
190
+ identifiers_by_partition = additional_identifiers.filter { |id, identifiers| identifiers['tableoid::regclass'] == table_partition }
191
+ ids = identifiers_by_partition.keys
192
+ ids.each_slice(batch_size) do |ids_subset|
193
+ additional_identifiers_subset = identifiers_by_partition.slice(*ids_subset)
194
+ ctids = additional_identifiers_subset.values.pluck('ctid')
195
+ deletion_by_partitioned_ctids(query, ids_subset, ctids, table_partition, identifiers_by_partition)
196
+ end
197
+ end
198
+ else
199
+ ids.each_slice(batch_size) do |ids_subset|
200
+ additional_identifiers_subset = additional_identifiers&.slice(*ids_subset)
201
+ # Get column-names/keys of any additional identifer columns
202
+ detected_additional_identifier_columns = additional_identifiers_subset&.values&.flat_map(&:keys)&.uniq || []
203
+ delete_in_db do
204
+ deletion_query = query
205
+ if opts_c.use_ctid_over_primary_key && detected_additional_identifier_columns.include?('ctid')
206
+ # Do nothing, query will be deleted via ctid
207
+ else
208
+ deletion_query = deletion_query.where(id: ids_subset)
209
+ end
210
+
211
+ # Apply any additional query identifiers (i.e. :ctid column)
212
+ detected_additional_identifier_columns.each do |column|
213
+ deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
214
+ end
215
+
216
+ # Perform Deletion
217
+ deletion_result = deletion_query.delete_all
218
+
219
+ # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
220
+ # and have access to these objects in their proc.
221
+ # - query can give them access to the klass and table_name
222
+ [deletion_result, query, ids_subset, additional_identifiers_subset]
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ def deletion_by_partitioned_ctids query, ids, ctids, partition_table_name, additional_identifiers
230
+ delete_in_db do
231
+ where_clause_sql = <<~SQL
232
+ ctid IN (#{ctids.map { |c| "'#{c}'" }.join(', ')})
233
+ SQL
234
+
235
+ deletion_sql = <<~SQL
236
+ DELETE FROM #{partition_table_name}
237
+ WHERE #{where_clause_sql};
238
+ SQL
239
+
240
+ # Due to the way we've mocked/aliased the partition table in this gem test, we need to also add the partition name as a where clause.
241
+ if ENV['TEST_GEM_ENV'] == 'bulk_dependency_eraser'
242
+ deletion_sql.sub!(/;\s*$/, "") # remove terminating semicolon
243
+ deletion_sql << " AND tableoid = '#{partition_table_name}';"
244
+ end
245
+
246
+ deleted_count = query.klass.connection.delete(deletion_sql)
247
+ selector_query = query.where(Arel.sql(where_clause_sql))
248
+ [deleted_count, selector_query, ids, additional_identifiers]
191
249
  end
192
250
  end
193
251
 
@@ -1,3 +1,3 @@
1
1
  module BulkDependencyEraser
2
- VERSION = "4.3.0".freeze
2
+ VERSION = "4.4.1".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulk_dependency_eraser
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.0
4
+ version: 4.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin.dana.software.dev@gmail.com