bulk_dependency_eraser 4.0.0 → 4.1.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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16b8469147c79e15bbd947d75f9b9e7aa243f219be6796f516967a4f134609bd
|
4
|
+
data.tar.gz: a7a45c24562a67a1762d120caa9b4a14ab211594245383be4b21a70fa9f5a5e0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8ea34f3b1963b7c8d7720292a47edffb1351f18b6ecb6822e2965fe96244700044d9dcba8124cc088033d3114da0775b0a3be0c4636424af11c101e5e90fb686
|
7
|
+
data.tar.gz: fe4901ea4c7f99ef0e5228b94280f76b6102ef9686c2a8a6fa5fcb4da768fc0303e1da2929a1672bfea77ebcb0e42c9692c7774df21bf891d4e8054979d2c3ea
|
@@ -52,10 +52,13 @@ module BulkDependencyEraser
|
|
52
52
|
# Applied to reading queries
|
53
53
|
# - 1st priority of scopes
|
54
54
|
reading_proc_scopes_per_class_name: {},
|
55
|
+
# Using PG system column CTID can result in a 10x deletion speed increase
|
56
|
+
# - is used in combination with the primary key column to ensure CTID hasn't changed.
|
57
|
+
use_pg_system_column_ctid: false,
|
55
58
|
}.freeze
|
56
59
|
|
57
60
|
# write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.
|
58
|
-
attr_accessor :deletion_list, :nullification_list
|
61
|
+
attr_accessor :deletion_list, :nullification_list, :deletion_list_with_additional_identifiers
|
59
62
|
attr_reader :ignore_table_deletion_list, :ignore_table_nullification_list
|
60
63
|
attr_reader :query_schema_parser
|
61
64
|
attr_reader :current_klass_name
|
@@ -65,6 +68,15 @@ module BulkDependencyEraser
|
|
65
68
|
def initialize query:, opts: {}
|
66
69
|
@query = query
|
67
70
|
@deletion_list = {}
|
71
|
+
# CTID column values are stored here
|
72
|
+
# - key: <klass_name (with or without suffixes)>
|
73
|
+
# - value: Hash
|
74
|
+
# - key: Individual record's ID
|
75
|
+
# - value: Hash
|
76
|
+
# - key: Column Name
|
77
|
+
# - value: plucked column value
|
78
|
+
# - these additional identifiers will be used when deleting the klasses
|
79
|
+
@deletion_list_with_additional_identifiers = {}
|
68
80
|
@nullification_list = {}
|
69
81
|
|
70
82
|
# For any ignored table results, they will be stored here
|
@@ -95,9 +107,13 @@ module BulkDependencyEraser
|
|
95
107
|
# - prior approach was to use table_name.classify, but we can't trust that approach.
|
96
108
|
opts_c.ignore_tables.each do |table_name|
|
97
109
|
table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
|
98
|
-
|
99
|
-
|
100
|
-
|
110
|
+
klass = klass_name.constantize
|
111
|
+
# Delete klasses from the deletion/nullification lists if they are from ignored tables.
|
112
|
+
# - Handles klass name indexes, with and without suffices.
|
113
|
+
deletion_index_keys(klass).each do |klass_index|
|
114
|
+
ignore_table_deletion_list[klass_index] = deletion_list.delete(klass_index) if deletion_list.key?(klass_index)
|
115
|
+
ignore_table_nullification_list[klass_index] = nullification_list.delete(klass_index) if nullification_list.key?(klass_index)
|
116
|
+
end
|
101
117
|
end
|
102
118
|
end
|
103
119
|
|
@@ -137,27 +153,44 @@ module BulkDependencyEraser
|
|
137
153
|
end
|
138
154
|
end
|
139
155
|
|
140
|
-
|
156
|
+
# @param [ActiveRecord::Relation] query
|
157
|
+
# @param [Symbol | Array<Symbol>] column - primary_key must be the first or only element in the column.
|
158
|
+
# @return [
|
159
|
+
# [Array<String | Int>] array of column values,
|
160
|
+
# [Hash] hash of column/value pairings - additional identifiers.
|
161
|
+
# ]
|
162
|
+
def pluck_from_query(query, column = :id, skip_ctid_check: false)
|
163
|
+
if column.is_a?(Array)
|
164
|
+
columns = column
|
165
|
+
else
|
166
|
+
columns = [column]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Only pluck CTID if we're plucking column :id (the only primary key supported)
|
170
|
+
if columns == [:id] && opts_c.use_pg_system_column_ctid && skip_ctid_check == false
|
171
|
+
columns << :ctid
|
172
|
+
end
|
173
|
+
|
141
174
|
set_current_klass_name(query)
|
142
175
|
# ordering shouldn't matter in these queries, and would slow it down
|
143
176
|
# - we're ignoring default_scope ordering, but assoc-defined ordering would still take effect
|
144
177
|
query = query.reorder('')
|
145
178
|
query = custom_scope_for_query(query)
|
146
179
|
|
147
|
-
|
180
|
+
query_results = []
|
148
181
|
read_from_db do
|
149
182
|
# If the query has a limit, then we don't want to clobber with batching.
|
150
183
|
if batching_disabled? || !query.where({}).limit_value.nil?
|
151
184
|
# query without batching
|
152
|
-
|
185
|
+
query_results = query.pluck(*columns)
|
153
186
|
elsif opts_c.disable_batch_ordering || opts_c.disable_batch_ordering_for_klasses.include?(current_klass_name)
|
154
187
|
# query with orderless batching
|
155
188
|
offset = 0
|
156
189
|
loop do
|
157
|
-
|
158
|
-
|
190
|
+
new_query_results = query.offset(offset).limit(batch_size).pluck(*columns)
|
191
|
+
query_results += new_query_results
|
159
192
|
|
160
|
-
break if
|
193
|
+
break if new_query_results.size < batch_size
|
161
194
|
|
162
195
|
# Move to the next batch
|
163
196
|
offset += batch_size
|
@@ -165,12 +198,35 @@ module BulkDependencyEraser
|
|
165
198
|
else
|
166
199
|
# query with ordered batching
|
167
200
|
query.in_batches(of: batch_size) do |subset_query|
|
168
|
-
|
201
|
+
query_results += subset_query.pluck(*columns)
|
169
202
|
end
|
170
203
|
end
|
171
204
|
end
|
172
205
|
|
173
|
-
|
206
|
+
# Early exit if we only plucked one column.
|
207
|
+
# - query_results would just be an array of IDs
|
208
|
+
return [query_results, nil] if columns.count == 1
|
209
|
+
|
210
|
+
transposed_results = query_results.transpose
|
211
|
+
|
212
|
+
query_primary_keys = transposed_results.shift || [] # could be an empty id set
|
213
|
+
columns.shift # shift of the primary key column
|
214
|
+
|
215
|
+
query_additional_identifiers = {}
|
216
|
+
results_per_additional_column = {}
|
217
|
+
columns.each do |column|
|
218
|
+
column_results = transposed_results.shift
|
219
|
+
results_per_additional_column[column] = column_results
|
220
|
+
end
|
221
|
+
|
222
|
+
query_primary_keys.each_with_index do |primary_key, plucked_index|
|
223
|
+
query_additional_identifiers[primary_key] ||= {}
|
224
|
+
results_per_additional_column.each do |column, column_results|
|
225
|
+
query_additional_identifiers[primary_key][column] = column_results[plucked_index]
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
return query_primary_keys, query_additional_identifiers
|
174
230
|
end
|
175
231
|
|
176
232
|
def batch_size
|
@@ -181,6 +237,32 @@ module BulkDependencyEraser
|
|
181
237
|
opts_c.disable_read_batching.nil? ? opts_c.disable_batching : opts_c.disable_read_batching
|
182
238
|
end
|
183
239
|
|
240
|
+
# No need for recursion here. Nullifying does not effect nested dependencies
|
241
|
+
def nullification_query_parser(query, nullfication_klass, klass_foreign_key, klass_foreign_type = nil)
|
242
|
+
# We're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
243
|
+
# Setting skip_ctid_check: true because we currently don't support CTID with nullifications yet
|
244
|
+
assoc_ids, additional_identifiers_by_id = pluck_from_query(query, skip_ctid_check: true)
|
245
|
+
klass_name = nullfication_klass.name
|
246
|
+
|
247
|
+
# No assoc_ids, no need to add it to the nullification list
|
248
|
+
return if assoc_ids.none?
|
249
|
+
|
250
|
+
klass_foreign_key = klass_foreign_key.to_s
|
251
|
+
nullification_list[klass_name] ||= {}
|
252
|
+
nullification_list[klass_name][klass_foreign_key] ||= []
|
253
|
+
nullification_list[klass_name][klass_foreign_key] += assoc_ids
|
254
|
+
nullification_list[klass_name][klass_foreign_key].uniq!
|
255
|
+
|
256
|
+
|
257
|
+
# Also nullify the 'type' field, if the association is polymorphic
|
258
|
+
if klass_foreign_type
|
259
|
+
klass_foreign_type = klass_foreign_type.to_s
|
260
|
+
nullification_list[klass_name][klass_foreign_type] ||= []
|
261
|
+
nullification_list[klass_name][klass_foreign_type] += assoc_ids
|
262
|
+
nullification_list[klass_name][klass_foreign_type].uniq!
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
184
266
|
def deletion_query_parser query, association_parent = nil
|
185
267
|
# necessary for "ActiveRecord::Reflection::ThroughReflection" use-case
|
186
268
|
# force_through_destroy_chains = options[:force_destroy_chain] || {}
|
@@ -225,24 +307,28 @@ module BulkDependencyEraser
|
|
225
307
|
end
|
226
308
|
|
227
309
|
# Pluck IDs of the current query
|
228
|
-
query_ids = pluck_from_query(query)
|
310
|
+
query_ids, additional_identifiers_by_id = pluck_from_query(query)
|
229
311
|
|
230
312
|
klass_index = initialize_deletion_list_for_klass(klass)
|
231
313
|
|
232
314
|
# prevent infinite recursion here.
|
233
315
|
# - Remove any IDs that have been processed before
|
234
|
-
query_ids = remove_already_deletion_processed_ids(klass, query_ids)
|
316
|
+
query_ids, additional_identifiers_by_id = remove_already_deletion_processed_ids(klass, query_ids, additional_identifiers_by_id:)
|
235
317
|
|
236
318
|
# If ids are nil, let's find that error
|
237
319
|
if query_ids.none? #|| query_ids.nil?
|
238
320
|
# quick cleanup, if turns out was an empty class
|
239
|
-
|
321
|
+
if deletion_list[klass_index].none?
|
322
|
+
deletion_list.delete(klass_index)
|
323
|
+
deletion_list_with_additional_identifiers.delete(klass_index)
|
324
|
+
end
|
240
325
|
return
|
241
326
|
end
|
242
327
|
|
243
328
|
# Use-case: We have more IDs to process
|
244
329
|
# - can now safely add to the list, since we've prevented infinite recursion
|
245
330
|
deletion_list[klass_index] += query_ids
|
331
|
+
deletion_list_with_additional_identifiers[klass_index].merge!(additional_identifiers_by_id)
|
246
332
|
|
247
333
|
# ignore associations that aren't a dependent destroyable type
|
248
334
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
@@ -378,25 +464,25 @@ module BulkDependencyEraser
|
|
378
464
|
end
|
379
465
|
|
380
466
|
# Look for manually specified keys in the assocation first
|
381
|
-
specified_primary_key = reflection.options[:primary_key]&.
|
382
|
-
specified_foreign_key = reflection.options[:foreign_key]&.
|
467
|
+
specified_primary_key = reflection.options[:primary_key]&.to_sym
|
468
|
+
specified_foreign_key = reflection.options[:foreign_key]&.to_sym
|
383
469
|
# For polymorphic_associations
|
384
470
|
specified_foreign_type = nil
|
385
471
|
|
386
472
|
# handle foreign_key edge cases
|
387
473
|
if specified_foreign_key.nil?
|
388
474
|
if reflection.options[:as]
|
389
|
-
specified_foreign_type = "#{reflection.options[:as]}_type"
|
390
|
-
specified_foreign_key = "#{reflection.options[:as]}_id"
|
475
|
+
specified_foreign_type = "#{reflection.options[:as]}_type".to_sym
|
476
|
+
specified_foreign_key = "#{reflection.options[:as]}_id".to_sym
|
391
477
|
# Only filtering by type here, the extra work for a poly assoc. We filter by IDs later
|
392
|
-
assoc_query = assoc_query.where({ specified_foreign_type
|
478
|
+
assoc_query = assoc_query.where({ specified_foreign_type => parent_class.name })
|
393
479
|
else
|
394
|
-
specified_foreign_key = parent_class.table_name.singularize
|
480
|
+
specified_foreign_key = "#{parent_class.table_name.singularize}_id".to_sym
|
395
481
|
end
|
396
482
|
end
|
397
483
|
|
398
484
|
# Check to see if foreign_key exists in association class's table
|
399
|
-
unless assoc_klass.column_names.include?(specified_foreign_key)
|
485
|
+
unless assoc_klass.column_names.include?(specified_foreign_key.to_s)
|
400
486
|
report_error(
|
401
487
|
"
|
402
488
|
For #{parent_class.name}'s assoc '#{assoc_klass.name}': Could not determine the assoc's foreign key.
|
@@ -409,11 +495,11 @@ module BulkDependencyEraser
|
|
409
495
|
# Build association query, based on parent class's primary key and the assoc's foreign key
|
410
496
|
# - handle primary key edge cases
|
411
497
|
# - The associations might not be using the primary_key of the klass table, but we can support that here.
|
412
|
-
if specified_primary_key && specified_primary_key
|
413
|
-
alt_primary_ids = pluck_from_query(query, specified_primary_key)
|
414
|
-
assoc_query = assoc_query.where(specified_foreign_key
|
498
|
+
if specified_primary_key && specified_primary_key != :id
|
499
|
+
alt_primary_ids, _ = pluck_from_query(query, specified_primary_key)
|
500
|
+
assoc_query = assoc_query.where(specified_foreign_key => alt_primary_ids)
|
415
501
|
else
|
416
|
-
assoc_query = assoc_query.where(specified_foreign_key
|
502
|
+
assoc_query = assoc_query.where(specified_foreign_key => query_ids)
|
417
503
|
end
|
418
504
|
|
419
505
|
# remove any ordering or limits imposed on the association queries from the association definitions
|
@@ -427,25 +513,7 @@ module BulkDependencyEraser
|
|
427
513
|
deletion_query_parser(assoc_query, parent_class)
|
428
514
|
end
|
429
515
|
elsif type == :nullify
|
430
|
-
|
431
|
-
# - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
432
|
-
assoc_ids = pluck_from_query(assoc_query)
|
433
|
-
|
434
|
-
# No assoc_ids, no need to add it to the nullification list
|
435
|
-
return if assoc_ids.none?
|
436
|
-
|
437
|
-
nullification_list[assoc_klass_name] ||= {}
|
438
|
-
nullification_list[assoc_klass_name][specified_foreign_key] ||= []
|
439
|
-
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
440
|
-
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
441
|
-
|
442
|
-
|
443
|
-
# Also nullify the 'type' field, if the association is polymorphic
|
444
|
-
if specified_foreign_type
|
445
|
-
nullification_list[assoc_klass_name][specified_foreign_type] ||= []
|
446
|
-
nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
|
447
|
-
nullification_list[assoc_klass_name][specified_foreign_type].uniq!
|
448
|
-
end
|
516
|
+
nullification_query_parser(assoc_query, assoc_klass, specified_foreign_key, specified_foreign_type)
|
449
517
|
else
|
450
518
|
raise "invalid parsing type: #{type}"
|
451
519
|
end
|
@@ -531,7 +599,7 @@ module BulkDependencyEraser
|
|
531
599
|
return
|
532
600
|
end
|
533
601
|
|
534
|
-
foreign_keys = pluck_from_query(query, specified_foreign_key)
|
602
|
+
foreign_keys, _ = pluck_from_query(query, specified_foreign_key, skip_ctid_check: true)
|
535
603
|
assoc_query = assoc_query.where(
|
536
604
|
specified_primary_key.to_sym => foreign_keys
|
537
605
|
)
|
@@ -690,8 +758,14 @@ module BulkDependencyEraser
|
|
690
758
|
opts_c.db_read_wrapper.call(block)
|
691
759
|
end
|
692
760
|
|
693
|
-
|
761
|
+
# @return [
|
762
|
+
# [Array] array of IDs that haven't already been added to the deletion list
|
763
|
+
# [Hash | Nil] the additional deletion query identifiers, minus the ones already processed
|
764
|
+
# - will be nil if we're not plucking additional columns.
|
765
|
+
# ]
|
766
|
+
def remove_already_deletion_processed_ids(klass, new_ids, additional_identifiers_by_id: nil)
|
694
767
|
already_processed_ids = []
|
768
|
+
additional_identifiers_by_id ||= {}
|
695
769
|
|
696
770
|
if is_a_circular_dependency_klass?(klass)
|
697
771
|
klass_keys = find_circular_dependency_deletion_keys(klass)
|
@@ -702,7 +776,10 @@ module BulkDependencyEraser
|
|
702
776
|
already_processed_ids = deletion_list[klass.name]
|
703
777
|
end
|
704
778
|
|
705
|
-
|
779
|
+
return [
|
780
|
+
(new_ids - already_processed_ids),
|
781
|
+
additional_identifiers_by_id.except(*already_processed_ids)
|
782
|
+
]
|
706
783
|
end
|
707
784
|
|
708
785
|
# Initializes deletion_list index
|
@@ -714,9 +791,11 @@ module BulkDependencyEraser
|
|
714
791
|
raise "circular_index already existed for klass: #{klass.name}" if deletion_list.key?(klass_index)
|
715
792
|
|
716
793
|
deletion_list[klass_index] = []
|
794
|
+
deletion_list_with_additional_identifiers[klass_index] = {}
|
717
795
|
else
|
718
796
|
# Not a circular dependency, define as normal
|
719
797
|
deletion_list[klass_index] ||= []
|
798
|
+
deletion_list_with_additional_identifiers[klass_index] ||= {}
|
720
799
|
end
|
721
800
|
|
722
801
|
klass_index
|
@@ -734,6 +813,14 @@ module BulkDependencyEraser
|
|
734
813
|
deletion_list.keys.select { |key| key.match?(regex) }
|
735
814
|
end
|
736
815
|
|
816
|
+
# returns any current index keys, with or without suffix, in the deletion_list for the given klass param
|
817
|
+
def deletion_index_keys(klass)
|
818
|
+
klass_keys = []
|
819
|
+
klass_keys << klass.name if deletion_list.keys.include?(klass.name)
|
820
|
+
klass_keys += find_circular_dependency_deletion_keys(klass)
|
821
|
+
return klass_keys
|
822
|
+
end
|
823
|
+
|
737
824
|
# If circular dependency, append a index suffix to the deletion hash key
|
738
825
|
# - they will be deleted in highest index to lowest index order.
|
739
826
|
def deletion_index_key(klass, increment_circular_index: false)
|
@@ -53,8 +53,9 @@ module BulkDependencyEraser
|
|
53
53
|
deletion_proc_scopes_per_class_name: {},
|
54
54
|
}.freeze
|
55
55
|
|
56
|
-
def initialize class_names_and_ids: {}, opts: {}
|
56
|
+
def initialize class_names_and_ids: {}, additional_identifiers_by_id: {}, opts: {}
|
57
57
|
@class_names_and_ids = class_names_and_ids
|
58
|
+
@additional_identifiers_by_id = additional_identifiers_by_id
|
58
59
|
super(opts:)
|
59
60
|
end
|
60
61
|
|
@@ -68,19 +69,23 @@ module BulkDependencyEraser
|
|
68
69
|
begin
|
69
70
|
class_names_and_ids.keys.reverse.each do |class_name|
|
70
71
|
current_class_name = class_name
|
72
|
+
additional_identifiers = additional_identifiers_by_id[class_name]
|
73
|
+
raise "invalid state! #{class_name} not found in 'additional_identifiers_by_id' list" if additional_identifiers.nil?
|
74
|
+
|
75
|
+
# Last in, First out
|
71
76
|
ids = class_names_and_ids[class_name].reverse
|
72
77
|
klass = constantize(class_name)
|
73
78
|
|
74
79
|
if opts_c.enable_invalid_foreign_key_detection
|
75
80
|
# delete with referential integrity
|
76
|
-
delete_by_klass_and_ids(klass, ids)
|
81
|
+
delete_by_klass_and_ids(klass, ids, additional_identifiers:)
|
77
82
|
else
|
78
83
|
# delete without referential integrity
|
79
84
|
# Disable any ActiveRecord::InvalidForeignKey raised errors.
|
80
85
|
# - src: https://stackoverflow.com/questions/41005849/rails-migrations-temporarily-ignore-foreign-key-constraint
|
81
86
|
# https://apidock.com/rails/ActiveRecord/ConnectionAdapters/AbstractAdapter/disable_referential_integrity
|
82
87
|
ActiveRecord::Base.connection.disable_referential_integrity do
|
83
|
-
delete_by_klass_and_ids(klass, ids)
|
88
|
+
delete_by_klass_and_ids(klass, ids, additional_identifiers:)
|
84
89
|
end
|
85
90
|
end
|
86
91
|
end
|
@@ -94,7 +99,7 @@ module BulkDependencyEraser
|
|
94
99
|
|
95
100
|
protected
|
96
101
|
|
97
|
-
attr_reader :class_names_and_ids
|
102
|
+
attr_reader :class_names_and_ids, :additional_identifiers_by_id
|
98
103
|
|
99
104
|
def custom_scope_for_query(query)
|
100
105
|
klass = query.klass
|
@@ -113,29 +118,49 @@ module BulkDependencyEraser
|
|
113
118
|
opts_c.disable_delete_batching.nil? ? opts_c.disable_batching : opts_c.disable_delete_batching
|
114
119
|
end
|
115
120
|
|
116
|
-
def delete_by_klass_and_ids
|
121
|
+
def delete_by_klass_and_ids(klass, ids, additional_identifiers:)
|
117
122
|
puts "Deleting #{klass.name}'s IDs: #{ids}" if opts_c.verbose
|
118
123
|
query = klass.unscoped
|
119
124
|
query = custom_scope_for_query(query)
|
120
125
|
|
121
126
|
if batching_disabled?
|
122
127
|
puts "Deleting without batching" if opts_c.verbose
|
128
|
+
# Get column-names/keys of any additional identifer columns
|
129
|
+
detected_additional_identifier_columns = additional_identifiers.values.flat_map(&:keys).uniq
|
123
130
|
delete_in_db do
|
124
|
-
|
131
|
+
deletion_query = query.where(id: ids)
|
132
|
+
# Apply any additional query identifiers (i.e. :ctid column)
|
133
|
+
detected_additional_identifier_columns.each do |column|
|
134
|
+
deletion_query = deletion_query.where(column => additional_identifiers.values.pluck(column))
|
135
|
+
end
|
136
|
+
|
137
|
+
# Perform Deletion
|
138
|
+
deletion_result = deletion_query.delete_all
|
125
139
|
# Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
|
126
140
|
# and have access to these objects in their proc.
|
127
141
|
# - query can give them access to the klass and table_name
|
128
|
-
[deletion_result, query, ids]
|
142
|
+
[deletion_result, query, ids, additional_identifiers]
|
129
143
|
end
|
130
144
|
else
|
131
145
|
puts "Deleting with batching" if opts_c.verbose
|
132
146
|
ids.each_slice(batch_size) do |ids_subset|
|
147
|
+
additional_identifiers_subset = additional_identifiers.slice(*ids_subset)
|
148
|
+
# Get column-names/keys of any additional identifer columns
|
149
|
+
detected_additional_identifier_columns = additional_identifiers_subset.values.flat_map(&:keys).uniq
|
133
150
|
delete_in_db do
|
134
|
-
|
151
|
+
deletion_query = query.where(id: ids_subset)
|
152
|
+
# Apply any additional query identifiers (i.e. :ctid column)
|
153
|
+
detected_additional_identifier_columns.each do |column|
|
154
|
+
deletion_query = deletion_query.where(column => additional_identifiers_subset.values.pluck(column))
|
155
|
+
end
|
156
|
+
|
157
|
+
# Perform Deletion
|
158
|
+
deletion_result = deletion_query.delete_all
|
159
|
+
|
135
160
|
# Returning the following data in the event that the gem-implementer wants to insert their own db_delete_wrapper proc
|
136
161
|
# and have access to these objects in their proc.
|
137
162
|
# - query can give them access to the klass and table_name
|
138
|
-
[deletion_result, query, ids_subset]
|
163
|
+
[deletion_result, query, ids_subset, additional_identifiers_subset]
|
139
164
|
end
|
140
165
|
end
|
141
166
|
end
|
@@ -7,7 +7,7 @@ module BulkDependencyEraser
|
|
7
7
|
verbose: false,
|
8
8
|
}.freeze
|
9
9
|
|
10
|
-
delegate :nullification_list, :deletion_list, to: :dependency_builder
|
10
|
+
delegate :nullification_list, :deletion_list, :deletion_list_with_additional_identifiers, to: :dependency_builder
|
11
11
|
delegate :ignore_table_deletion_list, :ignore_table_nullification_list, to: :dependency_builder
|
12
12
|
delegate :circular_dependency_klasses, :flat_dependencies_per_klass, to: :dependency_builder
|
13
13
|
|
@@ -49,7 +49,11 @@ module BulkDependencyEraser
|
|
49
49
|
end
|
50
50
|
|
51
51
|
def delete!
|
52
|
-
@deleter = BulkDependencyEraser::Deleter.new(
|
52
|
+
@deleter = BulkDependencyEraser::Deleter.new(
|
53
|
+
class_names_and_ids: deletion_list,
|
54
|
+
additional_identifiers_by_id: deletion_list_with_additional_identifiers,
|
55
|
+
opts:
|
56
|
+
)
|
53
57
|
deleter_execution = deleter.execute
|
54
58
|
unless deleter_execution
|
55
59
|
puts "Deleter execution FAILED" if opts_c.verbose
|