strong_migrations 1.2.0 → 1.3.2

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 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