mysql_online_migrations 0.1.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +12 -0
- data/Gemfile.lock +1 -1
- data/README.md +17 -9
- data/gemfiles/rails3.gemfile +7 -0
- data/gemfiles/rails4.gemfile +7 -0
- data/lib/mysql_online_migrations.rb +10 -16
- data/lib/mysql_online_migrations/mysql2_adapter_without_lock.rb +32 -0
- data/mysql_online_migrations.gemspec +1 -1
- data/spec/lib/migration/column_spec.rb +138 -0
- data/spec/lib/migration/index_spec.rb +49 -0
- data/spec/lib/migration/table_spec.rb +46 -0
- data/spec/lib/mysql_online_migrations/mysql2_adapter_without_lock_spec.rb +137 -0
- data/spec/lib/mysql_online_migrations_spec.rb +30 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/support/helpers.rb +41 -16
- data/spec/support/shared_examples/migration.rb +71 -0
- metadata +19 -11
- data/lib/mysql_online_migrations/columns.rb +0 -63
- data/lib/mysql_online_migrations/indexes.rb +0 -21
- data/spec/lib/mysql_online_migrations/columns_spec.rb +0 -180
- data/spec/lib/mysql_online_migrations/indexes_spec.rb +0 -68
- data/spec/support/shared_examples/mysql_online_migrations.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1f20fadc6f95454a18f4aa4ad5fa9334406b2bce
|
4
|
+
data.tar.gz: 3b7d11339aeb6016a62994a0455bf780a28f8f67
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b72941a2a27bad5275b3af95d80382524a907ed6b315771bf35723aecc54387e8ee718c4608351aedce2097947476195394c040caf15397ee7bf8baa7cdb0f0
|
7
|
+
data.tar.gz: 9046f16159ae85f1931893191931fb9171f5b0512353cf9b653a1b60c37d6bd2051e0251f898a3d05fb2e4e4586a009997e7545505e4fb2a83c63ad21ce097ab
|
data/.travis.yml
ADDED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
mysql_online_migrations
|
2
2
|
=======================
|
3
3
|
|
4
|
-
Patch Rails migrations to enforce MySQL 5.6 online migrations
|
4
|
+
Patch Rails migrations to enforce MySQL 5.6 online migrations
|
5
5
|
Prior to MySQL 5.6, when adding / removing / renaming indexes and columns, MySQL would lock the writes of the whole table.
|
6
6
|
MySQL 5.6 by default will try to apply the least locking possible. You however don't know what kind of locking it applies and there's situations where it can't allow writes during a migration (See Caveats).
|
7
7
|
This gem enforces `LOCK=NONE` in all migration statements of Rails. Therefore, you're getting an error when MySQL cannot write during the migration so there's no surprise when rolling out in production.
|
@@ -9,24 +9,27 @@ This gem enforces `LOCK=NONE` in all migration statements of Rails. Therefore, y
|
|
9
9
|
|
10
10
|
Requirements
|
11
11
|
=======================
|
12
|
-
Built for Rails 3.2.15
|
13
|
-
This gem actually just requires ActiveRecord and ActiveSupport, the full Rails gem should not be required.
|
12
|
+
Built for Rails 3.2.15+, including Rails 4.
|
14
13
|
|
15
14
|
List of requirements :
|
16
15
|
|
17
16
|
- Use mysql2 adapter
|
18
|
-
- Use
|
17
|
+
- Use Rails ">= 3.2.15"
|
19
18
|
- Use MySQL or Percona Server 5.6.X with InnoDB
|
20
19
|
|
21
20
|
Scope of this gem
|
22
21
|
=======================
|
23
22
|
|
24
|
-
Patch Rails migrations to automatically add `LOCK=NONE`
|
23
|
+
Patch Rails migrations to automatically add `LOCK=NONE` when issuing `ALTER`, `CREATE INDEX`, `CREATE UNIQUE INDEX`. `DROP INDEX` statements from any methods of ActiveRecord:
|
25
24
|
|
26
25
|
- Index management : `add_index`, `remove_index`, `rename_index`
|
27
26
|
- Add column : `add_column`, `add_timestamps`
|
28
27
|
- Remove column : `remove_column`, `remove_timestamps`
|
29
28
|
- Change column : `change_column`, `change_column_null`, `change_column_default`
|
29
|
+
- Any other method that was added in Rails 4 etc ...
|
30
|
+
|
31
|
+
__Please note that it only modifies sql queries sent in Rails Migrations.__
|
32
|
+
This way we avoid patching all of ActiveRecord statements all the time.
|
30
33
|
|
31
34
|
Usage
|
32
35
|
=======================
|
@@ -43,10 +46,15 @@ Example for environment test (your CI might not use MySQL 5.6 yet), add the foll
|
|
43
46
|
`config.active_record.mysql_online_migrations = false`
|
44
47
|
|
45
48
|
### Turn it off for a specific statement
|
46
|
-
|
47
|
-
|
49
|
+
Call your migration statement within `with_lock` method. Example :
|
50
|
+
|
51
|
+
`````
|
52
|
+
with_lock do
|
53
|
+
add_index :my_table, :my_field
|
54
|
+
end
|
55
|
+
`````
|
48
56
|
|
49
|
-
The `
|
57
|
+
The `with_lock` method will be useful when hitting the caveats of `LOCK=NONE`. Please read the following section.
|
50
58
|
|
51
59
|
Caveats
|
52
60
|
=======================
|
@@ -59,6 +67,6 @@ The MySQL manual contains a list of which DDL statements can be run with `LOCK=N
|
|
59
67
|
- Set a column to NOT NULL (at least not with the default SQL_MODE)
|
60
68
|
- Adding an AUTO_INCREMENT column,
|
61
69
|
|
62
|
-
If you don't use
|
70
|
+
If you don't use the `with_lock` method when online migration is not supported, you'll get a MySQL exception. No risk to lock the table by accident.
|
63
71
|
It's therefore highly recommended to use it in development/test/staging environment before running migrations in production.
|
64
72
|
If you have to perform such a migration without locking the table, tools such as [pt-online-schema-change](http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html) and [LHM](https://github.com/soundcloud/lhm) are viable options
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'active_record'
|
2
|
+
require "active_record/migration"
|
2
3
|
require "active_record/connection_adapters/mysql2_adapter"
|
3
4
|
|
4
5
|
%w(*.rb).each do |path|
|
@@ -6,28 +7,21 @@ require "active_record/connection_adapters/mysql2_adapter"
|
|
6
7
|
end
|
7
8
|
|
8
9
|
module MysqlOnlineMigrations
|
9
|
-
|
10
|
-
include Columns
|
11
|
-
|
12
|
-
def self.included(base)
|
10
|
+
def self.prepended(base)
|
13
11
|
ActiveRecord::Base.send(:class_attribute, :mysql_online_migrations, :instance_writer => false)
|
14
12
|
ActiveRecord::Base.send("mysql_online_migrations=", true)
|
15
13
|
end
|
16
14
|
|
17
|
-
def
|
18
|
-
|
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)]
|
15
|
+
def connection
|
16
|
+
@no_lock_adapter ||= ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.new(super)
|
26
17
|
end
|
27
18
|
|
28
|
-
def
|
29
|
-
ActiveRecord::Base.mysql_online_migrations
|
19
|
+
def with_lock
|
20
|
+
original_value = ActiveRecord::Base.mysql_online_migrations
|
21
|
+
ActiveRecord::Base.mysql_online_migrations = false
|
22
|
+
yield
|
23
|
+
ActiveRecord::Base.mysql_online_migrations = original_value
|
30
24
|
end
|
31
25
|
end
|
32
26
|
|
33
|
-
ActiveRecord::
|
27
|
+
ActiveRecord::Migration.send(:prepend, MysqlOnlineMigrations)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
class Mysql2AdapterWithoutLock < Mysql2Adapter
|
4
|
+
OPTIMIZABLE_DDL_REGEX = /^(alter|create (unique )? ?index|drop index) /i
|
5
|
+
DDL_WITH_COMMA_REGEX = /^alter /i
|
6
|
+
DDL_WITH_LOCK_NONE_REGEX = / LOCK=NONE\s*$/i
|
7
|
+
|
8
|
+
def initialize(mysql2_adapter)
|
9
|
+
params = [:@connection, :@logger, :@connection_options, :@config].map do |sym|
|
10
|
+
mysql2_adapter.instance_variable_get(sym)
|
11
|
+
end
|
12
|
+
super(*params)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :original_execute, :execute
|
16
|
+
def execute(sql, name = nil)
|
17
|
+
if sql =~ OPTIMIZABLE_DDL_REGEX
|
18
|
+
sql = "#{sql} #{lock_none_statement(sql)}"
|
19
|
+
end
|
20
|
+
original_execute(sql, name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def lock_none_statement(sql)
|
24
|
+
return "" unless ActiveRecord::Base.mysql_online_migrations
|
25
|
+
return "" if sql =~ DDL_WITH_LOCK_NONE_REGEX
|
26
|
+
comma_delimiter = (sql =~ DDL_WITH_COMMA_REGEX ? "," : "")
|
27
|
+
puts "ONLINE MIGRATION"
|
28
|
+
"#{comma_delimiter} LOCK=NONE"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = 'mysql_online_migrations'
|
3
|
-
s.version = '0.
|
3
|
+
s.version = '1.0.0'
|
4
4
|
s.summary = "Use MySQL 5.6+ capacities to enforce online migrations"
|
5
5
|
s.description = "MySQL 5.6 adds a `LOCK=NONE` option to make sure migrations are done with no locking. Let's use it."
|
6
6
|
s.authors = ["Anthony Alberto"]
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration do
|
4
|
+
let(:comma_before_lock_none) { true }
|
5
|
+
let(:migration_arguments_with_lock) { [] }
|
6
|
+
|
7
|
+
context "#add_column" do
|
8
|
+
let(:method_name) { :add_column }
|
9
|
+
let(:migration_arguments) do
|
10
|
+
[
|
11
|
+
[:testing, :foo2, :string],
|
12
|
+
[:testing, :foo2, :string, { limit: 20, null: false, default: 'def' }],
|
13
|
+
[:testing, :foo2, :decimal, { precision:3, scale: 2 }]
|
14
|
+
]
|
15
|
+
end
|
16
|
+
|
17
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
18
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#add_timestamps" do
|
22
|
+
let(:migration_arguments) do
|
23
|
+
[
|
24
|
+
[:testing2]
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:method_name) { :add_timestamps }
|
29
|
+
|
30
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
31
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
32
|
+
end
|
33
|
+
|
34
|
+
context "#remove_column" do
|
35
|
+
let(:migration_arguments) do
|
36
|
+
[
|
37
|
+
[:testing, :foo],
|
38
|
+
[:testing, :foo, :bar]
|
39
|
+
]
|
40
|
+
end
|
41
|
+
|
42
|
+
let(:method_name) { :remove_column }
|
43
|
+
|
44
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
45
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
46
|
+
end
|
47
|
+
|
48
|
+
context "#remove_timestamps" do
|
49
|
+
let(:migration_arguments) do
|
50
|
+
[
|
51
|
+
[:testing]
|
52
|
+
]
|
53
|
+
end
|
54
|
+
|
55
|
+
let(:method_name) { :remove_timestamps }
|
56
|
+
|
57
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
58
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
59
|
+
end
|
60
|
+
|
61
|
+
context "#change_column" do
|
62
|
+
let(:migration_arguments) do
|
63
|
+
# Unsupported with lock=none : change column type, change limit, set NOT NULL.
|
64
|
+
[
|
65
|
+
[:testing, :foo, :string, { default: 'def', limit: 100 }],
|
66
|
+
[:testing, :foo, :string, { null: true, limit: 100 }]
|
67
|
+
]
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:migration_arguments_with_lock) do
|
71
|
+
[
|
72
|
+
[:testing, :foo, :string, { limit: 200 }],
|
73
|
+
[:testing, :foo, :string, { default: 'def' }],
|
74
|
+
[:testing, :foo, :string, { null: false }],
|
75
|
+
[:testing, :foo, :string, { null: false, default: 'def', limit: 200 }],
|
76
|
+
[:testing, :foo, :string, { null: true }],
|
77
|
+
[:testing, :foo, :integer, { null: true, limit: 6 }],
|
78
|
+
[:testing, :foo, :integer, { null: true, limit: 1 }]
|
79
|
+
]
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:method_name) { :change_column }
|
83
|
+
|
84
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
85
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
86
|
+
it_behaves_like "a migration with a non-lockable statement"
|
87
|
+
end
|
88
|
+
|
89
|
+
context "#change_column_default" do
|
90
|
+
let(:migration_arguments) do
|
91
|
+
[
|
92
|
+
[:testing, :foo, 'def'],
|
93
|
+
[:testing, :foo, nil]
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
let(:method_name) { :change_column_default }
|
98
|
+
|
99
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
100
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
101
|
+
end
|
102
|
+
|
103
|
+
context "#change_column_null" do
|
104
|
+
let(:migration_arguments) do
|
105
|
+
#change_column_null doesn't set DEFAULT in sql. It just issues an update statement before setting the NULL value if setting NULL to false
|
106
|
+
[
|
107
|
+
[:testing, :bam, true, nil],
|
108
|
+
[:testing, :bam, true, 'def']
|
109
|
+
]
|
110
|
+
end
|
111
|
+
|
112
|
+
let(:migration_arguments_with_lock) do
|
113
|
+
[
|
114
|
+
[:testing, :bam, false, nil],
|
115
|
+
[:testing, :bam, false, 'def']
|
116
|
+
]
|
117
|
+
end
|
118
|
+
|
119
|
+
let(:method_name) { :change_column_null }
|
120
|
+
|
121
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
122
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
123
|
+
it_behaves_like "a migration with a non-lockable statement"
|
124
|
+
end
|
125
|
+
|
126
|
+
context "#rename_column" do
|
127
|
+
let(:migration_arguments) do
|
128
|
+
[
|
129
|
+
[:testing, :foo, :foo2]
|
130
|
+
]
|
131
|
+
end
|
132
|
+
|
133
|
+
let(:method_name) { :rename_column }
|
134
|
+
|
135
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
136
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration do
|
4
|
+
let(:comma_before_lock_none) { false }
|
5
|
+
let(:migration_arguments_with_lock) { [] }
|
6
|
+
context "#add_index" do
|
7
|
+
let(:method_name) { :add_index }
|
8
|
+
let(:migration_arguments) do
|
9
|
+
[
|
10
|
+
[:testing, :foo],
|
11
|
+
[:testing, :foo, { length: 10 }],
|
12
|
+
[:testing, [:foo, :bar, :baz], {}],
|
13
|
+
[:testing, [:foo, :bar, :baz], { unique: true }],
|
14
|
+
[:testing, [:foo, :bar, :baz], { unique: true, name: "best_index_of_the_world" }]
|
15
|
+
]
|
16
|
+
end
|
17
|
+
|
18
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
19
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
20
|
+
end
|
21
|
+
|
22
|
+
context "#remove_index" do
|
23
|
+
let(:method_name) { :remove_index }
|
24
|
+
let(:migration_arguments) do
|
25
|
+
[
|
26
|
+
[:testing, :baz],
|
27
|
+
[:testing, [:bar, :baz]],
|
28
|
+
[:testing, { column: [:bar, :baz] }],
|
29
|
+
[:testing, { name: "best_index_of_the_world2" }]
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
34
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
35
|
+
end
|
36
|
+
|
37
|
+
context "#rename_index" do
|
38
|
+
let(:method_name) { :rename_index }
|
39
|
+
let(:migration_arguments) do
|
40
|
+
[
|
41
|
+
[:testing, "best_index_of_the_world2", "renamed_best_index_of_the_world2"],
|
42
|
+
[:testing, "best_index_of_the_world3", "renamed_best_index_of_the_world3"]
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
47
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::Migration do
|
4
|
+
let(:comma_before_lock_none) { true }
|
5
|
+
let(:migration_arguments_with_lock) { [] }
|
6
|
+
|
7
|
+
context "#create_table" do
|
8
|
+
let(:method_name) { :create_table }
|
9
|
+
let(:migration_arguments) do
|
10
|
+
[
|
11
|
+
[:test5]
|
12
|
+
]
|
13
|
+
end
|
14
|
+
|
15
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
16
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
17
|
+
end
|
18
|
+
|
19
|
+
context "#drop_table" do
|
20
|
+
let(:method_name) { :drop_table }
|
21
|
+
let(:migration_arguments) do
|
22
|
+
[
|
23
|
+
[:testing]
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
28
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
29
|
+
end
|
30
|
+
|
31
|
+
context "#rename_table" do
|
32
|
+
before(:each) do
|
33
|
+
@rescue_statement_when_stubbed = true
|
34
|
+
end
|
35
|
+
|
36
|
+
let(:method_name) { :rename_table }
|
37
|
+
let(:migration_arguments) do
|
38
|
+
[
|
39
|
+
[:testing, :testing20]
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
it_behaves_like "a migration that adds LOCK=NONE when needed"
|
44
|
+
it_behaves_like "a migration that succeeds in MySQL"
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock do
|
4
|
+
context "#initialize" do
|
5
|
+
it "successfully instantiates a working adapter" do
|
6
|
+
ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.new(@adapter).should be_active
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
context "#lock_none_statement" do
|
11
|
+
context "with mysql_online_migrations set to true" do
|
12
|
+
context "with alter" do
|
13
|
+
let(:query) { "alter " }
|
14
|
+
it "adds ', LOCK=NONE'" do
|
15
|
+
@adapter_without_lock.lock_none_statement("alter ").should == ", LOCK=NONE"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
context "with drop index" do
|
19
|
+
let(:query) { "drop index " }
|
20
|
+
it "adds ' LOCK=NONE'" do
|
21
|
+
@adapter_without_lock.lock_none_statement("drop index ").should == " LOCK=NONE"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
context "with create index" do
|
25
|
+
let(:query) { "create index " }
|
26
|
+
it "adds ' LOCK=NONE'" do
|
27
|
+
@adapter_without_lock.lock_none_statement("create index ").should == " LOCK=NONE"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
context "with a query with LOCK=NONE already there" do
|
31
|
+
it "doesn't add anything" do
|
32
|
+
@adapter_without_lock.lock_none_statement("alter LOCK=NONE ").should == ""
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "with mysql_online_migrations set to false" do
|
38
|
+
before(:each) do
|
39
|
+
set_ar_setting(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
after(:each) do
|
43
|
+
set_ar_setting(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "doesn't add anything to the request" do
|
47
|
+
@adapter_without_lock.lock_none_statement("alter ").should == ""
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "#execute" do
|
53
|
+
shared_examples_for "#execute that changes the SQL" do
|
54
|
+
it "adds LOCK=NONE at the end of the query" do
|
55
|
+
comma = query =~ /alter /i ? "," : ""
|
56
|
+
expected_output = "#{query} #{comma} LOCK=NONE"
|
57
|
+
@adapter_without_lock.should_receive(:original_execute).with(expected_output, nil)
|
58
|
+
@adapter_without_lock.execute(query)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
shared_examples_for "#execute that doesn't change the SQL" do
|
63
|
+
it "just passes the query to original_execute" do
|
64
|
+
@adapter_without_lock.should_receive(:original_execute).with(query, nil)
|
65
|
+
@adapter_without_lock.execute(query)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context "with an optimizable DDL statement" do
|
70
|
+
context "with alter" do
|
71
|
+
let(:query) { "alter " }
|
72
|
+
it_behaves_like "#execute that changes the SQL"
|
73
|
+
end
|
74
|
+
context "with drop index" do
|
75
|
+
let(:query) { "drop index " }
|
76
|
+
it_behaves_like "#execute that changes the SQL"
|
77
|
+
end
|
78
|
+
context "with create index" do
|
79
|
+
let(:query) { "create index " }
|
80
|
+
it_behaves_like "#execute that changes the SQL"
|
81
|
+
end
|
82
|
+
context "with create unique index" do
|
83
|
+
let(:query) { "create unique index " }
|
84
|
+
it_behaves_like "#execute that changes the SQL"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
context "with other DDL statements" do
|
89
|
+
context "with create table" do
|
90
|
+
let(:query) { "create table " }
|
91
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with drop table" do
|
95
|
+
let(:query) { "drop table " }
|
96
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "with a regular statement" do
|
101
|
+
context "with select" do
|
102
|
+
let(:query) { "select " }
|
103
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
104
|
+
end
|
105
|
+
|
106
|
+
context "with set" do
|
107
|
+
let(:query) { "set " }
|
108
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
109
|
+
end
|
110
|
+
|
111
|
+
context "with insert" do
|
112
|
+
let(:query) { "insert " }
|
113
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
114
|
+
end
|
115
|
+
|
116
|
+
context "with update" do
|
117
|
+
let(:query) { "update " }
|
118
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
119
|
+
end
|
120
|
+
|
121
|
+
context "with delete" do
|
122
|
+
let(:query) { "delete " }
|
123
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
124
|
+
end
|
125
|
+
|
126
|
+
context "with show" do
|
127
|
+
let(:query) { "show " }
|
128
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
129
|
+
end
|
130
|
+
|
131
|
+
context "with explain" do
|
132
|
+
let(:query) { "explain " }
|
133
|
+
it_behaves_like "#execute that doesn't change the SQL"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe MysqlOnlineMigrations do
|
4
|
+
let(:migration) { migration = ActiveRecord::Migration.new }
|
5
|
+
|
6
|
+
context ".prepended" do
|
7
|
+
it "sets ActiveRecord::Base.mysql_online_migrations to true" do
|
8
|
+
ActiveRecord::Base.mysql_online_migrations.should be_true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
context "#connection" do
|
13
|
+
it "memoizes an instance of Mysql2AdapterWithoutLock" do
|
14
|
+
ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.should_receive(:new)
|
15
|
+
.with(an_instance_of(ActiveRecord::ConnectionAdapters::Mysql2Adapter)).once.and_call_original
|
16
|
+
3.times { @connection = migration.connection }
|
17
|
+
@connection.should be_an_instance_of(ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#with_lock" do
|
22
|
+
it "switches mysql_online_migrations flag to false and then back to original value after block execution" do
|
23
|
+
ActiveRecord::Base.mysql_online_migrations.should be_true
|
24
|
+
migration.with_lock do
|
25
|
+
ActiveRecord::Base.mysql_online_migrations.should be_false
|
26
|
+
end
|
27
|
+
ActiveRecord::Base.mysql_online_migrations.should be_true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -11,7 +11,7 @@ require 'mysql_online_migrations'
|
|
11
11
|
require 'logger'
|
12
12
|
require 'pry'
|
13
13
|
require 'support/helpers'
|
14
|
-
require 'support/shared_examples/
|
14
|
+
require 'support/shared_examples/migration'
|
15
15
|
|
16
16
|
RSpec.configure do |config|
|
17
17
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
data/spec/support/helpers.rb
CHANGED
@@ -1,21 +1,42 @@
|
|
1
1
|
module Helpers
|
2
|
-
CATCH_STATEMENT_REGEX = /^(alter|create|drop|update) /i
|
3
|
-
DDL_STATEMENT_REGEX = /^(alter|create|drop) /i
|
2
|
+
CATCH_STATEMENT_REGEX = /^(alter|create|drop|update|rename) /i
|
3
|
+
DDL_STATEMENT_REGEX = /^(alter|create (unique )? ?index|drop index) /i
|
4
4
|
|
5
|
-
def
|
5
|
+
def build_migration(method_name, args, &block)
|
6
|
+
migration = ActiveRecord::Migration.new
|
7
|
+
migration.instance_variable_set(:@test_method_name, method_name)
|
8
|
+
migration.instance_variable_set(:@test_args, args)
|
9
|
+
migration.instance_variable_set(:@test_block, block)
|
10
|
+
migration.define_singleton_method(:change) do
|
11
|
+
public_send(@test_method_name, *@test_args, &@test_block)
|
12
|
+
end
|
13
|
+
migration
|
14
|
+
end
|
15
|
+
|
16
|
+
def regular_execute(statement)
|
17
|
+
@queries_received_by_regular_adapter << statement
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute_without_lock(statement)
|
21
|
+
@queries_received_by_adapter_without_lock << statement
|
6
22
|
end
|
7
23
|
|
8
24
|
def unstub_execute
|
9
25
|
@adapter.unstub(:execute)
|
10
26
|
end
|
11
27
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
28
|
+
def stub_adapter_without_lock
|
29
|
+
ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.stub(:new).and_return(@adapter_without_lock)
|
30
|
+
end
|
31
|
+
|
32
|
+
def stub_execute(adapter, original_method, method_to_call)
|
33
|
+
original_execute = adapter.method(original_method)
|
34
|
+
|
35
|
+
adapter.stub(original_method) do |sql|
|
36
|
+
if sql =~ CATCH_STATEMENT_REGEX
|
37
|
+
send(method_to_call, sql.squeeze(' ').strip)
|
17
38
|
else
|
18
|
-
original_execute.call(
|
39
|
+
original_execute.call(sql)
|
19
40
|
end
|
20
41
|
end
|
21
42
|
end
|
@@ -28,9 +49,16 @@ module Helpers
|
|
28
49
|
end
|
29
50
|
end
|
30
51
|
|
52
|
+
def drop_all_tables
|
53
|
+
@adapter.tables.each do |table|
|
54
|
+
@adapter.drop_table(table) rescue nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
31
58
|
def rebuild_table
|
32
59
|
@table_name = :testing
|
33
|
-
|
60
|
+
drop_all_tables
|
61
|
+
|
34
62
|
@adapter.create_table @table_name do |t|
|
35
63
|
t.column :foo, :string, :limit => 100
|
36
64
|
t.column :bar, :string, :limit => 100
|
@@ -41,7 +69,6 @@ module Helpers
|
|
41
69
|
end
|
42
70
|
|
43
71
|
@table_name = :testing2
|
44
|
-
@adapter.drop_table @table_name rescue nil
|
45
72
|
@adapter.create_table @table_name do |t|
|
46
73
|
end
|
47
74
|
|
@@ -54,18 +81,16 @@ module Helpers
|
|
54
81
|
def setup
|
55
82
|
ActiveRecord::Base.establish_connection(
|
56
83
|
adapter: :mysql2,
|
57
|
-
reconnect: false,
|
58
84
|
database: "mysql_online_migrations",
|
59
|
-
username: "
|
60
|
-
|
61
|
-
encoding: "utf8",
|
62
|
-
socket: "/tmp/mysql.sock"
|
85
|
+
username: "travis",
|
86
|
+
encoding: "utf8"
|
63
87
|
)
|
64
88
|
|
65
89
|
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
66
90
|
ActiveRecord::Base.logger.level = Logger::INFO
|
67
91
|
|
68
92
|
@adapter = ActiveRecord::Base.connection
|
93
|
+
@adapter_without_lock = ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.new(@adapter)
|
69
94
|
|
70
95
|
rebuild_table
|
71
96
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
def reset_queries_collectors
|
2
|
+
@queries_received_by_regular_adapter = []
|
3
|
+
@queries_received_by_adapter_without_lock = []
|
4
|
+
end
|
5
|
+
|
6
|
+
def staged_for_travis
|
7
|
+
set_ar_setting(false) if ENV["TRAVIS"] # Travis doesn't run MySQL 5.6. Run tests locally first.
|
8
|
+
yield
|
9
|
+
set_ar_setting(true) if ENV["TRAVIS"]
|
10
|
+
end
|
11
|
+
|
12
|
+
shared_examples_for "a migration that adds LOCK=NONE when needed" do
|
13
|
+
before(:each) do
|
14
|
+
stub_adapter_without_lock
|
15
|
+
stub_execute(@adapter, :execute, :regular_execute)
|
16
|
+
stub_execute(@adapter_without_lock, :original_execute, :execute_without_lock)
|
17
|
+
@migration_arguments = migration_arguments + migration_arguments_with_lock
|
18
|
+
end
|
19
|
+
|
20
|
+
it "executes the same query as the original adapter, with LOCK=NONE when required" do
|
21
|
+
@migration_arguments.each do |migration_argument|
|
22
|
+
reset_queries_collectors
|
23
|
+
|
24
|
+
begin
|
25
|
+
@adapter.public_send(method_name, *migration_argument)
|
26
|
+
rescue => e
|
27
|
+
raise e unless @rescue_statement_when_stubbed
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
build_migration(method_name, migration_argument).migrate(:up)
|
32
|
+
rescue => e
|
33
|
+
raise e unless @rescue_statement_when_stubbed
|
34
|
+
end
|
35
|
+
|
36
|
+
@queries_received_by_regular_adapter.length.should > 0
|
37
|
+
@queries_received_by_regular_adapter.length.should == @queries_received_by_adapter_without_lock.length
|
38
|
+
@queries_received_by_regular_adapter.each_with_index do |query, index|
|
39
|
+
@queries_received_by_adapter_without_lock[index].should == add_lock_none(query, comma_before_lock_none)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
shared_examples_for "a migration that succeeds in MySQL" do
|
46
|
+
it "succeeds without exception" do
|
47
|
+
staged_for_travis do
|
48
|
+
migration_arguments.each do |migration_argument|
|
49
|
+
migration = build_migration(method_name, migration_argument)
|
50
|
+
migration.migrate(:up)
|
51
|
+
rebuild_table
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
shared_examples_for "a migration with a non-lockable statement" do
|
58
|
+
it "raises a MySQL exception" do
|
59
|
+
staged_for_travis do
|
60
|
+
migration_arguments_with_lock.each do |migration_argument|
|
61
|
+
migration = build_migration(method_name, migration_argument)
|
62
|
+
begin
|
63
|
+
migration.migrate(:up)
|
64
|
+
rescue ActiveRecord::StatementInvalid => e
|
65
|
+
e.message.should =~ /LOCK=NONE is not supported/
|
66
|
+
end
|
67
|
+
rebuild_table
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mysql_online_migrations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anthony Alberto
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -103,19 +103,24 @@ extra_rdoc_files: []
|
|
103
103
|
files:
|
104
104
|
- .gitignore
|
105
105
|
- .rspec
|
106
|
+
- .travis.yml
|
106
107
|
- Gemfile
|
107
108
|
- Gemfile.lock
|
108
109
|
- LICENSE
|
109
110
|
- README.md
|
111
|
+
- gemfiles/rails3.gemfile
|
112
|
+
- gemfiles/rails4.gemfile
|
110
113
|
- lib/mysql_online_migrations.rb
|
111
|
-
- lib/mysql_online_migrations/
|
112
|
-
- lib/mysql_online_migrations/indexes.rb
|
114
|
+
- lib/mysql_online_migrations/mysql2_adapter_without_lock.rb
|
113
115
|
- mysql_online_migrations.gemspec
|
114
|
-
- spec/lib/
|
115
|
-
- spec/lib/
|
116
|
+
- spec/lib/migration/column_spec.rb
|
117
|
+
- spec/lib/migration/index_spec.rb
|
118
|
+
- spec/lib/migration/table_spec.rb
|
119
|
+
- spec/lib/mysql_online_migrations/mysql2_adapter_without_lock_spec.rb
|
120
|
+
- spec/lib/mysql_online_migrations_spec.rb
|
116
121
|
- spec/spec_helper.rb
|
117
122
|
- spec/support/helpers.rb
|
118
|
-
- spec/support/shared_examples/
|
123
|
+
- spec/support/shared_examples/migration.rb
|
119
124
|
homepage: https://github.com/anthonyalberto/mysql_online_migrations
|
120
125
|
licenses:
|
121
126
|
- MIT
|
@@ -136,13 +141,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
136
141
|
version: '0'
|
137
142
|
requirements: []
|
138
143
|
rubyforge_project:
|
139
|
-
rubygems_version: 2.0.
|
144
|
+
rubygems_version: 2.0.3
|
140
145
|
signing_key:
|
141
146
|
specification_version: 4
|
142
147
|
summary: Use MySQL 5.6+ capacities to enforce online migrations
|
143
148
|
test_files:
|
144
|
-
- spec/lib/
|
145
|
-
- spec/lib/
|
149
|
+
- spec/lib/migration/column_spec.rb
|
150
|
+
- spec/lib/migration/index_spec.rb
|
151
|
+
- spec/lib/migration/table_spec.rb
|
152
|
+
- spec/lib/mysql_online_migrations/mysql2_adapter_without_lock_spec.rb
|
153
|
+
- spec/lib/mysql_online_migrations_spec.rb
|
146
154
|
- spec/spec_helper.rb
|
147
155
|
- spec/support/helpers.rb
|
148
|
-
- spec/support/shared_examples/
|
156
|
+
- spec/support/shared_examples/migration.rb
|
@@ -1,63 +0,0 @@
|
|
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 change_column_default(table_name, column_name, default, options = {})
|
19
|
-
column = column_for(table_name, column_name)
|
20
|
-
change_column table_name, column_name, column.sql_type, options.merge(:default => default)
|
21
|
-
end
|
22
|
-
|
23
|
-
def change_column_null(table_name, column_name, null, default = nil, options = {})
|
24
|
-
column = column_for(table_name, column_name)
|
25
|
-
|
26
|
-
unless null || default.nil?
|
27
|
-
execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
|
28
|
-
end
|
29
|
-
|
30
|
-
change_column table_name, column_name, column.sql_type, options.merge(:null => null)
|
31
|
-
end
|
32
|
-
|
33
|
-
def rename_column(table_name, column_name, new_column_name, options = {})
|
34
|
-
lock, options = extract_lock_from_options(options)
|
35
|
-
execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)} #{lock_statement(lock, true)}")
|
36
|
-
end
|
37
|
-
|
38
|
-
def remove_column(table_name, *column_names)
|
39
|
-
if column_names.flatten!
|
40
|
-
message = 'Passing array to remove_columns is deprecated, please use ' +
|
41
|
-
'multiple arguments, like: `remove_columns(:posts, :foo, :bar)`'
|
42
|
-
ActiveSupport::Deprecation.warn message, caller
|
43
|
-
end
|
44
|
-
|
45
|
-
lock, options = if column_names.last.is_a? Hash
|
46
|
-
options = column_names.last
|
47
|
-
column_names = column_names[0..-2]
|
48
|
-
extract_lock_from_options(options)
|
49
|
-
else
|
50
|
-
[false, {}]
|
51
|
-
end
|
52
|
-
|
53
|
-
columns_for_remove(table_name, *column_names).each do |column_name|
|
54
|
-
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name} #{lock_statement(lock, true)}"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
def remove_timestamps(table_name, options = {})
|
59
|
-
remove_column table_name, :updated_at, options
|
60
|
-
remove_column table_name, :created_at, options
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
@@ -1,21 +0,0 @@
|
|
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
|
@@ -1,180 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe MysqlOnlineMigrations::Columns do
|
4
|
-
let(:comma_before_lock_none) { true }
|
5
|
-
let(:queries_with_lock) { {} }
|
6
|
-
|
7
|
-
context "#add_column" do
|
8
|
-
let(:queries) do
|
9
|
-
{
|
10
|
-
[:testing, :foo2, :string, {}] =>
|
11
|
-
"ALTER TABLE `testing` ADD `foo2` varchar(255)",
|
12
|
-
[:testing, :foo2, :string, { limit: 20, null: false, default: 'def' }] =>
|
13
|
-
"ALTER TABLE `testing` ADD `foo2` varchar(20) DEFAULT 'def' NOT NULL",
|
14
|
-
[:testing, :foo2, :decimal, { precision:3, scale: 2 }] =>
|
15
|
-
"ALTER TABLE `testing` ADD `foo2` decimal(3,2)",
|
16
|
-
}
|
17
|
-
end
|
18
|
-
|
19
|
-
let(:method_name) { :add_column }
|
20
|
-
|
21
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
22
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
23
|
-
end
|
24
|
-
|
25
|
-
context "#add_timestamps" do
|
26
|
-
let(:queries) do
|
27
|
-
{
|
28
|
-
[:testing2, {}] =>
|
29
|
-
[
|
30
|
-
"ALTER TABLE `testing2` ADD `created_at` datetime",
|
31
|
-
"ALTER TABLE `testing2` ADD `updated_at` datetime",
|
32
|
-
]
|
33
|
-
}
|
34
|
-
end
|
35
|
-
|
36
|
-
let(:method_name) { :add_timestamps }
|
37
|
-
|
38
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
39
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
40
|
-
end
|
41
|
-
|
42
|
-
context "#remove_column" do
|
43
|
-
let(:queries) do
|
44
|
-
{
|
45
|
-
[:testing, :foo, {}] =>
|
46
|
-
"ALTER TABLE `testing` DROP `foo`",
|
47
|
-
[:testing, [:foo, :bar], {}] =>
|
48
|
-
[
|
49
|
-
"ALTER TABLE `testing` DROP `foo`",
|
50
|
-
"ALTER TABLE `testing` DROP `bar`"
|
51
|
-
],
|
52
|
-
[:testing, :foo, :bar, {}] =>
|
53
|
-
[
|
54
|
-
"ALTER TABLE `testing` DROP `foo`",
|
55
|
-
"ALTER TABLE `testing` DROP `bar`"
|
56
|
-
]
|
57
|
-
}
|
58
|
-
end
|
59
|
-
|
60
|
-
let(:method_name) { :remove_column }
|
61
|
-
|
62
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
63
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
64
|
-
end
|
65
|
-
|
66
|
-
context "#remove_timestamps" do
|
67
|
-
let(:queries) do
|
68
|
-
{
|
69
|
-
[:testing, {}] =>
|
70
|
-
[
|
71
|
-
"ALTER TABLE `testing` DROP `created_at`",
|
72
|
-
"ALTER TABLE `testing` DROP `updated_at`",
|
73
|
-
]
|
74
|
-
}
|
75
|
-
end
|
76
|
-
|
77
|
-
let(:method_name) { :remove_timestamps }
|
78
|
-
|
79
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
80
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
81
|
-
end
|
82
|
-
|
83
|
-
context "#change_column" do
|
84
|
-
let(:queries) do
|
85
|
-
# Unsupported with lock=none : change column type, change limit, set NOT NULL.
|
86
|
-
{
|
87
|
-
[:testing, :foo, :string, { default: 'def', limit: 100 }] =>
|
88
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(100) DEFAULT 'def'",
|
89
|
-
[:testing, :foo, :string, { null: true, limit: 100 }] =>
|
90
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(100) DEFAULT NULL",
|
91
|
-
}
|
92
|
-
end
|
93
|
-
|
94
|
-
let(:queries_with_lock) do
|
95
|
-
{
|
96
|
-
[:testing, :foo, :string, { limit: 200 }] =>
|
97
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(200) DEFAULT NULL",
|
98
|
-
[:testing, :foo, :string, { default: 'def' }] =>
|
99
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(255) DEFAULT 'def'",
|
100
|
-
[:testing, :foo, :string, { null: false }] =>
|
101
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(255) NOT NULL",
|
102
|
-
[:testing, :foo, :string, { null: false, default: 'def', limit: 200 }] =>
|
103
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(200) DEFAULT 'def' NOT NULL",
|
104
|
-
[:testing, :foo, :string, { null: true }] =>
|
105
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(255) DEFAULT NULL",
|
106
|
-
[:testing, :foo, :integer, { null: true, limit: 6 }] =>
|
107
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` bigint DEFAULT NULL",
|
108
|
-
[:testing, :foo, :integer, { null: true, limit: 1 }] =>
|
109
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` tinyint DEFAULT NULL"
|
110
|
-
}
|
111
|
-
end
|
112
|
-
|
113
|
-
let(:method_name) { :change_column }
|
114
|
-
|
115
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
116
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
117
|
-
it_behaves_like "queries_with_lock that executes properly"
|
118
|
-
end
|
119
|
-
|
120
|
-
context "#change_column_default" do
|
121
|
-
let(:queries) do
|
122
|
-
{
|
123
|
-
[:testing, :foo, 'def', {}] =>
|
124
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(100) DEFAULT 'def'",
|
125
|
-
[:testing, :foo, nil, {}] =>
|
126
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo` varchar(100) DEFAULT NULL"
|
127
|
-
}
|
128
|
-
end
|
129
|
-
|
130
|
-
let(:method_name) { :change_column_default }
|
131
|
-
|
132
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
133
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
134
|
-
it_behaves_like "queries_with_lock that executes properly"
|
135
|
-
end
|
136
|
-
|
137
|
-
context "#change_column_null" do
|
138
|
-
let(:queries) do
|
139
|
-
#change_column_null doesn't set DEFAULT in sql. It just issues an update statement before setting the NULL value if setting NULL to false
|
140
|
-
{
|
141
|
-
[:testing, :bam, true, nil, {}] =>
|
142
|
-
"ALTER TABLE `testing` CHANGE `bam` `bam` varchar(100) DEFAULT 'test'",
|
143
|
-
[:testing, :bam, true, 'def', {}] =>
|
144
|
-
"ALTER TABLE `testing` CHANGE `bam` `bam` varchar(100) DEFAULT 'test'"
|
145
|
-
}
|
146
|
-
end
|
147
|
-
|
148
|
-
let(:queries_with_lock) do
|
149
|
-
{
|
150
|
-
[:testing, :bam, false, nil, {}] =>
|
151
|
-
"ALTER TABLE `testing` CHANGE `bam` `bam` varchar(100) DEFAULT 'test' NOT NULL",
|
152
|
-
[:testing, :bam, false, 'def', {}] =>
|
153
|
-
[
|
154
|
-
"UPDATE `testing` SET `bam`='def' WHERE `bam` IS NULL",
|
155
|
-
"ALTER TABLE `testing` CHANGE `bam` `bam` varchar(100) DEFAULT 'test' NOT NULL"
|
156
|
-
]
|
157
|
-
}
|
158
|
-
end
|
159
|
-
|
160
|
-
let(:method_name) { :change_column_null }
|
161
|
-
|
162
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
163
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
164
|
-
it_behaves_like "queries_with_lock that executes properly"
|
165
|
-
end
|
166
|
-
|
167
|
-
context "#rename_column" do
|
168
|
-
let(:queries) do
|
169
|
-
{
|
170
|
-
[:testing, :foo, :foo2, {}] =>
|
171
|
-
"ALTER TABLE `testing` CHANGE `foo` `foo2` varchar(100) DEFAULT NULL"
|
172
|
-
}
|
173
|
-
end
|
174
|
-
|
175
|
-
let(:method_name) { :rename_column }
|
176
|
-
|
177
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
178
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
179
|
-
end
|
180
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
describe MysqlOnlineMigrations::Indexes do
|
4
|
-
let(:comma_before_lock_none) { false }
|
5
|
-
let(:queries_with_lock) { {} }
|
6
|
-
|
7
|
-
context "#add_index" do
|
8
|
-
let(:queries) do
|
9
|
-
{
|
10
|
-
[:testing, :foo, {}] =>
|
11
|
-
"CREATE INDEX `index_testing_on_foo` ON `testing` (`foo`)",
|
12
|
-
[:testing, :foo, { length: 10 }] =>
|
13
|
-
"CREATE INDEX `index_testing_on_foo` ON `testing` (`foo`(10))",
|
14
|
-
[:testing, [:foo, :bar, :baz], {}] =>
|
15
|
-
"CREATE INDEX `index_testing_on_foo_and_bar_and_baz` ON `testing` (`foo`, `bar`, `baz`)",
|
16
|
-
[:testing, [:foo, :bar, :baz], { unique: true }] =>
|
17
|
-
"CREATE UNIQUE INDEX `index_testing_on_foo_and_bar_and_baz` ON `testing` (`foo`, `bar`, `baz`)",
|
18
|
-
[:testing, [:foo, :bar, :baz], { unique: true, name: "best_index_of_the_world" }] =>
|
19
|
-
"CREATE UNIQUE INDEX `best_index_of_the_world` ON `testing` (`foo`, `bar`, `baz`)",
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
let(:method_name) { :add_index }
|
24
|
-
|
25
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
26
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
27
|
-
end
|
28
|
-
|
29
|
-
context "#remove_index" do
|
30
|
-
let(:queries) do
|
31
|
-
{
|
32
|
-
[:testing, :baz, {}] =>
|
33
|
-
"DROP INDEX `index_testing_on_baz` ON `testing`",
|
34
|
-
[:testing, [:bar, :baz], {}] =>
|
35
|
-
"DROP INDEX `index_testing_on_bar_and_baz` ON `testing`",
|
36
|
-
[:testing, { column: [:bar, :baz] }, {}] =>
|
37
|
-
"DROP INDEX `index_testing_on_bar_and_baz` ON `testing`",
|
38
|
-
[:testing, { name: "best_index_of_the_world2" }, {}] =>
|
39
|
-
"DROP INDEX `best_index_of_the_world2` ON `testing`"
|
40
|
-
}
|
41
|
-
end
|
42
|
-
let(:method_name) { :remove_index }
|
43
|
-
|
44
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
45
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
46
|
-
end
|
47
|
-
|
48
|
-
context "#rename_index" do
|
49
|
-
let(:queries) do
|
50
|
-
{
|
51
|
-
[:testing, "best_index_of_the_world2", "renamed_best_index_of_the_world2", {}] =>
|
52
|
-
[
|
53
|
-
"DROP INDEX `best_index_of_the_world2` ON `testing`",
|
54
|
-
"CREATE INDEX `renamed_best_index_of_the_world2` ON `testing` (`extra`)",
|
55
|
-
],
|
56
|
-
[:testing, "best_index_of_the_world3", "renamed_best_index_of_the_world3", {}] =>
|
57
|
-
[
|
58
|
-
"DROP INDEX `best_index_of_the_world3` ON `testing`",
|
59
|
-
"CREATE UNIQUE INDEX `renamed_best_index_of_the_world3` ON `testing` (`baz`, `extra`)",
|
60
|
-
]
|
61
|
-
}
|
62
|
-
end
|
63
|
-
let(:method_name) { :rename_index }
|
64
|
-
|
65
|
-
it_behaves_like "a method that adds LOCK=NONE when needed"
|
66
|
-
it_behaves_like "a request with LOCK=NONE that doesn't crash in MySQL"
|
67
|
-
end
|
68
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
shared_examples_for "a method that adds LOCK=NONE when needed" do
|
2
|
-
before(:each) do
|
3
|
-
stub_execute
|
4
|
-
@queries = queries.merge(queries_with_lock)
|
5
|
-
end
|
6
|
-
|
7
|
-
it "adds LOCK=NONE at the end of the query" do
|
8
|
-
@queries.each do |arguments, output|
|
9
|
-
Array.wrap(output).each { |out| should_receive(:execute).with(add_lock_none(out, comma_before_lock_none)) }
|
10
|
-
@adapter.public_send(method_name, *arguments)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
context "with AR global setting set to false" do
|
15
|
-
before(:each) { set_ar_setting(false) }
|
16
|
-
|
17
|
-
it "doesn't add lock none" do
|
18
|
-
@queries.each do |arguments, output|
|
19
|
-
Array.wrap(output).each { |out| should_receive(:execute).with(out) }
|
20
|
-
@adapter.public_send(method_name, *arguments)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
context "with lock: true option" do
|
26
|
-
it "doesn't add lock none" do
|
27
|
-
@queries.each do |arguments, output|
|
28
|
-
Array.wrap(output).each { |out| should_receive(:execute).with(out) }
|
29
|
-
arguments[-1] = arguments.last.merge(lock: true)
|
30
|
-
@adapter.public_send(method_name, *arguments)
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
shared_examples_for "a request with LOCK=NONE that doesn't crash in MySQL" do
|
37
|
-
it "succeeds without exception" do
|
38
|
-
queries.each do |_, output|
|
39
|
-
Array.wrap(output).each do |out|
|
40
|
-
@adapter.execute(add_lock_none(out, comma_before_lock_none))
|
41
|
-
rebuild_table
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
shared_examples_for "queries_with_lock that executes properly" do
|
48
|
-
it "succeeds without exception" do
|
49
|
-
queries.each do |_, output|
|
50
|
-
Array.wrap(output).each do |out|
|
51
|
-
@adapter.execute(out)
|
52
|
-
rebuild_table
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|