online_migrations 0.21.0 → 0.23.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/README.md +4 -4
- data/lib/generators/online_migrations/templates/initializer.rb.tt +17 -15
- data/lib/online_migrations/advisory_lock.rb +60 -0
- data/lib/online_migrations/background_migrations/config.rb +4 -36
- data/lib/online_migrations/background_migrations/migration.rb +13 -6
- data/lib/online_migrations/background_migrations/migration_helpers.rb +2 -2
- data/lib/online_migrations/background_migrations/migration_job.rb +1 -6
- data/lib/online_migrations/background_migrations/scheduler.rb +13 -8
- data/lib/online_migrations/background_schema_migrations/migration.rb +13 -6
- data/lib/online_migrations/background_schema_migrations/scheduler.rb +9 -1
- data/lib/online_migrations/change_column_type_helpers.rb +12 -40
- data/lib/online_migrations/command_checker.rb +26 -79
- data/lib/online_migrations/error_messages.rb +6 -15
- data/lib/online_migrations/schema_statements.rb +23 -99
- data/lib/online_migrations/utils.rb +0 -20
- data/lib/online_migrations/verbose_sql_logs.rb +3 -20
- data/lib/online_migrations/version.rb +1 -1
- data/lib/online_migrations.rb +1 -1
- metadata +6 -6
- data/lib/online_migrations/indexes_collector.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1052d2cc58898bc561b5e755daccc137d7d6e1d35afa3af5968c2b655ebe5805
|
4
|
+
data.tar.gz: ad72d601c36ea9192f192fb8941d30a2cfbcf0ed87e0717fb5d4ebf47b20f169
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 697ac61bcb41e00744cfa87965910a3e1d68eaa571ad5c6dc3b3d8a4d14deb16ae0cbc0d9771d8d57668be8529f200736a14c1769d7d8cf6c64fc083060ff74c
|
7
|
+
data.tar.gz: 7b4e383f7628ba5cc74c2bb86f4460890b70f3052738f0f20db3571ee770e22c49bc548f5c0ef3fd47caa456a84eb4174f031742869612ee3b27505faf49ecec
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
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
|
+
|
15
|
+
## 0.22.0 (2025-01-03)
|
16
|
+
|
17
|
+
- Make background data migrations scheduler run a single migration at a time
|
18
|
+
|
19
|
+
This is similar to background schema migrations scheduler's behavior.
|
20
|
+
Running multiple migrations at a time can cause unneeded database load and other bad effects.
|
21
|
+
|
22
|
+
- Add `#pausable?`, `#can_be_cancelled?` and `#can_be_paused?` helpers to background migrations
|
23
|
+
|
3
24
|
## 0.21.0 (2024-12-09)
|
4
25
|
|
5
26
|
- Fix `add_foreign_key` when referencing same table via different columns
|
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
|
20
|
-
- Rails
|
21
|
-
- PostgreSQL
|
19
|
+
- Ruby 3.0+
|
20
|
+
- Rails 7.0+
|
21
|
+
- PostgreSQL 12+
|
22
22
|
|
23
|
-
For older Ruby and Rails versions you can use
|
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
|
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 =
|
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
|
-
|
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
|
-
|
33
|
+
config.auto_analyze = false
|
32
34
|
|
33
35
|
# Alphabetize table columns when dumping the schema.
|
34
|
-
|
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
|
-
|
97
|
+
config.background_migrations.migrations_path = "lib"
|
96
98
|
|
97
99
|
# The module in which background migrations will be placed.
|
98
|
-
|
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
|
-
|
103
|
+
config.background_migrations.batch_size = 1_000
|
102
104
|
|
103
105
|
# The smaller batches size that the batches will be divided into.
|
104
|
-
|
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
|
-
|
109
|
+
config.background_migrations.batch_pause = 0.seconds
|
108
110
|
|
109
111
|
# The number of milliseconds to sleep between each sub_batch execution.
|
110
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
130
|
+
config.background_schema_migrations.max_attempts = 5
|
129
131
|
|
130
132
|
# Statement timeout value used when running background schema migration.
|
131
|
-
|
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
|
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
|
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 =
|
89
|
-
@sub_batch_size =
|
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
|
-
|
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
|
@@ -110,6 +105,18 @@ module OnlineMigrations
|
|
110
105
|
end
|
111
106
|
alias cancel cancelled!
|
112
107
|
|
108
|
+
def pausable?
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def can_be_paused?
|
113
|
+
enqueued? || running?
|
114
|
+
end
|
115
|
+
|
116
|
+
def can_be_cancelled?
|
117
|
+
!succeeded? && !cancelled?
|
118
|
+
end
|
119
|
+
|
113
120
|
def last_job
|
114
121
|
migration_jobs.order(:max_value).last
|
115
122
|
end
|
@@ -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 (
|
339
|
-
# @option options [Integer] :sub_batch_size (
|
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
|
-
|
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
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module OnlineMigrations
|
4
4
|
module BackgroundMigrations
|
5
5
|
# Class responsible for scheduling background migrations.
|
6
|
-
#
|
6
|
+
#
|
7
|
+
# It selects a single runnable background migration and runs it one step (one batch) at a time.
|
7
8
|
# A migration is considered runnable if it is not completed and the time interval between
|
8
|
-
#
|
9
|
+
# successive runs has passed.
|
9
10
|
#
|
10
11
|
# Scheduler should be configured to run periodically, for example, via cron.
|
11
12
|
# @example Run via whenever
|
@@ -22,17 +23,21 @@ module OnlineMigrations
|
|
22
23
|
# Runs Scheduler
|
23
24
|
def run
|
24
25
|
active_migrations = Migration.runnable.active.queue_order
|
25
|
-
|
26
|
+
runnable_migration = active_migrations.select(&:interval_elapsed?).first
|
27
|
+
|
28
|
+
if runnable_migration
|
29
|
+
runner = MigrationRunner.new(runnable_migration)
|
26
30
|
|
27
|
-
|
28
|
-
|
31
|
+
try_with_lock do
|
32
|
+
runner.run_migration_job
|
33
|
+
end
|
29
34
|
end
|
30
35
|
end
|
31
36
|
|
32
37
|
private
|
33
|
-
def
|
34
|
-
|
35
|
-
|
38
|
+
def try_with_lock(&block)
|
39
|
+
lock = AdvisoryLock.new(name: "online_migrations_data_scheduler")
|
40
|
+
lock.try_with_lock(&block)
|
36
41
|
end
|
37
42
|
end
|
38
43
|
end
|
@@ -49,12 +49,7 @@ module OnlineMigrations
|
|
49
49
|
|
50
50
|
alias_attribute :name, :migration_name
|
51
51
|
|
52
|
-
|
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
|
@@ -94,6 +89,18 @@ module OnlineMigrations
|
|
94
89
|
end
|
95
90
|
alias cancel cancelled!
|
96
91
|
|
92
|
+
def pausable?
|
93
|
+
false
|
94
|
+
end
|
95
|
+
|
96
|
+
def can_be_paused?
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def can_be_cancelled?
|
101
|
+
!succeeded? && !cancelled?
|
102
|
+
end
|
103
|
+
|
97
104
|
# Returns the progress of the background schema migration.
|
98
105
|
#
|
99
106
|
# @return [Float] value in range from 0.0 to 100.0
|
@@ -23,7 +23,10 @@ module OnlineMigrations
|
|
23
23
|
migration = find_migration
|
24
24
|
if migration
|
25
25
|
runner = MigrationRunner.new(migration)
|
26
|
-
|
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
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
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
|
-
|
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("
|
93
|
-
raise "#{adapter} <
|
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 (
|
105
|
-
#
|
106
|
-
|
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
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
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
|
-
|
409
|
-
|
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
|
-
|
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.
|
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
|
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
|
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
|
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
|
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 (
|
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
|
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
|
-
|
469
|
-
|
470
|
-
|
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
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
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
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
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
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
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
|
-
|
898
|
-
|
899
|
-
|
900
|
-
|
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
|
-
|
26
|
+
ActiveRecord.verbose_query_logs = false
|
27
27
|
end
|
28
28
|
|
29
29
|
def disable
|
30
30
|
ActiveRecord::Base.logger = @activerecord_logger_was
|
31
|
-
|
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
|
data/lib/online_migrations.rb
CHANGED
@@ -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.
|
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:
|
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: '
|
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: '
|
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: '
|
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
|