online_migrations 0.25.0 → 0.27.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +18 -73
  4. data/docs/0.27-upgrade.md +24 -0
  5. data/docs/background_data_migrations.md +200 -101
  6. data/docs/background_schema_migrations.md +2 -2
  7. data/docs/configuring.md +8 -0
  8. data/lib/generators/online_migrations/{background_migration_generator.rb → data_migration_generator.rb} +4 -4
  9. data/lib/generators/online_migrations/templates/add_sharding_to_online_migrations.rb.tt +1 -1
  10. data/lib/generators/online_migrations/templates/add_timestamps_to_background_migrations.rb.tt +1 -1
  11. data/lib/generators/online_migrations/templates/background_schema_migrations_change_unique_index.rb.tt +1 -1
  12. data/lib/generators/online_migrations/templates/change_background_data_migrations.rb.tt +34 -0
  13. data/lib/generators/online_migrations/templates/{background_data_migration.rb.tt → data_migration.rb.tt} +8 -9
  14. data/lib/generators/online_migrations/templates/initializer.rb.tt +22 -25
  15. data/lib/generators/online_migrations/templates/install_migration.rb.tt +9 -40
  16. data/lib/generators/online_migrations/upgrade_generator.rb +16 -8
  17. data/lib/online_migrations/active_record_batch_enumerator.rb +8 -0
  18. data/lib/online_migrations/background_data_migrations/backfill_column.rb +50 -0
  19. data/lib/online_migrations/background_data_migrations/config.rb +62 -0
  20. data/lib/online_migrations/{background_migrations → background_data_migrations}/copy_column.rb +15 -28
  21. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_associated_records.rb +9 -5
  22. data/lib/online_migrations/{background_migrations → background_data_migrations}/delete_orphaned_records.rb +5 -9
  23. data/lib/online_migrations/background_data_migrations/migration.rb +312 -0
  24. data/lib/online_migrations/{background_migrations → background_data_migrations}/migration_helpers.rb +72 -61
  25. data/lib/online_migrations/background_data_migrations/migration_job.rb +158 -0
  26. data/lib/online_migrations/background_data_migrations/migration_status_validator.rb +65 -0
  27. data/lib/online_migrations/{background_migrations → background_data_migrations}/perform_action_on_relation.rb +5 -5
  28. data/lib/online_migrations/{background_migrations → background_data_migrations}/reset_counters.rb +5 -5
  29. data/lib/online_migrations/background_data_migrations/scheduler.rb +78 -0
  30. data/lib/online_migrations/background_data_migrations/ticker.rb +62 -0
  31. data/lib/online_migrations/background_schema_migrations/config.rb +2 -2
  32. data/lib/online_migrations/background_schema_migrations/migration.rb +57 -127
  33. data/lib/online_migrations/background_schema_migrations/migration_helpers.rb +26 -47
  34. data/lib/online_migrations/background_schema_migrations/migration_runner.rb +43 -97
  35. data/lib/online_migrations/background_schema_migrations/scheduler.rb +2 -2
  36. data/lib/online_migrations/batch_iterator.rb +7 -4
  37. data/lib/online_migrations/change_column_type_helpers.rb +75 -14
  38. data/lib/online_migrations/command_checker.rb +32 -20
  39. data/lib/online_migrations/config.rb +12 -4
  40. data/lib/online_migrations/data_migration.rb +127 -0
  41. data/lib/online_migrations/error_messages.rb +16 -0
  42. data/lib/online_migrations/index_definition.rb +1 -1
  43. data/lib/online_migrations/lock_retrier.rb +5 -2
  44. data/lib/online_migrations/migration.rb +8 -1
  45. data/lib/online_migrations/schema_cache.rb +0 -78
  46. data/lib/online_migrations/schema_statements.rb +18 -74
  47. data/lib/online_migrations/shard_aware.rb +44 -0
  48. data/lib/online_migrations/utils.rb +1 -20
  49. data/lib/online_migrations/verbose_sql_logs.rb +1 -7
  50. data/lib/online_migrations/version.rb +1 -1
  51. data/lib/online_migrations.rb +19 -19
  52. metadata +25 -24
  53. data/lib/online_migrations/background_migration.rb +0 -64
  54. data/lib/online_migrations/background_migrations/backfill_column.rb +0 -54
  55. data/lib/online_migrations/background_migrations/background_migration_class_validator.rb +0 -29
  56. data/lib/online_migrations/background_migrations/config.rb +0 -74
  57. data/lib/online_migrations/background_migrations/migration.rb +0 -329
  58. data/lib/online_migrations/background_migrations/migration_job.rb +0 -109
  59. data/lib/online_migrations/background_migrations/migration_job_runner.rb +0 -66
  60. data/lib/online_migrations/background_migrations/migration_job_status_validator.rb +0 -29
  61. data/lib/online_migrations/background_migrations/migration_runner.rb +0 -161
  62. data/lib/online_migrations/background_migrations/migration_status_validator.rb +0 -48
  63. data/lib/online_migrations/background_migrations/scheduler.rb +0 -42
@@ -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
@@ -461,10 +477,11 @@ module OnlineMigrations
461
477
  end
462
478
 
463
479
  def __copy_check_constraints(table_name, from_column, to_column)
464
- check_constraints = check_constraints(table_name).select { |c| c.expression.include?(from_column) }
480
+ from_column_re = /\b#{from_column}\b/
481
+ check_constraints = check_constraints(table_name).select { |c| c.expression.match?(from_column_re) }
465
482
 
466
483
  check_constraints.each do |check|
467
- new_expression = check.expression.gsub(from_column, to_column)
484
+ new_expression = check.expression.gsub(from_column_re, to_column)
468
485
 
469
486
  add_check_constraint(table_name, new_expression, validate: false)
470
487
 
@@ -511,6 +528,13 @@ module OnlineMigrations
511
528
  # Lock the table explicitly to prevent new rows being inserted
512
529
  execute("LOCK TABLE #{quoted_table_name} IN ACCESS EXCLUSIVE MODE")
513
530
 
531
+ # https://stackoverflow.com/questions/47301722/how-can-view-depends-on-primary-key-constraint-in-postgres
532
+ #
533
+ # PG::DependentObjectsStillExist: ERROR: cannot drop constraint appointments_pkey on table appointments because other objects depend on it (PG::DependentObjectsStillExist)
534
+ # DETAIL: view appointment_statuses depends on constraint appointments_pkey on table appointments
535
+ # HINT: Use DROP ... CASCADE to drop the dependent objects too.
536
+ views = __drop_dependent_views(table_name)
537
+
514
538
  swap_column_names(table_name, column_name, tmp_column_name)
515
539
 
516
540
  # We need to update the trigger function in order to make PostgreSQL to
@@ -530,6 +554,8 @@ module OnlineMigrations
530
554
  execute("ALTER TABLE #{quoted_table_name} DROP CONSTRAINT #{quote_table_name(pkey_constraint_name)}")
531
555
  rename_index(table_name, pkey_index_name, pkey_constraint_name)
532
556
  execute("ALTER TABLE #{quoted_table_name} ADD CONSTRAINT #{quote_table_name(pkey_constraint_name)} PRIMARY KEY USING INDEX #{quote_table_name(pkey_constraint_name)}")
557
+
558
+ __recreate_views(views)
533
559
  end
534
560
  end
535
561
 
@@ -579,5 +605,40 @@ module OnlineMigrations
579
605
  function_name = __copy_triggers_name(table_name, column_names, tmp_column_names)
580
606
  execute("ALTER FUNCTION #{quote_table_name(function_name)}() RESET ALL")
581
607
  end
608
+
609
+ # https://stackoverflow.com/questions/69458819/is-there-any-way-to-list-all-the-views-related-to-a-table-in-the-existing-postgr
610
+ def __drop_dependent_views(table_name)
611
+ views = select_all(<<~SQL)
612
+ SELECT
613
+ u.view_schema AS schema,
614
+ u.view_name AS name,
615
+ v.view_definition AS definition,
616
+ c.relkind = 'm' AS materialized
617
+ FROM information_schema.view_table_usage u
618
+ JOIN information_schema.views v ON u.view_schema = v.table_schema
619
+ AND u.view_name = v.table_name
620
+ JOIN pg_class c ON c.relname = u.view_name
621
+ WHERE u.table_schema NOT IN ('information_schema', 'pg_catalog')
622
+ AND u.table_name = #{quote(table_name)}
623
+ ORDER BY u.view_schema, u.view_name
624
+ SQL
625
+
626
+ views.each do |row|
627
+ execute("DROP VIEW #{quote_table_name(row['schema'])}.#{quote_table_name(row['name'])}")
628
+ end
629
+
630
+ views
631
+ end
632
+
633
+ def __recreate_views(views)
634
+ views.each do |row|
635
+ schema, name, definition, materialized = row.values_at("schema", "name", "definition", "materialized")
636
+
637
+ execute(<<~SQL)
638
+ CREATE#{' MATERIALIZED' if materialized} VIEW #{quote_table_name(schema)}.#{quote_table_name(name)} AS
639
+ #{definition}
640
+ SQL
641
+ end
642
+ end
582
643
  end
583
644
  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
@@ -203,10 +210,10 @@ module OnlineMigrations
203
210
 
204
211
  # Configuration object to configure background migrations
205
212
  #
206
- # @return [BackgroundMigrationsConfig]
207
- # @see BackgroundMigrationsConfig
213
+ # @return [BackgroundDataMigrations::Config]
214
+ # @see BackgroundDataMigrations::Config
208
215
  #
209
- attr_reader :background_migrations
216
+ attr_reader :background_data_migrations
210
217
 
211
218
  attr_reader :background_schema_migrations
212
219
 
@@ -223,13 +230,14 @@ module OnlineMigrations
223
230
  lock_timeout: 0.2.seconds
224
231
  )
225
232
 
226
- @background_migrations = BackgroundMigrations::Config.new
233
+ @background_data_migrations = BackgroundDataMigrations::Config.new
227
234
  @background_schema_migrations = BackgroundSchemaMigrations::Config.new
228
235
 
229
236
  @checks = []
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
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ gem "sidekiq", ">= 7.3.3"
4
+ require "sidekiq"
5
+
6
+ module OnlineMigrations
7
+ # Base class that is inherited by the host application's data migration classes.
8
+ class DataMigration
9
+ class NotFoundError < NameError; end
10
+
11
+ class << self
12
+ # Finds a Data Migration with the given name.
13
+ #
14
+ # @param name [String] the name of the Data Migration to be found.
15
+ #
16
+ # @return [DataMigration] the Data Migration with the given name.
17
+ #
18
+ # @raise [NotFoundError] if a Data Migration with the given name does not exist.
19
+ #
20
+ def named(name)
21
+ namespace = OnlineMigrations.config.background_data_migrations.migrations_module.constantize
22
+ internal_namespace = ::OnlineMigrations::BackgroundDataMigrations
23
+
24
+ migration = "#{namespace}::#{name}".safe_constantize ||
25
+ "#{internal_namespace}::#{name}".safe_constantize
26
+
27
+ raise NotFoundError.new("Data Migration #{name} not found", name) if migration.nil?
28
+ if !(migration.is_a?(Class) && migration < self)
29
+ raise NotFoundError.new("#{name} is not a Data Migration", name)
30
+ end
31
+
32
+ migration
33
+ end
34
+
35
+ # @private
36
+ attr_accessor :active_record_enumerator_batch_size
37
+
38
+ # Limit the number of records that will be fetched in a single query when
39
+ # iterating over an Active Record collection migration.
40
+ #
41
+ # @param size [Integer] the number of records to fetch in a single query.
42
+ #
43
+ def collection_batch_size(size)
44
+ self.active_record_enumerator_batch_size = size
45
+ end
46
+ end
47
+
48
+ # A hook to override that will be called when the migration starts running.
49
+ #
50
+ def after_start
51
+ end
52
+
53
+ # A hook to override that will be called around 'process' each time.
54
+ #
55
+ # Can be useful for some metrics collection, performance tracking etc.
56
+ #
57
+ def around_process
58
+ yield
59
+ end
60
+
61
+ # A hook to override that will be called when the migration resumes its work.
62
+ #
63
+ def after_resume
64
+ end
65
+
66
+ # A hook to override that will be called each time the migration is interrupted.
67
+ #
68
+ # This can be due to interruption or sidekiq stopping.
69
+ #
70
+ def after_stop
71
+ end
72
+
73
+ # A hook to override that will be called when the migration finished its work.
74
+ #
75
+ def after_complete
76
+ end
77
+
78
+ # A hook to override that will be called when the migration is paused.
79
+ #
80
+ def after_pause
81
+ end
82
+
83
+ # A hook to override that will be called when the migration is cancelled.
84
+ #
85
+ def after_cancel
86
+ end
87
+
88
+ # The collection to be processed.
89
+ #
90
+ # @return [ActiveRecord::Relation, ActiveRecord::Batches::BatchEnumerator, Array, Enumerator]
91
+ #
92
+ # @raise [NotImplementedError] with a message advising subclasses to override this method.
93
+ #
94
+ def collection
95
+ raise NotImplementedError, "#{self.class.name} must implement a 'collection' method"
96
+ end
97
+
98
+ # The action to be performed on each item from the collection.
99
+ #
100
+ # @param _item the current item from the collection being iterated
101
+ # @raise [NotImplementedError] with a message advising subclasses to override this method.
102
+ #
103
+ def process(_item)
104
+ raise NotImplementedError, "#{self.class.name} must implement a 'process' method"
105
+ end
106
+
107
+ # Total count of iterations to be performed (optional, to be able to show progress).
108
+ #
109
+ # @return [Integer, nil]
110
+ #
111
+ def count
112
+ end
113
+
114
+ # Enumerator builder. You may override this method to return any Enumerator yielding
115
+ # pairs of `[item, item_cursor]`, instead of using `collection`.
116
+ #
117
+ # It is useful when it is not practical or impossible to define an explicit collection
118
+ # in the `collection` method.
119
+ #
120
+ # @param cursor [Object, nil] cursor position to resume from, or nil on initial call.
121
+ #
122
+ # @return [Enumerator]
123
+ #
124
+ def build_enumerator(cursor:)
125
+ end
126
+ end
127
+ end
@@ -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]
@@ -91,10 +91,13 @@ module OnlineMigrations
91
91
  else
92
92
  yield
93
93
  end
94
- rescue ActiveRecord::LockWaitTimeout
94
+ rescue ActiveRecord::LockWaitTimeout, ActiveRecord::Deadlocked => e
95
95
  if current_attempt <= attempts
96
96
  current_delay = delay(current_attempt)
97
- Utils.say("Lock timeout. Retrying in #{current_delay} seconds...")
97
+
98
+ problem = e.is_a?(ActiveRecord::Deadlocked) ? "Deadlock detected." : "Lock timeout."
99
+ Utils.say("#{problem} Retrying in #{current_delay} seconds...")
100
+
98
101
  sleep(current_delay)
99
102
  retry
100
103
  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)