strong_migrations 2.0.0 → 2.5.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.
@@ -11,10 +11,16 @@ module StrongMigrations
11
11
 
12
12
  def initialize(migration)
13
13
  @migration = migration
14
+ reset
15
+ end
16
+
17
+ def reset
14
18
  @new_tables = []
15
19
  @new_columns = []
16
20
  @timeouts_set = false
17
21
  @committed = false
22
+ @transaction_disabled = false
23
+ @skip_retries = false
18
24
  end
19
25
 
20
26
  def self.safety_assured
@@ -27,7 +33,10 @@ module StrongMigrations
27
33
  end
28
34
  end
29
35
 
30
- def perform(method, *args)
36
+ def perform(method, *args, &block)
37
+ return yield if skip?
38
+
39
+ check_adapter
31
40
  check_version_supported
32
41
  set_timeouts
33
42
  check_lock_timeout
@@ -70,6 +79,8 @@ module StrongMigrations
70
79
  check_remove_index(*args)
71
80
  when :rename_column
72
81
  check_rename_column
82
+ when :rename_schema
83
+ check_rename_schema
73
84
  when :rename_table
74
85
  check_rename_table
75
86
  when :validate_check_constraint
@@ -96,19 +107,26 @@ module StrongMigrations
96
107
  # TODO figure out how to handle methods that generate multiple statements
97
108
  # like add_reference(table, ref, index: {algorithm: :concurrently})
98
109
  # lock timeout after first statement will cause retry to fail
99
- retry_lock_timeouts { yield }
110
+ retry_lock_timeouts { perform_method(method, *args, &block) }
100
111
  else
101
- yield
112
+ perform_method(method, *args, &block)
102
113
  end
103
114
 
104
115
  # outdated statistics + a new index can hurt performance of existing queries
105
- if StrongMigrations.auto_analyze && direction == :up && method == :add_index
116
+ if StrongMigrations.auto_analyze && direction == :up && adds_index?(method, *args)
106
117
  adapter.analyze_table(args[0])
107
118
  end
108
119
 
109
120
  result
110
121
  end
111
122
 
123
+ def perform_method(method, *args)
124
+ if StrongMigrations.remove_invalid_indexes && direction == :up && method == :add_index && postgresql?
125
+ remove_invalid_index_if_needed(*args)
126
+ end
127
+ yield
128
+ end
129
+
112
130
  def retry_lock_timeouts(check_committed: false)
113
131
  retries = 0
114
132
  begin
@@ -129,8 +147,22 @@ module StrongMigrations
129
147
  version && version <= StrongMigrations.start_after
130
148
  end
131
149
 
150
+ def skip?
151
+ StrongMigrations.skipped_databases.map(&:to_s).include?(db_config_name)
152
+ end
153
+
132
154
  private
133
155
 
156
+ def check_adapter
157
+ return if defined?(@adapter_checked)
158
+
159
+ if adapter.instance_of?(Adapters::AbstractAdapter)
160
+ warn "[strong_migrations] Unsupported adapter: #{connection.adapter_name}. Use StrongMigrations.skip_database(#{db_config_name.to_sym.inspect}) to silence this warning."
161
+ end
162
+
163
+ @adapter_checked = true
164
+ end
165
+
134
166
  def check_version_supported
135
167
  return if defined?(@version_checked)
136
168
 
@@ -151,6 +183,9 @@ module StrongMigrations
151
183
  if StrongMigrations.statement_timeout
152
184
  adapter.set_statement_timeout(StrongMigrations.statement_timeout)
153
185
  end
186
+ if StrongMigrations.transaction_timeout
187
+ adapter.set_transaction_timeout(StrongMigrations.transaction_timeout)
188
+ end
154
189
  if StrongMigrations.lock_timeout
155
190
  adapter.set_lock_timeout(StrongMigrations.lock_timeout)
156
191
  end
@@ -200,12 +235,57 @@ module StrongMigrations
200
235
  @migration.connection
201
236
  end
202
237
 
238
+ def db_config_name
239
+ connection.pool.db_config.name
240
+ end
241
+
203
242
  def retry_lock_timeouts?(method)
204
243
  (
205
244
  StrongMigrations.lock_timeout_retries > 0 &&
206
245
  !in_transaction? &&
207
- method != :transaction
246
+ method != :transaction &&
247
+ !@skip_retries
208
248
  )
209
249
  end
250
+
251
+ def without_retries
252
+ previous_value = @skip_retries
253
+ begin
254
+ @skip_retries = true
255
+ yield
256
+ ensure
257
+ @skip_retries = previous_value
258
+ end
259
+ end
260
+
261
+ def adds_index?(method, *args)
262
+ case method
263
+ when :add_index
264
+ true
265
+ when :add_reference, :add_belongs_to
266
+ options = args.extract_options!
267
+ !!options.fetch(:index, true)
268
+ else
269
+ false
270
+ end
271
+ end
272
+
273
+ # REINDEX INDEX CONCURRENTLY leaves a new invalid index if it fails, so use remove_index instead
274
+ def remove_invalid_index_if_needed(*args)
275
+ options = args.extract_options!
276
+
277
+ # ensures has same options as existing index
278
+ # check args to avoid errors with index_exists?
279
+ return unless args.size == 2 && connection.index_exists?(*args, **options.merge(valid: false))
280
+
281
+ table, columns = args
282
+ index_name = options.fetch(:name, connection.index_name(table, columns))
283
+
284
+ @migration.say("Attempting to remove invalid index")
285
+ without_retries do
286
+ # TODO pass index schema for extra safety?
287
+ @migration.remove_index(table, **{name: index_name}.merge(options.slice(:algorithm)))
288
+ end
289
+ end
210
290
  end
211
291
  end
@@ -10,7 +10,7 @@ module StrongMigrations
10
10
  if !new_table?(table)
11
11
  if postgresql? && options[:validate] != false
12
12
  add_options = options.merge(validate: false)
13
- name = options[:name] || @migration.check_constraint_options(table, expression, options)[:name]
13
+ name = options[:name] || connection.check_constraint_options(table, expression, options)[:name]
14
14
  validate_options = {name: name}
15
15
 
16
16
  if StrongMigrations.safe_by_default
@@ -210,17 +210,43 @@ module StrongMigrations
210
210
  end
211
211
 
212
212
  raise_error :change_column, rewrite_blocks: adapter.rewrite_blocks unless safe
213
+
214
+ # constraints must be rechecked
215
+ # Postgres recommends dropping constraints before and adding them back
216
+ # https://www.postgresql.org/docs/current/ddl-alter.html#DDL-ALTER-COLUMN-TYPE
217
+ if postgresql?
218
+ constraints = adapter.constraints(table, column)
219
+ if constraints.any?
220
+ change_commands = []
221
+ constraints.each do |c|
222
+ change_commands << command_str(:remove_check_constraint, [table, c.expression, {name: c.name}])
223
+ end
224
+ change_commands << command_str(:change_column, args + [options])
225
+ constraints.each do |c|
226
+ change_commands << command_str(:add_check_constraint, [table, c.expression, {name: c.name, validate: false}])
227
+ end
228
+
229
+ validate_commands = []
230
+ constraints.each do |c|
231
+ validate_commands << command_str(:validate_check_constraint, [table, {name: c.name}])
232
+ end
233
+
234
+ raise_error :change_column_constraint,
235
+ change_column_code: change_commands.join("\n "),
236
+ validate_constraint_code: validate_commands.join("\n ")
237
+ end
238
+ end
213
239
  end
214
240
 
215
241
  def check_change_column_default(*args)
216
242
  table, column, _default_or_changes = args
217
243
 
218
244
  # just check ActiveRecord::Base, even though can override on model
219
- partial_inserts = ar_version >= 7 ? ActiveRecord::Base.partial_inserts : ActiveRecord::Base.partial_writes
245
+ partial_inserts = ActiveRecord::Base.partial_inserts
220
246
 
221
247
  if partial_inserts && !new_column?(table, column)
222
248
  raise_error :change_column_default,
223
- config: ar_version >= 7 ? "partial_inserts" : "partial_writes"
249
+ config: "partial_inserts"
224
250
  end
225
251
  end
226
252
 
@@ -228,32 +254,40 @@ module StrongMigrations
228
254
  table, column, null, default = args
229
255
  if !null
230
256
  if postgresql?
231
- safe = adapter.constraints(table).any? { |c| c["def"] == "CHECK ((#{column} IS NOT NULL))" || c["def"] == "CHECK ((#{connection.quote_column_name(column)} IS NOT NULL))" }
257
+ constraints = connection.check_constraints(table)
258
+ safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
232
259
 
233
260
  unless safe
261
+ expression = "#{quote_column_if_needed(column)} IS NOT NULL"
262
+
234
263
  # match https://github.com/nullobject/rein
235
264
  constraint_name = "#{table}_#{column}_null"
265
+ if adapter.max_constraint_name_length && constraint_name.bytesize > adapter.max_constraint_name_length
266
+ constraint_name = connection.check_constraint_options(table, expression, {})[:name]
236
267
 
237
- add_code = constraint_str("ALTER TABLE %s ADD CONSTRAINT %s CHECK (%s IS NOT NULL) NOT VALID", [table, constraint_name, column])
238
- validate_code = constraint_str("ALTER TABLE %s VALIDATE CONSTRAINT %s", [table, constraint_name])
239
- remove_code = constraint_str("ALTER TABLE %s DROP CONSTRAINT %s", [table, constraint_name])
240
-
241
- validate_constraint_code = String.new(command_str(:validate_check_constraint, [table, {name: constraint_name}]))
268
+ # avoid collision with Active Record naming for safe_by_default
269
+ if StrongMigrations.safe_by_default
270
+ constraint_name = constraint_name.sub("rails", "strong_migrations")
271
+ end
272
+ end
242
273
 
274
+ add_args = [table, expression, {name: constraint_name, validate: false}]
275
+ validate_args = [table, {name: constraint_name}]
243
276
  change_args = [table, column, null]
244
-
245
- validate_constraint_code << "\n #{command_str(:change_column_null, change_args)}"
246
- validate_constraint_code << "\n #{command_str(:remove_check_constraint, [table, {name: constraint_name}])}"
277
+ remove_args = [table, {name: constraint_name}]
247
278
 
248
279
  if StrongMigrations.safe_by_default
249
- safe_change_column_null(add_code, validate_code, change_args, remove_code, default)
280
+ safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
250
281
  throw :safe
251
282
  end
252
283
 
253
- add_constraint_code = command_str(:add_check_constraint, [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}])
284
+ add_constraint_code = command_str(:add_check_constraint, add_args)
254
285
 
286
+ up_code = String.new(command_str(:validate_check_constraint, validate_args))
287
+ up_code << "\n #{command_str(:change_column_null, change_args)}"
288
+ up_code << "\n #{command_str(:remove_check_constraint, remove_args)}"
255
289
  down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
256
- validate_constraint_code = "def up\n #{validate_constraint_code}\n end\n\n def down\n #{down_code}\n end"
290
+ validate_constraint_code = "def up\n #{up_code}\n end\n\n def down\n #{down_code}\n end"
257
291
 
258
292
  raise_error :change_column_null_postgresql,
259
293
  add_constraint_code: add_constraint_code,
@@ -302,29 +336,28 @@ module StrongMigrations
302
336
  columns =
303
337
  case method
304
338
  when :remove_timestamps
305
- ["created_at", "updated_at"]
339
+ [:created_at, :updated_at]
306
340
  when :remove_column
307
- [args[1].to_s]
341
+ [args[1]]
308
342
  when :remove_columns
309
- # Active Record 6.1+ supports options
310
343
  if args.last.is_a?(Hash)
311
- args[1..-2].map(&:to_s)
344
+ args[1..-2]
312
345
  else
313
- args[1..-1].map(&:to_s)
346
+ args[1..-1]
314
347
  end
315
348
  else
316
349
  options = args[2] || {}
317
350
  reference = args[1]
318
351
  cols = []
319
- cols << "#{reference}_type" if options[:polymorphic]
320
- cols << "#{reference}_id"
352
+ cols << "#{reference}_type".to_sym if options[:polymorphic]
353
+ cols << "#{reference}_id".to_sym
321
354
  cols
322
355
  end
323
356
 
324
- code = "self.ignored_columns += #{columns.inspect}"
357
+ code = "self.ignored_columns += #{columns.map(&:to_s).inspect}"
325
358
 
326
359
  raise_error :remove_column,
327
- model: args[0].to_s.classify,
360
+ model: model_name(args[0]),
328
361
  code: code,
329
362
  command: command_str(method, args),
330
363
  column_suffix: columns.size > 1 ? "s" : ""
@@ -351,6 +384,10 @@ module StrongMigrations
351
384
  raise_error :rename_column
352
385
  end
353
386
 
387
+ def check_rename_schema
388
+ raise_error :rename_schema
389
+ end
390
+
354
391
  def check_rename_table
355
392
  raise_error :rename_table
356
393
  end
@@ -392,7 +429,7 @@ module StrongMigrations
392
429
  message = message + append if append
393
430
 
394
431
  vars[:migration_name] = @migration.class.name
395
- vars[:migration_suffix] = "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
432
+ vars[:migration_suffix] = migration_suffix
396
433
  vars[:base_model] = "ApplicationRecord"
397
434
 
398
435
  # escape % not followed by {
@@ -433,13 +470,13 @@ module StrongMigrations
433
470
  end
434
471
 
435
472
  def backfill_code(table, column, default, function = false)
436
- model = table.to_s.classify
473
+ model = model_name(table)
437
474
  if function
438
475
  # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
439
476
  update_expr = "#{quote_column_if_needed(column)} = #{default}"
440
- "#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
477
+ "#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
441
478
  else
442
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
479
+ "#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
443
480
  end
444
481
  end
445
482
 
@@ -456,5 +493,13 @@ module StrongMigrations
456
493
  def new_column?(table, column)
457
494
  new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
458
495
  end
496
+
497
+ def migration_suffix
498
+ "[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
499
+ end
500
+
501
+ def model_name(table)
502
+ table.to_s.classify
503
+ end
459
504
  end
460
505
  end
@@ -60,6 +60,22 @@ while the entire table is rewritten. A safer approach is to:
60
60
  change_column_with_not_null:
61
61
  "Changing the type is safe, but setting NOT NULL is not.",
62
62
 
63
+ change_column_constraint: "Changing the type of a column that has check constraints blocks reads and writes
64
+ while every row is checked. Drop the check constraints on the column before
65
+ changing the type and add them back afterwards.
66
+
67
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
68
+ def change
69
+ %{change_column_code}
70
+ end
71
+ end
72
+
73
+ class Validate%{migration_name} < ActiveRecord::Migration%{migration_suffix}
74
+ def change
75
+ %{validate_constraint_code}
76
+ end
77
+ end",
78
+
63
79
  remove_column: "Active Record caches attributes, which causes problems
64
80
  when removing columns. Be sure to ignore the column%{column_suffix}:
65
81
 
@@ -86,6 +102,17 @@ in your application. A safer approach is to:
86
102
  5. Stop writing to the old column
87
103
  6. Drop the old column",
88
104
 
105
+ rename_schema:
106
+ "Renaming a schema that's in use will cause errors
107
+ in your application. A safer approach is to:
108
+
109
+ 1. Create a new schema
110
+ 2. Write to both schemas
111
+ 3. Backfill data from the old schema to the new schema
112
+ 4. Move reads from the old schema to the new schema
113
+ 5. Stop writing to the old schema
114
+ 6. Drop the old schema",
115
+
89
116
  rename_table:
90
117
  "Renaming a table that's in use will cause errors
91
118
  in your application. A safer approach is to:
@@ -147,7 +174,7 @@ you're doing is safe before proceeding, then wrap it in a safety_assured { ... }
147
174
  create_table:
148
175
  "The force option will destroy existing tables.
149
176
  If this is intended, drop the existing table first.
150
- Otherwise, remove the force option.",
177
+ In any case, remove the force option.",
151
178
 
152
179
  execute:
153
180
  "Strong Migrations does not support inspecting what happens inside an
@@ -7,10 +7,7 @@ module StrongMigrations
7
7
  end
8
8
 
9
9
  def method_missing(method, *args)
10
- return super if is_a?(ActiveRecord::Schema)
11
-
12
- # Active Record 7.0.2+ versioned schema
13
- return super if defined?(ActiveRecord::Schema::Definition) && is_a?(ActiveRecord::Schema::Definition)
10
+ return super if is_a?(ActiveRecord::Schema) || is_a?(ActiveRecord::Schema::Definition)
14
11
 
15
12
  catch(:safe) do
16
13
  strong_migrations_checker.perform(method, *args) do
@@ -18,7 +15,8 @@ module StrongMigrations
18
15
  end
19
16
  end
20
17
  end
21
- ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
18
+ # same as ActiveRecord::Migration
19
+ ruby2_keywords(:method_missing)
22
20
 
23
21
  def revert(*)
24
22
  if strong_migrations_checker.version_safe?
@@ -1,9 +1,26 @@
1
1
  module StrongMigrations
2
- module DatabaseTasks
3
- # Active Record 7 adds version argument
4
- def migrate(*args)
2
+ module MigrationContext
3
+ def up(...)
5
4
  super
6
5
  rescue => e
6
+ strong_migrations_process_exception(e)
7
+ end
8
+
9
+ def down(...)
10
+ super
11
+ rescue => e
12
+ strong_migrations_process_exception(e)
13
+ end
14
+
15
+ def run(...)
16
+ super
17
+ rescue => e
18
+ strong_migrations_process_exception(e)
19
+ end
20
+
21
+ private
22
+
23
+ def strong_migrations_process_exception(e)
7
24
  if e.cause.is_a?(StrongMigrations::Error)
8
25
  # strip cause and clean backtrace
9
26
  def e.cause
@@ -1,18 +1,20 @@
1
1
  module StrongMigrations
2
2
  module Migrator
3
- def ddl_transaction(migration, *args)
3
+ def ddl_transaction(migration, ...)
4
4
  return super unless StrongMigrations.lock_timeout_retries > 0 && use_transaction?(migration)
5
5
 
6
6
  # handle MigrationProxy class
7
- migration = migration.send(:migration) if migration.respond_to?(:migration, true)
7
+ migration = migration.send(:migration) if !migration.is_a?(ActiveRecord::Migration) && migration.respond_to?(:migration, true)
8
8
 
9
- # retry migration since the entire transaction needs to be rerun
10
9
  checker = migration.send(:strong_migrations_checker)
10
+ return super if checker.skip?
11
+
12
+ # retry migration since the entire transaction needs to be rerun
11
13
  checker.retry_lock_timeouts(check_committed: true) do
12
14
  # failed transaction reverts timeout, so need to re-apply
13
- checker.timeouts_set = false
15
+ checker.reset
14
16
 
15
- super(migration, *args)
17
+ super(migration, ...)
16
18
  end
17
19
  end
18
20
  end
@@ -1,10 +1,9 @@
1
1
  module StrongMigrations
2
2
  module SafeMethods
3
3
  def safe_by_default_method?(method)
4
- StrongMigrations.safe_by_default && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
4
+ StrongMigrations.safe_by_default && !version_safe? && [:add_index, :add_belongs_to, :add_reference, :remove_index, :add_foreign_key, :add_check_constraint, :change_column_null].include?(method)
5
5
  end
6
6
 
7
- # TODO check if invalid index with expected name exists and remove if needed
8
7
  def safe_add_index(*args, **options)
9
8
  disable_transaction
10
9
  @migration.add_index(*args, **options.merge(algorithm: :concurrently))
@@ -47,14 +46,14 @@ module StrongMigrations
47
46
  def safe_add_foreign_key(from_table, to_table, *args, **options)
48
47
  @migration.reversible do |dir|
49
48
  dir.up do
50
- @migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
49
+ if !connection.foreign_key_exists?(from_table, to_table, **options.merge(validate: false))
50
+ @migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
51
+ end
51
52
  disable_transaction
52
- validate_options = options.slice(:column, :name)
53
- @migration.validate_foreign_key(from_table, to_table, **validate_options)
53
+ @migration.validate_foreign_key(from_table, to_table, **options.slice(:column, :name))
54
54
  end
55
55
  dir.down do
56
- remove_options = options.slice(:column, :name)
57
- @migration.remove_foreign_key(from_table, to_table, **remove_options)
56
+ @migration.remove_foreign_key(from_table, to_table, **options.slice(:column, :name))
58
57
  end
59
58
  end
60
59
  end
@@ -62,7 +61,10 @@ module StrongMigrations
62
61
  def safe_add_check_constraint(table, expression, *args, add_options, validate_options)
63
62
  @migration.reversible do |dir|
64
63
  dir.up do
65
- @migration.add_check_constraint(table, expression, *args, **add_options)
64
+ # only skip invalid constraints
65
+ unless connection.check_constraints(table).any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
66
+ @migration.add_check_constraint(table, expression, *args, **add_options)
67
+ end
66
68
  disable_transaction
67
69
  @migration.validate_check_constraint(table, **validate_options)
68
70
  end
@@ -72,35 +74,60 @@ module StrongMigrations
72
74
  end
73
75
  end
74
76
 
75
- def safe_change_column_null(add_code, validate_code, change_args, remove_code, default)
77
+ def safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
76
78
  @migration.reversible do |dir|
77
79
  dir.up do
78
80
  unless default.nil?
79
- raise Error, "default value not supported yet with safe_by_default"
80
- end
81
+ # TODO search for parent model if needed
82
+ if connection.pool != ActiveRecord::Base.connection_pool
83
+ raise_error :change_column_null,
84
+ code: backfill_code(table, column, default)
85
+ end
86
+
87
+ model =
88
+ Class.new(ActiveRecord::Base) do
89
+ self.table_name = table
90
+
91
+ def self.to_s
92
+ "Backfill"
93
+ end
94
+ end
95
+
96
+ update_sql =
97
+ model.connection_pool.with_connection do |c|
98
+ quoted_column = c.quote_column_name(column)
99
+ quoted_default = c.quote_default_expression(default, c.send(:column_for, table, column))
100
+ "#{quoted_column} = #{quoted_default}"
101
+ end
81
102
 
82
- @migration.safety_assured do
83
- @migration.execute(add_code)
103
+ @migration.say("Backfilling default")
84
104
  disable_transaction
85
- @migration.execute(validate_code)
86
- end
87
- if change_args
88
- @migration.change_column_null(*change_args)
89
- @migration.safety_assured do
90
- @migration.execute(remove_code)
105
+ model.unscoped.in_batches(of: 10000) do |relation|
106
+ relation.where(column => nil).update_all(update_sql)
107
+ sleep(0.01)
91
108
  end
92
109
  end
110
+
111
+ add_options = add_args.extract_options!
112
+ validate_options = validate_args.extract_options!
113
+ remove_options = remove_args.extract_options!
114
+
115
+ # only skip invalid constraints
116
+ unless constraints.any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
117
+ @migration.add_check_constraint(*add_args, **add_options)
118
+ end
119
+ disable_transaction
120
+
121
+ connection.begin_db_transaction
122
+ @migration.validate_check_constraint(*validate_args, **validate_options)
123
+ @migration.change_column_null(*change_args)
124
+ @migration.remove_check_constraint(*remove_args, **remove_options)
125
+ connection.commit_db_transaction
93
126
  end
94
127
  dir.down do
95
- if change_args
96
- down_args = change_args.dup
97
- down_args[2] = true
98
- @migration.change_column_null(*down_args)
99
- else
100
- @migration.safety_assured do
101
- @migration.execute(remove_code)
102
- end
103
- end
128
+ down_args = change_args.dup
129
+ down_args[2] = true
130
+ @migration.change_column_null(*down_args)
104
131
  end
105
132
  end
106
133
  end
@@ -109,13 +136,13 @@ module StrongMigrations
109
136
  # so just commit at start
110
137
  def disable_transaction
111
138
  if in_transaction? && !transaction_disabled
112
- @migration.connection.commit_db_transaction
139
+ connection.commit_db_transaction
113
140
  self.transaction_disabled = true
114
141
  end
115
142
  end
116
143
 
117
144
  def in_transaction?
118
- @migration.connection.open_transactions > 0
145
+ connection.open_transactions > 0
119
146
  end
120
147
  end
121
148
  end
@@ -1,9 +1,9 @@
1
1
  module StrongMigrations
2
2
  module SchemaDumper
3
- def initialize(connection, *args, **options)
3
+ def initialize(connection, ...)
4
4
  return super unless StrongMigrations.alphabetize_schema
5
5
 
6
- super(WrappedConnection.new(connection), *args, **options)
6
+ super(WrappedConnection.new(connection), ...)
7
7
  end
8
8
  end
9
9
 
@@ -14,8 +14,19 @@ module StrongMigrations
14
14
  @connection = connection
15
15
  end
16
16
 
17
- def columns(*args, **options)
18
- @connection.columns(*args, **options).sort_by(&:name)
17
+ def columns(...)
18
+ @connection.columns(...).sort_by(&:name)
19
+ end
20
+
21
+ # forward private methods with send
22
+ # method_missing cannot tell how method was called
23
+ # this is not ideal, but other solutions have drawbacks
24
+ def send(name, ...)
25
+ if respond_to?(name, true)
26
+ super
27
+ else
28
+ @connection.send(name, ...)
29
+ end
19
30
  end
20
31
  end
21
32
  end
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "2.0.0"
2
+ VERSION = "2.5.0"
3
3
  end