mysql_online_migrations 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|