online_migrations 0.5.3 → 0.6.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 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