online_migrations 0.29.2 → 0.30.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 +9 -0
 - data/docs/configuring.md +123 -0
 - data/lib/online_migrations/background_schema_migrations/migration.rb +1 -1
 - data/lib/online_migrations/lock_retrier.rb +45 -28
 - data/lib/online_migrations/migration.rb +2 -2
 - data/lib/online_migrations/migrator.rb +5 -0
 - data/lib/online_migrations/schema_statements.rb +4 -4
 - data/lib/online_migrations/version.rb +1 -1
 - metadata +3 -7
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 428fd65e363b915608f720370a2cd91ec14749cd17933ec4e26351bb4745084a
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ae292bba4cd1b557694f78240a7da761c8d890aa691cc6b485ddd9f71fd339c5
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 59c0573c24d75488c849fa746d6346c31e439d66032725264ce724e97ab8ffbd16cebb03a57ad75071e2b29bf884be0b47696abdcadc72d974effaafd73fe8b4
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 73de95254d001d4d285a48ad4d3adb60726ab6bf0bb86b085dde8466b6e1e85fbb668738f09121c38718504ad253615afb3da5e24f56bc48bd01ce9d369cdd4c
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ## master (unreleased)
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ## 0.30.0 (2025-10-17)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            - Fix `remove_check_constraint` when using `:if_exists`
         
     | 
| 
      
 6 
     | 
    
         
            +
            - Add schema statement to lock retrier
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            ## 0.29.3 (2025-08-19)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            - Fix an edge case where a background index might not be added
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
       3 
12 
     | 
    
         
             
            ## 0.29.2 (2025-07-21)
         
     | 
| 
       4 
13 
     | 
    
         | 
| 
       5 
14 
     | 
    
         
             
            - Fix analyzing tables when indexes are added
         
     | 
    
        data/docs/configuring.md
    CHANGED
    
    | 
         @@ -113,6 +113,129 @@ When a statement within transaction fails - the whole transaction is retried. If 
     | 
|
| 
       113 
113 
     | 
    
         | 
| 
       114 
114 
     | 
    
         
             
            **Note**: Statements are retried by default, unless lock retries are disabled. It is possible to implement more sophisticated lock retriers. See [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/lock_retrier.rb) for the examples.
         
     | 
| 
       115 
115 
     | 
    
         | 
| 
      
 116 
     | 
    
         
            +
            ### Command-specific lock retry configuration
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            For migrations using `disable_ddl_transaction!`, you can implement command-specific lock retry behavior. This is useful when different DDL operations have different locking characteristics:
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
            - `add_index` with `algorithm: :concurrently` uses `ShareUpdateExclusiveLock` (less restrictive), so can use longer timeouts
         
     | 
| 
      
 121 
     | 
    
         
            +
            - `add_foreign_key` uses `AccessExclusiveLock` (blocks all access), so should use shorter timeouts to fail fast
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            **Note**: Command-specific configuration only works for migrations with `disable_ddl_transaction!`. For migrations running within transactions (the default), the lock retrier wraps the entire transaction and doesn't have visibility into individual DDL commands.
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 126 
     | 
    
         
            +
            module OnlineMigrations
         
     | 
| 
      
 127 
     | 
    
         
            +
              class CommandAwareLockRetrier < LockRetrier
         
     | 
| 
      
 128 
     | 
    
         
            +
                # You can vary the number of attempts based on the command
         
     | 
| 
      
 129 
     | 
    
         
            +
                def attempts(command = nil, arguments = [])
         
     | 
| 
      
 130 
     | 
    
         
            +
                  case command
         
     | 
| 
      
 131 
     | 
    
         
            +
                  when :add_index
         
     | 
| 
      
 132 
     | 
    
         
            +
                    # Concurrent index creation uses longer individual timeouts,
         
     | 
| 
      
 133 
     | 
    
         
            +
                    # so fewer attempts are needed to reach the same overall window
         
     | 
| 
      
 134 
     | 
    
         
            +
                    10
         
     | 
| 
      
 135 
     | 
    
         
            +
                  when :add_foreign_key
         
     | 
| 
      
 136 
     | 
    
         
            +
                    # Foreign keys use shorter timeouts to fail fast,
         
     | 
| 
      
 137 
     | 
    
         
            +
                    # so more attempts are needed to reach the same overall window
         
     | 
| 
      
 138 
     | 
    
         
            +
                    60
         
     | 
| 
      
 139 
     | 
    
         
            +
                  else
         
     | 
| 
      
 140 
     | 
    
         
            +
                    # Default attempts for other operations
         
     | 
| 
      
 141 
     | 
    
         
            +
                    30
         
     | 
| 
      
 142 
     | 
    
         
            +
                  end
         
     | 
| 
      
 143 
     | 
    
         
            +
                end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                def lock_timeout(attempt, command = nil, arguments = [])
         
     | 
| 
      
 146 
     | 
    
         
            +
                  case command
         
     | 
| 
      
 147 
     | 
    
         
            +
                  when :add_index
         
     | 
| 
      
 148 
     | 
    
         
            +
                    # Concurrent index creation is less restrictive, use longer timeout
         
     | 
| 
      
 149 
     | 
    
         
            +
                    30.seconds
         
     | 
| 
      
 150 
     | 
    
         
            +
                  when :add_foreign_key
         
     | 
| 
      
 151 
     | 
    
         
            +
                    # Foreign keys block all access, use shorter timeout to fail fast
         
     | 
| 
      
 152 
     | 
    
         
            +
                    5.seconds
         
     | 
| 
      
 153 
     | 
    
         
            +
                  else
         
     | 
| 
      
 154 
     | 
    
         
            +
                    # Default timeout for other operations
         
     | 
| 
      
 155 
     | 
    
         
            +
                    10.seconds
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
                def delay(attempt, command = nil, arguments = [])
         
     | 
| 
      
 160 
     | 
    
         
            +
                  case command
         
     | 
| 
      
 161 
     | 
    
         
            +
                  when :add_index
         
     | 
| 
      
 162 
     | 
    
         
            +
                    # Longer delay for index operations since they take time anyway
         
     | 
| 
      
 163 
     | 
    
         
            +
                    3.seconds
         
     | 
| 
      
 164 
     | 
    
         
            +
                  when :add_foreign_key
         
     | 
| 
      
 165 
     | 
    
         
            +
                    # Shorter delay to retry faster for quick FK operations
         
     | 
| 
      
 166 
     | 
    
         
            +
                    1.second
         
     | 
| 
      
 167 
     | 
    
         
            +
                  else
         
     | 
| 
      
 168 
     | 
    
         
            +
                    # Default delay for other operations
         
     | 
| 
      
 169 
     | 
    
         
            +
                    2.seconds
         
     | 
| 
      
 170 
     | 
    
         
            +
                  end
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
              end
         
     | 
| 
      
 173 
     | 
    
         
            +
            end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
            config.lock_retrier = OnlineMigrations::CommandAwareLockRetrier.new
         
     | 
| 
      
 176 
     | 
    
         
            +
            ```
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
      
 178 
     | 
    
         
            +
            All three methods (`attempts`, `lock_timeout`, and `delay`) can receive command-specific parameters:
         
     | 
| 
      
 179 
     | 
    
         
            +
            - `command` - the migration method being called (e.g., `:add_index`, `:add_column`, `:add_foreign_key`), or `nil` for transaction-wrapped migrations
         
     | 
| 
      
 180 
     | 
    
         
            +
            - `arguments` - an array of arguments passed to the migration method
         
     | 
| 
      
 181 
     | 
    
         
            +
             
     | 
| 
      
 182 
     | 
    
         
            +
            Additionally, `lock_timeout` and `delay` receive:
         
     | 
| 
      
 183 
     | 
    
         
            +
            - `attempt` - the current retry attempt number (1-indexed)
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
            This allows you to fine-tune the retry strategy for different commands. For example, to maintain roughly the same total timeout window:
         
     | 
| 
      
 186 
     | 
    
         
            +
            - `add_index`: 10 attempts × (30s lock + 3s delay) = ~5.5 minute window
         
     | 
| 
      
 187 
     | 
    
         
            +
            - `add_foreign_key`: 60 attempts × (5s lock + 1s delay) = ~6 minute window
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
            #### Alternative: Configuration Hash Approach
         
     | 
| 
      
 190 
     | 
    
         
            +
             
     | 
| 
      
 191 
     | 
    
         
            +
            For simpler use cases, you can use a configuration hash instead of case statements:
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 194 
     | 
    
         
            +
            module OnlineMigrations
         
     | 
| 
      
 195 
     | 
    
         
            +
              class ConfigurableLockRetrier < LockRetrier
         
     | 
| 
      
 196 
     | 
    
         
            +
                COMMAND_CONFIGS = {
         
     | 
| 
      
 197 
     | 
    
         
            +
                  add_index: {
         
     | 
| 
      
 198 
     | 
    
         
            +
                    attempts: 10,
         
     | 
| 
      
 199 
     | 
    
         
            +
                    lock_timeout: 30.seconds,
         
     | 
| 
      
 200 
     | 
    
         
            +
                    delay: 3.seconds
         
     | 
| 
      
 201 
     | 
    
         
            +
                  },
         
     | 
| 
      
 202 
     | 
    
         
            +
                  add_foreign_key: {
         
     | 
| 
      
 203 
     | 
    
         
            +
                    attempts: 60,
         
     | 
| 
      
 204 
     | 
    
         
            +
                    lock_timeout: 5.seconds,
         
     | 
| 
      
 205 
     | 
    
         
            +
                    delay: 1.second
         
     | 
| 
      
 206 
     | 
    
         
            +
                  },
         
     | 
| 
      
 207 
     | 
    
         
            +
                  default: {
         
     | 
| 
      
 208 
     | 
    
         
            +
                    attempts: 30,
         
     | 
| 
      
 209 
     | 
    
         
            +
                    lock_timeout: 10.seconds,
         
     | 
| 
      
 210 
     | 
    
         
            +
                    delay: 2.seconds
         
     | 
| 
      
 211 
     | 
    
         
            +
                  }
         
     | 
| 
      
 212 
     | 
    
         
            +
                }
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                def attempts(command = nil, arguments = [])
         
     | 
| 
      
 215 
     | 
    
         
            +
                  config_for(command)[:attempts]
         
     | 
| 
      
 216 
     | 
    
         
            +
                end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
      
 218 
     | 
    
         
            +
                def lock_timeout(attempt, command = nil, arguments = [])
         
     | 
| 
      
 219 
     | 
    
         
            +
                  config_for(command)[:lock_timeout]
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                def delay(attempt, command = nil, arguments = [])
         
     | 
| 
      
 223 
     | 
    
         
            +
                  config_for(command)[:delay]
         
     | 
| 
      
 224 
     | 
    
         
            +
                end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                private
         
     | 
| 
      
 227 
     | 
    
         
            +
             
     | 
| 
      
 228 
     | 
    
         
            +
                def config_for(command)
         
     | 
| 
      
 229 
     | 
    
         
            +
                  COMMAND_CONFIGS[command] || COMMAND_CONFIGS[:default]
         
     | 
| 
      
 230 
     | 
    
         
            +
                end
         
     | 
| 
      
 231 
     | 
    
         
            +
              end
         
     | 
| 
      
 232 
     | 
    
         
            +
            end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
            config.lock_retrier = OnlineMigrations::ConfigurableLockRetrier.new
         
     | 
| 
      
 235 
     | 
    
         
            +
            ```
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
            This approach is more concise and easier to maintain when you have simple static configurations per command. The case statement approach (shown above) is better when you need conditional logic or want to use the `attempt` parameter dynamically.
         
     | 
| 
      
 238 
     | 
    
         
            +
             
     | 
| 
       116 
239 
     | 
    
         
             
            To temporarily disable lock retries while running migrations, set `DISABLE_LOCK_RETRIES` env variable. This is useful when you are deploying a hotfix and do not want to wait too long while the lock retrier safely tries to acquire the lock, but try to acquire the lock immediately with the default configured lock timeout value.
         
     | 
| 
       117 
240 
     | 
    
         | 
| 
       118 
241 
     | 
    
         
             
            To permanently disable lock retries, you can set `lock_retrier` to `nil`.
         
     | 
| 
         @@ -133,7 +133,7 @@ module OnlineMigrations 
     | 
|
| 
       133 
133 
     | 
    
         | 
| 
       134 
134 
     | 
    
         
             
                        with_statement_timeout(connection, statement_timeout) do
         
     | 
| 
       135 
135 
     | 
    
         
             
                          if index_addition?
         
     | 
| 
       136 
     | 
    
         
            -
                            index = connection.indexes(table_name).find { |i| name. 
     | 
| 
      
 136 
     | 
    
         
            +
                            index = connection.indexes(table_name).find { |i| name.match?(/\b#{i.name}\b/) }
         
     | 
| 
       137 
137 
     | 
    
         
             
                            if index
         
     | 
| 
       138 
138 
     | 
    
         
             
                              if index.valid?
         
     | 
| 
       139 
139 
     | 
    
         
             
                                return
         
     | 
| 
         @@ -36,7 +36,7 @@ module OnlineMigrations 
     | 
|
| 
       36 
36 
     | 
    
         
             
              #         TIMINGS.size
         
     | 
| 
       37 
37 
     | 
    
         
             
              #       end
         
     | 
| 
       38 
38 
     | 
    
         
             
              #
         
     | 
| 
       39 
     | 
    
         
            -
              #       def lock_timeout(attempt)
         
     | 
| 
      
 39 
     | 
    
         
            +
              #       def lock_timeout(attempt, command = nil, arguments = [])
         
     | 
| 
       40 
40 
     | 
    
         
             
              #         TIMINGS[attempt - 1][0]
         
     | 
| 
       41 
41 
     | 
    
         
             
              #       end
         
     | 
| 
       42 
42 
     | 
    
         
             
              #
         
     | 
| 
         @@ -48,21 +48,34 @@ module OnlineMigrations 
     | 
|
| 
       48 
48 
     | 
    
         
             
              class LockRetrier
         
     | 
| 
       49 
49 
     | 
    
         
             
                # Returns the number of retrying attempts
         
     | 
| 
       50 
50 
     | 
    
         
             
                #
         
     | 
| 
       51 
     | 
    
         
            -
                 
     | 
| 
      
 51 
     | 
    
         
            +
                # @param _command [Symbol, nil] the migration method being executed (e.g., :add_index, :add_column).
         
     | 
| 
      
 52 
     | 
    
         
            +
                #   Will be nil when called from a transaction-wrapped migration (the default).
         
     | 
| 
      
 53 
     | 
    
         
            +
                #   Only populated for migrations using `disable_ddl_transaction!`.
         
     | 
| 
      
 54 
     | 
    
         
            +
                # @param _arguments [Array] the arguments passed to the migration method
         
     | 
| 
      
 55 
     | 
    
         
            +
                #
         
     | 
| 
      
 56 
     | 
    
         
            +
                def attempts(_command = nil, _arguments = [])
         
     | 
| 
       52 
57 
     | 
    
         
             
                  raise NotImplementedError
         
     | 
| 
       53 
58 
     | 
    
         
             
                end
         
     | 
| 
       54 
59 
     | 
    
         | 
| 
       55 
60 
     | 
    
         
             
                # Returns database lock timeout value (in seconds) for specified attempt number
         
     | 
| 
       56 
61 
     | 
    
         
             
                #
         
     | 
| 
       57 
62 
     | 
    
         
             
                # @param _attempt [Integer] attempt number
         
     | 
| 
      
 63 
     | 
    
         
            +
                # @param _command [Symbol, nil] the migration method being executed (e.g., :add_index, :add_column).
         
     | 
| 
      
 64 
     | 
    
         
            +
                #   Will be nil when called from a transaction-wrapped migration (the default).
         
     | 
| 
      
 65 
     | 
    
         
            +
                #   Only populated for migrations using `disable_ddl_transaction!`.
         
     | 
| 
      
 66 
     | 
    
         
            +
                # @param _arguments [Array] the arguments passed to the migration method
         
     | 
| 
       58 
67 
     | 
    
         
             
                #
         
     | 
| 
       59 
     | 
    
         
            -
                def lock_timeout(_attempt); end
         
     | 
| 
      
 68 
     | 
    
         
            +
                def lock_timeout(_attempt, _command = nil, _arguments = []); end
         
     | 
| 
       60 
69 
     | 
    
         | 
| 
       61 
70 
     | 
    
         
             
                # Returns sleep time after unsuccessful lock attempt (in seconds)
         
     | 
| 
       62 
71 
     | 
    
         
             
                #
         
     | 
| 
       63 
72 
     | 
    
         
             
                # @param _attempt [Integer] attempt number
         
     | 
| 
      
 73 
     | 
    
         
            +
                # @param _command [Symbol, nil] the migration method being executed (e.g., :add_index, :add_column).
         
     | 
| 
      
 74 
     | 
    
         
            +
                #   Will be nil when called from a transaction-wrapped migration (the default).
         
     | 
| 
      
 75 
     | 
    
         
            +
                #   Only populated for migrations using `disable_ddl_transaction!`.
         
     | 
| 
      
 76 
     | 
    
         
            +
                # @param _arguments [Array] the arguments passed to the migration method
         
     | 
| 
       64 
77 
     | 
    
         
             
                #
         
     | 
| 
       65 
     | 
    
         
            -
                def delay(_attempt)
         
     | 
| 
      
 78 
     | 
    
         
            +
                def delay(_attempt, _command = nil, _arguments = [])
         
     | 
| 
       66 
79 
     | 
    
         
             
                  raise NotImplementedError
         
     | 
| 
       67 
80 
     | 
    
         
             
                end
         
     | 
| 
       68 
81 
     | 
    
         | 
| 
         @@ -77,7 +90,7 @@ module OnlineMigrations 
     | 
|
| 
       77 
90 
     | 
    
         
             
                #     add_column(:users, :name, :string)
         
     | 
| 
       78 
91 
     | 
    
         
             
                #   end
         
     | 
| 
       79 
92 
     | 
    
         
             
                #
         
     | 
| 
       80 
     | 
    
         
            -
                def with_lock_retries(connection, &block)
         
     | 
| 
      
 93 
     | 
    
         
            +
                def with_lock_retries(connection, command = nil, *arguments, &block)
         
     | 
| 
       81 
94 
     | 
    
         
             
                  return yield if lock_retries_disabled?
         
     | 
| 
       82 
95 
     | 
    
         | 
| 
       83 
96 
     | 
    
         
             
                  current_attempt = 0
         
     | 
| 
         @@ -85,15 +98,15 @@ module OnlineMigrations 
     | 
|
| 
       85 
98 
     | 
    
         
             
                  begin
         
     | 
| 
       86 
99 
     | 
    
         
             
                    current_attempt += 1
         
     | 
| 
       87 
100 
     | 
    
         | 
| 
       88 
     | 
    
         
            -
                    current_lock_timeout = lock_timeout(current_attempt)
         
     | 
| 
      
 101 
     | 
    
         
            +
                    current_lock_timeout = lock_timeout(current_attempt, command, arguments)
         
     | 
| 
       89 
102 
     | 
    
         
             
                    if current_lock_timeout
         
     | 
| 
       90 
103 
     | 
    
         
             
                      with_lock_timeout(connection, current_lock_timeout.in_milliseconds, &block)
         
     | 
| 
       91 
104 
     | 
    
         
             
                    else
         
     | 
| 
       92 
105 
     | 
    
         
             
                      yield
         
     | 
| 
       93 
106 
     | 
    
         
             
                    end
         
     | 
| 
       94 
107 
     | 
    
         
             
                  rescue ActiveRecord::LockWaitTimeout, ActiveRecord::Deadlocked => e
         
     | 
| 
       95 
     | 
    
         
            -
                    if current_attempt <= attempts
         
     | 
| 
       96 
     | 
    
         
            -
                      current_delay = delay(current_attempt)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    if current_attempt <= attempts(command, arguments)
         
     | 
| 
      
 109 
     | 
    
         
            +
                      current_delay = delay(current_attempt, command, arguments)
         
     | 
| 
       97 
110 
     | 
    
         | 
| 
       98 
111 
     | 
    
         
             
                      problem = e.is_a?(ActiveRecord::Deadlocked) ? "Deadlock detected." : "Lock timeout."
         
     | 
| 
       99 
112 
     | 
    
         
             
                      Utils.say("#{problem} Retrying in #{current_delay} seconds...")
         
     | 
| 
         @@ -130,13 +143,6 @@ module OnlineMigrations 
     | 
|
| 
       130 
143 
     | 
    
         
             
              #   config.retrier = OnlineMigrations::ConstantLockRetrier.new(attempts: 5, delay: 2.seconds, lock_timeout: 0.05.seconds)
         
     | 
| 
       131 
144 
     | 
    
         
             
              #
         
     | 
| 
       132 
145 
     | 
    
         
             
              class ConstantLockRetrier < LockRetrier
         
     | 
| 
       133 
     | 
    
         
            -
                # LockRetrier API implementation
         
     | 
| 
       134 
     | 
    
         
            -
                #
         
     | 
| 
       135 
     | 
    
         
            -
                # @return [Integer] Number of retrying attempts
         
     | 
| 
       136 
     | 
    
         
            -
                # @see LockRetrier#attempts
         
     | 
| 
       137 
     | 
    
         
            -
                #
         
     | 
| 
       138 
     | 
    
         
            -
                attr_reader :attempts
         
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
       140 
146 
     | 
    
         
             
                # Create a new ConstantLockRetrier instance
         
     | 
| 
       141 
147 
     | 
    
         
             
                #
         
     | 
| 
       142 
148 
     | 
    
         
             
                # @param attempts [Integer] Maximum number of attempts
         
     | 
| 
         @@ -150,12 +156,21 @@ module OnlineMigrations 
     | 
|
| 
       150 
156 
     | 
    
         
             
                  @lock_timeout = lock_timeout
         
     | 
| 
       151 
157 
     | 
    
         
             
                end
         
     | 
| 
       152 
158 
     | 
    
         | 
| 
      
 159 
     | 
    
         
            +
                # LockRetrier API implementation
         
     | 
| 
      
 160 
     | 
    
         
            +
                #
         
     | 
| 
      
 161 
     | 
    
         
            +
                # @return [Integer] Number of retrying attempts
         
     | 
| 
      
 162 
     | 
    
         
            +
                # @see LockRetrier#attempts
         
     | 
| 
      
 163 
     | 
    
         
            +
                #
         
     | 
| 
      
 164 
     | 
    
         
            +
                def attempts(_command = nil, _arguments = [])
         
     | 
| 
      
 165 
     | 
    
         
            +
                  @attempts
         
     | 
| 
      
 166 
     | 
    
         
            +
                end
         
     | 
| 
      
 167 
     | 
    
         
            +
             
     | 
| 
       153 
168 
     | 
    
         
             
                # LockRetrier API implementation
         
     | 
| 
       154 
169 
     | 
    
         
             
                #
         
     | 
| 
       155 
170 
     | 
    
         
             
                # @return [Numeric] Database lock timeout value (in seconds)
         
     | 
| 
       156 
171 
     | 
    
         
             
                # @see LockRetrier#lock_timeout
         
     | 
| 
       157 
172 
     | 
    
         
             
                #
         
     | 
| 
       158 
     | 
    
         
            -
                def lock_timeout(_attempt)
         
     | 
| 
      
 173 
     | 
    
         
            +
                def lock_timeout(_attempt, _command = nil, _arguments = [])
         
     | 
| 
       159 
174 
     | 
    
         
             
                  @lock_timeout
         
     | 
| 
       160 
175 
     | 
    
         
             
                end
         
     | 
| 
       161 
176 
     | 
    
         | 
| 
         @@ -164,7 +179,7 @@ module OnlineMigrations 
     | 
|
| 
       164 
179 
     | 
    
         
             
                # @return [Numeric] Sleep time after unsuccessful lock attempt (in seconds)
         
     | 
| 
       165 
180 
     | 
    
         
             
                # @see LockRetrier#delay
         
     | 
| 
       166 
181 
     | 
    
         
             
                #
         
     | 
| 
       167 
     | 
    
         
            -
                def delay(_attempt)
         
     | 
| 
      
 182 
     | 
    
         
            +
                def delay(_attempt, _command = nil, _arguments = [])
         
     | 
| 
       168 
183 
     | 
    
         
             
                  @delay
         
     | 
| 
       169 
184 
     | 
    
         
             
                end
         
     | 
| 
       170 
185 
     | 
    
         
             
              end
         
     | 
| 
         @@ -180,13 +195,6 @@ module OnlineMigrations 
     | 
|
| 
       180 
195 
     | 
    
         
             
              #       base_delay: 0.01.seconds, max_delay: 1.minute, lock_timeout: 0.2.seconds)
         
     | 
| 
       181 
196 
     | 
    
         
             
              #
         
     | 
| 
       182 
197 
     | 
    
         
             
              class ExponentialLockRetrier < LockRetrier
         
     | 
| 
       183 
     | 
    
         
            -
                # LockRetrier API implementation
         
     | 
| 
       184 
     | 
    
         
            -
                #
         
     | 
| 
       185 
     | 
    
         
            -
                # @return [Integer] Number of retrying attempts
         
     | 
| 
       186 
     | 
    
         
            -
                # @see LockRetrier#attempts
         
     | 
| 
       187 
     | 
    
         
            -
                #
         
     | 
| 
       188 
     | 
    
         
            -
                attr_reader :attempts
         
     | 
| 
       189 
     | 
    
         
            -
             
     | 
| 
       190 
198 
     | 
    
         
             
                # Create a new ExponentialLockRetrier instance
         
     | 
| 
       191 
199 
     | 
    
         
             
                #
         
     | 
| 
       192 
200 
     | 
    
         
             
                # @param attempts [Integer] Maximum number of attempts
         
     | 
| 
         @@ -202,12 +210,21 @@ module OnlineMigrations 
     | 
|
| 
       202 
210 
     | 
    
         
             
                  @lock_timeout = lock_timeout
         
     | 
| 
       203 
211 
     | 
    
         
             
                end
         
     | 
| 
       204 
212 
     | 
    
         | 
| 
      
 213 
     | 
    
         
            +
                # LockRetrier API implementation
         
     | 
| 
      
 214 
     | 
    
         
            +
                #
         
     | 
| 
      
 215 
     | 
    
         
            +
                # @return [Integer] Number of retrying attempts
         
     | 
| 
      
 216 
     | 
    
         
            +
                # @see LockRetrier#attempts
         
     | 
| 
      
 217 
     | 
    
         
            +
                #
         
     | 
| 
      
 218 
     | 
    
         
            +
                def attempts(_command = nil, _arguments = [])
         
     | 
| 
      
 219 
     | 
    
         
            +
                  @attempts
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
       205 
222 
     | 
    
         
             
                # LockRetrier API implementation
         
     | 
| 
       206 
223 
     | 
    
         
             
                #
         
     | 
| 
       207 
224 
     | 
    
         
             
                # @return [Numeric] Database lock timeout value (in seconds)
         
     | 
| 
       208 
225 
     | 
    
         
             
                # @see LockRetrier#lock_timeout
         
     | 
| 
       209 
226 
     | 
    
         
             
                #
         
     | 
| 
       210 
     | 
    
         
            -
                def lock_timeout(_attempt)
         
     | 
| 
      
 227 
     | 
    
         
            +
                def lock_timeout(_attempt, _command = nil, _arguments = [])
         
     | 
| 
       211 
228 
     | 
    
         
             
                  @lock_timeout
         
     | 
| 
       212 
229 
     | 
    
         
             
                end
         
     | 
| 
       213 
230 
     | 
    
         | 
| 
         @@ -217,21 +234,21 @@ module OnlineMigrations 
     | 
|
| 
       217 
234 
     | 
    
         
             
                # @see LockRetrier#delay
         
     | 
| 
       218 
235 
     | 
    
         
             
                # @see https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
         
     | 
| 
       219 
236 
     | 
    
         
             
                #
         
     | 
| 
       220 
     | 
    
         
            -
                def delay(attempt)
         
     | 
| 
      
 237 
     | 
    
         
            +
                def delay(attempt, _command = nil, _arguments = [])
         
     | 
| 
       221 
238 
     | 
    
         
             
                  (rand * [@max_delay, @base_delay * (2**(attempt - 1))].min).ceil
         
     | 
| 
       222 
239 
     | 
    
         
             
                end
         
     | 
| 
       223 
240 
     | 
    
         
             
              end
         
     | 
| 
       224 
241 
     | 
    
         | 
| 
       225 
242 
     | 
    
         
             
              # @private
         
     | 
| 
       226 
243 
     | 
    
         
             
              class NullLockRetrier < LockRetrier
         
     | 
| 
       227 
     | 
    
         
            -
                def attempts( 
     | 
| 
      
 244 
     | 
    
         
            +
                def attempts(_command = nil, _arguments = [])
         
     | 
| 
       228 
245 
     | 
    
         
             
                  0
         
     | 
| 
       229 
246 
     | 
    
         
             
                end
         
     | 
| 
       230 
247 
     | 
    
         | 
| 
       231 
248 
     | 
    
         
             
                def lock_timeout(*)
         
     | 
| 
       232 
249 
     | 
    
         
             
                end
         
     | 
| 
       233 
250 
     | 
    
         | 
| 
       234 
     | 
    
         
            -
                def delay( 
     | 
| 
      
 251 
     | 
    
         
            +
                def delay(_attempt, _command = nil, _arguments = [])
         
     | 
| 
       235 
252 
     | 
    
         
             
                end
         
     | 
| 
       236 
253 
     | 
    
         | 
| 
       237 
254 
     | 
    
         
             
                def with_lock_retries(_connection)
         
     | 
| 
         @@ -23,9 +23,9 @@ module OnlineMigrations 
     | 
|
| 
       23 
23 
     | 
    
         
             
                    if in_transaction?
         
     | 
| 
       24 
24 
     | 
    
         
             
                      super
         
     | 
| 
       25 
25 
     | 
    
         
             
                    elsif method == :with_lock_retries
         
     | 
| 
       26 
     | 
    
         
            -
                      connection.with_lock_retries(*args, &block)
         
     | 
| 
      
 26 
     | 
    
         
            +
                      connection.with_lock_retries(method, *args, &block)
         
     | 
| 
       27 
27 
     | 
    
         
             
                    else
         
     | 
| 
       28 
     | 
    
         
            -
                      connection.with_lock_retries { super }
         
     | 
| 
      
 28 
     | 
    
         
            +
                      connection.with_lock_retries(method, *args) { super }
         
     | 
| 
       29 
29 
     | 
    
         
             
                    end
         
     | 
| 
       30 
30 
     | 
    
         
             
                  end
         
     | 
| 
       31 
31 
     | 
    
         
             
                end
         
     | 
| 
         @@ -12,6 +12,11 @@ module OnlineMigrations 
     | 
|
| 
       12 
12 
     | 
    
         
             
                    end
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
14 
     | 
    
         
             
                  if use_transaction?(migration)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    # Wrap the entire transaction with lock retries so that if the transaction
         
     | 
| 
      
 16 
     | 
    
         
            +
                    # fails to acquire any locks, the whole migration is retried.
         
     | 
| 
      
 17 
     | 
    
         
            +
                    # Note: at this point we don't have visibility into individual DDL commands,
         
     | 
| 
      
 18 
     | 
    
         
            +
                    # so command and arguments will be nil when lock_timeout is called.
         
     | 
| 
      
 19 
     | 
    
         
            +
                    # For command-specific retry behavior, migrations must use `disable_ddl_transaction!`
         
     | 
| 
       15 
20 
     | 
    
         
             
                    migration.connection.with_lock_retries do
         
     | 
| 
       16 
21 
     | 
    
         
             
                      super
         
     | 
| 
       17 
22 
     | 
    
         
             
                    end
         
     | 
| 
         @@ -801,7 +801,7 @@ module OnlineMigrations 
     | 
|
| 
       801 
801 
     | 
    
         
             
                #
         
     | 
| 
       802 
802 
     | 
    
         
             
                # @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_check_constraint
         
     | 
| 
       803 
803 
     | 
    
         
             
                #
         
     | 
| 
       804 
     | 
    
         
            -
                def add_check_constraint(table_name, expression, **options)
         
     | 
| 
      
 804 
     | 
    
         
            +
                def add_check_constraint(table_name, expression, if_not_exists: false, **options)
         
     | 
| 
       805 
805 
     | 
    
         
             
                  if check_constraint_exists?(table_name, expression: expression, **options)
         
     | 
| 
       806 
806 
     | 
    
         
             
                    Utils.say(<<~MSG.squish)
         
     | 
| 
       807 
807 
     | 
    
         
             
                      Check constraint was not created because it already exists (this may be due to an aborted migration or similar).
         
     | 
| 
         @@ -816,7 +816,7 @@ module OnlineMigrations 
     | 
|
| 
       816 
816 
     | 
    
         
             
                #
         
     | 
| 
       817 
817 
     | 
    
         
             
                # @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_check_constraint
         
     | 
| 
       818 
818 
     | 
    
         
             
                #
         
     | 
| 
       819 
     | 
    
         
            -
                def remove_check_constraint(table_name, expression = nil, **options)
         
     | 
| 
      
 819 
     | 
    
         
            +
                def remove_check_constraint(table_name, expression = nil, if_exists: false, **options)
         
     | 
| 
       820 
820 
     | 
    
         
             
                  if check_constraint_exists?(table_name, expression: expression, **options)
         
     | 
| 
       821 
821 
     | 
    
         
             
                    super
         
     | 
| 
       822 
822 
     | 
    
         
             
                  else
         
     | 
| 
         @@ -862,11 +862,11 @@ module OnlineMigrations 
     | 
|
| 
       862 
862 
     | 
    
         
             
                # Executes the block with a retry mechanism that alters the `lock_timeout`
         
     | 
| 
       863 
863 
     | 
    
         
             
                # and sleep time between attempts.
         
     | 
| 
       864 
864 
     | 
    
         
             
                #
         
     | 
| 
       865 
     | 
    
         
            -
                def with_lock_retries(&block)
         
     | 
| 
      
 865 
     | 
    
         
            +
                def with_lock_retries(command = nil, *args, &block)
         
     | 
| 
       866 
866 
     | 
    
         
             
                  __ensure_not_in_transaction!
         
     | 
| 
       867 
867 
     | 
    
         | 
| 
       868 
868 
     | 
    
         
             
                  retrier = OnlineMigrations.config.lock_retrier
         
     | 
| 
       869 
     | 
    
         
            -
                  retrier.with_lock_retries(self, &block)
         
     | 
| 
      
 869 
     | 
    
         
            +
                  retrier.with_lock_retries(self, command, *args, &block)
         
     | 
| 
       870 
870 
     | 
    
         
             
                end
         
     | 
| 
       871 
871 
     | 
    
         | 
| 
       872 
872 
     | 
    
         
             
                private
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,13 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: online_migrations
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.30.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - fatkodima
         
     | 
| 
       8 
     | 
    
         
            -
            autorequire:
         
     | 
| 
       9 
8 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
9 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2025- 
     | 
| 
      
 10 
     | 
    
         
            +
            date: 2025-10-17 00:00:00.000000000 Z
         
     | 
| 
       12 
11 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
12 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
13 
     | 
    
         
             
              name: activerecord
         
     | 
| 
         @@ -24,7 +23,6 @@ dependencies: 
     | 
|
| 
       24 
23 
     | 
    
         
             
                - - ">="
         
     | 
| 
       25 
24 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       26 
25 
     | 
    
         
             
                    version: '7.1'
         
     | 
| 
       27 
     | 
    
         
            -
            description:
         
     | 
| 
       28 
26 
     | 
    
         
             
            email:
         
     | 
| 
       29 
27 
     | 
    
         
             
            - fatkodima123@gmail.com
         
     | 
| 
       30 
28 
     | 
    
         
             
            executables: []
         
     | 
| 
         @@ -101,7 +99,6 @@ metadata: 
     | 
|
| 
       101 
99 
     | 
    
         
             
              homepage_uri: https://github.com/fatkodima/online_migrations
         
     | 
| 
       102 
100 
     | 
    
         
             
              source_code_uri: https://github.com/fatkodima/online_migrations
         
     | 
| 
       103 
101 
     | 
    
         
             
              changelog_uri: https://github.com/fatkodima/online_migrations/blob/master/CHANGELOG.md
         
     | 
| 
       104 
     | 
    
         
            -
            post_install_message:
         
     | 
| 
       105 
102 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       106 
103 
     | 
    
         
             
            require_paths:
         
     | 
| 
       107 
104 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -116,8 +113,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       116 
113 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       117 
114 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       118 
115 
     | 
    
         
             
            requirements: []
         
     | 
| 
       119 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       120 
     | 
    
         
            -
            signing_key:
         
     | 
| 
      
 116 
     | 
    
         
            +
            rubygems_version: 3.6.2
         
     | 
| 
       121 
117 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       122 
118 
     | 
    
         
             
            summary: Catch unsafe PostgreSQL migrations in development and run them easier in
         
     | 
| 
       123 
119 
     | 
    
         
             
              production
         
     |