strong_migrations 1.2.0 → 1.3.1

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: b330db9323d9942a9e24a5fdc3ab75931030f859505cb9eef2f428df840b4dcc
4
+ data.tar.gz: 8aa1acd2debbc49206cf9b3928c16e85d4f2f47140142d4e9b981d1d54254e12
5
5
  SHA512:
6
- metadata.gz: 50db9d240d49b60b75d97c76765b8f5e3b252c2cc826bd5e50e9b57311082dfea2c9cc5790fd905a2eac45a3eb6afb2784c7ed7962dcaf0172157548e1c8d233
7
- data.tar.gz: 2f2e5311cad061b712ee9dfb69a41b5e81f31be8db257bd095cbd8124bc461551afebb305d95b0bb090d6f7ef85c61618d61e64e9b73f266338a0aa7f5101b53
6
+ metadata.gz: 2bce01385b02f0c005cae82cda4325f5332b400949cc0c5814a25c4bc30f78ff0a66730179dc52458bae242aa5cc4d1eb6be755696c731521580b3554f3eee6c
7
+ data.tar.gz: 72bcd4096a69bd3c8afd78caa43651b10bf5924318635adeb52308724751fa9fdbaeaa59e07a690a4a0915524afbab3487a8f1db17086c18e0b742c0d3de6b28
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ ## 1.3.1 (2022-09-21)
2
+
3
+ - Fixed check for `add_column` with `default: nil` with Postgres 10
4
+
5
+ ## 1.3.0 (2022-08-30)
6
+
7
+ - Added check for `add_column` with `uuid` type and volatile default value
8
+
1
9
  ## 1.2.0 (2022-06-10)
2
10
 
3
11
  - 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 = "
@@ -44,9 +48,10 @@ Then add the NOT NULL constraint in separate migrations."
44
48
  add_command: command_str("add_column", [table, column, type, options.except(:default)]),
45
49
  change_command: command_str("change_column_default", [table, column, default]),
46
50
  remove_command: command_str("remove_column", [table, column]),
47
- code: backfill_code(table, column, default),
51
+ code: backfill_code(table, column, default, volatile),
48
52
  append: append,
49
- rewrite_blocks: adapter.rewrite_blocks
53
+ rewrite_blocks: adapter.rewrite_blocks,
54
+ default_type: (volatile ? "volatile" : "non-null")
50
55
  elsif default.is_a?(Proc) && postgresql?
51
56
  # adding a column with a VOLATILE default is not safe
52
57
  # https://www.postgresql.org/docs/current/sql-altertable.html#SQL-ALTERTABLE-NOTES
@@ -219,16 +224,22 @@ Then add the foreign key in separate migrations."
219
224
 
220
225
  add_constraint_code =
221
226
  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}])
227
+ command_str(:add_check_constraint, [table, "#{quote_column_if_needed(column)} IS NOT NULL", {name: constraint_name, validate: false}])
225
228
  else
226
229
  safety_assured_str(add_code)
227
230
  end
228
231
 
232
+ validate_constraint_code =
233
+ if safe_with_check_constraint
234
+ down_code = "#{add_constraint_code}\n #{command_str(:change_column_null, [table, column, true])}"
235
+ "def up\n #{validate_constraint_code}\n end\n\n def down\n #{down_code}\n end"
236
+ else
237
+ "def change\n #{validate_constraint_code}\n end"
238
+ end
239
+
229
240
  raise_error :change_column_null_postgresql,
230
241
  add_constraint_code: add_constraint_code,
231
- validate_constraint_code: "def change\n #{validate_constraint_code}\n end"
242
+ validate_constraint_code: validate_constraint_code
232
243
  end
233
244
  elsif mysql? || mariadb?
234
245
  unless adapter.strict_mode?
@@ -409,9 +420,21 @@ Then add the foreign key in separate migrations."
409
420
  "#{command} #{str_args.join(", ")}"
410
421
  end
411
422
 
412
- def backfill_code(table, column, default)
423
+ def backfill_code(table, column, default, function = false)
413
424
  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"
425
+ if function
426
+ # update_all(column: Arel.sql(default)) also works in newer versions of Active Record
427
+ update_expr = "#{quote_column_if_needed(column)} = #{default}"
428
+ "#{model}.unscoped.in_batches do |relation| \n relation.where(#{column}: nil).update_all(#{update_expr.inspect})\n sleep(0.01)\n end"
429
+ else
430
+ "#{model}.unscoped.in_batches do |relation| \n relation.update_all #{column}: #{default.inspect}\n sleep(0.01)\n end"
431
+ end
432
+ end
433
+
434
+ # only quote when needed
435
+ # important! only use for display purposes
436
+ def quote_column_if_needed(column)
437
+ column.to_s =~ /\A[a-z0-9_]+\z/ ? column : connection.quote_column_name(column)
415
438
  end
416
439
 
417
440
  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}
@@ -1,3 +1,3 @@
1
1
  module StrongMigrations
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.1"
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.1
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-09-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord