online_migrations 0.7.3 → 0.8.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 +6 -0
- data/README.md +110 -16
- data/docs/background_migrations.md +1 -1
- data/docs/configuring.md +2 -2
- data/lib/online_migrations/background_migrations/copy_column.rb +1 -1
- data/lib/online_migrations/background_migrations/delete_orphaned_records.rb +3 -3
- data/lib/online_migrations/background_migrations/migration.rb +1 -1
- data/lib/online_migrations/background_migrations/migration_helpers.rb +2 -2
- data/lib/online_migrations/background_migrations/migration_job.rb +2 -2
- data/lib/online_migrations/background_migrations/reset_counters.rb +2 -2
- data/lib/online_migrations/command_checker.rb +74 -13
- data/lib/online_migrations/error_messages.rb +35 -1
- data/lib/online_migrations/lock_retrier.rb +1 -1
- data/lib/online_migrations/schema_cache.rb +84 -3
- data/lib/online_migrations/schema_statements.rb +20 -20
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +11 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46406fc60e15af6b4dfec0d548c6ca04f9dc3572eb544046e45f2dbd58cbf2eb
|
4
|
+
data.tar.gz: f969563b52c1dac70ede26c9a5bd2094295e93d5fc61e85056adec6b3b22fa69
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7e728653580b3a1635b895c4b6efba8425f300a5a239ad8dd0f07cce06503e1f1a994597535c05b4946444c97f6f210c51ece0796799ac7fa8744ff93c1d48c
|
7
|
+
data.tar.gz: 917fc0a780b7bba7050b65c9cdcd1b5544afb87133078cdf833863b892459bc69ae657a365dd72bd5fd856a0a63a3862a4e19e3f9cd282c252a4a60b9415c19b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
## master (unreleased)
|
2
2
|
|
3
|
+
## 0.8.0 (2023-07-24)
|
4
|
+
|
5
|
+
- Add check for `change_column_default`
|
6
|
+
- Add check for `add_unique_key` (Active Record >= 7.1)
|
7
|
+
- Add check for `add_column` with stored generated columns
|
8
|
+
|
3
9
|
## 0.7.3 (2023-05-30)
|
4
10
|
|
5
11
|
- Fix removing columns having expression indexes on them
|
data/README.md
CHANGED
@@ -143,7 +143,9 @@ Potentially dangerous operations:
|
|
143
143
|
- [adding a reference](#adding-a-reference)
|
144
144
|
- [adding a foreign key](#adding-a-foreign-key)
|
145
145
|
- [adding an exclusion constraint](#adding-an-exclusion-constraint)
|
146
|
+
- [adding a unique key](#adding-a-unique-key)
|
146
147
|
- [adding a json column](#adding-a-json-column)
|
148
|
+
- [adding a stored generated column](#adding-a-stored-generated-column)
|
147
149
|
- [using primary key with short integer type](#using-primary-key-with-short-integer-type)
|
148
150
|
- [hash indexes](#hash-indexes)
|
149
151
|
- [adding multiple foreign keys](#adding-multiple-foreign-keys)
|
@@ -151,13 +153,17 @@ Potentially dangerous operations:
|
|
151
153
|
- [mismatched reference column types](#mismatched-reference-column-types)
|
152
154
|
- [adding a single table inheritance column](#adding-a-single-table-inheritance-column)
|
153
155
|
|
156
|
+
Config-specific checks:
|
157
|
+
|
158
|
+
- [changing the default value of a column](#changing-the-default-value-of-a-column)
|
159
|
+
|
154
160
|
You can also add [custom checks](#custom-checks) or [disable specific checks](#disable-checks).
|
155
161
|
|
156
162
|
### Removing a column
|
157
163
|
|
158
164
|
:x: **Bad**
|
159
165
|
|
160
|
-
|
166
|
+
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
161
167
|
|
162
168
|
```ruby
|
163
169
|
class RemoveNameFromUsers < ActiveRecord::Migration[7.0]
|
@@ -172,12 +178,12 @@ end
|
|
172
178
|
1. Ignore the column:
|
173
179
|
|
174
180
|
```ruby
|
175
|
-
# For
|
181
|
+
# For Active Record 5+
|
176
182
|
class User < ApplicationRecord
|
177
183
|
self.ignored_columns = ["name"]
|
178
184
|
end
|
179
185
|
|
180
|
-
# For
|
186
|
+
# For Active Record < 5
|
181
187
|
class User < ActiveRecord::Base
|
182
188
|
def self.columns
|
183
189
|
super.reject { |c| c.name == "name" }
|
@@ -241,7 +247,7 @@ end
|
|
241
247
|
|
242
248
|
:x: **Bad**
|
243
249
|
|
244
|
-
|
250
|
+
Active Record wraps each migration in a transaction, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
245
251
|
|
246
252
|
```ruby
|
247
253
|
class AddAdminToUsers < ActiveRecord::Migration[7.0]
|
@@ -405,7 +411,7 @@ The technique is built on top of database views, using the following steps:
|
|
405
411
|
|
406
412
|
1. Rename the table to some temporary name
|
407
413
|
2. Create a VIEW using the old table name with addition of a new column as an alias of the old one
|
408
|
-
3. Add a workaround for
|
414
|
+
3. Add a workaround for Active Record's schema cache
|
409
415
|
|
410
416
|
For the previous example, to rename `name` column to `first_name` of the `users` table, we can run:
|
411
417
|
|
@@ -416,9 +422,9 @@ CREATE VIEW users AS SELECT *, first_name AS name FROM users_column_rename;
|
|
416
422
|
COMMIT;
|
417
423
|
```
|
418
424
|
|
419
|
-
As database views do not expose the underlying table schema (default values, not null constraints, indexes, etc), further steps are needed to update the application to use the new table name.
|
425
|
+
As database views do not expose the underlying table schema (default values, not null constraints, indexes, etc), further steps are needed to update the application to use the new table name. Active Record heavily relies on this data, for example, to initialize new models.
|
420
426
|
|
421
|
-
To work around this limitation, we need to tell
|
427
|
+
To work around this limitation, we need to tell Active Record to acquire this information from original table using the new table name.
|
422
428
|
|
423
429
|
**Online Migrations** provides several helpers to implement column renaming:
|
424
430
|
|
@@ -460,12 +466,12 @@ end
|
|
460
466
|
(is disabled by default in Active Record >= 7), then you need to ignore old column:
|
461
467
|
|
462
468
|
```ruby
|
463
|
-
# For
|
469
|
+
# For Active Record 5+
|
464
470
|
class User < ApplicationRecord
|
465
471
|
self.ignored_columns = ["name"]
|
466
472
|
end
|
467
473
|
|
468
|
-
# For
|
474
|
+
# For Active Record < 5
|
469
475
|
class User < ActiveRecord::Base
|
470
476
|
def self.columns
|
471
477
|
super.reject { |c| c.name == "name" }
|
@@ -521,7 +527,7 @@ The technique is built on top of database views, using the following steps:
|
|
521
527
|
|
522
528
|
1. Rename the database table
|
523
529
|
2. Create a VIEW using the old table name by pointing to the new table name
|
524
|
-
3. Add a workaround for
|
530
|
+
3. Add a workaround for Active Record's schema cache
|
525
531
|
|
526
532
|
For the previous example, to rename `name` column to `first_name` of the `users` table, we can run:
|
527
533
|
|
@@ -532,9 +538,9 @@ CREATE VIEW clients AS SELECT * FROM users;
|
|
532
538
|
COMMIT;
|
533
539
|
```
|
534
540
|
|
535
|
-
As database views do not expose the underlying table schema (default values, not null constraints, indexes, etc), further steps are needed to update the application to use the new table name.
|
541
|
+
As database views do not expose the underlying table schema (default values, not null constraints, indexes, etc), further steps are needed to update the application to use the new table name. Active Record heavily relies on this data, for example, to initialize new models.
|
536
542
|
|
537
|
-
To work around this limitation, we need to tell
|
543
|
+
To work around this limitation, we need to tell Active Record to acquire this information from original table using the new table name.
|
538
544
|
|
539
545
|
**Online Migrations** provides several helpers to implement table renaming:
|
540
546
|
|
@@ -874,6 +880,46 @@ end
|
|
874
880
|
|
875
881
|
[Let us know](https://github.com/fatkodima/online_migrations/issues/new) if you have a safe way to do this (exclusion constraints cannot be marked `NOT VALID`).
|
876
882
|
|
883
|
+
### Adding a unique key
|
884
|
+
|
885
|
+
:x: **Bad**
|
886
|
+
|
887
|
+
Adding a unique key blocks reads and writes while the underlying index is being built.
|
888
|
+
|
889
|
+
```ruby
|
890
|
+
class AddUniqueKey < ActiveRecord::Migration[7.1]
|
891
|
+
def change
|
892
|
+
add_unique_key :sections, :position, deferrable: :deferred
|
893
|
+
end
|
894
|
+
end
|
895
|
+
```
|
896
|
+
|
897
|
+
:white_check_mark: **Good**
|
898
|
+
|
899
|
+
A safer approach is to create a unique index first, and then create a unique key using that index.
|
900
|
+
|
901
|
+
```ruby
|
902
|
+
class AddUniqueKeyAddIndex < ActiveRecord::Migration[7.1]
|
903
|
+
disable_ddl_transaction!
|
904
|
+
|
905
|
+
def change
|
906
|
+
add_index :sections, :position, unique: true, name: "index_sections_on_position", algorithm: :concurrently
|
907
|
+
end
|
908
|
+
end
|
909
|
+
```
|
910
|
+
|
911
|
+
```ruby
|
912
|
+
class AddUniqueKey < ActiveRecord::Migration[7.1]
|
913
|
+
def up
|
914
|
+
add_unique_key :sections, :position, deferrable: :deferred, using_index: "index_sections_on_position"
|
915
|
+
end
|
916
|
+
|
917
|
+
def down
|
918
|
+
remove_unique_key :sections, :position
|
919
|
+
end
|
920
|
+
end
|
921
|
+
```
|
922
|
+
|
877
923
|
### Adding a json column
|
878
924
|
|
879
925
|
:x: **Bad**
|
@@ -900,11 +946,29 @@ class AddSettingsToProjects < ActiveRecord::Migration[7.0]
|
|
900
946
|
end
|
901
947
|
```
|
902
948
|
|
949
|
+
### Adding a stored generated column
|
950
|
+
|
951
|
+
:x: **Bad**
|
952
|
+
|
953
|
+
Adding a stored generated column causes the entire table to be rewritten. During this time, reads and writes are blocked.
|
954
|
+
|
955
|
+
```ruby
|
956
|
+
class AddLowerEmailToUsers < ActiveRecord::Migration[7.0]
|
957
|
+
def change
|
958
|
+
add_column :users, :lower_email, :virtual, type: :string, as: "LOWER(email)", stored: true
|
959
|
+
end
|
960
|
+
end
|
961
|
+
```
|
962
|
+
|
963
|
+
:white_check_mark: **Good**
|
964
|
+
|
965
|
+
Add a non-generated column and use callbacks or triggers instead.
|
966
|
+
|
903
967
|
### Using primary key with short integer type
|
904
968
|
|
905
969
|
:x: **Bad**
|
906
970
|
|
907
|
-
When using short integer types as primary key types, [there is a risk](https://m.signalvnoise.com/update-on-basecamp-3-being-stuck-in-read-only-as-of-nov-8-922am-cst/) of running out of IDs on inserts. The default type in
|
971
|
+
When using short integer types as primary key types, [there is a risk](https://m.signalvnoise.com/update-on-basecamp-3-being-stuck-in-read-only-as-of-nov-8-922am-cst/) of running out of IDs on inserts. The default type in Active Record < 5.1 for primary and foreign keys is `INTEGER`, which allows a little over of 2 billion records. Active Record 5.1 changed the default type to `BIGINT`.
|
908
972
|
|
909
973
|
```ruby
|
910
974
|
class CreateUsers < ActiveRecord::Migration[7.0]
|
@@ -1094,12 +1158,12 @@ A safer approach is to:
|
|
1094
1158
|
1. ignore the column:
|
1095
1159
|
|
1096
1160
|
```ruby
|
1097
|
-
# For
|
1161
|
+
# For Active Record 5+
|
1098
1162
|
class User < ApplicationRecord
|
1099
1163
|
self.ignored_columns = ["type"]
|
1100
1164
|
end
|
1101
1165
|
|
1102
|
-
# For
|
1166
|
+
# For Active Record < 5
|
1103
1167
|
class User < ActiveRecord::Base
|
1104
1168
|
def self.columns
|
1105
1169
|
super.reject { |c| c.name == "type" }
|
@@ -1111,6 +1175,36 @@ A safer approach is to:
|
|
1111
1175
|
3. remove the column ignoring from step 1 and apply initial code changes
|
1112
1176
|
4. deploy
|
1113
1177
|
|
1178
|
+
### Changing the default value of a column
|
1179
|
+
|
1180
|
+
:x: **Bad**
|
1181
|
+
|
1182
|
+
Active Record < 7 enables partial writes by default, which can cause incorrect values to be inserted when changing the default value of a column.
|
1183
|
+
|
1184
|
+
```ruby
|
1185
|
+
class ChangeSomeColumnDefault < ActiveRecord::Migration[7.0]
|
1186
|
+
def change
|
1187
|
+
change_column_default :users, :some_column, from: "old", to: "new"
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
User.create!(some_column: "old") # can insert "new"
|
1192
|
+
```
|
1193
|
+
|
1194
|
+
:white_check_mark: **Good**
|
1195
|
+
|
1196
|
+
Disable partial writes in `config/application.rb`. For Active Record < 7, use:
|
1197
|
+
|
1198
|
+
```ruby
|
1199
|
+
config.active_record.partial_writes = false
|
1200
|
+
```
|
1201
|
+
|
1202
|
+
For Active Record 7, use:
|
1203
|
+
|
1204
|
+
```ruby
|
1205
|
+
config.active_record.partial_inserts = false
|
1206
|
+
```
|
1207
|
+
|
1114
1208
|
## Assuring Safety
|
1115
1209
|
|
1116
1210
|
To mark a step in the migration as safe, despite using a method that might otherwise be dangerous, wrap it in a `safety_assured` block.
|
@@ -1143,7 +1237,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/fatkod
|
|
1143
1237
|
|
1144
1238
|
## Development
|
1145
1239
|
|
1146
|
-
After checking out the repo, run `bundle install` to install dependencies. Run `createdb online_migrations_test` to create a test database. Then, run `bundle exec rake test` to run the tests. This project uses multiple Gemfiles to test against multiple versions of
|
1240
|
+
After checking out the repo, run `bundle install` to install dependencies. Run `createdb online_migrations_test` to create a test database. Then, run `bundle exec rake test` to run the tests. This project uses multiple Gemfiles to test against multiple versions of Active Record; you can run the tests against the specific version with `BUNDLE_GEMFILE=gemfiles/activerecord_61.gemfile bundle exec rake test`.
|
1147
1241
|
|
1148
1242
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
1149
1243
|
|
@@ -84,7 +84,7 @@ end
|
|
84
84
|
# ...
|
85
85
|
```
|
86
86
|
|
87
|
-
`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/
|
87
|
+
`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.
|
88
88
|
|
89
89
|
## Custom Background Migration Arguments
|
90
90
|
|
data/docs/configuring.md
CHANGED
@@ -114,7 +114,7 @@ To mark migrations as safe that were created before installing this gem, configu
|
|
114
114
|
|
115
115
|
config.start_after = 20220101000000
|
116
116
|
|
117
|
-
# or if you use multiple databases (
|
117
|
+
# or if you use multiple databases (Active Record 6+)
|
118
118
|
config.start_after = { primary: 20211112000000, animals: 20220101000000 }
|
119
119
|
```
|
120
120
|
|
@@ -129,7 +129,7 @@ If your development database version is different from production, you can speci
|
|
129
129
|
|
130
130
|
config.target_version = 10 # or "12.9" etc
|
131
131
|
|
132
|
-
# or if you use multiple databases (
|
132
|
+
# or if you use multiple databases (Active Record 6+)
|
133
133
|
config.target_version = { primary: 10, animals: 14.1 }
|
134
134
|
```
|
135
135
|
|
@@ -43,7 +43,7 @@ module OnlineMigrations
|
|
43
43
|
old_value = arel_table[from_column]
|
44
44
|
if (type_cast_function = type_cast_functions[from_column])
|
45
45
|
if Utils.ar_version <= 5.2
|
46
|
-
#
|
46
|
+
# Active Record <= 5.2 does not support quoting of Arel::Nodes::NamedFunction
|
47
47
|
old_value = Arel.sql("#{type_cast_function}(#{connection.quote_column_name(from_column)})")
|
48
48
|
else
|
49
49
|
old_value = Arel::Nodes::NamedFunction.new(type_cast_function, [old_value])
|
@@ -12,7 +12,7 @@ module OnlineMigrations
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def relation
|
15
|
-
# For
|
15
|
+
# For Active Record 6.1+ we can use `where.missing`
|
16
16
|
# https://github.com/rails/rails/pull/34727
|
17
17
|
associations.inject(model.unscoped) do |relation, association|
|
18
18
|
reflection = model.reflect_on_association(association)
|
@@ -20,7 +20,7 @@ module OnlineMigrations
|
|
20
20
|
raise ArgumentError, "'#{model.name}' has no association called '#{association}'"
|
21
21
|
end
|
22
22
|
|
23
|
-
# left_joins was added in
|
23
|
+
# left_joins was added in Active Record 5.0 - https://github.com/rails/rails/pull/12071
|
24
24
|
relation
|
25
25
|
.left_joins(association)
|
26
26
|
.where(reflection.table_name => { reflection.association_primary_key => nil })
|
@@ -31,7 +31,7 @@ module OnlineMigrations
|
|
31
31
|
if Utils.ar_version > 5.0
|
32
32
|
relation.delete_all
|
33
33
|
else
|
34
|
-
# Older
|
34
|
+
# Older Active Record generates incorrect query when running delete_all
|
35
35
|
primary_key = model.primary_key
|
36
36
|
model.unscoped.where(primary_key => relation.select(primary_key)).delete_all
|
37
37
|
end
|
@@ -139,7 +139,7 @@ module OnlineMigrations
|
|
139
139
|
# rubocop:disable Lint/UnreachableLoop
|
140
140
|
iterator.each_batch(of: batch_size, column: batch_column_name, start: next_min_value) do |relation|
|
141
141
|
if Utils.ar_version <= 4.2
|
142
|
-
#
|
142
|
+
# Active Record <= 4.2 does not support pluck with Arel nodes
|
143
143
|
quoted_column = self.class.connection.quote_column_name(batch_column_name)
|
144
144
|
batch_range = relation.pluck("MIN(#{quoted_column}), MAX(#{quoted_column})").first
|
145
145
|
else
|
@@ -191,7 +191,7 @@ module OnlineMigrations
|
|
191
191
|
# @see https://api.rubyonrails.org/classes/ActiveRecord/CounterCache/ClassMethods.html#method-i-reset_counters
|
192
192
|
#
|
193
193
|
# @note This method is better suited for large tables (10/100s of millions of records).
|
194
|
-
# For smaller tables it is probably better and easier to use `reset_counters` from the
|
194
|
+
# For smaller tables it is probably better and easier to use `reset_counters` from the Active Record.
|
195
195
|
#
|
196
196
|
def reset_counters_in_background(model_name, *counters, touch: nil, **options)
|
197
197
|
model_name = model_name.name if model_name.is_a?(Class)
|
@@ -224,7 +224,7 @@ module OnlineMigrations
|
|
224
224
|
#
|
225
225
|
def delete_orphaned_records_in_background(model_name, *associations, **options)
|
226
226
|
if Utils.ar_version <= 4.2
|
227
|
-
raise "#{__method__} does not support
|
227
|
+
raise "#{__method__} does not support Active Record <= 4.2 yet"
|
228
228
|
end
|
229
229
|
|
230
230
|
model_name = model_name.name if model_name.is_a?(Class)
|
@@ -12,7 +12,7 @@ module OnlineMigrations
|
|
12
12
|
|
13
13
|
self.table_name = :background_migration_jobs
|
14
14
|
|
15
|
-
# For
|
15
|
+
# For Active Record <= 4.2 needs to fully specify enum values
|
16
16
|
scope :active, -> { where(status: [statuses[:enqueued], statuses[:running]]) }
|
17
17
|
scope :completed, -> { where(status: [statuses[:failed], statuses[:succeeded]]) }
|
18
18
|
scope :stuck, -> do
|
@@ -45,7 +45,7 @@ module OnlineMigrations
|
|
45
45
|
|
46
46
|
belongs_to :migration
|
47
47
|
|
48
|
-
# For
|
48
|
+
# For Active Record 5.0+ this is validated by default from belongs_to
|
49
49
|
validates :migration, presence: true
|
50
50
|
|
51
51
|
validates :min_value, :max_value, presence: true, numericality: { greater_than: 0 }
|
@@ -41,7 +41,7 @@ module OnlineMigrations
|
|
41
41
|
names = Array.wrap(names)
|
42
42
|
options = names.extract_options!
|
43
43
|
touch_updates = touch_attributes_with_time(*names, **options)
|
44
|
-
# In
|
44
|
+
# In Active Record 4.2 sanitize_sql_for_assignment is protected
|
45
45
|
updates << model.send(:sanitize_sql_for_assignment, touch_updates)
|
46
46
|
end
|
47
47
|
|
@@ -65,7 +65,7 @@ module OnlineMigrations
|
|
65
65
|
has_many_association = has_many.find do |association|
|
66
66
|
counter_cache_column = association.counter_cache_column
|
67
67
|
|
68
|
-
#
|
68
|
+
# Active Record <= 4.2 is able to return only explicitly provided `counter_cache` column.
|
69
69
|
if !counter_cache_column && Utils.ar_version <= 4.2
|
70
70
|
counter_cache_column = "#{association.name}_count"
|
71
71
|
end
|
@@ -13,6 +13,7 @@ module OnlineMigrations
|
|
13
13
|
@migration = migration
|
14
14
|
@safe = false
|
15
15
|
@new_tables = []
|
16
|
+
@new_columns = []
|
16
17
|
@lock_timeout_checked = false
|
17
18
|
@foreign_key_tables = Set.new
|
18
19
|
@removed_indexes = []
|
@@ -116,7 +117,13 @@ module OnlineMigrations
|
|
116
117
|
check_columns_removal(command, *args, **options)
|
117
118
|
else
|
118
119
|
if respond_to?(command, true)
|
119
|
-
|
120
|
+
if options.any?
|
121
|
+
# Workaround for Active Record < 5 change_column_default
|
122
|
+
# not accepting options.
|
123
|
+
send(command, *args, **options, &block)
|
124
|
+
else
|
125
|
+
send(command, *args, &block)
|
126
|
+
end
|
120
127
|
else
|
121
128
|
# assume it is safe
|
122
129
|
true
|
@@ -173,19 +180,30 @@ module OnlineMigrations
|
|
173
180
|
end
|
174
181
|
|
175
182
|
def add_column(table_name, column_name, type, **options)
|
183
|
+
type = type.to_sym
|
176
184
|
default = options[:default]
|
177
185
|
volatile_default = false
|
178
|
-
if !new_or_small_table?(table_name) && options.key?(:default) &&
|
179
|
-
(postgresql_version < Gem::Version.new("11") || (!default.nil? && (volatile_default = Utils.volatile_default?(connection, type, default))))
|
180
186
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
187
|
+
# Keep track of new columns for change_column_default check.
|
188
|
+
@new_columns << [table_name.to_s, column_name.to_s]
|
189
|
+
|
190
|
+
if !new_or_small_table?(table_name)
|
191
|
+
if options.key?(:default) &&
|
192
|
+
(postgresql_version < Gem::Version.new("11") || (!default.nil? && (volatile_default = Utils.volatile_default?(connection, type, default))))
|
193
|
+
|
194
|
+
if default.nil?
|
195
|
+
raise_error :add_column_with_default_null,
|
196
|
+
code: command_str(:add_column, table_name, column_name, type, options.except(:default))
|
197
|
+
else
|
198
|
+
raise_error :add_column_with_default,
|
199
|
+
code: command_str(:add_column_with_default, table_name, column_name, type, options),
|
200
|
+
not_null: options[:null] == false,
|
201
|
+
volatile_default: volatile_default
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
if type == :virtual && options[:stored]
|
206
|
+
raise_error :add_column_generated_stored
|
189
207
|
end
|
190
208
|
end
|
191
209
|
|
@@ -201,6 +219,8 @@ module OnlineMigrations
|
|
201
219
|
end
|
202
220
|
|
203
221
|
def add_column_with_default(table_name, column_name, type, **options)
|
222
|
+
type = type.to_sym
|
223
|
+
|
204
224
|
if type == :json
|
205
225
|
raise_error :add_column_json,
|
206
226
|
code: command_str(:add_column_with_default, table_name, column_name, :jsonb, options)
|
@@ -333,6 +353,13 @@ module OnlineMigrations
|
|
333
353
|
end
|
334
354
|
end
|
335
355
|
|
356
|
+
def change_column_default(table_name, column_name, _default_or_changes)
|
357
|
+
if Utils.ar_partial_writes? && !new_column?(table_name, column_name)
|
358
|
+
raise_error :change_column_default,
|
359
|
+
config: Utils.ar_partial_writes_setting
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
336
363
|
def change_column_null(table_name, column_name, allow_null, default = nil, **)
|
337
364
|
if !allow_null && !new_or_small_table?(table_name)
|
338
365
|
safe = false
|
@@ -406,6 +433,9 @@ module OnlineMigrations
|
|
406
433
|
end
|
407
434
|
|
408
435
|
def add_timestamps(table_name, **options)
|
436
|
+
@new_columns << [table_name.to_s, "created_at"]
|
437
|
+
@new_columns << [table_name.to_s, "updated_at"]
|
438
|
+
|
409
439
|
volatile_default = false
|
410
440
|
if !new_or_small_table?(table_name) && !options[:default].nil? &&
|
411
441
|
(postgresql_version < Gem::Version.new("11") || (volatile_default = Utils.volatile_default?(connection, :datetime, options[:default])))
|
@@ -448,7 +478,7 @@ module OnlineMigrations
|
|
448
478
|
end
|
449
479
|
|
450
480
|
unless options[:polymorphic]
|
451
|
-
type = options[:type] || (Utils.ar_version >= 5.1 ? :bigint : :integer)
|
481
|
+
type = (options[:type] || (Utils.ar_version >= 5.1 ? :bigint : :integer)).to_sym
|
452
482
|
column_name = "#{ref_name}_id"
|
453
483
|
|
454
484
|
foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
|
@@ -545,6 +575,33 @@ module OnlineMigrations
|
|
545
575
|
end
|
546
576
|
end
|
547
577
|
|
578
|
+
def add_unique_key(table_name, column_name = nil, **options)
|
579
|
+
return if new_or_small_table?(table_name) || options[:using_index] || !column_name
|
580
|
+
|
581
|
+
index_name = index_name(table_name, column_name)
|
582
|
+
|
583
|
+
raise_error :add_unique_key,
|
584
|
+
add_index_code: command_str(:add_index, table_name, column_name, unique: true, name: index_name, algorithm: :concurrently),
|
585
|
+
add_code: command_str(:add_unique_key, table_name, **options.merge(using_index: index_name)),
|
586
|
+
remove_code: command_str(:remove_unique_key, table_name, column_name)
|
587
|
+
end
|
588
|
+
|
589
|
+
# Implementation is from Active Record
|
590
|
+
def index_name(table_name, column_name)
|
591
|
+
max_index_name_size = 62
|
592
|
+
name = "index_#{table_name}_on_#{Array(column_name) * '_and_'}"
|
593
|
+
return name if name.bytesize <= max_index_name_size
|
594
|
+
|
595
|
+
# Fallback to short version, add hash to ensure uniqueness
|
596
|
+
hashed_identifier = "_#{OpenSSL::Digest::SHA256.hexdigest(name).first(10)}"
|
597
|
+
name = "idx_on_#{Array(column_name) * '_'}"
|
598
|
+
|
599
|
+
short_limit = max_index_name_size - hashed_identifier.bytesize
|
600
|
+
short_name = name[0, short_limit]
|
601
|
+
|
602
|
+
"#{short_name}#{hashed_identifier}"
|
603
|
+
end
|
604
|
+
|
548
605
|
def validate_constraint(*)
|
549
606
|
if crud_blocked?
|
550
607
|
raise_error :validate_constraint
|
@@ -619,6 +676,10 @@ module OnlineMigrations
|
|
619
676
|
@new_tables.include?(table_name.to_s)
|
620
677
|
end
|
621
678
|
|
679
|
+
def new_column?(table_name, column_name)
|
680
|
+
new_table?(table_name) || @new_columns.include?([table_name.to_s, column_name.to_s])
|
681
|
+
end
|
682
|
+
|
622
683
|
def small_table?(table_name)
|
623
684
|
OnlineMigrations.config.small_tables.include?(table_name.to_s)
|
624
685
|
end
|
@@ -768,7 +829,7 @@ module OnlineMigrations
|
|
768
829
|
connection.columns(table_name).find { |column| column.name == column_name.to_s }
|
769
830
|
end
|
770
831
|
|
771
|
-
# From
|
832
|
+
# From Active Record
|
772
833
|
def derive_join_table_name(table1, table2)
|
773
834
|
[table1.to_s, table2.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
|
774
835
|
end
|
@@ -84,6 +84,10 @@ class <%= migration_name %> < <%= migration_parent %>
|
|
84
84
|
end
|
85
85
|
end",
|
86
86
|
|
87
|
+
add_column_generated_stored:
|
88
|
+
"Adding a stored generated column blocks reads and writes while the entire table is rewritten.
|
89
|
+
Add a non-generated column and use callbacks or triggers instead.",
|
90
|
+
|
87
91
|
add_column_json:
|
88
92
|
"There's no equality operator for the json column type, which can cause errors for
|
89
93
|
existing SELECT DISTINCT queries in your application. Use jsonb instead.
|
@@ -131,6 +135,7 @@ migration_helpers provides a safer approach to do this:
|
|
131
135
|
}
|
132
136
|
}
|
133
137
|
<% unless partial_writes %>
|
138
|
+
|
134
139
|
NOTE: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
|
135
140
|
until the process of column rename is fully done.
|
136
141
|
# config/application.rb
|
@@ -243,6 +248,13 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
243
248
|
|
244
249
|
6. Deploy",
|
245
250
|
|
251
|
+
change_column_default:
|
252
|
+
"Partial writes are enabled, which can cause incorrect values
|
253
|
+
to be inserted when changing the default value of a column.
|
254
|
+
Disable partial writes in config/application.rb:
|
255
|
+
|
256
|
+
config.active_record.<%= config %> = false",
|
257
|
+
|
246
258
|
change_column_null:
|
247
259
|
"Setting NOT NULL on an existing column blocks reads and writes while every row is checked.
|
248
260
|
A safer approach is to add a NOT NULL check constraint and validate it in a separate transaction.
|
@@ -286,7 +298,7 @@ class <%= migration_name %>RemoveIndexes < <%= migration_parent %>
|
|
286
298
|
end
|
287
299
|
end
|
288
300
|
<% else %>
|
289
|
-
|
301
|
+
Active Record caches database columns at runtime, so if you drop a column, it can cause exceptions until your app reboots.
|
290
302
|
A safer approach is to:
|
291
303
|
|
292
304
|
1. Ignore the column(s):
|
@@ -426,6 +438,28 @@ class <%= migration_name %> < <%= migration_parent %>
|
|
426
438
|
end
|
427
439
|
end",
|
428
440
|
|
441
|
+
add_unique_key:
|
442
|
+
"Adding a unique key blocks reads and writes while the underlying index is being built.
|
443
|
+
A safer approach is to create a unique index first, and then create a unique key using that index.
|
444
|
+
|
445
|
+
class <%= migration_name %>AddIndex < <%= migration_parent %>
|
446
|
+
disable_ddl_transaction!
|
447
|
+
|
448
|
+
def change
|
449
|
+
<%= add_index_code %>
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
class <%= migration_name %> < <%= migration_parent %>
|
454
|
+
def up
|
455
|
+
<%= add_code %>
|
456
|
+
end
|
457
|
+
|
458
|
+
def down
|
459
|
+
<%= remove_code %>
|
460
|
+
end
|
461
|
+
end",
|
462
|
+
|
429
463
|
validate_constraint:
|
430
464
|
"Validating a constraint while holding heavy locks on tables is dangerous.
|
431
465
|
Use disable_ddl_transaction! or a separate migration.",
|
@@ -96,7 +96,7 @@ module OnlineMigrations
|
|
96
96
|
else
|
97
97
|
yield
|
98
98
|
end
|
99
|
-
# ActiveRecord::LockWaitTimeout can be used for
|
99
|
+
# ActiveRecord::LockWaitTimeout can be used for Active Record 5.2+
|
100
100
|
rescue ActiveRecord::StatementInvalid => e
|
101
101
|
if lock_timeout_error?(e) && current_attempt <= attempts
|
102
102
|
current_delay = delay(current_attempt)
|
@@ -49,7 +49,7 @@ module OnlineMigrations
|
|
49
49
|
super(column_rename_table(name))
|
50
50
|
end
|
51
51
|
|
52
|
-
super
|
52
|
+
super
|
53
53
|
end
|
54
54
|
|
55
55
|
private
|
@@ -73,9 +73,90 @@ module OnlineMigrations
|
|
73
73
|
def duplicate_column(old_column_name, new_column_name, columns)
|
74
74
|
old_column = columns.find { |column| column.name == old_column_name }
|
75
75
|
new_column = old_column.dup
|
76
|
-
#
|
76
|
+
# Active Record defines only reader for :name
|
77
77
|
new_column.instance_variable_set(:@name, new_column_name)
|
78
|
-
# Correspond to the
|
78
|
+
# Correspond to the Active Record freezing of each column
|
79
|
+
columns << new_column.freeze
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# @private
|
84
|
+
module SchemaCache7
|
85
|
+
# Active Record >= 7.1 changed signature of the methods,
|
86
|
+
# see https://github.com/rails/rails/pull/48716.
|
87
|
+
def primary_keys(connection, table_name)
|
88
|
+
if (renamed_table = renamed_table?(connection, table_name))
|
89
|
+
super(connection, renamed_table)
|
90
|
+
elsif renamed_column?(connection, table_name)
|
91
|
+
super(connection, column_rename_table(table_name))
|
92
|
+
else
|
93
|
+
super
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def columns(connection, table_name)
|
98
|
+
if (renamed_table = renamed_table?(connection, table_name))
|
99
|
+
super(connection, renamed_table)
|
100
|
+
elsif renamed_column?(connection, table_name)
|
101
|
+
columns = super(connection, column_rename_table(table_name))
|
102
|
+
OnlineMigrations.config.column_renames[table_name].each do |old_column_name, new_column_name|
|
103
|
+
duplicate_column(old_column_name, new_column_name, columns)
|
104
|
+
end
|
105
|
+
columns
|
106
|
+
else
|
107
|
+
super.reject { |column| column.name.end_with?("_for_type_change") }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def indexes(connection, table_name)
|
112
|
+
# Available only in Active Record 6.0+
|
113
|
+
return if !defined?(super)
|
114
|
+
|
115
|
+
if (renamed_table = renamed_table?(connection, table_name))
|
116
|
+
super(connection, renamed_table)
|
117
|
+
elsif renamed_column?(connection, table_name)
|
118
|
+
super(connection, column_rename_table(table_name))
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def clear_data_source_cache!(connection, name)
|
125
|
+
if (renamed_table = renamed_table?(connection, name))
|
126
|
+
super(connection, renamed_table)
|
127
|
+
end
|
128
|
+
|
129
|
+
if renamed_column?(connection, name)
|
130
|
+
super(connection, column_rename_table(name))
|
131
|
+
end
|
132
|
+
|
133
|
+
super
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
def renamed_table?(connection, table_name)
|
138
|
+
table_renames = OnlineMigrations.config.table_renames
|
139
|
+
if table_renames.key?(table_name)
|
140
|
+
views = connection.views
|
141
|
+
table_renames[table_name] if views.include?(table_name)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def renamed_column?(connection, table_name)
|
146
|
+
column_renames = OnlineMigrations.config.column_renames
|
147
|
+
column_renames.key?(table_name) && connection.views.include?(table_name)
|
148
|
+
end
|
149
|
+
|
150
|
+
def column_rename_table(table_name)
|
151
|
+
"#{table_name}_column_rename"
|
152
|
+
end
|
153
|
+
|
154
|
+
def duplicate_column(old_column_name, new_column_name, columns)
|
155
|
+
old_column = columns.find { |column| column.name == old_column_name }
|
156
|
+
new_column = old_column.dup
|
157
|
+
# Active Record defines only reader for :name
|
158
|
+
new_column.instance_variable_set(:@name, new_column_name)
|
159
|
+
# Correspond to the Active Record freezing of each column
|
79
160
|
columns << new_column.freeze
|
80
161
|
end
|
81
162
|
end
|
@@ -102,7 +102,7 @@ module OnlineMigrations
|
|
102
102
|
if Utils.ar_version <= 5.2
|
103
103
|
columns_and_values.map do |(column_name, value)|
|
104
104
|
rhs =
|
105
|
-
#
|
105
|
+
# Active Record <= 5.2 can't quote these - we need to handle these cases manually
|
106
106
|
case value
|
107
107
|
when Arel::Attributes::Attribute
|
108
108
|
quote_column_name(value.name)
|
@@ -138,7 +138,7 @@ module OnlineMigrations
|
|
138
138
|
# The technique is built on top of database views, using the following steps:
|
139
139
|
# 1. Rename the table to some temporary name
|
140
140
|
# 2. Create a VIEW using the old table name with addition of a new column as an alias of the old one
|
141
|
-
# 3. Add a workaround for
|
141
|
+
# 3. Add a workaround for Active Record's schema cache
|
142
142
|
#
|
143
143
|
# For example, to rename `name` column to `first_name` of the `users` table, we can run:
|
144
144
|
#
|
@@ -149,9 +149,9 @@ module OnlineMigrations
|
|
149
149
|
#
|
150
150
|
# As database views do not expose the underlying table schema (default values, not null constraints,
|
151
151
|
# indexes, etc), further steps are needed to update the application to use the new table name.
|
152
|
-
#
|
152
|
+
# Active Record heavily relies on this data, for example, to initialize new models.
|
153
153
|
#
|
154
|
-
# To work around this limitation, we need to tell
|
154
|
+
# To work around this limitation, we need to tell Active Record to acquire this information
|
155
155
|
# from original table using the new table name (see notes).
|
156
156
|
#
|
157
157
|
# @param table_name [String, Symbol] table name
|
@@ -165,7 +165,7 @@ module OnlineMigrations
|
|
165
165
|
#
|
166
166
|
# @note
|
167
167
|
# Prior to using this method, you need to register the database table so that
|
168
|
-
# it instructs
|
168
|
+
# it instructs Active Record to fetch the database table information (for SchemaCache)
|
169
169
|
# using the original table name (if it's present). Otherwise, fall back to the old table name:
|
170
170
|
#
|
171
171
|
# ```OnlineMigrations.config.column_renames[table_name] = { old_column_name => new_column_name }```
|
@@ -298,7 +298,7 @@ module OnlineMigrations
|
|
298
298
|
# The technique is built on top of database views, using the following steps:
|
299
299
|
# 1. Rename the database table
|
300
300
|
# 2. Create a database view using the old table name by pointing to the new table name
|
301
|
-
# 3. Add a workaround for
|
301
|
+
# 3. Add a workaround for Active Record's schema cache
|
302
302
|
#
|
303
303
|
# For example, to rename `clients` table name to `users`, we can run:
|
304
304
|
#
|
@@ -309,9 +309,9 @@ module OnlineMigrations
|
|
309
309
|
#
|
310
310
|
# As database views do not expose the underlying table schema (default values, not null constraints,
|
311
311
|
# indexes, etc), further steps are needed to update the application to use the new table name.
|
312
|
-
#
|
312
|
+
# Active Record heavily relies on this data, for example, to initialize new models.
|
313
313
|
#
|
314
|
-
# To work around this limitation, we need to tell
|
314
|
+
# To work around this limitation, we need to tell Active Record to acquire this information
|
315
315
|
# from original table using the new table name (see notes).
|
316
316
|
#
|
317
317
|
# @param table_name [String, Symbol]
|
@@ -324,7 +324,7 @@ module OnlineMigrations
|
|
324
324
|
#
|
325
325
|
# @note
|
326
326
|
# Prior to using this method, you need to register the database table so that
|
327
|
-
# it instructs
|
327
|
+
# it instructs Active Record to fetch the database table information (for SchemaCache)
|
328
328
|
# using the new table name (if it's present). Otherwise, fall back to the old table name:
|
329
329
|
#
|
330
330
|
# ```
|
@@ -638,7 +638,7 @@ module OnlineMigrations
|
|
638
638
|
|
639
639
|
# Adds a reference to the table with minimal locking
|
640
640
|
#
|
641
|
-
#
|
641
|
+
# Active Record adds an index non-`CONCURRENTLY` to references by default, which blocks writes.
|
642
642
|
# It also adds a validated foreign key by default, which blocks writes on both tables while
|
643
643
|
# validating existing rows.
|
644
644
|
#
|
@@ -730,7 +730,7 @@ module OnlineMigrations
|
|
730
730
|
end
|
731
731
|
end
|
732
732
|
|
733
|
-
# Extends default method to be idempotent and accept `:algorithm` option for
|
733
|
+
# Extends default method to be idempotent and accept `:algorithm` option for Active Record <= 4.2.
|
734
734
|
#
|
735
735
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_index
|
736
736
|
#
|
@@ -760,7 +760,7 @@ module OnlineMigrations
|
|
760
760
|
# It only conflicts with constraint validations, other creating/removing indexes,
|
761
761
|
# and some "ALTER TABLE"s.
|
762
762
|
|
763
|
-
#
|
763
|
+
# Active Record <= 4.2 does not support removing indexes concurrently
|
764
764
|
if Utils.ar_version <= 4.2 && algorithm == :concurrently
|
765
765
|
execute("DROP INDEX CONCURRENTLY #{quote_table_name(index_name)}")
|
766
766
|
else
|
@@ -773,7 +773,7 @@ module OnlineMigrations
|
|
773
773
|
end
|
774
774
|
end
|
775
775
|
|
776
|
-
# Extends default method to be idempotent and accept `:validate` option for
|
776
|
+
# Extends default method to be idempotent and accept `:validate` option for Active Record < 5.2.
|
777
777
|
#
|
778
778
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key
|
779
779
|
#
|
@@ -785,7 +785,7 @@ module OnlineMigrations
|
|
785
785
|
|
786
786
|
Utils.say(message)
|
787
787
|
else
|
788
|
-
#
|
788
|
+
# Active Record >= 5.2 supports adding non-validated foreign keys natively
|
789
789
|
options = options.dup
|
790
790
|
options[:column] ||= "#{to_table.to_s.singularize}_id"
|
791
791
|
options[:primary_key] ||= "id"
|
@@ -812,7 +812,7 @@ module OnlineMigrations
|
|
812
812
|
# Extends default method with disabled statement timeout while validation is run
|
813
813
|
#
|
814
814
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQL/SchemaStatements.html#method-i-validate_foreign_key
|
815
|
-
# @note This method was added in
|
815
|
+
# @note This method was added in Active Record 5.2
|
816
816
|
#
|
817
817
|
def validate_foreign_key(from_table, to_table = nil, **options)
|
818
818
|
fk_name_to_validate = __foreign_key_for!(from_table, to_table: to_table, **options).name
|
@@ -835,7 +835,7 @@ module OnlineMigrations
|
|
835
835
|
# Extends default method to be idempotent
|
836
836
|
#
|
837
837
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_check_constraint
|
838
|
-
# @note This method was added in
|
838
|
+
# @note This method was added in Active Record 6.1
|
839
839
|
#
|
840
840
|
def add_check_constraint(table_name, expression, validate: true, **options)
|
841
841
|
constraint_name = __check_constraint_name(table_name, expression: expression, **options)
|
@@ -857,7 +857,7 @@ module OnlineMigrations
|
|
857
857
|
# Extends default method with disabled statement timeout while validation is run
|
858
858
|
#
|
859
859
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/PostgreSQL/SchemaStatements.html#method-i-validate_check_constraint
|
860
|
-
# @note This method was added in
|
860
|
+
# @note This method was added in Active Record 6.1
|
861
861
|
#
|
862
862
|
def validate_check_constraint(table_name, **options)
|
863
863
|
constraint_name = __check_constraint_name!(table_name, **options)
|
@@ -878,7 +878,7 @@ module OnlineMigrations
|
|
878
878
|
|
879
879
|
if Utils.ar_version < 6.1
|
880
880
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_check_constraint
|
881
|
-
# @note This method was added in
|
881
|
+
# @note This method was added in Active Record 6.1
|
882
882
|
#
|
883
883
|
def remove_check_constraint(table_name, expression = nil, **options)
|
884
884
|
constraint_name = __check_constraint_name!(table_name, expression: expression, **options)
|
@@ -963,7 +963,7 @@ module OnlineMigrations
|
|
963
963
|
|
964
964
|
private
|
965
965
|
# Private methods are prefixed with `__` to avoid clashes with existing or future
|
966
|
-
#
|
966
|
+
# Active Record methods
|
967
967
|
def __ensure_not_in_transaction!(method_name = caller[0])
|
968
968
|
if transaction_open?
|
969
969
|
raise <<-MSG.strip_heredoc
|
@@ -1016,7 +1016,7 @@ module OnlineMigrations
|
|
1016
1016
|
end
|
1017
1017
|
|
1018
1018
|
def __index_valid?(index_name, schema:)
|
1019
|
-
#
|
1019
|
+
# Active Record <= 4.2 returns a string, instead of automatically casting to boolean
|
1020
1020
|
valid = select_value <<-SQL.strip_heredoc
|
1021
1021
|
SELECT indisvalid
|
1022
1022
|
FROM pg_index i
|
data/lib/online_migrations.rb
CHANGED
@@ -22,9 +22,13 @@ module OnlineMigrations
|
|
22
22
|
autoload :IndexDefinition
|
23
23
|
autoload :IndexesCollector
|
24
24
|
autoload :CommandChecker
|
25
|
-
autoload :SchemaCache
|
26
25
|
autoload :BackgroundMigration
|
27
26
|
|
27
|
+
autoload_at "online_migrations/schema_cache" do
|
28
|
+
autoload :SchemaCache
|
29
|
+
autoload :SchemaCache7
|
30
|
+
end
|
31
|
+
|
28
32
|
autoload_at "online_migrations/lock_retrier" do
|
29
33
|
autoload :LockRetrier
|
30
34
|
autoload :ConstantLockRetrier
|
@@ -79,9 +83,14 @@ module OnlineMigrations
|
|
79
83
|
ActiveRecord::Migrator.prepend(OnlineMigrations::Migrator)
|
80
84
|
|
81
85
|
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(OnlineMigrations::DatabaseTasks)
|
82
|
-
ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
|
83
86
|
ActiveRecord::Migration::CommandRecorder.include(OnlineMigrations::CommandRecorder)
|
84
87
|
|
88
|
+
if OnlineMigrations::Utils.ar_version >= 7.1
|
89
|
+
ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache7)
|
90
|
+
else
|
91
|
+
ActiveRecord::ConnectionAdapters::SchemaCache.prepend(OnlineMigrations::SchemaCache)
|
92
|
+
end
|
93
|
+
|
85
94
|
if OnlineMigrations::Utils.ar_version <= 5.1
|
86
95
|
ActiveRecord::ConnectionAdapters::ForeignKeyDefinition.prepend(OnlineMigrations::ForeignKeyDefinition)
|
87
96
|
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.
|
4
|
+
version: 0.8.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-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -102,7 +102,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
102
|
- !ruby/object:Gem::Version
|
103
103
|
version: '0'
|
104
104
|
requirements: []
|
105
|
-
rubygems_version: 3.4.
|
105
|
+
rubygems_version: 3.4.6
|
106
106
|
signing_key:
|
107
107
|
specification_version: 4
|
108
108
|
summary: Catch unsafe PostgreSQL migrations in development and run them easier in
|