online_migrations 0.9.2 → 0.11.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 +41 -0
- data/README.md +155 -150
- data/docs/background_migrations.md +43 -10
- data/docs/configuring.md +23 -18
- 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 +12 -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 +11 -19
- 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 +71 -86
- data/lib/online_migrations/command_checker.rb +50 -46
- data/lib/online_migrations/config.rb +19 -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 +21 -0
- data/lib/online_migrations/schema_statements.rb +80 -256
- data/lib/online_migrations/utils.rb +36 -55
- data/lib/online_migrations/verbose_sql_logs.rb +3 -2
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +9 -6
- metadata +9 -7
- data/lib/online_migrations/background_migrations/advisory_lock.rb +0 -62
- data/lib/online_migrations/foreign_key_definition.rb +0 -17
@@ -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
|
|
@@ -125,13 +154,7 @@ module OnlineMigrations
|
|
125
154
|
check_columns_removal(command, *args, **options)
|
126
155
|
else
|
127
156
|
if respond_to?(command, true)
|
128
|
-
|
129
|
-
# Workaround for Active Record < 5 change_column_default
|
130
|
-
# not accepting options.
|
131
|
-
send(command, *args, **options, &block)
|
132
|
-
else
|
133
|
-
send(command, *args, &block)
|
134
|
-
end
|
157
|
+
send(command, *args, **options, &block)
|
135
158
|
else
|
136
159
|
# assume it is safe
|
137
160
|
true
|
@@ -333,9 +356,7 @@ module OnlineMigrations
|
|
333
356
|
precision = options[:precision] || options[:limit] || 6
|
334
357
|
existing_precision = existing_column.precision || existing_column.limit || 6
|
335
358
|
|
336
|
-
|
337
|
-
(existing_type == :interval || (Utils.ar_version < 6.1 && existing_column.sql_type.start_with?("interval"))) &&
|
338
|
-
precision >= existing_precision
|
359
|
+
existing_type == :interval && precision >= existing_precision
|
339
360
|
when :inet
|
340
361
|
existing_type == :cidr
|
341
362
|
else
|
@@ -458,7 +479,7 @@ module OnlineMigrations
|
|
458
479
|
|
459
480
|
def add_reference(table_name, ref_name, **options)
|
460
481
|
# Always added by default in 5.0+
|
461
|
-
index = options.fetch(:index)
|
482
|
+
index = options.fetch(:index, true)
|
462
483
|
|
463
484
|
if index.is_a?(Hash) && index[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
|
464
485
|
raise_error :add_hash_index
|
@@ -486,7 +507,7 @@ module OnlineMigrations
|
|
486
507
|
end
|
487
508
|
|
488
509
|
if !options[:polymorphic]
|
489
|
-
type = (options[:type] ||
|
510
|
+
type = (options[:type] || :bigint).to_sym
|
490
511
|
column_name = "#{ref_name}_id"
|
491
512
|
|
492
513
|
foreign_key_options = foreign_key.is_a?(Hash) ? foreign_key : {}
|
@@ -522,6 +543,11 @@ module OnlineMigrations
|
|
522
543
|
end
|
523
544
|
end
|
524
545
|
end
|
546
|
+
|
547
|
+
# Outdated statistics + a new index can hurt performance of existing queries.
|
548
|
+
if OnlineMigrations.config.auto_analyze && direction == :up
|
549
|
+
connection.execute("ANALYZE #{table_name}")
|
550
|
+
end
|
525
551
|
end
|
526
552
|
end
|
527
553
|
|
@@ -539,9 +565,9 @@ module OnlineMigrations
|
|
539
565
|
end
|
540
566
|
|
541
567
|
if index_def
|
542
|
-
existing_options = [:name, :columns, :unique, :where, :type, :using, :opclasses].
|
568
|
+
existing_options = [:name, :columns, :unique, :where, :type, :using, :opclasses].filter_map do |option|
|
543
569
|
[option, index_def.public_send(option)] if index_def.respond_to?(option)
|
544
|
-
end.
|
570
|
+
end.to_h
|
545
571
|
|
546
572
|
@removed_indexes << IndexDefinition.new(table: table_name, **existing_options)
|
547
573
|
end
|
@@ -586,7 +612,7 @@ module OnlineMigrations
|
|
586
612
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
587
613
|
return if new_or_small_table?(table_name) || options[:using_index] || !column_name
|
588
614
|
|
589
|
-
index_name = index_name(table_name, column_name)
|
615
|
+
index_name = Utils.index_name(table_name, column_name)
|
590
616
|
|
591
617
|
raise_error :add_unique_constraint,
|
592
618
|
add_index_code: command_str(:add_index, table_name, column_name, unique: true, name: index_name, algorithm: :concurrently),
|
@@ -594,22 +620,6 @@ module OnlineMigrations
|
|
594
620
|
remove_code: command_str(:remove_unique_constraint, table_name, column_name)
|
595
621
|
end
|
596
622
|
|
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
623
|
def validate_constraint(*)
|
614
624
|
if crud_blocked?
|
615
625
|
raise_error :validate_constraint
|
@@ -720,19 +730,13 @@ module OnlineMigrations
|
|
720
730
|
template = OnlineMigrations.config.error_messages.fetch(message_key)
|
721
731
|
|
722
732
|
vars[:migration_name] = @migration.name
|
723
|
-
vars[:migration_parent] = Utils.
|
724
|
-
vars[:model_parent] = Utils.model_parent_string
|
733
|
+
vars[:migration_parent] = "ActiveRecord::Migration[#{Utils.ar_version}]"
|
725
734
|
vars[:ar_version] = Utils.ar_version
|
726
735
|
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
b = TOPLEVEL_BINDING.dup
|
732
|
-
vars.each_pair do |key, value|
|
733
|
-
b.local_variable_set(key, value)
|
734
|
-
end
|
735
|
-
message = ERB.new(template, nil, "<>").result(b)
|
736
|
+
message = ERB.new(template, trim_mode: "<>").result_with_hash(vars)
|
737
|
+
|
738
|
+
if (link = ERROR_MESSAGE_TO_LINK[message_key])
|
739
|
+
message += "\nFor more details, see https://github.com/fatkodima/online_migrations##{link}"
|
736
740
|
end
|
737
741
|
|
738
742
|
@migration.stop!(message, header: header || "Dangerous operation detected")
|
@@ -768,7 +772,7 @@ module OnlineMigrations
|
|
768
772
|
end
|
769
773
|
|
770
774
|
def crud_blocked?
|
771
|
-
locks_query =
|
775
|
+
locks_query = <<~SQL
|
772
776
|
SELECT relation::regclass::text
|
773
777
|
FROM pg_locks
|
774
778
|
WHERE mode IN ('ShareLock', 'ShareRowExclusiveLock', 'ExclusiveLock', 'AccessExclusiveLock')
|
@@ -786,7 +790,7 @@ module OnlineMigrations
|
|
786
790
|
end
|
787
791
|
|
788
792
|
def check_constraints(table_name)
|
789
|
-
constraints_query =
|
793
|
+
constraints_query = <<~SQL
|
790
794
|
SELECT pg_get_constraintdef(oid) AS def
|
791
795
|
FROM pg_constraint
|
792
796
|
WHERE contype = 'c'
|
@@ -807,7 +811,7 @@ module OnlineMigrations
|
|
807
811
|
|
808
812
|
def check_mismatched_foreign_key_type(table_name, column_name, type, **options)
|
809
813
|
column_name = column_name.to_s
|
810
|
-
ref_name = column_name.
|
814
|
+
ref_name = column_name.delete_suffix("_id")
|
811
815
|
|
812
816
|
if like_foreign_key?(column_name, type)
|
813
817
|
foreign_table_name = Utils.foreign_table_name(ref_name, options)
|
@@ -16,7 +16,6 @@ module OnlineMigrations
|
|
16
16
|
#
|
17
17
|
def start_after=(value)
|
18
18
|
if value.is_a?(Hash)
|
19
|
-
ensure_supports_multiple_dbs
|
20
19
|
@start_after = value.stringify_keys
|
21
20
|
else
|
22
21
|
@start_after = value
|
@@ -49,7 +48,6 @@ module OnlineMigrations
|
|
49
48
|
#
|
50
49
|
def target_version=(value)
|
51
50
|
if value.is_a?(Hash)
|
52
|
-
ensure_supports_multiple_dbs
|
53
51
|
@target_version = value.stringify_keys
|
54
52
|
else
|
55
53
|
@target_version = value
|
@@ -84,6 +82,16 @@ module OnlineMigrations
|
|
84
82
|
#
|
85
83
|
attr_accessor :error_messages
|
86
84
|
|
85
|
+
# Whether to automatically run ANALYZE on the table after the index was added
|
86
|
+
# @return [Boolean]
|
87
|
+
#
|
88
|
+
attr_accessor :auto_analyze
|
89
|
+
|
90
|
+
# Whether to alphabetize schema
|
91
|
+
# @return [Boolean]
|
92
|
+
#
|
93
|
+
attr_accessor :alphabetize_schema
|
94
|
+
|
87
95
|
# Maximum allowed lock timeout value (in seconds)
|
88
96
|
#
|
89
97
|
# If set lock timeout is greater than this value, the migration will fail.
|
@@ -138,7 +146,7 @@ module OnlineMigrations
|
|
138
146
|
# Returns a list of enabled checks
|
139
147
|
#
|
140
148
|
# All checks are enabled by default. To disable/enable a check use `disable_check`/`enable_check`.
|
141
|
-
# For the list of available checks look at `
|
149
|
+
# For the list of available checks look at the `error_messages.rb` file.
|
142
150
|
#
|
143
151
|
# @return [Array]
|
144
152
|
#
|
@@ -174,7 +182,7 @@ module OnlineMigrations
|
|
174
182
|
attempts: 30,
|
175
183
|
base_delay: 0.01.seconds,
|
176
184
|
max_delay: 1.minute,
|
177
|
-
lock_timeout: 0.
|
185
|
+
lock_timeout: 0.2.seconds
|
178
186
|
)
|
179
187
|
|
180
188
|
@background_migrations = BackgroundMigrations::Config.new
|
@@ -184,8 +192,10 @@ module OnlineMigrations
|
|
184
192
|
@target_version = nil
|
185
193
|
@small_tables = []
|
186
194
|
@check_down = false
|
187
|
-
@
|
188
|
-
@
|
195
|
+
@auto_analyze = false
|
196
|
+
@alphabetize_schema = false
|
197
|
+
@enabled_checks = @error_messages.keys.index_with({})
|
198
|
+
@verbose_sql_logs = defined?(Rails.env) && (Rails.env.production? || Rails.env.staging?)
|
189
199
|
end
|
190
200
|
|
191
201
|
def lock_retrier=(value)
|
@@ -198,7 +208,7 @@ module OnlineMigrations
|
|
198
208
|
|
199
209
|
# Enables specific check
|
200
210
|
#
|
201
|
-
# For the list of available checks look at `
|
211
|
+
# For the list of available checks look at the `error_messages.rb` file.
|
202
212
|
#
|
203
213
|
# @param name [Symbol] check name
|
204
214
|
# @param start_after [Integer] migration version from which this check will be performed
|
@@ -210,7 +220,7 @@ module OnlineMigrations
|
|
210
220
|
|
211
221
|
# Disables specific check
|
212
222
|
#
|
213
|
-
# For the list of available checks look at `
|
223
|
+
# For the list of available checks look at the `error_messages.rb` file.
|
214
224
|
#
|
215
225
|
# @param name [Symbol] check name
|
216
226
|
# @return [void]
|
@@ -221,7 +231,7 @@ module OnlineMigrations
|
|
221
231
|
|
222
232
|
# Test whether specific check is enabled
|
223
233
|
#
|
224
|
-
# For the list of available checks look at `
|
234
|
+
# For the list of available checks look at the `error_messages.rb` file.
|
225
235
|
#
|
226
236
|
# @param name [Symbol] check name
|
227
237
|
# @param version [Integer] migration version
|
@@ -260,12 +270,6 @@ module OnlineMigrations
|
|
260
270
|
end
|
261
271
|
|
262
272
|
private
|
263
|
-
def ensure_supports_multiple_dbs
|
264
|
-
if !Utils.supports_multiple_dbs?
|
265
|
-
raise "OnlineMigrations does not support multiple databases for Active Record < 6.1"
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
273
|
def db_config_name
|
270
274
|
connection = OnlineMigrations.current_migration.connection
|
271
275
|
connection.pool.db_config.name
|
@@ -18,12 +18,12 @@ module OnlineMigrations
|
|
18
18
|
"trigger_#{hashed_identifier}"
|
19
19
|
end
|
20
20
|
|
21
|
-
def create(from_columns, to_columns)
|
21
|
+
def create(from_columns, to_columns, type_cast_functions: {})
|
22
22
|
from_columns, to_columns = normalize_column_names(from_columns, to_columns)
|
23
23
|
trigger_name = name(from_columns, to_columns)
|
24
|
-
assignment_clauses = assignment_clauses_for_columns(from_columns, to_columns)
|
24
|
+
assignment_clauses = assignment_clauses_for_columns(from_columns, to_columns, type_cast_functions)
|
25
25
|
|
26
|
-
connection.execute(
|
26
|
+
connection.execute(<<~SQL)
|
27
27
|
CREATE OR REPLACE FUNCTION #{trigger_name}() RETURNS TRIGGER AS $$
|
28
28
|
BEGIN
|
29
29
|
#{assignment_clauses};
|
@@ -32,11 +32,11 @@ module OnlineMigrations
|
|
32
32
|
$$ LANGUAGE plpgsql;
|
33
33
|
SQL
|
34
34
|
|
35
|
-
connection.execute(
|
35
|
+
connection.execute(<<~SQL)
|
36
36
|
DROP TRIGGER IF EXISTS #{trigger_name} ON #{quoted_table_name}
|
37
37
|
SQL
|
38
38
|
|
39
|
-
connection.execute(
|
39
|
+
connection.execute(<<~SQL)
|
40
40
|
CREATE TRIGGER #{trigger_name}
|
41
41
|
BEFORE INSERT OR UPDATE
|
42
42
|
ON #{quoted_table_name}
|
@@ -75,14 +75,19 @@ module OnlineMigrations
|
|
75
75
|
[from_columns, to_columns]
|
76
76
|
end
|
77
77
|
|
78
|
-
def assignment_clauses_for_columns(from_columns, to_columns)
|
78
|
+
def assignment_clauses_for_columns(from_columns, to_columns, type_cast_functions)
|
79
79
|
combined_column_names = to_columns.zip(from_columns)
|
80
80
|
|
81
81
|
assignment_clauses = combined_column_names.map do |(new_name, old_name)|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
82
|
+
quoted_new_name = connection.quote_column_name(new_name)
|
83
|
+
quoted_old_name = connection.quote_column_name(old_name)
|
84
|
+
type_cast_function = type_cast_functions[old_name]
|
85
|
+
|
86
|
+
if type_cast_function
|
87
|
+
"NEW.#{quoted_new_name} := #{type_cast_function.gsub(old_name.to_s, "NEW.#{quoted_old_name}")}"
|
88
|
+
else
|
89
|
+
"NEW.#{quoted_new_name} := NEW.#{quoted_old_name}"
|
90
|
+
end
|
86
91
|
end
|
87
92
|
|
88
93
|
assignment_clauses.join(";\n ")
|
@@ -109,14 +109,8 @@ A safer approach is to:
|
|
109
109
|
|
110
110
|
1. ignore the column:
|
111
111
|
|
112
|
-
class <%= model %> <
|
113
|
-
<% if ar_version >= 5 %>
|
112
|
+
class <%= model %> < ApplicationRecord
|
114
113
|
self.ignored_columns = [\"<%= column_name %>\"]
|
115
|
-
<% else %>
|
116
|
-
def self.columns
|
117
|
-
super.reject { |c| c.name == \"<%= column_name %>\" }
|
118
|
-
end
|
119
|
-
<% end %>
|
120
114
|
end
|
121
115
|
|
122
116
|
2. deploy
|
@@ -157,13 +151,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
|
|
157
151
|
<% if enumerate_columns_in_select_statements %>
|
158
152
|
5. Ignore old column
|
159
153
|
|
160
|
-
<% if ar_version >= 5 %>
|
161
154
|
self.ignored_columns = [:<%= column_name %>]
|
162
|
-
<% else %>
|
163
|
-
def self.columns
|
164
|
-
super.reject { |c| c.name == \"<%= column_name %>\" }
|
165
|
-
end
|
166
|
-
<% end %>
|
167
155
|
|
168
156
|
6. Deploy
|
169
157
|
7. Remove the column rename config from step 1
|
@@ -216,6 +204,8 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
216
204
|
|
217
205
|
def up
|
218
206
|
<%= backfill_code %>
|
207
|
+
# You can use `backfill_column_for_type_change_in_background` if want to
|
208
|
+
# backfill using background migrations.
|
219
209
|
end
|
220
210
|
|
221
211
|
def down
|
@@ -223,7 +213,10 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
223
213
|
end
|
224
214
|
end
|
225
215
|
|
226
|
-
3.
|
216
|
+
3. Make sure your application works with values in both formats (when read from the database, converting
|
217
|
+
during writes works automatically). For most column type changes, this does not need any updates in the app.
|
218
|
+
4. Deploy
|
219
|
+
5. Copy indexes, foreign keys, check constraints, NOT NULL constraint, swap new column in place:
|
227
220
|
|
228
221
|
class Finalize<%= migration_name %> < <%= migration_parent %>
|
229
222
|
disable_ddl_transaction!
|
@@ -233,8 +226,8 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
233
226
|
end
|
234
227
|
end
|
235
228
|
|
236
|
-
|
237
|
-
|
229
|
+
6. Deploy
|
230
|
+
7. Finally, if everything works as expected, remove copy trigger and old column:
|
238
231
|
|
239
232
|
class Cleanup<%= migration_name %> < <%= migration_parent %>
|
240
233
|
def up
|
@@ -246,7 +239,8 @@ which will be passed to `add_column` when creating a new column, so you can over
|
|
246
239
|
end
|
247
240
|
end
|
248
241
|
|
249
|
-
|
242
|
+
8. Remove changes from step 3, if any
|
243
|
+
9. Deploy",
|
250
244
|
|
251
245
|
change_column_default:
|
252
246
|
"Partial writes are enabled, which can cause incorrect values
|
@@ -303,14 +297,8 @@ A safer approach is to:
|
|
303
297
|
|
304
298
|
1. Ignore the column(s):
|
305
299
|
|
306
|
-
class <%= model %> <
|
307
|
-
<% if ar_version >= 5 %>
|
300
|
+
class <%= model %> < ApplicationRecord
|
308
301
|
self.ignored_columns = <%= columns %>
|
309
|
-
<% else %>
|
310
|
-
def self.columns
|
311
|
-
super.reject { |c| <%= columns %>.include?(c.name) }
|
312
|
-
end
|
313
|
-
<% end %>
|
314
302
|
end
|
315
303
|
|
316
304
|
2. Deploy
|
@@ -504,7 +492,7 @@ execute call, so cannot help you here. Make really sure that what
|
|
504
492
|
you're doing is safe before proceeding, then wrap it in a safety_assured { ... } block.",
|
505
493
|
|
506
494
|
multiple_foreign_keys:
|
507
|
-
"Adding multiple foreign keys in a single migration blocks
|
495
|
+
"Adding multiple foreign keys in a single migration blocks writes on all involved tables until migration is completed.
|
508
496
|
Avoid adding foreign key more than once per migration file, unless the source and target tables are identical.",
|
509
497
|
|
510
498
|
drop_table_multiple_foreign_keys:
|
@@ -12,8 +12,8 @@ module OnlineMigrations
|
|
12
12
|
@indexes = []
|
13
13
|
end
|
14
14
|
|
15
|
-
def collect
|
16
|
-
|
15
|
+
def collect
|
16
|
+
yield self
|
17
17
|
end
|
18
18
|
|
19
19
|
def index(_column_name, **options)
|
@@ -21,7 +21,7 @@ module OnlineMigrations
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def references(*_ref_names, **options)
|
24
|
-
index = options.fetch(:index)
|
24
|
+
index = options.fetch(:index, true)
|
25
25
|
|
26
26
|
if index
|
27
27
|
using = index.is_a?(Hash) ? index[:using].to_s : nil
|
@@ -96,9 +96,8 @@ module OnlineMigrations
|
|
96
96
|
else
|
97
97
|
yield
|
98
98
|
end
|
99
|
-
|
100
|
-
|
101
|
-
if lock_timeout_error?(e) && current_attempt <= attempts
|
99
|
+
rescue ActiveRecord::LockWaitTimeout
|
100
|
+
if current_attempt <= attempts
|
102
101
|
current_delay = delay(current_attempt)
|
103
102
|
Utils.say("Lock timeout. Retrying in #{current_delay} seconds...")
|
104
103
|
sleep(current_delay)
|
@@ -122,10 +121,6 @@ module OnlineMigrations
|
|
122
121
|
ensure
|
123
122
|
connection.execute("SET lock_timeout TO #{connection.quote(prev_value)}")
|
124
123
|
end
|
125
|
-
|
126
|
-
def lock_timeout_error?(error)
|
127
|
-
error.message.include?("canceling statement due to lock timeout")
|
128
|
-
end
|
129
124
|
end
|
130
125
|
|
131
126
|
# `LockRetrier` implementation that has a constant delay between tries
|
@@ -181,10 +176,10 @@ module OnlineMigrations
|
|
181
176
|
#
|
182
177
|
# @example
|
183
178
|
# # This will attempt 30 retries starting with delay of 10ms between each unsuccessful try, increasing exponentially
|
184
|
-
# # up to the maximum delay of 1 minute and
|
179
|
+
# # up to the maximum delay of 1 minute and 200ms set as lock timeout for each try:
|
185
180
|
#
|
186
181
|
# config.retrier = OnlineMigrations::ConstantLockRetrier.new(attempts: 30,
|
187
|
-
# base_delay: 0.01.seconds, max_delay: 1.minute, lock_timeout: 0.
|
182
|
+
# base_delay: 0.01.seconds, max_delay: 1.minute, lock_timeout: 0.2.seconds)
|
188
183
|
#
|
189
184
|
class ExponentialLockRetrier < LockRetrier
|
190
185
|
# LockRetrier API implementation
|
@@ -28,9 +28,6 @@ module OnlineMigrations
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def indexes(table_name)
|
31
|
-
# Available only in Active Record 6.0+
|
32
|
-
return if !defined?(super)
|
33
|
-
|
34
31
|
if (renamed_table = renamed_table?(table_name))
|
35
32
|
super(renamed_table)
|
36
33
|
elsif renamed_column?(table_name)
|
@@ -109,9 +106,6 @@ module OnlineMigrations
|
|
109
106
|
end
|
110
107
|
|
111
108
|
def indexes(connection, table_name)
|
112
|
-
# Available only in Active Record 6.0+
|
113
|
-
return if !defined?(super)
|
114
|
-
|
115
109
|
if (renamed_table = renamed_table?(connection, table_name))
|
116
110
|
super(connection, renamed_table)
|
117
111
|
elsif renamed_column?(connection, table_name)
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "delegate"
|
4
|
+
|
5
|
+
module OnlineMigrations
|
6
|
+
module SchemaDumper
|
7
|
+
def initialize(connection, 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
|