online_migrations 0.5.3 → 0.6.0

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: 0ac8a2eb2657fcc3536a9b56d16298f90f8e1a5e626ba52a6e9703b5bcaa7df0
4
- data.tar.gz: c5357f94854fb43d1b8404ed9dcfafc2178430aeb9c6d69555f50629a94da3bd
3
+ metadata.gz: 625229a6ef3cc2b9a6a6201ae2033b4560ca395482d797115563abd1dace6716
4
+ data.tar.gz: bc24c264f8a6be48e9232959a6e654e5a814c9872317d88e788eca8260dd6a7e
5
5
  SHA512:
6
- metadata.gz: 4ccbc274c76ab01f8ece559b3fee27c1c5c0c495225b393c3a4afb7941f0b61d3aab9ef2885195be78ed00f29bd5e6b18e3fe0cb044b745cd791aacf34283492
7
- data.tar.gz: 04fb250e30e7620ac1bd06fddf24a5fdfe616f285422e47e19423e23be0c8117e57d45e45029a2e1212cc5e6ad9baf3605fac8b038152fdc73b2d9a818ba211c
6
+ metadata.gz: 8054e3f5fec6fbc066cc3d6184a3002a2e77b21dee5ee7adca1025fa907c3e2263fae206b25150b662e37cd1dfac46cd66a54c82d725848020c30ed0cecef5c9
7
+ data.tar.gz: 2ad342552fbf328c341875192aba31c441b2bd5a72d3fac9edfef9caa9fa2eab003c9ae7d441d4778b3425f82aed140290de82df3291c030e0b1e7867871ae47
@@ -120,7 +120,7 @@ enqueue_background_migration("MyMigrationWithArgs", arg1, arg2, ...)
120
120
  * `CopyColumn` - copies data from one column(s) to other(s) (enqueue using `copy_column_in_background`)
121
121
  * `DeleteAssociatedRecords` - deletes records associated with a parent object (enqueue using `delete_associated_records_in_background`)
122
122
  * `DeleteOrphanedRecords` - deletes records with one or more missing relations (enqueue using `delete_orphaned_records_in_background`)
123
- * `PerformActionOnRelation` - performs specific action on a relation or indvidual records (enqueue using `perform_action_on_relation_in_background`)
123
+ * `PerformActionOnRelation` - performs specific action on a relation or individual records (enqueue using `perform_action_on_relation_in_background`)
124
124
  * `ResetCounters` - resets one or more counter caches to their correct value (enqueue using `reset_counters_in_background`)
125
125
 
126
126
  ## Testing
@@ -240,7 +240,7 @@ Specify the throttle condition as a block:
240
240
  ```ruby
241
241
  # config/initializers/online_migrations.rb
242
242
 
243
- OnlineMigrations.config.backround_migrations.throttler = -> { DatabaseStatus.unhealthy? }
243
+ OnlineMigrations.config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
244
244
  ```
245
245
 
246
246
  Note that it's up to you to define a throttling condition that makes sense for your app. For example, you can check various PostgreSQL metrics such as replication lag, DB threads, whether DB writes are available, etc.
@@ -254,7 +254,7 @@ If you want to integrate with an exception monitoring service (e.g. Bugsnag), yo
254
254
  ```ruby
255
255
  # config/initializers/online_migrations.rb
256
256
 
257
- OnlineMigrations.config.backround_migrations.error_handler = ->(error, errored_job) do
257
+ OnlineMigrations.config.background_migrations.error_handler = ->(error, errored_job) do
258
258
  Bugsnag.notify(error) do |notification|
259
259
  notification.add_metadata(:background_migration, { name: errored_job.migration_name })
260
260
  end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.6.0 (2023-02-04)
4
+
5
+ - Ignore internal Active Record migrations compatibility related options when suggesting a safe column type change
6
+ - Added check for `add_exclusion_constraint`
7
+ - Fix preserving old column options (`:comment` and `:collation`) when changing column type
8
+ - Set `NOT NULL` during new column creation when changing column type for PostgreSQL >= 11
9
+
10
+ ## 0.5.4 (2023-01-03)
11
+
12
+ - Support ruby 3.2.0
13
+
3
14
  ## 0.5.3 (2022-11-10)
4
15
 
5
16
  - Fix removing index by name
data/README.md CHANGED
@@ -20,6 +20,8 @@ See [comparison to `strong_migrations`](#comparison-to-strong_migrations)
20
20
  - Rails 4.2+
21
21
  - PostgreSQL 9.6+
22
22
 
23
+ **Note**: Since some migration helpers use database `VIEW`s to implement their logic, it is recommended to use `structure.sql` schema format, or otherwise add some gem (like [scenic](https://github.com/scenic-views/scenic)) to be able to dump them into the `schema.rb`.
24
+
23
25
  ## Installation
24
26
 
25
27
  Add this line to your application's Gemfile:
@@ -69,7 +71,7 @@ class AddAdminToUsers < ActiveRecord::Migration[7.0]
69
71
  # Do not wrap the migration in a transaction so that locks are held for a shorter time.
70
72
  disable_ddl_transaction!
71
73
 
72
- def change
74
+ def up
73
75
  # Lower PostgreSQL's lock timeout to avoid statement queueing.
74
76
  execute "SET lock_timeout TO '5s'" # The lock_timeout duration is customizable.
75
77
 
@@ -88,6 +90,10 @@ class AddAdminToUsers < ActiveRecord::Migration[7.0]
88
90
  execute "SET statement_timeout TO '5s'"
89
91
  change_column_null :users, :admin, false
90
92
  end
93
+
94
+ def down
95
+ remove_column :users, :admin
96
+ end
91
97
  end
92
98
  ```
93
99
 
@@ -136,6 +142,7 @@ Potentially dangerous operations:
136
142
  - [replacing an index](#replacing-an-index)
137
143
  - [adding a reference](#adding-a-reference)
138
144
  - [adding a foreign key](#adding-a-foreign-key)
145
+ - [adding an exclusion constraint](#adding-an-exclusion-constraint)
139
146
  - [adding a json column](#adding-a-json-column)
140
147
  - [using primary key with short integer type](#using-primary-key-with-short-integer-type)
141
148
  - [hash indexes](#hash-indexes)
@@ -317,6 +324,9 @@ A safer approach can be accomplished in several steps:
317
324
  end
318
325
  ```
319
326
 
327
+ **Note**: `initialize_column_type_change` accepts additional options (like `:limit`, `:default` etc)
328
+ which will be passed to `add_column` when creating a new column, so you can override previous values.
329
+
320
330
  2. Backfill data from the old column to the new column:
321
331
 
322
332
  ```ruby
@@ -391,7 +401,7 @@ For the previous example, to rename `name` column to `first_name` of the `users`
391
401
  ```sql
392
402
  BEGIN;
393
403
  ALTER TABLE users RENAME TO users_column_rename;
394
- CREATE VIEW users AS SELECT *, first_name AS name FROM users;
404
+ CREATE VIEW users AS SELECT *, first_name AS name FROM users_column_rename;
395
405
  COMMIT;
396
406
  ```
397
407
 
@@ -410,9 +420,21 @@ OnlineMigrations.config.column_renames = {
410
420
  }
411
421
  }
412
422
  ```
423
+ NOTE: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
424
+ until the process of column rename is fully done.
425
+ ```ruby
426
+ # config/application.rb
427
+ # For Active Record >= 7
428
+ config.active_record.partial_inserts = true
429
+
430
+ # Or for Active Record < 7
431
+ config.active_record.partial_writes = true
432
+ ```
413
433
 
414
434
  2. Deploy
415
- 3. Create a VIEW with aliased column:
435
+ 3. Tell the database that you are going to rename a column. This will not actually rename any columns,
436
+ nor any data/indexes/foreign keys copying will be made, so will be instantaneous.
437
+ It will use a combination of a VIEW and column aliasing to work with both column names simultaneously
416
438
 
417
439
  ```ruby
418
440
  class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.0]
@@ -425,7 +447,7 @@ end
425
447
  4. Replace usages of the old column with a new column in the codebase
426
448
  5. Deploy
427
449
  6. Remove the column rename config from step 1
428
- 7. Remove the VIEW created in step 3:
450
+ 7. Remove the VIEW created in step 3 and finally rename the column:
429
451
 
430
452
  ```ruby
431
453
  class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.0]
@@ -794,6 +816,24 @@ end
794
816
 
795
817
  **Note**: If you forget `disable_ddl_transaction!`, the migration will fail.
796
818
 
819
+ ### Adding an exclusion constraint
820
+
821
+ :x: **Bad**
822
+
823
+ Adding an exclusion constraint blocks reads and writes while every row is checked.
824
+
825
+ ```ruby
826
+ class AddExclusionContraint < ActiveRecord::Migration[7.1]
827
+ def change
828
+ add_exclusion_constraint :users, "number WITH =", using: :gist
829
+ end
830
+ end
831
+ ```
832
+
833
+ :white_check_mark: **Good**
834
+
835
+ [Let us know](https://github.com/fatkodima/online_migrations/issues/new) if you have a safe way to do this (exclusion constraints cannot be marked `NOT VALID`).
836
+
797
837
  ### Adding a json column
798
838
 
799
839
  :x: **Bad**
@@ -38,7 +38,7 @@ OnlineMigrations.configure do |config|
38
38
  # migration failure in production. This is also useful in development to get
39
39
  # a better grasp of what is going on for high-level statements like add_column_with_default.
40
40
  #
41
- # Note: It can be overriden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
41
+ # Note: It can be overridden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
42
42
  config.verbose_sql_logs = defined?(Rails) && Rails.env.production?
43
43
 
44
44
  # Lock retries.
@@ -67,25 +67,25 @@ OnlineMigrations.configure do |config|
67
67
 
68
68
  # ==> Background migrations configuration
69
69
  # The number of rows to process in a single background migration run.
70
- # config.backround_migrations.batch_size = 20_000
70
+ # config.background_migrations.batch_size = 20_000
71
71
 
72
72
  # The smaller batches size that the batches will be divided into.
73
- # config.backround_migrations.sub_batch_size = 1000
73
+ # config.background_migrations.sub_batch_size = 1000
74
74
 
75
75
  # The pause interval between each background migration job's execution (in seconds).
76
- # config.backround_migrations.batch_pause = 0.seconds
76
+ # config.background_migrations.batch_pause = 0.seconds
77
77
 
78
78
  # The number of milliseconds to sleep between each sub_batch execution.
79
- # config.backround_migrations.sub_batch_pause_ms = 100
79
+ # config.background_migrations.sub_batch_pause_ms = 100
80
80
 
81
81
  # Maximum number of batch run attempts.
82
82
  # When attempts are exhausted, the individual batch is marked as failed.
83
- # config.backround_migrations.batch_max_attempts = 5
83
+ # config.background_migrations.batch_max_attempts = 5
84
84
 
85
85
  # Configure custom throttler for background migrations.
86
86
  # It will be called before each batch run.
87
87
  # If throttled, the current run will be retried next time.
88
- # config.backround_migrations.throttler = -> { DatabaseStatus.unhealthy? }
88
+ # config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
89
89
 
90
90
  # The number of seconds that must pass before the running job is considered stuck.
91
91
  # config.background_migrations.stuck_jobs_timeout = 1.hour
@@ -95,7 +95,7 @@ OnlineMigrations.configure do |config|
95
95
  config.background_migrations.backtrace_cleaner = Rails.backtrace_cleaner
96
96
 
97
97
  # The callback to perform when an error occurs in the migration job.
98
- # config.backround_migrations.error_handler = ->(error, errored_job) do
98
+ # config.background_migrations.error_handler = ->(error, errored_job) do
99
99
  # Bugsnag.notify(error) do |notification|
100
100
  # notification.add_metadata(:background_migration, { name: errored_job.migration_name })
101
101
  # end
@@ -43,7 +43,7 @@ module OnlineMigrations
43
43
  # @return [Proc]
44
44
  #
45
45
  # @example
46
- # OnlineMigrations.config.backround_migrations.throttler = -> { DatabaseStatus.unhealthy? }
46
+ # OnlineMigrations.config.background_migrations.throttler = -> { DatabaseStatus.unhealthy? }
47
47
  #
48
48
  attr_reader :throttler
49
49
 
@@ -64,7 +64,7 @@ module OnlineMigrations
64
64
  # The callback to perform when an error occurs in the migration job.
65
65
  #
66
66
  # @example
67
- # OnlineMigrations.config.backround_migrations.error_handler = ->(error, errored_job) do
67
+ # OnlineMigrations.config.background_migrations.error_handler = ->(error, errored_job) do
68
68
  # Bugsnag.notify(error) do |notification|
69
69
  # notification.add_metadata(:background_migration, { name: errored_job.migration_name })
70
70
  # end
@@ -107,19 +107,26 @@ module OnlineMigrations
107
107
  transaction do
108
108
  columns_and_types.each do |(column_name, new_type)|
109
109
  old_col = __column_for(table_name, column_name)
110
+ old_col_options = __options_from_column(old_col, [:collation, :comment])
110
111
  column_options = options[column_name] || {}
111
112
  tmp_column_name = conversions[column_name]
112
113
 
113
- if raw_connection.server_version >= 11_00_00 &&
114
- primary_key(table_name) == column_name.to_s && old_col.type == :integer
115
- # If the column to be converted is a Primary Key, set it to
116
- # `NOT NULL DEFAULT 0` and we'll copy the correct values when backfilling.
117
- # That way, we skip the expensive validation step required to add
118
- # a `NOT NULL` constraint at the end of the process.
119
- add_column(table_name, tmp_column_name, new_type,
120
- **column_options.merge(default: old_col.default || 0, null: false))
114
+ if raw_connection.server_version >= 11_00_00
115
+ if primary_key(table_name) == column_name.to_s && old_col.type == :integer
116
+ # If the column to be converted is a Primary Key, set it to
117
+ # `NOT NULL DEFAULT 0` and we'll copy the correct values when backfilling.
118
+ # That way, we skip the expensive validation step required to add
119
+ # a `NOT NULL` constraint at the end of the process.
120
+ add_column(table_name, tmp_column_name, new_type,
121
+ **old_col_options.merge(column_options).merge(default: old_col.default || 0, null: false))
122
+ else
123
+ unless old_col.default.nil?
124
+ old_col_options = old_col_options.merge(default: old_col.default, null: old_col.null)
125
+ end
126
+ add_column(table_name, tmp_column_name, new_type, **old_col_options.merge(column_options))
127
+ end
121
128
  else
122
- add_column(table_name, tmp_column_name, new_type, **column_options)
129
+ add_column(table_name, tmp_column_name, new_type, **old_col_options.merge(column_options))
123
130
  change_column_default(table_name, tmp_column_name, old_col.default) unless old_col.default.nil?
124
131
  end
125
132
  end
@@ -377,6 +384,17 @@ module OnlineMigrations
377
384
  "#{column_name}_for_type_change"
378
385
  end
379
386
 
387
+ def __options_from_column(column, options)
388
+ result = {}
389
+ options.each do |option|
390
+ if column.respond_to?(option)
391
+ value = column.public_send(option)
392
+ result[option] = value unless value.nil?
393
+ end
394
+ end
395
+ result
396
+ end
397
+
380
398
  def __copy_triggers_name(table_name, from_column, to_column)
381
399
  CopyTrigger.on_table(table_name, connection: self).name(from_column, to_column)
382
400
  end
@@ -42,6 +42,7 @@ module OnlineMigrations
42
42
 
43
43
  true
44
44
  end
45
+ ruby2_keywords(:check) if respond_to?(:ruby2_keywords, true)
45
46
 
46
47
  private
47
48
  def check_database_version
@@ -228,6 +229,10 @@ module OnlineMigrations
228
229
 
229
230
  type = type.to_sym
230
231
 
232
+ # Ignore internal Active Record migrations compatibility related
233
+ # options, like `_uses_legacy_table_name` etc. They all are starting with "_".
234
+ options = options.reject { |key, _| key.to_s.start_with?("_") }
235
+
231
236
  existing_column = column_for(table_name, column_name)
232
237
  if existing_column
233
238
  existing_type = existing_column.type.to_sym
@@ -271,7 +276,7 @@ module OnlineMigrations
271
276
  !options[:limit] || (existing_column.limit && options[:limit] >= existing_column.limit)
272
277
  end
273
278
  when :numeric, :decimal
274
- # numeric and decimal are equivalent and can be used interchangably
279
+ # numeric and decimal are equivalent and can be used interchangeably
275
280
  [:numeric, :decimal].include?(existing_type) &&
276
281
  (
277
282
  (
@@ -517,6 +522,12 @@ module OnlineMigrations
517
522
  end
518
523
  end
519
524
 
525
+ def add_exclusion_constraint(table_name, _expression, **_options)
526
+ unless new_or_small_table?(table_name)
527
+ raise_error :add_exclusion_constraint
528
+ end
529
+ end
530
+
520
531
  def add_check_constraint(table_name, expression, **options)
521
532
  if !new_or_small_table?(table_name) && options[:validate] != false
522
533
  name = options[:name] || check_constraint_name(table_name, expression)
@@ -153,7 +153,7 @@ module OnlineMigrations
153
153
  # This feature is enabled by default in a production Rails environment.
154
154
  # @return [Boolean]
155
155
  #
156
- # @note: It can be overriden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
156
+ # @note: It can be overridden by `ONLINE_MIGRATIONS_VERBOSE_SQL_LOGS` environment variable.
157
157
  #
158
158
  attr_accessor :verbose_sql_logs
159
159
 
@@ -131,7 +131,8 @@ migration_helpers provides a safer approach to do this:
131
131
  }
132
132
  }
133
133
  <% unless partial_writes %>
134
- NOTE: You also need to temporarily enable partial writes until the process of column rename is fully done.
134
+ NOTE: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
135
+ until the process of column rename is fully done.
135
136
  # config/application.rb
136
137
  config.active_record.<%= partial_writes_setting %> = true
137
138
  <% end %>
@@ -150,7 +151,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
150
151
  4. Replace usages of the old column with a new column in the codebase
151
152
  5. Deploy
152
153
  6. Remove the column rename config from step 1
153
- 7. Remove the VIEW created in step 3:
154
+ 7. Remove the VIEW created in step 3 and finally rename the column:
154
155
 
155
156
  class Finalize<%= migration_name %> < <%= migration_parent %>
156
157
  def change
@@ -175,6 +176,9 @@ A safer approach can be accomplished in several steps:
175
176
  end
176
177
  end
177
178
 
179
+ **Note**: `initialize_column_type_change` accepts additional options (like `:limit`, `:default` etc)
180
+ which will be passed to `add_column` when creating a new column, so you can override previous values.
181
+
178
182
  2. Backfill data from the old column to the new column:
179
183
 
180
184
  class Backfill<%= migration_name %> < <%= migration_parent %>
@@ -380,6 +384,9 @@ end",
380
384
  "Validating a foreign key while holding heavy locks on tables is dangerous.
381
385
  Use disable_ddl_transaction! or a separate migration.",
382
386
 
387
+ add_exclusion_constraint:
388
+ "Adding an exclusion constraint blocks reads and writes while every row is checked.",
389
+
383
390
  add_check_constraint:
384
391
  "Adding a check constraint blocks reads and writes while every row is checked.
385
392
  A safer approach is to add the check constraint without validating existing rows,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.5.3"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: online_migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-10 00:00:00.000000000 Z
11
+ date: 2023-02-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -101,7 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0'
103
103
  requirements: []
104
- rubygems_version: 3.1.6
104
+ rubygems_version: 3.4.3
105
105
  signing_key:
106
106
  specification_version: 4
107
107
  summary: Catch unsafe PostgreSQL migrations in development and run them easier in