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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/README.md +18 -73
  4. data/docs/background_schema_migrations.md +2 -1
  5. data/docs/configuring.md +8 -0
  6. data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +1 -1
  7. data/lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt +31 -0
  8. data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +1 -1
  9. data/lib/generators/online_migrations/templates/initializer.rb.tt +3 -0
  10. data/lib/generators/online_migrations/templates/install_migration.rb.tt +2 -0
  11. data/lib/generators/online_migrations/templates/migration.rb.tt +2 -2
  12. data/lib/generators/online_migrations/upgrade_generator.rb +4 -0
  13. data/lib/online_migrations/background_migrations/migration.rb +16 -22
  14. data/lib/online_migrations/background_migrations/migration_job.rb +8 -7
  15. data/lib/online_migrations/background_migrations/migration_job_runner.rb +3 -1
  16. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +2 -1
  17. data/lib/online_migrations/background_migrations/migration_runner.rb +13 -6
  18. data/lib/online_migrations/background_schema_migrations/migration.rb +30 -25
  19. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +1 -1
  20. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +6 -2
  21. data/lib/online_migrations/background_schema_migrations/migration_status_validator.rb +6 -2
  22. data/lib/online_migrations/batch_iterator.rb +7 -4
  23. data/lib/online_migrations/change_column_type_helpers.rb +72 -12
  24. data/lib/online_migrations/command_checker.rb +32 -20
  25. data/lib/online_migrations/config.rb +8 -0
  26. data/lib/online_migrations/error_messages.rb +16 -0
  27. data/lib/online_migrations/index_definition.rb +1 -1
  28. data/lib/online_migrations/lock_retrier.rb +6 -9
  29. data/lib/online_migrations/migration.rb +8 -1
  30. data/lib/online_migrations/schema_cache.rb +0 -78
  31. data/lib/online_migrations/schema_statements.rb +20 -81
  32. data/lib/online_migrations/utils.rb +1 -20
  33. data/lib/online_migrations/verbose_sql_logs.rb +1 -7
  34. data/lib/online_migrations/version.rb +1 -1
  35. data/lib/online_migrations.rb +1 -8
  36. 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
- unscopes = Utils.ar_version < 7.1 ? [:includes] : [:includes, :preload, :eager_load]
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
- relation = relation.where(relation.arel_table[column].public_send((order == :asc ? :gteq : :lteq), start))
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
- relation = relation.where(relation.arel_table[column].public_send((order == :asc ? :lteq : :gteq), finish))
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
- conversions = column_names.index_with do |column_name|
362
- __change_type_column(column_name)
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, conversions.keys, conversions.values)
367
- remove_columns(table_name, *conversions.values)
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 index.columns.is_a?(String)
407
- index.columns.gsub(/\b#{from_column}\b/, to_column)
412
+ if columns.is_a?(String)
413
+ columns.gsub(/\b#{from_column}\b/, to_column)
408
414
  else
409
- index.columns.map do |column|
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["def"] == "CHECK ((#{column_name} IS NOT NULL))" ||
393
- c["def"] == "CHECK ((#{connection.quote_column_name(column_name)} IS NOT NULL))"
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 = Utils.index_name(table_name, column_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
- # Expression index
838
- (index.columns.is_a?(String) && index.columns.include?(column)) ||
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
- (columns & Array(other.columns)).any?
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
- # Rewrite this with `IndexDefinition#defined_for?` when Active Record >= 7.1 is supported.
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
- schema = __schema_for_table(table_name)
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 __check_constraint_exists?(table_name, expression: expression, **options)
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 __check_constraint_exists?(table_name, expression: expression, **options)
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
- if Utils.ar_version >= 7.1
878
- def add_exclusion_constraint(table_name, expression, **options)
879
- if __exclusion_constraint_exists?(table_name, expression: expression, **options)
880
- Utils.say(<<~MSG.squish)
881
- Exclusion constraint was not created because it already exists (this may be due to an aborted migration or similar).
882
- table_name: #{table_name}, expression: #{expression}
883
- MSG
884
- else
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.select do |old_name, _|
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.select do |table_name, _|
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.connection = self
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
- __check_constraint_exists?(table_name, name: name)
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
- __check_constraint_exists?(table_name, name: name)
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"