Skip to content

Commit 53e30c3

Browse files
author
Thomas Hareau
authored
Add warnings when useless statement are used (#29)
* Add warnings when useless statement are used * Remove unused method * Move test to dedicated file * Using super * Add foreign_key * Add remove_index * Fix rubocop * Fix * Messing with methods again
1 parent 0ed9639 commit 53e30c3

File tree

4 files changed

+172
-2
lines changed

4 files changed

+172
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ Note: the addition of the not null constraint may timeout. In that case, you may
122122

123123
</details>
124124

125-
<details><summary>Safe <code>add_index</code> and <code>remove_index</code></summary>
125+
<details><summary id="safe_add_remove_index">Safe <code>add_index</code> and <code>remove_index</code></summary>
126126

127127
Creating an index requires a `SHARE` lock on the target table which blocks all write on the table while the index is created (which can take some time on a large table). This is usually not practical in a live environment. Thus, **Safe PG Migrations** ensures indexes are created concurrently.
128128

@@ -135,7 +135,7 @@ If you still get lock timeout while adding / removing indexes, it might be for o
135135

136136
</details>
137137

138-
<details><summary>safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
138+
<details><summary id="safe_add_foreign_key">safe <code>add_foreign_key</code> (and <code>add_reference</code>)</summary>
139139

140140
Adding a foreign key requires a `SHARE ROW EXCLUSIVE` lock, which **prevent writing in the tables** while the migration is running.
141141

lib/safe-pg-migrations/base.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
require 'safe-pg-migrations/plugins/statement_insurer'
77
require 'safe-pg-migrations/plugins/statement_retrier'
88
require 'safe-pg-migrations/plugins/idem_potent_statements'
9+
require 'safe-pg-migrations/plugins/useless_statements_logger'
910

1011
module SafePgMigrations
1112
# Order matters: the bottom-most plugin will have precedence
@@ -14,6 +15,7 @@ module SafePgMigrations
1415
IdemPotentStatements,
1516
StatementRetrier,
1617
StatementInsurer,
18+
UselessStatementsLogger,
1719
].freeze
1820

1921
class << self
@@ -78,6 +80,7 @@ def exec_migration(connection, direction)
7880
end
7981

8082
def disable_ddl_transaction
83+
UselessStatementsLogger.warn_useless '`disable_ddl_transaction`' if super
8184
true
8285
end
8386

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
module SafePgMigrations
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
8+
end
9+
10+
def add_index(*, **options)
11+
warn_for_index(**options)
12+
super
13+
end
14+
15+
def remove_index(table_name, options = {})
16+
warn_for_index(options) if options.is_a? Hash
17+
super
18+
end
19+
20+
def add_foreign_key(*, **options)
21+
if options[:validate] == false
22+
UselessStatementsLogger.warn_useless '`validate: :false`', 'https://github.com/doctolib/safe-pg-migrations#safe_add_foreign_key'
23+
end
24+
super
25+
end
26+
27+
def warn_for_index(**options)
28+
return unless options[:algorithm] == :concurrently
29+
30+
UselessStatementsLogger.warn_useless '`algorithm: :concurrently`', 'https://github.com/doctolib/safe-pg-migrations#safe_add_remove_index'
31+
end
32+
end
33+
end
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class UselessStatementLoggerTest < MiniTest::Unit::TestCase
6+
def setup
7+
SafePgMigrations.instance_variable_set(:@config, nil)
8+
@connection = ActiveRecord::Base.connection
9+
@verbose_was = ActiveRecord::Migration.verbose
10+
@connection.create_table(:schema_migrations) { |t| t.string :version }
11+
ActiveRecord::SchemaMigration.create_table
12+
ActiveRecord::Migration.verbose = false
13+
@connection.execute("SET statement_timeout TO '70s'")
14+
@connection.execute("SET lock_timeout TO '70s'")
15+
end
16+
17+
def teardown
18+
ActiveRecord::SchemaMigration.drop_table
19+
@connection.execute('SET statement_timeout TO 0')
20+
@connection.execute("SET lock_timeout TO '30s'")
21+
@connection.drop_table(:messages, if_exists: true)
22+
@connection.drop_table(:users, if_exists: true)
23+
ActiveRecord::Migration.verbose = @verbose_was
24+
end
25+
26+
def test_ddl_transactions
27+
@migration =
28+
Class.new(ActiveRecord::Migration::Current) do
29+
disable_ddl_transaction!
30+
31+
def change
32+
create_table(:users) { |t| t.string :email }
33+
end
34+
end.new
35+
36+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
37+
38+
assert_includes(
39+
write_calls,
40+
'/!\ No need to explicitly use `disable_ddl_transaction`, safe-pg-migrations does it for you'
41+
)
42+
end
43+
44+
def test_no_warning_when_no_ddl_transaction
45+
@migration =
46+
Class.new(ActiveRecord::Migration::Current) do
47+
def change
48+
create_table(:users) { |t| t.string :email }
49+
end
50+
end.new
51+
52+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
53+
54+
refute_includes write_calls, '/!\ No need to explicitly disable DDL transaction, safe-pg-migrations does it for you'
55+
end
56+
57+
def test_add_index_concurrently
58+
@connection.create_table(:users) { |t| t.string :email }
59+
@migration =
60+
Class.new(ActiveRecord::Migration::Current) do
61+
def change
62+
add_index :users, :email, algorithm: :concurrently
63+
end
64+
end.new
65+
66+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
67+
68+
assert_includes(
69+
write_calls,
70+
'/!\ No need to explicitly use `algorithm: :concurrently`, safe-pg-migrations does it for you'
71+
)
72+
end
73+
74+
def test_no_warning_when_no_index_concurrently
75+
@connection.create_table(:users) { |t| t.string :email }
76+
@migration =
77+
Class.new(ActiveRecord::Migration::Current) do
78+
def change
79+
add_index :users, :email
80+
end
81+
end.new
82+
83+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
84+
85+
refute_includes(
86+
write_calls,
87+
'/!\ No need to explicitly use `algorithm: :concurrently`, safe-pg-migrations does it for you'
88+
)
89+
end
90+
91+
def test_add_foreign_key_validate_false
92+
@connection.create_table(:users) { |t| t.string :email }
93+
@connection.create_table(:messages) do |t|
94+
t.string :message
95+
t.bigint :user_id
96+
end
97+
98+
@migration =
99+
Class.new(ActiveRecord::Migration::Current) do
100+
def change
101+
add_foreign_key :messages, :users, validate: false
102+
end
103+
end.new
104+
105+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
106+
107+
assert_includes(
108+
write_calls,
109+
'/!\ No need to explicitly use `validate: :false`, safe-pg-migrations does it for you'
110+
)
111+
end
112+
113+
def test_add_foreign_key_no_validation
114+
@connection.create_table(:users) { |t| t.string :email }
115+
@connection.create_table(:messages) do |t|
116+
t.string :message
117+
t.bigint :user_id
118+
end
119+
120+
@migration =
121+
Class.new(ActiveRecord::Migration::Current) do
122+
def change
123+
add_foreign_key :messages, :users
124+
end
125+
end.new
126+
127+
write_calls = record_calls(SafePgMigrations, :say) { run_migration }.map(&:first)
128+
129+
refute_includes(
130+
write_calls,
131+
'/!\ No need to explicitly use `validate: :false`, safe-pg-migrations does it for you'
132+
)
133+
end
134+
end

0 commit comments

Comments
 (0)