online_migrations 0.6.0 → 0.7.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: 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