online_migrations 0.26.0 → 0.27.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/docs/0.27-upgrade.md +24 -0
  4. data/docs/background_data_migrations.md +200 -101
  5. data/docs/background_schema_migrations.md +2 -2
  6. data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
  7. data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
  8. data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
  9. data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
  10. data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
  11. data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
  12. data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
  13. data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
  14. data/lib/online_migrations/background_data_migrations/config.rb +62 -0
  15. data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
  16. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
  17. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
  18. data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
  19. data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
  20. data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
  21. data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
  22. data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
  23. data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
  24. data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
  25. data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
  26. data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
  27. data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
  28. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
  29. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
  30. data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
  31. data/lib/online_migrations/change_column_type_helpers.rb +3 -2
  32. data/lib/online_migrations/config.rb +4 -4
  33. data/lib/online_migrations/data_migration.rb +127 -0
  34. data/lib/online_migrations/lock_retrier.rb +5 -2
  35. data/lib/online_migrations/schema_statements.rb +1 -1
  36. data/lib/online_migrations/shard_aware.rb +44 -0
  37. data/lib/online_migrations/version.rb +1 -1
  38. data/lib/online_migrations.rb +18 -11
  39. metadata +22 -21
  40. data/lib/online_migrations/background_migration.rb +0 -64
  41. data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
  42. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
  43. data/lib/online_migrations/background_migrations/config.rb +0 -74
  44. data/lib/online_migrations/background_migrations/migration.rb +0 -329
  45. data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
  46. data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
  47. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
  48. data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
  49. data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
  50. data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -0,0 +1,312 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ module BackgroundDataMigrations
5
+ # Class representing background data migration.
6
+ #
7
+ # @note The records of this class should not be created manually, but via
8
+ # `enqueue_background_data_migration` helper inside migrations.
9
+ #
10
+ class Migration < ApplicationRecord
11
+ include ShardAware
12
+
13
+ STATUSES = [
14
+ "enqueued", # The migration has been enqueued by the user.
15
+ "running", # The migration is being performed by a migration executor.
16
+ "pausing", # The migration has been told to pause but is finishing work.
17
+ "paused", # The migration was paused in the middle of the run by the user.
18
+ "failed", # The migration raises an exception when running.
19
+ "succeeded", # The migration finished without error.
20
+ "cancelling", # The migration has been told to cancel but is finishing work.
21
+ "cancelled", # The migration was cancelled by the user.
22
+ ]
23
+
24
+ COMPLETED_STATUSES = ["succeeded", "failed", "cancelled"]
25
+
26
+ ACTIVE_STATUSES = [
27
+ "enqueued",
28
+ "running",
29
+ "pausing",
30
+ "paused",
31
+ "cancelling",
32
+ ]
33
+
34
+ STOPPING_STATUSES = ["pausing", "cancelling", "cancelled"]
35
+
36
+ self.table_name = :background_data_migrations
37
+ self.ignored_columns += ["parent_id", "batch_column_name", "min_value", "max_value", "rows_count",
38
+ "batch_size", "sub_batch_size", "batch_pause", "sub_batch_pause_ms", "composite"]
39
+
40
+ scope :queue_order, -> { order(created_at: :asc) }
41
+ scope :for_migration_name, ->(migration_name) { where(migration_name: normalize_migration_name(migration_name)) }
42
+ scope :for_configuration, ->(migration_name, arguments) do
43
+ for_migration_name(migration_name).where("arguments = ?", arguments.to_json)
44
+ end
45
+
46
+ alias_attribute :name, :migration_name
47
+
48
+ enum :status, STATUSES.index_with(&:to_s)
49
+
50
+ validates :migration_name, presence: true
51
+ validates :arguments, uniqueness: { scope: [:migration_name, :shard] }
52
+
53
+ validates_with MigrationStatusValidator, on: :update
54
+
55
+ before_save :set_defaults
56
+ after_save :instrument_status_change, if: :status_previously_changed?
57
+
58
+ # @private
59
+ def self.normalize_migration_name(migration_name)
60
+ namespace = ::OnlineMigrations.config.background_data_migrations.migrations_module
61
+ migration_name.sub(/^(::)?#{namespace}::/, "")
62
+ end
63
+
64
+ def migration_name=(class_name)
65
+ class_name = class_name.name if class_name.is_a?(Class)
66
+ write_attribute(:migration_name, self.class.normalize_migration_name(class_name))
67
+ end
68
+ alias name= migration_name=
69
+
70
+ # Returns whether the migration has been started, which is indicated by the
71
+ # started_at timestamp being present.
72
+ #
73
+ # @return [Boolean] whether the migration was started.
74
+ #
75
+ def started?
76
+ started_at.present?
77
+ end
78
+
79
+ # Returns whether the migration is completed, which is defined as
80
+ # having a status of succeeded, failed, or cancelled.
81
+ #
82
+ # @return [Boolean] whether the migration is completed.
83
+ #
84
+ def completed?
85
+ COMPLETED_STATUSES.include?(status)
86
+ end
87
+
88
+ # Returns whether the migration is active, which is defined as
89
+ # having a status of enqueued, running, pausing, paused, or cancelling.
90
+ #
91
+ # @return [Boolean] whether the migration is active.
92
+ #
93
+ def active?
94
+ ACTIVE_STATUSES.include?(status)
95
+ end
96
+
97
+ # Returns whether the migration is stopping, which is defined as having a status
98
+ # of pausing or cancelling. The status of cancelled is also considered
99
+ # stopping since a migration can be cancelled while its job still exists in the
100
+ # queue, and we want to handle it the same way as a cancelling run.
101
+ #
102
+ # @return [Boolean] whether the migration is stopping.
103
+ #
104
+ def stopping?
105
+ STOPPING_STATUSES.include?(status)
106
+ end
107
+
108
+ # Returns whether a migration is stuck, which is defined as having a status of
109
+ # cancelling or pausing, and not having been updated in the last 5 minutes.
110
+ #
111
+ # @return [Boolean] whether the migration is stuck.
112
+ #
113
+ def stuck?
114
+ stuck_timeout = OnlineMigrations.config.background_data_migrations.stuck_timeout
115
+ (cancelling? || pausing?) && updated_at <= stuck_timeout.ago
116
+ end
117
+
118
+ # @private
119
+ def start
120
+ if running? && !started?
121
+ update!(started_at: Time.current)
122
+ data_migration.after_start
123
+ true
124
+ elsif enqueued?
125
+ update!(status: :running, started_at: Time.current)
126
+ data_migration.after_start
127
+ true
128
+ else
129
+ false
130
+ end
131
+ end
132
+
133
+ # Cancel this data migration. No-op if migration is completed.
134
+ #
135
+ # @return [Boolean] whether this data migration was cancelled.
136
+ #
137
+ def cancel
138
+ return false if completed?
139
+
140
+ if paused? || stuck?
141
+ update!(status: :cancelled, finished_at: Time.current)
142
+ elsif enqueued?
143
+ cancelled!
144
+ else
145
+ cancelling!
146
+ end
147
+
148
+ true
149
+ end
150
+
151
+ # Pause this data migration. No-op if migration is completed.
152
+ #
153
+ # @return [Boolean] whether this data migration was paused.
154
+ #
155
+ def pause
156
+ return false if completed?
157
+
158
+ if enqueued? || stuck?
159
+ paused!
160
+ else
161
+ pausing!
162
+ end
163
+
164
+ true
165
+ end
166
+
167
+ # Resume this data migration. No-op if migration is not paused.
168
+ #
169
+ # @return [Boolean] whether this data migration was resumed.
170
+ #
171
+ def resume
172
+ if paused?
173
+ enqueued!
174
+ true
175
+ else
176
+ false
177
+ end
178
+ end
179
+
180
+ # @private
181
+ def stop
182
+ return false if completed?
183
+
184
+ if cancelling?
185
+ update!(status: :cancelled, finished_at: Time.current)
186
+ data_migration.after_cancel
187
+ data_migration.after_complete
188
+ elsif pausing?
189
+ paused!
190
+ data_migration.after_pause
191
+ end
192
+
193
+ data_migration.after_stop
194
+ true
195
+ end
196
+
197
+ # @private
198
+ def complete
199
+ return false if completed?
200
+
201
+ if running?
202
+ update!(status: :succeeded, finished_at: Time.current)
203
+ data_migration.after_complete
204
+ elsif pausing?
205
+ update!(status: :paused, finished_at: Time.current)
206
+ elsif cancelling?
207
+ update!(status: :cancelled, finished_at: Time.current)
208
+ data_migration.after_complete
209
+ end
210
+
211
+ true
212
+ end
213
+
214
+ # @private
215
+ def persist_progress(cursor, number_of_ticks, duration)
216
+ update!(
217
+ cursor: cursor,
218
+ tick_count: tick_count + number_of_ticks,
219
+ time_running: time_running + duration
220
+ )
221
+ end
222
+
223
+ # @private
224
+ def persist_error(error)
225
+ backtrace = error.backtrace
226
+ backtrace_cleaner = OnlineMigrations.config.backtrace_cleaner
227
+ backtrace = backtrace_cleaner.clean(backtrace) if backtrace_cleaner
228
+
229
+ update!(
230
+ status: :failed,
231
+ finished_at: Time.current,
232
+ error_class: error.class.name,
233
+ error_message: error.message,
234
+ backtrace: backtrace
235
+ )
236
+ end
237
+
238
+ # Returns whether this migration is pausable.
239
+ #
240
+ def pausable?
241
+ true
242
+ end
243
+
244
+ # Returns the progress of the data migration.
245
+ #
246
+ # @return [Float, nil]
247
+ # - when background migration is configured to not track progress, returns `nil`
248
+ # - otherwise returns value in range from 0.0 to 100.0
249
+ #
250
+ def progress
251
+ if succeeded?
252
+ 100.0
253
+ elsif enqueued? || tick_total == 0
254
+ 0.0
255
+ elsif tick_total
256
+ ([tick_count.to_f / tick_total, 1.0].min * 100)
257
+ end
258
+ end
259
+
260
+ # Returns data migration associated with this migration.
261
+ #
262
+ # @return [OnlineMigrations::DataMigration]
263
+ #
264
+ def data_migration
265
+ @data_migration ||= begin
266
+ klass = DataMigration.named(migration_name)
267
+ klass.new(*arguments)
268
+ end
269
+ end
270
+
271
+ # Mark this migration as ready to be processed again.
272
+ #
273
+ # This method marks failed migrations as ready to be processed again, and
274
+ # they will be picked up on the next Scheduler run.
275
+ #
276
+ def retry
277
+ if failed?
278
+ update!(
279
+ status: :enqueued,
280
+ started_at: nil,
281
+ finished_at: nil,
282
+ error_class: nil,
283
+ error_message: nil,
284
+ backtrace: nil,
285
+ jid: nil
286
+ )
287
+ true
288
+ else
289
+ false
290
+ end
291
+ end
292
+
293
+ private
294
+ def set_defaults
295
+ config = ::OnlineMigrations.config.background_data_migrations
296
+ self.max_attempts ||= config.max_attempts
297
+ self.tick_total ||= on_shard_if_present do
298
+ data_migration.count
299
+ end
300
+ end
301
+
302
+ def instrument_status_change
303
+ payload = { migration: self }
304
+ if running?
305
+ ActiveSupport::Notifications.instrument("started.background_data_migrations", payload)
306
+ elsif succeeded?
307
+ ActiveSupport::Notifications.instrument("completed.background_data_migrations", payload)
308
+ end
309
+ end
310
+ end
311
+ end
312
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- module BackgroundMigrations
4
+ module BackgroundDataMigrations
5
5
  module MigrationHelpers
6
6
  # Backfills column data using background migrations.
7
7
  #
@@ -13,7 +13,7 @@ module OnlineMigrations
13
13
  # @param options [Hash] used to control the behavior of background migration.
14
14
  # See `#enqueue_background_data_migration`
15
15
  #
16
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
16
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
17
17
  #
18
18
  # @example
19
19
  # backfill_column_in_background(:users, :admin, false)
@@ -69,7 +69,7 @@ module OnlineMigrations
69
69
  # @param options [Hash] used to control the behavior of background migration.
70
70
  # See `#enqueue_background_data_migration`
71
71
  #
72
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
72
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
73
73
  #
74
74
  # @example
75
75
  # backfill_column_for_type_change_in_background(:files, :size)
@@ -108,7 +108,14 @@ module OnlineMigrations
108
108
  end
109
109
 
110
110
  tmp_columns = column_names.map { |column_name| "#{column_name}_for_type_change" }
111
- model_name = model_name.name if model_name.is_a?(Class)
111
+
112
+ if model_name
113
+ model_name = model_name.name if model_name.is_a?(Class)
114
+ connection_class_name = Utils.find_connection_class(model_name.constantize).name
115
+ end
116
+
117
+ # model_name = model_name.name if model_name.is_a?(Class)
118
+ # connection_class = Utils.find_connection_class(model_name.constantize) if model_name
112
119
 
113
120
  enqueue_background_data_migration(
114
121
  "CopyColumn",
@@ -117,6 +124,7 @@ module OnlineMigrations
117
124
  tmp_columns,
118
125
  model_name,
119
126
  type_cast_functions,
127
+ connection_class_name: connection_class_name,
120
128
  **options
121
129
  )
122
130
  end
@@ -134,7 +142,7 @@ module OnlineMigrations
134
142
  # @param options [Hash] used to control the behavior of background migration.
135
143
  # See `#enqueue_background_data_migration`
136
144
  #
137
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
145
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
138
146
  #
139
147
  # @example
140
148
  # copy_column_in_background(:users, :id, :id_for_type_change)
@@ -189,7 +197,7 @@ module OnlineMigrations
189
197
  # @param options [Hash] used to control the behavior of background migration.
190
198
  # See `#enqueue_background_data_migration`
191
199
  #
192
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
200
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
193
201
  #
194
202
  # @example
195
203
  # reset_counters_in_background("User", :projects, :friends, touch: true)
@@ -226,7 +234,7 @@ module OnlineMigrations
226
234
  # @param options [Hash] used to control the behavior of background migration.
227
235
  # See `#enqueue_background_data_migration`
228
236
  #
229
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
237
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
230
238
  #
231
239
  # @example
232
240
  # delete_orphaned_records_in_background("Post", :author)
@@ -255,7 +263,7 @@ module OnlineMigrations
255
263
  # @param options [Hash] used to control the behavior of background migration.
256
264
  # See `#enqueue_background_data_migration`
257
265
  #
258
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
266
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
259
267
  #
260
268
  # @example
261
269
  # delete_associated_records_in_background("Link", 1, :clicks)
@@ -286,7 +294,7 @@ module OnlineMigrations
286
294
  # @param options [Hash] used to control the behavior of background migration.
287
295
  # See `#enqueue_background_data_migration`
288
296
  #
289
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
297
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
290
298
  #
291
299
  # @example Delete records
292
300
  # perform_action_on_relation_in_background("User", { banned: true }, :delete_all)
@@ -328,56 +336,64 @@ module OnlineMigrations
328
336
  # based on the current migration settings and the previous batch bounds. Each job's execution status
329
337
  # is tracked in the database as the migration runs.
330
338
  #
331
- # @param migration_name [String, Class] Background migration job class name
332
- # @param arguments [Array] Extra arguments to pass to the job instance when the migration runs
333
- # @option options [Symbol, String] :batch_column_name (primary key) Column name the migration will batch over
334
- # @option options [Integer] :min_value Value in the column the batching will begin at,
335
- # defaults to `SELECT MIN(batch_column_name)`
336
- # @option options [Integer] :max_value Value in the column the batching will end at,
337
- # defaults to `SELECT MAX(batch_column_name)`
338
- # @option options [Integer] :batch_size (1_000) Number of rows to process in a single background migration run
339
- # @option options [Integer] :sub_batch_size (100) Smaller batches size that the batches will be divided into
340
- # @option options [Integer] :batch_pause (0) Pause interval between each background migration job's execution (in seconds)
341
- # @option options [Integer] :sub_batch_pause_ms (100) Number of milliseconds to sleep between each sub_batch execution
342
- # @option options [Integer] :batch_max_attempts (5) Maximum number of batch run attempts
343
- #
344
- # @return [OnlineMigrations::BackgroundMigrations::Migration]
339
+ # @param migration_name [String, Class] Background migration class name
340
+ # @param arguments [Array] Extra arguments to pass to the migration instance when the migration runs
341
+ # @option options [Integer] :max_attempts (5) Maximum number of batch run attempts
342
+ # @option options [String, nil] :connection_class_name Class name to use to get connections
343
+ #
344
+ # @return [OnlineMigrations::BackgroundDataMigrations::Migration]
345
345
  #
346
346
  # @example
347
- # enqueue_background_data_migration("BackfillProjectIssuesCount",
348
- # batch_size: 10_000, batch_max_attempts: 10)
347
+ # enqueue_background_data_migration("BackfillProjectIssuesCount")
349
348
  #
350
349
  # # Given the background migration exists:
351
350
  #
352
- # class BackfillProjectIssuesCount < OnlineMigrations::BackgroundMigration
353
- # def relation
354
- # Project.all
351
+ # class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
352
+ # def collection
353
+ # Project.in_batches(of: 100)
355
354
  # end
356
355
  #
357
- # def process_batch(projects)
356
+ # def process(projects)
358
357
  # projects.update_all(
359
358
  # "issues_count = (SELECT COUNT(*) FROM issues WHERE issues.project_id = projects.id)"
360
359
  # )
361
360
  # end
362
361
  #
363
- # # To be able to track progress, you need to define this method
362
+ # # To be able to track progress, you need to define this method.
364
363
  # def count
365
364
  # Project.maximum(:id)
366
365
  # end
367
366
  # end
368
367
  #
369
- # @note For convenience, the enqueued background migration is run inline
368
+ # @note For convenience, the enqueued background data migration is run inline
370
369
  # in development and test environments
371
370
  #
372
371
  def enqueue_background_data_migration(migration_name, *arguments, **options)
373
- migration = create_background_data_migration(migration_name, *arguments, **options)
372
+ options.assert_valid_keys(:max_attempts, :connection_class_name)
373
+
374
+ migration_name = migration_name.name if migration_name.is_a?(Class)
375
+ options[:connection_class_name] ||= compute_connection_class_name(migration_name, arguments)
374
376
 
375
- if Utils.run_background_migrations_inline?
376
- runner = MigrationRunner.new(migration)
377
- runner.run_all_migration_jobs
377
+ if Utils.multiple_databases? && !options[:connection_class_name]
378
+ raise ArgumentError, "You must pass a :connection_class_name when using multiple databases."
378
379
  end
379
380
 
380
- migration
381
+ connection_class = options[:connection_class_name].constantize
382
+ shards = Utils.shard_names(connection_class)
383
+ shards = [nil] if shards.size == 1
384
+
385
+ shards.each do |shard|
386
+ # Can't use `find_or_create_by` or hash syntax here, because it does not correctly work with json `arguments`.
387
+ migration = Migration.where(migration_name: migration_name, shard: shard).where("arguments = ?", arguments.to_json).first
388
+ migration ||= Migration.create!(**options, migration_name: migration_name, arguments: arguments, shard: shard)
389
+
390
+ if Utils.run_background_migrations_inline?
391
+ job = OnlineMigrations.config.background_data_migrations.job
392
+ job.constantize.perform_inline(migration.id)
393
+ end
394
+ end
395
+
396
+ true
381
397
  end
382
398
  alias enqueue_background_migration enqueue_background_data_migration
383
399
 
@@ -418,45 +434,40 @@ module OnlineMigrations
418
434
 
419
435
  if arguments
420
436
  arguments = Array(arguments)
421
- migration = Migration.parents.for_configuration(migration_name, arguments).first
437
+ migrations = Migration.for_configuration(migration_name, arguments).to_a
422
438
  configuration[:arguments] = arguments.to_json
423
439
  else
424
- migration = Migration.parents.for_migration_name(migration_name).first
440
+ migrations = Migration.for_migration_name(migration_name).to_a
425
441
  end
426
442
 
427
- if migration.nil?
428
- Utils.raise_in_prod_or_say_in_dev("Could not find background data migration for the given configuration: #{configuration}")
429
- elsif !migration.succeeded?
430
- raise "Expected background data migration for the given configuration to be marked as 'succeeded', " \
431
- "but it is '#{migration.status}': #{configuration}"
443
+ if migrations.empty?
444
+ Utils.raise_in_prod_or_say_in_dev("Could not find background data migration(s) for the given configuration: #{configuration}.")
445
+ elsif !migrations.all?(&:succeeded?)
446
+ raise "Expected background data migration(s) for the given configuration to be marked as 'succeeded': #{configuration}."
432
447
  end
433
448
  end
434
449
  alias ensure_background_migration_succeeded ensure_background_data_migration_succeeded
435
450
 
436
- # @private
437
- def create_background_data_migration(migration_name, *arguments, **options)
438
- options.assert_valid_keys(:batch_column_name, :min_value, :max_value, :batch_size, :sub_batch_size,
439
- :batch_pause, :sub_batch_pause_ms, :batch_max_attempts)
451
+ private
452
+ def compute_connection_class_name(migration_name, arguments)
453
+ klass = DataMigration.named(migration_name)
454
+ data_migration = klass.new(*arguments)
440
455
 
441
- migration_name = migration_name.name if migration_name.is_a?(Class)
456
+ collection = data_migration.collection
442
457
 
443
- # Can't use `find_or_create_by` or hash syntax here, because it does not correctly work with json `arguments`.
444
- existing_migration = Migration.find_by("migration_name = ? AND arguments = ? AND shard IS NULL", migration_name, arguments.to_json)
445
- return existing_migration if existing_migration
446
-
447
- Migration.create!(**options, migration_name: migration_name, arguments: arguments, shard: nil) do |migration|
448
- shards = Utils.shard_names(migration.migration_model)
449
- if shards.size > 1
450
- migration.children = shards.map do |shard|
451
- child = migration.dup
452
- child.shard = shard
453
- child
458
+ model =
459
+ case collection
460
+ when ActiveRecord::Relation then collection.model
461
+ when ActiveRecord::Batches::BatchEnumerator then collection.relation.model
454
462
  end
455
463
 
456
- migration.composite = true
464
+ if model
465
+ connection_class = Utils.find_connection_class(model)
466
+ connection_class.name
457
467
  end
468
+ rescue NotImplementedError
469
+ nil
458
470
  end
459
- end
460
471
  end
461
472
  end
462
473
  end