online_migrations 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/README.md +34 -31
  4. data/docs/background_migrations.md +36 -4
  5. data/docs/configuring.md +3 -2
  6. data/lib/generators/online_migrations/install_generator.rb +3 -7
  7. data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +18 -0
  8. data/lib/generators/online_migrations/templates/initializer.rb.tt +5 -3
  9. data/lib/generators/online_migrations/templates/migration.rb.tt +8 -3
  10. data/lib/generators/online_migrations/upgrade_generator.rb +33 -0
  11. data/lib/online_migrations/background_migrations/application_record.rb +13 -0
  12. data/lib/online_migrations/background_migrations/backfill_column.rb +1 -1
  13. data/lib/online_migrations/background_migrations/copy_column.rb +6 -20
  14. data/lib/online_migrations/background_migrations/delete_orphaned_records.rb +2 -20
  15. data/lib/online_migrations/background_migrations/migration.rb +123 -34
  16. data/lib/online_migrations/background_migrations/migration_helpers.rb +0 -4
  17. data/lib/online_migrations/background_migrations/migration_job.rb +15 -12
  18. data/lib/online_migrations/background_migrations/migration_job_runner.rb +2 -2
  19. data/lib/online_migrations/background_migrations/migration_runner.rb +56 -11
  20. data/lib/online_migrations/background_migrations/reset_counters.rb +3 -9
  21. data/lib/online_migrations/background_migrations/scheduler.rb +5 -15
  22. data/lib/online_migrations/change_column_type_helpers.rb +68 -83
  23. data/lib/online_migrations/command_checker.rb +11 -29
  24. data/lib/online_migrations/config.rb +7 -15
  25. data/lib/online_migrations/copy_trigger.rb +15 -10
  26. data/lib/online_migrations/error_messages.rb +13 -25
  27. data/lib/online_migrations/foreign_keys_collector.rb +2 -2
  28. data/lib/online_migrations/indexes_collector.rb +3 -3
  29. data/lib/online_migrations/lock_retrier.rb +4 -9
  30. data/lib/online_migrations/schema_cache.rb +0 -6
  31. data/lib/online_migrations/schema_dumper.rb +1 -1
  32. data/lib/online_migrations/schema_statements.rb +64 -256
  33. data/lib/online_migrations/utils.rb +18 -56
  34. data/lib/online_migrations/verbose_sql_logs.rb +3 -2
  35. data/lib/online_migrations/version.rb +1 -1
  36. data/lib/online_migrations.rb +7 -6
  37. metadata +8 -7
  38. data/lib/online_migrations/background_migrations/advisory_lock.rb +0 -62
  39. data/lib/online_migrations/foreign_key_definition.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 966629fafbc4d8c9de88240bc705799f66ab205754cd1acc99a1ae129d737de5
4
- data.tar.gz: 61379d6eeec17e532314551d5a643ed465ba23bac69739be063f2fd704657451
3
+ metadata.gz: 9ceda570f99a1712496ac5c9549859c7dc2cad7e3cc4ab59c97a22ba17f3bfd0
4
+ data.tar.gz: dfc5a83147a92bf5e04a532210e72b6f32f561f032b2a1b746ff142bf4e16635
5
5
  SHA512:
6
- metadata.gz: ae49cb8499bc7b086d9b1883106926d150a1227f495e1aff7e1f259197d68065a20b819797d5e7c9199d413be746e9deb94f7c9bc6005a53b9b94bbefd2d88cb
7
- data.tar.gz: 4c36ad512328174d04600b7d10001721ed985ab73184cbfd7c2f2cde70788a32ef4a4e8de754134206510b29e17920da06820903f86a809766aa3c5aa4005408
6
+ metadata.gz: 0ff6cce820134ef6b893f9405b71a0d8f8cd42dd79616123aaa492983671ebcff98dbff21477afa78b29342d40098822da4b8de0306489faf956b21ee44e2113
7
+ data.tar.gz: 42dcf132290c9affe812ef7489397d4c05d48f33069f3ee09710fced6aaf8ac928fee63c1498333657a824f98885e3af5e706e4dcc4a089b9cd62284ad0223d1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.11.0 (2024-01-09)
4
+
5
+ - Support sharding for background migrations
6
+
7
+ Now, if a `relation` inside background migration definition is defined on a sharded model,
8
+ then that background migration would automatically run on all the shards.
9
+
10
+ To get all the new sharding related schema changes, you need to run:
11
+
12
+ ```sh
13
+ $ bin/rails generate online_migrations:upgrade
14
+ $ bin/rails db:migrate
15
+ ```
16
+
17
+ - Change background migration `progress` to return values in range from 0.0 to 100.0
18
+
19
+ Previously, these were values in range from 0.0 to 1.0 and caused confusion
20
+
21
+ - Copy exclusion constraints when changing column type
22
+ - Update `revert_finalize_columns_type_change` to not remove indexes, foreign keys etc
23
+ - Fix verbose query logging when `ActiveRecord::Base.logger` is `nil`
24
+ - Add a shortcut for running background migrations
25
+
26
+ ```ruby
27
+ # Before:
28
+ OnlineMigrations::BackgroundMigrations::Scheduler.run
29
+ # After
30
+ OnlineMigrations.run_background_migrations
31
+ ```
32
+
33
+ - Add support for `:type_cast_function` to `initialize_column_type_change` helper
34
+ - Drop support for Ruby < 2.7 and Rails < 6.1
35
+
3
36
  ## 0.10.0 (2023-12-12)
4
37
 
5
38
  - Add `auto_analyze` configuration option
data/README.md CHANGED
@@ -16,10 +16,12 @@ See [comparison to `strong_migrations`](#comparison-to-strong_migrations)
16
16
 
17
17
  ## Requirements
18
18
 
19
- - Ruby 2.1+
20
- - Rails 4.2+
19
+ - Ruby 2.7+
20
+ - Rails 6.1+
21
21
  - PostgreSQL 9.6+
22
22
 
23
+ For older Ruby and Rails versions you can use '< 0.11' version of this gem.
24
+
23
25
  **Note**: Since some migration helpers use database `VIEW`s to implement their logic, it is recommended to use `structure.sql` schema format, or otherwise add some gem (like [scenic](https://github.com/scenic-views/scenic)) to be able to dump them into the `schema.rb`.
24
26
 
25
27
  ## Installation
@@ -35,10 +37,20 @@ And then run:
35
37
  ```sh
36
38
  $ bundle install
37
39
  $ bin/rails generate online_migrations:install
40
+ $ bin/rails db:migrate
38
41
  ```
39
42
 
40
43
  **Note**: If you do not have plans on using [background migrations](docs/background_migrations.md) feature, then you can delete the generated migration and regenerate it later, if needed.
41
44
 
45
+ ### Upgrading
46
+
47
+ If you're already using [background migrations](docs/background_migrations.md), your background migrations tables may require additional columns. After every upgrade run:
48
+
49
+ ```sh
50
+ $ bin/rails generate online_migrations:upgrade
51
+ $ bin/rails db:migrate
52
+ ```
53
+
42
54
  ## Motivation
43
55
 
44
56
  Writing a safe migration can be daunting. Numerous articles have been written on the topic and a few gems are trying to address the problem. Even for someone who has a pretty good command of PostgreSQL, remembering all the subtleties of explicit locking can be problematic.
@@ -178,17 +190,9 @@ end
178
190
  1. Ignore the column:
179
191
 
180
192
  ```ruby
181
- # For Active Record 5+
182
193
  class User < ApplicationRecord
183
194
  self.ignored_columns = ["name"]
184
195
  end
185
-
186
- # For Active Record < 5
187
- class User < ActiveRecord::Base
188
- def self.columns
189
- super.reject { |c| c.name == "name" }
190
- end
191
- end
192
196
  ```
193
197
 
194
198
  2. Deploy
@@ -316,6 +320,17 @@ Type | Safe Changes
316
320
 
317
321
  :white_check_mark: **Good**
318
322
 
323
+ #### "Classic" approach (abstract)
324
+
325
+ 1. Create a new column
326
+ 2. Write to both columns
327
+ 3. Backfill data from the old column to the new column
328
+ 4. Move reads from the old column to the new column
329
+ 5. Stop writing to the old column
330
+ 6. Drop the old column
331
+
332
+ #### :bullettrain_side: Concrete steps for Active Record
333
+
319
334
  **Note**: The following steps can also be used to change the primary key's type (e.g., from `integer` to `bigint`).
320
335
 
321
336
  A safer approach can be accomplished in several steps:
@@ -349,7 +364,10 @@ A safer approach can be accomplished in several steps:
349
364
  end
350
365
  ```
351
366
 
352
- 3. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
367
+ 3. Make sure your application works with values in both formats (when read from the database, converting
368
+ during writes works automatically). For most column type changes, this does not need any updates in the app.
369
+ 4. Deploy
370
+ 5. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
353
371
 
354
372
  ```ruby
355
373
  class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7.1]
@@ -361,8 +379,8 @@ A safer approach can be accomplished in several steps:
361
379
  end
362
380
  ```
363
381
 
364
- 4. Deploy
365
- 5. Finally, if everything is working as expected, remove copy trigger and old column:
382
+ 6. Deploy
383
+ 7. Finally, if everything works as expected, remove copy trigger and old column:
366
384
 
367
385
  ```ruby
368
386
  class CleanupChangeFilesSizeType < ActiveRecord::Migration[7.1]
@@ -376,7 +394,8 @@ A safer approach can be accomplished in several steps:
376
394
  end
377
395
  ```
378
396
 
379
- 6. Deploy
397
+ 8. Remove changes from step 3, if any
398
+ 9. Deploy
380
399
 
381
400
  ### Renaming a column
382
401
 
@@ -468,17 +487,9 @@ It will use a combination of a VIEW and column aliasing to work with both column
468
487
  (is disabled by default in Active Record >= 7), then you need to ignore old column:
469
488
 
470
489
  ```ruby
471
- # For Active Record 5+
472
490
  class User < ApplicationRecord
473
491
  self.ignored_columns = ["name"]
474
492
  end
475
-
476
- # For Active Record < 5
477
- class User < ActiveRecord::Base
478
- def self.columns
479
- super.reject { |c| c.name == "name" }
480
- end
481
- end
482
493
  ```
483
494
 
484
495
  6. Deploy
@@ -1026,7 +1037,7 @@ end
1026
1037
 
1027
1038
  :x: **Bad**
1028
1039
 
1029
- Adding multiple foreign keys in a single migration blocks reads and writes on all involved tables until migration is completed.
1040
+ Adding multiple foreign keys in a single migration blocks writes on all involved tables until migration is completed.
1030
1041
  Avoid adding foreign key more than once per migration file, unless the source and target tables are identical.
1031
1042
 
1032
1043
  ```ruby
@@ -1160,17 +1171,9 @@ A safer approach is to:
1160
1171
  1. ignore the column:
1161
1172
 
1162
1173
  ```ruby
1163
- # For Active Record 5+
1164
1174
  class User < ApplicationRecord
1165
1175
  self.ignored_columns = ["type"]
1166
1176
  end
1167
-
1168
- # For Active Record < 5
1169
- class User < ActiveRecord::Base
1170
- def self.columns
1171
- super.reject { |c| c.name == "type" }
1172
- end
1173
- end
1174
1177
  ```
1175
1178
 
1176
1179
  2. deploy
@@ -18,7 +18,7 @@ Start a background migrations scheduler. For example, to run it on cron using [w
18
18
 
19
19
  ```ruby
20
20
  every 1.minute do
21
- runner "OnlineMigrations::BackgroundMigrations::Scheduler.run"
21
+ runner "OnlineMigrations.run_background_migrations"
22
22
  end
23
23
  ```
24
24
 
@@ -116,13 +116,15 @@ enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
116
116
 
117
117
  ## Predefined background migrations
118
118
 
119
- * `BackfillColumn` - backfills column(s) with scalar values (enqueue using `backfill_column_in_background`)
119
+ * `BackfillColumn` - backfills column(s) with scalar values (enqueue using `backfill_column_in_background`; or `backfill_column_for_type_change_in_background` if backfilling column for which type change is in progress)
120
120
  * `CopyColumn` - copies data from one column(s) to other(s) (enqueue using `copy_column_in_background`)
121
121
  * `DeleteAssociatedRecords` - deletes records associated with a parent object (enqueue using `delete_associated_records_in_background`)
122
122
  * `DeleteOrphanedRecords` - deletes records with one or more missing relations (enqueue using `delete_orphaned_records_in_background`)
123
123
  * `PerformActionOnRelation` - performs specific action on a relation or individual records (enqueue using `perform_action_on_relation_in_background`)
124
124
  * `ResetCounters` - resets one or more counter caches to their correct value (enqueue using `reset_counters_in_background`)
125
125
 
126
+ **Note**: These migration helpers should be run inside the migration against the database where background migrations tables are defined.
127
+
126
128
  ## Testing
127
129
 
128
130
  At a minimum, it's recommended that the `#process_batch` method in your background migration is tested. You may also want to test the `#relation` and `#count` methods if they are sufficiently complex.
@@ -221,7 +223,7 @@ To get the progress (assuming `#count` method on background migration class was
221
223
 
222
224
  ```ruby
223
225
  migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
224
- migration.progress # value from 0 to 1.0
226
+ migration.progress # value from 0 to 100.0
225
227
  ```
226
228
 
227
229
  **Note**: It will be easier to work with background migrations through some kind of Web UI, but until it is implemented, we can work with them only manually.
@@ -230,7 +232,19 @@ migration.progress # value from 0 to 1.0
230
232
 
231
233
  There are a few configurable options for the Background Migrations. Custom configurations should be placed in a `online_migrations.rb` initializer.
232
234
 
233
- **Note**: Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_migrations/config.rb) for the list of all available configuration options.
235
+ Check the [source code](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/background_migrations/config.rb) for the list of all available configuration options.
236
+
237
+ **Note**: You can dynamically change certain migration parameters while the migration is run.
238
+ For example,
239
+ ```ruby
240
+ migration = OnlineMigrations::BackgroundMigrations::Migration.find(id)
241
+ migration.update!(
242
+ batch_size: 50_000, # The # of records migration will update per run
243
+ sub_batch_size: 10_000, # The # of records migration will update via single `UPDATE`
244
+ batch_pause: 1.second, # Minimum time (in seconds) between successive migration runs
245
+ sub_batch_pause_ms: 20 # Minimum time (in ms) between successive migration `UPDATE`s
246
+ )
247
+ ```
234
248
 
235
249
  ### Throttling
236
250
 
@@ -294,3 +308,21 @@ OnlineMigrations.config.background_migrations.backtrace_cleaner = cleaner
294
308
  ```
295
309
 
296
310
  If none is specified, the default `Rails.backtrace_cleaner` will be used to clean backtraces.
311
+
312
+ ### Multiple databases and sharding
313
+
314
+ If you have multiple databases or sharding, you may need to configure where background migrations related tables live
315
+ by configuring the parent model:
316
+
317
+ ```ruby
318
+ # config/initializers/online_migrations.rb
319
+
320
+ # Referring to one of the databases
321
+ OnlineMigrations::BackgroundMigrations::ApplicationRecord.connects_to database: { writing: :animals }
322
+
323
+ # Referring to one of the shards (via `:database` option)
324
+ OnlineMigrations::BackgroundMigrations::ApplicationRecord.connects_to database: { writing: :shard_one }
325
+ ```
326
+
327
+ By default, ActiveRecord uses the database config named `:primary` (if exists) under the environment section from the `database.yml`.
328
+ Otherwise, the first config under the environment section is used.
data/docs/configuring.md CHANGED
@@ -86,14 +86,15 @@ config.lock_retrier = OnlineMigrations::ExponentialLockRetrier.new(
86
86
  attempts: 30, # attempt 30 retries
87
87
  base_delay: 0.01.seconds, # starting with delay of 10ms between each unsuccessful try, increasing exponentially
88
88
  max_delay: 1.minute, # maximum delay is 1 minute
89
- lock_timeout: 0.05.seconds # and 50ms set as lock timeout for each try
89
+ lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try
90
90
  )
91
91
  ```
92
92
 
93
93
  When statement within transaction fails - the whole transaction is retried.
94
94
 
95
95
  To permanently disable lock retries, you can set `lock_retrier` to `nil`.
96
- To temporarily disable lock retries while running migrations, set `DISABLE_LOCK_RETRIES` env variable.
96
+
97
+ 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.
97
98
 
98
99
  **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.
99
100
 
@@ -15,20 +15,16 @@ module OnlineMigrations
15
15
  end
16
16
 
17
17
  def create_migration_file
18
- migration_template("migration.rb", File.join(migrations_dir, "install_online_migrations.rb"))
18
+ migration_template("migration.rb", File.join(db_migrate_path, "install_online_migrations.rb"))
19
19
  end
20
20
 
21
21
  private
22
22
  def migration_parent
23
- Utils.migration_parent_string
23
+ "ActiveRecord::Migration[#{Utils.ar_version}]"
24
24
  end
25
25
 
26
26
  def start_after
27
- self.class.current_migration_number(migrations_dir)
28
- end
29
-
30
- def migrations_dir
31
- Utils.ar_version >= 5.1 ? db_migrate_path : "db/migrate"
27
+ self.class.current_migration_number(db_migrate_path)
32
28
  end
33
29
  end
34
30
  end
@@ -0,0 +1,18 @@
1
+ class AddShardingToOnlineMigrations < <%= migration_parent %>
2
+ def change
3
+ safety_assured do
4
+ remove_index :background_migrations, [:migration_name, :arguments], unique: true
5
+
6
+ change_table :background_migrations do |t|
7
+ t.bigint :parent_id
8
+ t.string :shard
9
+ t.boolean :composite, default: false, null: false
10
+
11
+ t.foreign_key :background_migrations, column: :parent_id, on_delete: :cascade
12
+
13
+ t.index [:migration_name, :arguments, :shard],
14
+ unique: true, name: :index_background_migrations_on_unique_configuration
15
+ end
16
+ end
17
+ end
18
+ end
@@ -31,12 +31,14 @@ OnlineMigrations.configure do |config|
31
31
  # config.alphabetize_schema = true
32
32
 
33
33
  # Disable specific checks.
34
- # For the list of available checks look at `lib/error_messages` folder.
34
+ # For the list of available checks look at the `error_messages.rb` file inside
35
+ # the `online_migrations` gem.
35
36
  # config.disable_check(:remove_index)
36
37
 
37
38
  # Enable specific checks. All checks are enabled by default,
38
39
  # but this may change in the future.
39
- # For the list of available checks look at `lib/error_messages` folder.
40
+ # For the list of available checks look at the `error_messages.rb` file inside
41
+ # the `online_migrations` gem.
40
42
  # config.enable_check(:remove_index)
41
43
 
42
44
  # Configure whether to log every SQL query happening in a migration.
@@ -55,7 +57,7 @@ OnlineMigrations.configure do |config|
55
57
  attempts: 30, # attempt 30 retries
56
58
  base_delay: 0.01.seconds, # starting with delay of 10ms between each unsuccessful try, increasing exponentially
57
59
  max_delay: 1.minute, # up to the maximum delay of 1 minute
58
- lock_timeout: 0.05.seconds # and 50ms set as lock timeout for each try
60
+ lock_timeout: 0.2.seconds # and 200ms set as lock timeout for each try
59
61
  )
60
62
 
61
63
  # Configure tables that are in the process of being renamed.
@@ -1,6 +1,7 @@
1
1
  class InstallOnlineMigrations < <%= migration_parent %>
2
2
  def change
3
3
  create_table :background_migrations do |t|
4
+ t.bigint :parent_id
4
5
  t.string :migration_name, null: false
5
6
  t.jsonb :arguments, default: [], null: false
6
7
  t.string :batch_column_name, null: false
@@ -13,9 +14,13 @@ class InstallOnlineMigrations < <%= migration_parent %>
13
14
  t.integer :sub_batch_pause_ms, null: false
14
15
  t.integer :batch_max_attempts, null: false
15
16
  t.string :status, default: "enqueued", null: false
16
- t.timestamps null: false
17
+ t.string :shard
18
+ t.boolean :composite, default: false, null: false
19
+ t.timestamps
17
20
 
18
- t.index [:migration_name, :arguments],
21
+ t.foreign_key :background_migrations, column: :parent_id, on_delete: :cascade
22
+
23
+ t.index [:migration_name, :arguments, :shard],
19
24
  unique: true, name: :index_background_migrations_on_unique_configuration
20
25
  end
21
26
 
@@ -34,7 +39,7 @@ class InstallOnlineMigrations < <%= migration_parent %>
34
39
  t.string :error_class
35
40
  t.string :error_message
36
41
  t.string :backtrace, array: true
37
- t.timestamps null: false
42
+ t.timestamps
38
43
 
39
44
  t.foreign_key :background_migrations, column: :migration_id, on_delete: :cascade
40
45
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+ require "rails/generators/active_record/migration"
5
+
6
+ module OnlineMigrations
7
+ # @private
8
+ class UpgradeGenerator < Rails::Generators::Base
9
+ include ActiveRecord::Generators::Migration
10
+
11
+ source_root File.expand_path("templates", __dir__)
12
+
13
+ def copy_templates
14
+ migrations_to_be_applied.each do |migration|
15
+ migration_template("#{migration}.rb", File.join(db_migrate_path, "#{migration}.rb"))
16
+ end
17
+ end
18
+
19
+ private
20
+ def migrations_to_be_applied
21
+ connection = BackgroundMigrations::Migration.connection
22
+ columns = connection.columns(BackgroundMigrations::Migration.table_name).map(&:name)
23
+
24
+ migrations = []
25
+ migrations << "add_sharding_to_online_migrations" if !columns.include?("shard")
26
+ migrations
27
+ end
28
+
29
+ def migration_parent
30
+ "ActiveRecord::Migration[#{Utils.ar_version}]"
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OnlineMigrations
4
+ module BackgroundMigrations
5
+ # Base class for all records used by this gem.
6
+ #
7
+ # Can be extended to setup different database where all tables related to
8
+ # online_migrations will live.
9
+ class ApplicationRecord < ActiveRecord::Base
10
+ self.abstract_class = true
11
+ end
12
+ end
13
+ end
@@ -22,7 +22,7 @@ module OnlineMigrations
22
22
  quoted_column = connection.quote_column_name(column)
23
23
  model.unscoped.where("#{quoted_column} != ? OR #{quoted_column} IS NULL", value)
24
24
  else
25
- Utils.ar_where_not_multiple_conditions(model.unscoped, updates)
25
+ model.unscoped.where.not(updates)
26
26
  end
27
27
  end
28
28
 
@@ -25,14 +25,10 @@ module OnlineMigrations
25
25
  end
26
26
 
27
27
  def relation
28
- relation = model
28
+ model
29
29
  .unscoped
30
- .where(copy_to.map { |to_column| [to_column, nil] }.to_h)
31
-
32
- Utils.ar_where_not_multiple_conditions(
33
- relation,
34
- copy_from.map { |from_column| [from_column, nil] }.to_h
35
- )
30
+ .where(copy_to.index_with(nil))
31
+ .where.not(copy_from.index_with(nil))
36
32
  end
37
33
 
38
34
  def process_batch(relation)
@@ -43,13 +39,8 @@ module OnlineMigrations
43
39
  old_value = arel_table[from_column]
44
40
  if (type_cast_function = type_cast_functions[from_column])
45
41
  old_value =
46
- if type_cast_function =~ /\A\w+\z/
47
- if Utils.ar_version <= 5.2
48
- # Active Record <= 5.2 does not support quoting of Arel::Nodes::NamedFunction
49
- Arel.sql("#{type_cast_function}(#{connection.quote_column_name(from_column)})")
50
- else
51
- Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
52
- end
42
+ if type_cast_function.match?(/\A\w+\z/)
43
+ Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
53
44
  else
54
45
  # We got a cast expression.
55
46
  Arel.sql(type_cast_function)
@@ -58,12 +49,7 @@ module OnlineMigrations
58
49
  old_value
59
50
  end
60
51
 
61
- if Utils.ar_version <= 4.2
62
- stmt = Arel::UpdateManager.new(arel.engine)
63
- else
64
- stmt = Arel::UpdateManager.new
65
- end
66
-
52
+ stmt = Arel::UpdateManager.new
67
53
  stmt.table(arel_table)
68
54
  stmt.wheres = arel.constraints
69
55
 
@@ -12,29 +12,11 @@ module OnlineMigrations
12
12
  end
13
13
 
14
14
  def relation
15
- # For Active Record 6.1+ we can use `where.missing`
16
- # https://github.com/rails/rails/pull/34727
17
- associations.inject(model.unscoped) do |relation, association|
18
- reflection = model.reflect_on_association(association)
19
- if reflection.nil?
20
- raise ArgumentError, "'#{model.name}' has no association called '#{association}'"
21
- end
22
-
23
- # left_joins was added in Active Record 5.0 - https://github.com/rails/rails/pull/12071
24
- relation
25
- .left_joins(association)
26
- .where(reflection.table_name => { reflection.association_primary_key => nil })
27
- end
15
+ model.unscoped.where.missing(*associations)
28
16
  end
29
17
 
30
18
  def process_batch(relation)
31
- if Utils.ar_version > 5.0
32
- relation.delete_all
33
- else
34
- # Older Active Record generates incorrect query when running delete_all
35
- primary_key = model.primary_key
36
- model.unscoped.where(primary_key => relation.select(primary_key)).delete_all
37
- end
19
+ relation.delete_all
38
20
  end
39
21
 
40
22
  def count