safe-pg-migrations 1.2.3 → 1.4.1
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 +4 -4
- data/README.md +10 -7
- data/lib/safe-pg-migrations/base.rb +18 -5
- data/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb +3 -1
- data/lib/safe-pg-migrations/plugins/idempotent_statements.rb +92 -0
- data/lib/safe-pg-migrations/plugins/legacy_active_record_support.rb +36 -0
- data/lib/safe-pg-migrations/plugins/statement_insurer.rb +23 -26
- data/lib/safe-pg-migrations/plugins/statement_retrier.rb +1 -0
- data/lib/safe-pg-migrations/plugins/useless_statements_logger.rb +12 -7
- data/lib/safe-pg-migrations/version.rb +1 -1
- metadata +23 -111
- data/lib/safe-pg-migrations/plugins/idem_potent_statements.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d05d2e6c8647af8e618652235043dfde1b104065e71113f9f55c25117dd6ab6
|
4
|
+
data.tar.gz: 1f67d82d62855e6566f39c92b3cfdf4e9d6f68cc2bd0b9105c3e61ba32fd8ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: daeed81cfc524ad80427179930e86b9ba71d4743e390dc8c5ce805d01ea3fdbb9866c30707f76d6b7578c9370b9761564b784813f165c52e1512afdffd9d74ed
|
7
|
+
data.tar.gz: cb4346bcaf47707bb811cefcace498683f63a7854d80c47487dac462b1e79ec62ee702a75fd18985b114a0ab4b69a8863274eb2af86517f166f50795dff3cc55
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
ActiveRecord migrations for Postgres made safe.
|
4
4
|
|
5
|
+

|
6
|
+
|
5
7
|
## Requirements
|
6
8
|
|
7
9
|
- Ruby 2.5+
|
@@ -91,7 +93,7 @@ When **Safe PG Migrations** is used, migrations are not wrapped in a transaction
|
|
91
93
|
- In order to be able to retry statements that have failed because of a lock timeout, we have to be outside a transaction.
|
92
94
|
- In order to add an index concurrently, we have to be outside a transaction.
|
93
95
|
|
94
|
-
Note that if a migration fails, it won't be
|
96
|
+
Note that if a migration fails, it won't be rolled back. This can result in migrations being partially applied. In that case, they need to be manually reverted.
|
95
97
|
|
96
98
|
</details>
|
97
99
|
|
@@ -108,7 +110,7 @@ PG will still needs to update every row of the table, and will most likely state
|
|
108
110
|
|
109
111
|
<blockquote>
|
110
112
|
|
111
|
-
**Note: Pre-
|
113
|
+
**Note: Pre-postgres 11**
|
112
114
|
Adding a column with a default value and a not-null constraint is [dangerous](https://wework.github.io/data/2015/11/05/add-columns-with-default-values-to-large-tables-in-rails-postgres/).
|
113
115
|
|
114
116
|
**Safe PG Migrations** makes it safe by:
|
@@ -120,7 +122,7 @@ Adding a column with a default value and a not-null constraint is [dangerous](ht
|
|
120
122
|
|
121
123
|
Note: the addition of the not null constraint may timeout. In that case, you may want to add the not-null constraint as initially not valid and validate it in a separate statement. See [Adding a not-null constraint on Postgres with minimal locking](https://medium.com/doctolib-engineering/adding-a-not-null-constraint-on-pg-faster-with-minimal-locking-38b2c00c4d1c).
|
122
124
|
|
123
|
-
</blockquote>
|
125
|
+
</blockquote>
|
124
126
|
|
125
127
|
</details>
|
126
128
|
|
@@ -137,7 +139,7 @@ If you still get lock timeout while adding / removing indexes, it might be for o
|
|
137
139
|
|
138
140
|
</details>
|
139
141
|
|
140
|
-
<details><summary id="safe_add_foreign_key">
|
142
|
+
<details><summary id="safe_add_foreign_key">Safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
|
141
143
|
|
142
144
|
Adding a foreign key requires a `SHARE ROW EXCLUSIVE` lock, which **prevent writing in the tables** while the migration is running.
|
143
145
|
|
@@ -150,7 +152,7 @@ Adding the constraint itself is rather fast, the major part of the time is spent
|
|
150
152
|
|
151
153
|
<details><summary>Retry after lock timeout</summary>
|
152
154
|
|
153
|
-
When a statement fails with a lock timeout, **Safe PG Migrations** retries it (5 times max) [list of
|
155
|
+
When a statement fails with a lock timeout, **Safe PG Migrations** retries it (5 times max) [list of retriable statements](https://github.com/doctolib/safe-pg-migrations/blob/66933256252b6bbf12e404b829a361dbba30e684/lib/safe-pg-migrations/plugins/statement_retrier.rb#L5)
|
154
156
|
</details>
|
155
157
|
|
156
158
|
<details><summary>Blocking activity logging</summary>
|
@@ -219,11 +221,11 @@ SafePgMigrations.config.retry_delay = 1.minute # Delay between retries for retry
|
|
219
221
|
SafePgMigrations.config.max_tries = 5 # Number of retries before abortion of the migration
|
220
222
|
```
|
221
223
|
|
222
|
-
##
|
224
|
+
## Running tests
|
223
225
|
|
224
226
|
```bash
|
225
227
|
bundle
|
226
|
-
psql -h localhost -
|
228
|
+
psql -h localhost -c 'CREATE DATABASE safe_pg_migrations_test'
|
227
229
|
rake test
|
228
230
|
```
|
229
231
|
|
@@ -257,3 +259,4 @@ Interesting reads:
|
|
257
259
|
- [Safe Operations For High Volume PostgreSQL](https://www.braintreepayments.com/blog/safe-operations-for-high-volume-postgresql/)
|
258
260
|
- [Rails Migrations with Zero Downtime](https://blog.codeship.com/rails-migrations-zero-downtime/)
|
259
261
|
- [Stop worrying about PostgreSQL locks in your Rails migrations](https://medium.com/doctolib/stop-worrying-about-postgresql-locks-in-your-rails-migrations-3426027e9cc9)
|
262
|
+
- [PostgreSQL at Scale: Database Schema Changes Without Downtime](https://medium.com/paypal-tech/postgresql-at-scale-database-schema-changes-without-downtime-20d3749ed680)
|
@@ -1,21 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'ruby2_keywords'
|
3
4
|
require 'safe-pg-migrations/configuration'
|
4
5
|
require 'safe-pg-migrations/plugins/verbose_sql_logger'
|
5
6
|
require 'safe-pg-migrations/plugins/blocking_activity_logger'
|
6
7
|
require 'safe-pg-migrations/plugins/statement_insurer'
|
7
8
|
require 'safe-pg-migrations/plugins/statement_retrier'
|
8
|
-
require 'safe-pg-migrations/plugins/
|
9
|
+
require 'safe-pg-migrations/plugins/idempotent_statements'
|
9
10
|
require 'safe-pg-migrations/plugins/useless_statements_logger'
|
11
|
+
require 'safe-pg-migrations/plugins/legacy_active_record_support'
|
10
12
|
|
11
13
|
module SafePgMigrations
|
12
14
|
# Order matters: the bottom-most plugin will have precedence
|
13
15
|
PLUGINS = [
|
14
16
|
BlockingActivityLogger,
|
15
|
-
|
17
|
+
IdempotentStatements,
|
16
18
|
StatementRetrier,
|
17
19
|
StatementInsurer,
|
18
20
|
UselessStatementsLogger,
|
21
|
+
LegacyActiveRecordSupport,
|
19
22
|
].freeze
|
20
23
|
|
21
24
|
class << self
|
@@ -50,13 +53,13 @@ module SafePgMigrations
|
|
50
53
|
@alternate_connection = nil
|
51
54
|
end
|
52
55
|
|
53
|
-
def say(*args)
|
56
|
+
ruby2_keywords def say(*args)
|
54
57
|
return unless current_migration
|
55
58
|
|
56
59
|
current_migration.say(*args)
|
57
60
|
end
|
58
61
|
|
59
|
-
def say_method_call(method, *args)
|
62
|
+
ruby2_keywords def say_method_call(method, *args)
|
60
63
|
say "#{method}(#{args.map(&:inspect) * ', '})", true
|
61
64
|
end
|
62
65
|
|
@@ -84,13 +87,23 @@ module SafePgMigrations
|
|
84
87
|
true
|
85
88
|
end
|
86
89
|
|
87
|
-
SAFE_METHODS = %i[
|
90
|
+
SAFE_METHODS = %i[
|
91
|
+
execute
|
92
|
+
add_column
|
93
|
+
add_index
|
94
|
+
add_reference
|
95
|
+
add_belongs_to
|
96
|
+
change_column_null
|
97
|
+
add_foreign_key
|
98
|
+
].freeze
|
99
|
+
|
88
100
|
SAFE_METHODS.each do |method|
|
89
101
|
define_method method do |*args|
|
90
102
|
return super(*args) unless respond_to?(:safety_assured)
|
91
103
|
|
92
104
|
safety_assured { super(*args) }
|
93
105
|
end
|
106
|
+
ruby2_keywords method
|
94
107
|
end
|
95
108
|
end
|
96
109
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SafePgMigrations
|
4
|
-
module BlockingActivityLogger
|
4
|
+
module BlockingActivityLogger # rubocop:disable Metrics/ModuleLength
|
5
5
|
FILTERED_COLUMNS = %w[
|
6
6
|
blocked_activity.xact_start
|
7
7
|
blocked_locks.locktype
|
@@ -21,6 +21,7 @@ module SafePgMigrations
|
|
21
21
|
define_method method do |*args, &block|
|
22
22
|
log_blocking_queries { super(*args, &block) }
|
23
23
|
end
|
24
|
+
ruby2_keywords method
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
@@ -112,6 +113,7 @@ module SafePgMigrations
|
|
112
113
|
end
|
113
114
|
|
114
115
|
def format_start_time(start_time, reference_time = Time.now)
|
116
|
+
start_time = Time.parse(start_time) unless start_time.is_a? Time
|
115
117
|
duration = (reference_time - start_time).round
|
116
118
|
"transaction started #{duration} #{'second'.pluralize(duration)} ago"
|
117
119
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SafePgMigrations
|
4
|
+
module IdempotentStatements
|
5
|
+
ruby2_keywords def add_index(table_name, column_name, *args)
|
6
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
7
|
+
|
8
|
+
index_definition = index_definition(table_name, column_name, **options)
|
9
|
+
|
10
|
+
return super unless index_name_exists?(index_definition.table, index_definition.name)
|
11
|
+
|
12
|
+
if index_valid?(index_definition.name)
|
13
|
+
SafePgMigrations.say(
|
14
|
+
"/!\\ Index '#{index_definition.name}' already exists in '#{table_name}'. Skipping statement.",
|
15
|
+
true
|
16
|
+
)
|
17
|
+
return
|
18
|
+
end
|
19
|
+
|
20
|
+
remove_index(table_name, name: index_definition.name)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
ruby2_keywords def add_column(table_name, column_name, type, *)
|
25
|
+
return super unless column_exists?(table_name, column_name)
|
26
|
+
|
27
|
+
SafePgMigrations.say("/!\\ Column '#{column_name}' already exists in '#{table_name}'. Skipping statement.", true)
|
28
|
+
end
|
29
|
+
|
30
|
+
ruby2_keywords def remove_column(table_name, column_name, type = nil, *)
|
31
|
+
return super if column_exists?(table_name, column_name)
|
32
|
+
|
33
|
+
SafePgMigrations.say("/!\\ Column '#{column_name}' not found on table '#{table_name}'. Skipping statement.", true)
|
34
|
+
end
|
35
|
+
|
36
|
+
ruby2_keywords def remove_index(table_name, *args)
|
37
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
38
|
+
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, options)
|
39
|
+
|
40
|
+
return super if index_name_exists?(table_name, index_name)
|
41
|
+
|
42
|
+
SafePgMigrations.say("/!\\ Index '#{index_name}' not found on table '#{table_name}'. Skipping statement.", true)
|
43
|
+
end
|
44
|
+
|
45
|
+
ruby2_keywords def add_foreign_key(from_table, to_table, *args)
|
46
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
47
|
+
suboptions = options.slice(:name, :column)
|
48
|
+
return super unless foreign_key_exists?(from_table, suboptions.present? ? nil : to_table, **suboptions)
|
49
|
+
|
50
|
+
SafePgMigrations.say(
|
51
|
+
"/!\\ Foreign key '#{from_table}' -> '#{to_table}' already exists. Skipping statement.",
|
52
|
+
true
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
ruby2_keywords def create_table(table_name, *args)
|
57
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
58
|
+
return super if options[:force] || !table_exists?(table_name)
|
59
|
+
|
60
|
+
SafePgMigrations.say "/!\\ Table '#{table_name}' already exists.", true
|
61
|
+
|
62
|
+
td = create_table_definition(table_name, *args)
|
63
|
+
|
64
|
+
yield td if block_given?
|
65
|
+
|
66
|
+
SafePgMigrations.say(td.indexes.empty? ? '-- Skipping statement' : '-- Creating indexes', true)
|
67
|
+
|
68
|
+
td.indexes.each do |column_name, index_options|
|
69
|
+
add_index(table_name, column_name, **index_options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def index_definition(table_name, column_name, **options)
|
76
|
+
index_definition, = add_index_options(table_name, column_name, **options)
|
77
|
+
index_definition
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def index_valid?(index_name)
|
83
|
+
query_value <<~SQL.squish
|
84
|
+
SELECT indisvalid
|
85
|
+
FROM pg_index i
|
86
|
+
JOIN pg_class c
|
87
|
+
ON i.indexrelid = c.oid
|
88
|
+
WHERE c.relname = '#{index_name}';
|
89
|
+
SQL
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SafePgMigrations
|
4
|
+
module LegacyActiveRecordSupport
|
5
|
+
ruby2_keywords def validate_foreign_key(from_table, to_table = nil, **options)
|
6
|
+
return super(from_table, to_table || options) unless satisfied? '>=6.0.0'
|
7
|
+
|
8
|
+
super(from_table, to_table, **options)
|
9
|
+
end
|
10
|
+
|
11
|
+
ruby2_keywords def foreign_key_exists?(from_table, to_table = nil, **options)
|
12
|
+
return super(from_table, to_table || options) unless satisfied? '>=6.0.0'
|
13
|
+
|
14
|
+
super(from_table, to_table, **options)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
IndexDefinition = Struct.new(:table, :name)
|
20
|
+
|
21
|
+
def index_definition(table_name, column_name, **options)
|
22
|
+
return super(table_name, column_name, **options) if satisfied? '>=6.1.0'
|
23
|
+
|
24
|
+
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, index_column_names(column_name))
|
25
|
+
validate_index_length!(table_name, index_name, options.fetch(:internal, false))
|
26
|
+
|
27
|
+
IndexDefinition.new(table_name, index_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def satisfied?(version)
|
33
|
+
Gem::Requirement.new(version).satisfied_by? Gem::Version.new(::ActiveRecord::VERSION::STRING)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -8,9 +8,11 @@ module SafePgMigrations
|
|
8
8
|
define_method method do |*args, &block|
|
9
9
|
with_setting(:statement_timeout, SafePgMigrations.config.pg_safe_timeout) { super(*args, &block) }
|
10
10
|
end
|
11
|
+
ruby2_keywords method
|
11
12
|
end
|
12
13
|
|
13
|
-
def add_column(table_name, column_name, type,
|
14
|
+
ruby2_keywords def add_column(table_name, column_name, type, *args) # rubocop:disable Metrics/CyclomaticComplexity
|
15
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
14
16
|
return super if SafePgMigrations.pg_version_num >= PG_11_VERSION_NUM
|
15
17
|
|
16
18
|
default = options.delete(:default)
|
@@ -36,17 +38,21 @@ module SafePgMigrations
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
39
|
-
def add_foreign_key(from_table, to_table,
|
41
|
+
ruby2_keywords def add_foreign_key(from_table, to_table, *args)
|
42
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
40
43
|
validate_present = options.key? :validate
|
41
44
|
options[:validate] = false unless validate_present
|
45
|
+
with_setting(:statement_timeout, SafePgMigrations.config.pg_safe_timeout) do
|
46
|
+
super(from_table, to_table, **options)
|
47
|
+
end
|
42
48
|
|
43
|
-
|
49
|
+
return if validate_present
|
44
50
|
|
45
|
-
|
46
|
-
without_statement_timeout { validate_foreign_key from_table,
|
51
|
+
suboptions = options.slice(:name, :column)
|
52
|
+
without_statement_timeout { validate_foreign_key from_table, suboptions.present? ? nil : to_table, **suboptions }
|
47
53
|
end
|
48
54
|
|
49
|
-
def create_table(*)
|
55
|
+
ruby2_keywords def create_table(*)
|
50
56
|
with_setting(:statement_timeout, SafePgMigrations.config.pg_safe_timeout) do
|
51
57
|
super do |td|
|
52
58
|
yield td if block_given?
|
@@ -65,34 +71,25 @@ module SafePgMigrations
|
|
65
71
|
options[:algorithm] = :concurrently
|
66
72
|
end
|
67
73
|
|
68
|
-
SafePgMigrations.say_method_call(:add_index, table_name, column_name, options)
|
74
|
+
SafePgMigrations.say_method_call(:add_index, table_name, column_name, **options)
|
69
75
|
|
70
|
-
without_timeout { super }
|
76
|
+
without_timeout { super(table_name, column_name, **options) }
|
71
77
|
end
|
72
78
|
|
73
|
-
def remove_index(table_name,
|
74
|
-
options = { column:
|
75
|
-
options[:algorithm] = :concurrently
|
76
|
-
SafePgMigrations.say_method_call(:remove_index, table_name, options)
|
79
|
+
ruby2_keywords def remove_index(table_name, *args)
|
80
|
+
options = args.last.is_a?(Hash) ? args.last : { column: args.last }
|
81
|
+
options[:algorithm] = :concurrently unless options.key?(:algorithm)
|
82
|
+
SafePgMigrations.say_method_call(:remove_index, table_name, **options)
|
77
83
|
|
78
|
-
without_timeout { super }
|
84
|
+
without_timeout { super(table_name, **options) }
|
79
85
|
end
|
80
86
|
|
81
87
|
def backfill_column_default(table_name, column_name)
|
82
|
-
|
88
|
+
model = Class.new(ActiveRecord::Base) { self.table_name = table_name }
|
83
89
|
quoted_column_name = quote_column_name(column_name)
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
SELECT id FROM #{quoted_table_name} WHERE id > #{primary_key_offset}
|
88
|
-
ORDER BY id LIMIT #{SafePgMigrations.config.batch_size}
|
89
|
-
SQL
|
90
|
-
break if ids.empty?
|
91
|
-
|
92
|
-
primary_key_offset = ids.last
|
93
|
-
execute <<~SQL.squish
|
94
|
-
UPDATE #{quoted_table_name} SET #{quoted_column_name} = DEFAULT WHERE id IN (#{ids.join(',')})
|
95
|
-
SQL
|
90
|
+
|
91
|
+
model.in_batches(of: SafePgMigrations.config.batch_size).each do |relation|
|
92
|
+
relation.update_all("#{quoted_column_name} = DEFAULT")
|
96
93
|
end
|
97
94
|
end
|
98
95
|
|
@@ -2,22 +2,27 @@
|
|
2
2
|
|
3
3
|
module SafePgMigrations
|
4
4
|
module UselessStatementsLogger
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class << self
|
6
|
+
ruby2_keywords def warn_useless(action, link = nil, *args)
|
7
|
+
SafePgMigrations.say "/!\\ No need to explicitly use #{action}, safe-pg-migrations does it for you", *args
|
8
|
+
SafePgMigrations.say "\t see #{link} for more details", *args if link
|
9
|
+
end
|
8
10
|
end
|
9
11
|
|
10
|
-
def add_index(
|
12
|
+
ruby2_keywords def add_index(*args)
|
13
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
11
14
|
warn_for_index(**options)
|
12
15
|
super
|
13
16
|
end
|
14
17
|
|
15
|
-
def remove_index(table_name,
|
16
|
-
|
18
|
+
ruby2_keywords def remove_index(table_name, *args)
|
19
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
20
|
+
warn_for_index(**options) unless options.empty?
|
17
21
|
super
|
18
22
|
end
|
19
23
|
|
20
|
-
def add_foreign_key(
|
24
|
+
ruby2_keywords def add_foreign_key(*args)
|
25
|
+
options = args.last.is_a?(Hash) ? args.last : {}
|
21
26
|
if options[:validate] == false
|
22
27
|
UselessStatementsLogger.warn_useless '`validate: :false`', 'https://github.com/doctolib/safe-pg-migrations#safe_add_foreign_key'
|
23
28
|
end
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: safe-pg-migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthieu Prat
|
8
8
|
- Romain Choquet
|
9
|
-
|
9
|
+
- Thomas Hareau
|
10
|
+
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date:
|
13
|
+
date: 2022-03-01 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: activerecord
|
@@ -40,119 +41,21 @@ dependencies:
|
|
40
41
|
- !ruby/object:Gem::Version
|
41
42
|
version: '5.2'
|
42
43
|
- !ruby/object:Gem::Dependency
|
43
|
-
name:
|
44
|
+
name: ruby2_keywords
|
44
45
|
requirement: !ruby/object:Gem::Requirement
|
45
46
|
requirements:
|
46
47
|
- - ">="
|
47
48
|
- !ruby/object:Gem::Version
|
48
|
-
version:
|
49
|
-
type: :
|
50
|
-
prerelease: false
|
51
|
-
version_requirements: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
- !ruby/object:Gem::Dependency
|
57
|
-
name: minitest
|
58
|
-
requirement: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
63
|
-
type: :development
|
64
|
-
prerelease: false
|
65
|
-
version_requirements: !ruby/object:Gem::Requirement
|
66
|
-
requirements:
|
67
|
-
- - ">="
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
version: '0'
|
70
|
-
- !ruby/object:Gem::Dependency
|
71
|
-
name: mocha
|
72
|
-
requirement: !ruby/object:Gem::Requirement
|
73
|
-
requirements:
|
74
|
-
- - ">="
|
75
|
-
- !ruby/object:Gem::Version
|
76
|
-
version: '0'
|
77
|
-
type: :development
|
78
|
-
prerelease: false
|
79
|
-
version_requirements: !ruby/object:Gem::Requirement
|
80
|
-
requirements:
|
81
|
-
- - ">="
|
82
|
-
- !ruby/object:Gem::Version
|
83
|
-
version: '0'
|
84
|
-
- !ruby/object:Gem::Dependency
|
85
|
-
name: pg
|
86
|
-
requirement: !ruby/object:Gem::Requirement
|
87
|
-
requirements:
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: '0'
|
91
|
-
type: :development
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - ">="
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '0'
|
98
|
-
- !ruby/object:Gem::Dependency
|
99
|
-
name: pry
|
100
|
-
requirement: !ruby/object:Gem::Requirement
|
101
|
-
requirements:
|
102
|
-
- - ">="
|
103
|
-
- !ruby/object:Gem::Version
|
104
|
-
version: '0'
|
105
|
-
type: :development
|
106
|
-
prerelease: false
|
107
|
-
version_requirements: !ruby/object:Gem::Requirement
|
108
|
-
requirements:
|
109
|
-
- - ">="
|
110
|
-
- !ruby/object:Gem::Version
|
111
|
-
version: '0'
|
112
|
-
- !ruby/object:Gem::Dependency
|
113
|
-
name: pry-coolline
|
114
|
-
requirement: !ruby/object:Gem::Requirement
|
115
|
-
requirements:
|
116
|
-
- - ">="
|
117
|
-
- !ruby/object:Gem::Version
|
118
|
-
version: '0'
|
119
|
-
type: :development
|
120
|
-
prerelease: false
|
121
|
-
version_requirements: !ruby/object:Gem::Requirement
|
122
|
-
requirements:
|
123
|
-
- - ">="
|
124
|
-
- !ruby/object:Gem::Version
|
125
|
-
version: '0'
|
126
|
-
- !ruby/object:Gem::Dependency
|
127
|
-
name: rake
|
128
|
-
requirement: !ruby/object:Gem::Requirement
|
129
|
-
requirements:
|
130
|
-
- - ">="
|
131
|
-
- !ruby/object:Gem::Version
|
132
|
-
version: '0'
|
133
|
-
type: :development
|
134
|
-
prerelease: false
|
135
|
-
version_requirements: !ruby/object:Gem::Requirement
|
136
|
-
requirements:
|
137
|
-
- - ">="
|
138
|
-
- !ruby/object:Gem::Version
|
139
|
-
version: '0'
|
140
|
-
- !ruby/object:Gem::Dependency
|
141
|
-
name: rubocop
|
142
|
-
requirement: !ruby/object:Gem::Requirement
|
143
|
-
requirements:
|
144
|
-
- - ">="
|
145
|
-
- !ruby/object:Gem::Version
|
146
|
-
version: '0'
|
147
|
-
type: :development
|
49
|
+
version: 0.0.4
|
50
|
+
type: :runtime
|
148
51
|
prerelease: false
|
149
52
|
version_requirements: !ruby/object:Gem::Requirement
|
150
53
|
requirements:
|
151
54
|
- - ">="
|
152
55
|
- !ruby/object:Gem::Version
|
153
|
-
version:
|
56
|
+
version: 0.0.4
|
154
57
|
description: Make your PG migrations safe.
|
155
|
-
email:
|
58
|
+
email:
|
156
59
|
executables: []
|
157
60
|
extensions: []
|
158
61
|
extra_rdoc_files: []
|
@@ -163,7 +66,8 @@ files:
|
|
163
66
|
- lib/safe-pg-migrations/base.rb
|
164
67
|
- lib/safe-pg-migrations/configuration.rb
|
165
68
|
- lib/safe-pg-migrations/plugins/blocking_activity_logger.rb
|
166
|
-
- lib/safe-pg-migrations/plugins/
|
69
|
+
- lib/safe-pg-migrations/plugins/idempotent_statements.rb
|
70
|
+
- lib/safe-pg-migrations/plugins/legacy_active_record_support.rb
|
167
71
|
- lib/safe-pg-migrations/plugins/statement_insurer.rb
|
168
72
|
- lib/safe-pg-migrations/plugins/statement_retrier.rb
|
169
73
|
- lib/safe-pg-migrations/plugins/useless_statements_logger.rb
|
@@ -173,8 +77,13 @@ files:
|
|
173
77
|
homepage: https://github.com/doctolib/safe-pg-migrations
|
174
78
|
licenses:
|
175
79
|
- MIT
|
176
|
-
metadata:
|
177
|
-
|
80
|
+
metadata:
|
81
|
+
bug_tracker_uri: https://github.com/doctolib/safe-pg-migrations/issues
|
82
|
+
homepage_uri: https://github.com/doctolib/safe-pg-migrations#safe-pg-migrations
|
83
|
+
mailing_list_uri: https://doctolib.engineering/engineering-news-ruby-rails-react
|
84
|
+
source_code_uri: https://github.com/doctolib/safe-pg-migrations
|
85
|
+
contributors_uri: https://github.com/doctolib/safe-pg-migrations/graphs/contributors
|
86
|
+
post_install_message:
|
178
87
|
rdoc_options: []
|
179
88
|
require_paths:
|
180
89
|
- lib
|
@@ -183,15 +92,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
183
92
|
- - ">="
|
184
93
|
- !ruby/object:Gem::Version
|
185
94
|
version: '2.5'
|
95
|
+
- - "<"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '4'
|
186
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
187
99
|
requirements:
|
188
100
|
- - ">="
|
189
101
|
- !ruby/object:Gem::Version
|
190
102
|
version: '0'
|
191
103
|
requirements: []
|
192
|
-
rubyforge_project:
|
104
|
+
rubyforge_project:
|
193
105
|
rubygems_version: 2.7.3
|
194
|
-
signing_key:
|
106
|
+
signing_key:
|
195
107
|
specification_version: 4
|
196
108
|
summary: Make your PG migrations safe.
|
197
109
|
test_files: []
|
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module SafePgMigrations
|
4
|
-
module IdemPotentStatements
|
5
|
-
def add_index(table_name, column_name, **options)
|
6
|
-
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, index_column_names(column_name))
|
7
|
-
return super unless index_name_exists?(table_name, index_name)
|
8
|
-
|
9
|
-
return if index_valid?(index_name)
|
10
|
-
|
11
|
-
remove_index(table_name, name: index_name)
|
12
|
-
super
|
13
|
-
end
|
14
|
-
|
15
|
-
def add_column(table_name, column_name, type, options = {})
|
16
|
-
return super unless column_exists?(table_name, column_name)
|
17
|
-
|
18
|
-
SafePgMigrations.say("/!\\ Column '#{column_name}' already exists in '#{table_name}'. Skipping statement.", true)
|
19
|
-
end
|
20
|
-
|
21
|
-
def remove_column(table_name, column_name, type = nil, options = {})
|
22
|
-
return super if column_exists?(table_name, column_name)
|
23
|
-
|
24
|
-
SafePgMigrations.say("/!\\ Column '#{column_name}' not found on table '#{table_name}'. Skipping statement.", true)
|
25
|
-
end
|
26
|
-
|
27
|
-
def remove_index(table_name, options = {})
|
28
|
-
index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, options)
|
29
|
-
|
30
|
-
return super if index_name_exists?(table_name, index_name)
|
31
|
-
|
32
|
-
SafePgMigrations.say("/!\\ Index '#{index_name}' not found on table '#{table_name}'. Skipping statement.", true)
|
33
|
-
end
|
34
|
-
|
35
|
-
def add_foreign_key(from_table, to_table, **options)
|
36
|
-
options_or_to_table = options.slice(:name, :column).presence || to_table
|
37
|
-
return super unless foreign_key_exists?(from_table, options_or_to_table)
|
38
|
-
|
39
|
-
SafePgMigrations.say(
|
40
|
-
"/!\\ Foreign key '#{from_table}' -> '#{to_table}' already exists. Skipping statement.",
|
41
|
-
true
|
42
|
-
)
|
43
|
-
end
|
44
|
-
|
45
|
-
def create_table(table_name, comment: nil, **options)
|
46
|
-
return super if options[:force] || !table_exists?(table_name)
|
47
|
-
|
48
|
-
SafePgMigrations.say "/!\\ Table '#{table_name}' already exists.", true
|
49
|
-
|
50
|
-
td = create_table_definition(table_name, **options)
|
51
|
-
|
52
|
-
yield td if block_given?
|
53
|
-
|
54
|
-
SafePgMigrations.say(td.indexes.empty? ? '-- Skipping statement' : '-- Creating indexes', true)
|
55
|
-
|
56
|
-
td.indexes.each do |column_name, index_options|
|
57
|
-
add_index(table_name, column_name, index_options)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def index_valid?(index_name)
|
64
|
-
query_value <<~SQL.squish
|
65
|
-
SELECT indisvalid
|
66
|
-
FROM pg_index i
|
67
|
-
JOIN pg_class c
|
68
|
-
ON i.indexrelid = c.oid
|
69
|
-
WHERE c.relname = '#{index_name}';
|
70
|
-
SQL
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|