bulk_dependency_eraser 3.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
|
@@ -28,6 +28,12 @@ module BulkDependencyEraser
|
|
28
28
|
ignore_tables_and_dependencies: [],
|
29
29
|
ignore_klass_names_and_dependencies: [],
|
30
30
|
disable_batching: false,
|
31
|
+
# Disable model ordering during build batching
|
32
|
+
# - Some tables can be too big to order, and just need to trust the DB order
|
33
|
+
# - Not 100% guaranteed and could leave orphaned records behind.
|
34
|
+
disable_batch_ordering: false,
|
35
|
+
# Same as 'disable_batch_ordering', but only for select classes
|
36
|
+
disable_batch_ordering_for_klasses: [],
|
31
37
|
# a general batching size
|
32
38
|
batch_size: 10_000,
|
33
39
|
# A specific batching size for this class, overrides the batch_size
|
@@ -46,10 +52,13 @@ module BulkDependencyEraser
|
|
46
52
|
# Applied to reading queries
|
47
53
|
# - 1st priority of scopes
|
48
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,
|
49
58
|
}.freeze
|
50
59
|
|
51
60
|
# write access so that these can be edited in-place by end-users who might need to manually adjust deletion order.
|
52
|
-
attr_accessor :deletion_list, :nullification_list
|
61
|
+
attr_accessor :deletion_list, :nullification_list, :deletion_list_with_additional_identifiers
|
53
62
|
attr_reader :ignore_table_deletion_list, :ignore_table_nullification_list
|
54
63
|
attr_reader :query_schema_parser
|
55
64
|
attr_reader :current_klass_name
|
@@ -59,6 +68,15 @@ module BulkDependencyEraser
|
|
59
68
|
def initialize query:, opts: {}
|
60
69
|
@query = query
|
61
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 = {}
|
62
80
|
@nullification_list = {}
|
63
81
|
|
64
82
|
# For any ignored table results, they will be stored here
|
@@ -89,9 +107,13 @@ module BulkDependencyEraser
|
|
89
107
|
# - prior approach was to use table_name.classify, but we can't trust that approach.
|
90
108
|
opts_c.ignore_tables.each do |table_name|
|
91
109
|
table_names_to_parsed_klass_names.dig(table_name)&.each do |klass_name|
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
95
117
|
end
|
96
118
|
end
|
97
119
|
|
@@ -131,35 +153,80 @@ module BulkDependencyEraser
|
|
131
153
|
end
|
132
154
|
end
|
133
155
|
|
134
|
-
|
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
|
+
|
135
174
|
set_current_klass_name(query)
|
136
175
|
# ordering shouldn't matter in these queries, and would slow it down
|
137
176
|
# - we're ignoring default_scope ordering, but assoc-defined ordering would still take effect
|
138
177
|
query = query.reorder('')
|
139
178
|
query = custom_scope_for_query(query)
|
140
179
|
|
141
|
-
|
180
|
+
query_results = []
|
142
181
|
read_from_db do
|
143
182
|
# If the query has a limit, then we don't want to clobber with batching.
|
144
183
|
if batching_disabled? || !query.where({}).limit_value.nil?
|
145
184
|
# query without batching
|
146
|
-
|
147
|
-
|
148
|
-
# query with batching
|
185
|
+
query_results = query.pluck(*columns)
|
186
|
+
elsif opts_c.disable_batch_ordering || opts_c.disable_batch_ordering_for_klasses.include?(current_klass_name)
|
187
|
+
# query with orderless batching
|
149
188
|
offset = 0
|
150
189
|
loop do
|
151
|
-
|
152
|
-
|
190
|
+
new_query_results = query.offset(offset).limit(batch_size).pluck(*columns)
|
191
|
+
query_results += new_query_results
|
153
192
|
|
154
|
-
break if
|
193
|
+
break if new_query_results.size < batch_size
|
155
194
|
|
156
195
|
# Move to the next batch
|
157
196
|
offset += batch_size
|
158
197
|
end
|
198
|
+
else
|
199
|
+
# query with ordered batching
|
200
|
+
query.in_batches(of: batch_size) do |subset_query|
|
201
|
+
query_results += subset_query.pluck(*columns)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
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]
|
159
226
|
end
|
160
227
|
end
|
161
228
|
|
162
|
-
return
|
229
|
+
return query_primary_keys, query_additional_identifiers
|
163
230
|
end
|
164
231
|
|
165
232
|
def batch_size
|
@@ -170,6 +237,32 @@ module BulkDependencyEraser
|
|
170
237
|
opts_c.disable_read_batching.nil? ? opts_c.disable_batching : opts_c.disable_read_batching
|
171
238
|
end
|
172
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
|
+
|
173
266
|
def deletion_query_parser query, association_parent = nil
|
174
267
|
# necessary for "ActiveRecord::Reflection::ThroughReflection" use-case
|
175
268
|
# force_through_destroy_chains = options[:force_destroy_chain] || {}
|
@@ -214,24 +307,28 @@ module BulkDependencyEraser
|
|
214
307
|
end
|
215
308
|
|
216
309
|
# Pluck IDs of the current query
|
217
|
-
query_ids = pluck_from_query(query)
|
310
|
+
query_ids, additional_identifiers_by_id = pluck_from_query(query)
|
218
311
|
|
219
312
|
klass_index = initialize_deletion_list_for_klass(klass)
|
220
313
|
|
221
314
|
# prevent infinite recursion here.
|
222
315
|
# - Remove any IDs that have been processed before
|
223
|
-
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:)
|
224
317
|
|
225
318
|
# If ids are nil, let's find that error
|
226
319
|
if query_ids.none? #|| query_ids.nil?
|
227
320
|
# quick cleanup, if turns out was an empty class
|
228
|
-
|
321
|
+
if deletion_list[klass_index].none?
|
322
|
+
deletion_list.delete(klass_index)
|
323
|
+
deletion_list_with_additional_identifiers.delete(klass_index)
|
324
|
+
end
|
229
325
|
return
|
230
326
|
end
|
231
327
|
|
232
328
|
# Use-case: We have more IDs to process
|
233
329
|
# - can now safely add to the list, since we've prevented infinite recursion
|
234
330
|
deletion_list[klass_index] += query_ids
|
331
|
+
deletion_list_with_additional_identifiers[klass_index].merge!(additional_identifiers_by_id)
|
235
332
|
|
236
333
|
# ignore associations that aren't a dependent destroyable type
|
237
334
|
destroy_associations = query.reflect_on_all_associations.select do |reflection|
|
@@ -367,25 +464,25 @@ module BulkDependencyEraser
|
|
367
464
|
end
|
368
465
|
|
369
466
|
# Look for manually specified keys in the assocation first
|
370
|
-
specified_primary_key = reflection.options[:primary_key]&.
|
371
|
-
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
|
372
469
|
# For polymorphic_associations
|
373
470
|
specified_foreign_type = nil
|
374
471
|
|
375
472
|
# handle foreign_key edge cases
|
376
473
|
if specified_foreign_key.nil?
|
377
474
|
if reflection.options[:as]
|
378
|
-
specified_foreign_type = "#{reflection.options[:as]}_type"
|
379
|
-
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
|
380
477
|
# Only filtering by type here, the extra work for a poly assoc. We filter by IDs later
|
381
|
-
assoc_query = assoc_query.where({ specified_foreign_type
|
478
|
+
assoc_query = assoc_query.where({ specified_foreign_type => parent_class.name })
|
382
479
|
else
|
383
|
-
specified_foreign_key = parent_class.table_name.singularize
|
480
|
+
specified_foreign_key = "#{parent_class.table_name.singularize}_id".to_sym
|
384
481
|
end
|
385
482
|
end
|
386
483
|
|
387
484
|
# Check to see if foreign_key exists in association class's table
|
388
|
-
unless assoc_klass.column_names.include?(specified_foreign_key)
|
485
|
+
unless assoc_klass.column_names.include?(specified_foreign_key.to_s)
|
389
486
|
report_error(
|
390
487
|
"
|
391
488
|
For #{parent_class.name}'s assoc '#{assoc_klass.name}': Could not determine the assoc's foreign key.
|
@@ -398,11 +495,11 @@ module BulkDependencyEraser
|
|
398
495
|
# Build association query, based on parent class's primary key and the assoc's foreign key
|
399
496
|
# - handle primary key edge cases
|
400
497
|
# - The associations might not be using the primary_key of the klass table, but we can support that here.
|
401
|
-
if specified_primary_key && specified_primary_key
|
402
|
-
alt_primary_ids = pluck_from_query(query, specified_primary_key)
|
403
|
-
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)
|
404
501
|
else
|
405
|
-
assoc_query = assoc_query.where(specified_foreign_key
|
502
|
+
assoc_query = assoc_query.where(specified_foreign_key => query_ids)
|
406
503
|
end
|
407
504
|
|
408
505
|
# remove any ordering or limits imposed on the association queries from the association definitions
|
@@ -416,25 +513,7 @@ module BulkDependencyEraser
|
|
416
513
|
deletion_query_parser(assoc_query, parent_class)
|
417
514
|
end
|
418
515
|
elsif type == :nullify
|
419
|
-
|
420
|
-
# - we're not destroying these assocs (just nullifying foreign_key columns) so we don't need to parse their dependencies.
|
421
|
-
assoc_ids = pluck_from_query(assoc_query)
|
422
|
-
|
423
|
-
# No assoc_ids, no need to add it to the nullification list
|
424
|
-
return if assoc_ids.none?
|
425
|
-
|
426
|
-
nullification_list[assoc_klass_name] ||= {}
|
427
|
-
nullification_list[assoc_klass_name][specified_foreign_key] ||= []
|
428
|
-
nullification_list[assoc_klass_name][specified_foreign_key] += assoc_ids
|
429
|
-
nullification_list[assoc_klass_name][specified_foreign_key].uniq!
|
430
|
-
|
431
|
-
|
432
|
-
# Also nullify the 'type' field, if the association is polymorphic
|
433
|
-
if specified_foreign_type
|
434
|
-
nullification_list[assoc_klass_name][specified_foreign_type] ||= []
|
435
|
-
nullification_list[assoc_klass_name][specified_foreign_type] += assoc_ids
|
436
|
-
nullification_list[assoc_klass_name][specified_foreign_type].uniq!
|
437
|
-
end
|
516
|
+
nullification_query_parser(assoc_query, assoc_klass, specified_foreign_key, specified_foreign_type)
|
438
517
|
else
|
439
518
|
raise "invalid parsing type: #{type}"
|
440
519
|
end
|
@@ -520,7 +599,7 @@ module BulkDependencyEraser
|
|
520
599
|
return
|
521
600
|
end
|
522
601
|
|
523
|
-
foreign_keys = pluck_from_query(query, specified_foreign_key)
|
602
|
+
foreign_keys, _ = pluck_from_query(query, specified_foreign_key, skip_ctid_check: true)
|
524
603
|
assoc_query = assoc_query.where(
|
525
604
|
specified_primary_key.to_sym => foreign_keys
|
526
605
|
)
|
@@ -679,8 +758,14 @@ module BulkDependencyEraser
|
|
679
758
|
opts_c.db_read_wrapper.call(block)
|
680
759
|
end
|
681
760
|
|
682
|
-
|
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)
|
683
767
|
already_processed_ids = []
|
768
|
+
additional_identifiers_by_id ||= {}
|
684
769
|
|
685
770
|
if is_a_circular_dependency_klass?(klass)
|
686
771
|
klass_keys = find_circular_dependency_deletion_keys(klass)
|
@@ -691,7 +776,10 @@ module BulkDependencyEraser
|
|
691
776
|
already_processed_ids = deletion_list[klass.name]
|
692
777
|
end
|
693
778
|
|
694
|
-
|
779
|
+
return [
|
780
|
+
(new_ids - already_processed_ids),
|
781
|
+
additional_identifiers_by_id.except(*already_processed_ids)
|
782
|
+
]
|
695
783
|
end
|
696
784
|
|
697
785
|
# Initializes deletion_list index
|
@@ -703,9 +791,11 @@ module BulkDependencyEraser
|
|
703
791
|
raise "circular_index already existed for klass: #{klass.name}" if deletion_list.key?(klass_index)
|
704
792
|
|
705
793
|
deletion_list[klass_index] = []
|
794
|
+
deletion_list_with_additional_identifiers[klass_index] = {}
|
706
795
|
else
|
707
796
|
# Not a circular dependency, define as normal
|
708
797
|
deletion_list[klass_index] ||= []
|
798
|
+
deletion_list_with_additional_identifiers[klass_index] ||= {}
|
709
799
|
end
|
710
800
|
|
711
801
|
klass_index
|
@@ -723,6 +813,14 @@ module BulkDependencyEraser
|
|
723
813
|
deletion_list.keys.select { |key| key.match?(regex) }
|
724
814
|
end
|
725
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
|
+
|
726
824
|
# If circular dependency, append a index suffix to the deletion hash key
|
727
825
|
# - they will be deleted in highest index to lowest index order.
|
728
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
|