online_migrations 0.19.3 → 0.19.4
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 +13 -0
- data/README.md +64 -64
- data/docs/background_data_migrations.md +1 -1
- data/docs/configuring.md +1 -1
- data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +9 -0
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +2 -1
- data/lib/generators/online_migrations/upgrade_generator.rb +6 -0
- data/lib/online_migrations/background_schema_migrations/migration.rb +13 -3
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +19 -3
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +7 -0
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +3 -2
- data/lib/online_migrations/schema_statements.rb +24 -7
- data/lib/online_migrations/version.rb +1 -1
- metadata +3 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6392ff9889da502701252f97a71e4fa8c3b403dc61aa010957a5b79221a9366b
         | 
| 4 | 
            +
              data.tar.gz: 1c880f23e20b142c335679701bdac977f089ac5ef076352a438e6dfa469b54cb
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 30f7c9ed855f97180ba1b4f9c90f717f58ad9198fbd90f1b80b914b35994a2b863499239263878115d220413927ca3f5a05519e917b66335a346607b19a8f2ab
         | 
| 7 | 
            +
              data.tar.gz: 271a237cb9df14677ed5c364c6c90657a6726347ae94b6992ad37c468b0bf59a76e51c0a1b79f187264d2369637e6c6de83d454d6f8c1c368648640c5ee23068
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -1,5 +1,18 @@ | |
| 1 1 | 
             
            ## master (unreleased)
         | 
| 2 2 |  | 
| 3 | 
            +
            ## 0.19.4 (2024-09-02)
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            - Fix an edge case for background schema migrations
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                If you use background schema migrations, you need to run
         | 
| 8 | 
            +
                ```
         | 
| 9 | 
            +
                bin/rails generate online_migrations:upgrade
         | 
| 10 | 
            +
                bin/rails db:migrate
         | 
| 11 | 
            +
                ```
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            - Fix retrying running stuck background schema migrations
         | 
| 14 | 
            +
            - Fix renaming columns for tables with long names
         | 
| 15 | 
            +
             | 
| 3 16 | 
             
            ## 0.19.3 (2024-08-09)
         | 
| 4 17 |  | 
| 5 18 | 
             
            - Fix idempotency for `add_index`/`remove_index` for expression indexes
         | 
    
        data/README.md
    CHANGED
    
    | @@ -67,7 +67,7 @@ An operation is classified as dangerous if it either: | |
| 67 67 | 
             
            Consider the following migration:
         | 
| 68 68 |  | 
| 69 69 | 
             
            ```ruby
         | 
| 70 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 70 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 71 71 | 
             
              def change
         | 
| 72 72 | 
             
                add_column :users, :admin, :boolean, default: false, null: false
         | 
| 73 73 | 
             
              end
         | 
| @@ -79,7 +79,7 @@ If the `users` table is large, running this migration on a live PostgreSQL < 11 | |
| 79 79 | 
             
            A safer approach would be to run something like the following:
         | 
| 80 80 |  | 
| 81 81 | 
             
            ```ruby
         | 
| 82 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 82 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 83 83 | 
             
              # Do not wrap the migration in a transaction so that locks are held for a shorter time.
         | 
| 84 84 | 
             
              disable_ddl_transaction!
         | 
| 85 85 |  | 
| @@ -124,7 +124,7 @@ A safer approach is to: | |
| 124 124 |  | 
| 125 125 | 
             
            add_column_with_default takes care of all this steps:
         | 
| 126 126 |  | 
| 127 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 127 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 128 128 | 
             
              disable_ddl_transaction!
         | 
| 129 129 |  | 
| 130 130 | 
             
              def change
         | 
| @@ -178,7 +178,7 @@ You can also add [custom checks](docs/configuring.md#custom-checks) or [disable | |
| 178 178 | 
             
            Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
         | 
| 179 179 |  | 
| 180 180 | 
             
            ```ruby
         | 
| 181 | 
            -
            class RemoveNameFromUsers < ActiveRecord::Migration[7. | 
| 181 | 
            +
            class RemoveNameFromUsers < ActiveRecord::Migration[7.2]
         | 
| 182 182 | 
             
              def change
         | 
| 183 183 | 
             
                remove_column :users, :name
         | 
| 184 184 | 
             
              end
         | 
| @@ -199,7 +199,7 @@ end | |
| 199 199 | 
             
            3. Wrap column removing in a `safety_assured` block:
         | 
| 200 200 |  | 
| 201 201 | 
             
               ```ruby
         | 
| 202 | 
            -
               class RemoveNameFromUsers < ActiveRecord::Migration[7. | 
| 202 | 
            +
               class RemoveNameFromUsers < ActiveRecord::Migration[7.2]
         | 
| 203 203 | 
             
                 def change
         | 
| 204 204 | 
             
                   safety_assured { remove_column :users, :name }
         | 
| 205 205 | 
             
                 end
         | 
| @@ -216,7 +216,7 @@ end | |
| 216 216 | 
             
            In earlier versions of PostgreSQL adding a column with a non-null default value to an existing table blocks reads and writes while the entire table is rewritten.
         | 
| 217 217 |  | 
| 218 218 | 
             
            ```ruby
         | 
| 219 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 219 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 220 220 | 
             
              def change
         | 
| 221 221 | 
             
                add_column :users, :admin, :boolean, default: false
         | 
| 222 222 | 
             
              end
         | 
| @@ -236,7 +236,7 @@ A safer approach is to: | |
| 236 236 | 
             
            `add_column_with_default` helper takes care of all this steps:
         | 
| 237 237 |  | 
| 238 238 | 
             
            ```ruby
         | 
| 239 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 239 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 240 240 | 
             
              disable_ddl_transaction!
         | 
| 241 241 |  | 
| 242 242 | 
             
              def change
         | 
| @@ -254,7 +254,7 @@ end | |
| 254 254 | 
             
            Active Record wraps each migration in a transaction, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
         | 
| 255 255 |  | 
| 256 256 | 
             
            ```ruby
         | 
| 257 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 257 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 258 258 | 
             
              def change
         | 
| 259 259 | 
             
                add_column :users, :admin, :boolean
         | 
| 260 260 | 
             
                User.update_all(admin: false)
         | 
| @@ -269,13 +269,13 @@ Also, running a single query to update data can cause issues for large tables. | |
| 269 269 | 
             
            There are three keys to backfilling safely: batching, throttling, and running it outside a transaction. Use a `update_column_in_batches` helper in a separate migration with `disable_ddl_transaction!`.
         | 
| 270 270 |  | 
| 271 271 | 
             
            ```ruby
         | 
| 272 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 272 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 273 273 | 
             
              def change
         | 
| 274 274 | 
             
                add_column :users, :admin, :boolean
         | 
| 275 275 | 
             
              end
         | 
| 276 276 | 
             
            end
         | 
| 277 277 |  | 
| 278 | 
            -
            class BackfillUsersAdminColumn < ActiveRecord::Migration[7. | 
| 278 | 
            +
            class BackfillUsersAdminColumn < ActiveRecord::Migration[7.2]
         | 
| 279 279 | 
             
              disable_ddl_transaction!
         | 
| 280 280 |  | 
| 281 281 | 
             
              def up
         | 
| @@ -294,7 +294,7 @@ end | |
| 294 294 | 
             
            Changing the type of an existing column blocks reads and writes while the entire table is rewritten.
         | 
| 295 295 |  | 
| 296 296 | 
             
            ```ruby
         | 
| 297 | 
            -
            class ChangeFilesSizeType < ActiveRecord::Migration[7. | 
| 297 | 
            +
            class ChangeFilesSizeType < ActiveRecord::Migration[7.2]
         | 
| 298 298 | 
             
              def change
         | 
| 299 299 | 
             
                change_column :files, :size, :bigint
         | 
| 300 300 | 
             
              end
         | 
| @@ -338,7 +338,7 @@ A safer approach can be accomplished in several steps: | |
| 338 338 | 
             
            1. Create a new column and keep column's data in sync:
         | 
| 339 339 |  | 
| 340 340 | 
             
               ```ruby
         | 
| 341 | 
            -
               class InitializeChangeFilesSizeType < ActiveRecord::Migration[7. | 
| 341 | 
            +
               class InitializeChangeFilesSizeType < ActiveRecord::Migration[7.2]
         | 
| 342 342 | 
             
                 def change
         | 
| 343 343 | 
             
                   initialize_column_type_change :files, :size, :bigint
         | 
| 344 344 | 
             
                 end
         | 
| @@ -351,7 +351,7 @@ A safer approach can be accomplished in several steps: | |
| 351 351 | 
             
            2. Backfill data from the old column to the new column:
         | 
| 352 352 |  | 
| 353 353 | 
             
               ```ruby
         | 
| 354 | 
            -
               class BackfillChangeFilesSizeType < ActiveRecord::Migration[7. | 
| 354 | 
            +
               class BackfillChangeFilesSizeType < ActiveRecord::Migration[7.2]
         | 
| 355 355 | 
             
                 disable_ddl_transaction!
         | 
| 356 356 |  | 
| 357 357 | 
             
                 def up
         | 
| @@ -372,7 +372,7 @@ during writes works automatically). For most column type changes, this does not | |
| 372 372 | 
             
            5. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
         | 
| 373 373 |  | 
| 374 374 | 
             
               ```ruby
         | 
| 375 | 
            -
               class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7. | 
| 375 | 
            +
               class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7.2]
         | 
| 376 376 | 
             
                 disable_ddl_transaction!
         | 
| 377 377 |  | 
| 378 378 | 
             
                 def change
         | 
| @@ -385,7 +385,7 @@ during writes works automatically). For most column type changes, this does not | |
| 385 385 | 
             
            7. Finally, if everything works as expected, remove copy trigger and old column:
         | 
| 386 386 |  | 
| 387 387 | 
             
               ```ruby
         | 
| 388 | 
            -
               class CleanupChangeFilesSizeType < ActiveRecord::Migration[7. | 
| 388 | 
            +
               class CleanupChangeFilesSizeType < ActiveRecord::Migration[7.2]
         | 
| 389 389 | 
             
                 def up
         | 
| 390 390 | 
             
                   cleanup_column_type_change :files, :size
         | 
| 391 391 | 
             
                 end
         | 
| @@ -406,7 +406,7 @@ during writes works automatically). For most column type changes, this does not | |
| 406 406 | 
             
            Renaming a column that's in use will cause errors in your application.
         | 
| 407 407 |  | 
| 408 408 | 
             
            ```ruby
         | 
| 409 | 
            -
            class RenameUsersNameToFirstName < ActiveRecord::Migration[7. | 
| 409 | 
            +
            class RenameUsersNameToFirstName < ActiveRecord::Migration[7.2]
         | 
| 410 410 | 
             
              def change
         | 
| 411 411 | 
             
                rename_column :users, :name, :first_name
         | 
| 412 412 | 
             
              end
         | 
| @@ -477,7 +477,7 @@ nor any data/indexes/foreign keys copying will be made, so will be instantaneous | |
| 477 477 | 
             
            It will use a combination of a VIEW and column aliasing to work with both column names simultaneously
         | 
| 478 478 |  | 
| 479 479 | 
             
               ```ruby
         | 
| 480 | 
            -
               class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7. | 
| 480 | 
            +
               class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.2]
         | 
| 481 481 | 
             
                 def change
         | 
| 482 482 | 
             
                   initialize_column_rename :users, :name, :first_name
         | 
| 483 483 | 
             
                 end
         | 
| @@ -500,7 +500,7 @@ It will use a combination of a VIEW and column aliasing to work with both column | |
| 500 500 | 
             
            9. Remove the VIEW created in step 3 and finally rename the column:
         | 
| 501 501 |  | 
| 502 502 | 
             
               ```ruby
         | 
| 503 | 
            -
               class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7. | 
| 503 | 
            +
               class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.2]
         | 
| 504 504 | 
             
                 def change
         | 
| 505 505 | 
             
                   finalize_column_rename :users, :name, :first_name
         | 
| 506 506 | 
             
                 end
         | 
| @@ -516,7 +516,7 @@ It will use a combination of a VIEW and column aliasing to work with both column | |
| 516 516 | 
             
            Renaming a table that's in use will cause errors in your application.
         | 
| 517 517 |  | 
| 518 518 | 
             
            ```ruby
         | 
| 519 | 
            -
            class RenameClientsToUsers < ActiveRecord::Migration[7. | 
| 519 | 
            +
            class RenameClientsToUsers < ActiveRecord::Migration[7.2]
         | 
| 520 520 | 
             
              def change
         | 
| 521 521 | 
             
                rename_table :clients, :users
         | 
| 522 522 | 
             
              end
         | 
| @@ -571,7 +571,7 @@ To work around this limitation, we need to tell Active Record to acquire this in | |
| 571 571 | 
             
            3. Create a VIEW:
         | 
| 572 572 |  | 
| 573 573 | 
             
               ```ruby
         | 
| 574 | 
            -
               class InitializeRenameClientsToUsers < ActiveRecord::Migration[7. | 
| 574 | 
            +
               class InitializeRenameClientsToUsers < ActiveRecord::Migration[7.2]
         | 
| 575 575 | 
             
                 def change
         | 
| 576 576 | 
             
                   initialize_table_rename :clients, :users
         | 
| 577 577 | 
             
                 end
         | 
| @@ -584,7 +584,7 @@ To work around this limitation, we need to tell Active Record to acquire this in | |
| 584 584 | 
             
            7. Remove the VIEW created in step 3:
         | 
| 585 585 |  | 
| 586 586 | 
             
               ```ruby
         | 
| 587 | 
            -
               class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7. | 
| 587 | 
            +
               class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7.2]
         | 
| 588 588 | 
             
                 def change
         | 
| 589 589 | 
             
                   finalize_table_rename :clients, :users
         | 
| 590 590 | 
             
                 end
         | 
| @@ -600,7 +600,7 @@ To work around this limitation, we need to tell Active Record to acquire this in | |
| 600 600 | 
             
            The `force` option can drop an existing table.
         | 
| 601 601 |  | 
| 602 602 | 
             
            ```ruby
         | 
| 603 | 
            -
            class CreateUsers < ActiveRecord::Migration[7. | 
| 603 | 
            +
            class CreateUsers < ActiveRecord::Migration[7.2]
         | 
| 604 604 | 
             
              def change
         | 
| 605 605 | 
             
                create_table :users, force: true do |t|
         | 
| 606 606 | 
             
                  # ...
         | 
| @@ -614,7 +614,7 @@ end | |
| 614 614 | 
             
            Create tables without the `force` option.
         | 
| 615 615 |  | 
| 616 616 | 
             
            ```ruby
         | 
| 617 | 
            -
            class CreateUsers < ActiveRecord::Migration[7. | 
| 617 | 
            +
            class CreateUsers < ActiveRecord::Migration[7.2]
         | 
| 618 618 | 
             
              def change
         | 
| 619 619 | 
             
                create_table :users do |t|
         | 
| 620 620 | 
             
                  # ...
         | 
| @@ -632,7 +632,7 @@ If you intend to drop an existing table, run `drop_table` first. | |
| 632 632 | 
             
            Adding a check constraint blocks reads and writes while every row is checked.
         | 
| 633 633 |  | 
| 634 634 | 
             
            ```ruby
         | 
| 635 | 
            -
            class AddCheckConstraint < ActiveRecord::Migration[7. | 
| 635 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[7.2]
         | 
| 636 636 | 
             
              def change
         | 
| 637 637 | 
             
                add_check_constraint :users, "char_length(name) >= 1", name: "name_check"
         | 
| 638 638 | 
             
              end
         | 
| @@ -644,7 +644,7 @@ end | |
| 644 644 | 
             
            Add the check constraint without validating existing rows, and then validate them in a separate transaction:
         | 
| 645 645 |  | 
| 646 646 | 
             
            ```ruby
         | 
| 647 | 
            -
            class AddCheckConstraint < ActiveRecord::Migration[7. | 
| 647 | 
            +
            class AddCheckConstraint < ActiveRecord::Migration[7.2]
         | 
| 648 648 | 
             
              disable_ddl_transaction!
         | 
| 649 649 |  | 
| 650 650 | 
             
              def change
         | 
| @@ -663,7 +663,7 @@ end | |
| 663 663 | 
             
            Setting `NOT NULL` on an existing column blocks reads and writes while every row is checked.
         | 
| 664 664 |  | 
| 665 665 | 
             
            ```ruby
         | 
| 666 | 
            -
            class ChangeUsersNameNull < ActiveRecord::Migration[7. | 
| 666 | 
            +
            class ChangeUsersNameNull < ActiveRecord::Migration[7.2]
         | 
| 667 667 | 
             
              def change
         | 
| 668 668 | 
             
                change_column_null :users, :name, false
         | 
| 669 669 | 
             
              end
         | 
| @@ -675,7 +675,7 @@ end | |
| 675 675 | 
             
            Instead, add a check constraint and validate it in a separate transaction:
         | 
| 676 676 |  | 
| 677 677 | 
             
            ```ruby
         | 
| 678 | 
            -
            class ChangeUsersNameNull < ActiveRecord::Migration[7. | 
| 678 | 
            +
            class ChangeUsersNameNull < ActiveRecord::Migration[7.2]
         | 
| 679 679 | 
             
              disable_ddl_transaction!
         | 
| 680 680 |  | 
| 681 681 | 
             
              def change
         | 
| @@ -690,7 +690,7 @@ end | |
| 690 690 | 
             
            A `NOT NULL` check constraint is functionally equivalent to setting `NOT NULL` on the column (but it won't show up in `schema.rb` in Rails < 6.1). In PostgreSQL 12+, once the check constraint is validated, you can safely set `NOT NULL` on the column and drop the check constraint.
         | 
| 691 691 |  | 
| 692 692 | 
             
            ```ruby
         | 
| 693 | 
            -
            class ChangeUsersNameNullDropCheck < ActiveRecord::Migration[7. | 
| 693 | 
            +
            class ChangeUsersNameNullDropCheck < ActiveRecord::Migration[7.2]
         | 
| 694 694 | 
             
              def change
         | 
| 695 695 | 
             
                # in PostgreSQL 12+, you can then safely set NOT NULL on the column
         | 
| 696 696 | 
             
                change_column_null :users, :name, false
         | 
| @@ -704,7 +704,7 @@ end | |
| 704 704 | 
             
            Online Migrations does not support inspecting what happens inside an `execute` call, so cannot help you here. Make really sure that what you're doing is safe before proceeding, then wrap it in a `safety_assured { ... }` block:
         | 
| 705 705 |  | 
| 706 706 | 
             
            ```ruby
         | 
| 707 | 
            -
            class ExecuteSQL < ActiveRecord::Migration[7. | 
| 707 | 
            +
            class ExecuteSQL < ActiveRecord::Migration[7.2]
         | 
| 708 708 | 
             
              def change
         | 
| 709 709 | 
             
                safety_assured { execute "..." }
         | 
| 710 710 | 
             
              end
         | 
| @@ -718,7 +718,7 @@ end | |
| 718 718 | 
             
            Adding an index non-concurrently blocks writes.
         | 
| 719 719 |  | 
| 720 720 | 
             
            ```ruby
         | 
| 721 | 
            -
            class AddIndexOnUsersEmail < ActiveRecord::Migration[7. | 
| 721 | 
            +
            class AddIndexOnUsersEmail < ActiveRecord::Migration[7.2]
         | 
| 722 722 | 
             
              def change
         | 
| 723 723 | 
             
                add_index :users, :email, unique: true
         | 
| 724 724 | 
             
              end
         | 
| @@ -730,7 +730,7 @@ end | |
| 730 730 | 
             
            Add indexes concurrently.
         | 
| 731 731 |  | 
| 732 732 | 
             
            ```ruby
         | 
| 733 | 
            -
            class AddIndexOnUsersEmail < ActiveRecord::Migration[7. | 
| 733 | 
            +
            class AddIndexOnUsersEmail < ActiveRecord::Migration[7.2]
         | 
| 734 734 | 
             
              disable_ddl_transaction!
         | 
| 735 735 |  | 
| 736 736 | 
             
              def change
         | 
| @@ -748,7 +748,7 @@ end | |
| 748 748 | 
             
            While actual removing of an index is usually fast, removing it non-concurrently tries to obtain an `ACCESS EXCLUSIVE` lock on the table, waiting for all existing queries to complete and blocking all the subsequent queries (even `SELECT`s) on that table until the lock is obtained and index is removed.
         | 
| 749 749 |  | 
| 750 750 | 
             
            ```ruby
         | 
| 751 | 
            -
            class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7. | 
| 751 | 
            +
            class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.2]
         | 
| 752 752 | 
             
              def change
         | 
| 753 753 | 
             
                remove_index :users, :email
         | 
| 754 754 | 
             
              end
         | 
| @@ -760,7 +760,7 @@ end | |
| 760 760 | 
             
            Remove indexes concurrently.
         | 
| 761 761 |  | 
| 762 762 | 
             
            ```ruby
         | 
| 763 | 
            -
            class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7. | 
| 763 | 
            +
            class RemoveIndexOnUsersEmail < ActiveRecord::Migration[7.2]
         | 
| 764 764 | 
             
              disable_ddl_transaction!
         | 
| 765 765 |  | 
| 766 766 | 
             
              def change
         | 
| @@ -778,7 +778,7 @@ end | |
| 778 778 | 
             
            Removing an old index before replacing it with the new one might result in slow queries while building the new index.
         | 
| 779 779 |  | 
| 780 780 | 
             
            ```ruby
         | 
| 781 | 
            -
            class AddIndexOnCreationToProjects < ActiveRecord::Migration[7. | 
| 781 | 
            +
            class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.2]
         | 
| 782 782 | 
             
              disable_ddl_transaction!
         | 
| 783 783 |  | 
| 784 784 | 
             
              def change
         | 
| @@ -795,7 +795,7 @@ end | |
| 795 795 | 
             
            A safer approach is to create the new index and then delete the old one.
         | 
| 796 796 |  | 
| 797 797 | 
             
            ```ruby
         | 
| 798 | 
            -
            class AddIndexOnCreationToProjects < ActiveRecord::Migration[7. | 
| 798 | 
            +
            class AddIndexOnCreationToProjects < ActiveRecord::Migration[7.2]
         | 
| 799 799 | 
             
              disable_ddl_transaction!
         | 
| 800 800 |  | 
| 801 801 | 
             
              def change
         | 
| @@ -812,7 +812,7 @@ end | |
| 812 812 | 
             
            Rails adds an index non-concurrently to references by default, which blocks writes. Additionally, if `foreign_key` option (without `validate: false`) is provided, both tables are blocked while it is validated.
         | 
| 813 813 |  | 
| 814 814 | 
             
            ```ruby
         | 
| 815 | 
            -
            class AddUserToProjects < ActiveRecord::Migration[7. | 
| 815 | 
            +
            class AddUserToProjects < ActiveRecord::Migration[7.2]
         | 
| 816 816 | 
             
              def change
         | 
| 817 817 | 
             
                add_reference :projects, :user, foreign_key: true
         | 
| 818 818 | 
             
              end
         | 
| @@ -825,7 +825,7 @@ Make sure the index is added concurrently and the foreign key is added in a sepa | |
| 825 825 | 
             
            Or you can use `add_reference_concurrently` helper. It will create a reference and take care of safely adding index and/or foreign key.
         | 
| 826 826 |  | 
| 827 827 | 
             
            ```ruby
         | 
| 828 | 
            -
            class AddUserToProjects < ActiveRecord::Migration[7. | 
| 828 | 
            +
            class AddUserToProjects < ActiveRecord::Migration[7.2]
         | 
| 829 829 | 
             
              disable_ddl_transaction!
         | 
| 830 830 |  | 
| 831 831 | 
             
              def change
         | 
| @@ -843,7 +843,7 @@ end | |
| 843 843 | 
             
            Adding a foreign key blocks writes on both tables.
         | 
| 844 844 |  | 
| 845 845 | 
             
            ```ruby
         | 
| 846 | 
            -
            class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7. | 
| 846 | 
            +
            class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.2]
         | 
| 847 847 | 
             
              def change
         | 
| 848 848 | 
             
                add_foreign_key :projects, :users
         | 
| 849 849 | 
             
              end
         | 
| @@ -853,7 +853,7 @@ end | |
| 853 853 | 
             
            or
         | 
| 854 854 |  | 
| 855 855 | 
             
            ```ruby
         | 
| 856 | 
            -
            class AddReferenceToProjectsUser < ActiveRecord::Migration[7. | 
| 856 | 
            +
            class AddReferenceToProjectsUser < ActiveRecord::Migration[7.2]
         | 
| 857 857 | 
             
              def change
         | 
| 858 858 | 
             
                add_reference :projects, :user, foreign_key: true
         | 
| 859 859 | 
             
              end
         | 
| @@ -865,7 +865,7 @@ end | |
| 865 865 | 
             
            Add the foreign key without validating existing rows:
         | 
| 866 866 |  | 
| 867 867 | 
             
            ```ruby
         | 
| 868 | 
            -
            class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7. | 
| 868 | 
            +
            class AddForeignKeyToProjectsUser < ActiveRecord::Migration[7.2]
         | 
| 869 869 | 
             
              def change
         | 
| 870 870 | 
             
                add_foreign_key :projects, :users, validate: false
         | 
| 871 871 | 
             
              end
         | 
| @@ -875,7 +875,7 @@ end | |
| 875 875 | 
             
            Then validate them in a separate migration:
         | 
| 876 876 |  | 
| 877 877 | 
             
            ```ruby
         | 
| 878 | 
            -
            class ValidateForeignKeyOnProjectsUser < ActiveRecord::Migration[7. | 
| 878 | 
            +
            class ValidateForeignKeyOnProjectsUser < ActiveRecord::Migration[7.2]
         | 
| 879 879 | 
             
              def change
         | 
| 880 880 | 
             
                validate_foreign_key :projects, :users
         | 
| 881 881 | 
             
              end
         | 
| @@ -889,7 +889,7 @@ end | |
| 889 889 | 
             
            Adding an exclusion constraint blocks reads and writes while every row is checked.
         | 
| 890 890 |  | 
| 891 891 | 
             
            ```ruby
         | 
| 892 | 
            -
            class AddExclusionContraint < ActiveRecord::Migration[7. | 
| 892 | 
            +
            class AddExclusionContraint < ActiveRecord::Migration[7.2]
         | 
| 893 893 | 
             
              def change
         | 
| 894 894 | 
             
                add_exclusion_constraint :users, "number WITH =", using: :gist
         | 
| 895 895 | 
             
              end
         | 
| @@ -907,7 +907,7 @@ end | |
| 907 907 | 
             
            Adding a unique constraint blocks reads and writes while the underlying index is being built.
         | 
| 908 908 |  | 
| 909 909 | 
             
            ```ruby
         | 
| 910 | 
            -
            class AddUniqueConstraint < ActiveRecord::Migration[7. | 
| 910 | 
            +
            class AddUniqueConstraint < ActiveRecord::Migration[7.2]
         | 
| 911 911 | 
             
              def change
         | 
| 912 912 | 
             
                add_unique_constraint :sections, :position, deferrable: :deferred
         | 
| 913 913 | 
             
              end
         | 
| @@ -919,7 +919,7 @@ end | |
| 919 919 | 
             
            A safer approach is to create a unique index first, and then create a unique key using that index.
         | 
| 920 920 |  | 
| 921 921 | 
             
            ```ruby
         | 
| 922 | 
            -
            class AddUniqueConstraintAddIndex < ActiveRecord::Migration[7. | 
| 922 | 
            +
            class AddUniqueConstraintAddIndex < ActiveRecord::Migration[7.2]
         | 
| 923 923 | 
             
              disable_ddl_transaction!
         | 
| 924 924 |  | 
| 925 925 | 
             
              def change
         | 
| @@ -929,7 +929,7 @@ end | |
| 929 929 | 
             
            ```
         | 
| 930 930 |  | 
| 931 931 | 
             
            ```ruby
         | 
| 932 | 
            -
            class AddUniqueConstraint < ActiveRecord::Migration[7. | 
| 932 | 
            +
            class AddUniqueConstraint < ActiveRecord::Migration[7.2]
         | 
| 933 933 | 
             
              def up
         | 
| 934 934 | 
             
                add_unique_constraint :sections, :position, deferrable: :deferred, using_index: "index_sections_on_position"
         | 
| 935 935 | 
             
              end
         | 
| @@ -947,7 +947,7 @@ end | |
| 947 947 | 
             
            There's no equality operator for the `json` column type, which can cause errors for existing `SELECT DISTINCT` queries in your application.
         | 
| 948 948 |  | 
| 949 949 | 
             
            ```ruby
         | 
| 950 | 
            -
            class AddSettingsToProjects < ActiveRecord::Migration[7. | 
| 950 | 
            +
            class AddSettingsToProjects < ActiveRecord::Migration[7.2]
         | 
| 951 951 | 
             
              def change
         | 
| 952 952 | 
             
                add_column :projects, :settings, :json
         | 
| 953 953 | 
             
              end
         | 
| @@ -959,7 +959,7 @@ end | |
| 959 959 | 
             
            Use `jsonb` instead.
         | 
| 960 960 |  | 
| 961 961 | 
             
            ```ruby
         | 
| 962 | 
            -
            class AddSettingsToProjects < ActiveRecord::Migration[7. | 
| 962 | 
            +
            class AddSettingsToProjects < ActiveRecord::Migration[7.2]
         | 
| 963 963 | 
             
              def change
         | 
| 964 964 | 
             
                add_column :projects, :settings, :jsonb
         | 
| 965 965 | 
             
              end
         | 
| @@ -973,7 +973,7 @@ end | |
| 973 973 | 
             
            Adding a stored generated column causes the entire table to be rewritten. During this time, reads and writes are blocked.
         | 
| 974 974 |  | 
| 975 975 | 
             
            ```ruby
         | 
| 976 | 
            -
            class AddLowerEmailToUsers < ActiveRecord::Migration[7. | 
| 976 | 
            +
            class AddLowerEmailToUsers < ActiveRecord::Migration[7.2]
         | 
| 977 977 | 
             
              def change
         | 
| 978 978 | 
             
                add_column :users, :lower_email, :virtual, type: :string, as: "LOWER(email)", stored: true
         | 
| 979 979 | 
             
              end
         | 
| @@ -991,7 +991,7 @@ Add a non-generated column and use callbacks or triggers instead. | |
| 991 991 | 
             
            When using short integer types as primary key types, [there is a risk](https://m.signalvnoise.com/update-on-basecamp-3-being-stuck-in-read-only-as-of-nov-8-922am-cst/) of running out of IDs on inserts. The default type in Active Record < 5.1 for primary and foreign keys is `INTEGER`, which allows a little over of 2 billion records. Active Record 5.1 changed the default type to `BIGINT`.
         | 
| 992 992 |  | 
| 993 993 | 
             
            ```ruby
         | 
| 994 | 
            -
            class CreateUsers < ActiveRecord::Migration[7. | 
| 994 | 
            +
            class CreateUsers < ActiveRecord::Migration[7.2]
         | 
| 995 995 | 
             
              def change
         | 
| 996 996 | 
             
                create_table :users, id: :integer do |t|
         | 
| 997 997 | 
             
                  # ...
         | 
| @@ -1005,7 +1005,7 @@ end | |
| 1005 1005 | 
             
            Use one of `bigint`, `bigserial`, `uuid` instead.
         | 
| 1006 1006 |  | 
| 1007 1007 | 
             
            ```ruby
         | 
| 1008 | 
            -
            class CreateUsers < ActiveRecord::Migration[7. | 
| 1008 | 
            +
            class CreateUsers < ActiveRecord::Migration[7.2]
         | 
| 1009 1009 | 
             
              def change
         | 
| 1010 1010 | 
             
                create_table :users, id: :bigint do |t| # bigint is the default for Active Record >= 5.1
         | 
| 1011 1011 | 
             
                  # ...
         | 
| @@ -1021,7 +1021,7 @@ end | |
| 1021 1021 | 
             
            Hash index operations are not WAL-logged, so hash indexes might need to be rebuilt with `REINDEX` after a database crash if there were unwritten changes. Also, changes to hash indexes are not replicated over streaming or file-based replication after the initial base backup, so they give wrong answers to queries that subsequently use them. For these reasons, hash index use is discouraged.
         | 
| 1022 1022 |  | 
| 1023 1023 | 
             
            ```ruby
         | 
| 1024 | 
            -
            class AddIndexToUsersOnEmail < ActiveRecord::Migration[7. | 
| 1024 | 
            +
            class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.2]
         | 
| 1025 1025 | 
             
              def change
         | 
| 1026 1026 | 
             
                add_index :users, :email, unique: true, using: :hash
         | 
| 1027 1027 | 
             
              end
         | 
| @@ -1033,7 +1033,7 @@ end | |
| 1033 1033 | 
             
            Use B-tree indexes instead.
         | 
| 1034 1034 |  | 
| 1035 1035 | 
             
            ```ruby
         | 
| 1036 | 
            -
            class AddIndexToUsersOnEmail < ActiveRecord::Migration[7. | 
| 1036 | 
            +
            class AddIndexToUsersOnEmail < ActiveRecord::Migration[7.2]
         | 
| 1037 1037 | 
             
              def change
         | 
| 1038 1038 | 
             
                add_index :users, :email, unique: true # B-tree by default
         | 
| 1039 1039 | 
             
              end
         | 
| @@ -1048,7 +1048,7 @@ Adding multiple foreign keys in a single migration blocks writes on all involved | |
| 1048 1048 | 
             
            Avoid adding foreign key more than once per migration file, unless the source and target tables are identical.
         | 
| 1049 1049 |  | 
| 1050 1050 | 
             
            ```ruby
         | 
| 1051 | 
            -
            class CreateUserProjects < ActiveRecord::Migration[7. | 
| 1051 | 
            +
            class CreateUserProjects < ActiveRecord::Migration[7.2]
         | 
| 1052 1052 | 
             
              def change
         | 
| 1053 1053 | 
             
                create_table :user_projects do |t|
         | 
| 1054 1054 | 
             
                  t.belongs_to :user, foreign_key: true
         | 
| @@ -1063,7 +1063,7 @@ end | |
| 1063 1063 | 
             
            Add additional foreign keys in separate migration files. See [adding a foreign key](#adding-a-foreign-key) for how to properly add foreign keys.
         | 
| 1064 1064 |  | 
| 1065 1065 | 
             
            ```ruby
         | 
| 1066 | 
            -
            class CreateUserProjects < ActiveRecord::Migration[7. | 
| 1066 | 
            +
            class CreateUserProjects < ActiveRecord::Migration[7.2]
         | 
| 1067 1067 | 
             
              def change
         | 
| 1068 1068 | 
             
                create_table :user_projects do |t|
         | 
| 1069 1069 | 
             
                  t.belongs_to :user, foreign_key: true
         | 
| @@ -1072,7 +1072,7 @@ class CreateUserProjects < ActiveRecord::Migration[7.1] | |
| 1072 1072 | 
             
              end
         | 
| 1073 1073 | 
             
            end
         | 
| 1074 1074 |  | 
| 1075 | 
            -
            class AddForeignKeyFromUserProjectsToProject < ActiveRecord::Migration[7. | 
| 1075 | 
            +
            class AddForeignKeyFromUserProjectsToProject < ActiveRecord::Migration[7.2]
         | 
| 1076 1076 | 
             
              def change
         | 
| 1077 1077 | 
             
                add_foreign_key :user_projects, :projects
         | 
| 1078 1078 | 
             
              end
         | 
| @@ -1089,7 +1089,7 @@ Remove all the foreign keys first. | |
| 1089 1089 | 
             
            Assuming, `projects` has foreign keys on `users.id` and `repositories.id`:
         | 
| 1090 1090 |  | 
| 1091 1091 | 
             
            ```ruby
         | 
| 1092 | 
            -
            class DropProjects < ActiveRecord::Migration[7. | 
| 1092 | 
            +
            class DropProjects < ActiveRecord::Migration[7.2]
         | 
| 1093 1093 | 
             
              def change
         | 
| 1094 1094 | 
             
                drop_table :projects
         | 
| 1095 1095 | 
             
              end
         | 
| @@ -1101,13 +1101,13 @@ end | |
| 1101 1101 | 
             
            Remove all the foreign keys first:
         | 
| 1102 1102 |  | 
| 1103 1103 | 
             
            ```ruby
         | 
| 1104 | 
            -
            class RemoveProjectsUserFk < ActiveRecord::Migration[7. | 
| 1104 | 
            +
            class RemoveProjectsUserFk < ActiveRecord::Migration[7.2]
         | 
| 1105 1105 | 
             
              def change
         | 
| 1106 1106 | 
             
                remove_foreign_key :projects, :users
         | 
| 1107 1107 | 
             
              end
         | 
| 1108 1108 | 
             
            end
         | 
| 1109 1109 |  | 
| 1110 | 
            -
            class RemoveProjectsRepositoryFk < ActiveRecord::Migration[7. | 
| 1110 | 
            +
            class RemoveProjectsRepositoryFk < ActiveRecord::Migration[7.2]
         | 
| 1111 1111 | 
             
              def change
         | 
| 1112 1112 | 
             
                remove_foreign_key :projects, :repositories
         | 
| 1113 1113 | 
             
              end
         | 
| @@ -1117,7 +1117,7 @@ end | |
| 1117 1117 | 
             
            Then remove the table:
         | 
| 1118 1118 |  | 
| 1119 1119 | 
             
            ```ruby
         | 
| 1120 | 
            -
            class DropProjects < ActiveRecord::Migration[7. | 
| 1120 | 
            +
            class DropProjects < ActiveRecord::Migration[7.2]
         | 
| 1121 1121 | 
             
              def change
         | 
| 1122 1122 | 
             
                drop_table :projects
         | 
| 1123 1123 | 
             
              end
         | 
| @@ -1134,7 +1134,7 @@ Otherwise, there's a risk of bugs caused by IDs representable by one type but no | |
| 1134 1134 | 
             
            Assuming, there is a `users` table with `bigint` primary key type:
         | 
| 1135 1135 |  | 
| 1136 1136 | 
             
            ```ruby
         | 
| 1137 | 
            -
            class AddUserIdToProjects < ActiveRecord::Migration[7. | 
| 1137 | 
            +
            class AddUserIdToProjects < ActiveRecord::Migration[7.2]
         | 
| 1138 1138 | 
             
              def change
         | 
| 1139 1139 | 
             
                add_column :projects, :user_id, :integer
         | 
| 1140 1140 | 
             
              end
         | 
| @@ -1148,7 +1148,7 @@ Add a reference column of the same type as a referenced primary key. | |
| 1148 1148 | 
             
            Assuming, there is a `users` table with `bigint` primary key type:
         | 
| 1149 1149 |  | 
| 1150 1150 | 
             
            ```ruby
         | 
| 1151 | 
            -
            class AddUserIdToProjects < ActiveRecord::Migration[7. | 
| 1151 | 
            +
            class AddUserIdToProjects < ActiveRecord::Migration[7.2]
         | 
| 1152 1152 | 
             
              def change
         | 
| 1153 1153 | 
             
                add_column :projects, :user_id, :bigint
         | 
| 1154 1154 | 
             
              end
         | 
| @@ -1162,7 +1162,7 @@ end | |
| 1162 1162 | 
             
            Adding a single table inheritance column might cause errors in old instances of your application.
         | 
| 1163 1163 |  | 
| 1164 1164 | 
             
            ```ruby
         | 
| 1165 | 
            -
            class AddTypeToUsers < ActiveRecord::Migration[7. | 
| 1165 | 
            +
            class AddTypeToUsers < ActiveRecord::Migration[7.2]
         | 
| 1166 1166 | 
             
              def change
         | 
| 1167 1167 | 
             
                add_column :users, :string, :type, default: "Member"
         | 
| 1168 1168 | 
             
              end
         | 
| @@ -1194,7 +1194,7 @@ A safer approach is to: | |
| 1194 1194 | 
             
            Active Record < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
         | 
| 1195 1195 |  | 
| 1196 1196 | 
             
            ```ruby
         | 
| 1197 | 
            -
            class ChangeSomeColumnDefault < ActiveRecord::Migration[7. | 
| 1197 | 
            +
            class ChangeSomeColumnDefault < ActiveRecord::Migration[7.2]
         | 
| 1198 1198 | 
             
              def change
         | 
| 1199 1199 | 
             
                change_column_default :users, :some_column, from: "old", to: "new"
         | 
| 1200 1200 | 
             
              end
         | 
| @@ -1222,7 +1222,7 @@ config.active_record.partial_inserts = false | |
| 1222 1222 | 
             
            To mark a step in the migration as safe, despite using a method that might otherwise be dangerous, wrap it in a `safety_assured` block.
         | 
| 1223 1223 |  | 
| 1224 1224 | 
             
            ```ruby
         | 
| 1225 | 
            -
            class MySafeMigration < ActiveRecord::Migration[7. | 
| 1225 | 
            +
            class MySafeMigration < ActiveRecord::Migration[7.2]
         | 
| 1226 1226 | 
             
              def change
         | 
| 1227 1227 | 
             
                safety_assured { remove_column :users, :some_column }
         | 
| 1228 1228 | 
             
              end
         | 
| @@ -78,7 +78,7 @@ You can enqueue your background migration to be run by the scheduler via: | |
| 78 78 |  | 
| 79 79 | 
             
            ```ruby
         | 
| 80 80 | 
             
            # db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb
         | 
| 81 | 
            -
            class EnqueueBackfillProjectIssuesCount < ActiveRecord::Migration[7. | 
| 81 | 
            +
            class EnqueueBackfillProjectIssuesCount < ActiveRecord::Migration[7.2]
         | 
| 82 82 | 
             
              def up
         | 
| 83 83 | 
             
                enqueue_background_data_migration("BackfillProjectIssuesCount")
         | 
| 84 84 | 
             
              end
         | 
    
        data/docs/configuring.md
    CHANGED
    
    | @@ -156,7 +156,7 @@ This is useful to demystify `online_migrations` inner workings, and to better in | |
| 156 156 | 
             
            Consider migration, running on PostgreSQL < 11:
         | 
| 157 157 |  | 
| 158 158 | 
             
            ```ruby
         | 
| 159 | 
            -
            class AddAdminToUsers < ActiveRecord::Migration[7. | 
| 159 | 
            +
            class AddAdminToUsers < ActiveRecord::Migration[7.2]
         | 
| 160 160 | 
             
              disable_ddl_transaction!
         | 
| 161 161 |  | 
| 162 162 | 
             
              def change
         | 
| @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            class BackgroundSchemaMigrationsChangeUniqueIndex < <%= migration_parent %>
         | 
| 2 | 
            +
              def change
         | 
| 3 | 
            +
                safety_assured do
         | 
| 4 | 
            +
                  remove_index :background_schema_migrations, name: :index_background_schema_migrations_on_unique_configuration
         | 
| 5 | 
            +
                  add_index :background_schema_migrations, [:migration_name, :shard, :connection_class_name], unique: true,
         | 
| 6 | 
            +
                    name: :index_background_schema_migrations_on_unique_configuration
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
            end
         | 
| @@ -69,7 +69,8 @@ class InstallOnlineMigrations < <%= migration_parent %> | |
| 69 69 |  | 
| 70 70 | 
             
                  t.foreign_key :background_schema_migrations, column: :parent_id, on_delete: :cascade
         | 
| 71 71 |  | 
| 72 | 
            -
                  t.index [:migration_name, :shard], unique: true, | 
| 72 | 
            +
                  t.index [:migration_name, :shard, :connection_class_name], unique: true,
         | 
| 73 | 
            +
                    name: :index_background_schema_migrations_on_unique_configuration
         | 
| 73 74 | 
             
                end
         | 
| 74 75 | 
             
              end
         | 
| 75 76 | 
             
            end
         | 
| @@ -28,6 +28,12 @@ module OnlineMigrations | |
| 28 28 | 
             
                      migrations << "create_background_schema_migrations"
         | 
| 29 29 | 
             
                    end
         | 
| 30 30 |  | 
| 31 | 
            +
                    indexes = connection.indexes(BackgroundSchemaMigrations::Migration.table_name)
         | 
| 32 | 
            +
                    unique_index = indexes.find { |i| i.unique && i.columns.sort == ["connection_class_name", "migration_name", "shard"] }
         | 
| 33 | 
            +
                    if !unique_index
         | 
| 34 | 
            +
                      migrations << "background_schema_migrations_change_unique_index"
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
             | 
| 31 37 | 
             
                    migrations
         | 
| 32 38 | 
             
                  end
         | 
| 33 39 |  | 
| @@ -62,7 +62,7 @@ module OnlineMigrations | |
| 62 62 | 
             
                  validates :table_name, presence: true, length: { maximum: MAX_IDENTIFIER_LENGTH }
         | 
| 63 63 | 
             
                  validates :definition, presence: true
         | 
| 64 64 | 
             
                  validates :migration_name, presence: true, uniqueness: {
         | 
| 65 | 
            -
                    scope: :shard,
         | 
| 65 | 
            +
                    scope: [:connection_class_name, :shard],
         | 
| 66 66 | 
             
                    message: ->(object, data) do
         | 
| 67 67 | 
             
                      message = "(#{data[:value]}) has already been taken."
         | 
| 68 68 | 
             
                      if object.index_addition?
         | 
| @@ -116,6 +116,16 @@ module OnlineMigrations | |
| 116 116 | 
             
                    end
         | 
| 117 117 | 
             
                  end
         | 
| 118 118 |  | 
| 119 | 
            +
                  # Whether the migration is considered stuck (is running for some configured time).
         | 
| 120 | 
            +
                  #
         | 
| 121 | 
            +
                  def stuck?
         | 
| 122 | 
            +
                    # Composite migrations are not considered stuck.
         | 
| 123 | 
            +
                    return false if composite?
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                    stuck_timeout = (statement_timeout || 1.day) + 10.minutes
         | 
| 126 | 
            +
                    (enqueued? || running?) && updated_at <= stuck_timeout.seconds.ago
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 119 129 | 
             
                  # Mark this migration as ready to be processed again.
         | 
| 120 130 | 
             
                  #
         | 
| 121 131 | 
             
                  # This is used to manually retrying failed migrations.
         | 
| @@ -180,7 +190,7 @@ module OnlineMigrations | |
| 180 190 | 
             
                              if connection.send(:__index_valid?, name, schema: schema)
         | 
| 181 191 | 
             
                                return
         | 
| 182 192 | 
             
                              else
         | 
| 183 | 
            -
                                connection.remove_index(table_name, name: name)
         | 
| 193 | 
            +
                                connection.remove_index(table_name, name: name, algorithm: :concurrently)
         | 
| 184 194 | 
             
                              end
         | 
| 185 195 | 
             
                            end
         | 
| 186 196 | 
             
                          end
         | 
| @@ -204,7 +214,7 @@ module OnlineMigrations | |
| 204 214 |  | 
| 205 215 | 
             
                    def validate_connection_class
         | 
| 206 216 | 
             
                      klass = connection_class_name.safe_constantize
         | 
| 207 | 
            -
                      if !(klass  | 
| 217 | 
            +
                      if !(klass <= ActiveRecord::Base)
         | 
| 208 218 | 
             
                        errors.add(:connection_class_name, "is not an ActiveRecord::Base child class")
         | 
| 209 219 | 
             
                      end
         | 
| 210 220 | 
             
                    end
         | 
| @@ -85,10 +85,15 @@ module OnlineMigrations | |
| 85 85 | 
             
                  end
         | 
| 86 86 |  | 
| 87 87 | 
             
                  # @private
         | 
| 88 | 
            -
                  def create_background_schema_migration(migration_name, table_name, **options)
         | 
| 89 | 
            -
                    options.assert_valid_keys(:definition, :max_attempts, :statement_timeout | 
| 88 | 
            +
                  def create_background_schema_migration(migration_name, table_name, connection_class_name: nil, **options)
         | 
| 89 | 
            +
                    options.assert_valid_keys(:definition, :max_attempts, :statement_timeout)
         | 
| 90 90 |  | 
| 91 | 
            -
                     | 
| 91 | 
            +
                    if connection_class_name
         | 
| 92 | 
            +
                      connection_class_name = __normalize_connection_class_name(connection_class_name)
         | 
| 93 | 
            +
                    end
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                    Migration.find_or_create_by!(migration_name: migration_name, shard: nil,
         | 
| 96 | 
            +
                                                 connection_class_name: connection_class_name) do |migration|
         | 
| 92 97 | 
             
                      migration.assign_attributes(**options, table_name: table_name)
         | 
| 93 98 |  | 
| 94 99 | 
             
                      shards = Utils.shard_names(migration.connection_class)
         | 
| @@ -103,6 +108,17 @@ module OnlineMigrations | |
| 103 108 | 
             
                      end
         | 
| 104 109 | 
             
                    end
         | 
| 105 110 | 
             
                  end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  private
         | 
| 113 | 
            +
                    def __normalize_connection_class_name(connection_class_name)
         | 
| 114 | 
            +
                      if connection_class_name
         | 
| 115 | 
            +
                        klass = connection_class_name.safe_constantize
         | 
| 116 | 
            +
                        if klass
         | 
| 117 | 
            +
                          connection_class = Utils.find_connection_class(klass)
         | 
| 118 | 
            +
                          connection_class.name if connection_class
         | 
| 119 | 
            +
                        end
         | 
| 120 | 
            +
                      end
         | 
| 121 | 
            +
                    end
         | 
| 106 122 | 
             
                end
         | 
| 107 123 | 
             
              end
         | 
| 108 124 | 
             
            end
         | 
| @@ -68,6 +68,13 @@ module OnlineMigrations | |
| 68 68 | 
             
                        migration.run
         | 
| 69 69 | 
             
                      end
         | 
| 70 70 |  | 
| 71 | 
            +
                      # Background schema migrations could take a while to run. It is possible, that the process
         | 
| 72 | 
            +
                      # never reaches this (or the rescue below) line of code. E.g., when it is force quitted
         | 
| 73 | 
            +
                      # (SIGKILL etc.) and so the migration will end up in the "running" state and the query is
         | 
| 74 | 
            +
                      # still executing (or already finished) in the database. This migration can either be safely
         | 
| 75 | 
            +
                      # manually retried or will be picked up in the future by scheduler when it decides that
         | 
| 76 | 
            +
                      # this migration is stuck.
         | 
| 77 | 
            +
             | 
| 71 78 | 
             
                      migration.update!(status: :succeeded, finished_at: Time.current)
         | 
| 72 79 |  | 
| 73 80 | 
             
                      ActiveSupport::Notifications.instrument("completed.background_schema_migrations", migration_payload)
         | 
| @@ -29,12 +29,13 @@ module OnlineMigrations | |
| 29 29 |  | 
| 30 30 | 
             
                  private
         | 
| 31 31 | 
             
                    def find_migration
         | 
| 32 | 
            -
                      active_migrations = Migration.running. | 
| 32 | 
            +
                      active_migrations = Migration.running.reject(&:stuck?)
         | 
| 33 33 | 
             
                      runnable_migrations = Migration.runnable.enqueued.queue_order.to_a + Migration.retriable.queue_order.to_a
         | 
| 34 34 |  | 
| 35 35 | 
             
                      runnable_migrations.find do |runnable_migration|
         | 
| 36 36 | 
             
                        active_migrations.none? do |active_migration|
         | 
| 37 | 
            -
                          active_migration. | 
| 37 | 
            +
                          active_migration.connection_class_name == runnable_migration.connection_class_name &&
         | 
| 38 | 
            +
                            active_migration.shard == runnable_migration.shard &&
         | 
| 38 39 | 
             
                            active_migration.table_name == runnable_migration.table_name
         | 
| 39 40 | 
             
                        end
         | 
| 40 41 | 
             
                      end
         | 
| @@ -179,7 +179,7 @@ module OnlineMigrations | |
| 179 179 | 
             
                #
         | 
| 180 180 | 
             
                def initialize_columns_rename(table_name, old_new_column_hash)
         | 
| 181 181 | 
             
                  transaction do
         | 
| 182 | 
            -
                     | 
| 182 | 
            +
                    __rename_table_and_create_view(table_name, old_new_column_hash)
         | 
| 183 183 | 
             
                  end
         | 
| 184 184 | 
             
                end
         | 
| 185 185 |  | 
| @@ -214,7 +214,9 @@ module OnlineMigrations | |
| 214 214 | 
             
                def revert_initialize_columns_rename(table_name, _old_new_column_hash = nil)
         | 
| 215 215 | 
             
                  transaction do
         | 
| 216 216 | 
             
                    execute("DROP VIEW #{quote_table_name(table_name)}")
         | 
| 217 | 
            -
             | 
| 217 | 
            +
             | 
| 218 | 
            +
                    tmp_table = __tmp_table_name_for_column_rename(table_name)
         | 
| 219 | 
            +
                    rename_table(tmp_table, table_name)
         | 
| 218 220 | 
             
                  end
         | 
| 219 221 | 
             
                end
         | 
| 220 222 |  | 
| @@ -241,7 +243,9 @@ module OnlineMigrations | |
| 241 243 | 
             
                def finalize_columns_rename(table_name, old_new_column_hash)
         | 
| 242 244 | 
             
                  transaction do
         | 
| 243 245 | 
             
                    execute("DROP VIEW #{quote_table_name(table_name)}")
         | 
| 244 | 
            -
             | 
| 246 | 
            +
             | 
| 247 | 
            +
                    tmp_table = __tmp_table_name_for_column_rename(table_name)
         | 
| 248 | 
            +
                    rename_table(tmp_table, table_name)
         | 
| 245 249 | 
             
                    old_new_column_hash.each do |column_name, new_column_name|
         | 
| 246 250 | 
             
                      rename_column(table_name, column_name, new_column_name)
         | 
| 247 251 | 
             
                    end
         | 
| @@ -273,7 +277,7 @@ module OnlineMigrations | |
| 273 277 | 
             
                    old_new_column_hash.each do |column_name, new_column_name|
         | 
| 274 278 | 
             
                      rename_column(table_name, new_column_name, column_name)
         | 
| 275 279 | 
             
                    end
         | 
| 276 | 
            -
                     | 
| 280 | 
            +
                    __rename_table_and_create_view(table_name, old_new_column_hash)
         | 
| 277 281 | 
             
                  end
         | 
| 278 282 | 
             
                end
         | 
| 279 283 |  | 
| @@ -906,7 +910,8 @@ module OnlineMigrations | |
| 906 910 | 
             
                  if renamed_tables.key?(table)
         | 
| 907 911 | 
             
                    super(renamed_tables[table])
         | 
| 908 912 | 
             
                  elsif renamed_columns.key?(table)
         | 
| 909 | 
            -
                     | 
| 913 | 
            +
                    tmp_table = __tmp_table_name_for_column_rename(table)
         | 
| 914 | 
            +
                    super(tmp_table)
         | 
| 910 915 | 
             
                  else
         | 
| 911 916 | 
             
                    super
         | 
| 912 917 | 
             
                  end
         | 
| @@ -1046,8 +1051,9 @@ module OnlineMigrations | |
| 1046 1051 | 
             
                    schema ? quote(schema) : "current_schema()"
         | 
| 1047 1052 | 
             
                  end
         | 
| 1048 1053 |  | 
| 1049 | 
            -
                  def  | 
| 1050 | 
            -
                    tmp_table =  | 
| 1054 | 
            +
                  def __rename_table_and_create_view(table_name, old_new_column_hash)
         | 
| 1055 | 
            +
                    tmp_table = __tmp_table_name_for_column_rename(table_name)
         | 
| 1056 | 
            +
             | 
| 1051 1057 | 
             
                    rename_table(table_name, tmp_table)
         | 
| 1052 1058 | 
             
                    column_mapping = old_new_column_hash.map do |column_name, new_column_name|
         | 
| 1053 1059 | 
             
                      "#{quote_column_name(column_name)} AS #{quote_column_name(new_column_name)}"
         | 
| @@ -1059,5 +1065,16 @@ module OnlineMigrations | |
| 1059 1065 | 
             
                        FROM #{quote_table_name(tmp_table)}
         | 
| 1060 1066 | 
             
                    SQL
         | 
| 1061 1067 | 
             
                  end
         | 
| 1068 | 
            +
             | 
| 1069 | 
            +
                  def __tmp_table_name_for_column_rename(table_name)
         | 
| 1070 | 
            +
                    suffix = "_column_rename"
         | 
| 1071 | 
            +
             | 
| 1072 | 
            +
                    # On ActiveRecord 7.1 can use table_name_length instead of max_identifier_length,
         | 
| 1073 | 
            +
                    # see https://github.com/rails/rails/pull/45136.
         | 
| 1074 | 
            +
                    # Also we need to account for "_pkey", because older versions does not correctly rename
         | 
| 1075 | 
            +
                    # tables with long names. Remove when supporting newer versions only.
         | 
| 1076 | 
            +
                    prefix_length = max_identifier_length - "_pkey".size - suffix.length
         | 
| 1077 | 
            +
                    table_name[0, prefix_length] + suffix
         | 
| 1078 | 
            +
                  end
         | 
| 1062 1079 | 
             
              end
         | 
| 1063 1080 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: online_migrations
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.19. | 
| 4 | 
            +
              version: 0.19.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - fatkodima
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2024- | 
| 11 | 
            +
            date: 2024-09-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activerecord
         | 
| @@ -41,6 +41,7 @@ files: | |
| 41 41 | 
             
            - lib/generators/online_migrations/install_generator.rb
         | 
| 42 42 | 
             
            - lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt
         | 
| 43 43 | 
             
            - lib/generators/online_migrations/templates/background_data_migration.rb.tt
         | 
| 44 | 
            +
            - lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt
         | 
| 44 45 | 
             
            - lib/generators/online_migrations/templates/create_background_schema_migrations.rb.tt
         | 
| 45 46 | 
             
            - lib/generators/online_migrations/templates/initializer.rb.tt
         | 
| 46 47 | 
             
            - lib/generators/online_migrations/templates/install_migration.rb.tt
         |