bulk_dependency_eraser 4.2.3 → 4.4.0

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: 5409fc52fbc918546360e196bd06c0be4837f0aa848af8a99aeed4015219f0e7
4
- data.tar.gz: d3818d4f8c8e2a3be0c81c5e3b37bc0295d2e7ca077c08a45e7ae143975f2f3e
3
+ metadata.gz: dcd62dc450607a490d23f589f457c2e2ad30b6ba4096b775266372d2f3952445
4
+ data.tar.gz: 8e430d8e74cf2d401b75d335a7fc3da82672aa4fedbc0446024bf94c1f632116
5
5
  SHA512:
6
- metadata.gz: 14d334bd9f2baf4008a653c8f19b0544bc32e02e36dea965f6fa08ad3f317ca4e3bff5eb2417aeb70549a54d109341884d87c9af7591c2a258b018d6444d993d
7
- data.tar.gz: 1f84b22cd3cfadf48d5450225c4cc3cbd4e3302acf15cd6c357904538b7427631a5d735621b3ecf4e574a31184525d9601630c9fd8c939d9e7f10fea6060e573
6
+ metadata.gz: f9added303e9a0982bd9cc026ef03937e0c095421fe89ba7edbdf24c7af27af61cce2f1e3c84b8f56d79fbf441ab5a4d72362fe9053b536af0286cb917c1abbc
7
+ data.tar.gz: 794831e885f3659586f5ffd075e93aee67d15e919bc2915023e733460e8739d4e5d7f86079f23feaf1cecb0a4a9ffaa6be5ccf885376203464fa7641952413c7
@@ -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
@@ -54,6 +54,11 @@ module BulkDependencyEraser
54
54
  # Applied to deletion queries
55
55
  # - 1st priority of scopes
56
56
  deletion_proc_scopes_per_class_name: {},
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,
57
62
  }.freeze
58
63
 
59
64
  def initialize class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}
@@ -130,49 +135,117 @@ module BulkDependencyEraser
130
135
  end
131
136
 
132
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
133
140
  def delete_by_klass_and_ids(klass, ids, additional_identifiers:)
134
141
  puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
135
142
  query = klass.unscoped
136
143
  query = custom_scope_for_query(query)
137
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
+ partioned_tables = additional_identifiers.values.pluck('tableoid::regclass').uniq
150
+ end
151
+
138
152
  if batching_disabled?
139
153
  puts "Deleting without batching" if opts_c.verbose
140
- # Get column-names/keys of any additional identifer columns
141
- detected_additional_identifier_columns = additional_identifiers&.values&.flat_map(&:keys)&.uniq || []
142
- delete_in_db do
143
- deletion_query = query.where(id: ids)
144
- # Apply any additional query identifiers (i.e. :ctid column)
145
- detected_additional_identifier_columns.each do |column|
146
- deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
154
+ # Delete by partioned CTIDs
155
+ if opts_c.use_pg_system_column_ctid && opts_c.delete_ctids_by_partions && (detected_additional_identifier_columns & ['ctid', 'tableoid::regclass']).size == 2
156
+ puts "Deleting via CTID and table partitions" if opts_c.verbose
157
+ partioned_tables.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
+ delete_in_db do
161
+ sql = <<~SQL
162
+ DELETE FROM #{table_partition}
163
+ WHERE ctid IN (#{ctids.map { |c| "'#{c}'" }.join(', ')});
164
+ SQL
165
+ # 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.
166
+ if ENV['TEST_GEM_ENV'] == 'bulk_dependency_eraser'
167
+ sql.sub!(/;\s*$/, "") # remove terminating semicolon
168
+ sql << " AND tableoid = '#{table_partition}';"
169
+ end
170
+ deleted_count = klass.connection.delete(sql)
171
+ [deleted_count, sql, identifiers_by_partition.keys, identifiers_by_partition]
172
+ end
147
173
  end
148
-
149
- # Perform Deletion
150
- deletion_result = deletion_query.delete_all
151
- # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
152
- # and have access to these objects in their proc.
153
- # - query can give them access to the klass and table_name
154
- [deletion_result, query, ids, additional_identifiers]
155
- end
156
- else
157
- puts "Deleting with batching" if opts_c.verbose
158
- ids.each_slice(batch_size) do |ids_subset|
159
- additional_identifiers_subset = additional_identifiers&.slice(*ids_subset)
160
- # Get column-names/keys of any additional identifer columns
161
- detected_additional_identifier_columns = additional_identifiers_subset&.values&.flat_map(&:keys)&.uniq || []
174
+ else
162
175
  delete_in_db do
163
- deletion_query = query.where(id: ids_subset)
176
+ deletion_query = query
177
+ if opts_c.use_ctid_over_primary_key && detected_additional_identifier_columns.include?('ctid')
178
+ # Do nothing, query will be deleted via ctid
179
+ else
180
+ deletion_query = deletion_query.where(id: ids)
181
+ end
182
+
164
183
  # Apply any additional query identifiers (i.e. :ctid column)
165
184
  detected_additional_identifier_columns.each do |column|
166
- deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
185
+ deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
167
186
  end
168
187
 
169
188
  # Perform Deletion
170
189
  deletion_result = deletion_query.delete_all
171
-
172
190
  # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
173
191
  # and have access to these objects in their proc.
174
- # - query can give them access to the klass and table_name
175
- [deletion_result, query, ids_subset, additional_identifiers_subset]
192
+ # - query can give them access to the klass and table_name
193
+ [deletion_result, query, ids, additional_identifiers]
194
+ end
195
+ end
196
+ else
197
+ puts "Deleting with batching" if opts_c.verbose
198
+ if opts_c.use_pg_system_column_ctid && opts_c.delete_ctids_by_partions && (detected_additional_identifier_columns & ['ctid', 'tableoid::regclass']).size == 2
199
+ puts "Deleting via CTID and table partitions" if opts_c.verbose
200
+ partioned_tables.each do |table_partition|
201
+ identifiers_by_partition = additional_identifiers.filter { |id, identifiers| identifiers['tableoid::regclass'] == table_partition }
202
+ ids = identifiers_by_partition.keys
203
+ ids.each_slice(batch_size) do |ids_subset|
204
+ additional_identifiers_subset = identifiers_by_partition.slice(*ids_subset)
205
+ ctids = additional_identifiers_subset.values.pluck('ctid')
206
+ delete_in_db do
207
+ sql = <<~SQL
208
+ DELETE FROM #{table_partition}
209
+ WHERE ctid IN (#{ctids.map { |c| "'#{c}'" }.join(', ')});
210
+ SQL
211
+
212
+ # 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.
213
+ if ENV['TEST_GEM_ENV'] == 'bulk_dependency_eraser'
214
+ sql.sub!(/;\s*$/, "") # remove terminating semicolon
215
+ sql << " AND tableoid = '#{table_partition}';"
216
+ end
217
+
218
+ deleted_count = klass.connection.delete(sql)
219
+ [deleted_count, sql, ids_subset, additional_identifiers_subset]
220
+ end
221
+ end
222
+ end
223
+ else
224
+ ids.each_slice(batch_size) do |ids_subset|
225
+ additional_identifiers_subset = additional_identifiers&.slice(*ids_subset)
226
+ # Get column-names/keys of any additional identifer columns
227
+ detected_additional_identifier_columns = additional_identifiers_subset&.values&.flat_map(&:keys)&.uniq || []
228
+ delete_in_db do
229
+ deletion_query = query
230
+ if opts_c.use_ctid_over_primary_key && detected_additional_identifier_columns.include?('ctid')
231
+ # Do nothing, query will be deleted via ctid
232
+ else
233
+ deletion_query = deletion_query.where(id: ids_subset)
234
+ end
235
+
236
+ # Apply any additional query identifiers (i.e. :ctid column)
237
+ detected_additional_identifier_columns.each do |column|
238
+ deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
239
+ end
240
+
241
+ # Perform Deletion
242
+ deletion_result = deletion_query.delete_all
243
+
244
+ # Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
245
+ # and have access to these objects in their proc.
246
+ # - query can give them access to the klass and table_name
247
+ [deletion_result, query, ids_subset, additional_identifiers_subset]
248
+ end
176
249
  end
177
250
  end
178
251
  end
@@ -1,3 +1,3 @@
1
1
  module BulkDependencyEraser
2
- VERSION = "4.2.3".freeze
2
+ VERSION = "4.4.0".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.2.3
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin.dana.software.dev@gmail.com