online_migrations 0.19.3 → 0.19.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4d8223e51d4016b3752a738ecbc04cc200c94ee44dd3d91a1be2aefbc74060a9
4
- data.tar.gz: bbaa8a04c4b730ac33e1503c59782658f6810f9782b4f882ab2b19bd0e0402d2
3
+ metadata.gz: 6392ff9889da502701252f97a71e4fa8c3b403dc61aa010957a5b79221a9366b
4
+ data.tar.gz: 1c880f23e20b142c335679701bdac977f089ac5ef076352a438e6dfa469b54cb
5
5
  SHA512:
6
- metadata.gz: 878031d1e81e5a069500e4c9c8f0d2bb9b9b60320e07ce101a7ae8680556a6a8114de4d0f6bf092340ec3651f9bbbb74fdc6e6453db39be3c22f6846b5a2ad94
7
- data.tar.gz: 143a618f2d46a8a452c9f32ca87f44e47dffe5268bae00dcff008f54865cf272e20bb6aa4af7243cf642a195e64e9aed8269660f1658ebb3440bff12eeed37df
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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.1]
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, name: :index_background_schema_migrations_on_unique_configuration
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 < ActiveRecord::Base)
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, :connection_class_name)
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
- Migration.find_or_create_by!(migration_name: migration_name, shard: nil) do |migration|
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.select(:table_name, :shard).to_a
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.shard == runnable_migration.shard &&
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
- rename_table_create_view(table_name, old_new_column_hash)
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
- rename_table("#{table_name}_column_rename", table_name)
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
- rename_table("#{table_name}_column_rename", table_name)
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
- rename_table_create_view(table_name, old_new_column_hash)
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
- super("#{table}_column_rename")
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 rename_table_create_view(table_name, old_new_column_hash)
1050
- tmp_table = "#{table_name}_column_rename"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.19.3"
4
+ VERSION = "0.19.4"
5
5
  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.3
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-08-08 00:00:00.000000000 Z
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