activerecord-safer_migrations 0.1.0
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 +7 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +30 -0
- data/.ruby-version +1 -0
- data/.travis.yml +17 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +73 -0
- data/LICENSE.txt +22 -0
- data/README.md +71 -0
- data/activerecord-safer_migrations.gemspec +17 -0
- data/lib/active_record/safer_migrations/migration.rb +64 -0
- data/lib/active_record/safer_migrations/postgresql_adapter.rb +38 -0
- data/lib/active_record/safer_migrations/railtie.rb +11 -0
- data/lib/active_record/safer_migrations/setting_helper.rb +49 -0
- data/lib/active_record/safer_migrations/version.rb +5 -0
- data/lib/activerecord-safer_migrations.rb +38 -0
- data/spec/active_record/safer_migrations/migration_spec.rb +160 -0
- data/spec/spec_helper.rb +39 -0
- metadata +74 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 75845456397be277043e0b89d6fedbac416d3fe3
|
4
|
+
data.tar.gz: 01add6f99123fd1bd0246f41504ec754d674dc87
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b95179e85acb6a179fe9a05f018a4775fad27680bdb8f5945dbe43cc12187a53d91b13e4567097721df8ad5f14cdd465d68fb6aedc0896d86e19214e5b932d24
|
7
|
+
data.tar.gz: dbcd63bb99f1202a72d7a01b7857af9635bcce72d1c9703bedfa3035b607b9fcf2752bcb9953bba2b54412b0b46702f93a35c79355b7f78edf1d5729cd035148
|
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
/.bundle
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
AllCops:
|
2
|
+
DisplayCopNames: true
|
3
|
+
|
4
|
+
# Use trailing rather than leading dots on multi-line call chains
|
5
|
+
Style/DotPosition:
|
6
|
+
EnforcedStyle: trailing
|
7
|
+
|
8
|
+
Style/Documentation:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Style/StringLiterals:
|
12
|
+
EnforcedStyle: double_quotes
|
13
|
+
|
14
|
+
Style/TrailingComma:
|
15
|
+
EnforcedStyleForMultiline: comma
|
16
|
+
|
17
|
+
Style/AccessorMethodName:
|
18
|
+
Enabled: false
|
19
|
+
|
20
|
+
Style/SignalException:
|
21
|
+
EnforcedStyle: only_raise
|
22
|
+
|
23
|
+
Style/GlobalVars:
|
24
|
+
Enabled: false
|
25
|
+
|
26
|
+
Style/FileName:
|
27
|
+
Enabled: false
|
28
|
+
|
29
|
+
Metrics/LineLength:
|
30
|
+
Max: 100
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
2.0.0-p353
|
data/.travis.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
language: ruby
|
2
|
+
|
3
|
+
rvm:
|
4
|
+
- 2.2
|
5
|
+
|
6
|
+
addons:
|
7
|
+
postgresql: "9.3"
|
8
|
+
|
9
|
+
before_script:
|
10
|
+
- psql -c 'CREATE DATABASE safer_migrations_test;' -U postgres
|
11
|
+
|
12
|
+
script:
|
13
|
+
- bundle exec rspec spec
|
14
|
+
- bundle exec rubocop
|
15
|
+
|
16
|
+
env:
|
17
|
+
- "DATABASE_URL=postgres://postgres@localhost/safer_migrations_test"
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
activerecord-safer_migrations (0.1.0)
|
5
|
+
activerecord (~> 4.2)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activemodel (4.2.5)
|
11
|
+
activesupport (= 4.2.5)
|
12
|
+
builder (~> 3.1)
|
13
|
+
activerecord (4.2.5)
|
14
|
+
activemodel (= 4.2.5)
|
15
|
+
activesupport (= 4.2.5)
|
16
|
+
arel (~> 6.0)
|
17
|
+
activesupport (4.2.5)
|
18
|
+
i18n (~> 0.7)
|
19
|
+
json (~> 1.7, >= 1.7.7)
|
20
|
+
minitest (~> 5.1)
|
21
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
22
|
+
tzinfo (~> 1.1)
|
23
|
+
arel (6.0.3)
|
24
|
+
ast (2.1.0)
|
25
|
+
astrolabe (1.3.1)
|
26
|
+
parser (~> 2.2)
|
27
|
+
builder (3.2.2)
|
28
|
+
diff-lcs (1.2.5)
|
29
|
+
i18n (0.7.0)
|
30
|
+
json (1.8.3)
|
31
|
+
minitest (5.8.3)
|
32
|
+
parser (2.2.3.0)
|
33
|
+
ast (>= 1.1, < 3.0)
|
34
|
+
pg (0.18.3)
|
35
|
+
powerpack (0.1.1)
|
36
|
+
rainbow (2.0.0)
|
37
|
+
rspec (3.3.0)
|
38
|
+
rspec-core (~> 3.3.0)
|
39
|
+
rspec-expectations (~> 3.3.0)
|
40
|
+
rspec-mocks (~> 3.3.0)
|
41
|
+
rspec-core (3.3.2)
|
42
|
+
rspec-support (~> 3.3.0)
|
43
|
+
rspec-expectations (3.3.1)
|
44
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
45
|
+
rspec-support (~> 3.3.0)
|
46
|
+
rspec-mocks (3.3.2)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.3.0)
|
49
|
+
rspec-support (3.3.0)
|
50
|
+
rubocop (0.35.1)
|
51
|
+
astrolabe (~> 1.3)
|
52
|
+
parser (>= 2.2.3.0, < 3.0)
|
53
|
+
powerpack (~> 0.1)
|
54
|
+
rainbow (>= 1.99.1, < 3.0)
|
55
|
+
ruby-progressbar (~> 1.7)
|
56
|
+
tins (<= 1.6.0)
|
57
|
+
ruby-progressbar (1.7.5)
|
58
|
+
thread_safe (0.3.5)
|
59
|
+
tins (1.6.0)
|
60
|
+
tzinfo (1.2.2)
|
61
|
+
thread_safe (~> 0.1)
|
62
|
+
|
63
|
+
PLATFORMS
|
64
|
+
ruby
|
65
|
+
|
66
|
+
DEPENDENCIES
|
67
|
+
activerecord-safer_migrations!
|
68
|
+
pg (~> 0.18.3)
|
69
|
+
rspec (~> 3.3.0)
|
70
|
+
rubocop (~> 0.35.1)
|
71
|
+
|
72
|
+
BUNDLED WITH
|
73
|
+
1.10.6
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 GoCardless Ltd.
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
## ActiveRecord safer migration helpers
|
2
|
+
|
3
|
+
*Note: this library only supports PostgreSQL 9.3+. If you're interested in adding support for other databases, we're open to pull requests!*
|
4
|
+
|
5
|
+
Postgres holds ACCESS EXCLUSIVE locks for [almost all][pg-alter-table] DDL
|
6
|
+
operations. ACCESS EXCLUSIVE locks conflict with all other table-level locks,
|
7
|
+
which can cause issues in several situations. For instance:
|
8
|
+
|
9
|
+
1. If the lock is held for a long time, all other access to the table will be
|
10
|
+
blocked, which can result in downtime.
|
11
|
+
2. Even if the lock is only held breifly, it will block all other access to the
|
12
|
+
table while it is in the lock queue, as it conflicts with all other locks.
|
13
|
+
The lock can't be acquired until all other queries ahead of it have finished,
|
14
|
+
so having to wait on long-running queries can also result in downtime.
|
15
|
+
See [here][blog-post] for more details.
|
16
|
+
|
17
|
+
Both these issues can be avoided by setting timeouts on the migration connection -
|
18
|
+
`statement_timeout` and `lock_timeout` respectively.
|
19
|
+
|
20
|
+
Once this gem is loaded, all migrations will automatically have a
|
21
|
+
`lock_timeout` and a `statement_timeout` set. The initial `lock_timeout`
|
22
|
+
default is 750ms, and the initial `statement_timeout` default is 1500ms. Both
|
23
|
+
defaults can be easily changed (e.g. in a Rails initializer).
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
ActiveRecord::SaferMigrations.default_lock_timeout = 1000
|
27
|
+
ActiveRecord::SaferMigrations.default_statement_timeout = 2000
|
28
|
+
```
|
29
|
+
|
30
|
+
To explicitly set timeouts for a given migration, use the `set_lock_timeout` and
|
31
|
+
`set_statement_timeout` class methods in the migration.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class LockTest < ActiveRecord::Migration
|
35
|
+
set_lock_timeout(250)
|
36
|
+
set_statement_timeout(750)
|
37
|
+
|
38
|
+
def change
|
39
|
+
create_table :lock_test
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
To disable timeouts for a migration, use the `disable_lock_timeout!` and
|
45
|
+
`disable_statement_timeout!` class methods. Note that this is [extremely
|
46
|
+
dangerous][blog-post] if you're doing any schema alterations in your migration.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class LockTest < ActiveRecord::Migration
|
50
|
+
# Only do this if you really know what you're doing!
|
51
|
+
disable_lock_timeout!
|
52
|
+
disable_statement_timeout!
|
53
|
+
|
54
|
+
def change
|
55
|
+
create_table :lock_test
|
56
|
+
end
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
### Use with PgBouncer
|
61
|
+
|
62
|
+
This gem sets session-level settings on Postgres connections. If you're using
|
63
|
+
PgBouncer in transaction pooling mode, using session-level settings is
|
64
|
+
dangerous, as you can't guarantee which connection will receive the setting.
|
65
|
+
For this reason, this gem is incompatible with transaction-pooling and should
|
66
|
+
only be used if migrations are run on connections that support session-level
|
67
|
+
features.
|
68
|
+
|
69
|
+
[blog-post]: https://gocardless.com/blog/zero-downtime-postgres-migrations-the-hard-parts/
|
70
|
+
[pg-alter-table]: http://www.postgresql.org/docs/9.4/static/sql-altertable.html
|
71
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path("../lib/active_record/safer_migrations/version", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = "activerecord-safer_migrations"
|
5
|
+
gem.version = ActiveRecord::SaferMigrations::VERSION
|
6
|
+
gem.date = "2015-08-10"
|
7
|
+
gem.summary = "ActiveRecord migration helpers to avoid downtime"
|
8
|
+
gem.description = ""
|
9
|
+
gem.authors = ["GoCardless Engineering"]
|
10
|
+
gem.email = "developers@gocardless.com"
|
11
|
+
gem.files = `git ls-files`.split("\n")
|
12
|
+
gem.require_paths = ["lib"]
|
13
|
+
gem.homepage = "https://github.com/gocardless/activerecord-safer_migrations"
|
14
|
+
gem.license = "MIT"
|
15
|
+
|
16
|
+
gem.add_runtime_dependency "activerecord", "~> 4.2"
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "active_record/safer_migrations/setting_helper"
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module SaferMigrations
|
5
|
+
module Migration
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
# Use Rails' class_attribute to get an attribute that you can
|
9
|
+
# override in subclasses
|
10
|
+
class_attribute :lock_timeout
|
11
|
+
class_attribute :statement_timeout
|
12
|
+
|
13
|
+
prepend(InstanceMethods)
|
14
|
+
extend(ClassMethods)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module InstanceMethods
|
19
|
+
def exec_migration(conn, direction)
|
20
|
+
# lock_timeout is an instance accessor created by class_attribute
|
21
|
+
lock_timeout_ms = lock_timeout || SaferMigrations.default_lock_timeout
|
22
|
+
statement_timeout_ms = statement_timeout || SaferMigrations.default_statement_timeout
|
23
|
+
SettingHelper.new(conn, :lock_timeout, lock_timeout_ms).with_setting do
|
24
|
+
SettingHelper.new(conn, :statement_timeout, statement_timeout_ms).with_setting do
|
25
|
+
super(conn, direction)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def set_lock_timeout(timeout)
|
33
|
+
if timeout == 0
|
34
|
+
raise "Setting lock_timeout to 0 is dangerous - it disables the lock " \
|
35
|
+
"timeout rather than instantly timing out. If you *actually* " \
|
36
|
+
"want to disable the lock timeout (not recommended!), use the " \
|
37
|
+
"`disable_lock_timeout!` method."
|
38
|
+
end
|
39
|
+
self.lock_timeout = timeout
|
40
|
+
end
|
41
|
+
|
42
|
+
def disable_lock_timeout!
|
43
|
+
say "WARNING: disabling the lock timeout. This is very dangerous."
|
44
|
+
self.lock_timeout = 0
|
45
|
+
end
|
46
|
+
|
47
|
+
def set_statement_timeout(timeout)
|
48
|
+
if timeout == 0
|
49
|
+
raise "Setting statement_timeout to 0 is dangerous - it disables the statement " \
|
50
|
+
"timeout rather than instantly timing out. If you *actually* " \
|
51
|
+
"want to disable the statement timeout (not recommended!), use the " \
|
52
|
+
"`disable_statement_timeout!` method."
|
53
|
+
end
|
54
|
+
self.statement_timeout = timeout
|
55
|
+
end
|
56
|
+
|
57
|
+
def disable_statement_timeout!
|
58
|
+
say "WARNING: disabling the statement timeout. This is very dangerous."
|
59
|
+
self.statement_timeout = 0
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module SaferMigrations
|
3
|
+
module PostgreSQLAdapter
|
4
|
+
SET_SETTING_SQL = <<-SQL
|
5
|
+
UPDATE
|
6
|
+
pg_settings
|
7
|
+
SET
|
8
|
+
setting = :value
|
9
|
+
WHERE
|
10
|
+
name = :setting_name
|
11
|
+
SQL
|
12
|
+
|
13
|
+
GET_SETTING_SQL = <<-SQL
|
14
|
+
SELECT
|
15
|
+
setting
|
16
|
+
FROM
|
17
|
+
pg_settings
|
18
|
+
WHERE
|
19
|
+
name = :setting_name
|
20
|
+
SQL
|
21
|
+
|
22
|
+
def set_setting(setting_name, value)
|
23
|
+
sql = fill_sql_values(SET_SETTING_SQL, value: value, setting_name: setting_name)
|
24
|
+
execute(sql)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_setting(setting_name)
|
28
|
+
sql = fill_sql_values(GET_SETTING_SQL, setting_name: setting_name)
|
29
|
+
result = execute(sql)
|
30
|
+
result.first["setting"]
|
31
|
+
end
|
32
|
+
|
33
|
+
def fill_sql_values(sql, values)
|
34
|
+
ActiveRecord::Base.send(:replace_named_bind_variables, sql, values)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module SaferMigrations
|
3
|
+
class SettingHelper
|
4
|
+
def initialize(connection, setting_name, value)
|
5
|
+
@connection = connection
|
6
|
+
@setting_name = setting_name
|
7
|
+
@value = value
|
8
|
+
end
|
9
|
+
|
10
|
+
# We're changing a connection level setting, and we need to make sure we return
|
11
|
+
# it to the original value. It is automatically reverted if set within a
|
12
|
+
# transaction which rolls back, so that case needs handling differently.
|
13
|
+
#
|
14
|
+
# | In Transaction | Not in transaction
|
15
|
+
# ---------------------------------------------------------
|
16
|
+
# Raises | Reset setting | Reset setting
|
17
|
+
# Doesn't raise | Don't reset setting | Reset setting
|
18
|
+
def with_setting
|
19
|
+
record_current_setting
|
20
|
+
set_new_setting
|
21
|
+
yield
|
22
|
+
reset_setting
|
23
|
+
rescue
|
24
|
+
reset_setting unless in_transaction?
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def record_current_setting
|
31
|
+
@original_value = @connection.get_setting(@setting_name)
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_new_setting
|
35
|
+
puts "-- set_setting(#{@setting_name.inspect}, #{@value})"
|
36
|
+
@connection.set_setting(@setting_name, @value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset_setting
|
40
|
+
puts "-- set_setting(#{@setting_name.inspect}, #{@original_value})"
|
41
|
+
@connection.set_setting(@setting_name, @original_value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def in_transaction?
|
45
|
+
ActiveRecord::Base.connection.open_transactions > 0
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "active_record/connection_adapters/postgresql_adapter"
|
2
|
+
require "active_record/safer_migrations/postgresql_adapter"
|
3
|
+
require "active_record/safer_migrations/migration"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module SaferMigrations
|
7
|
+
@default_lock_timeout = 750
|
8
|
+
@default_statement_timeout = 1500
|
9
|
+
|
10
|
+
def self.default_lock_timeout
|
11
|
+
@default_lock_timeout
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.default_lock_timeout=(timeout_ms)
|
15
|
+
@default_lock_timeout = timeout_ms
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.default_statement_timeout
|
19
|
+
@default_statement_timeout
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.default_statement_timeout=(timeout_ms)
|
23
|
+
@default_statement_timeout = timeout_ms
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.load
|
27
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
|
28
|
+
include ActiveRecord::SaferMigrations::PostgreSQLAdapter
|
29
|
+
end
|
30
|
+
|
31
|
+
ActiveRecord::Migration.class_eval do
|
32
|
+
include ActiveRecord::SaferMigrations::Migration
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require "active_record/safer_migrations/railtie" if defined?(::Rails)
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe ActiveRecord::SaferMigrations::Migration do
|
4
|
+
before { nuke_migrations }
|
5
|
+
before { TimeoutTestHelpers.set(:lock_timeout, 0) }
|
6
|
+
before { TimeoutTestHelpers.set(:statement_timeout, 0) }
|
7
|
+
|
8
|
+
describe "setting timeouts explicitly" do
|
9
|
+
before { $lock_timeout = nil }
|
10
|
+
before { $statement_timeout = nil }
|
11
|
+
|
12
|
+
shared_examples_for "running the migration" do
|
13
|
+
let(:migration) do
|
14
|
+
Class.new(ActiveRecord::Migration) do
|
15
|
+
set_lock_timeout(5000)
|
16
|
+
set_statement_timeout(5001)
|
17
|
+
|
18
|
+
def change
|
19
|
+
$lock_timeout = TimeoutTestHelpers.get(:lock_timeout)
|
20
|
+
$statement_timeout = TimeoutTestHelpers.get(:statement_timeout)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "sets the lock timeout for the duration of the migration" do
|
26
|
+
silence_stream($stdout) { run_migration.call }
|
27
|
+
expect($lock_timeout).to eq(5000)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "unsets the lock timeout after the migration" do
|
31
|
+
silence_stream($stdout) { run_migration.call }
|
32
|
+
expect(TimeoutTestHelpers.get(:lock_timeout)).to eq(0)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "sets the statement timeout for the duration of the migration" do
|
36
|
+
silence_stream($stdout) { run_migration.call }
|
37
|
+
expect($statement_timeout).to eq(5001)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "unsets the statement timeout after the migration" do
|
41
|
+
silence_stream($stdout) { run_migration.call }
|
42
|
+
expect(TimeoutTestHelpers.get(:statement_timeout)).to eq(0)
|
43
|
+
end
|
44
|
+
|
45
|
+
context "when the original timeout is not 0" do
|
46
|
+
before { TimeoutTestHelpers.set(:lock_timeout, 8000) }
|
47
|
+
before { TimeoutTestHelpers.set(:statement_timeout, 8001) }
|
48
|
+
|
49
|
+
it "unsets the lock timeout after the migration" do
|
50
|
+
silence_stream($stdout) { run_migration.call }
|
51
|
+
expect(TimeoutTestHelpers.get(:lock_timeout)).to eq(8000)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "unsets the statement timeout after the migration" do
|
55
|
+
silence_stream($stdout) { run_migration.call }
|
56
|
+
expect(TimeoutTestHelpers.get(:statement_timeout)).to eq(8001)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when running with transactional DDL" do
|
62
|
+
let(:run_migration) do
|
63
|
+
-> { ActiveRecord::Base.transaction { migration.migrate(:up) } }
|
64
|
+
end
|
65
|
+
|
66
|
+
include_examples "running the migration"
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when running without transactional DDL" do
|
70
|
+
let(:run_migration) { -> { migration.migrate(:up) } }
|
71
|
+
|
72
|
+
include_examples "running the migration"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "the default timeouts" do
|
77
|
+
before { $lock_timeout = nil }
|
78
|
+
before { $statement_timeout = nil }
|
79
|
+
before { ActiveRecord::SaferMigrations.default_lock_timeout = 6000 }
|
80
|
+
before { ActiveRecord::SaferMigrations.default_statement_timeout = 6001 }
|
81
|
+
let(:migration) do
|
82
|
+
Class.new(ActiveRecord::Migration) do
|
83
|
+
def change
|
84
|
+
$lock_timeout = TimeoutTestHelpers.get(:lock_timeout)
|
85
|
+
$statement_timeout = TimeoutTestHelpers.get(:statement_timeout)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "sets the lock timeout for the duration of the migration" do
|
91
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
92
|
+
expect($lock_timeout).to eq(6000)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "unsets the lock timeout after the migration" do
|
96
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
97
|
+
expect(TimeoutTestHelpers.get(:lock_timeout)).to eq(0)
|
98
|
+
end
|
99
|
+
|
100
|
+
it "sets the statement timeout for the duration of the migration" do
|
101
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
102
|
+
expect($statement_timeout).to eq(6001)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "unsets the statement timeout after the migration" do
|
106
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
107
|
+
expect(TimeoutTestHelpers.get(:statement_timeout)).to eq(0)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "when inheriting from a migration with timeouts defined" do
|
112
|
+
before { $lock_timeout = nil }
|
113
|
+
before { $statement_timeout = nil }
|
114
|
+
before { ActiveRecord::SaferMigrations.default_lock_timeout = 6000 }
|
115
|
+
before { ActiveRecord::SaferMigrations.default_statement_timeout = 6001 }
|
116
|
+
let(:base_migration) do
|
117
|
+
Class.new(ActiveRecord::Migration) do
|
118
|
+
set_lock_timeout(7000)
|
119
|
+
set_statement_timeout(7001)
|
120
|
+
def change
|
121
|
+
$lock_timeout = TimeoutTestHelpers.get(:lock_timeout)
|
122
|
+
$statement_timeout = TimeoutTestHelpers.get(:statement_timeout)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "when the timeout isn't overridden" do
|
128
|
+
let(:migration) { Class.new(base_migration) {} }
|
129
|
+
|
130
|
+
it "sets the base class' lock timeout for the duration of the migration" do
|
131
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
132
|
+
expect($lock_timeout).to eq(7000)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "sets the base class' statement timeout for the duration of the migration" do
|
136
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
137
|
+
expect($statement_timeout).to eq(7001)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context "when the timeout is overridden" do
|
142
|
+
let(:migration) do
|
143
|
+
Class.new(base_migration) do
|
144
|
+
set_lock_timeout(8000)
|
145
|
+
set_statement_timeout(8001)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it "sets the subclass' lock timeout for the duration of the migration" do
|
150
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
151
|
+
expect($lock_timeout).to eq(8000)
|
152
|
+
end
|
153
|
+
|
154
|
+
it "sets the subclass' statement timeout for the duration of the migration" do
|
155
|
+
silence_stream($stdout) { migration.migrate(:up) }
|
156
|
+
expect($statement_timeout).to eq(8001)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "activerecord-safer_migrations"
|
3
|
+
|
4
|
+
ActiveRecord::SaferMigrations.load
|
5
|
+
ActiveRecord::Base.establish_connection
|
6
|
+
|
7
|
+
def silence_stream(stream)
|
8
|
+
old_stream = stream.dup
|
9
|
+
stream.reopen(IO::NULL)
|
10
|
+
stream.sync = true
|
11
|
+
yield
|
12
|
+
ensure
|
13
|
+
stream.reopen(old_stream)
|
14
|
+
old_stream.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def nuke_migrations
|
18
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
19
|
+
conn.execute("DROP TABLE IF EXISTS schema_migrations")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module TimeoutTestHelpers
|
24
|
+
def self.get(timeout_name)
|
25
|
+
sql = <<-SQL
|
26
|
+
SELECT
|
27
|
+
setting AS #{timeout_name}
|
28
|
+
FROM
|
29
|
+
pg_settings
|
30
|
+
WHERE
|
31
|
+
name = '#{timeout_name}'
|
32
|
+
SQL
|
33
|
+
ActiveRecord::Base.connection.execute(sql).first[timeout_name.to_s].to_i
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.set(timeout_name, timeout)
|
37
|
+
ActiveRecord::Base.connection.execute("SET #{timeout_name} = #{timeout}")
|
38
|
+
end
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-safer_migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- GoCardless Engineering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.2'
|
27
|
+
description: ''
|
28
|
+
email: developers@gocardless.com
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- .rubocop.yml
|
35
|
+
- .ruby-version
|
36
|
+
- .travis.yml
|
37
|
+
- Gemfile
|
38
|
+
- Gemfile.lock
|
39
|
+
- LICENSE.txt
|
40
|
+
- README.md
|
41
|
+
- activerecord-safer_migrations.gemspec
|
42
|
+
- lib/active_record/safer_migrations/migration.rb
|
43
|
+
- lib/active_record/safer_migrations/postgresql_adapter.rb
|
44
|
+
- lib/active_record/safer_migrations/railtie.rb
|
45
|
+
- lib/active_record/safer_migrations/setting_helper.rb
|
46
|
+
- lib/active_record/safer_migrations/version.rb
|
47
|
+
- lib/activerecord-safer_migrations.rb
|
48
|
+
- spec/active_record/safer_migrations/migration_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
homepage: https://github.com/gocardless/activerecord-safer_migrations
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.0.14
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: ActiveRecord migration helpers to avoid downtime
|
74
|
+
test_files: []
|