online_migrations 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +144 -142
- data/docs/background_migrations.md +7 -6
- data/docs/configuring.md +20 -16
- data/lib/generators/online_migrations/templates/initializer.rb.tt +7 -0
- data/lib/online_migrations/background_migrations/copy_column.rb +12 -6
- data/lib/online_migrations/change_column_type_helpers.rb +5 -5
- data/lib/online_migrations/command_checker.rb +40 -18
- data/lib/online_migrations/config.rb +12 -0
- data/lib/online_migrations/schema_dumper.rb +21 -0
- data/lib/online_migrations/schema_statements.rb +16 -0
- data/lib/online_migrations/utils.rb +19 -0
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 966629fafbc4d8c9de88240bc705799f66ab205754cd1acc99a1ae129d737de5
|
4
|
+
data.tar.gz: 61379d6eeec17e532314551d5a643ed465ba23bac69739be063f2fd704657451
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae49cb8499bc7b086d9b1883106926d150a1227f495e1aff7e1f259197d68065a20b819797d5e7c9199d413be746e9deb94f7c9bc6005a53b9b94bbefd2d88cb
|
7
|
+
data.tar.gz: 4c36ad512328174d04600b7d10001721ed985ab73184cbfd7c2f2cde70788a32ef4a4e8de754134206510b29e17920da06820903f86a809766aa3c5aa4005408
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.10.0 (2023-12-12)
|
4
|
+
|
5
|
+
- Add `auto_analyze` configuration option
|
6
|
+
- Add `alphabetize_schema` configuration option
|
7
|
+
- Fix `backfill_column_for_type_change_in_background` for cast expressions
|
8
|
+
- Fix copying indexes with long names when changing column type
|
9
|
+
- Enhance error messages with the link to the detailed description
|
10
|
+
|
3
11
|
## 0.9.2 (2023-11-02)
|
4
12
|
|
5
13
|
- Fix checking which expression indexes to copy when changing column type
|
data/README.md
CHANGED
@@ -90,7 +90,7 @@ class AddAdminToUsers < ActiveRecord::Migration[7.1]
|
|
90
90
|
execute "SET statement_timeout TO '5s'"
|
91
91
|
change_column_null :users, :admin, false
|
92
92
|
end
|
93
|
-
|
93
|
+
|
94
94
|
def down
|
95
95
|
remove_column :users, :admin
|
96
96
|
end
|
@@ -177,30 +177,30 @@ end
|
|
177
177
|
|
178
178
|
1. Ignore the column:
|
179
179
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
180
|
+
```ruby
|
181
|
+
# For Active Record 5+
|
182
|
+
class User < ApplicationRecord
|
183
|
+
self.ignored_columns = ["name"]
|
184
|
+
end
|
185
185
|
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
+
```
|
193
193
|
|
194
194
|
2. Deploy
|
195
195
|
3. Wrap column removing in a `safety_assured` block:
|
196
196
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
197
|
+
```ruby
|
198
|
+
class RemoveNameFromUsers < ActiveRecord::Migration[7.1]
|
199
|
+
def change
|
200
|
+
safety_assured { remove_column :users, :name }
|
201
|
+
end
|
202
|
+
end
|
203
|
+
```
|
204
204
|
|
205
205
|
4. Remove column ignoring from `User` model
|
206
206
|
5. Deploy
|
@@ -322,59 +322,59 @@ A safer approach can be accomplished in several steps:
|
|
322
322
|
|
323
323
|
1. Create a new column and keep column's data in sync:
|
324
324
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
325
|
+
```ruby
|
326
|
+
class InitializeChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
327
|
+
def change
|
328
|
+
initialize_column_type_change :files, :size, :bigint
|
329
|
+
end
|
330
|
+
end
|
331
|
+
```
|
332
332
|
|
333
|
-
**Note**: `initialize_column_type_change` accepts additional options (like `:limit`, `:default` etc)
|
334
|
-
which will be passed to `add_column` when creating a new column, so you can override previous values.
|
333
|
+
**Note**: `initialize_column_type_change` accepts additional options (like `:limit`, `:default` etc)
|
334
|
+
which will be passed to `add_column` when creating a new column, so you can override previous values.
|
335
335
|
|
336
336
|
2. Backfill data from the old column to the new column:
|
337
337
|
|
338
|
-
|
339
|
-
|
340
|
-
|
338
|
+
```ruby
|
339
|
+
class BackfillChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
340
|
+
disable_ddl_transaction!
|
341
341
|
|
342
|
-
|
343
|
-
|
344
|
-
|
342
|
+
def up
|
343
|
+
backfill_column_for_type_change :files, :size
|
344
|
+
end
|
345
345
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
346
|
+
def down
|
347
|
+
# no op
|
348
|
+
end
|
349
|
+
end
|
350
|
+
```
|
351
351
|
|
352
352
|
3. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
|
353
353
|
|
354
|
-
|
355
|
-
|
356
|
-
|
354
|
+
```ruby
|
355
|
+
class FinalizeChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
356
|
+
disable_ddl_transaction!
|
357
357
|
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
358
|
+
def change
|
359
|
+
finalize_column_type_change :files, :size
|
360
|
+
end
|
361
|
+
end
|
362
|
+
```
|
363
363
|
|
364
364
|
4. Deploy
|
365
365
|
5. Finally, if everything is working as expected, remove copy trigger and old column:
|
366
366
|
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
367
|
+
```ruby
|
368
|
+
class CleanupChangeFilesSizeType < ActiveRecord::Migration[7.1]
|
369
|
+
def up
|
370
|
+
cleanup_column_type_change :files, :size
|
371
|
+
end
|
372
372
|
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
373
|
+
def down
|
374
|
+
initialize_column_type_change :files, :size, :integer
|
375
|
+
end
|
376
|
+
end
|
377
|
+
```
|
378
378
|
|
379
379
|
6. Deploy
|
380
380
|
|
@@ -430,67 +430,69 @@ To work around this limitation, we need to tell Active Record to acquire this in
|
|
430
430
|
|
431
431
|
1. Instruct Rails that you are going to rename a column:
|
432
432
|
|
433
|
-
```ruby
|
434
|
-
OnlineMigrations.config.column_renames = {
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
}
|
439
|
-
```
|
440
|
-
NOTE: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
|
441
|
-
until the process of column rename is fully done.
|
442
|
-
```ruby
|
443
|
-
# config/application.rb
|
444
|
-
# For Active Record >= 7
|
445
|
-
config.active_record.partial_inserts = true
|
433
|
+
```ruby
|
434
|
+
OnlineMigrations.config.column_renames = {
|
435
|
+
"users" => {
|
436
|
+
"name" => "first_name"
|
437
|
+
}
|
438
|
+
}
|
439
|
+
```
|
446
440
|
|
447
|
-
|
448
|
-
|
449
|
-
|
441
|
+
**Note**: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
|
442
|
+
until the process of column rename is fully done.
|
443
|
+
|
444
|
+
```ruby
|
445
|
+
# config/application.rb
|
446
|
+
# For Active Record >= 7
|
447
|
+
config.active_record.partial_inserts = true
|
448
|
+
|
449
|
+
# Or for Active Record < 7
|
450
|
+
config.active_record.partial_writes = true
|
451
|
+
```
|
450
452
|
|
451
453
|
2. Deploy
|
452
454
|
3. Tell the database that you are going to rename a column. This will not actually rename any columns,
|
453
455
|
nor any data/indexes/foreign keys copying will be made, so will be instantaneous.
|
454
456
|
It will use a combination of a VIEW and column aliasing to work with both column names simultaneously
|
455
457
|
|
456
|
-
```ruby
|
457
|
-
class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
end
|
462
|
-
```
|
458
|
+
```ruby
|
459
|
+
class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
460
|
+
def change
|
461
|
+
initialize_column_rename :users, :name, :first_name
|
462
|
+
end
|
463
|
+
end
|
464
|
+
```
|
463
465
|
|
464
466
|
4. Replace usages of the old column with a new column in the codebase
|
465
467
|
5. If you enabled Active Record `enumerate_columns_in_select_statements` setting in your application
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
468
|
+
(is disabled by default in Active Record >= 7), then you need to ignore old column:
|
469
|
+
|
470
|
+
```ruby
|
471
|
+
# For Active Record 5+
|
472
|
+
class User < ApplicationRecord
|
473
|
+
self.ignored_columns = ["name"]
|
474
|
+
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
|
+
```
|
481
483
|
|
482
484
|
6. Deploy
|
483
485
|
7. Remove the column rename config from step 1
|
484
486
|
8. Remove the column ignore from step 5, if added
|
485
487
|
9. Remove the VIEW created in step 3 and finally rename the column:
|
486
488
|
|
487
|
-
```ruby
|
488
|
-
class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
end
|
493
|
-
```
|
489
|
+
```ruby
|
490
|
+
class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.1]
|
491
|
+
def change
|
492
|
+
finalize_column_rename :users, :name, :first_name
|
493
|
+
end
|
494
|
+
end
|
495
|
+
```
|
494
496
|
|
495
497
|
10. Deploy
|
496
498
|
|
@@ -546,35 +548,35 @@ To work around this limitation, we need to tell Active Record to acquire this in
|
|
546
548
|
|
547
549
|
1. Instruct Rails that you are going to rename a table:
|
548
550
|
|
549
|
-
```ruby
|
550
|
-
OnlineMigrations.config.table_renames = {
|
551
|
-
|
552
|
-
}
|
553
|
-
```
|
551
|
+
```ruby
|
552
|
+
OnlineMigrations.config.table_renames = {
|
553
|
+
"clients" => "users"
|
554
|
+
}
|
555
|
+
```
|
554
556
|
|
555
557
|
2. Deploy
|
556
558
|
3. Create a VIEW:
|
557
559
|
|
558
|
-
```ruby
|
559
|
-
class InitializeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
end
|
564
|
-
```
|
560
|
+
```ruby
|
561
|
+
class InitializeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
562
|
+
def change
|
563
|
+
initialize_table_rename :clients, :users
|
564
|
+
end
|
565
|
+
end
|
566
|
+
```
|
565
567
|
|
566
568
|
4. Replace usages of the old table with a new table in the codebase
|
567
569
|
5. Remove the table rename config from step 1
|
568
570
|
6. Deploy
|
569
571
|
7. Remove the VIEW created in step 3:
|
570
572
|
|
571
|
-
```ruby
|
572
|
-
class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
end
|
577
|
-
```
|
573
|
+
```ruby
|
574
|
+
class FinalizeRenameClientsToUsers < ActiveRecord::Migration[7.1]
|
575
|
+
def change
|
576
|
+
finalize_table_rename :clients, :users
|
577
|
+
end
|
578
|
+
end
|
579
|
+
```
|
578
580
|
|
579
581
|
8. Deploy
|
580
582
|
|
@@ -1157,19 +1159,19 @@ A safer approach is to:
|
|
1157
1159
|
|
1158
1160
|
1. ignore the column:
|
1159
1161
|
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1162
|
+
```ruby
|
1163
|
+
# For Active Record 5+
|
1164
|
+
class User < ApplicationRecord
|
1165
|
+
self.ignored_columns = ["type"]
|
1166
|
+
end
|
1165
1167
|
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1172
|
-
|
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
|
+
```
|
1173
1175
|
|
1174
1176
|
2. deploy
|
1175
1177
|
3. remove the column ignoring from step 1 and apply initial code changes
|
@@ -1281,18 +1283,18 @@ The main differences are:
|
|
1281
1283
|
|
1282
1284
|
1. `strong_migrations` provides you **text guidance** on how to run migrations safer and you should implement them yourself. This new gem has actual [**code helpers**](https://github.com/fatkodima/online_migrations/blob/master/lib/online_migrations/schema_statements.rb) (and suggests them when fails on unsafe migrations) you can use to do what you want. See [example](#example) for an example.
|
1283
1285
|
|
1284
|
-
It has migrations helpers for:
|
1286
|
+
It has migrations helpers for:
|
1285
1287
|
|
1286
|
-
* renaming tables/columns
|
1287
|
-
* changing columns types (including changing primary/foreign keys from `integer` to `bigint`)
|
1288
|
-
* adding columns with default values
|
1289
|
-
* backfilling data
|
1290
|
-
* adding different types of constraints
|
1291
|
-
* and others
|
1288
|
+
* renaming tables/columns
|
1289
|
+
* changing columns types (including changing primary/foreign keys from `integer` to `bigint`)
|
1290
|
+
* adding columns with default values
|
1291
|
+
* backfilling data
|
1292
|
+
* adding different types of constraints
|
1293
|
+
* and others
|
1292
1294
|
|
1293
1295
|
2. This gem has a [powerful internal framework](https://github.com/fatkodima/online_migrations/blob/master/docs/background_migrations.md) for running data migrations on very large tables using background migrations.
|
1294
1296
|
|
1295
|
-
For example, you can use background migrations to migrate data that’s stored in a single JSON column to a separate table instead; backfill values from one column to another (as one of the steps when changing column type); or backfill some column’s value from an API.
|
1297
|
+
For example, you can use background migrations to migrate data that’s stored in a single JSON column to a separate table instead; backfill values from one column to another (as one of the steps when changing column type); or backfill some column’s value from an API.
|
1296
1298
|
|
1297
1299
|
3. Yet, it has more checks for unsafe changes (see [checks](#checks)).
|
1298
1300
|
|
@@ -137,17 +137,18 @@ require "test_helper"
|
|
137
137
|
module OnlineMigrations
|
138
138
|
module BackgroundMigrations
|
139
139
|
class BackfillProjectIssuesCountTest < ActiveSupport::TestCase
|
140
|
-
test "#process_batch performs
|
141
|
-
rails = Project.create!(name: "
|
140
|
+
test "#process_batch performs an iteration" do
|
141
|
+
rails = Project.create!(name: "Ruby on Rails")
|
142
142
|
postgres = Project.create!(name: "PostgreSQL")
|
143
143
|
|
144
144
|
2.times { rails.issues.create! }
|
145
|
-
|
145
|
+
postgres.issues.create!
|
146
146
|
|
147
|
-
BackfillProjectIssuesCount.new
|
147
|
+
migration = BackfillProjectIssuesCount.new
|
148
|
+
migration.process_batch(migration.relation)
|
148
149
|
|
149
|
-
assert_equal 2, rails.issues_count
|
150
|
-
assert_equal 1, postgres.issues_count
|
150
|
+
assert_equal 2, rails.reload.issues_count
|
151
|
+
assert_equal 1, postgres.reload.issues_count
|
151
152
|
end
|
152
153
|
end
|
153
154
|
end
|
data/docs/configuring.md
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
There are a few configurable options for the gem. Custom configurations should be placed in a `online_migrations.rb` initializer.
|
4
4
|
|
5
5
|
```ruby
|
6
|
+
# config/initializers/online_migrations.rb
|
7
|
+
|
6
8
|
OnlineMigrations.configure do |config|
|
7
9
|
# ...
|
8
10
|
end
|
@@ -15,8 +17,6 @@ end
|
|
15
17
|
Add your own custom checks with:
|
16
18
|
|
17
19
|
```ruby
|
18
|
-
# config/initializers/online_migrations.rb
|
19
|
-
|
20
20
|
config.add_check do |method, args|
|
21
21
|
if method == :add_column && args[0].to_s == "users"
|
22
22
|
stop!("No more columns on the users table")
|
@@ -33,8 +33,6 @@ Use the `stop!` method to stop migrations.
|
|
33
33
|
Disable specific checks with:
|
34
34
|
|
35
35
|
```ruby
|
36
|
-
# config/initializers/online_migrations.rb
|
37
|
-
|
38
36
|
config.disable_check(:remove_index)
|
39
37
|
```
|
40
38
|
|
@@ -45,8 +43,6 @@ Check the [source code](https://github.com/fatkodima/online_migrations/blob/mast
|
|
45
43
|
By default, checks are disabled when migrating down. Enable them with:
|
46
44
|
|
47
45
|
```ruby
|
48
|
-
# config/initializers/online_migrations.rb
|
49
|
-
|
50
46
|
config.check_down = true
|
51
47
|
```
|
52
48
|
|
@@ -55,8 +51,6 @@ config.check_down = true
|
|
55
51
|
You can customize specific error messages:
|
56
52
|
|
57
53
|
```ruby
|
58
|
-
# config/initializers/online_migrations.rb
|
59
|
-
|
60
54
|
config.error_messages[:add_column_default] = "Your custom instructions"
|
61
55
|
```
|
62
56
|
|
@@ -88,8 +82,6 @@ ALTER ROLE myuser SET statement_timeout = '15s';
|
|
88
82
|
You can configure this gem to automatically retry statements that exceed the lock timeout:
|
89
83
|
|
90
84
|
```ruby
|
91
|
-
# config/initializers/online_migrations.rb
|
92
|
-
|
93
85
|
config.lock_retrier = OnlineMigrations::ExponentialLockRetrier.new(
|
94
86
|
attempts: 30, # attempt 30 retries
|
95
87
|
base_delay: 0.01.seconds, # starting with delay of 10ms between each unsuccessful try, increasing exponentially
|
@@ -110,8 +102,6 @@ To temporarily disable lock retries while running migrations, set `DISABLE_LOCK_
|
|
110
102
|
To mark migrations as safe that were created before installing this gem, configure the migration version starting after which checks are performed:
|
111
103
|
|
112
104
|
```ruby
|
113
|
-
# config/initializers/online_migrations.rb
|
114
|
-
|
115
105
|
config.start_after = 20220101000000
|
116
106
|
|
117
107
|
# or if you use multiple databases (Active Record 6+)
|
@@ -125,8 +115,6 @@ Use the version from your latest migration.
|
|
125
115
|
If your development database version is different from production, you can specify the production version so the right checks run in development.
|
126
116
|
|
127
117
|
```ruby
|
128
|
-
# config/initializers/online_migrations.rb
|
129
|
-
|
130
118
|
config.target_version = 10 # or "12.9" etc
|
131
119
|
|
132
120
|
# or if you use multiple databases (Active Record 6+)
|
@@ -200,9 +188,25 @@ So you can actually check which steps are performed.
|
|
200
188
|
To enable verbose sql logs:
|
201
189
|
|
202
190
|
```ruby
|
203
|
-
# config/initializers/online_migrations.rb
|
204
|
-
|
205
191
|
config.verbose_sql_logs = true
|
206
192
|
```
|
207
193
|
|
208
194
|
This feature is enabled by default in a production Rails environment. You can override this setting via `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
|
195
|
+
|
196
|
+
## Analyze Tables
|
197
|
+
|
198
|
+
Analyze tables automatically (to update planner statistics) after an index is added.
|
199
|
+
Add to an initializer file:
|
200
|
+
|
201
|
+
```ruby
|
202
|
+
config.auto_analyze = true
|
203
|
+
```
|
204
|
+
|
205
|
+
## Schema Sanity
|
206
|
+
|
207
|
+
Columns can flip order in `db/schema.rb` when you have multiple developers. One way to prevent this is to [alphabetize them](https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/).
|
208
|
+
To alphabetize columns:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
config.alphabetize_schema = true
|
212
|
+
```
|
@@ -23,6 +23,13 @@ OnlineMigrations.configure do |config|
|
|
23
23
|
# It is considered safe to perform most of the dangerous operations on them.
|
24
24
|
# config.small_tables = []
|
25
25
|
|
26
|
+
# Analyze tables after indexes are added.
|
27
|
+
# Outdated statistics can sometimes hurt performance.
|
28
|
+
# config.auto_analyze = true
|
29
|
+
|
30
|
+
# Alphabetize table columns when dumping the schema.
|
31
|
+
# config.alphabetize_schema = true
|
32
|
+
|
26
33
|
# Disable specific checks.
|
27
34
|
# For the list of available checks look at `lib/error_messages` folder.
|
28
35
|
# config.disable_check(:remove_index)
|
@@ -42,12 +42,18 @@ module OnlineMigrations
|
|
42
42
|
old_values = copy_from.map do |from_column|
|
43
43
|
old_value = arel_table[from_column]
|
44
44
|
if (type_cast_function = type_cast_functions[from_column])
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
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
|
53
|
+
else
|
54
|
+
# We got a cast expression.
|
55
|
+
Arel.sql(type_cast_function)
|
56
|
+
end
|
51
57
|
end
|
52
58
|
old_value
|
53
59
|
end
|
@@ -415,13 +415,13 @@ module OnlineMigrations
|
|
415
415
|
end
|
416
416
|
end
|
417
417
|
|
418
|
-
|
419
|
-
|
420
|
-
raise "The index #{index.name} can not be copied as it does not " \
|
421
|
-
"mention the old column. You have to rename this index manually first."
|
418
|
+
if index.name.include?(from_column)
|
419
|
+
name = index.name.gsub(from_column, to_column)
|
422
420
|
end
|
423
421
|
|
424
|
-
name
|
422
|
+
# Generate a shorter name if needed.
|
423
|
+
max_identifier_length = 63 # could use just `max_identifier_length` method for ActiveRecord >= 5.0.
|
424
|
+
name = index_name(table_name, new_columns) if !name || name.length > max_identifier_length
|
425
425
|
|
426
426
|
options = {
|
427
427
|
unique: index.unique,
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "erb"
|
4
|
-
require "openssl"
|
5
4
|
require "set"
|
6
5
|
|
7
6
|
module OnlineMigrations
|
@@ -53,6 +52,36 @@ module OnlineMigrations
|
|
53
52
|
end
|
54
53
|
|
55
54
|
private
|
55
|
+
ERROR_MESSAGE_TO_LINK = {
|
56
|
+
multiple_foreign_keys: "adding-multiple-foreign-keys",
|
57
|
+
create_table: "creating-a-table-with-the-force-option",
|
58
|
+
short_primary_key_type: "using-primary-key-with-short-integer-type",
|
59
|
+
drop_table_multiple_foreign_keys: "removing-a-table-with-multiple-foreign-keys",
|
60
|
+
rename_table: "renaming-a-table",
|
61
|
+
add_column_with_default_null: "adding-a-column-with-a-default-value",
|
62
|
+
add_column_with_default: "adding-a-column-with-a-default-value",
|
63
|
+
add_column_generated_stored: "adding-a-stored-generated-column",
|
64
|
+
add_column_json: "adding-a-json-column",
|
65
|
+
rename_column: "renaming-a-column",
|
66
|
+
change_column: "changing-the-type-of-a-column",
|
67
|
+
change_column_default: "changing-the-default-value-of-a-column",
|
68
|
+
change_column_null: "setting-not-null-on-an-existing-column",
|
69
|
+
remove_column: "removing-a-column",
|
70
|
+
add_timestamps_with_default: "adding-a-column-with-a-default-value",
|
71
|
+
add_hash_index: "hash-indexes",
|
72
|
+
add_reference: "adding-a-reference",
|
73
|
+
add_index: "adding-an-index-non-concurrently",
|
74
|
+
replace_index: "replacing-an-index",
|
75
|
+
remove_index: "removing-an-index-non-concurrently",
|
76
|
+
add_foreign_key: "adding-a-foreign-key",
|
77
|
+
add_exclusion_constraint: "adding-an-exclusion-constraint",
|
78
|
+
add_check_constraint: "adding-a-check-constraint",
|
79
|
+
add_unique_constraint: "adding-a-unique-constraint",
|
80
|
+
execute: "executing-SQL-directly",
|
81
|
+
add_inheritance_column: "adding-a-single-table-inheritance-column",
|
82
|
+
mismatched_foreign_key_type: "mismatched-reference-column-types",
|
83
|
+
}
|
84
|
+
|
56
85
|
def check_database_version
|
57
86
|
return if defined?(@database_version_checked)
|
58
87
|
|
@@ -522,6 +551,11 @@ module OnlineMigrations
|
|
522
551
|
end
|
523
552
|
end
|
524
553
|
end
|
554
|
+
|
555
|
+
# Outdated statistics + a new index can hurt performance of existing queries.
|
556
|
+
if OnlineMigrations.config.auto_analyze && direction == :up
|
557
|
+
connection.execute("ANALYZE #{table_name}")
|
558
|
+
end
|
525
559
|
end
|
526
560
|
end
|
527
561
|
|
@@ -586,7 +620,7 @@ module OnlineMigrations
|
|
586
620
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
587
621
|
return if new_or_small_table?(table_name) || options[:using_index] || !column_name
|
588
622
|
|
589
|
-
index_name = index_name(table_name, column_name)
|
623
|
+
index_name = Utils.index_name(table_name, column_name)
|
590
624
|
|
591
625
|
raise_error :add_unique_constraint,
|
592
626
|
add_index_code: command_str(:add_index, table_name, column_name, unique: true, name: index_name, algorithm: :concurrently),
|
@@ -594,22 +628,6 @@ module OnlineMigrations
|
|
594
628
|
remove_code: command_str(:remove_unique_constraint, table_name, column_name)
|
595
629
|
end
|
596
630
|
|
597
|
-
# Implementation is from Active Record
|
598
|
-
def index_name(table_name, column_name)
|
599
|
-
max_index_name_size = 62
|
600
|
-
name = "index_#{table_name}_on_#{Array(column_name) * '_and_'}"
|
601
|
-
return name if name.bytesize <= max_index_name_size
|
602
|
-
|
603
|
-
# Fallback to short version, add hash to ensure uniqueness
|
604
|
-
hashed_identifier = "_#{OpenSSL::Digest::SHA256.hexdigest(name).first(10)}"
|
605
|
-
name = "idx_on_#{Array(column_name) * '_'}"
|
606
|
-
|
607
|
-
short_limit = max_index_name_size - hashed_identifier.bytesize
|
608
|
-
short_name = name[0, short_limit]
|
609
|
-
|
610
|
-
"#{short_name}#{hashed_identifier}"
|
611
|
-
end
|
612
|
-
|
613
631
|
def validate_constraint(*)
|
614
632
|
if crud_blocked?
|
615
633
|
raise_error :validate_constraint
|
@@ -735,6 +753,10 @@ module OnlineMigrations
|
|
735
753
|
message = ERB.new(template, nil, "<>").result(b)
|
736
754
|
end
|
737
755
|
|
756
|
+
if (link = ERROR_MESSAGE_TO_LINK[message_key])
|
757
|
+
message += "\nFor more details, see https://github.com/fatkodima/online_migrations##{link}"
|
758
|
+
end
|
759
|
+
|
738
760
|
@migration.stop!(message, header: header || "Dangerous operation detected")
|
739
761
|
end
|
740
762
|
|
@@ -84,6 +84,16 @@ module OnlineMigrations
|
|
84
84
|
#
|
85
85
|
attr_accessor :error_messages
|
86
86
|
|
87
|
+
# Whether to automatically run ANALYZE on the table after the index was added
|
88
|
+
# @return [Boolean]
|
89
|
+
#
|
90
|
+
attr_accessor :auto_analyze
|
91
|
+
|
92
|
+
# Whether to alphabetize schema
|
93
|
+
# @return [Boolean]
|
94
|
+
#
|
95
|
+
attr_accessor :alphabetize_schema
|
96
|
+
|
87
97
|
# Maximum allowed lock timeout value (in seconds)
|
88
98
|
#
|
89
99
|
# If set lock timeout is greater than this value, the migration will fail.
|
@@ -184,6 +194,8 @@ module OnlineMigrations
|
|
184
194
|
@target_version = nil
|
185
195
|
@small_tables = []
|
186
196
|
@check_down = false
|
197
|
+
@auto_analyze = false
|
198
|
+
@alphabetize_schema = false
|
187
199
|
@enabled_checks = @error_messages.keys.map { |k| [k, {}] }.to_h
|
188
200
|
@verbose_sql_logs = defined?(Rails.env) && Rails.env.production?
|
189
201
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module OnlineMigrations
|
6
|
+
module SchemaDumper
|
7
|
+
def initialize(connection, *args, **options)
|
8
|
+
if OnlineMigrations.config.alphabetize_schema
|
9
|
+
connection = WrappedConnection.new(connection)
|
10
|
+
end
|
11
|
+
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class WrappedConnection < SimpleDelegator
|
17
|
+
def columns(table_name)
|
18
|
+
super.sort_by(&:name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -777,6 +777,22 @@ module OnlineMigrations
|
|
777
777
|
end
|
778
778
|
end
|
779
779
|
|
780
|
+
# @private
|
781
|
+
# From ActiveRecord. Will not be needed for ActiveRecord >= 7.1.
|
782
|
+
def index_name(table_name, options)
|
783
|
+
if options.is_a?(Hash)
|
784
|
+
if options[:column]
|
785
|
+
Utils.index_name(table_name, options[:column])
|
786
|
+
elsif options[:name]
|
787
|
+
options[:name]
|
788
|
+
else
|
789
|
+
raise ArgumentError, "You must specify the index name"
|
790
|
+
end
|
791
|
+
else
|
792
|
+
index_name(table_name, column: options)
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
780
796
|
# Extends default method to be idempotent and accept `:validate` option for Active Record < 5.2.
|
781
797
|
#
|
782
798
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "openssl"
|
4
|
+
|
3
5
|
module OnlineMigrations
|
4
6
|
# @private
|
5
7
|
module Utils
|
@@ -78,6 +80,23 @@ module OnlineMigrations
|
|
78
80
|
end
|
79
81
|
end
|
80
82
|
|
83
|
+
# Implementation is from ActiveRecord.
|
84
|
+
# This is not needed for ActiveRecord < 7.1 (https://github.com/rails/rails/pull/47753).
|
85
|
+
def index_name(table_name, column_name)
|
86
|
+
max_index_name_size = 62
|
87
|
+
name = "index_#{table_name}_on_#{Array(column_name) * '_and_'}"
|
88
|
+
return name if name.bytesize <= max_index_name_size
|
89
|
+
|
90
|
+
# Fallback to short version, add hash to ensure uniqueness
|
91
|
+
hashed_identifier = "_#{OpenSSL::Digest::SHA256.hexdigest(name).first(10)}"
|
92
|
+
name = "idx_on_#{Array(column_name) * '_'}"
|
93
|
+
|
94
|
+
short_limit = max_index_name_size - hashed_identifier.bytesize
|
95
|
+
short_name = name[0, short_limit]
|
96
|
+
|
97
|
+
"#{short_name}#{hashed_identifier}"
|
98
|
+
end
|
99
|
+
|
81
100
|
def ar_partial_writes?
|
82
101
|
ActiveRecord::Base.public_send(ar_partial_writes_setting)
|
83
102
|
end
|
data/lib/online_migrations.rb
CHANGED
@@ -16,6 +16,7 @@ module OnlineMigrations
|
|
16
16
|
autoload :VerboseSqlLogs
|
17
17
|
autoload :Migration
|
18
18
|
autoload :Migrator
|
19
|
+
autoload :SchemaDumper
|
19
20
|
autoload :DatabaseTasks
|
20
21
|
autoload :ForeignKeyDefinition
|
21
22
|
autoload :ForeignKeysCollector
|
@@ -81,6 +82,7 @@ module OnlineMigrations
|
|
81
82
|
|
82
83
|
ActiveRecord::Migration.prepend(OnlineMigrations::Migration)
|
83
84
|
ActiveRecord::Migrator.prepend(OnlineMigrations::Migrator)
|
85
|
+
ActiveRecord::SchemaDumper.prepend(OnlineMigrations::SchemaDumper)
|
84
86
|
|
85
87
|
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(OnlineMigrations::DatabaseTasks)
|
86
88
|
ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder)
|
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.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fatkodima
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-12-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -76,6 +76,7 @@ files:
|
|
76
76
|
- lib/online_migrations/migration.rb
|
77
77
|
- lib/online_migrations/migrator.rb
|
78
78
|
- lib/online_migrations/schema_cache.rb
|
79
|
+
- lib/online_migrations/schema_dumper.rb
|
79
80
|
- lib/online_migrations/schema_statements.rb
|
80
81
|
- lib/online_migrations/utils.rb
|
81
82
|
- lib/online_migrations/verbose_sql_logs.rb
|