online_migrations 0.24.0 → 0.26.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 +34 -0
- data/README.md +18 -73
- data/docs/background_schema_migrations.md +2 -1
- data/docs/configuring.md +8 -0
- data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +1 -1
- data/lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt +31 -0
- data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +1 -1
- data/lib/generators/online_migrations/templates/initializer.rb.tt +3 -0
- data/lib/generators/online_migrations/templates/install_migration.rb.tt +2 -0
- data/lib/generators/online_migrations/templates/migration.rb.tt +2 -2
- data/lib/generators/online_migrations/upgrade_generator.rb +4 -0
- data/lib/online_migrations/background_migrations/migration.rb +16 -22
- data/lib/online_migrations/background_migrations/migration_job.rb +8 -7
- data/lib/online_migrations/background_migrations/migration_job_runner.rb +3 -1
- data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +2 -1
- data/lib/online_migrations/background_migrations/migration_runner.rb +13 -6
- data/lib/online_migrations/background_schema_migrations/migration.rb +30 -25
- data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +1 -1
- data/lib/online_migrations/background_schema_migrations/migration_runner.rb +6 -2
- data/lib/online_migrations/background_schema_migrations/migration_status_validator.rb +6 -2
- data/lib/online_migrations/batch_iterator.rb +7 -4
- data/lib/online_migrations/change_column_type_helpers.rb +72 -12
- data/lib/online_migrations/command_checker.rb +32 -20
- data/lib/online_migrations/config.rb +8 -0
- data/lib/online_migrations/error_messages.rb +16 -0
- data/lib/online_migrations/index_definition.rb +1 -1
- data/lib/online_migrations/lock_retrier.rb +6 -9
- data/lib/online_migrations/migration.rb +8 -1
- data/lib/online_migrations/schema_cache.rb +0 -78
- data/lib/online_migrations/schema_statements.rb +20 -81
- data/lib/online_migrations/utils.rb +1 -20
- data/lib/online_migrations/verbose_sql_logs.rb +1 -7
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +1 -8
- metadata +6 -5
@@ -19,8 +19,7 @@ module OnlineMigrations
|
|
19
19
|
end
|
20
20
|
|
21
21
|
relation = apply_limits(self.relation, column, start, finish, order)
|
22
|
-
|
23
|
-
base_relation = relation.unscope(*unscopes).reselect(column).reorder(column => order)
|
22
|
+
base_relation = relation.unscope(:includes, :preload, :eager_load).reselect(column).reorder(column => order)
|
24
23
|
|
25
24
|
start_id = start || begin
|
26
25
|
start_row = base_relation.uncached { base_relation.first }
|
@@ -84,12 +83,16 @@ module OnlineMigrations
|
|
84
83
|
|
85
84
|
private
|
86
85
|
def apply_limits(relation, column, start, finish, order)
|
86
|
+
arel_column = relation.arel_table[column]
|
87
|
+
|
87
88
|
if start
|
88
|
-
|
89
|
+
predicate = order == :asc ? :gteq : :lteq
|
90
|
+
relation = relation.where(arel_column.public_send(predicate, start))
|
89
91
|
end
|
90
92
|
|
91
93
|
if finish
|
92
|
-
|
94
|
+
predicate = order == :asc ? :lteq : :gteq
|
95
|
+
relation = relation.where(arel_column.public_send(predicate, finish))
|
93
96
|
end
|
94
97
|
|
95
98
|
relation
|
@@ -267,11 +267,7 @@ module OnlineMigrations
|
|
267
267
|
__copy_indexes(table_name, column_name, tmp_column_name)
|
268
268
|
__copy_foreign_keys(table_name, column_name, tmp_column_name)
|
269
269
|
__copy_check_constraints(table_name, column_name, tmp_column_name)
|
270
|
-
|
271
|
-
# Exclusion constraints were added in https://github.com/rails/rails/pull/40224.
|
272
|
-
if Utils.ar_version >= 7.1
|
273
|
-
__copy_exclusion_constraints(table_name, column_name, tmp_column_name)
|
274
|
-
end
|
270
|
+
__copy_exclusion_constraints(table_name, column_name, tmp_column_name)
|
275
271
|
|
276
272
|
if column_name == primary_key
|
277
273
|
__finalize_primary_key_type_change(table_name, column_name, column_names)
|
@@ -358,13 +354,22 @@ module OnlineMigrations
|
|
358
354
|
# @see #cleanup_column_type_change
|
359
355
|
#
|
360
356
|
def cleanup_columns_type_change(table_name, *column_names)
|
361
|
-
|
362
|
-
|
357
|
+
tmp_column_names = column_names.map { |column_name| __change_type_column(column_name) }
|
358
|
+
|
359
|
+
# Safely remove existing indexes and foreign keys first, if any.
|
360
|
+
tmp_column_names.each do |column_name|
|
361
|
+
__indexes_for(table_name, column_name).each do |index|
|
362
|
+
remove_index(table_name, name: index.name, algorithm: :concurrently)
|
363
|
+
end
|
364
|
+
|
365
|
+
__foreign_keys_for(table_name, column_name).each do |fk|
|
366
|
+
remove_foreign_key(table_name, name: fk.name)
|
367
|
+
end
|
363
368
|
end
|
364
369
|
|
365
370
|
transaction do
|
366
|
-
__remove_copy_triggers(table_name,
|
367
|
-
remove_columns(table_name, *
|
371
|
+
__remove_copy_triggers(table_name, column_names, tmp_column_names)
|
372
|
+
remove_columns(table_name, *tmp_column_names)
|
368
373
|
end
|
369
374
|
end
|
370
375
|
|
@@ -401,12 +406,13 @@ module OnlineMigrations
|
|
401
406
|
to_column = to_column.to_s
|
402
407
|
|
403
408
|
__indexes_for(table_name, from_column).each do |index|
|
409
|
+
columns = index.columns
|
404
410
|
new_columns =
|
405
411
|
# Expression index.
|
406
|
-
if
|
407
|
-
|
412
|
+
if columns.is_a?(String)
|
413
|
+
columns.gsub(/\b#{from_column}\b/, to_column)
|
408
414
|
else
|
409
|
-
|
415
|
+
columns.map do |column|
|
410
416
|
column == from_column ? to_column : column
|
411
417
|
end
|
412
418
|
end
|
@@ -429,6 +435,16 @@ module OnlineMigrations
|
|
429
435
|
options[:opclass] = opclasses
|
430
436
|
end
|
431
437
|
|
438
|
+
# If the index name is custom - do not rely on auto generated index names, because this
|
439
|
+
# doesn't work (idempotency check does not work, rails does not consider ':where' option)
|
440
|
+
# when there are partial and "classic" indexes on the same columns.
|
441
|
+
if index.name == index_name(table_name, columns)
|
442
|
+
options[:name] = index_name(table_name, new_columns)
|
443
|
+
else
|
444
|
+
truncated_index_name = index.name[0, max_identifier_length - "_2".length]
|
445
|
+
options[:name] = "#{truncated_index_name}_2"
|
446
|
+
end
|
447
|
+
|
432
448
|
add_index(table_name, new_columns, **options, algorithm: :concurrently)
|
433
449
|
end
|
434
450
|
end
|
@@ -511,6 +527,13 @@ module OnlineMigrations
|
|
511
527
|
# Lock the table explicitly to prevent new rows being inserted
|
512
528
|
execute("LOCK TABLE #{quoted_table_name} IN ACCESS EXCLUSIVE MODE")
|
513
529
|
|
530
|
+
# https://stackoverflow.com/questions/47301722/how-can-view-depends-on-primary-key-constraint-in-postgres
|
531
|
+
#
|
532
|
+
# PG::DependentObjectsStillExist: ERROR: cannot drop constraint appointments_pkey on table appointments because other objects depend on it (PG::DependentObjectsStillExist)
|
533
|
+
# DETAIL: view appointment_statuses depends on constraint appointments_pkey on table appointments
|
534
|
+
# HINT: Use DROP ... CASCADE to drop the dependent objects too.
|
535
|
+
views = __drop_dependent_views(table_name)
|
536
|
+
|
514
537
|
swap_column_names(table_name, column_name, tmp_column_name)
|
515
538
|
|
516
539
|
# We need to update the trigger function in order to make PostgreSQL to
|
@@ -530,6 +553,8 @@ module OnlineMigrations
|
|
530
553
|
execute("ALTER TABLE #{quoted_table_name} DROP CONSTRAINT #{quote_table_name(pkey_constraint_name)}")
|
531
554
|
rename_index(table_name, pkey_index_name, pkey_constraint_name)
|
532
555
|
execute("ALTER TABLE #{quoted_table_name} ADD CONSTRAINT #{quote_table_name(pkey_constraint_name)} PRIMARY KEY USING INDEX #{quote_table_name(pkey_constraint_name)}")
|
556
|
+
|
557
|
+
__recreate_views(views)
|
533
558
|
end
|
534
559
|
end
|
535
560
|
|
@@ -579,5 +604,40 @@ module OnlineMigrations
|
|
579
604
|
function_name = __copy_triggers_name(table_name, column_names, tmp_column_names)
|
580
605
|
execute("ALTER FUNCTION #{quote_table_name(function_name)}() RESET ALL")
|
581
606
|
end
|
607
|
+
|
608
|
+
# https://stackoverflow.com/questions/69458819/is-there-any-way-to-list-all-the-views-related-to-a-table-in-the-existing-postgr
|
609
|
+
def __drop_dependent_views(table_name)
|
610
|
+
views = select_all(<<~SQL)
|
611
|
+
SELECT
|
612
|
+
u.view_schema AS schema,
|
613
|
+
u.view_name AS name,
|
614
|
+
v.view_definition AS definition,
|
615
|
+
c.relkind = 'm' AS materialized
|
616
|
+
FROM information_schema.view_table_usage u
|
617
|
+
JOIN information_schema.views v ON u.view_schema = v.table_schema
|
618
|
+
AND u.view_name = v.table_name
|
619
|
+
JOIN pg_class c ON c.relname = u.view_name
|
620
|
+
WHERE u.table_schema NOT IN ('information_schema', 'pg_catalog')
|
621
|
+
AND u.table_name = #{quote(table_name)}
|
622
|
+
ORDER BY u.view_schema, u.view_name
|
623
|
+
SQL
|
624
|
+
|
625
|
+
views.each do |row|
|
626
|
+
execute("DROP VIEW #{quote_table_name(row['schema'])}.#{quote_table_name(row['name'])}")
|
627
|
+
end
|
628
|
+
|
629
|
+
views
|
630
|
+
end
|
631
|
+
|
632
|
+
def __recreate_views(views)
|
633
|
+
views.each do |row|
|
634
|
+
schema, name, definition, materialized = row.values_at("schema", "name", "definition", "materialized")
|
635
|
+
|
636
|
+
execute(<<~SQL)
|
637
|
+
CREATE#{' MATERIALIZED' if materialized} VIEW #{quote_table_name(schema)}.#{quote_table_name(name)} AS
|
638
|
+
#{definition}
|
639
|
+
SQL
|
640
|
+
end
|
641
|
+
end
|
582
642
|
end
|
583
643
|
end
|
@@ -80,6 +80,7 @@ module OnlineMigrations
|
|
80
80
|
add_inheritance_column: "adding-a-single-table-inheritance-column",
|
81
81
|
mismatched_foreign_key_type: "mismatched-reference-column-types",
|
82
82
|
}
|
83
|
+
private_constant :ERROR_MESSAGE_TO_LINK
|
83
84
|
|
84
85
|
def check_database_version
|
85
86
|
return if defined?(@database_version_checked)
|
@@ -375,6 +376,31 @@ module OnlineMigrations
|
|
375
376
|
cleanup_code: command_str(:cleanup_column_type_change, table_name, column_name),
|
376
377
|
cleanup_down_code: command_str(:initialize_column_type_change, table_name, column_name, existing_type)
|
377
378
|
end
|
379
|
+
|
380
|
+
# Constraints must be rechecked.
|
381
|
+
# PostgreSQL recommends dropping constraints before and adding them back.
|
382
|
+
# https://www.postgresql.org/docs/current/ddl-alter.html#DDL-ALTER-COLUMN-TYPE
|
383
|
+
constraints = connection.check_constraints(table_name).select do |c|
|
384
|
+
c.validated? && c.expression.match?(/\b#{column_name}\b/)
|
385
|
+
end
|
386
|
+
|
387
|
+
if constraints.any?
|
388
|
+
change_commands = constraints.map do |c|
|
389
|
+
command_str(:remove_check_constraint, table_name, c.expression, { name: c.name })
|
390
|
+
end
|
391
|
+
change_commands << command_str(:change_column, table_name, column_name, type, **options)
|
392
|
+
constraints.each do |c|
|
393
|
+
change_commands << command_str(:add_check_constraint, table_name, c.expression, { name: c.name, validate: false })
|
394
|
+
end
|
395
|
+
|
396
|
+
validate_commands = constraints.map do |c|
|
397
|
+
command_str(:validate_check_constraint, table_name, { name: c.name })
|
398
|
+
end
|
399
|
+
|
400
|
+
raise_error :change_column_constraint,
|
401
|
+
change_column_code: change_commands.join("\n "),
|
402
|
+
validate_constraint_code: validate_commands.join("\n ")
|
403
|
+
end
|
378
404
|
end
|
379
405
|
end
|
380
406
|
|
@@ -388,9 +414,9 @@ module OnlineMigrations
|
|
388
414
|
if !allow_null && !new_or_small_table?(table_name)
|
389
415
|
# In PostgreSQL 12+ you can add a check constraint to the table
|
390
416
|
# and then "promote" it to NOT NULL for the column.
|
391
|
-
safe = check_constraints(table_name).any? do |c|
|
392
|
-
c
|
393
|
-
c
|
417
|
+
safe = connection.check_constraints(table_name).select(&:validated?).any? do |c|
|
418
|
+
c.expression == "#{column_name} IS NOT NULL" ||
|
419
|
+
c.expression == "#{connection.quote_column_name(column_name)} IS NOT NULL"
|
394
420
|
end
|
395
421
|
|
396
422
|
if !safe
|
@@ -601,7 +627,7 @@ module OnlineMigrations
|
|
601
627
|
def add_unique_constraint(table_name, column_name = nil, **options)
|
602
628
|
return if new_or_small_table?(table_name) || options[:using_index] || !column_name
|
603
629
|
|
604
|
-
index_name =
|
630
|
+
index_name = connection.index_name(table_name, column_name)
|
605
631
|
|
606
632
|
raise_error :add_unique_constraint,
|
607
633
|
add_index_code: command_str(:add_index, table_name, column_name, unique: true, name: index_name, algorithm: :concurrently),
|
@@ -765,18 +791,6 @@ module OnlineMigrations
|
|
765
791
|
"chk_rails_#{hashed_identifier}"
|
766
792
|
end
|
767
793
|
|
768
|
-
def check_constraints(table_name)
|
769
|
-
constraints_query = <<~SQL
|
770
|
-
SELECT pg_get_constraintdef(oid) AS def
|
771
|
-
FROM pg_constraint
|
772
|
-
WHERE contype = 'c'
|
773
|
-
AND convalidated
|
774
|
-
AND conrelid = #{connection.quote(table_name)}::regclass
|
775
|
-
SQL
|
776
|
-
|
777
|
-
connection.select_all(constraints_query).to_a
|
778
|
-
end
|
779
|
-
|
780
794
|
def check_inheritance_column(table_name, column_name, default)
|
781
795
|
if column_name.to_s == ActiveRecord::Base.inheritance_column && !default.nil?
|
782
796
|
raise_error :add_inheritance_column,
|
@@ -834,10 +848,8 @@ module OnlineMigrations
|
|
834
848
|
end
|
835
849
|
|
836
850
|
def index_include_column?(index, column)
|
837
|
-
|
838
|
-
|
839
|
-
index.columns.include?(column) ||
|
840
|
-
(Utils.ar_version >= 7.1 && index.include && index.include.include?(column)) ||
|
851
|
+
index.columns.include?(column) ||
|
852
|
+
(index.include && index.include.include?(column)) ||
|
841
853
|
(index.where && index.where.include?(column))
|
842
854
|
end
|
843
855
|
|
@@ -73,6 +73,13 @@ module OnlineMigrations
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
+
# Whether to require safety reason explanation when calling #safery_assured
|
77
|
+
#
|
78
|
+
# Disabled by default
|
79
|
+
# @return [Boolean]
|
80
|
+
#
|
81
|
+
attr_accessor :require_safety_assured_reason
|
82
|
+
|
76
83
|
# Whether to perform checks when migrating down
|
77
84
|
#
|
78
85
|
# Disabled by default
|
@@ -230,6 +237,7 @@ module OnlineMigrations
|
|
230
237
|
@start_after = 0
|
231
238
|
@target_version = nil
|
232
239
|
@small_tables = []
|
240
|
+
@require_safety_assured_reason = false
|
233
241
|
@check_down = false
|
234
242
|
@auto_analyze = false
|
235
243
|
@alphabetize_schema = false
|
@@ -242,6 +242,22 @@ during writes works automatically). For most column type changes, this does not
|
|
242
242
|
8. Remove changes from step 3, if any
|
243
243
|
9. Deploy",
|
244
244
|
|
245
|
+
change_column_constraint: "Changing the type of a column that has check constraints blocks reads and writes
|
246
|
+
while every row is checked. Drop the check constraints on the column before
|
247
|
+
changing the type and add them back afterwards.
|
248
|
+
|
249
|
+
class <%= migration_name %> < <%= migration_parent %>
|
250
|
+
def change
|
251
|
+
<%= change_column_code %>
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class Validate<%= migration_name %> < <%= migration_parent %>
|
256
|
+
def change
|
257
|
+
<%= validate_constraint_code %>
|
258
|
+
end
|
259
|
+
end",
|
260
|
+
|
245
261
|
change_column_default:
|
246
262
|
"Partial writes are enabled, which can cause incorrect values
|
247
263
|
to be inserted when changing the default value of a column.
|
@@ -20,7 +20,7 @@ module OnlineMigrations
|
|
20
20
|
# For ActiveRecord::ConnectionAdapters::IndexDefinition is for expression indexes,
|
21
21
|
# `columns` is a string
|
22
22
|
table == other.table &&
|
23
|
-
(
|
23
|
+
columns.intersect?(Array(other.columns))
|
24
24
|
end
|
25
25
|
|
26
26
|
# @param other [OnlineMigrations::IndexDefinition, ActiveRecord::ConnectionAdapters::IndexDefinition]
|
@@ -46,10 +46,6 @@ module OnlineMigrations
|
|
46
46
|
# end
|
47
47
|
#
|
48
48
|
class LockRetrier
|
49
|
-
# Database connection on which retries are run
|
50
|
-
#
|
51
|
-
attr_accessor :connection
|
52
|
-
|
53
49
|
# Returns the number of retrying attempts
|
54
50
|
#
|
55
51
|
def attempts
|
@@ -73,14 +69,15 @@ module OnlineMigrations
|
|
73
69
|
# Executes the block with a retry mechanism that alters the `lock_timeout`
|
74
70
|
# and sleep time between attempts.
|
75
71
|
#
|
72
|
+
# @param connection The connection on which to retry lock timeouts
|
76
73
|
# @return [void]
|
77
74
|
#
|
78
75
|
# @example
|
79
|
-
# retrier.with_lock_retries do
|
76
|
+
# retrier.with_lock_retries(connection) do
|
80
77
|
# add_column(:users, :name, :string)
|
81
78
|
# end
|
82
79
|
#
|
83
|
-
def with_lock_retries(&block)
|
80
|
+
def with_lock_retries(connection, &block)
|
84
81
|
return yield if lock_retries_disabled?
|
85
82
|
|
86
83
|
current_attempt = 0
|
@@ -90,7 +87,7 @@ module OnlineMigrations
|
|
90
87
|
|
91
88
|
current_lock_timeout = lock_timeout(current_attempt)
|
92
89
|
if current_lock_timeout
|
93
|
-
with_lock_timeout(current_lock_timeout.in_milliseconds, &block)
|
90
|
+
with_lock_timeout(connection, current_lock_timeout.in_milliseconds, &block)
|
94
91
|
else
|
95
92
|
yield
|
96
93
|
end
|
@@ -110,7 +107,7 @@ module OnlineMigrations
|
|
110
107
|
Utils.to_bool(ENV["DISABLE_LOCK_RETRIES"])
|
111
108
|
end
|
112
109
|
|
113
|
-
def with_lock_timeout(value)
|
110
|
+
def with_lock_timeout(connection, value)
|
114
111
|
value = value.ceil.to_i
|
115
112
|
prev_value = connection.select_value("SHOW lock_timeout")
|
116
113
|
connection.execute("SET lock_timeout TO #{connection.quote("#{value}ms")}")
|
@@ -234,7 +231,7 @@ module OnlineMigrations
|
|
234
231
|
def delay(*)
|
235
232
|
end
|
236
233
|
|
237
|
-
def with_lock_retries
|
234
|
+
def with_lock_retries(_connection)
|
238
235
|
yield
|
239
236
|
end
|
240
237
|
end
|
@@ -45,7 +45,14 @@ module OnlineMigrations
|
|
45
45
|
# @example
|
46
46
|
# safety_assured { remove_column(:users, :some_column) }
|
47
47
|
#
|
48
|
-
def safety_assured(&block)
|
48
|
+
def safety_assured(reason = nil, &block)
|
49
|
+
config = OnlineMigrations.config
|
50
|
+
safe_version = version && version <= config.start_after
|
51
|
+
|
52
|
+
if config.require_safety_assured_reason && reason.blank? && !safe_version
|
53
|
+
raise OnlineMigrations::Error, "Specify a safety reason explanation when calling #safety_assured."
|
54
|
+
end
|
55
|
+
|
49
56
|
command_checker.class.safety_assured(&block)
|
50
57
|
end
|
51
58
|
|
@@ -3,84 +3,6 @@
|
|
3
3
|
module OnlineMigrations
|
4
4
|
# @private
|
5
5
|
module SchemaCache
|
6
|
-
def primary_keys(table_name)
|
7
|
-
if (renamed_table = renamed_table?(table_name))
|
8
|
-
super(renamed_table)
|
9
|
-
elsif renamed_column?(table_name)
|
10
|
-
super(column_rename_table(table_name))
|
11
|
-
else
|
12
|
-
super
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
def columns(table_name)
|
17
|
-
if (renamed_table = renamed_table?(table_name))
|
18
|
-
super(renamed_table)
|
19
|
-
elsif renamed_column?(table_name)
|
20
|
-
columns = super(column_rename_table(table_name))
|
21
|
-
OnlineMigrations.config.column_renames[table_name].each do |old_column_name, new_column_name|
|
22
|
-
duplicate_column(old_column_name, new_column_name, columns)
|
23
|
-
end
|
24
|
-
columns
|
25
|
-
else
|
26
|
-
super.reject { |column| column.name.end_with?("_for_type_change") }
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def indexes(table_name)
|
31
|
-
if (renamed_table = renamed_table?(table_name))
|
32
|
-
super(renamed_table)
|
33
|
-
elsif renamed_column?(table_name)
|
34
|
-
super(column_rename_table(table_name))
|
35
|
-
else
|
36
|
-
super
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def clear_data_source_cache!(name)
|
41
|
-
if (renamed_table = renamed_table?(name))
|
42
|
-
super(renamed_table)
|
43
|
-
end
|
44
|
-
|
45
|
-
if renamed_column?(name)
|
46
|
-
super(column_rename_table(name))
|
47
|
-
end
|
48
|
-
|
49
|
-
super
|
50
|
-
end
|
51
|
-
|
52
|
-
private
|
53
|
-
def renamed_table?(table_name)
|
54
|
-
table_renames = OnlineMigrations.config.table_renames
|
55
|
-
if table_renames.key?(table_name)
|
56
|
-
views = connection.views
|
57
|
-
table_renames[table_name] if views.include?(table_name)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def renamed_column?(table_name)
|
62
|
-
column_renames = OnlineMigrations.config.column_renames
|
63
|
-
column_renames.key?(table_name) && connection.views.include?(table_name)
|
64
|
-
end
|
65
|
-
|
66
|
-
def column_rename_table(table_name)
|
67
|
-
"#{table_name}_column_rename"
|
68
|
-
end
|
69
|
-
|
70
|
-
def duplicate_column(old_column_name, new_column_name, columns)
|
71
|
-
old_column = columns.find { |column| column.name == old_column_name }
|
72
|
-
new_column = old_column.dup
|
73
|
-
# Active Record defines only reader for :name
|
74
|
-
new_column.instance_variable_set(:@name, new_column_name)
|
75
|
-
# Correspond to the Active Record freezing of each column
|
76
|
-
columns << new_column.freeze
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# @private
|
81
|
-
module SchemaCache71
|
82
|
-
# Active Record >= 7.1 changed signature of the methods,
|
83
|
-
# see https://github.com/rails/rails/pull/48716.
|
84
6
|
def primary_keys(connection, table_name)
|
85
7
|
if (renamed_table = renamed_table?(connection, table_name))
|
86
8
|
super(connection, renamed_table)
|
@@ -568,8 +568,8 @@ module OnlineMigrations
|
|
568
568
|
#
|
569
569
|
def add_text_limit_constraint(table_name, column_name, limit, name: nil, validate: true)
|
570
570
|
column = column_for(table_name, column_name)
|
571
|
-
if column.type != :text
|
572
|
-
raise "add_text_limit_constraint must be used only with :text columns"
|
571
|
+
if column.type != :text && column.type != :string
|
572
|
+
raise "add_text_limit_constraint must be used only with :text or :string columns"
|
573
573
|
end
|
574
574
|
|
575
575
|
name ||= __text_limit_constraint_name(table_name, column_name)
|
@@ -710,16 +710,12 @@ module OnlineMigrations
|
|
710
710
|
index_name = (options[:name] || index_name(table_name, column_name)).to_s
|
711
711
|
indexes(table_name).find { |i| i.name == index_name }
|
712
712
|
else
|
713
|
-
|
714
|
-
# See https://github.com/rails/rails/pull/45160.
|
715
|
-
indexes(table_name).find { |i| __index_defined_for?(i, column_name, **options) }
|
713
|
+
indexes(table_name).find { |i| i.defined_for?(column_name, **options) }
|
716
714
|
end
|
717
715
|
|
718
716
|
if index
|
719
|
-
|
720
|
-
|
721
|
-
if __index_valid?(index.name, schema: schema)
|
722
|
-
Utils.say("Index was not created because it already exists.")
|
717
|
+
if index.valid?
|
718
|
+
Utils.say("Index #{index.name} was not created because it already exists.")
|
723
719
|
return
|
724
720
|
else
|
725
721
|
Utils.say("Recreating invalid index: table_name: #{table_name}, column_name: #{column_name}")
|
@@ -764,22 +760,6 @@ module OnlineMigrations
|
|
764
760
|
end
|
765
761
|
end
|
766
762
|
|
767
|
-
# @private
|
768
|
-
# From ActiveRecord. Will not be needed for ActiveRecord >= 7.1.
|
769
|
-
def index_name(table_name, options)
|
770
|
-
if options.is_a?(Hash)
|
771
|
-
if options[:column]
|
772
|
-
Utils.index_name(table_name, options[:column])
|
773
|
-
elsif options[:name]
|
774
|
-
options[:name]
|
775
|
-
else
|
776
|
-
raise ArgumentError, "You must specify the index name"
|
777
|
-
end
|
778
|
-
else
|
779
|
-
index_name(table_name, column: options)
|
780
|
-
end
|
781
|
-
end
|
782
|
-
|
783
763
|
# Extends default method to be idempotent.
|
784
764
|
#
|
785
765
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key
|
@@ -833,7 +813,7 @@ module OnlineMigrations
|
|
833
813
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_check_constraint
|
834
814
|
#
|
835
815
|
def add_check_constraint(table_name, expression, **options)
|
836
|
-
if
|
816
|
+
if check_constraint_exists?(table_name, expression: expression, **options)
|
837
817
|
Utils.say(<<~MSG.squish)
|
838
818
|
Check constraint was not created because it already exists (this may be due to an aborted migration or similar).
|
839
819
|
table_name: #{table_name}, expression: #{expression}
|
@@ -864,7 +844,7 @@ module OnlineMigrations
|
|
864
844
|
# @see https://edgeapi.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_check_constraint
|
865
845
|
#
|
866
846
|
def remove_check_constraint(table_name, expression = nil, **options)
|
867
|
-
if
|
847
|
+
if check_constraint_exists?(table_name, expression: expression, **options)
|
868
848
|
super
|
869
849
|
else
|
870
850
|
Utils.say(<<~MSG.squish)
|
@@ -874,16 +854,14 @@ module OnlineMigrations
|
|
874
854
|
end
|
875
855
|
end
|
876
856
|
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
super
|
886
|
-
end
|
857
|
+
def add_exclusion_constraint(table_name, expression, **options)
|
858
|
+
if __exclusion_constraint_exists?(table_name, expression: expression, **options)
|
859
|
+
Utils.say(<<~MSG.squish)
|
860
|
+
Exclusion constraint was not created because it already exists (this may be due to an aborted migration or similar).
|
861
|
+
table_name: #{table_name}, expression: #{expression}
|
862
|
+
MSG
|
863
|
+
else
|
864
|
+
super
|
887
865
|
end
|
888
866
|
end
|
889
867
|
|
@@ -892,14 +870,10 @@ module OnlineMigrations
|
|
892
870
|
views = self.views
|
893
871
|
|
894
872
|
table_renames = OnlineMigrations.config.table_renames
|
895
|
-
renamed_tables = table_renames.
|
896
|
-
views.include?(old_name)
|
897
|
-
end
|
873
|
+
renamed_tables = table_renames.slice(*views)
|
898
874
|
|
899
875
|
column_renames = OnlineMigrations.config.column_renames
|
900
|
-
renamed_columns = column_renames.
|
901
|
-
views.include?(table_name)
|
902
|
-
end
|
876
|
+
renamed_columns = column_renames.slice(*views)
|
903
877
|
|
904
878
|
if renamed_tables.key?(table)
|
905
879
|
super(renamed_tables[table])
|
@@ -919,8 +893,7 @@ module OnlineMigrations
|
|
919
893
|
__ensure_not_in_transaction!
|
920
894
|
|
921
895
|
retrier = OnlineMigrations.config.lock_retrier
|
922
|
-
retrier.
|
923
|
-
retrier.with_lock_retries(&block)
|
896
|
+
retrier.with_lock_retries(self, &block)
|
924
897
|
end
|
925
898
|
|
926
899
|
private
|
@@ -937,20 +910,9 @@ module OnlineMigrations
|
|
937
910
|
end
|
938
911
|
end
|
939
912
|
|
940
|
-
# Will not be needed for Active Record >= 7.1
|
941
|
-
def __index_defined_for?(index, columns = nil, name: nil, unique: nil, valid: nil, include: nil, nulls_not_distinct: nil, **options)
|
942
|
-
columns = options[:column] if columns.blank?
|
943
|
-
(columns.nil? || Array(index.columns) == Array(columns).map(&:to_s)) &&
|
944
|
-
(name.nil? || index.name == name.to_s) &&
|
945
|
-
(unique.nil? || index.unique == unique) &&
|
946
|
-
(valid.nil? || index.valid == valid) &&
|
947
|
-
(include.nil? || Array(index.include) == Array(include).map(&:to_s)) &&
|
948
|
-
(nulls_not_distinct.nil? || index.nulls_not_distinct == nulls_not_distinct)
|
949
|
-
end
|
950
|
-
|
951
913
|
def __not_null_constraint_exists?(table_name, column_name, name: nil)
|
952
914
|
name ||= __not_null_constraint_name(table_name, column_name)
|
953
|
-
|
915
|
+
check_constraint_exists?(table_name, name: name)
|
954
916
|
end
|
955
917
|
|
956
918
|
def __not_null_constraint_name(table_name, column_name)
|
@@ -963,21 +925,7 @@ module OnlineMigrations
|
|
963
925
|
|
964
926
|
def __text_limit_constraint_exists?(table_name, column_name, name: nil)
|
965
927
|
name ||= __text_limit_constraint_name(table_name, column_name)
|
966
|
-
|
967
|
-
end
|
968
|
-
|
969
|
-
# Can use index validity attribute for Active Record >= 7.1.
|
970
|
-
def __index_valid?(index_name, schema:)
|
971
|
-
select_value(<<~SQL)
|
972
|
-
SELECT indisvalid
|
973
|
-
FROM pg_index i
|
974
|
-
JOIN pg_class c
|
975
|
-
ON i.indexrelid = c.oid
|
976
|
-
JOIN pg_namespace n
|
977
|
-
ON c.relnamespace = n.oid
|
978
|
-
WHERE n.nspname = #{schema}
|
979
|
-
AND c.relname = #{quote(index_name)}
|
980
|
-
SQL
|
928
|
+
check_constraint_exists?(table_name, name: name)
|
981
929
|
end
|
982
930
|
|
983
931
|
def __copy_foreign_key(fk, to_column, **options)
|
@@ -1001,15 +949,6 @@ module OnlineMigrations
|
|
1001
949
|
end
|
1002
950
|
end
|
1003
951
|
|
1004
|
-
# Can be replaced by native method in Active Record >= 7.1.
|
1005
|
-
def __check_constraint_exists?(table_name, **options)
|
1006
|
-
if !options.key?(:name) && !options.key?(:expression)
|
1007
|
-
raise ArgumentError, "At least one of :name or :expression must be supplied"
|
1008
|
-
end
|
1009
|
-
|
1010
|
-
check_constraint_for(table_name, **options).present?
|
1011
|
-
end
|
1012
|
-
|
1013
952
|
def __exclusion_constraint_exists?(table_name, **options)
|
1014
953
|
if !options.key?(:name) && !options.key?(:expression)
|
1015
954
|
raise ArgumentError, "At least one of :name or :expression must be supplied"
|