mysql_online_migrations 0.1.3 → 0.1.4

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: 8b8c18f26ec8b8f57c2d469d2e11e3837de24130
4
- data.tar.gz: 3a65090310d0a049c85ad22da8099ea46939a913
3
+ metadata.gz: 1577a541999a6a5ba4675641f7ce752819a79253
4
+ data.tar.gz: fa2c478e55d72bc8d78e386a36f493fe98d58f30
5
5
  SHA512:
6
- metadata.gz: 3bb7753d1b88a3a77cddf0f23a2a2b6cfe3717821dc625b0fb5024bd4cce25fc45aecd66a75cbb1e8b16464fb8cdd11da1e6b548e57a2c38adc7ec0dd2e77c6a
7
- data.tar.gz: a631129c1c90cddd1528658c37a815749a0954882fdf8ddcf387bdf1f7c555fd63dd5ecf18ca2b60443833a9df33fdd96f98a2bfe6a08f7c3e8691b09d544f76
6
+ metadata.gz: 24384b66dfa4347dffa0c38469937eea5097ea2b1a90b4dbf64fc2be96f1f6b52ce95204c0035e68150503a422fbfab5ad0bed2244368ec0f8d0e3bf8cecd02e
7
+ data.tar.gz: 4ec8b0d6cf8558b4a1e1c36d6f7bd46eaabc805d2226acc793593b9722c4d9de94c0bb64b9a51e744eedb2163a872dea2c6e525002c17954fef19baa68326d71
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mysql_online_migrations (0.1.2)
4
+ mysql_online_migrations (0.1.4)
5
5
  activerecord (~> 3.2.15)
6
6
  activesupport (~> 3.2.15)
7
7
  mysql2
data/README.md CHANGED
@@ -1,21 +1,22 @@
1
1
  mysql_online_migrations
2
2
  =======================
3
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.
4
+ Patch Rails migrations to enforce MySQL 5.6 online migrations.
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 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
+ 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.
7
8
 
8
9
 
9
10
  Requirements
10
11
  =======================
11
- Built for Rails 3.2.15, may be compatible with Rails 4.0 but you'd lose the new features introduced.
12
+ Built for Rails 3.2.15, may be compatible with Rails 4.0 but you'd lose the new features introduced.
12
13
  This gem actually just requires ActiveRecord and ActiveSupport, the full Rails gem should not be required.
13
14
 
14
15
  List of requirements :
15
16
 
16
17
  - Use mysql2 adapter
17
18
  - Use ActiveRecord "~> 3.2.15"
18
- - Use MySQL or Percona Server 5.6.X
19
+ - Use MySQL or Percona Server 5.6.X with InnoDB
19
20
 
20
21
  Scope of this gem
21
22
  =======================
@@ -25,36 +26,39 @@ Patch Rails migrations to automatically add `LOCK=NONE` in the following context
25
26
  - Index management : `add_index`, `remove_index`, `rename_index`
26
27
  - Add column : `add_column`, `add_timestamps`
27
28
  - Remove column : `remove_column`, `remove_timestamps`
29
+ - Change column : `change_column`, `change_column_null`, `change_column_default`
28
30
 
29
31
  Usage
30
32
  =======================
31
- In a typical Rails app, just add it to your gem file :
33
+ In a typical Rails app, just add it to your Gemfile :
34
+
32
35
  `gem 'mysql_online_migrations'`
33
36
 
34
- And you're ready for online migrations!
37
+ Then run `bundle install`
38
+
39
+ You're ready for online migrations! Please read the caveats section though.
35
40
 
36
41
  ### 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`:
42
+ Example for environment test (your CI might not use MySQL 5.6 yet), add the following to `config/environments/test.rb`:
38
43
  `config.active_record.mysql_online_migrations = false`
39
44
 
40
45
  ### Turn it off for a specific statement
41
- Add `lock: true` to any of the method calls mentioned above. Example :
46
+ Add `lock: true` to any of the method calls mentioned above. Example :
42
47
  `add_index :users, :name, lock: true`
43
48
 
44
- The `lock: none` will be useful when hitting the caveats of `LOCK=NONE`. Please read the following section.
49
+ The `lock: true` will be useful when hitting the caveats of `LOCK=NONE`. Please read the following section.
45
50
 
46
51
  Caveats
47
52
  =======================
48
53
 
49
- Here's a list of things you can't do with LOCK=NONE and therefore you should provide `lock: true`:
54
+ The MySQL manual contains a list of which DDL statements can be run with `LOCK=NONE` under [Table 14.5 Summary of Online Status for DDL Operations](http://dev.mysql.com/doc/refman/5.6/en/innodb-create-index-overview.html). The short version is that __you can not yet__:
50
55
 
51
56
  - Index a column of type text
52
57
  - Change the type of a column
53
58
  - 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
+ - Set a column to NOT NULL (at least not with the default SQL_MODE)
60
+ - Adding an AUTO_INCREMENT column,
59
61
 
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.
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.
63
+ It's therefore highly recommended to use it in development/test/staging environment before running migrations in production.
64
+ 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
@@ -15,6 +15,21 @@ module MysqlOnlineMigrations
15
15
  execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)} #{lock_statement(lock, true)}")
16
16
  end
17
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
+
18
33
  def rename_column(table_name, column_name, new_column_name, options = {})
19
34
  lock, options = extract_lock_from_options(options)
20
35
  execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)} #{lock_statement(lock, true)}")
@@ -1,8 +1,8 @@
1
1
  Gem::Specification.new do |s|
2
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."
3
+ s.version = '0.1.4'
4
+ s.summary = "Use MySQL 5.6+ capacities to enforce online migrations"
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"]
7
7
  s.email = 'alberto.anthony@gmail.com'
8
8
  s.homepage = 'https://github.com/anthonyalberto/mysql_online_migrations'
@@ -18,4 +18,5 @@ Gem::Specification.new do |s|
18
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
- end
21
+ s.license = 'MIT'
22
+ end
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe MysqlOnlineMigrations::Columns do
4
4
  let(:comma_before_lock_none) { true }
5
+ let(:queries_with_lock) { {} }
5
6
 
6
7
  context "#add_column" do
7
8
  let(:queries) do
@@ -81,10 +82,31 @@ describe MysqlOnlineMigrations::Columns do
81
82
 
82
83
  context "#change_column" do
83
84
  let(:queries) do
84
- # Unsupported with lock=none : change column type, change limit, change null
85
+ # Unsupported with lock=none : change column type, change limit, set NOT NULL.
85
86
  {
86
87
  [:testing, :foo, :string, { default: 'def', limit: 100 }] =>
87
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"
88
110
  }
89
111
  end
90
112
 
@@ -92,6 +114,54 @@ describe MysqlOnlineMigrations::Columns do
92
114
 
93
115
  it_behaves_like "a method that adds LOCK=NONE when needed"
94
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"
95
165
  end
96
166
 
97
167
  context "#rename_column" do
@@ -2,6 +2,7 @@ require "spec_helper"
2
2
 
3
3
  describe MysqlOnlineMigrations::Indexes do
4
4
  let(:comma_before_lock_none) { false }
5
+ let(:queries_with_lock) { {} }
5
6
 
6
7
  context "#add_index" do
7
8
  let(:queries) do
@@ -1,4 +1,7 @@
1
1
  module Helpers
2
+ CATCH_STATEMENT_REGEX = /^(alter|create|drop|update) /i
3
+ DDL_STATEMENT_REGEX = /^(alter|create|drop) /i
4
+
2
5
  def execute(statement)
3
6
  end
4
7
 
@@ -9,7 +12,7 @@ module Helpers
9
12
  def stub_execute
10
13
  original_execute = @adapter.method(:execute)
11
14
  @adapter.stub(:execute) do |statement|
12
- if statement =~ /^(alter|create|drop) /i
15
+ if statement =~ CATCH_STATEMENT_REGEX
13
16
  execute(statement.squeeze(' ').strip)
14
17
  else
15
18
  original_execute.call(statement)
@@ -18,7 +21,11 @@ module Helpers
18
21
  end
19
22
 
20
23
  def add_lock_none(str, with_comma)
21
- "#{str}#{with_comma ? ' ,' : ''} LOCK=NONE"
24
+ if str =~ DDL_STATEMENT_REGEX
25
+ "#{str}#{with_comma ? ' ,' : ''} LOCK=NONE"
26
+ else
27
+ str
28
+ end
22
29
  end
23
30
 
24
31
  def rebuild_table
@@ -28,6 +35,7 @@ module Helpers
28
35
  t.column :foo, :string, :limit => 100
29
36
  t.column :bar, :string, :limit => 100
30
37
  t.column :baz, :string, :limit => 100
38
+ t.column :bam, :string, :limit => 100, default: "test", null: false
31
39
  t.column :extra, :string, :limit => 100
32
40
  t.timestamps
33
41
  end
@@ -1,10 +1,11 @@
1
1
  shared_examples_for "a method that adds LOCK=NONE when needed" do
2
2
  before(:each) do
3
3
  stub_execute
4
+ @queries = queries.merge(queries_with_lock)
4
5
  end
5
6
 
6
7
  it "adds LOCK=NONE at the end of the query" do
7
- queries.each do |arguments, output|
8
+ @queries.each do |arguments, output|
8
9
  Array.wrap(output).each { |out| should_receive(:execute).with(add_lock_none(out, comma_before_lock_none)) }
9
10
  @adapter.public_send(method_name, *arguments)
10
11
  end
@@ -14,7 +15,7 @@ shared_examples_for "a method that adds LOCK=NONE when needed" do
14
15
  before(:each) { set_ar_setting(false) }
15
16
 
16
17
  it "doesn't add lock none" do
17
- queries.each do |arguments, output|
18
+ @queries.each do |arguments, output|
18
19
  Array.wrap(output).each { |out| should_receive(:execute).with(out) }
19
20
  @adapter.public_send(method_name, *arguments)
20
21
  end
@@ -23,7 +24,7 @@ shared_examples_for "a method that adds LOCK=NONE when needed" do
23
24
 
24
25
  context "with lock: true option" do
25
26
  it "doesn't add lock none" do
26
- queries.each do |arguments, output|
27
+ @queries.each do |arguments, output|
27
28
  Array.wrap(output).each { |out| should_receive(:execute).with(out) }
28
29
  arguments[-1] = arguments.last.merge(lock: true)
29
30
  @adapter.public_send(method_name, *arguments)
@@ -41,4 +42,15 @@ shared_examples_for "a request with LOCK=NONE that doesn't crash in MySQL" do
41
42
  end
42
43
  end
43
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
44
56
  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.3
4
+ version: 0.1.4
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-24 00:00:00.000000000 Z
11
+ date: 2013-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,8 +94,8 @@ dependencies:
94
94
  - - '>='
95
95
  - !ruby/object:Gem::Version
96
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.
97
+ description: MySQL 5.6 adds a `LOCK=NONE` option to make sure migrations are done
98
+ with no locking. Let's use it.
99
99
  email: alberto.anthony@gmail.com
100
100
  executables: []
101
101
  extensions: []
@@ -117,7 +117,8 @@ files:
117
117
  - spec/support/helpers.rb
118
118
  - spec/support/shared_examples/mysql_online_migrations.rb
119
119
  homepage: https://github.com/anthonyalberto/mysql_online_migrations
120
- licenses: []
120
+ licenses:
121
+ - MIT
121
122
  metadata: {}
122
123
  post_install_message:
123
124
  rdoc_options: []
@@ -138,7 +139,7 @@ rubyforge_project:
138
139
  rubygems_version: 2.0.2
139
140
  signing_key:
140
141
  specification_version: 4
141
- summary: Use MySQL 5.6+ capacities to perform online migrations
142
+ summary: Use MySQL 5.6+ capacities to enforce online migrations
142
143
  test_files:
143
144
  - spec/lib/mysql_online_migrations/columns_spec.rb
144
145
  - spec/lib/mysql_online_migrations/indexes_spec.rb