strong_migrations 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/README.md +36 -3
- data/lib/strong_migrations/adapters/abstract_adapter.rb +2 -2
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +8 -8
- data/lib/strong_migrations/checker.rb +70 -4
- data/lib/strong_migrations/checks.rb +30 -25
- data/lib/strong_migrations/error_messages.rb +1 -1
- data/lib/strong_migrations/migration.rb +2 -1
- data/lib/strong_migrations/{database_tasks.rb → migration_context.rb} +20 -2
- data/lib/strong_migrations/migrator.rb +4 -2
- data/lib/strong_migrations/safe_methods.rb +31 -29
- data/lib/strong_migrations/version.rb +1 -1
- data/lib/strong_migrations.rb +9 -6
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c90bd33b534a7c37e59499e81b9c61e7d67a45bc849fd9dfe4d58285631438bf
|
4
|
+
data.tar.gz: 3771261014091caf3c28369db3ae4f97a1434d92b3207b351bf92c839caaec70
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aaa494cbbf0545df8795a5243d80f150e3be939507ab9379c20eb8b7cd68d5fde29c86cce63fffb005e3cd6ff5c858d084eb335bc7c911cccb4da751cfbbac32
|
7
|
+
data.tar.gz: b4833b425a23328795f14a6bcd94a4d06e11da5272976dfbeaaebcc469454b685058bc65c109561c513bc2434acf9119ecbae8b135b3c6d020d5ada607e86a38
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## 2.1.0 (2024-11-08)
|
2
|
+
|
3
|
+
- Added `skip_database` method
|
4
|
+
- Added experimental `remove_invalid_indexes` option
|
5
|
+
- Added warning for unsupported adapters
|
6
|
+
- Improved output for `db:forward`, `db:rollback`, `db:migrate:up`, and `db:migrate:down`
|
7
|
+
- Made operations more retriable with `safe_by_default`
|
8
|
+
|
1
9
|
## 2.0.2 (2024-10-30)
|
2
10
|
|
3
11
|
- Fixed migrations not running with Active Record 8 rc2
|
data/README.md
CHANGED
@@ -351,6 +351,8 @@ end
|
|
351
351
|
|
352
352
|
### Backfilling data
|
353
353
|
|
354
|
+
Note: Strong Migrations does not detect dangerous backfills.
|
355
|
+
|
354
356
|
#### Bad
|
355
357
|
|
356
358
|
Active Record creates a transaction around each migration, and backfilling in the same transaction that alters a table keeps the table locked for the [duration of the backfill](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
@@ -383,6 +385,8 @@ class BackfillSomeColumn < ActiveRecord::Migration[7.2]
|
|
383
385
|
end
|
384
386
|
```
|
385
387
|
|
388
|
+
Note: If backfilling with a method other than `update_all`, use `User.reset_column_information` to ensure the model has up-to-date column information.
|
389
|
+
|
386
390
|
### Adding an index non-concurrently
|
387
391
|
|
388
392
|
:turtle: Safe by default available
|
@@ -608,11 +612,16 @@ Then validate it in a separate migration. Once the check constraint is validated
|
|
608
612
|
|
609
613
|
```ruby
|
610
614
|
class ValidateSomeColumnNotNull < ActiveRecord::Migration[7.2]
|
611
|
-
def
|
615
|
+
def up
|
612
616
|
validate_check_constraint :users, name: "users_some_column_null"
|
613
617
|
change_column_null :users, :some_column, false
|
614
618
|
remove_check_constraint :users, name: "users_some_column_null"
|
615
619
|
end
|
620
|
+
|
621
|
+
def down
|
622
|
+
add_check_constraint :users, "some_column IS NOT NULL", name: "users_some_column_null", validate: false
|
623
|
+
change_column_null :users, :some_column, true
|
624
|
+
end
|
616
625
|
end
|
617
626
|
```
|
618
627
|
|
@@ -723,7 +732,7 @@ Certain methods like `execute` and `change_table` cannot be inspected and are pr
|
|
723
732
|
|
724
733
|
## Safe by Default
|
725
734
|
|
726
|
-
Make operations safe by default.
|
735
|
+
Make certain operations safe by default. This allows you to write the code under the "Bad" section, but the migration will be performed as if you had written the "Good" version.
|
727
736
|
|
728
737
|
- adding and removing an index
|
729
738
|
- adding a foreign key
|
@@ -772,6 +781,16 @@ StrongMigrations.disable_check(:add_index)
|
|
772
781
|
|
773
782
|
Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
|
774
783
|
|
784
|
+
## Skip Databases
|
785
|
+
|
786
|
+
Skip checks and other functionality for specific databases with:
|
787
|
+
|
788
|
+
```ruby
|
789
|
+
StrongMigrations.skip_database(:catalog)
|
790
|
+
```
|
791
|
+
|
792
|
+
Note: This does not affect `alphabetize_schema`.
|
793
|
+
|
775
794
|
## Down Migrations / Rollbacks
|
776
795
|
|
777
796
|
By default, checks are disabled when migrating down. Enable them with:
|
@@ -853,7 +872,21 @@ production:
|
|
853
872
|
|
854
873
|
For HTTP connections, Redis, and other services, check out [this guide](https://github.com/ankane/the-ultimate-guide-to-ruby-timeouts).
|
855
874
|
|
856
|
-
##
|
875
|
+
## Invalid Indexes
|
876
|
+
|
877
|
+
In Postgres, adding an index non-concurrently can leave behind an invalid index if the lock timeout is reached. Running the migration again can result in an error.
|
878
|
+
|
879
|
+
To automatically remove the invalid index when the migration runs again, use:
|
880
|
+
|
881
|
+
```ruby
|
882
|
+
StrongMigrations.remove_invalid_indexes = true
|
883
|
+
```
|
884
|
+
|
885
|
+
Note: This feature is experimental.
|
886
|
+
|
887
|
+
## Lock Timeout Retries
|
888
|
+
|
889
|
+
Note: This feature is experimental.
|
857
890
|
|
858
891
|
There’s the option to automatically retry statements for migrations when the lock timeout is reached. Here’s how it works:
|
859
892
|
|
@@ -13,11 +13,11 @@ module StrongMigrations
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def set_statement_timeout(timeout)
|
16
|
-
|
16
|
+
# do nothing
|
17
17
|
end
|
18
18
|
|
19
19
|
def set_lock_timeout(timeout)
|
20
|
-
|
20
|
+
# do nothing
|
21
21
|
end
|
22
22
|
|
23
23
|
def check_lock_timeout(limit)
|
@@ -127,19 +127,19 @@ module StrongMigrations
|
|
127
127
|
safe
|
128
128
|
end
|
129
129
|
|
130
|
-
|
130
|
+
# TODO remove when Active Record < 7.1 is no longer supported
|
131
|
+
def index_invalid?(table_name, index_name)
|
131
132
|
query = <<~SQL
|
132
133
|
SELECT
|
133
|
-
|
134
|
-
pg_get_constraintdef(oid) AS def
|
134
|
+
indisvalid
|
135
135
|
FROM
|
136
|
-
|
136
|
+
pg_index
|
137
137
|
WHERE
|
138
|
-
|
139
|
-
|
140
|
-
|
138
|
+
indrelid = to_regclass(#{connection.quote(connection.quote_table_name(table_name))}) AND
|
139
|
+
indexrelid = to_regclass(#{connection.quote(connection.quote_table_name(index_name))}) AND
|
140
|
+
indisvalid = false
|
141
141
|
SQL
|
142
|
-
select_all(query.squish).
|
142
|
+
select_all(query.squish).any?
|
143
143
|
end
|
144
144
|
|
145
145
|
def writes_blocked?
|
@@ -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
|
@@ -96,9 +105,9 @@ module StrongMigrations
|
|
96
105
|
# TODO figure out how to handle methods that generate multiple statements
|
97
106
|
# like add_reference(table, ref, index: {algorithm: :concurrently})
|
98
107
|
# lock timeout after first statement will cause retry to fail
|
99
|
-
retry_lock_timeouts {
|
108
|
+
retry_lock_timeouts { perform_method(method, *args, &block) }
|
100
109
|
else
|
101
|
-
|
110
|
+
perform_method(method, *args, &block)
|
102
111
|
end
|
103
112
|
|
104
113
|
# outdated statistics + a new index can hurt performance of existing queries
|
@@ -109,6 +118,13 @@ module StrongMigrations
|
|
109
118
|
result
|
110
119
|
end
|
111
120
|
|
121
|
+
def perform_method(method, *args)
|
122
|
+
if StrongMigrations.remove_invalid_indexes && direction == :up && method == :add_index && postgresql?
|
123
|
+
remove_invalid_index_if_needed(*args)
|
124
|
+
end
|
125
|
+
yield
|
126
|
+
end
|
127
|
+
|
112
128
|
def retry_lock_timeouts(check_committed: false)
|
113
129
|
retries = 0
|
114
130
|
begin
|
@@ -129,8 +145,22 @@ module StrongMigrations
|
|
129
145
|
version && version <= StrongMigrations.start_after
|
130
146
|
end
|
131
147
|
|
148
|
+
def skip?
|
149
|
+
StrongMigrations.skipped_databases.map(&:to_s).include?(db_config_name)
|
150
|
+
end
|
151
|
+
|
132
152
|
private
|
133
153
|
|
154
|
+
def check_adapter
|
155
|
+
return if defined?(@adapter_checked)
|
156
|
+
|
157
|
+
if adapter.instance_of?(Adapters::AbstractAdapter)
|
158
|
+
warn "[strong_migrations] Unsupported adapter: #{connection.adapter_name}. Use StrongMigrations.skip_database(#{db_config_name.to_sym.inspect}) to silence this warning."
|
159
|
+
end
|
160
|
+
|
161
|
+
@adapter_checked = true
|
162
|
+
end
|
163
|
+
|
134
164
|
def check_version_supported
|
135
165
|
return if defined?(@version_checked)
|
136
166
|
|
@@ -200,12 +230,48 @@ module StrongMigrations
|
|
200
230
|
@migration.connection
|
201
231
|
end
|
202
232
|
|
233
|
+
def db_config_name
|
234
|
+
connection.pool.db_config.name
|
235
|
+
end
|
236
|
+
|
203
237
|
def retry_lock_timeouts?(method)
|
204
238
|
(
|
205
239
|
StrongMigrations.lock_timeout_retries > 0 &&
|
206
240
|
!in_transaction? &&
|
207
|
-
method != :transaction
|
241
|
+
method != :transaction &&
|
242
|
+
!@skip_retries
|
208
243
|
)
|
209
244
|
end
|
245
|
+
|
246
|
+
def without_retries
|
247
|
+
previous_value = @skip_retries
|
248
|
+
begin
|
249
|
+
@skip_retries = true
|
250
|
+
yield
|
251
|
+
ensure
|
252
|
+
@skip_retries = previous_value
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# REINDEX INDEX CONCURRENTLY leaves a new invalid index if it fails, so use remove_index instead
|
257
|
+
def remove_invalid_index_if_needed(*args)
|
258
|
+
options = args.extract_options!
|
259
|
+
|
260
|
+
# ensures has same options as existing index
|
261
|
+
# check args to avoid errors with index_exists?
|
262
|
+
return unless args.size == 2 && connection.index_exists?(*args, **options.merge(valid: false))
|
263
|
+
|
264
|
+
table, columns = args
|
265
|
+
index_name = options.fetch(:name, connection.index_name(table, columns))
|
266
|
+
|
267
|
+
# valid option is ignored for Active Record < 7.1, so check name as well
|
268
|
+
return if ar_version < 7.1 && !adapter.index_invalid?(table, index_name)
|
269
|
+
|
270
|
+
@migration.say("Attempting to remove invalid index")
|
271
|
+
without_retries do
|
272
|
+
# TODO pass index schema for extra safety?
|
273
|
+
@migration.remove_index(table, **{name: index_name}.merge(options.slice(:algorithm)))
|
274
|
+
end
|
275
|
+
end
|
210
276
|
end
|
211
277
|
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] ||
|
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
|
@@ -228,32 +228,30 @@ module StrongMigrations
|
|
228
228
|
table, column, null, default = args
|
229
229
|
if !null
|
230
230
|
if postgresql?
|
231
|
-
|
231
|
+
constraints = connection.check_constraints(table)
|
232
|
+
safe = constraints.any? { |c| c.options[:validate] && (c.expression == "#{column} IS NOT NULL" || c.expression == "#{connection.quote_column_name(column)} IS NOT NULL") }
|
232
233
|
|
233
234
|
unless safe
|
234
235
|
# match https://github.com/nullobject/rein
|
235
236
|
constraint_name = "#{table}_#{column}_null"
|
236
237
|
|
237
|
-
|
238
|
-
|
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}]))
|
242
|
-
|
238
|
+
add_args = [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}]
|
239
|
+
validate_args = [table, {name: constraint_name}]
|
243
240
|
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}])}"
|
241
|
+
remove_args = [table, {name: constraint_name}]
|
247
242
|
|
248
243
|
if StrongMigrations.safe_by_default
|
249
|
-
safe_change_column_null(
|
244
|
+
safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
250
245
|
throw :safe
|
251
246
|
end
|
252
247
|
|
253
|
-
add_constraint_code = command_str(:add_check_constraint,
|
248
|
+
add_constraint_code = command_str(:add_check_constraint, add_args)
|
254
249
|
|
250
|
+
up_code = String.new(command_str(:validate_check_constraint, validate_args))
|
251
|
+
up_code << "\n #{command_str(:change_column_null, change_args)}"
|
252
|
+
up_code << "\n #{command_str(:remove_check_constraint, remove_args)}"
|
255
253
|
down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
|
256
|
-
validate_constraint_code = "def up\n #{
|
254
|
+
validate_constraint_code = "def up\n #{up_code}\n end\n\n def down\n #{down_code}\n end"
|
257
255
|
|
258
256
|
raise_error :change_column_null_postgresql,
|
259
257
|
add_constraint_code: add_constraint_code,
|
@@ -302,29 +300,28 @@ module StrongMigrations
|
|
302
300
|
columns =
|
303
301
|
case method
|
304
302
|
when :remove_timestamps
|
305
|
-
[
|
303
|
+
[:created_at, :updated_at]
|
306
304
|
when :remove_column
|
307
|
-
[args[1]
|
305
|
+
[args[1]]
|
308
306
|
when :remove_columns
|
309
|
-
# Active Record 6.1+ supports options
|
310
307
|
if args.last.is_a?(Hash)
|
311
|
-
args[1..-2]
|
308
|
+
args[1..-2]
|
312
309
|
else
|
313
|
-
args[1..-1]
|
310
|
+
args[1..-1]
|
314
311
|
end
|
315
312
|
else
|
316
313
|
options = args[2] || {}
|
317
314
|
reference = args[1]
|
318
315
|
cols = []
|
319
|
-
cols << "#{reference}_type" if options[:polymorphic]
|
320
|
-
cols << "#{reference}_id"
|
316
|
+
cols << "#{reference}_type".to_sym if options[:polymorphic]
|
317
|
+
cols << "#{reference}_id".to_sym
|
321
318
|
cols
|
322
319
|
end
|
323
320
|
|
324
|
-
code = "self.ignored_columns += #{columns.inspect}"
|
321
|
+
code = "self.ignored_columns += #{columns.map(&:to_s).inspect}"
|
325
322
|
|
326
323
|
raise_error :remove_column,
|
327
|
-
model: args[0]
|
324
|
+
model: model_name(args[0]),
|
328
325
|
code: code,
|
329
326
|
command: command_str(method, args),
|
330
327
|
column_suffix: columns.size > 1 ? "s" : ""
|
@@ -392,7 +389,7 @@ module StrongMigrations
|
|
392
389
|
message = message + append if append
|
393
390
|
|
394
391
|
vars[:migration_name] = @migration.class.name
|
395
|
-
vars[:migration_suffix] =
|
392
|
+
vars[:migration_suffix] = migration_suffix
|
396
393
|
vars[:base_model] = "ApplicationRecord"
|
397
394
|
|
398
395
|
# escape % not followed by {
|
@@ -433,7 +430,7 @@ module StrongMigrations
|
|
433
430
|
end
|
434
431
|
|
435
432
|
def backfill_code(table, column, default, function = false)
|
436
|
-
model = table
|
433
|
+
model = model_name(table)
|
437
434
|
if function
|
438
435
|
# update_all(column: Arel.sql(default)) also works in newer versions of Active Record
|
439
436
|
update_expr = "#{quote_column_if_needed(column)} = #{default}"
|
@@ -456,5 +453,13 @@ module StrongMigrations
|
|
456
453
|
def new_column?(table, column)
|
457
454
|
new_table?(table) || @new_columns.include?([table.to_s, column.to_s])
|
458
455
|
end
|
456
|
+
|
457
|
+
def migration_suffix
|
458
|
+
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
459
|
+
end
|
460
|
+
|
461
|
+
def model_name(table)
|
462
|
+
table.to_s.classify
|
463
|
+
end
|
459
464
|
end
|
460
465
|
end
|
@@ -147,7 +147,7 @@ you're doing is safe before proceeding, then wrap it in a safety_assured { ... }
|
|
147
147
|
create_table:
|
148
148
|
"The force option will destroy existing tables.
|
149
149
|
If this is intended, drop the existing table first.
|
150
|
-
|
150
|
+
In any case, remove the force option.",
|
151
151
|
|
152
152
|
execute:
|
153
153
|
"Strong Migrations does not support inspecting what happens inside an
|
@@ -1,8 +1,26 @@
|
|
1
1
|
module StrongMigrations
|
2
|
-
module
|
3
|
-
def
|
2
|
+
module MigrationContext
|
3
|
+
def up(...)
|
4
4
|
super
|
5
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)
|
6
24
|
if e.cause.is_a?(StrongMigrations::Error)
|
7
25
|
# strip cause and clean backtrace
|
8
26
|
def e.cause
|
@@ -6,11 +6,13 @@ module StrongMigrations
|
|
6
6
|
# handle MigrationProxy class
|
7
7
|
migration = migration.send(:migration) if 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.
|
15
|
+
checker.reset
|
14
16
|
|
15
17
|
super(migration, ...)
|
16
18
|
end
|
@@ -4,7 +4,6 @@ module StrongMigrations
|
|
4
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)
|
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,16 @@ 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
|
-
|
49
|
+
# validate option is unintentionally ignored for Active Record < 7.1
|
50
|
+
# https://github.com/rails/rails/pull/45896
|
51
|
+
if !connection.foreign_key_exists?(from_table, to_table, **options.merge(validate: false))
|
52
|
+
@migration.add_foreign_key(from_table, to_table, *args, **options.merge(validate: false))
|
53
|
+
end
|
51
54
|
disable_transaction
|
52
|
-
|
53
|
-
@migration.validate_foreign_key(from_table, to_table, **validate_options)
|
55
|
+
@migration.validate_foreign_key(from_table, to_table, **options.slice(:column, :name))
|
54
56
|
end
|
55
57
|
dir.down do
|
56
|
-
|
57
|
-
@migration.remove_foreign_key(from_table, to_table, **remove_options)
|
58
|
+
@migration.remove_foreign_key(from_table, to_table, **options.slice(:column, :name))
|
58
59
|
end
|
59
60
|
end
|
60
61
|
end
|
@@ -62,7 +63,10 @@ module StrongMigrations
|
|
62
63
|
def safe_add_check_constraint(table, expression, *args, add_options, validate_options)
|
63
64
|
@migration.reversible do |dir|
|
64
65
|
dir.up do
|
65
|
-
|
66
|
+
# only skip invalid constraints
|
67
|
+
unless connection.check_constraints(table).any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
|
68
|
+
@migration.add_check_constraint(table, expression, *args, **add_options)
|
69
|
+
end
|
66
70
|
disable_transaction
|
67
71
|
@migration.validate_check_constraint(table, **validate_options)
|
68
72
|
end
|
@@ -72,35 +76,33 @@ module StrongMigrations
|
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
75
|
-
def safe_change_column_null(
|
79
|
+
def safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
76
80
|
@migration.reversible do |dir|
|
77
81
|
dir.up do
|
78
82
|
unless default.nil?
|
79
83
|
raise Error, "default value not supported yet with safe_by_default"
|
80
84
|
end
|
81
85
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
@migration.
|
89
|
-
@migration.safety_assured do
|
90
|
-
@migration.execute(remove_code)
|
91
|
-
end
|
86
|
+
add_options = add_args.extract_options!
|
87
|
+
validate_options = validate_args.extract_options!
|
88
|
+
remove_options = remove_args.extract_options!
|
89
|
+
|
90
|
+
# only skip invalid constraints
|
91
|
+
unless constraints.any? { |c| c.options[:name] == validate_options[:name] && !c.options[:validate] }
|
92
|
+
@migration.add_check_constraint(*add_args, **add_options)
|
92
93
|
end
|
94
|
+
disable_transaction
|
95
|
+
|
96
|
+
connection.begin_db_transaction
|
97
|
+
@migration.validate_check_constraint(*validate_args, **validate_options)
|
98
|
+
@migration.change_column_null(*change_args)
|
99
|
+
@migration.remove_check_constraint(*remove_args, **remove_options)
|
100
|
+
connection.commit_db_transaction
|
93
101
|
end
|
94
102
|
dir.down do
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@migration.change_column_null(*down_args)
|
99
|
-
else
|
100
|
-
@migration.safety_assured do
|
101
|
-
@migration.execute(remove_code)
|
102
|
-
end
|
103
|
-
end
|
103
|
+
down_args = change_args.dup
|
104
|
+
down_args[2] = true
|
105
|
+
@migration.change_column_null(*down_args)
|
104
106
|
end
|
105
107
|
end
|
106
108
|
end
|
@@ -109,13 +111,13 @@ module StrongMigrations
|
|
109
111
|
# so just commit at start
|
110
112
|
def disable_transaction
|
111
113
|
if in_transaction? && !transaction_disabled
|
112
|
-
|
114
|
+
connection.commit_db_transaction
|
113
115
|
self.transaction_disabled = true
|
114
116
|
end
|
115
117
|
end
|
116
118
|
|
117
119
|
def in_transaction?
|
118
|
-
|
120
|
+
connection.open_transactions > 0
|
119
121
|
end
|
120
122
|
end
|
121
123
|
end
|
data/lib/strong_migrations.rb
CHANGED
@@ -11,8 +11,8 @@ require_relative "strong_migrations/adapters/postgresql_adapter"
|
|
11
11
|
require_relative "strong_migrations/checks"
|
12
12
|
require_relative "strong_migrations/safe_methods"
|
13
13
|
require_relative "strong_migrations/checker"
|
14
|
-
require_relative "strong_migrations/database_tasks"
|
15
14
|
require_relative "strong_migrations/migration"
|
15
|
+
require_relative "strong_migrations/migration_context"
|
16
16
|
require_relative "strong_migrations/migrator"
|
17
17
|
require_relative "strong_migrations/version"
|
18
18
|
|
@@ -29,7 +29,7 @@ module StrongMigrations
|
|
29
29
|
:target_postgresql_version, :target_mysql_version, :target_mariadb_version,
|
30
30
|
:enabled_checks, :lock_timeout, :statement_timeout, :check_down, :target_version,
|
31
31
|
:safe_by_default, :target_sql_mode, :lock_timeout_retries, :lock_timeout_retry_delay,
|
32
|
-
:alphabetize_schema
|
32
|
+
:alphabetize_schema, :skipped_databases, :remove_invalid_indexes
|
33
33
|
attr_writer :lock_timeout_limit
|
34
34
|
end
|
35
35
|
self.auto_analyze = false
|
@@ -40,6 +40,8 @@ module StrongMigrations
|
|
40
40
|
self.safe_by_default = false
|
41
41
|
self.check_down = false
|
42
42
|
self.alphabetize_schema = false
|
43
|
+
self.skipped_databases = []
|
44
|
+
self.remove_invalid_indexes = false
|
43
45
|
|
44
46
|
# private
|
45
47
|
def self.developer_env?
|
@@ -83,6 +85,10 @@ module StrongMigrations
|
|
83
85
|
false
|
84
86
|
end
|
85
87
|
end
|
88
|
+
|
89
|
+
def self.skip_database(database)
|
90
|
+
self.skipped_databases << database
|
91
|
+
end
|
86
92
|
end
|
87
93
|
|
88
94
|
# load error messages
|
@@ -90,12 +96,9 @@ require_relative "strong_migrations/error_messages"
|
|
90
96
|
|
91
97
|
ActiveSupport.on_load(:active_record) do
|
92
98
|
ActiveRecord::Migration.prepend(StrongMigrations::Migration)
|
99
|
+
ActiveRecord::MigrationContext.prepend(StrongMigrations::MigrationContext)
|
93
100
|
ActiveRecord::Migrator.prepend(StrongMigrations::Migrator)
|
94
101
|
|
95
|
-
if defined?(ActiveRecord::Tasks::DatabaseTasks)
|
96
|
-
ActiveRecord::Tasks::DatabaseTasks.singleton_class.prepend(StrongMigrations::DatabaseTasks)
|
97
|
-
end
|
98
|
-
|
99
102
|
require_relative "strong_migrations/schema_dumper"
|
100
103
|
ActiveRecord::SchemaDumper.prepend(StrongMigrations::SchemaDumper)
|
101
104
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: strong_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2024-
|
13
|
+
date: 2024-11-09 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -47,9 +47,9 @@ files:
|
|
47
47
|
- lib/strong_migrations/adapters/postgresql_adapter.rb
|
48
48
|
- lib/strong_migrations/checker.rb
|
49
49
|
- lib/strong_migrations/checks.rb
|
50
|
-
- lib/strong_migrations/database_tasks.rb
|
51
50
|
- lib/strong_migrations/error_messages.rb
|
52
51
|
- lib/strong_migrations/migration.rb
|
52
|
+
- lib/strong_migrations/migration_context.rb
|
53
53
|
- lib/strong_migrations/migrator.rb
|
54
54
|
- lib/strong_migrations/railtie.rb
|
55
55
|
- lib/strong_migrations/safe_methods.rb
|
@@ -75,7 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
75
|
- !ruby/object:Gem::Version
|
76
76
|
version: '0'
|
77
77
|
requirements: []
|
78
|
-
rubygems_version: 3.5.
|
78
|
+
rubygems_version: 3.5.22
|
79
79
|
signing_key:
|
80
80
|
specification_version: 4
|
81
81
|
summary: Catch unsafe migrations in development
|