strong_migrations 1.2.0 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c0ea5c8cf3e904b89b8c4ab2f28ff60031515e40edf5b0c617ca4b8f8249650
4
- data.tar.gz: 5cb0fd63058bf618595cd929ef90de3cf90f02963f0adca21182c578f41c3320
3
+ metadata.gz: 227ddfd8f939d855d93232f5d30d02a0c219f46c6393d98e2be5d8756c445e33
4
+ data.tar.gz: bde562f14e23d1fc7a1209e075efffad03454594b842bcde3c7b1ec6fa3046b4
5
5
  SHA512:
6
- metadata.gz: 50db9d240d49b60b75d97c76765b8f5e3b252c2cc826bd5e50e9b57311082dfea2c9cc5790fd905a2eac45a3eb6afb2784c7ed7962dcaf0172157548e1c8d233
7
- data.tar.gz: 2f2e5311cad061b712ee9dfb69a41b5e81f31be8db257bd095cbd8124bc461551afebb305d95b0bb090d6f7ef85c61618d61e64e9b73f266338a0aa7f5101b53
6
+ metadata.gz: 972103d956c2a8cdff88e478401f18459eabe93d7260107b5c19908cc76bef9451b19e99852511d8a8675639b076107d79d26a6046308d4627afc88f580afc76
7
+ data.tar.gz: 3bf1e422cf0d5cd4297bf63a6121570e5afa9acddccf42c4fa7a21eac40fa0cc6d5fca04e2227a45132d7efde0b7f702bb90f3be916097079f9b569cdd04ba50
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## 1.3.2 (2022-10-09)
2
+
3
+ - Improved error message for `add_column` with `default: nil` with Postgres 10
4
+
5
+ ## 1.3.1 (2022-09-21)
6
+
7
+ - Fixed check for `add_column` with `default: nil` with Postgres 10
8
+
9
+ ## 1.3.0 (2022-08-30)
10
+
11
+ - Added check for `add_column` with `uuid` type and volatile default value
12
+
1
13
  ## 1.2.0 (2022-06-10)
2
14
 
3
15
  - Added check for index corruption with Postgres 14.0 to 14.3
data/README.md CHANGED
@@ -135,7 +135,7 @@ class AddSomeColumnToUsers < ActiveRecord::Migration[7.0]
135
135
  end
136
136
  ```
137
137
 
138
- In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe.
138
+ In Postgres 11+, MySQL 8.0.12+, and MariaDB 10.3.2+, this no longer requires a table rewrite and is safe (except for volatile functions like `gen_random_uuid()`).
139
139
 
140
140
  #### Good
141
141
 
@@ -681,7 +681,7 @@ Disable specific checks with:
681
681
  StrongMigrations.disable_check(:add_index)
682
682
  ```
683
683
 
684
- Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations.rb) for the list of keys.
684
+ Check the [source code](https://github.com/ankane/strong_migrations/blob/master/lib/strong_migrations/error_messages.rb) for the list of keys.
685
685
 
686
686
  ## Down Migrations / Rollbacks
687
687
 
@@ -14,11 +14,7 @@ module StrongMigrations
14
14
  target_version(StrongMigrations.target_postgresql_version) do
15
15
  version = select_all("SHOW server_version_num").first["server_version_num"].to_i
16
16
  # major and minor version
17
- if version >= 100000
18
- "#{version / 10000}.#{(version % 10000)}"
19
- else
20
- "#{version / 10000}.#{(version % 10000) / 100}"
21
- end
17
+ "#{version / 10000}.#{(version % 10000)}"
22
18
  end
23
19
  end
24
20
  end
@@ -76,7 +72,7 @@ module StrongMigrations
76
72
  # but there doesn't seem to be a way to set/modify it
77
73
  # https://wiki.postgresql.org/wiki/What%27s_new_in_PostgreSQL_9.2#Reduce_ALTER_TABLE_rewrites
78
74
  when "numeric", "decimal"
79
- # numeric and decimal are equivalent and can be used interchangably
75
+ # numeric and decimal are equivalent and can be used interchangeably
80
76
  safe = ["numeric", "decimal"].include?(existing_type) &&
81
77
  (
82
78
  (
@@ -166,6 +162,13 @@ module StrongMigrations
166
162
  !StrongMigrations.developer_env?
167
163
  end
168
164
 
165
+ # default to true if unsure
166
+ def default_volatile?(default)
167
+ name = default.to_s.delete_suffix("()")
168
+ rows = select_all("SELECT provolatile FROM pg_proc WHERE proname = #{connection.quote(name)}").to_a
169
+ rows.empty? || rows.any? { |r| r["provolatile"] == "v" }
170
+ end
171
+
169
172
  private
170
173
 
171
174
  def set_timeout(setting, timeout)
@@ -32,7 +32,11 @@ module StrongMigrations
32
32
  table, column, type = args
33
33
  default = options[:default]
34
34
 
35
- if !default.nil? && !adapter.add_column_default_safe?
35
+ # Check key since DEFAULT NULL behaves differently from no default
36
+ #
37
+ # Also, Active Record has special case for uuid columns that allows function default values
38
+ # https://github.com/rails/rails/blob/v7.0.3.1/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L92-L93
39
+ if options.key?(:default) && (!adapter.add_column_default_safe? || (volatile = (postgresql? && type.to_s == "uuid" && default.to_s.include?("()") && adapter.default_volatile?(default))))
36
40
  if options[:null] == false
37
41
  options = options.except(:null)
38
42
  append = "
@@ -40,13 +44,21 @@ module StrongMigrations
40
44
  Then add the NOT NULL constraint in separate migrations."
41
45
  end
42
46
 
43
- raise_error :add_column_default,
44
- add_command: command_str("add_column", [table, column, type, options.except(:default)]),
45
- change_command: command_str("change_column_default", [table, column, default]),
46
- remove_command: command_str("remove_column", [table, column]),
47
- code: backfill_code(table, column, default),
48
- append: append,
49
- rewrite_blocks: adapter.rewrite_blocks
47
+ if default.nil?
48
+ raise_error :add_column_default_null,
49
+ command: command_str("add_column", [table, column, type, options.except(:default)]),
50
+ append: append,
51
+ rewrite_blocks: adapter.rewrite_blocks
52
+ else
53
+ raise_error :add_column_default,
54
+ add_command: command_str("add_column", [table, column, type, options.except(:default)]),
55
+ change_command: command_str("change_column_default", [table, column, default]),
56
+ remove_command: command_str("remove_column", [table, column]),
57
+ code: backfill_code(table, column, default, volatile),
58
+ append: append,
59
+ rewrite_blocks: adapter.rewrite_blocks,
60
+ default_type: (volatile ? "volatile" : "non-null")
61
+ end
50
62
  elsif default.is_a?(Proc) && postgresql?
51
63
  # adding a column with a VOLATILE default is not safe
52
64
  # https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
@@ -219,16 +231,22 @@ Then add the foreign key in separate migrations."
219
231
 
220
232
  add_constraint_code =
221
233
  if constraint_methods
222
- # only quote when needed
223
- expr_column = column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
224
- command_str(:add_check_constraint, [table, "#{expr_column} IS NOT NULL", {name: constraint_name, validate: false}])
234
+ command_str(:add_check_constraint, [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}])
225
235
  else
226
236
  safety_assured_str(add_code)
227
237
  end
228
238
 
239
+ validate_constraint_code =
240
+ if safe_with_check_constraint
241
+ down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
242
+ "def up\n #{validate_constraint_code}\n end\n\n def down\n #{down_code}\n end"
243
+ else
244
+ "def change\n #{validate_constraint_code}\n end"
245
+ end
246
+
229
247
  raise_error :change_column_null_postgresql,
230
248
  add_constraint_code: add_constraint_code,
231
- validate_constraint_code: "def change\n #{validate_constraint_code}\n end"
249
+ validate_constraint_code: validate_constraint_code
232
250
  end
233
251
  elsif mysql? || mariadb?
234
252
  unless adapter.strict_mode?
@@ -409,9 +427,21 @@ Then add the foreign key in separate migrations."
409
427
  "#{command} #{str_args.join(", ")}"
410
428
  end
411
429
 
412
- def backfill_code(table, column, default)
430
+ def backfill_code(table, column, default, function = false)
413
431
  model = table.to_s.classify
414
- "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
432
+ if function
433
+ # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
434
+ update_expr = "#{quote_column_if_needed(column)} = #{default}"
435
+ "#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
436
+ else
437
+ "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
438
+ end
439
+ end
440
+
441
+ # only quote when needed
442
+ # important! only use for display purposes
443
+ def quote_column_if_needed(column)
444
+ column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
415
445
  end
416
446
 
417
447
  def new_table?(table)
@@ -1,7 +1,7 @@
1
1
  module StrongMigrations
2
2
  self.error_messages = {
3
3
  add_column_default:
4
- "Adding a column with a non-null default blocks %{rewrite_blocks} while the entire table is rewritten.
4
+ "Adding a column with a %{default_type} default blocks %{rewrite_blocks} while the entire table is rewritten.
5
5
  Instead, add the column without a default value, then change the default.
6
6
 
7
7
  class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
@@ -25,6 +25,16 @@ class Backfill%{migration_name} < ActiveRecord::Migration%{migration_suffix}
25
25
  end
26
26
  end",
27
27
 
28
+ add_column_default_null:
29
+ "Adding a column with a null default blocks %{rewrite_blocks} while the entire table is rewritten.
30
+ Instead, add the column without a default value.
31
+
32
+ class %{migration_name} < ActiveRecord::Migration%{migration_suffix}
33
+ def change
34
+ %{command}
35
+ end
36
+ end",
37
+
28
38
  add_column_default_callable:
29
39
  "Strong Migrations does not support inspecting callable default values.
30
40
  Please make really sure you're not calling a VOLATILE function,
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.2"
3
3
  end
@@ -1,5 +1,5 @@
1
1
  namespace :strong_migrations do
2
- # https://www.pgrs.net/2008/03/13/alphabetize-schema-rb-columns/
2
+ # https://www.pgrs.net/2008/03/12/alphabetize-schema-rb-columns/
3
3
  task :alphabetize_columns do
4
4
  $stderr.puts "Dumping schema"
5
5
  ActiveRecord::Base.logger.level = Logger::INFO
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: 1.2.0
4
+ version: 1.3.2
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: 2022-06-10 00:00:00.000000000 Z
13
+ date: 2022-10-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord