online_migrations 0.14.1 → 0.15.0

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: 330d5ed7f11aae3b85e7fb5aab5a1ea66d809da704cf193420b6d4206f72d82b
4
- data.tar.gz: 36d47a6f2a1cae3de33331a4ec9e6b21fc7b3b8b09a7d5e9c34d1be6c92e5d35
3
+ metadata.gz: 2b1b826df875fae97622c702205853da01d67153172ef73fbb0edf28a2f7ff60
4
+ data.tar.gz: 18ae161255543e7ab7c266efc94f1ca166a344aa18b92dd4ab2f37209fb42547
5
5
  SHA512:
6
- metadata.gz: e07937136867b4b6bc2d0dfb2e5ccb8471faf6ce0339a247bc0c63d591cc163e084da731e86fc01ba839c7744a51e9c63e99ce632452e471f9eed98a95b9dd02
7
- data.tar.gz: f1a33636d5fbd01f25a0cee32af0d74e377c804eaef0feee2c778c5318a93bbe8942dc03e8ea2bad0a610d0046a8d4e42b505d0991c7d215cc9842a24d5e5065
6
+ metadata.gz: 705e4ce816bd8b4fcbcb78871589274bdf37acca01fbdf031883e87403a403782d28990ebed10acdf57adcbf3fb4e30e1dbf187123029bf6b1b7d31f26ccd3ca
7
+ data.tar.gz: 60ea354440b96645c5c03345d60793a66ba61d8aa1b946737098bc1449ba67e0b24caeb4581c525b3b014c5dd44b0ac28e0c539c8e3454ad67a3a1610cffdd79
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.15.0 (2024-03-19)
4
+
5
+ - Reraise errors when running background migrations inline
6
+ - Add `remove_background_migration` migration helper
7
+ - Allow adding bigint foreign keys referencing integer primary keys
8
+ - Fix `add_reference_concurrently` to check for mismatched key types
9
+
3
10
  ## 0.14.1 (2024-02-21)
4
11
 
5
12
  - Fix `MigrationRunner` to consider `run_background_migrations_inline` proc
data/README.md CHANGED
@@ -191,7 +191,7 @@ end
191
191
 
192
192
  ```ruby
193
193
  class User < ApplicationRecord
194
- self.ignored_columns = ["name"]
194
+ self.ignored_columns += ["name"]
195
195
  end
196
196
  ```
197
197
 
@@ -488,7 +488,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
488
488
 
489
489
  ```ruby
490
490
  class User < ApplicationRecord
491
- self.ignored_columns = ["name"]
491
+ self.ignored_columns += ["name"]
492
492
  end
493
493
  ```
494
494
 
@@ -1172,7 +1172,7 @@ A safer approach is to:
1172
1172
 
1173
1173
  ```ruby
1174
1174
  class User < ApplicationRecord
1175
- self.ignored_columns = ["type"]
1175
+ self.ignored_columns += ["type"]
1176
1176
  end
1177
1177
  ```
1178
1178
 
@@ -30,7 +30,8 @@ A generator is provided to create background migrations. Generate a new backgrou
30
30
  $ bin/rails generate online_migrations:background_migration backfill_project_issues_count
31
31
  ```
32
32
 
33
- This creates the background migration file `lib/online_migrations/background_migrations/backfill_project_issues_count.rb`.
33
+ This creates the background migration file `lib/online_migrations/background_migrations/backfill_project_issues_count.rb`
34
+ and the regular migration file `db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb` where we enqueue it.
34
35
 
35
36
  The generated class is a subclass of `OnlineMigrations::BackgroundMigration` that implements:
36
37
 
@@ -76,12 +77,16 @@ end
76
77
  You can enqueue your background migration to be run by the scheduler via:
77
78
 
78
79
  ```ruby
79
- # db/migrate/xxxxxxxxxxxxxx_backfill_project_issues_count.rb
80
- # ...
81
- def up
82
- enqueue_background_migration("BackfillProjectIssuesCount")
80
+ # db/migrate/xxxxxxxxxxxxxx_enqueue_backfill_project_issues_count.rb
81
+ class EnqueueBackfillProjectIssuesCount < ActiveRecord::Migration[7.1]
82
+ def up
83
+ enqueue_background_migration("BackfillProjectIssuesCount")
84
+ end
85
+
86
+ def down
87
+ remove_background_migration("BackfillProjectIssuesCount")
88
+ end
83
89
  end
84
- # ...
85
90
  ```
86
91
 
87
92
  `enqueue_background_migration` accepts additional configuration options which controls how the background migration is run. Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_migrations/migration_helpers.rb) for the list of all available configuration options.
@@ -106,7 +111,17 @@ end
106
111
  And pass them when enqueuing:
107
112
 
108
113
  ```ruby
109
- enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
114
+ def up
115
+ enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
116
+ end
117
+ ```
118
+
119
+ Make sure to also pass the arguments inside the `down` method of the migration:
120
+
121
+ ```ruby
122
+ def down
123
+ remove_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
124
+ end
110
125
  ```
111
126
 
112
127
  ## Considerations when writing Background Migrations
data/docs/configuring.md CHANGED
@@ -101,13 +101,15 @@ config.lock_retrier = OnlineMigrations::ExponentialLockRetrier.new(
101
101
  )
102
102
  ```
103
103
 
104
- When statement within transaction fails - the whole transaction is retried.
104
+ When a statement within transaction fails - the whole transaction is retried. If any statement fails when running outside a transaction (e.g. using `disable_ddl_transaction!`) then only that statement is retried.
105
105
 
106
- To permanently disable lock retries, you can set `lock_retrier` to `nil`.
106
+ **Note**: Statements are retried by default, unless lock retries are disabled. It is possible to implement more sophisticated lock retriers. See [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/lock_retrier.rb) for the examples.
107
107
 
108
108
  To temporarily disable lock retries while running migrations, set `DISABLE_LOCK_RETRIES` env variable. This is useful when you are deploying a hotfix and do not want to wait too long while the lock retrier safely tries to acquire the lock, but try to acquire the lock immediately with the default configured lock timeout value.
109
109
 
110
- **Note**: Statements are retried by default, unless lock retries are disabled. It is possible to implement more sophisticated lock retriers. See [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/lock_retrier.rb) for the examples.
110
+ To permanently disable lock retries, you can set `lock_retrier` to `nil`.
111
+
112
+ Finally, if your lock retrier implementation does not have an explicit `lock_timeout` value configured, then the timeout behavior will fallback to the database configuration (`config/database.yml`) or the PostgreSQL server config value ([off by default](https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-LOCK-TIMEOUT)). Take care configuring this value, as this fallback may result in your migrations running without a lock timeout!
111
113
 
112
114
  ## Existing Migrations
113
115
 
@@ -1,12 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails/generators"
4
+ require "rails/generators/active_record/migration"
4
5
 
5
6
  module OnlineMigrations
6
7
  # @private
7
8
  class BackgroundMigrationGenerator < Rails::Generators::NamedBase
9
+ include ActiveRecord::Generators::Migration
10
+
8
11
  source_root File.expand_path("templates", __dir__)
9
- desc "This generator creates a background migration file."
12
+ desc "This generator creates a background migration related files."
10
13
 
11
14
  def create_background_migration_file
12
15
  migrations_module_file_path = migrations_module.underscore
@@ -20,6 +23,10 @@ module OnlineMigrations
20
23
  template("background_migration.rb", template_file)
21
24
  end
22
25
 
26
+ def create_migration_file
27
+ migration_template("migration.rb", File.join(db_migrate_path, "enqueue_#{file_name}.rb"))
28
+ end
29
+
23
30
  private
24
31
  def migrations_module
25
32
  config.migrations_module
@@ -28,5 +35,9 @@ module OnlineMigrations
28
35
  def config
29
36
  OnlineMigrations.config.background_migrations
30
37
  end
38
+
39
+ def migration_parent
40
+ "ActiveRecord::Migration[#{Utils.ar_version}]"
41
+ end
31
42
  end
32
43
  end
@@ -15,7 +15,7 @@ module OnlineMigrations
15
15
  end
16
16
 
17
17
  def create_migration_file
18
- migration_template("migration.rb", File.join(db_migrate_path, "install_online_migrations.rb"))
18
+ migration_template("install_migration.rb", File.join(db_migrate_path, "install_online_migrations.rb"))
19
19
  end
20
20
 
21
21
  private
@@ -0,0 +1,51 @@
1
+ class InstallOnlineMigrations < <%= migration_parent %>
2
+ def change
3
+ create_table :background_migrations do |t|
4
+ t.bigint :parent_id
5
+ t.string :migration_name, null: false
6
+ t.jsonb :arguments, default: [], null: false
7
+ t.string :batch_column_name, null: false
8
+ t.bigint :min_value, null: false
9
+ t.bigint :max_value, null: false
10
+ t.bigint :rows_count
11
+ t.integer :batch_size, null: false
12
+ t.integer :sub_batch_size, null: false
13
+ t.integer :batch_pause, null: false
14
+ t.integer :sub_batch_pause_ms, null: false
15
+ t.integer :batch_max_attempts, null: false
16
+ t.string :status, default: "enqueued", null: false
17
+ t.string :shard
18
+ t.boolean :composite, default: false, null: false
19
+ t.timestamps
20
+
21
+ t.foreign_key :background_migrations, column: :parent_id, on_delete: :cascade
22
+
23
+ t.index [:migration_name, :arguments, :shard],
24
+ unique: true, name: :index_background_migrations_on_unique_configuration
25
+ end
26
+
27
+ create_table :background_migration_jobs do |t|
28
+ t.bigint :migration_id, null: false
29
+ t.bigint :min_value, null: false
30
+ t.bigint :max_value, null: false
31
+ t.integer :batch_size, null: false
32
+ t.integer :sub_batch_size, null: false
33
+ t.integer :pause_ms, null: false
34
+ t.datetime :started_at
35
+ t.datetime :finished_at
36
+ t.string :status, default: "enqueued", null: false
37
+ t.integer :max_attempts, null: false
38
+ t.integer :attempts, default: 0, null: false
39
+ t.string :error_class
40
+ t.string :error_message
41
+ t.string :backtrace, array: true
42
+ t.timestamps
43
+
44
+ t.foreign_key :background_migrations, column: :migration_id, on_delete: :cascade
45
+
46
+ t.index [:migration_id, :max_value], name: :index_background_migration_jobs_on_max_value
47
+ t.index [:migration_id, :status, :updated_at], name: :index_background_migration_jobs_on_updated_at
48
+ t.index [:migration_id, :finished_at], name: :index_background_migration_jobs_on_finished_at
49
+ end
50
+ end
51
+ end
@@ -1,51 +1,10 @@
1
- class InstallOnlineMigrations < <%= migration_parent %>
2
- def change
3
- create_table :background_migrations do |t|
4
- t.bigint :parent_id
5
- t.string :migration_name, null: false
6
- t.jsonb :arguments, default: [], null: false
7
- t.string :batch_column_name, null: false
8
- t.bigint :min_value, null: false
9
- t.bigint :max_value, null: false
10
- t.bigint :rows_count
11
- t.integer :batch_size, null: false
12
- t.integer :sub_batch_size, null: false
13
- t.integer :batch_pause, null: false
14
- t.integer :sub_batch_pause_ms, null: false
15
- t.integer :batch_max_attempts, null: false
16
- t.string :status, default: "enqueued", null: false
17
- t.string :shard
18
- t.boolean :composite, default: false, null: false
19
- t.timestamps
20
-
21
- t.foreign_key :background_migrations, column: :parent_id, on_delete: :cascade
22
-
23
- t.index [:migration_name, :arguments, :shard],
24
- unique: true, name: :index_background_migrations_on_unique_configuration
25
- end
26
-
27
- create_table :background_migration_jobs do |t|
28
- t.bigint :migration_id, null: false
29
- t.bigint :min_value, null: false
30
- t.bigint :max_value, null: false
31
- t.integer :batch_size, null: false
32
- t.integer :sub_batch_size, null: false
33
- t.integer :pause_ms, null: false
34
- t.datetime :started_at
35
- t.datetime :finished_at
36
- t.string :status, default: "enqueued", null: false
37
- t.integer :max_attempts, null: false
38
- t.integer :attempts, default: 0, null: false
39
- t.string :error_class
40
- t.string :error_message
41
- t.string :backtrace, array: true
42
- t.timestamps
43
-
44
- t.foreign_key :background_migrations, column: :migration_id, on_delete: :cascade
1
+ class Enqueue<%= class_name %> < <%= migration_parent %>
2
+ def up
3
+ enqueue_background_migration("<%= class_name %>", ...args)
4
+ end
45
5
 
46
- t.index [:migration_id, :max_value], name: :index_background_migration_jobs_on_max_value
47
- t.index [:migration_id, :status, :updated_at], name: :index_background_migration_jobs_on_updated_at
48
- t.index [:migration_id, :finished_at], name: :index_background_migration_jobs_on_finished_at
49
- end
6
+ def down
7
+ # Make sure to pass the same arguments as in the "up" method, if any.
8
+ remove_background_migration("<%= class_name %>", ...args)
50
9
  end
51
10
  end
@@ -16,13 +16,6 @@ module OnlineMigrations
16
16
  return
17
17
  end
18
18
 
19
- if relation.joins_values.present? && !record.batch_column_name.to_s.include?(".")
20
- record.errors.add(
21
- :batch_column_name,
22
- "must be a fully-qualified column if you join a table"
23
- )
24
- end
25
-
26
19
  if relation.arel.orders.present? || relation.arel.taken.present?
27
20
  record.errors.add(
28
21
  :migration_name,
@@ -21,7 +21,7 @@ module OnlineMigrations
21
21
  end
22
22
 
23
23
  def process_batch(relation)
24
- relation.delete_all(:delete_all)
24
+ relation.delete_all
25
25
  end
26
26
  end
27
27
  end
@@ -372,8 +372,7 @@ module OnlineMigrations
372
372
  def enqueue_background_migration(migration_name, *arguments, **options)
373
373
  migration = create_background_migration(migration_name, *arguments, **options)
374
374
 
375
- run_inline = OnlineMigrations.config.run_background_migrations_inline
376
- if run_inline && run_inline.call
375
+ if Utils.run_background_migrations_inline?
377
376
  runner = MigrationRunner.new(migration)
378
377
  runner.run_all_migration_jobs
379
378
  end
@@ -381,11 +380,26 @@ module OnlineMigrations
381
380
  migration
382
381
  end
383
382
 
383
+ # Removes the background migration for the given class name and arguments, if exists.
384
+ #
385
+ # @param migration_name [String, Class] Background migration job class name
386
+ # @param arguments [Array] Extra arguments the migration was originally created with
387
+ #
388
+ # @example
389
+ # remove_background_migration("BackfillProjectIssuesCount")
390
+ #
391
+ def remove_background_migration(migration_name, *arguments)
392
+ migration_name = migration_name.name if migration_name.is_a?(Class)
393
+ Migration.for_configuration(migration_name, arguments).delete_all
394
+ end
395
+
384
396
  # @private
385
397
  def create_background_migration(migration_name, *arguments, **options)
386
398
  options.assert_valid_keys(:batch_column_name, :min_value, :max_value, :batch_size, :sub_batch_size,
387
399
  :batch_pause, :sub_batch_pause_ms, :batch_max_attempts)
388
400
 
401
+ migration_name = migration_name.name if migration_name.is_a?(Class)
402
+
389
403
  migration = Migration.new(
390
404
  migration_name: migration_name,
391
405
  arguments: arguments,
@@ -44,7 +44,7 @@ module OnlineMigrations
44
44
  enum status: STATUSES.index_with(&:to_s)
45
45
  end
46
46
 
47
- delegate :migration_class, :migration_object, :migration_relation, :batch_column_name,
47
+ delegate :migration_name, :migration_class, :migration_object, :migration_relation, :batch_column_name,
48
48
  :arguments, :batch_pause, to: :migration
49
49
 
50
50
  belongs_to :migration
@@ -73,7 +73,10 @@ module OnlineMigrations
73
73
  status: self.class.statuses[:enqueued],
74
74
  attempts: 0,
75
75
  started_at: nil,
76
- finished_at: nil
76
+ finished_at: nil,
77
+ error_class: nil,
78
+ error_message: nil,
79
+ backtrace: nil
77
80
  )
78
81
  end
79
82
 
@@ -46,6 +46,7 @@ module OnlineMigrations
46
46
  )
47
47
 
48
48
  ::OnlineMigrations.config.background_migrations.error_handler.call(e, migration_job)
49
+ raise if Utils.run_background_migrations_inline?
49
50
  end
50
51
 
51
52
  private
@@ -527,6 +527,30 @@ module OnlineMigrations
527
527
  end
528
528
  alias add_belongs_to add_reference
529
529
 
530
+ def add_reference_concurrently(table_name, ref_name, **options)
531
+ # Always added by default in 5.0+
532
+ index = options.fetch(:index, true)
533
+
534
+ if index.is_a?(Hash) && index[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
535
+ raise_error :add_hash_index
536
+ end
537
+
538
+ foreign_key = options.fetch(:foreign_key, false)
539
+
540
+ if foreign_key
541
+ foreign_table_name = Utils.foreign_table_name(ref_name, options)
542
+ @foreign_key_tables << foreign_table_name.to_s
543
+ end
544
+
545
+ if !options[:polymorphic]
546
+ type = (options[:type] || :bigint).to_sym
547
+ column_name = "#{ref_name}_id"
548
+
549
+ foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
550
+ check_mismatched_foreign_key_type(table_name, column_name, type, **foreign_key_options)
551
+ end
552
+ end
553
+
530
554
  def add_index(table_name, column_name, **options)
531
555
  if options[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
532
556
  raise_error :add_hash_index
@@ -830,8 +854,14 @@ module OnlineMigrations
830
854
  if connection.table_exists?(foreign_table_name)
831
855
  primary_key = options[:primary_key] || connection.primary_key(foreign_table_name)
832
856
  primary_key_column = column_for(foreign_table_name, primary_key)
857
+ return if primary_key_column.nil?
858
+
859
+ primary_key_type = primary_key_column.sql_type.to_sym
860
+ # Having bigint foreign keys is safe and people should
861
+ # detect integer primary keys via some other tools.
862
+ return if type == :bigint && primary_key_type == :integer
833
863
 
834
- if primary_key_column && type != primary_key_column.sql_type.to_sym
864
+ if type != primary_key_type
835
865
  raise_error :mismatched_foreign_key_type,
836
866
  table_name: table_name, column_name: column_name
837
867
  end
@@ -26,6 +26,7 @@ module OnlineMigrations
26
26
  :add_reference_concurrently,
27
27
  :change_column_type_in_background,
28
28
  :enqueue_background_migration,
29
+ :remove_background_migration,
29
30
 
30
31
  # column type change helpers
31
32
  :initialize_column_type_change,
@@ -77,6 +78,10 @@ module OnlineMigrations
77
78
 
78
79
  include StraightReversions
79
80
 
81
+ def invert_add_reference_concurrently(args)
82
+ [:remove_reference, args]
83
+ end
84
+
80
85
  def invert_swap_column_names(args)
81
86
  table_name, column1, column2 = args
82
87
  [:swap_column_names, [table_name, column2, column1]]
@@ -110,7 +110,7 @@ A safer approach is to:
110
110
  1. ignore the column:
111
111
 
112
112
  class <%= model %> < ApplicationRecord
113
- self.ignored_columns = [\"<%= column_name %>\"]
113
+ self.ignored_columns += [\"<%= column_name %>\"]
114
114
  end
115
115
 
116
116
  2. deploy
@@ -151,7 +151,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
151
151
  <% if enumerate_columns_in_select_statements %>
152
152
  5. Ignore old column
153
153
 
154
- self.ignored_columns = [:<%= column_name %>]
154
+ self.ignored_columns += [:<%= column_name %>]
155
155
 
156
156
  6. Deploy
157
157
  7. Remove the column rename config from step 1
@@ -298,7 +298,7 @@ A safer approach is to:
298
298
  1. Ignore the column:
299
299
 
300
300
  class <%= model %> < ApplicationRecord
301
- self.ignored_columns = <%= columns %>
301
+ self.ignored_columns += <%= columns %>
302
302
  end
303
303
 
304
304
  2. Deploy
@@ -229,11 +229,9 @@ module OnlineMigrations
229
229
  end
230
230
 
231
231
  def lock_timeout(*)
232
- 0
233
232
  end
234
233
 
235
234
  def delay(*)
236
- 0
237
235
  end
238
236
 
239
237
  def with_lock_retries
@@ -151,6 +151,11 @@ module OnlineMigrations
151
151
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: env)
152
152
  db_config.reject(&:replica?).size > 1
153
153
  end
154
+
155
+ def run_background_migrations_inline?
156
+ run_inline = OnlineMigrations.config.run_background_migrations_inline
157
+ run_inline && run_inline.call
158
+ end
154
159
  end
155
160
  end
156
161
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.14.1"
4
+ VERSION = "0.15.0"
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.14.1
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-21 00:00:00.000000000 Z
11
+ date: 2024-03-19 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/templates/add_sharding_to_online_migrations.rb.tt
42
42
  - lib/generators/online_migrations/templates/background_migration.rb.tt
43
43
  - lib/generators/online_migrations/templates/initializer.rb.tt
44
+ - lib/generators/online_migrations/templates/install_migration.rb.tt
44
45
  - lib/generators/online_migrations/templates/migration.rb.tt
45
46
  - lib/generators/online_migrations/upgrade_generator.rb
46
47
  - lib/online_migrations.rb