online_migrations 0.6.0 → 0.7.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: 625229a6ef3cc2b9a6a6201ae2033b4560ca395482d797115563abd1dace6716
4
- data.tar.gz: bc24c264f8a6be48e9232959a6e654e5a814c9872317d88e788eca8260dd6a7e
3
+ metadata.gz: '029dd11fe1935f72c4933c081b45c972613caa252c578a005b34a902122923d6'
4
+ data.tar.gz: 04d66e91c1c54f1fe19d8a743d4b6f70ef218bbe2310eb58a779602a92e72736
5
5
  SHA512:
6
- metadata.gz: 8054e3f5fec6fbc066cc3d6184a3002a2e77b21dee5ee7adca1025fa907c3e2263fae206b25150b662e37cd1dfac46cd66a54c82d725848020c30ed0cecef5c9
7
- data.tar.gz: 2ad342552fbf328c341875192aba31c441b2bd5a72d3fac9edfef9caa9fa2eab003c9ae7d441d4778b3425f82aed140290de82df3291c030e0b1e7867871ae47
6
+ metadata.gz: '07865cb5068a3cba7956db786f78d0999070b9445798af0df17f9360fa9b3e5b3f19409756f383b9eb402fa8cfa0a2b2bb6ca57475267f915844a248598004b5'
7
+ data.tar.gz: 0f44b2cd5bd9bad6f85c2980da2ea41ad79c100c46bdce0c4be2d9630cc239b42a0217ae07fd6f568503a42a640da6b63a2840d3b714994fce65708bd8dbf289
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.7.0 (2023-02-14)
4
+
5
+ - Add support for renaming multiple columns at once in the same table
6
+ - Fix quoting table/column names across the library
7
+ - Fix deffered foreign keys support in `add_foreign_key` (Active Record >= 7)
8
+ - Reset attempts of failing jobs before executing background migration inline
9
+
3
10
  ## 0.6.0 (2023-02-04)
4
11
 
5
12
  - Ignore internal Active Record migrations compatibility related options when suggesting a safe column type change
@@ -123,6 +123,14 @@ module OnlineMigrations
123
123
  end
124
124
  end
125
125
 
126
+ # @private
127
+ def reset_failed_jobs_attempts
128
+ iterator = BatchIterator.new(migration_jobs.failed.attempts_exceeded)
129
+ iterator.each_batch(of: 100) do |relation|
130
+ relation.update_all(attempts: 0)
131
+ end
132
+ end
133
+
126
134
  # @private
127
135
  def next_batch_range
128
136
  iterator = BatchIterator.new(migration_relation)
@@ -36,6 +36,7 @@ module OnlineMigrations
36
36
  end
37
37
 
38
38
  scope :except_succeeded, -> { where("status != ?", statuses[:succeeded]) }
39
+ scope :attempts_exceeded, -> { where("attempts >= max_attempts") }
39
40
 
40
41
  enum status: STATUSES.map { |status| [status, status.to_s] }.to_h
41
42
 
@@ -67,6 +67,7 @@ module OnlineMigrations
67
67
  # Mark is as finishing to avoid being picked up
68
68
  # by the background migrations scheduler.
69
69
  migration.finishing!
70
+ migration.reset_failed_jobs_attempts
70
71
 
71
72
  while migration.finishing?
72
73
  run_migration_job
@@ -570,7 +570,7 @@ module OnlineMigrations
570
570
  transaction do
571
571
  # We'll need ACCESS EXCLUSIVE lock on the related tables,
572
572
  # lets make sure it can be acquired from the start.
573
- execute("LOCK TABLE #{table_name}, #{referencing_table_name} IN ACCESS EXCLUSIVE MODE")
573
+ execute("LOCK TABLE #{quote_table_name(table_name)}, #{quote_table_name(referencing_table_name)} IN ACCESS EXCLUSIVE MODE")
574
574
 
575
575
  remove_foreign_key(referencing_table_name, name: existing_name)
576
576
  __rename_constraint(referencing_table_name, tmp_name, existing_name)
@@ -6,9 +6,13 @@ module OnlineMigrations
6
6
  REVERSIBLE_AND_IRREVERSIBLE_METHODS = [
7
7
  :update_column_in_batches,
8
8
  :initialize_column_rename,
9
+ :initialize_columns_rename,
9
10
  :revert_initialize_column_rename,
11
+ :revert_initialize_columns_rename,
10
12
  :finalize_column_rename,
13
+ :finalize_columns_rename,
11
14
  :revert_finalize_column_rename,
15
+ :revert_finalize_columns_rename,
12
16
  :initialize_table_rename,
13
17
  :revert_initialize_table_rename,
14
18
  :finalize_table_rename,
@@ -49,7 +53,9 @@ module OnlineMigrations
49
53
  module StraightReversions
50
54
  {
51
55
  initialize_column_rename: :revert_initialize_column_rename,
56
+ initialize_columns_rename: :revert_initialize_columns_rename,
52
57
  finalize_column_rename: :revert_finalize_column_rename,
58
+ finalize_columns_rename: :revert_finalize_columns_rename,
53
59
  initialize_table_rename: :revert_initialize_table_rename,
54
60
  finalize_table_rename: :revert_finalize_table_rename,
55
61
  add_not_null_constraint: :remove_not_null_constraint,
@@ -84,11 +90,20 @@ module OnlineMigrations
84
90
  _table, column, new_column = args
85
91
  if !column || !new_column
86
92
  raise ActiveRecord::IrreversibleMigration,
87
- "invert_revert_initialize_column_rename is only reversible if given a column and new_column."
93
+ "revert_initialize_column_rename is only reversible if given a column and new_column."
88
94
  end
89
95
  [:initialize_column_rename, args]
90
96
  end
91
97
 
98
+ def invert_revert_initialize_columns_rename(args)
99
+ _table, old_new_column_hash = args
100
+ unless old_new_column_hash
101
+ raise ActiveRecord::IrreversibleMigration,
102
+ "revert_initialize_columns_rename is only reversible if given a hash of old and new columns."
103
+ end
104
+ [:initialize_columns_rename, args]
105
+ end
106
+
92
107
  def invert_finalize_table_rename(args)
93
108
  _table_name, new_name = args
94
109
  unless new_name
@@ -18,17 +18,10 @@ module OnlineMigrations
18
18
  super(renamed_tables[table_name])
19
19
  elsif renamed_columns.key?(table_name)
20
20
  columns = super(column_rename_table(table_name))
21
-
22
- old_column_name, new_column_name = renamed_columns[table_name].first.to_a
23
-
24
- old_column = columns.find { |column| column.name == old_column_name }
25
- new_column = old_column.dup
26
-
27
- # ActiveRecord defines only reader for :name
28
- new_column.instance_variable_set(:@name, new_column_name)
29
-
30
- # Correspond to the ActiveRecord freezing of each column
31
- columns << new_column.freeze
21
+ renamed_columns[table_name].each do |old_column_name, new_column_name|
22
+ duplicate_column(old_column_name, new_column_name, columns)
23
+ end
24
+ columns
32
25
  else
33
26
  super.reject { |column| column.name.end_with?("_for_type_change") }
34
27
  end
@@ -94,5 +87,14 @@ module OnlineMigrations
94
87
  @renamed_columns = nil
95
88
  @renamed_tables = nil
96
89
  end
90
+
91
+ def duplicate_column(old_column_name, new_column_name, columns)
92
+ old_column = columns.find { |column| column.name == old_column_name }
93
+ new_column = old_column.dup
94
+ # ActiveRecord defines only reader for :name
95
+ new_column.instance_variable_set(:@name, new_column_name)
96
+ # Correspond to the ActiveRecord freezing of each column
97
+ columns << new_column.freeze
98
+ end
97
99
  end
98
100
  end
@@ -177,20 +177,34 @@ module OnlineMigrations
177
177
  # until `finalize_column_rename` is run
178
178
  #
179
179
  def initialize_column_rename(table_name, column_name, new_column_name)
180
- tmp_table = "#{table_name}_column_rename"
180
+ initialize_columns_rename(table_name, { column_name => new_column_name })
181
+ end
181
182
 
183
+ # Same as `initialize_column_rename` but for multiple columns.
184
+ #
185
+ # This is useful to avoid multiple iterations of the safe column rename steps
186
+ # when renaming multiple columns.
187
+ #
188
+ # @param table_name [String, Symbol] table name
189
+ # @param old_new_column_hash [Hash] the hash of old and new columns
190
+ #
191
+ # @example
192
+ # initialize_columns_rename(:users, { fname: :first_name, lname: :last_name })
193
+ #
194
+ # @see #initialize_column_rename
195
+ #
196
+ def initialize_columns_rename(table_name, old_new_column_hash)
182
197
  transaction do
183
- rename_table(table_name, tmp_table)
184
- execute("CREATE VIEW #{table_name} AS SELECT *, #{column_name} AS #{new_column_name} FROM #{tmp_table}")
198
+ rename_table_create_view(table_name, old_new_column_hash)
185
199
  end
186
200
  end
187
201
 
188
202
  # Reverts operations performed by initialize_column_rename
189
203
  #
190
204
  # @param table_name [String, Symbol] table name
191
- # @param _column_name [String, Symbol] the name of the column to be renamed.
205
+ # @param column_name [String, Symbol] the name of the column to be renamed.
192
206
  # Passing this argument will make this change reversible in migration
193
- # @param _new_column_name [String, Symbol] new new name of the column.
207
+ # @param new_column_name [String, Symbol] new new name of the column.
194
208
  # Passing this argument will make this change reversible in migration
195
209
  #
196
210
  # @return [void]
@@ -198,9 +212,24 @@ module OnlineMigrations
198
212
  # @example
199
213
  # revert_initialize_column_rename(:users, :name, :first_name)
200
214
  #
201
- def revert_initialize_column_rename(table_name, _column_name = nil, _new_column_name = nil)
215
+ def revert_initialize_column_rename(table_name, column_name = nil, new_column_name = nil)
216
+ revert_initialize_columns_rename(table_name, { column_name => new_column_name })
217
+ end
218
+
219
+ # Same as `revert_initialize_column_rename` but for multiple columns.
220
+ #
221
+ # @param table_name [String, Symbol] table name
222
+ # @param _old_new_column_hash [Hash] the hash of old and new columns
223
+ # Passing this argument will make this change reversible in migration
224
+ #
225
+ # @return [void]
226
+ #
227
+ # @example
228
+ # revert_initialize_columns_rename(:users, { fname: :first_name, lname: :last_name })
229
+ #
230
+ def revert_initialize_columns_rename(table_name, _old_new_column_hash = nil)
202
231
  transaction do
203
- execute("DROP VIEW #{table_name}")
232
+ execute("DROP VIEW #{quote_table_name(table_name)}")
204
233
  rename_table("#{table_name}_column_rename", table_name)
205
234
  end
206
235
  end
@@ -214,10 +243,24 @@ module OnlineMigrations
214
243
  # finalize_column_rename(:users, :name, :first_name)
215
244
  #
216
245
  def finalize_column_rename(table_name, column_name, new_column_name)
246
+ finalize_columns_rename(table_name, { column_name => new_column_name })
247
+ end
248
+
249
+ # Same as `finalize_column_rename` but for multiple columns.
250
+ #
251
+ # @param (see #initialize_columns_rename)
252
+ # @return [void]
253
+ #
254
+ # @example
255
+ # finalize_columns_rename(:users, { fname: :first_name, lname: :last_name })
256
+ #
257
+ def finalize_columns_rename(table_name, old_new_column_hash)
217
258
  transaction do
218
- execute("DROP VIEW #{table_name}")
259
+ execute("DROP VIEW #{quote_table_name(table_name)}")
219
260
  rename_table("#{table_name}_column_rename", table_name)
220
- rename_column(table_name, column_name, new_column_name)
261
+ old_new_column_hash.each do |column_name, new_column_name|
262
+ rename_column(table_name, column_name, new_column_name)
263
+ end
221
264
  end
222
265
  end
223
266
 
@@ -230,12 +273,23 @@ module OnlineMigrations
230
273
  # revert_finalize_column_rename(:users, :name, :first_name)
231
274
  #
232
275
  def revert_finalize_column_rename(table_name, column_name, new_column_name)
233
- tmp_table = "#{table_name}_column_rename"
276
+ revert_finalize_columns_rename(table_name, { column_name => new_column_name })
277
+ end
234
278
 
279
+ # Same as `revert_finalize_column_rename` but for multiple columns.
280
+ #
281
+ # @param (see #initialize_columns_rename)
282
+ # @return [void]
283
+ #
284
+ # @example
285
+ # revert_finalize_columns_rename(:users, { fname: :first_name, lname: :last_name })
286
+ #
287
+ def revert_finalize_columns_rename(table_name, old_new_column_hash)
235
288
  transaction do
236
- rename_column(table_name, new_column_name, column_name)
237
- rename_table(table_name, tmp_table)
238
- execute("CREATE VIEW #{table_name} AS SELECT *, #{column_name} AS #{new_column_name} FROM #{tmp_table}")
289
+ old_new_column_hash.each do |column_name, new_column_name|
290
+ rename_column(table_name, new_column_name, column_name)
291
+ end
292
+ rename_table_create_view(table_name, old_new_column_hash)
239
293
  end
240
294
  end
241
295
 
@@ -286,7 +340,7 @@ module OnlineMigrations
286
340
  def initialize_table_rename(table_name, new_name)
287
341
  transaction do
288
342
  rename_table(table_name, new_name)
289
- execute("CREATE VIEW #{table_name} AS SELECT * FROM #{new_name}")
343
+ execute("CREATE VIEW #{quote_table_name(table_name)} AS SELECT * FROM #{quote_table_name(new_name)}")
290
344
  end
291
345
  end
292
346
 
@@ -300,7 +354,7 @@ module OnlineMigrations
300
354
  #
301
355
  def revert_initialize_table_rename(table_name, new_name)
302
356
  transaction do
303
- execute("DROP VIEW IF EXISTS #{table_name}")
357
+ execute("DROP VIEW IF EXISTS #{quote_table_name(table_name)}")
304
358
  rename_table(new_name, table_name)
305
359
  end
306
360
  end
@@ -316,7 +370,7 @@ module OnlineMigrations
316
370
  # finalize_table_rename(:users, :clients)
317
371
  #
318
372
  def finalize_table_rename(table_name, _new_name = nil)
319
- execute("DROP VIEW IF EXISTS #{table_name}")
373
+ execute("DROP VIEW IF EXISTS #{quote_table_name(table_name)}")
320
374
  end
321
375
 
322
376
  # Reverts operations performed by finalize_table_rename
@@ -329,7 +383,7 @@ module OnlineMigrations
329
383
  # revert_finalize_table_rename(:users, :clients)
330
384
  #
331
385
  def revert_finalize_table_rename(table_name, new_name)
332
- execute("CREATE VIEW #{table_name} AS SELECT * FROM #{new_name}")
386
+ execute("CREATE VIEW #{quote_table_name(table_name)} AS SELECT * FROM #{quote_table_name(new_name)}")
333
387
  end
334
388
 
335
389
  # Swaps two column names in a table
@@ -738,14 +792,18 @@ module OnlineMigrations
738
792
  options[:name] ||= __foreign_key_name(to_table, options[:column])
739
793
 
740
794
  query = <<-SQL.strip_heredoc.dup
741
- ALTER TABLE #{from_table}
742
- ADD CONSTRAINT #{options[:name]}
743
- FOREIGN KEY (#{options[:column]})
744
- REFERENCES #{to_table} (#{options[:primary_key]})
795
+ ALTER TABLE #{quote_table_name(from_table)}
796
+ ADD CONSTRAINT #{quote_column_name(options[:name])}
797
+ FOREIGN KEY (#{quote_column_name(options[:column])})
798
+ REFERENCES #{quote_table_name(to_table)} (#{quote_column_name(options[:primary_key])})
745
799
  SQL
746
800
  query << "#{__action_sql('DELETE', options[:on_delete])}\n" if options[:on_delete].present?
747
801
  query << "#{__action_sql('UPDATE', options[:on_update])}\n" if options[:on_update].present?
748
802
  query << "NOT VALID\n" if !validate
803
+ if Utils.ar_version >= 7.0 && options[:deferrable]
804
+ query << " DEFERRABLE"
805
+ query << " INITIALLY #{options[:deferrable].to_s.upcase}\n" if options[:deferrable] != true
806
+ end
749
807
 
750
808
  execute(query.squish)
751
809
  end
@@ -766,7 +824,7 @@ module OnlineMigrations
766
824
  # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
767
825
  # It only conflicts with other validations, creating/removing indexes,
768
826
  # and some other "ALTER TABLE"s.
769
- execute("ALTER TABLE #{from_table} VALIDATE CONSTRAINT #{fk_name_to_validate}")
827
+ execute("ALTER TABLE #{quote_table_name(from_table)} VALIDATE CONSTRAINT #{quote_column_name(fk_name_to_validate)}")
770
828
  end
771
829
  end
772
830
 
@@ -786,7 +844,10 @@ module OnlineMigrations
786
844
  Utils.say("Check constraint was not created because it already exists (this may be due to an aborted migration " \
787
845
  "or similar) table_name: #{table_name}, expression: #{expression}, constraint name: #{constraint_name}")
788
846
  else
789
- query = "ALTER TABLE #{table_name} ADD CONSTRAINT #{constraint_name} CHECK (#{expression})"
847
+ query = <<-SQL.squish
848
+ ALTER TABLE #{quote_table_name(table_name)}
849
+ ADD CONSTRAINT #{quote_column_name(constraint_name)} CHECK (#{expression})
850
+ SQL
790
851
  query += " NOT VALID" if !validate
791
852
 
792
853
  execute(query)
@@ -808,7 +869,10 @@ module OnlineMigrations
808
869
  # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
809
870
  # It only conflicts with other validations, creating/removing indexes,
810
871
  # and some other "ALTER TABLE"s.
811
- execute("ALTER TABLE #{table_name} VALIDATE CONSTRAINT #{constraint_name}")
872
+ execute(<<-SQL.squish)
873
+ ALTER TABLE #{quote_table_name(table_name)}
874
+ VALIDATE CONSTRAINT #{quote_column_name(constraint_name)}
875
+ SQL
812
876
  end
813
877
  end
814
878
 
@@ -818,7 +882,10 @@ module OnlineMigrations
818
882
  #
819
883
  def remove_check_constraint(table_name, expression = nil, **options)
820
884
  constraint_name = __check_constraint_name!(table_name, expression: expression, **options)
821
- execute("ALTER TABLE #{table_name} DROP CONSTRAINT #{constraint_name}")
885
+ execute(<<-SQL.squish)
886
+ ALTER TABLE #{quote_table_name(table_name)}
887
+ DROP CONSTRAINT #{quote_column_name(constraint_name)}
888
+ SQL
822
889
  end
823
890
  end
824
891
 
@@ -1084,5 +1151,19 @@ module OnlineMigrations
1084
1151
  _, schema = table_name.to_s.split(".").reverse
1085
1152
  schema ? quote(schema) : "current_schema()"
1086
1153
  end
1154
+
1155
+ def rename_table_create_view(table_name, old_new_column_hash)
1156
+ tmp_table = "#{table_name}_column_rename"
1157
+ rename_table(table_name, tmp_table)
1158
+ column_mapping = old_new_column_hash.map do |column_name, new_column_name|
1159
+ "#{quote_column_name(column_name)} AS #{quote_column_name(new_column_name)}"
1160
+ end.join(", ")
1161
+
1162
+ execute(<<-SQL.squish)
1163
+ CREATE VIEW #{quote_table_name(table_name)} AS
1164
+ SELECT *, #{column_mapping}
1165
+ FROM #{quote_table_name(tmp_table)}
1166
+ SQL
1167
+ end
1087
1168
  end
1088
1169
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.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.6.0
4
+ version: 0.7.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-02-04 00:00:00.000000000 Z
11
+ date: 2023-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord