online_migrations 0.5.4 → 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: 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