strong_migrations 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/README.md +2 -2
- data/lib/strong_migrations/adapters/postgresql_adapter.rb +5 -0
- data/lib/strong_migrations/checks.rb +29 -3
- data/lib/strong_migrations/error_messages.rb +16 -0
- data/lib/strong_migrations/safe_methods.rb +30 -3
- data/lib/strong_migrations/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3538811c535186c2757c2e92a56ec753c77842a99728a727aabe323dd8852182
|
4
|
+
data.tar.gz: 6d33a69582e57e3035d7ef540894ce0020957aef8d4e081e693552615ac9af8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50a4b8ea0d0677bc0d5a3fa740f9f2e8b00acee0e673abaf7197c0c89540165ecb0cad913ed4244b61e3ac23342d4c44b8209991dea3841ee6dd7d7fbef827e3
|
7
|
+
data.tar.gz: fd6d44273d6365ca9e33a246e072a24da93191bfc73b5d425df22c0b3b6523ce218d6aa6360dd6a8fcbcf88b5a06bf41c7221ccdceb69f26b734510cd38876f7
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
## 2.3.0 (2025-04-03)
|
2
|
+
|
3
|
+
- Added check for `change_column` for columns with check constraints with Postgres
|
4
|
+
|
5
|
+
## 2.2.1 (2025-03-21)
|
6
|
+
|
7
|
+
- Added support for `change_column_null` with default value with `safe_by_default` option
|
8
|
+
- Improved backfill instructions
|
9
|
+
- Fixed `safe_by_default` applying to migrations before `start_after`
|
10
|
+
|
1
11
|
## 2.2.0 (2025-02-01)
|
2
12
|
|
3
13
|
- Fixed constraint name for long table and column names with `change_column_null`
|
data/README.md
CHANGED
@@ -377,8 +377,8 @@ class BackfillSomeColumn < ActiveRecord::Migration[8.0]
|
|
377
377
|
disable_ddl_transaction!
|
378
378
|
|
379
379
|
def up
|
380
|
-
User.unscoped.in_batches do |relation|
|
381
|
-
relation.update_all some_column: "default_value"
|
380
|
+
User.unscoped.in_batches(of: 10000) do |relation|
|
381
|
+
relation.where(some_column: nil).update_all some_column: "default_value"
|
382
382
|
sleep(0.01) # throttle
|
383
383
|
end
|
384
384
|
end
|
@@ -177,6 +177,11 @@ module StrongMigrations
|
|
177
177
|
63
|
178
178
|
end
|
179
179
|
|
180
|
+
def constraints(table, column)
|
181
|
+
# TODO improve column check
|
182
|
+
connection.check_constraints(table).select { |c| /\b#{Regexp.escape(column.to_s)}\b/.match?(c.expression) }
|
183
|
+
end
|
184
|
+
|
180
185
|
private
|
181
186
|
|
182
187
|
def set_timeout(setting, timeout)
|
@@ -210,6 +210,32 @@ 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)
|
@@ -251,7 +277,7 @@ module StrongMigrations
|
|
251
277
|
remove_args = [table, {name: constraint_name}]
|
252
278
|
|
253
279
|
if StrongMigrations.safe_by_default
|
254
|
-
safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
280
|
+
safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
|
255
281
|
throw :safe
|
256
282
|
end
|
257
283
|
|
@@ -444,9 +470,9 @@ module StrongMigrations
|
|
444
470
|
if function
|
445
471
|
# update_all(column: Arel.sql(default)) also works in newer versions of Active Record
|
446
472
|
update_expr = "#{quote_column_if_needed(column)} = #{default}"
|
447
|
-
"#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
|
473
|
+
"#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
|
448
474
|
else
|
449
|
-
"#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
|
475
|
+
"#{model}.unscoped.in_batches(of: 10000) do |relation| \n relation.where(#{column}: nil).update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
|
450
476
|
end
|
451
477
|
end
|
452
478
|
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
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
7
|
def safe_add_index(*args, **options)
|
@@ -76,11 +76,38 @@ module StrongMigrations
|
|
76
76
|
end
|
77
77
|
end
|
78
78
|
|
79
|
-
def safe_change_column_null(add_args, validate_args, change_args, remove_args, default, constraints)
|
79
|
+
def safe_change_column_null(add_args, validate_args, change_args, remove_args, table, column, default, constraints)
|
80
80
|
@migration.reversible do |dir|
|
81
81
|
dir.up do
|
82
82
|
unless default.nil?
|
83
|
-
|
83
|
+
# TODO search for parent model if needed
|
84
|
+
if connection.pool != ActiveRecord::Base.connection_pool
|
85
|
+
raise_error :change_column_null,
|
86
|
+
code: backfill_code(table, column, default)
|
87
|
+
end
|
88
|
+
|
89
|
+
model =
|
90
|
+
Class.new(ActiveRecord::Base) do
|
91
|
+
self.table_name = table
|
92
|
+
|
93
|
+
def self.to_s
|
94
|
+
"Backfill"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
update_sql =
|
99
|
+
model.connection_pool.with_connection do |c|
|
100
|
+
quoted_column = c.quote_column_name(column)
|
101
|
+
quoted_default = c.quote_default_expression(default, c.send(:column_for, table, column))
|
102
|
+
"#{quoted_column} = #{quoted_default}"
|
103
|
+
end
|
104
|
+
|
105
|
+
@migration.say("Backfilling default")
|
106
|
+
disable_transaction
|
107
|
+
model.unscoped.in_batches(of: 10000) do |relation|
|
108
|
+
relation.where(column => nil).update_all(update_sql)
|
109
|
+
sleep(0.01)
|
110
|
+
end
|
84
111
|
end
|
85
112
|
|
86
113
|
add_options = add_args.extract_options!
|
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.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Kane
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
- David Waller
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2025-
|
12
|
+
date: 2025-04-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|