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.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +14 -0
 - data/docs/0.27-upgrade.md +24 -0
 - data/docs/background_data_migrations.md +200 -101
 - data/docs/background_schema_migrations.md +2 -2
 - data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
 - data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
 - data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
 - data/lib/generators/online_migrations/templates/initializer.rb.tt +19 -25
 - data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
 - data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
 - data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
 - data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
 - data/lib/online_migrations/background_data_migrations/config.rb +62 -0
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
 - data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
 - data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
 - data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
 - data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
 - data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
 - data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
 - data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
 - data/lib/online_migrations/background_schema_migrations/migration.rb +51 -123
 - data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +25 -46
 - data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
 - data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
 - data/lib/online_migrations/change_column_type_helpers.rb +3 -2
 - data/lib/online_migrations/config.rb +4 -4
 - data/lib/online_migrations/data_migration.rb +127 -0
 - data/lib/online_migrations/lock_retrier.rb +5 -2
 - data/lib/online_migrations/schema_statements.rb +1 -1
 - data/lib/online_migrations/shard_aware.rb +44 -0
 - data/lib/online_migrations/version.rb +1 -1
 - data/lib/online_migrations.rb +18 -11
 - metadata +22 -21
 - data/lib/online_migrations/background_migration.rb +0 -64
 - data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
 - data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
 - data/lib/online_migrations/background_migrations/config.rb +0 -74
 - data/lib/online_migrations/background_migrations/migration.rb +0 -329
 - data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
 - data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
 - data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
 - data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
 - data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
 - 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
         
     | 
    
        data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb
    RENAMED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
            module OnlineMigrations
         
     | 
| 
       4 
     | 
    
         
            -
              module  
     | 
| 
      
 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:: 
     | 
| 
      
 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:: 
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 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:: 
     | 
| 
      
 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:: 
     | 
| 
      
 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:: 
     | 
| 
      
 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:: 
     | 
| 
      
 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:: 
     | 
| 
      
 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  
     | 
| 
       332 
     | 
    
         
            -
                  # @param arguments [Array] Extra arguments to pass to the  
     | 
| 
       333 
     | 
    
         
            -
                  # @option options [ 
     | 
| 
       334 
     | 
    
         
            -
                  # @option options [ 
     | 
| 
       335 
     | 
    
         
            -
                  # 
     | 
| 
       336 
     | 
    
         
            -
                  # @ 
     | 
| 
       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:: 
     | 
| 
       353 
     | 
    
         
            -
                  #     def  
     | 
| 
       354 
     | 
    
         
            -
                  #       Project. 
     | 
| 
      
 351 
     | 
    
         
            +
                  #   class BackfillProjectIssuesCount < OnlineMigrations::DataMigration
         
     | 
| 
      
 352 
     | 
    
         
            +
                  #     def collection
         
     | 
| 
      
 353 
     | 
    
         
            +
                  #       Project.in_batches(of: 100)
         
     | 
| 
       355 
354 
     | 
    
         
             
                  #     end
         
     | 
| 
       356 
355 
     | 
    
         
             
                  #
         
     | 
| 
       357 
     | 
    
         
            -
                  #     def  
     | 
| 
      
 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 
     | 
    
         
            -
                     
     | 
| 
      
 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. 
     | 
| 
       376 
     | 
    
         
            -
                       
     | 
| 
       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 
     | 
    
         
            -
                     
     | 
| 
      
 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 
     | 
    
         
            -
                       
     | 
| 
      
 437 
     | 
    
         
            +
                      migrations = Migration.for_configuration(migration_name, arguments).to_a
         
     | 
| 
       422 
438 
     | 
    
         
             
                      configuration[:arguments] = arguments.to_json
         
     | 
| 
       423 
439 
     | 
    
         
             
                    else
         
     | 
| 
       424 
     | 
    
         
            -
                       
     | 
| 
      
 440 
     | 
    
         
            +
                      migrations = Migration.for_migration_name(migration_name).to_a
         
     | 
| 
       425 
441 
     | 
    
         
             
                    end
         
     | 
| 
       426 
442 
     | 
    
         | 
| 
       427 
     | 
    
         
            -
                    if  
     | 
| 
       428 
     | 
    
         
            -
                      Utils.raise_in_prod_or_say_in_dev("Could not find background data migration for the given configuration: #{configuration}")
         
     | 
| 
       429 
     | 
    
         
            -
                    elsif ! 
     | 
| 
       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 
     | 
    
         
            -
                   
     | 
| 
       437 
     | 
    
         
            -
             
     | 
| 
       438 
     | 
    
         
            -
             
     | 
| 
       439 
     | 
    
         
            -
             
     | 
| 
      
 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 
     | 
    
         
            -
             
     | 
| 
      
 456 
     | 
    
         
            +
                      collection = data_migration.collection
         
     | 
| 
       442 
457 
     | 
    
         | 
| 
       443 
     | 
    
         
            -
             
     | 
| 
       444 
     | 
    
         
            -
             
     | 
| 
       445 
     | 
    
         
            -
             
     | 
| 
       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 
     | 
    
         
            -
             
     | 
| 
      
 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
         
     |