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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1577a541999a6a5ba4675641f7ce752819a79253
4
- data.tar.gz: fa2c478e55d72bc8d78e386a36f493fe98d58f30
3
+ metadata.gz: 1f20fadc6f95454a18f4aa4ad5fa9334406b2bce
4
+ data.tar.gz: 3b7d11339aeb6016a62994a0455bf780a28f8f67
5
5
  SHA512:
6
- metadata.gz: 24384b66dfa4347dffa0c38469937eea5097ea2b1a90b4dbf64fc2be96f1f6b52ce95204c0035e68150503a422fbfab5ad0bed2244368ec0f8d0e3bf8cecd02e
7
- data.tar.gz: 4ec8b0d6cf8558b4a1e1c36d6f7bd46eaabc805d2226acc793593b9722c4d9de94c0bb64b9a51e744eedb2163a872dea2c6e525002c17954fef19baa68326d71
6
+ metadata.gz: 4b72941a2a27bad5275b3af95d80382524a907ed6b315771bf35723aecc54387e8ee718c4608351aedce2097947476195394c040caf15397ee7bf8baa7cdb0f0
7
+ data.tar.gz: 9046f16159ae85f1931893191931fb9171f5b0512353cf9b653a1b60c37d6bd2051e0251f898a3d05fb2e4e4586a009997e7545505e4fb2a83c63ad21ce097ab
data/.travis.yml ADDED
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+
5
+ before_script:
6
+ - mysql -e 'create database mysql_online_migrations;'
7
+
8
+ gemfile:
9
+ - gemfiles/rails3.gemfile
10
+ - gemfiles/rails4.gemfile
11
+
12
+ script: bundle exec rspec spec
data/Gemfile.lock CHANGED
@@ -24,7 +24,7 @@ GEM
24
24
  builder (3.0.4)
25
25
  coderay (1.0.9)
26
26
  diff-lcs (1.2.5)
27
- i18n (0.6.5)
27
+ i18n (0.6.9)
28
28
  logger (1.2.8)
29
29
  method_source (0.8.2)
30
30
  multi_json (1.8.2)
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, may be compatible with Rails 4.0 but you'd lose the new features introduced.
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 ActiveRecord "~> 3.2.15"
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` in the following context :
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
- Add `lock: true` to any of the method calls mentioned above. Example :
47
- `add_index :users, :name, lock: true`
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 `lock: true` will be useful when hitting the caveats of `LOCK=NONE`. Please read the following section.
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 `lock: true` when online migration is not supported, you'll get a MySQL exception. No risk to lock the table by accident.
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
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+ gem "activerecord", "3.2.16"
3
+ gem "activesupport", "3.2.16"
4
+ gem "mysql2"
5
+ gem "logger"
6
+ gem "rspec"
7
+ gem "pry"
@@ -0,0 +1,7 @@
1
+ source :rubygems
2
+ gem "activerecord", "4.0.2"
3
+ gem "activesupport", "4.0.2"
4
+ gem "mysql2"
5
+ gem "logger"
6
+ gem "rspec"
7
+ gem "pry"
@@ -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
- include Indexes
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 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)]
15
+ def connection
16
+ @no_lock_adapter ||= ActiveRecord::ConnectionAdapters::Mysql2AdapterWithoutLock.new(super)
26
17
  end
27
18
 
28
- def perform_migrations_online?
29
- ActiveRecord::Base.mysql_online_migrations == true
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::ConnectionAdapters::Mysql2Adapter.send(:include, MysqlOnlineMigrations)
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.1.4'
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/mysql_online_migrations'
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
@@ -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 execute(statement)
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 stub_execute
13
- original_execute = @adapter.method(:execute)
14
- @adapter.stub(:execute) do |statement|
15
- if statement =~ CATCH_STATEMENT_REGEX
16
- execute(statement.squeeze(' ').strip)
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(statement)
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
- @adapter.drop_table @table_name rescue nil
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: "root",
60
- host: "localhost",
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.1.4
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-27 00:00:00.000000000 Z
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/columns.rb
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/mysql_online_migrations/columns_spec.rb
115
- - spec/lib/mysql_online_migrations/indexes_spec.rb
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/mysql_online_migrations.rb
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.2
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/mysql_online_migrations/columns_spec.rb
145
- - spec/lib/mysql_online_migrations/indexes_spec.rb
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/mysql_online_migrations.rb
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