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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b01cc2376f8bcfa1908bdc642a51bd444420540eef7aa01dc403f13ea0b19c8
4
- data.tar.gz: 0c09d3bc52ce84542fd50b119cef667714a6058d5fcf59f1be83af72318b3862
3
+ metadata.gz: 9d05d2e6c8647af8e618652235043dfde1b104065e71113f9f55c25117dd6ab6
4
+ data.tar.gz: 1f67d82d62855e6566f39c92b3cfdf4e9d6f68cc2bd0b9105c3e61ba32fd8ddf
5
5
  SHA512:
6
- metadata.gz: f7a822bbb6cc2e924c8da76ca1501cec16588b4972e850fd07e7fbc120e159dfef715a8293c3399454aa9072e2cc6c2da8927e6dc84f15c72670bb16182fe53c
7
- data.tar.gz: 4821e1fb76ce6e868a2032eb0e53aceea8f745f31fce521333949c20143150bd03950187d7286d63d1d9c7269b6015f8b9be8e38833d3e6df659f2f8e5940902
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
+ ![safe-pg-migrations](./logo.png)
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 rollbacked. This can result in migrations being partially applied. In that case, they need to be manually reverted.
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-postgre 11**
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">safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
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 retryable statments](https://github.com/doctolib/safe-pg-migrations/blob/66933256252b6bbf12e404b829a361dbba30e684/lib/safe-pg-migrations/plugins/statement_retrier.rb#L5)
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
- ## Runnings tests
224
+ ## Running tests
223
225
 
224
226
  ```bash
225
227
  bundle
226
- psql -h localhost -U postgres -c 'CREATE DATABASE safe_pg_migrations_test'
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/idem_potent_statements'
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
- IdemPotentStatements,
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[execute add_column add_index add_reference add_belongs_to change_column_null].freeze
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, **options)
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, **options)
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
- with_setting(:statement_timeout, SafePgMigrations.config.pg_safe_timeout) { super }
49
+ return if validate_present
44
50
 
45
- options_or_to_table = options.slice(:name, :column).presence || to_table
46
- without_statement_timeout { validate_foreign_key from_table, options_or_to_table } unless validate_present
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, options = {})
74
- options = { column: options } unless options.is_a?(Hash)
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
- quoted_table_name = quote_table_name(table_name)
88
+ model = Class.new(ActiveRecord::Base) { self.table_name = table_name }
83
89
  quoted_column_name = quote_column_name(column_name)
84
- primary_key_offset = 0
85
- loop do
86
- ids = query_values <<~SQL.squish
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
 
@@ -10,6 +10,7 @@ module SafePgMigrations
10
10
  define_method method do |*args, &block|
11
11
  retry_if_lock_timeout { super(*args, &block) }
12
12
  end
13
+ ruby2_keywords method
13
14
  end
14
15
 
15
16
  private
@@ -2,22 +2,27 @@
2
2
 
3
3
  module SafePgMigrations
4
4
  module UselessStatementsLogger
5
- def self.warn_useless(action, link = nil, *args)
6
- SafePgMigrations.say "/!\\ No need to explicitly use #{action}, safe-pg-migrations does it for you", *args
7
- SafePgMigrations.say "\t see #{link} for more details", *args if link
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(*, **options)
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, options = {})
16
- warn_for_index(options) if options.is_a? Hash
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(*, **options)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SafePgMigrations
4
- VERSION = '1.2.3'
4
+ VERSION = '1.4.1'
5
5
  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.2.3
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu Prat
8
8
  - Romain Choquet
9
- autorequire:
9
+ - Thomas Hareau
10
+ autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2021-04-30 00:00:00.000000000 Z
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: bundler
44
+ name: ruby2_keywords
44
45
  requirement: !ruby/object:Gem::Requirement
45
46
  requirements:
46
47
  - - ">="
47
48
  - !ruby/object:Gem::Version
48
- version: '0'
49
- type: :development
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: '0'
56
+ version: 0.0.4
154
57
  description: Make your PG migrations safe.
155
- email: matthieuprat@gmail.com
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/idem_potent_statements.rb
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
- post_install_message:
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