mysql_online_migrations 0.1.3
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 +21 -0
- data/.rspec +2 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +54 -0
- data/LICENSE +20 -0
- data/README.md +60 -0
- data/lib/mysql_online_migrations.rb +33 -0
- data/lib/mysql_online_migrations/columns.rb +48 -0
- data/lib/mysql_online_migrations/indexes.rb +21 -0
- data/mysql_online_migrations.gemspec +21 -0
- data/spec/lib/mysql_online_migrations/columns_spec.rb +110 -0
- data/spec/lib/mysql_online_migrations/indexes_spec.rb +67 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/helpers.rb +73 -0
- data/spec/support/shared_examples/mysql_online_migrations.rb +44 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8b8c18f26ec8b8f57c2d469d2e11e3837de24130
|
4
|
+
data.tar.gz: 3a65090310d0a049c85ad22da8099ea46939a913
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3bb7753d1b88a3a77cddf0f23a2a2b6cfe3717821dc625b0fb5024bd4cce25fc45aecd66a75cbb1e8b16464fb8cdd11da1e6b548e57a2c38adc7ec0dd2e77c6a
|
7
|
+
data.tar.gz: a631129c1c90cddd1528658c37a815749a0954882fdf8ddcf387bdf1f7c555fd63dd5ecf18ca2b60443833a9df33fdd96f98a2bfe6a08f7c3e8691b09d544f76
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
mysql_online_migrations (0.1.2)
|
5
|
+
activerecord (~> 3.2.15)
|
6
|
+
activesupport (~> 3.2.15)
|
7
|
+
mysql2
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: http://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activemodel (3.2.15)
|
13
|
+
activesupport (= 3.2.15)
|
14
|
+
builder (~> 3.0.0)
|
15
|
+
activerecord (3.2.15)
|
16
|
+
activemodel (= 3.2.15)
|
17
|
+
activesupport (= 3.2.15)
|
18
|
+
arel (~> 3.0.2)
|
19
|
+
tzinfo (~> 0.3.29)
|
20
|
+
activesupport (3.2.15)
|
21
|
+
i18n (~> 0.6, >= 0.6.4)
|
22
|
+
multi_json (~> 1.0)
|
23
|
+
arel (3.0.3)
|
24
|
+
builder (3.0.4)
|
25
|
+
coderay (1.0.9)
|
26
|
+
diff-lcs (1.2.5)
|
27
|
+
i18n (0.6.5)
|
28
|
+
logger (1.2.8)
|
29
|
+
method_source (0.8.2)
|
30
|
+
multi_json (1.8.2)
|
31
|
+
mysql2 (0.3.14)
|
32
|
+
pry (0.9.12.2)
|
33
|
+
coderay (~> 1.0.5)
|
34
|
+
method_source (~> 0.8)
|
35
|
+
slop (~> 3.4)
|
36
|
+
rspec (2.14.1)
|
37
|
+
rspec-core (~> 2.14.0)
|
38
|
+
rspec-expectations (~> 2.14.0)
|
39
|
+
rspec-mocks (~> 2.14.0)
|
40
|
+
rspec-core (2.14.7)
|
41
|
+
rspec-expectations (2.14.4)
|
42
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
43
|
+
rspec-mocks (2.14.4)
|
44
|
+
slop (3.4.6)
|
45
|
+
tzinfo (0.3.38)
|
46
|
+
|
47
|
+
PLATFORMS
|
48
|
+
ruby
|
49
|
+
|
50
|
+
DEPENDENCIES
|
51
|
+
logger
|
52
|
+
mysql_online_migrations!
|
53
|
+
pry
|
54
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Anthony Alberto
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
mysql_online_migrations
|
2
|
+
=======================
|
3
|
+
|
4
|
+
Patch Rails migrations to support MySQL 5.6 online migrations capabilities.
|
5
|
+
Prior to MySQL 5.6, when adding / removing / renaming indexes and columns, MySQL would lock the writes of the whole table.
|
6
|
+
MySQL 5.6 adds a way to append `LOCK=NONE` to those alter table statements to allow online migrations.
|
7
|
+
|
8
|
+
|
9
|
+
Requirements
|
10
|
+
=======================
|
11
|
+
Built for Rails 3.2.15, may be compatible with Rails 4.0 but you'd lose the new features introduced.
|
12
|
+
This gem actually just requires ActiveRecord and ActiveSupport, the full Rails gem should not be required.
|
13
|
+
|
14
|
+
List of requirements :
|
15
|
+
|
16
|
+
- Use mysql2 adapter
|
17
|
+
- Use ActiveRecord "~> 3.2.15"
|
18
|
+
- Use MySQL or Percona Server 5.6.X
|
19
|
+
|
20
|
+
Scope of this gem
|
21
|
+
=======================
|
22
|
+
|
23
|
+
Patch Rails migrations to automatically add `LOCK=NONE` in the following context :
|
24
|
+
|
25
|
+
- Index management : `add_index`, `remove_index`, `rename_index`
|
26
|
+
- Add column : `add_column`, `add_timestamps`
|
27
|
+
- Remove column : `remove_column`, `remove_timestamps`
|
28
|
+
|
29
|
+
Usage
|
30
|
+
=======================
|
31
|
+
In a typical Rails app, just add it to your gem file :
|
32
|
+
`gem 'mysql_online_migrations'`
|
33
|
+
|
34
|
+
And you're ready for online migrations!
|
35
|
+
|
36
|
+
### Turn it off for a whole environment
|
37
|
+
Example for environment test (your CI might not use MySQL 5.6 yet), add the following to `config/environments/test.rb`:
|
38
|
+
`config.active_record.mysql_online_migrations = false`
|
39
|
+
|
40
|
+
### Turn it off for a specific statement
|
41
|
+
Add `lock: true` to any of the method calls mentioned above. Example :
|
42
|
+
`add_index :users, :name, lock: true`
|
43
|
+
|
44
|
+
The `lock: none` will be useful when hitting the caveats of `LOCK=NONE`. Please read the following section.
|
45
|
+
|
46
|
+
Caveats
|
47
|
+
=======================
|
48
|
+
|
49
|
+
Here's a list of things you can't do with LOCK=NONE and therefore you should provide `lock: true`:
|
50
|
+
|
51
|
+
- Index a column of type text
|
52
|
+
- Change the type of a column
|
53
|
+
- Change the length of a column
|
54
|
+
- Change the nullable value of a column
|
55
|
+
- When adding an AUTO_INCREMENT column,
|
56
|
+
- Other stuff found on https://blogs.oracle.com/mysqlinnodb/entry/online_alter_table_in_mysql :
|
57
|
+
- when the table contains FULLTEXT indexes or a hidden FTS_DOC_ID column, or
|
58
|
+
- when there are FOREIGN KEY constraints referring to the table, with ON…CASCADE or ON…SET NULL option.
|
59
|
+
|
60
|
+
If you don't use `lock: true` when it's not supported, you'll get a MySQL exception. No risk to lock the table by accident.
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require "active_record/connection_adapters/mysql2_adapter"
|
3
|
+
|
4
|
+
%w(*.rb).each do |path|
|
5
|
+
Dir["#{File.dirname(__FILE__)}/mysql_online_migrations/#{path}"].each { |f| require(f) }
|
6
|
+
end
|
7
|
+
|
8
|
+
module MysqlOnlineMigrations
|
9
|
+
include Indexes
|
10
|
+
include Columns
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
ActiveRecord::Base.send(:class_attribute, :mysql_online_migrations, :instance_writer => false)
|
14
|
+
ActiveRecord::Base.send("mysql_online_migrations=", true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock_statement(lock, with_comma = false)
|
18
|
+
return "" if lock == true
|
19
|
+
return "" unless perform_migrations_online?
|
20
|
+
puts "ONLINE MIGRATION"
|
21
|
+
"#{with_comma ? ', ' : ''} LOCK=NONE"
|
22
|
+
end
|
23
|
+
|
24
|
+
def extract_lock_from_options(options)
|
25
|
+
[options[:lock], options.except(:lock)]
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_migrations_online?
|
29
|
+
ActiveRecord::Base.mysql_online_migrations == true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActiveRecord::ConnectionAdapters::Mysql2Adapter.send(:include, MysqlOnlineMigrations)
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module MysqlOnlineMigrations
|
2
|
+
module Columns
|
3
|
+
def add_column(table_name, column_name, type, options = {})
|
4
|
+
lock, options = extract_lock_from_options(options)
|
5
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{add_column_sql(table_name, column_name, type, options)} #{lock_statement(lock, true)}")
|
6
|
+
end
|
7
|
+
|
8
|
+
def add_timestamps(table_name, options = {})
|
9
|
+
add_column table_name, :created_at, :datetime, options
|
10
|
+
add_column table_name, :updated_at, :datetime, options
|
11
|
+
end
|
12
|
+
|
13
|
+
def change_column(table_name, column_name, type, options = {})
|
14
|
+
lock, options = extract_lock_from_options(options)
|
15
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)} #{lock_statement(lock, true)}")
|
16
|
+
end
|
17
|
+
|
18
|
+
def rename_column(table_name, column_name, new_column_name, options = {})
|
19
|
+
lock, options = extract_lock_from_options(options)
|
20
|
+
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)} #{lock_statement(lock, true)}")
|
21
|
+
end
|
22
|
+
|
23
|
+
def remove_column(table_name, *column_names)
|
24
|
+
if column_names.flatten!
|
25
|
+
message = 'Passing array to remove_columns is deprecated, please use ' +
|
26
|
+
'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
|
27
|
+
ActiveSupport::Deprecation.warn message, caller
|
28
|
+
end
|
29
|
+
|
30
|
+
lock, options = if column_names.last.is_a? Hash
|
31
|
+
options = column_names.last
|
32
|
+
column_names = column_names[0..-2]
|
33
|
+
extract_lock_from_options(options)
|
34
|
+
else
|
35
|
+
[false, {}]
|
36
|
+
end
|
37
|
+
|
38
|
+
columns_for_remove(table_name, *column_names).each do |column_name|
|
39
|
+
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name} #{lock_statement(lock, true)}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_timestamps(table_name, options = {})
|
44
|
+
remove_column table_name, :updated_at, options
|
45
|
+
remove_column table_name, :created_at, options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module MysqlOnlineMigrations
|
2
|
+
module Indexes
|
3
|
+
def add_index(table_name, column_name, options = {})
|
4
|
+
lock, options = extract_lock_from_options(options)
|
5
|
+
index_name, index_type, index_columns = add_index_options(table_name, column_name, options)
|
6
|
+
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} (#{index_columns}) #{lock_statement(lock)}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_index(table_name, options_index_name, options = {})
|
10
|
+
lock, options = extract_lock_from_options(options)
|
11
|
+
execute "DROP INDEX #{quote_column_name(index_name_for_remove(table_name, options_index_name))} ON #{quote_table_name(table_name)} #{lock_statement(lock)}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def rename_index(table_name, old_name, new_name, options = {})
|
15
|
+
old_index_def = indexes(table_name).detect { |i| i.name == old_name }
|
16
|
+
return unless old_index_def
|
17
|
+
remove_index(table_name, { :name => old_name }, options)
|
18
|
+
add_index(table_name, old_index_def.columns, options.merge(name: new_name, unique: old_index_def.unique))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'mysql_online_migrations'
|
3
|
+
s.version = '0.1.3'
|
4
|
+
s.summary = "Use MySQL 5.6+ capacities to perform online migrations"
|
5
|
+
s.description = "MySQL 5.6 adds a way to append `LOCK=NONE` to alter table statements to allow online migrations. Let's use it."
|
6
|
+
s.authors = ["Anthony Alberto"]
|
7
|
+
s.email = 'alberto.anthony@gmail.com'
|
8
|
+
s.homepage = 'https://github.com/anthonyalberto/mysql_online_migrations'
|
9
|
+
|
10
|
+
s.add_runtime_dependency "activerecord", "~> 3.2.15"
|
11
|
+
s.add_runtime_dependency "activesupport", "~> 3.2.15"
|
12
|
+
s.add_runtime_dependency "mysql2"
|
13
|
+
s.add_development_dependency "logger"
|
14
|
+
s.add_development_dependency "rspec"
|
15
|
+
s.add_development_dependency "pry"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MysqlOnlineMigrations::Columns do
|
4
|
+
let(:comma_before_lock_none) { true }
|
5
|
+
|
6
|
+
context "#add_column" do
|
7
|
+
let(:queries) do
|
8
|
+
{
|
9
|
+
[:testing, :foo2, :string, {}] =>
|
10
|
+
"ALTER TABLE `testing` ADD `foo2` varchar(255)",
|
11
|
+
[:testing, :foo2, :string, { limit: 20, null: false, default: 'def' }] =>
|
12
|
+
"ALTER TABLE `testing` ADD `foo2` varchar(20) DEFAULT 'def' NOT NULL",
|
13
|
+
[:testing, :foo2, :decimal, { precision:3, scale: 2 }] =>
|
14
|
+
"ALTER TABLE `testing` ADD `foo2` decimal(3,2)",
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
let(:method_name) { :add_column }
|
19
|
+
|
20
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
21
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
22
|
+
end
|
23
|
+
|
24
|
+
context "#add_timestamps" do
|
25
|
+
let(:queries) do
|
26
|
+
{
|
27
|
+
[:testing2, {}] =>
|
28
|
+
[
|
29
|
+
"ALTER TABLE `testing2` ADD `created_at` datetime",
|
30
|
+
"ALTER TABLE `testing2` ADD `updated_at` datetime",
|
31
|
+
]
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
let(:method_name) { :add_timestamps }
|
36
|
+
|
37
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
38
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#remove_column" do
|
42
|
+
let(:queries) do
|
43
|
+
{
|
44
|
+
[:testing, :foo, {}] =>
|
45
|
+
"ALTER TABLE `testing` DROP `foo`",
|
46
|
+
[:testing, [:foo, :bar], {}] =>
|
47
|
+
[
|
48
|
+
"ALTER TABLE `testing` DROP `foo`",
|
49
|
+
"ALTER TABLE `testing` DROP `bar`"
|
50
|
+
],
|
51
|
+
[:testing, :foo, :bar, {}] =>
|
52
|
+
[
|
53
|
+
"ALTER TABLE `testing` DROP `foo`",
|
54
|
+
"ALTER TABLE `testing` DROP `bar`"
|
55
|
+
]
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
let(:method_name) { :remove_column }
|
60
|
+
|
61
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
62
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
63
|
+
end
|
64
|
+
|
65
|
+
context "#remove_timestamps" do
|
66
|
+
let(:queries) do
|
67
|
+
{
|
68
|
+
[:testing, {}] =>
|
69
|
+
[
|
70
|
+
"ALTER TABLE `testing` DROP `created_at`",
|
71
|
+
"ALTER TABLE `testing` DROP `updated_at`",
|
72
|
+
]
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
let(:method_name) { :remove_timestamps }
|
77
|
+
|
78
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
79
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
80
|
+
end
|
81
|
+
|
82
|
+
context "#change_column" do
|
83
|
+
let(:queries) do
|
84
|
+
# Unsupported with lock=none : change column type, change limit, change null
|
85
|
+
{
|
86
|
+
[:testing, :foo, :string, { default: 'def', limit: 100 }] =>
|
87
|
+
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(100) DEFAULT 'def'",
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
let(:method_name) { :change_column }
|
92
|
+
|
93
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
94
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
95
|
+
end
|
96
|
+
|
97
|
+
context "#rename_column" do
|
98
|
+
let(:queries) do
|
99
|
+
{
|
100
|
+
[:testing, :foo, :foo2, {}] =>
|
101
|
+
"ALTER TABLE `testing` CHANGE `foo` `foo2` varchar(100) DEFAULT NULL"
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
let(:method_name) { :rename_column }
|
106
|
+
|
107
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
108
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MysqlOnlineMigrations::Indexes do
|
4
|
+
let(:comma_before_lock_none) { false }
|
5
|
+
|
6
|
+
context "#add_index" do
|
7
|
+
let(:queries) do
|
8
|
+
{
|
9
|
+
[:testing, :foo, {}] =>
|
10
|
+
"CREATE INDEX `index_testing_on_foo` ON `testing` (`foo`)",
|
11
|
+
[:testing, :foo, { length: 10 }] =>
|
12
|
+
"CREATE INDEX `index_testing_on_foo` ON `testing` (`foo`(10))",
|
13
|
+
[:testing, [:foo, :bar, :baz], {}] =>
|
14
|
+
"CREATE INDEX `index_testing_on_foo_and_bar_and_baz` ON `testing` (`foo`, `bar`, `baz`)",
|
15
|
+
[:testing, [:foo, :bar, :baz], { unique: true }] =>
|
16
|
+
"CREATE UNIQUE INDEX `index_testing_on_foo_and_bar_and_baz` ON `testing` (`foo`, `bar`, `baz`)",
|
17
|
+
[:testing, [:foo, :bar, :baz], { unique: true, name: "best_index_of_the_world" }] =>
|
18
|
+
"CREATE UNIQUE INDEX `best_index_of_the_world` ON `testing` (`foo`, `bar`, `baz`)",
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:method_name) { :add_index }
|
23
|
+
|
24
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
25
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
26
|
+
end
|
27
|
+
|
28
|
+
context "#remove_index" do
|
29
|
+
let(:queries) do
|
30
|
+
{
|
31
|
+
[:testing, :baz, {}] =>
|
32
|
+
"DROP INDEX `index_testing_on_baz` ON `testing`",
|
33
|
+
[:testing, [:bar, :baz], {}] =>
|
34
|
+
"DROP INDEX `index_testing_on_bar_and_baz` ON `testing`",
|
35
|
+
[:testing, { column: [:bar, :baz] }, {}] =>
|
36
|
+
"DROP INDEX `index_testing_on_bar_and_baz` ON `testing`",
|
37
|
+
[:testing, { name: "best_index_of_the_world2" }, {}] =>
|
38
|
+
"DROP INDEX `best_index_of_the_world2` ON `testing`"
|
39
|
+
}
|
40
|
+
end
|
41
|
+
let(:method_name) { :remove_index }
|
42
|
+
|
43
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
44
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
45
|
+
end
|
46
|
+
|
47
|
+
context "#rename_index" do
|
48
|
+
let(:queries) do
|
49
|
+
{
|
50
|
+
[:testing, "best_index_of_the_world2", "renamed_best_index_of_the_world2", {}] =>
|
51
|
+
[
|
52
|
+
"DROP INDEX `best_index_of_the_world2` ON `testing`",
|
53
|
+
"CREATE INDEX `renamed_best_index_of_the_world2` ON `testing` (`extra`)",
|
54
|
+
],
|
55
|
+
[:testing, "best_index_of_the_world3", "renamed_best_index_of_the_world3", {}] =>
|
56
|
+
[
|
57
|
+
"DROP INDEX `best_index_of_the_world3` ON `testing`",
|
58
|
+
"CREATE UNIQUE INDEX `renamed_best_index_of_the_world3` ON `testing` (`baz`, `extra`)",
|
59
|
+
]
|
60
|
+
}
|
61
|
+
end
|
62
|
+
let(:method_name) { :rename_index }
|
63
|
+
|
64
|
+
it_behaves_like "a method that adds LOCK=NONE when needed"
|
65
|
+
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
66
|
+
end
|
67
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
# require 'active_record'
|
8
|
+
require 'rubygems'
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'mysql_online_migrations'
|
11
|
+
require 'logger'
|
12
|
+
require 'pry'
|
13
|
+
require 'support/helpers'
|
14
|
+
require 'support/shared_examples/mysql_online_migrations'
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
18
|
+
config.run_all_when_everything_filtered = true
|
19
|
+
config.filter_run :focus
|
20
|
+
|
21
|
+
config.include Helpers
|
22
|
+
|
23
|
+
# Run specs in random order to surface order dependencies. If you find an
|
24
|
+
# order dependency and want to debug it, you can fix the order by providing
|
25
|
+
# the seed, which is printed after each run.
|
26
|
+
# --seed 1234
|
27
|
+
#config.order = 'random'
|
28
|
+
|
29
|
+
config.before(:all) do
|
30
|
+
setup
|
31
|
+
end
|
32
|
+
|
33
|
+
config.after(:all) do
|
34
|
+
teardown
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Helpers
|
2
|
+
def execute(statement)
|
3
|
+
end
|
4
|
+
|
5
|
+
def unstub_execute
|
6
|
+
@adapter.unstub(:execute)
|
7
|
+
end
|
8
|
+
|
9
|
+
def stub_execute
|
10
|
+
original_execute = @adapter.method(:execute)
|
11
|
+
@adapter.stub(:execute) do |statement|
|
12
|
+
if statement =~ /^(alter|create|drop) /i
|
13
|
+
execute(statement.squeeze(' ').strip)
|
14
|
+
else
|
15
|
+
original_execute.call(statement)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_lock_none(str, with_comma)
|
21
|
+
"#{str}#{with_comma ? ' ,' : ''} LOCK=NONE"
|
22
|
+
end
|
23
|
+
|
24
|
+
def rebuild_table
|
25
|
+
@table_name = :testing
|
26
|
+
@adapter.drop_table @table_name rescue nil
|
27
|
+
@adapter.create_table @table_name do |t|
|
28
|
+
t.column :foo, :string, :limit => 100
|
29
|
+
t.column :bar, :string, :limit => 100
|
30
|
+
t.column :baz, :string, :limit => 100
|
31
|
+
t.column :extra, :string, :limit => 100
|
32
|
+
t.timestamps
|
33
|
+
end
|
34
|
+
|
35
|
+
@table_name = :testing2
|
36
|
+
@adapter.drop_table @table_name rescue nil
|
37
|
+
@adapter.create_table @table_name do |t|
|
38
|
+
end
|
39
|
+
|
40
|
+
@adapter.add_index :testing, :baz
|
41
|
+
@adapter.add_index :testing, [:bar, :baz]
|
42
|
+
@adapter.add_index :testing, :extra, name: "best_index_of_the_world2"
|
43
|
+
@adapter.add_index :testing, [:baz, :extra], name: "best_index_of_the_world3", unique: true
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup
|
47
|
+
ActiveRecord::Base.establish_connection(
|
48
|
+
adapter: :mysql2,
|
49
|
+
reconnect: false,
|
50
|
+
database: "mysql_online_migrations",
|
51
|
+
username: "root",
|
52
|
+
host: "localhost",
|
53
|
+
encoding: "utf8",
|
54
|
+
socket: "/tmp/mysql.sock"
|
55
|
+
)
|
56
|
+
|
57
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
58
|
+
ActiveRecord::Base.logger.level = Logger::INFO
|
59
|
+
|
60
|
+
@adapter = ActiveRecord::Base.connection
|
61
|
+
|
62
|
+
rebuild_table
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_ar_setting(value)
|
66
|
+
ActiveRecord::Base.stub(:mysql_online_migrations).and_return(value)
|
67
|
+
end
|
68
|
+
|
69
|
+
def teardown
|
70
|
+
@adapter.drop_table :testing rescue nil
|
71
|
+
ActiveRecord::Base.primary_key_prefix_type = nil
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
shared_examples_for "a method that adds LOCK=NONE when needed" do
|
2
|
+
before(:each) do
|
3
|
+
stub_execute
|
4
|
+
end
|
5
|
+
|
6
|
+
it "adds LOCK=NONE at the end of the query" do
|
7
|
+
queries.each do |arguments, output|
|
8
|
+
Array.wrap(output).each { |out| should_receive(:execute).with(add_lock_none(out, comma_before_lock_none)) }
|
9
|
+
@adapter.public_send(method_name, *arguments)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context "with AR global setting set to false" do
|
14
|
+
before(:each) { set_ar_setting(false) }
|
15
|
+
|
16
|
+
it "doesn't add lock none" do
|
17
|
+
queries.each do |arguments, output|
|
18
|
+
Array.wrap(output).each { |out| should_receive(:execute).with(out) }
|
19
|
+
@adapter.public_send(method_name, *arguments)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context "with lock: true option" do
|
25
|
+
it "doesn't add lock none" do
|
26
|
+
queries.each do |arguments, output|
|
27
|
+
Array.wrap(output).each { |out| should_receive(:execute).with(out) }
|
28
|
+
arguments[-1] = arguments.last.merge(lock: true)
|
29
|
+
@adapter.public_send(method_name, *arguments)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
shared_examples_for "a request with LOCK=NONE that doesn't crash in MySQL" do
|
36
|
+
it "succeeds without exception" do
|
37
|
+
queries.each do |_, output|
|
38
|
+
Array.wrap(output).each do |out|
|
39
|
+
@adapter.execute(add_lock_none(out, comma_before_lock_none))
|
40
|
+
rebuild_table
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
metadata
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mysql_online_migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Anthony Alberto
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-24 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: 3.2.15
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 3.2.15
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activesupport
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.2.15
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.2.15
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: mysql2
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: logger
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: MySQL 5.6 adds a way to append `LOCK=NONE` to alter table statements
|
98
|
+
to allow online migrations. Let's use it.
|
99
|
+
email: alberto.anthony@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- .gitignore
|
105
|
+
- .rspec
|
106
|
+
- Gemfile
|
107
|
+
- Gemfile.lock
|
108
|
+
- LICENSE
|
109
|
+
- README.md
|
110
|
+
- lib/mysql_online_migrations.rb
|
111
|
+
- lib/mysql_online_migrations/columns.rb
|
112
|
+
- lib/mysql_online_migrations/indexes.rb
|
113
|
+
- mysql_online_migrations.gemspec
|
114
|
+
- spec/lib/mysql_online_migrations/columns_spec.rb
|
115
|
+
- spec/lib/mysql_online_migrations/indexes_spec.rb
|
116
|
+
- spec/spec_helper.rb
|
117
|
+
- spec/support/helpers.rb
|
118
|
+
- spec/support/shared_examples/mysql_online_migrations.rb
|
119
|
+
homepage: https://github.com/anthonyalberto/mysql_online_migrations
|
120
|
+
licenses: []
|
121
|
+
metadata: {}
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - '>='
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
requirements: []
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 2.0.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 4
|
141
|
+
summary: Use MySQL 5.6+ capacities to perform online migrations
|
142
|
+
test_files:
|
143
|
+
- spec/lib/mysql_online_migrations/columns_spec.rb
|
144
|
+
- spec/lib/mysql_online_migrations/indexes_spec.rb
|
145
|
+
- spec/spec_helper.rb
|
146
|
+
- spec/support/helpers.rb
|
147
|
+
- spec/support/shared_examples/mysql_online_migrations.rb
|