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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/README.md +34 -31
- data/docs/background_migrations.md +36 -4
- data/docs/configuring.md +3 -2
- data/lib/generators/online_migrations/install_generator.rb +3 -7
- data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +18 -0
- data/lib/generators/online_migrations/templates/initializer.rb.tt +5 -3
- data/lib/generators/online_migrations/templates/migration.rb.tt +8 -3
- data/lib/generators/online_migrations/upgrade_generator.rb +33 -0
- data/lib/online_migrations/background_migrations/application_record.rb +13 -0
- data/lib/online_migrations/background_migrations/backfill_column.rb +1 -1
- data/lib/online_migrations/background_migrations/copy_column.rb +6 -20
- data/lib/online_migrations/background_migrations/delete_orphaned_records.rb +2 -20
- data/lib/online_migrations/background_migrations/migration.rb +123 -34
- data/lib/online_migrations/background_migrations/migration_helpers.rb +0 -4
- data/lib/online_migrations/background_migrations/migration_job.rb +15 -12
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +2 -2
- data/lib/online_migrations/background_migrations/migration_runner.rb +56 -11
- data/lib/online_migrations/background_migrations/reset_counters.rb +3 -9
- data/lib/online_migrations/background_migrations/scheduler.rb +5 -15
- data/lib/online_migrations/change_column_type_helpers.rb +68 -83
- data/lib/online_migrations/command_checker.rb +11 -29
- data/lib/online_migrations/config.rb +7 -15
- data/lib/online_migrations/copy_trigger.rb +15 -10
- data/lib/online_migrations/error_messages.rb +13 -25
- data/lib/online_migrations/foreign_keys_collector.rb +2 -2
- data/lib/online_migrations/indexes_collector.rb +3 -3
- data/lib/online_migrations/lock_retrier.rb +4 -9
- data/lib/online_migrations/schema_cache.rb +0 -6
- data/lib/online_migrations/schema_dumper.rb +1 -1
- data/lib/online_migrations/schema_statements.rb +64 -256
- data/lib/online_migrations/utils.rb +18 -56
- data/lib/online_migrations/verbose_sql_logs.rb +3 -2
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +7 -6
- metadata +8 -7
- data/lib/online_migrations/background_migrations/advisory_lock.rb +0 -62
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9ceda570f99a1712496ac5c9549859c7dc2cad7e3cc4ab59c97a22ba17f3bfd0
|
4
|
+
data.tar.gz: dfc5a83147a92bf5e04a532210e72b6f32f561f032b2a1b746ff142bf4e16635
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
20
|
-
- Rails
|
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.
|
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
|
-
|
365
|
-
|
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
|
-
|
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
|
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
|
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
|
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
|
-
|
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.
|
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
|
-
|
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(
|
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.
|
23
|
+
"ActiveRecord::Migration[#{Utils.ar_version}]"
|
24
24
|
end
|
25
25
|
|
26
26
|
def start_after
|
27
|
-
self.class.current_migration_number(
|
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 `
|
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 `
|
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.
|
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.
|
17
|
+
t.string :shard
|
18
|
+
t.boolean :composite, default: false, null: false
|
19
|
+
t.timestamps
|
17
20
|
|
18
|
-
t.
|
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
|
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
|
-
|
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
|
-
|
28
|
+
model
|
29
29
|
.unscoped
|
30
|
-
.where(copy_to.
|
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
|
47
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|