safe-pg-migrations 1.2.3 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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