online_migrations 0.22.0 → 0.23.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: 493a3f1a84eb30974c533f81ca5baf8b739d69d4ec6e30810d891284791632f1
4
- data.tar.gz: 9dae1370ea3073146888d3757314bac6c8e968c5e35a2c2be345f3617f610881
3
+ metadata.gz: 1052d2cc58898bc561b5e755daccc137d7d6e1d35afa3af5968c2b655ebe5805
4
+ data.tar.gz: ad72d601c36ea9192f192fb8941d30a2cfbcf0ed87e0717fb5d4ebf47b20f169
5
5
  SHA512:
6
- metadata.gz: d40cae89f1be37a97567d7bb05d1ded17479ce89b9783ed0c606d6bdfdec2d940d251de4de66557af774fc88e1262ae492539fe3fae5f4cb105d88526e7315d3
7
- data.tar.gz: 26316851d405210a67576ea0ae35ba0419574832aacc43a2f0ddd00a8f22223f0d1ed73ff1635f6f9aa4168877c48aebb56d0f93f05472b531d80f3fc19881e9
6
+ metadata.gz: 697ac61bcb41e00744cfa87965910a3e1d68eaa571ad5c6dc3b3d8a4d14deb16ae0cbc0d9771d8d57668be8529f200736a14c1769d7d8cf6c64fc083060ff74c
7
+ data.tar.gz: 7b4e383f7628ba5cc74c2bb86f4460890b70f3052738f0f20db3571ee770e22c49bc548f5c0ef3fd47caa456a84eb4174f031742869612ee3b27505faf49ecec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## master (unreleased)
2
2
 
3
+ ## 0.23.0 (2025-01-13)
4
+
5
+ - Prevent multiple instances of schedulers from being running simultaneously
6
+
7
+ - Reduce default batch sizes for background data migrations
8
+
9
+ `batch_size` was 20_000, now 1_000; `sub_batch_size` was 1_000, now 100
10
+
11
+ - Remove deprecated code
12
+ - Drop support for PostgreSQL < 12
13
+ - Drop support for Ruby < 3.0 and Rails < 7.0
14
+
3
15
  ## 0.22.0 (2025-01-03)
4
16
 
5
17
  - Make background data migrations scheduler run a single migration at a time
data/README.md CHANGED
@@ -16,11 +16,11 @@ See [comparison to `strong_migrations`](#comparison-to-strong_migrations)
16
16
 
17
17
  ## Requirements
18
18
 
19
- - Ruby 2.7+
20
- - Rails 6.1+
21
- - PostgreSQL 9.6+
19
+ - Ruby 3.0+
20
+ - Rails 7.0+
21
+ - PostgreSQL 12+
22
22
 
23
- For older Ruby and Rails versions you can use '< 0.11' version of this gem.
23
+ For older Ruby and Rails versions you can use older versions of this gem.
24
24
 
25
25
  **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`.
26
26
 
@@ -4,11 +4,13 @@ OnlineMigrations.configure do |config|
4
4
  # Configure the migration version starting after which checks are performed.
5
5
  # config.start_after = <%= start_after %>
6
6
 
7
- # Configure statement timeout used for migrations.
7
+ # Configure statement timeout for migrations (in seconds).
8
+ # Note: Background data migrations use application specific timeouts and
9
+ # background schema migrations use their custom timeouts.
8
10
  config.statement_timeout = 1.hour
9
11
 
10
12
  # Set the version of the production database so the right checks are run in development.
11
- # config.target_version = 10
13
+ # config.target_version = 17
12
14
 
13
15
  # Configure whether to perform checks when migrating down.
14
16
  config.check_down = false
@@ -19,7 +21,7 @@ OnlineMigrations.configure do |config|
19
21
 
20
22
  # Maximum allowed lock timeout value (in seconds).
21
23
  # If set lock timeout is greater than this value, the migration will fail.
22
- # config.lock_timeout_limit = 10.seconds
24
+ config.lock_timeout_limit = 10.seconds
23
25
 
24
26
  # Configure list of tables with permanently small number of records.
25
27
  # This tables are usually tables like "settings", "prices", "plans" etc.
@@ -28,10 +30,10 @@ OnlineMigrations.configure do |config|
28
30
 
29
31
  # Analyze tables after indexes are added.
30
32
  # Outdated statistics can sometimes hurt performance.
31
- # config.auto_analyze = true
33
+ config.auto_analyze = false
32
34
 
33
35
  # Alphabetize table columns when dumping the schema.
34
- # config.alphabetize_schema = true
36
+ config.alphabetize_schema = false
35
37
 
36
38
  # Disable specific checks.
37
39
  # For the list of available checks look at the `error_messages.rb` file inside
@@ -92,29 +94,29 @@ OnlineMigrations.configure do |config|
92
94
 
93
95
  # ==> Background data migrations configuration
94
96
  # The path where generated background migrations will be placed.
95
- # config.background_migrations.migrations_path = "lib"
97
+ config.background_migrations.migrations_path = "lib"
96
98
 
97
99
  # The module in which background migrations will be placed.
98
- # config.background_migrations.migrations_module = "OnlineMigrations::BackgroundMigrations"
100
+ config.background_migrations.migrations_module = "OnlineMigrations::BackgroundMigrations"
99
101
 
100
102
  # The number of rows to process in a single background migration run.
101
- # config.background_migrations.batch_size = 20_000
103
+ config.background_migrations.batch_size = 1_000
102
104
 
103
105
  # The smaller batches size that the batches will be divided into.
104
- # config.background_migrations.sub_batch_size = 1000
106
+ config.background_migrations.sub_batch_size = 100
105
107
 
106
108
  # The pause interval between each background migration job's execution (in seconds).
107
- # config.background_migrations.batch_pause = 0.seconds
109
+ config.background_migrations.batch_pause = 0.seconds
108
110
 
109
111
  # The number of milliseconds to sleep between each sub_batch execution.
110
- # config.background_migrations.sub_batch_pause_ms = 100
112
+ config.background_migrations.sub_batch_pause_ms = 100
111
113
 
112
114
  # Maximum number of batch run attempts.
113
115
  # When attempts are exhausted, the individual batch is marked as failed.
114
- # config.background_migrations.batch_max_attempts = 5
116
+ config.background_migrations.batch_max_attempts = 5
115
117
 
116
118
  # The number of seconds that must pass before the running job is considered stuck.
117
- # config.background_migrations.stuck_jobs_timeout = 1.hour
119
+ config.background_migrations.stuck_jobs_timeout = 1.hour
118
120
 
119
121
  # The callback to perform when an error occurs in the migration job.
120
122
  # config.background_migrations.error_handler = ->(error, errored_job) do
@@ -125,10 +127,10 @@ OnlineMigrations.configure do |config|
125
127
 
126
128
  # ==> Background schema migrations configuration
127
129
  # When attempts are exhausted, the failing migration stops to be retried.
128
- # config.background_schema_migrations.max_attempts = 5
130
+ config.background_schema_migrations.max_attempts = 5
129
131
 
130
132
  # Statement timeout value used when running background schema migration.
131
- # config.background_schema_migrations.statement_timeout = 1.hour
133
+ config.background_schema_migrations.statement_timeout = 1.hour
132
134
 
133
135
  # The callback to perform when an error occurs during the background schema migration.
134
136
  # config.background_schema_migrations.error_handler = ->(error, errored_migration) do
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zlib"
4
+
5
+ module OnlineMigrations
6
+ # @private
7
+ class AdvisoryLock
8
+ attr_reader :name, :connection
9
+
10
+ def initialize(name:, connection: ApplicationRecord.connection)
11
+ @name = name
12
+ @connection = connection
13
+ end
14
+
15
+ def try_lock
16
+ locked = connection.select_value("SELECT pg_try_advisory_lock(#{lock_key})")
17
+ Utils.to_bool(locked)
18
+ end
19
+
20
+ def unlock
21
+ connection.select_value("SELECT pg_advisory_unlock(#{lock_key})")
22
+ end
23
+
24
+ # Runs the given block if an advisory lock is able to be acquired.
25
+ def try_with_lock
26
+ if try_lock
27
+ begin
28
+ yield
29
+ ensure
30
+ unlock
31
+ end
32
+ end
33
+ end
34
+
35
+ def active?
36
+ objid = lock_key & 0xffffffff
37
+ classid = lock_key >> 32
38
+
39
+ active = connection.select_value(<<~SQL)
40
+ SELECT granted
41
+ FROM pg_locks
42
+ WHERE locktype = 'advisory'
43
+ AND pid = pg_backend_pid()
44
+ AND mode = 'ExclusiveLock'
45
+ AND classid = #{classid}
46
+ AND objid = #{objid}
47
+ SQL
48
+
49
+ Utils.to_bool(active)
50
+ end
51
+
52
+ private
53
+ SALT = 936723412
54
+
55
+ def lock_key
56
+ name_hash = Zlib.crc32(name)
57
+ SALT * name_hash
58
+ end
59
+ end
60
+ end
@@ -13,12 +13,12 @@ module OnlineMigrations
13
13
  attr_accessor :migrations_module
14
14
 
15
15
  # The number of rows to process in a single background migration run
16
- # @return [Integer] defaults to 20_000
16
+ # @return [Integer] defaults to 1_000
17
17
  #
18
18
  attr_accessor :batch_size
19
19
 
20
20
  # The smaller batches size that the batches will be divided into
21
- # @return [Integer] defaults to 1000
21
+ # @return [Integer] defaults to 100
22
22
  #
23
23
  attr_accessor :sub_batch_size
24
24
 
@@ -39,36 +39,12 @@ module OnlineMigrations
39
39
  #
40
40
  attr_accessor :batch_max_attempts
41
41
 
42
- def throttler
43
- OnlineMigrations.deprecator.warn(<<~MSG)
44
- `config.background_migrations.throttler` is deprecated and will be removed.
45
- Use `config.throttler` instead.
46
- MSG
47
- OnlineMigrations.config.throttler
48
- end
49
-
50
42
  # The number of seconds that must pass before the running job is considered stuck
51
43
  #
52
44
  # @return [Integer] defaults to 1 hour
53
45
  #
54
46
  attr_accessor :stuck_jobs_timeout
55
47
 
56
- def backtrace_cleaner
57
- OnlineMigrations.deprecator.warn(<<~MSG)
58
- `config.background_migrations.backtrace_cleaner` is deprecated and will be removed.
59
- Use `config.backtrace_cleaner` instead.
60
- MSG
61
- OnlineMigrations.config.backtrace_cleaner
62
- end
63
-
64
- def backtrace_cleaner=(value)
65
- OnlineMigrations.deprecator.warn(<<~MSG)
66
- `config.background_migrations.backtrace_cleaner=` is deprecated and will be removed.
67
- Use `config.backtrace_cleaner=` instead.
68
- MSG
69
- OnlineMigrations.config.backtrace_cleaner = value
70
- end
71
-
72
48
  # The callback to perform when an error occurs in the migration job.
73
49
  #
74
50
  # @example
@@ -85,22 +61,14 @@ module OnlineMigrations
85
61
  def initialize
86
62
  @migrations_path = "lib"
87
63
  @migrations_module = "OnlineMigrations::BackgroundMigrations"
88
- @batch_size = 20_000
89
- @sub_batch_size = 1000
64
+ @batch_size = 1_000
65
+ @sub_batch_size = 100
90
66
  @batch_pause = 0.seconds
91
67
  @sub_batch_pause_ms = 100
92
68
  @batch_max_attempts = 5
93
69
  @stuck_jobs_timeout = 1.hour
94
70
  @error_handler = ->(error, errored_job) {}
95
71
  end
96
-
97
- def throttler=(value)
98
- OnlineMigrations.deprecator.warn(<<~MSG)
99
- `config.background_migrations.throttler=` is deprecated and will be removed.
100
- Use `config.throttler=` instead.
101
- MSG
102
- OnlineMigrations.config.throttler = value
103
- end
104
72
  end
105
73
  end
106
74
  end
@@ -32,12 +32,7 @@ module OnlineMigrations
32
32
 
33
33
  alias_attribute :name, :migration_name
34
34
 
35
- # Avoid deprecation warnings.
36
- if Utils.ar_version >= 7
37
- enum :status, STATUSES.index_with(&:to_s)
38
- else
39
- enum status: STATUSES.index_with(&:to_s)
40
- end
35
+ enum :status, STATUSES.index_with(&:to_s)
41
36
 
42
37
  belongs_to :parent, class_name: name, optional: true, inverse_of: :children
43
38
  has_many :children, class_name: name, foreign_key: :parent_id, dependent: :delete_all, inverse_of: :parent
@@ -335,8 +335,8 @@ module OnlineMigrations
335
335
  # defaults to `SELECT MIN(batch_column_name)`
336
336
  # @option options [Integer] :max_value Value in the column the batching will end at,
337
337
  # defaults to `SELECT MAX(batch_column_name)`
338
- # @option options [Integer] :batch_size (20_000) Number of rows to process in a single background migration run
339
- # @option options [Integer] :sub_batch_size (1000) Smaller batches size that the batches will be divided into
338
+ # @option options [Integer] :batch_size (1_000) Number of rows to process in a single background migration run
339
+ # @option options [Integer] :sub_batch_size (100) Smaller batches size that the batches will be divided into
340
340
  # @option options [Integer] :batch_pause (0) Pause interval between each background migration job's execution (in seconds)
341
341
  # @option options [Integer] :sub_batch_pause_ms (100) Number of milliseconds to sleep between each sub_batch execution
342
342
  # @option options [Integer] :batch_max_attempts (5) Maximum number of batch run attempts
@@ -38,12 +38,7 @@ module OnlineMigrations
38
38
  scope :except_succeeded, -> { where.not(status: :succeeded) }
39
39
  scope :attempts_exceeded, -> { where("attempts >= max_attempts") }
40
40
 
41
- # Avoid deprecation warnings.
42
- if Utils.ar_version >= 7
43
- enum :status, STATUSES.index_with(&:to_s)
44
- else
45
- enum status: STATUSES.index_with(&:to_s)
46
- end
41
+ enum :status, STATUSES.index_with(&:to_s)
47
42
 
48
43
  delegate :migration_name, :migration_class, :migration_object, :migration_relation, :batch_column_name,
49
44
  :arguments, :batch_pause, to: :migration
@@ -27,9 +27,18 @@ module OnlineMigrations
27
27
 
28
28
  if runnable_migration
29
29
  runner = MigrationRunner.new(runnable_migration)
30
- runner.run_migration_job
30
+
31
+ try_with_lock do
32
+ runner.run_migration_job
33
+ end
31
34
  end
32
35
  end
36
+
37
+ private
38
+ def try_with_lock(&block)
39
+ lock = AdvisoryLock.new(name: "online_migrations_data_scheduler")
40
+ lock.try_with_lock(&block)
41
+ end
33
42
  end
34
43
  end
35
44
  end
@@ -49,12 +49,7 @@ module OnlineMigrations
49
49
 
50
50
  alias_attribute :name, :migration_name
51
51
 
52
- # Avoid deprecation warnings.
53
- if Utils.ar_version >= 7
54
- enum :status, STATUSES.index_with(&:to_s)
55
- else
56
- enum status: STATUSES.index_with(&:to_s)
57
- end
52
+ enum :status, STATUSES.index_with(&:to_s)
58
53
 
59
54
  belongs_to :parent, class_name: name, optional: true, inverse_of: :children
60
55
  has_many :children, class_name: name, foreign_key: :parent_id, inverse_of: :parent
@@ -23,7 +23,10 @@ module OnlineMigrations
23
23
  migration = find_migration
24
24
  if migration
25
25
  runner = MigrationRunner.new(migration)
26
- runner.run
26
+
27
+ try_with_lock do
28
+ runner.run
29
+ end
27
30
  end
28
31
  end
29
32
 
@@ -40,6 +43,11 @@ module OnlineMigrations
40
43
  end
41
44
  end
42
45
  end
46
+
47
+ def try_with_lock(&block)
48
+ lock = AdvisoryLock.new(name: "online_migrations_schema_scheduler")
49
+ lock.try_with_lock(&block)
50
+ end
43
51
  end
44
52
  end
45
53
  end
@@ -118,24 +118,19 @@ module OnlineMigrations
118
118
  type_cast_functions[column_name] = type_cast_function if type_cast_function
119
119
  tmp_column_name = conversions[column_name]
120
120
 
121
- if database_version >= 11_00_00
122
- if primary_key(table_name) == column_name.to_s && old_col.type == :integer
123
- # For PG < 11 and Primary Key conversions, setting a column as the PK
124
- # converts even check constraints to NOT NULL column constraints
125
- # and forces an inline re-verification of the whole table.
126
- # To avoid this, we instead set it to `NOT NULL DEFAULT 0` and we'll
127
- # copy the correct values when backfilling.
128
- add_column(table_name, tmp_column_name, new_type,
129
- **old_col_options, **column_options, default: old_col.default || 0, null: false)
130
- else
131
- if !old_col.default.nil?
132
- old_col_options = old_col_options.merge(default: old_col.default, null: old_col.null)
133
- end
134
- add_column(table_name, tmp_column_name, new_type, **old_col_options, **column_options)
135
- end
121
+ if primary_key(table_name) == column_name.to_s && old_col.type == :integer
122
+ # For PG < 11 and Primary Key conversions, setting a column as the PK
123
+ # converts even check constraints to NOT NULL column constraints
124
+ # and forces an inline re-verification of the whole table.
125
+ # To avoid this, we instead set it to `NOT NULL DEFAULT 0` and we'll
126
+ # copy the correct values when backfilling.
127
+ add_column(table_name, tmp_column_name, new_type,
128
+ **old_col_options, **column_options, default: old_col.default || 0, null: false)
136
129
  else
130
+ if !old_col.default.nil?
131
+ old_col_options = old_col_options.merge(default: old_col.default, null: old_col.null)
132
+ end
137
133
  add_column(table_name, tmp_column_name, new_type, **old_col_options, **column_options)
138
- change_column_default(table_name, tmp_column_name, old_col.default) if !old_col.default.nil?
139
134
  end
140
135
  end
141
136
 
@@ -264,7 +259,7 @@ module OnlineMigrations
264
259
 
265
260
  # At this point we are sure there are no NULLs in this column
266
261
  transaction do
267
- __set_not_null(table_name, tmp_column_name)
262
+ change_column_null(table_name, tmp_column_name, false)
268
263
  remove_not_null_constraint(table_name, tmp_column_name)
269
264
  end
270
265
  end
@@ -494,29 +489,6 @@ module OnlineMigrations
494
489
  end
495
490
  end
496
491
 
497
- def __set_not_null(table_name, column_name)
498
- # For PG >= 12 we can "promote" CHECK constraint to NOT NULL constraint:
499
- # https://github.com/postgres/postgres/commit/bbb96c3704c041d139181c6601e5bc770e045d26
500
- if database_version >= 12_00_00
501
- execute(<<~SQL)
502
- ALTER TABLE #{quote_table_name(table_name)}
503
- ALTER #{quote_column_name(column_name)}
504
- SET NOT NULL
505
- SQL
506
- else
507
- # For older versions we can set attribute as NOT NULL directly
508
- # through PG internal tables.
509
- # In-depth analysis of implications of this was made, so this approach
510
- # is considered safe - https://habr.com/ru/company/haulmont/blog/493954/ (in russian).
511
- execute(<<~SQL)
512
- UPDATE pg_catalog.pg_attribute
513
- SET attnotnull = true
514
- WHERE attrelid = #{quote(table_name)}::regclass
515
- AND attname = #{quote(column_name)}
516
- SQL
517
- end
518
- end
519
-
520
492
  def __rename_constraint(table_name, old_name, new_name)
521
493
  execute(<<~SQL)
522
494
  ALTER TABLE #{quote_table_name(table_name)}
@@ -59,7 +59,6 @@ module OnlineMigrations
59
59
  short_primary_key_type: "using-primary-key-with-short-integer-type",
60
60
  drop_table_multiple_foreign_keys: "removing-a-table-with-multiple-foreign-keys",
61
61
  rename_table: "renaming-a-table",
62
- add_column_with_default_null: "adding-a-column-with-a-default-value",
63
62
  add_column_with_default: "adding-a-column-with-a-default-value",
64
63
  add_column_generated_stored: "adding-a-stored-generated-column",
65
64
  add_column_json: "adding-a-json-column",
@@ -69,7 +68,6 @@ module OnlineMigrations
69
68
  change_column_null: "setting-not-null-on-an-existing-column",
70
69
  remove_column: "removing-a-column",
71
70
  add_timestamps_with_default: "adding-a-column-with-a-default-value",
72
- add_hash_index: "hash-indexes",
73
71
  add_reference: "adding-a-reference",
74
72
  add_index: "adding-an-index-non-concurrently",
75
73
  replace_index: "replacing-an-index",
@@ -89,8 +87,8 @@ module OnlineMigrations
89
87
  adapter = connection.adapter_name
90
88
  case adapter
91
89
  when /postg/i
92
- if postgresql_version < Gem::Version.new("9.6")
93
- raise "#{adapter} < 9.6 is not supported"
90
+ if postgresql_version < Gem::Version.new("12")
91
+ raise "#{adapter} < 12 is not supported"
94
92
  end
95
93
  else
96
94
  raise "#{adapter} is not supported"
@@ -101,9 +99,12 @@ module OnlineMigrations
101
99
 
102
100
  def set_statement_timeout
103
101
  if !defined?(@statement_timeout_set)
104
- if (statement_timeout = OnlineMigrations.config.statement_timeout)
105
- # TODO: inline this method call after deprecated `disable_statement_timeout` method removal.
106
- connection.__set_statement_timeout(statement_timeout)
102
+ if (timeout = OnlineMigrations.config.statement_timeout)
103
+ # use ceil to prevent no timeout for values under 1 ms
104
+ timeout = (timeout * 1000).ceil if !timeout.is_a?(String)
105
+
106
+ # Can't use `execute`, because command checker marks it as a dangerous operation.
107
+ connection.select_value("SET statement_timeout TO #{connection.quote(timeout)}")
107
108
  end
108
109
  @statement_timeout_set = true
109
110
  end
@@ -181,11 +182,7 @@ module OnlineMigrations
181
182
  # But I think this check is enough for now.
182
183
  raise_error :short_primary_key_type if short_primary_key_type?(options)
183
184
 
184
- if block
185
- collect_foreign_keys(&block)
186
- check_for_hash_indexes(&block) if postgresql_version < Gem::Version.new("10")
187
- end
188
-
185
+ collect_foreign_keys(&block) if block
189
186
  @new_tables << table_name.to_s
190
187
  end
191
188
 
@@ -230,18 +227,11 @@ module OnlineMigrations
230
227
  @new_columns << [table_name.to_s, column_name.to_s]
231
228
 
232
229
  if !new_or_small_table?(table_name)
233
- if options.key?(:default) &&
234
- (postgresql_version < Gem::Version.new("11") || (!default.nil? && (volatile_default = Utils.volatile_default?(connection, type, default))))
235
-
236
- if default.nil?
237
- raise_error :add_column_with_default_null,
238
- code: command_str(:add_column, table_name, column_name, type, options.except(:default))
239
- else
240
- raise_error :add_column_with_default,
241
- code: command_str(:add_column_with_default, table_name, column_name, type, options),
242
- not_null: options[:null] == false,
243
- volatile_default: volatile_default
244
- end
230
+ if options.key?(:default) && !default.nil? && (volatile_default = Utils.volatile_default?(connection, type, default))
231
+ raise_error :add_column_with_default,
232
+ code: command_str(:add_column_with_default, table_name, column_name, type, options),
233
+ not_null: options[:null] == false,
234
+ volatile_default: volatile_default
245
235
  end
246
236
 
247
237
  if type == :virtual && options[:stored]
@@ -280,10 +270,7 @@ module OnlineMigrations
280
270
  table_name: table_name,
281
271
  column_name: column_name,
282
272
  new_column: new_column,
283
- model: table_name.to_s.classify,
284
- partial_writes: Utils.ar_partial_writes?,
285
- partial_writes_setting: Utils.ar_partial_writes_setting,
286
- enumerate_columns_in_select_statements: Utils.ar_enumerate_columns_in_select_statements
273
+ model: table_name.to_s.classify
287
274
  end
288
275
  end
289
276
 
@@ -360,9 +347,7 @@ module OnlineMigrations
360
347
 
361
348
  [:datetime, :timestamp, :timestamptz].include?(existing_type) &&
362
349
  precision >= existing_precision &&
363
- (type == existing_type ||
364
- (postgresql_version >= Gem::Version.new("12") &&
365
- connection.select_value("SHOW timezone") == "UTC"))
350
+ (type == existing_type || connection.select_value("SHOW timezone") == "UTC")
366
351
  when :interval
367
352
  precision = options[:precision] || options[:limit] || 6
368
353
  existing_precision = existing_column.precision || existing_column.limit || 6
@@ -394,22 +379,18 @@ module OnlineMigrations
394
379
  end
395
380
 
396
381
  def change_column_default(table_name, column_name, _default_or_changes)
397
- if Utils.ar_partial_writes? && !new_column?(table_name, column_name)
398
- raise_error :change_column_default,
399
- config: Utils.ar_partial_writes_setting
382
+ if ActiveRecord::Base.partial_inserts && !new_column?(table_name, column_name)
383
+ raise_error :change_column_default
400
384
  end
401
385
  end
402
386
 
403
387
  def change_column_null(table_name, column_name, allow_null, default = nil, **)
404
388
  if !allow_null && !new_or_small_table?(table_name)
405
- safe = false
406
389
  # In PostgreSQL 12+ you can add a check constraint to the table
407
390
  # and then "promote" it to NOT NULL for the column.
408
- if postgresql_version >= Gem::Version.new("12")
409
- safe = check_constraints(table_name).any? do |c|
410
- c["def"] == "CHECK ((#{column_name} IS NOT NULL))" ||
411
- c["def"] == "CHECK ((#{connection.quote_column_name(column_name)} IS NOT NULL))"
412
- end
391
+ safe = check_constraints(table_name).any? do |c|
392
+ c["def"] == "CHECK ((#{column_name} IS NOT NULL))" ||
393
+ c["def"] == "CHECK ((#{connection.quote_column_name(column_name)} IS NOT NULL))"
413
394
  end
414
395
 
415
396
  if !safe
@@ -417,17 +398,13 @@ module OnlineMigrations
417
398
  vars = {
418
399
  add_constraint_code: command_str(:add_not_null_constraint, table_name, column_name, name: constraint_name, validate: false),
419
400
  validate_constraint_code: command_str(:validate_not_null_constraint, table_name, column_name, name: constraint_name),
420
- remove_constraint_code: nil,
421
401
  table_name: table_name,
422
402
  column_name: column_name,
423
403
  default: default,
404
+ remove_constraint_code: command_str(:remove_check_constraint, table_name, name: constraint_name),
405
+ change_column_null_code: command_str(:change_column_null, table_name, column_name, false),
424
406
  }
425
407
 
426
- if postgresql_version >= Gem::Version.new("12")
427
- vars[:remove_constraint_code] = command_str(:remove_check_constraint, table_name, name: constraint_name)
428
- vars[:change_column_null_code] = command_str(:change_column_null, table_name, column_name, false)
429
- end
430
-
431
408
  raise_error :change_column_null, **vars
432
409
  end
433
410
  end
@@ -470,15 +447,13 @@ module OnlineMigrations
470
447
  @new_columns << [table_name.to_s, "created_at"]
471
448
  @new_columns << [table_name.to_s, "updated_at"]
472
449
 
473
- volatile_default = false
474
450
  if !new_or_small_table?(table_name) && !options[:default].nil? &&
475
- (postgresql_version < Gem::Version.new("11") || (volatile_default = Utils.volatile_default?(connection, :datetime, options[:default])))
451
+ Utils.volatile_default?(connection, :datetime, options[:default])
476
452
 
477
453
  raise_error :add_timestamps_with_default,
478
454
  code: [command_str(:add_column_with_default, table_name, :created_at, :datetime, options),
479
455
  command_str(:add_column_with_default, table_name, :updated_at, :datetime, options)].join("\n "),
480
- not_null: options[:null] == false,
481
- volatile_default: volatile_default
456
+ not_null: options[:null] == false
482
457
  end
483
458
  end
484
459
 
@@ -486,10 +461,6 @@ module OnlineMigrations
486
461
  # Always added by default in 5.0+
487
462
  index = options.fetch(:index, true)
488
463
 
489
- if index.is_a?(Hash) && index[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
490
- raise_error :add_hash_index
491
- end
492
-
493
464
  concurrently_set = index.is_a?(Hash) && index[:algorithm] == :concurrently
494
465
  bad_index = index && !concurrently_set
495
466
 
@@ -522,13 +493,6 @@ module OnlineMigrations
522
493
  alias add_belongs_to add_reference
523
494
 
524
495
  def add_reference_concurrently(table_name, ref_name, **options)
525
- # Always added by default in 5.0+
526
- index = options.fetch(:index, true)
527
-
528
- if index.is_a?(Hash) && index[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
529
- raise_error :add_hash_index
530
- end
531
-
532
496
  foreign_key = options.fetch(:foreign_key, false)
533
497
 
534
498
  if foreign_key
@@ -546,10 +510,6 @@ module OnlineMigrations
546
510
  end
547
511
 
548
512
  def add_index(table_name, column_name, **options)
549
- if options[:using].to_s == "hash" && postgresql_version < Gem::Version.new("10")
550
- raise_error :add_hash_index
551
- end
552
-
553
513
  if !new_or_small_table?(table_name)
554
514
  if options[:algorithm] != :concurrently
555
515
  raise_error :add_index,
@@ -702,19 +662,6 @@ module OnlineMigrations
702
662
  @foreign_key_tables |= collector.referenced_tables
703
663
  end
704
664
 
705
- def check_for_hash_indexes(&block)
706
- indexes = collect_indexes(&block)
707
- if indexes.any? { |index| index.using == "hash" }
708
- raise_error :add_hash_index
709
- end
710
- end
711
-
712
- def collect_indexes(&block)
713
- collector = IndexesCollector.new
714
- collector.collect(&block)
715
- collector.indexes
716
- end
717
-
718
665
  def new_or_small_table?(table_name)
719
666
  new_table?(table_name) || small_table?(table_name)
720
667
  end
@@ -736,7 +683,7 @@ module OnlineMigrations
736
683
  if Utils.developer_env? && (target_version = OnlineMigrations.config.target_version)
737
684
  target_version.to_s
738
685
  else
739
- database_version = connection.select_value("SHOW server_version_num").to_i
686
+ database_version = connection.database_version
740
687
  major = database_version / 10000
741
688
  if database_version >= 100000
742
689
  minor = database_version % 10000
@@ -128,12 +128,12 @@ migration_helpers provides a safer approach to do this:
128
128
  <%= column_name.to_s.inspect %> => <%= new_column.to_s.inspect %>
129
129
  }
130
130
  }
131
- <% unless partial_writes %>
131
+ <% unless ActiveRecord::Base.partial_inserts %>
132
132
 
133
133
  NOTE: You also need to temporarily enable partial writes (is disabled by default in Active Record >= 7)
134
134
  until the process of column rename is fully done.
135
135
  # config/application.rb
136
- config.active_record.<%= partial_writes_setting %> = true
136
+ config.active_record.partial_inserts = true
137
137
  <% end %>
138
138
 
139
139
  2. Deploy
@@ -148,7 +148,7 @@ It will use a combination of a VIEW and column aliasing to work with both column
148
148
  end
149
149
 
150
150
  4. Replace usages of the old column with a new column in the codebase
151
- <% if enumerate_columns_in_select_statements %>
151
+ <% if ActiveRecord::Base.enumerate_columns_in_select_statements %>
152
152
  5. Ignore old column
153
153
 
154
154
  self.ignored_columns += [:<%= column_name %>]
@@ -247,7 +247,7 @@ during writes works automatically). For most column type changes, this does not
247
247
  to be inserted when changing the default value of a column.
248
248
  Disable partial writes in config/application.rb:
249
249
 
250
- config.active_record.<%= config %> = false",
250
+ config.active_record.partial_inserts = false",
251
251
 
252
252
  change_column_null:
253
253
  "Setting NOT NULL on an existing column blocks reads and writes while every row is checked.
@@ -317,7 +317,7 @@ A safer approach is to:
317
317
  <% end %>",
318
318
 
319
319
  add_timestamps_with_default:
320
- "Adding timestamps columns with non-null defaults blocks reads and writes while the entire table is rewritten.
320
+ "Adding timestamp columns with volatile defaults blocks reads and writes while the entire table is rewritten.
321
321
 
322
322
  A safer approach is to, for both timestamps columns:
323
323
  1. add the column without a default value
@@ -327,7 +327,6 @@ A safer approach is to, for both timestamps columns:
327
327
  4. add the NOT NULL constraint
328
328
  <% end %>
329
329
 
330
- <% unless volatile_default %>
331
330
  add_column_with_default takes care of all this steps:
332
331
 
333
332
  class <%= migration_name %> < <%= migration_parent %>
@@ -336,8 +335,7 @@ class <%= migration_name %> < <%= migration_parent %>
336
335
  def change
337
336
  <%= code %>
338
337
  end
339
- end
340
- <% end %>",
338
+ end",
341
339
 
342
340
  add_reference:
343
341
  "<% if bad_foreign_key %>
@@ -356,13 +354,6 @@ class <%= migration_name %> < <%= migration_parent %>
356
354
  end
357
355
  end",
358
356
 
359
- add_hash_index:
360
- "Hash index operations are not WAL-logged, so hash indexes might need to be rebuilt with REINDEX
361
- after a database crash if there were unwritten changes. Also, changes to hash indexes are not replicated
362
- over streaming or file-based replication after the initial base backup, so they give wrong answers
363
- to queries that subsequently use them. For these reasons, hash index use is discouraged.
364
- Use B-tree indexes instead.",
365
-
366
357
  add_index:
367
358
  "Adding an index non-concurrently blocks writes. Instead, use:
368
359
 
@@ -12,7 +12,7 @@ module OnlineMigrations
12
12
  # @param column_name [String, Symbol]
13
13
  # @param value value for the column. It is typically a literal. To perform a computed
14
14
  # update, an Arel literal can be used instead
15
- # @option options [Integer] :batch_size (1000) size of the batch
15
+ # @option options [Integer] :batch_size (1_000) size of the batch
16
16
  # @option options [String, Symbol] :batch_column_name (primary key) option is for tables without primary key, in this
17
17
  # case another unique integer column can be used. Example: `:user_id`
18
18
  # @option options [Proc, Boolean] :progress (false) whether to show progress while running.
@@ -439,9 +439,7 @@ module OnlineMigrations
439
439
  def add_column_with_default(table_name, column_name, type, **options)
440
440
  default = options.fetch(:default)
441
441
 
442
- if database_version >= 11_00_00 && !Utils.volatile_default?(self, type, default)
443
- add_column(table_name, column_name, type, **options)
444
- else
442
+ if Utils.volatile_default?(self, type, default)
445
443
  __ensure_not_in_transaction!
446
444
 
447
445
  batch_options = options.extract!(:batch_size, :batch_column_name, :progress, :pause_ms)
@@ -465,12 +463,12 @@ module OnlineMigrations
465
463
  add_not_null_constraint(table_name, column_name, validate: false)
466
464
  validate_not_null_constraint(table_name, column_name)
467
465
 
468
- if database_version >= 12_00_00
469
- # In PostgreSQL 12+ it is safe to "promote" a CHECK constraint to `NOT NULL` for the column
470
- change_column_null(table_name, column_name, false)
471
- remove_not_null_constraint(table_name, column_name)
472
- end
466
+ # In PostgreSQL 12+ it is safe to "promote" a CHECK constraint to `NOT NULL` for the column
467
+ change_column_null(table_name, column_name, false)
468
+ remove_not_null_constraint(table_name, column_name)
473
469
  end
470
+ else
471
+ add_column(table_name, column_name, type, **options)
474
472
  end
475
473
  end
476
474
 
@@ -729,23 +727,10 @@ module OnlineMigrations
729
727
  end
730
728
  end
731
729
 
732
- if OnlineMigrations.config.statement_timeout
733
- # "CREATE INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
734
- # It only conflicts with constraint validations, creating/removing indexes,
735
- # and some other "ALTER TABLE"s.
736
- super
737
- else
738
- OnlineMigrations.deprecator.warn(<<~MSG)
739
- Running `add_index` without a statement timeout is deprecated.
740
- Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
741
- or the default database statement timeout will be used.
742
- Example, `config.statement_timeout = 1.hour`.
743
- MSG
744
-
745
- disable_statement_timeout do
746
- super
747
- end
748
- end
730
+ # "CREATE INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
731
+ # It only conflicts with constraint validations, creating/removing indexes,
732
+ # and some other "ALTER TABLE"s.
733
+ super
749
734
  end
750
735
 
751
736
  # Extends default method to be idempotent.
@@ -770,23 +755,10 @@ module OnlineMigrations
770
755
  end
771
756
 
772
757
  if index_exists
773
- if OnlineMigrations.config.statement_timeout
774
- # "DROP INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
775
- # It only conflicts with constraint validations, other creating/removing indexes,
776
- # and some "ALTER TABLE"s.
777
- super
778
- else
779
- OnlineMigrations.deprecator.warn(<<~MSG)
780
- Running `remove_index` without a statement timeout is deprecated.
781
- Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
782
- or the default database statement timeout will be used.
783
- Example, `config.statement_timeout = 1.hour`.
784
- MSG
785
-
786
- disable_statement_timeout do
787
- super
788
- end
789
- end
758
+ # "DROP INDEX CONCURRENTLY" requires a "SHARE UPDATE EXCLUSIVE" lock.
759
+ # It only conflicts with constraint validations, other creating/removing indexes,
760
+ # and some "ALTER TABLE"s.
761
+ super
790
762
  else
791
763
  Utils.say("Index was not removed because it does not exist.")
792
764
  end
@@ -835,23 +807,10 @@ module OnlineMigrations
835
807
  # Skip costly operation if already validated.
836
808
  return if foreign_key.validated?
837
809
 
838
- if OnlineMigrations.config.statement_timeout
839
- # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
840
- # It only conflicts with other validations, creating/removing indexes,
841
- # and some other "ALTER TABLE"s.
842
- super
843
- else
844
- OnlineMigrations.deprecator.warn(<<~MSG)
845
- Running `validate_foreign_key` without a statement timeout is deprecated.
846
- Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
847
- or the default database statement timeout will be used.
848
- Example, `config.statement_timeout = 1.hour`.
849
- MSG
850
-
851
- disable_statement_timeout do
852
- super
853
- end
854
- end
810
+ # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
811
+ # It only conflicts with other validations, creating/removing indexes,
812
+ # and some other "ALTER TABLE"s.
813
+ super
855
814
  end
856
815
 
857
816
  # Extends default method to be idempotent.
@@ -894,23 +853,10 @@ module OnlineMigrations
894
853
  # Skip costly operation if already validated.
895
854
  return if check_constraint.validated?
896
855
 
897
- if OnlineMigrations.config.statement_timeout
898
- # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
899
- # It only conflicts with other validations, creating/removing indexes,
900
- # and some other "ALTER TABLE"s.
901
- super
902
- else
903
- OnlineMigrations.deprecator.warn(<<~MSG)
904
- Running `validate_check_constraint` without a statement timeout is deprecated.
905
- Configure an explicit statement timeout in the initializer file via `config.statement_timeout`
906
- or the default database statement timeout will be used.
907
- Example, `config.statement_timeout = 1.hour`.
908
- MSG
909
-
910
- disable_statement_timeout do
911
- super
912
- end
913
- end
856
+ # "VALIDATE CONSTRAINT" requires a "SHARE UPDATE EXCLUSIVE" lock.
857
+ # It only conflicts with other validations, creating/removing indexes,
858
+ # and some other "ALTER TABLE"s.
859
+ super
914
860
  end
915
861
 
916
862
  # Extends default method to be idempotent
@@ -965,28 +911,6 @@ module OnlineMigrations
965
911
  end
966
912
  end
967
913
 
968
- # @private
969
- def disable_statement_timeout
970
- OnlineMigrations.deprecator.warn(<<~MSG)
971
- `disable_statement_timeout` is deprecated and will be removed. Configure an explicit
972
- statement timeout in the initializer file via `config.statement_timeout` or the default
973
- database statement timeout will be used. Example, `config.statement_timeout = 1.hour`.
974
- MSG
975
-
976
- prev_value = select_value("SHOW statement_timeout")
977
- __set_statement_timeout(0)
978
- yield
979
- ensure
980
- __set_statement_timeout(prev_value)
981
- end
982
-
983
- # @private
984
- def __set_statement_timeout(timeout)
985
- # use ceil to prevent no timeout for values under 1 ms
986
- timeout = (timeout.to_f * 1000).ceil if !timeout.is_a?(String)
987
- execute("SET statement_timeout TO #{quote(timeout)}")
988
- end
989
-
990
914
  # @private
991
915
  # Executes the block with a retry mechanism that alters the `lock_timeout`
992
916
  # and sleep time between attempts.
@@ -86,26 +86,6 @@ module OnlineMigrations
86
86
  "#{short_name}#{hashed_identifier}"
87
87
  end
88
88
 
89
- def ar_partial_writes?
90
- ActiveRecord::Base.public_send(ar_partial_writes_setting)
91
- end
92
-
93
- def ar_partial_writes_setting
94
- if Utils.ar_version >= 7.0
95
- "partial_inserts"
96
- else
97
- "partial_writes"
98
- end
99
- end
100
-
101
- def ar_enumerate_columns_in_select_statements
102
- if ar_version >= 7
103
- ActiveRecord::Base.enumerate_columns_in_select_statements
104
- else
105
- false
106
- end
107
- end
108
-
109
89
  # Returns estimated rows count for a table.
110
90
  # https://www.citusdata.com/blog/2016/10/12/count-performance/
111
91
  def estimated_count(connection, table_name)
@@ -6,7 +6,7 @@ module OnlineMigrations
6
6
  class << self
7
7
  def enable
8
8
  @activerecord_logger_was = ActiveRecord::Base.logger
9
- @verbose_query_logs_was = verbose_query_logs
9
+ @verbose_query_logs_was = ActiveRecord.verbose_query_logs
10
10
  return if @activerecord_logger_was.nil?
11
11
 
12
12
  stdout_logger = ActiveSupport::Logger.new($stdout)
@@ -23,30 +23,13 @@ module OnlineMigrations
23
23
  end
24
24
 
25
25
  ActiveRecord::Base.logger = combined_logger
26
- set_verbose_query_logs(false)
26
+ ActiveRecord.verbose_query_logs = false
27
27
  end
28
28
 
29
29
  def disable
30
30
  ActiveRecord::Base.logger = @activerecord_logger_was
31
- set_verbose_query_logs(@verbose_query_logs_was)
31
+ ActiveRecord.verbose_query_logs = @verbose_query_logs_was
32
32
  end
33
-
34
- private
35
- def verbose_query_logs
36
- if Utils.ar_version >= 7.0
37
- ActiveRecord.verbose_query_logs
38
- else
39
- ActiveRecord::Base.verbose_query_logs
40
- end
41
- end
42
-
43
- def set_verbose_query_logs(value) # rubocop:disable Naming/AccessorMethodName
44
- if Utils.ar_version >= 7.0
45
- ActiveRecord.verbose_query_logs = value
46
- else
47
- ActiveRecord::Base.verbose_query_logs = value
48
- end
49
- end
50
33
  end
51
34
  end
52
35
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OnlineMigrations
4
- VERSION = "0.22.0"
4
+ VERSION = "0.23.0"
5
5
  end
@@ -23,12 +23,12 @@ module OnlineMigrations
23
23
 
24
24
  extend ActiveSupport::Autoload
25
25
 
26
+ autoload :AdvisoryLock
26
27
  autoload :ApplicationRecord
27
28
  autoload :BatchIterator
28
29
  autoload :VerboseSqlLogs
29
30
  autoload :ForeignKeysCollector
30
31
  autoload :IndexDefinition
31
- autoload :IndexesCollector
32
32
  autoload :CommandChecker
33
33
  autoload :BackgroundMigration
34
34
 
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.22.0
4
+ version: 0.23.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - fatkodima
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-03 00:00:00.000000000 Z
11
+ date: 2025-01-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '6.1'
19
+ version: '7.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '6.1'
26
+ version: '7.0'
27
27
  description:
28
28
  email:
29
29
  - fatkodima123@gmail.com
@@ -48,6 +48,7 @@ files:
48
48
  - lib/generators/online_migrations/templates/migration.rb.tt
49
49
  - lib/generators/online_migrations/upgrade_generator.rb
50
50
  - lib/online_migrations.rb
51
+ - lib/online_migrations/advisory_lock.rb
51
52
  - lib/online_migrations/application_record.rb
52
53
  - lib/online_migrations/background_migration.rb
53
54
  - lib/online_migrations/background_migrations/backfill_column.rb
@@ -82,7 +83,6 @@ files:
82
83
  - lib/online_migrations/error_messages.rb
83
84
  - lib/online_migrations/foreign_keys_collector.rb
84
85
  - lib/online_migrations/index_definition.rb
85
- - lib/online_migrations/indexes_collector.rb
86
86
  - lib/online_migrations/lock_retrier.rb
87
87
  - lib/online_migrations/migration.rb
88
88
  - lib/online_migrations/migrator.rb
@@ -107,7 +107,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '2.7'
110
+ version: '3.0'
111
111
  required_rubygems_version: !ruby/object:Gem::Requirement
112
112
  requirements:
113
113
  - - ">="
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module OnlineMigrations
4
- # @private
5
- class IndexesCollector
6
- COLUMN_TYPES = [:bigint, :binary, :boolean, :date, :datetime, :decimal,
7
- :float, :integer, :json, :string, :text, :time, :timestamp, :virtual]
8
-
9
- attr_reader :indexes
10
-
11
- def initialize
12
- @indexes = []
13
- end
14
-
15
- def collect
16
- yield self
17
- end
18
-
19
- def index(_column_name, **options)
20
- @indexes << IndexDefinition.new(using: options[:using].to_s)
21
- end
22
-
23
- def references(*_ref_names, **options)
24
- index = options.fetch(:index, true)
25
-
26
- if index
27
- using = index.is_a?(Hash) ? index[:using].to_s : nil
28
- @indexes << IndexDefinition.new(using: using)
29
- end
30
- end
31
- alias belongs_to references
32
-
33
- def method_missing(method_name, *_args, **options)
34
- # Check for type-based methods, where we can also specify an index:
35
- # t.string :email, index: true
36
- if COLUMN_TYPES.include?(method_name)
37
- index = options.fetch(:index, false)
38
-
39
- if index
40
- using = index.is_a?(Hash) ? index[:using].to_s : nil
41
- @indexes << IndexDefinition.new(using: using)
42
- end
43
- end
44
- end
45
- end
46
- end