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 +4 -4
- data/lib/bulk_dependency_eraser/builder.rb +14 -2
- data/lib/bulk_dependency_eraser/deleter.rb +90 -32
- data/lib/bulk_dependency_eraser/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 886ff4e4e736393ca417bfca554cc0f0cac8dd3d2c78233e8cfdb9c007d4b0f1
|
4
|
+
data.tar.gz: d81c392e904f999442287d77349b52659dd14621da208ce5f942ff8f4529e5a3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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] &&
|
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
|
-
#
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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:
|
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 =>
|
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
|
-
|
188
|
-
[deletion_result, query,
|
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
|
|