online_migrations 0.5.4 → 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: 974e87c3442a03f1a954a338678589549ed5c91fd293a7b4b5a8bad5ba807f32
4
- data.tar.gz: 1b1dacee542413f623d74e947f704f57fa93a032f5b26c06d3ffc3dec105cc99
3
+ metadata.gz: 625229a6ef3cc2b9a6a6201ae2033b4560ca395482d797115563abd1dace6716
4
+ data.tar.gz: bc24c264f8a6be48e9232959a6e654e5a814c9872317d88e788eca8260dd6a7e
5
5
  SHA512:
6
- metadata.gz: 2b4f245c656915711ffb2cb9c1a79f0a4a5a52374c38f01b0cd97a6a0bc06e803b5772ecb4cad36aef6f2dc5b97f543f66c35bcb1b195792c301746fc80c888c
7
- data.tar.gz: 908f3c213a86b4e7fd48e9120c603c80f68facaa210827fd6ff60637a2b2e62241171f17706fe60cfecc324b318b227575d777a3d988299277f137ecdde0b48f
6
+ metadata.gz: 8054e3f5fec6fbc066cc3d6184a3002a2e77b21dee5ee7adca1025fa907c3e2263fae206b25150b662e37cd1dfac46cd66a54c82d725848020c30ed0cecef5c9
7
+ data.tar.gz: 2ad342552fbf328c341875192aba31c441b2bd5a72d3fac9edfef9caa9fa2eab003c9ae7d441d4778b3425f82aed140290de82df3291c030e0b1e7867871ae47
data/CHANGELOG.md CHANGED
@@ -1,6 +1,13 @@
1
1
  ## master (unreleased)
2
2
 
3
- ## 0.5.4 (2022-01-03)
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)
4
11
 
5
12
  - Support ruby 3.2.0
6
13
 
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:
@@ -140,6 +142,7 @@ Potentially dangerous operations:
140
142
  - [replacing an index](#replacing-an-index)
141
143
  - [adding a reference](#adding-a-reference)
142
144
  - [adding a foreign key](#adding-a-foreign-key)
145
+ - [adding an exclusion constraint](#adding-an-exclusion-constraint)
143
146
  - [adding a json column](#adding-a-json-column)
144
147
  - [using primary key with short integer type](#using-primary-key-with-short-integer-type)
145
148
  - [hash indexes](#hash-indexes)
@@ -321,6 +324,9 @@ A safer approach can be accomplished in several steps:
321
324
  end
322
325
  ```
323
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
+
324
330
  2. Backfill data from the old column to the new column:
325
331
 
326
332
  ```ruby
@@ -395,7 +401,7 @@ For the previous example, to rename `name` column to `first_name` of the `users`
395
401
  ```sql
396
402
  BEGIN;
397
403
  ALTER TABLE users RENAME TO users_column_rename;
398
- 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;
399
405
  COMMIT;
400
406
  ```
401
407
 
@@ -414,9 +420,21 @@ OnlineMigrations.config.column_renames = {
414
420
  }
415
421
  }
416
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
+ ```
417
433
 
418
434
  2. Deploy
419
- 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
420
438
 
421
439
  ```ruby
422
440
  class InitializeRenameUsersNameToFirstName < ActiveRecord::Migration[7.0]
@@ -429,7 +447,7 @@ end
429
447
  4. Replace usages of the old column with a new column in the codebase
430
448
  5. Deploy
431
449
  6. Remove the column rename config from step 1
432
- 7. Remove the VIEW created in step 3:
450
+ 7. Remove the VIEW created in step 3 and finally rename the column:
433
451
 
434
452
  ```ruby
435
453
  class FinalizeRenameUsersNameToFirstName < ActiveRecord::Migration[7.0]
@@ -798,6 +816,24 @@ end
798
816
 
799
817
  **Note**: If you forget `disable_ddl_transaction!`, the migration will fail.
800
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
+
801
837
  ### Adding a json column
802
838
 
803
839
  :x: **Bad**
@@ -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
@@ -229,6 +229,10 @@ module OnlineMigrations
229
229
 
230
230
  type = type.to_sym
231
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
+
232
236
  existing_column = column_for(table_name, column_name)
233
237
  if existing_column
234
238
  existing_type = existing_column.type.to_sym
@@ -518,6 +522,12 @@ module OnlineMigrations
518
522
  end
519
523
  end
520
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
+
521
531
  def add_check_constraint(table_name, expression, **options)
522
532
  if !new_or_small_table?(table_name) && options[:validate] != false
523
533
  name = options[:name] || check_constraint_name(table_name, expression)
@@ -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.4"
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.4
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: 2023-01-03 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