pt-osc 0.2.0 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +19 -1
- data/CHANGELOG.md +12 -0
- data/README.md +17 -8
- data/lib/active_record/pt_osc_migration.rb +48 -11
- data/lib/pt-osc/version.rb +1 -1
- data/test/functional/pt_osc_migration_functional_test.rb +44 -17
- data/test/integration/pt_osc_migration_integration_test.rb +102 -0
- data/test/test_helper.rb +23 -0
- data/test/travisci/install_pt_osc.sh +30 -0
- data/test/unit/pt_osc_migration_unit_test.rb +132 -4
- metadata +8 -4
data/.travis.yml
CHANGED
@@ -7,13 +7,23 @@ rvm:
|
|
7
7
|
- 2.0.0
|
8
8
|
- 2.1.2
|
9
9
|
- 2.1
|
10
|
+
- 2.2
|
11
|
+
- ruby-head
|
10
12
|
env:
|
11
|
-
|
13
|
+
global:
|
14
|
+
- CODECLIMATE_REPO_TOKEN=0b58d4e91bfac6ed6e48b31606f6792598a3a59c27bded19d58ef8406c09f57c
|
15
|
+
matrix:
|
16
|
+
- PT_OSC_VERSION=LATEST
|
17
|
+
- PT_OSC_VERSION=DEVELOPMENT
|
18
|
+
- PT_OSC_VERSION=2.2.12
|
19
|
+
- PT_OSC_VERSION=2.2.11
|
20
|
+
- PT_OSC_VERSION=2.1.11
|
12
21
|
cache:
|
13
22
|
- bundler
|
14
23
|
- apt
|
15
24
|
bundler_args: --binstubs --retry=5
|
16
25
|
before_script:
|
26
|
+
- $TRAVIS_BUILD_DIR/test/travisci/install_pt_osc.sh
|
17
27
|
- unset RAILS_ENV
|
18
28
|
- unset RACK_ENV
|
19
29
|
- bundle exec rake db:test_create
|
@@ -22,3 +32,11 @@ script:
|
|
22
32
|
addons:
|
23
33
|
code_climate:
|
24
34
|
repo_token: 0b58d4e91bfac6ed6e48b31606f6792598a3a59c27bded19d58ef8406c09f57c
|
35
|
+
matrix:
|
36
|
+
fast_finish: true
|
37
|
+
allow_failures:
|
38
|
+
- rvm: ruby-head
|
39
|
+
- rvm: 2.2
|
40
|
+
- env: PT_OSC_VERSION=DEVELOPMENT
|
41
|
+
- env: PT_OSC_VERSION=1.0.2
|
42
|
+
- env: PT_OSC_VERSION=2.0.5
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,15 @@
|
|
1
|
+
## 0.2.2
|
2
|
+
|
3
|
+
- fix bugs with string quoting, use shellwords instead
|
4
|
+
- added integration tests for execution of pt-osc migrations
|
5
|
+
- support `user` and `password` flags in percona config
|
6
|
+
- pull `username` and `password` from database config when available
|
7
|
+
|
8
|
+
## 0.2.1
|
9
|
+
|
10
|
+
- properly quote string values in MySQL commands
|
11
|
+
- added additional tests
|
12
|
+
|
1
13
|
## 0.2.0
|
2
14
|
|
3
15
|
- support for setting the [`--check-alter` flag](http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--%5Bno%5Dcheck-alter)
|
data/README.md
CHANGED
@@ -41,6 +41,8 @@ Additional/modified options for the `percona` hash include:
|
|
41
41
|
- `check-alter`: When set to `false`, `ALTER` commands will not be checked when executing (same as [`--no-check-alter`](http://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html#cmdoption-pt-online-schema-change--%5Bno%5Dcheck-alter))
|
42
42
|
- `run_mode`: Specify `'execute'` to actually run `pt-online-schema-change` when the migration runs. Specify `'print'` to output the commands to run to STDOUT instead. Default is `'print'`.
|
43
43
|
- `log`: Specify the file used for logging activity. Can be a relative or absolute path.
|
44
|
+
- `username`: By default, the command will use the username given in your standard database config. Specify a username here to override. Specify `false` to omit username (such as when using a `defaults-file`.
|
45
|
+
- `password`: By default, the command will use the password given in your standard database config. Specify a password here to override. Specify `false` to omit password (such as when using a `defaults-file`.
|
44
46
|
|
45
47
|
#### Migrations
|
46
48
|
|
@@ -58,16 +60,23 @@ If you have migrations that you do not want to be run with `pt-online-schema-cha
|
|
58
60
|
|
59
61
|
This gem is not considered production ready. There will be bugs.
|
60
62
|
|
61
|
-
##### Compatibility
|
63
|
+
##### Requirements and Compatibility
|
62
64
|
|
63
|
-
|
64
|
-
-
|
65
|
-
- Ruby 1.9.3
|
66
|
-
- Ruby 2.0.0
|
67
|
-
- Ruby 2.1.2
|
68
|
-
- Ruby 2.1 latest
|
65
|
+
###### ActiveRecord
|
66
|
+
- 3.2 branch
|
69
67
|
|
70
|
-
|
68
|
+
###### Ruby
|
69
|
+
- Ruby 1.9.3
|
70
|
+
- Ruby 2.0.0
|
71
|
+
- Ruby 2.1.2
|
72
|
+
- Ruby 2.1 latest
|
73
|
+
|
74
|
+
###### Percona Toolkit
|
75
|
+
- 2.2 branch (latest)
|
76
|
+
- 2.1 branch (latest)
|
77
|
+
- does **not** work with 2.0 or 1.0 branches
|
78
|
+
|
79
|
+
Support for other versions of these tools is unknown and not guaranteed.
|
71
80
|
|
72
81
|
`pt-osc` requires the use of `pt-online-schema-change` 2.0 or later. Some options may not work with all versions.
|
73
82
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_record/migration'
|
2
2
|
require 'active_record/connection_adapters/mysql_pt_osc_adapter'
|
3
|
+
require 'shellwords'
|
3
4
|
|
4
5
|
module ActiveRecord
|
5
6
|
class PtOscMigration < Migration
|
@@ -19,7 +20,18 @@ module ActiveRecord
|
|
19
20
|
default: true,
|
20
21
|
mutator: :execute_only,
|
21
22
|
version: '>= 2.1',
|
22
|
-
}
|
23
|
+
},
|
24
|
+
'user' => {
|
25
|
+
mutator: :get_from_config,
|
26
|
+
arguments: {
|
27
|
+
key_name: 'username',
|
28
|
+
},
|
29
|
+
default: nil,
|
30
|
+
},
|
31
|
+
'password' => {
|
32
|
+
mutator: :get_from_config,
|
33
|
+
default: nil,
|
34
|
+
},
|
23
35
|
}.freeze
|
24
36
|
|
25
37
|
def self.percona_flags
|
@@ -98,7 +110,7 @@ module ActiveRecord
|
|
98
110
|
|
99
111
|
def execute_sql_for_table(execute_sql, database_name, table_name, dry_run = true)
|
100
112
|
command = percona_command(execute_sql, database_name, table_name, execute: !dry_run)
|
101
|
-
logger.info "Command is #{command}"
|
113
|
+
logger.info "Command is #{self.class.sanitize_command(command)}"
|
102
114
|
|
103
115
|
success = Kernel.system command
|
104
116
|
|
@@ -122,7 +134,7 @@ module ActiveRecord
|
|
122
134
|
announce 'Run the following commands:'
|
123
135
|
|
124
136
|
[true, false].each do |dry_run|
|
125
|
-
write percona_command(execute_sql, database_name, table_name, execute: !dry_run)
|
137
|
+
write self.class.sanitize_command(percona_command(execute_sql, database_name, table_name, execute: !dry_run))
|
126
138
|
end
|
127
139
|
|
128
140
|
end
|
@@ -131,7 +143,7 @@ module ActiveRecord
|
|
131
143
|
end
|
132
144
|
|
133
145
|
def percona_command(execute_sql, database_name, table_name, options = {})
|
134
|
-
command =
|
146
|
+
command = ['pt-online-schema-change', '--alter', execute_sql || '', "D=#{database_name},t=#{table_name}"]
|
135
147
|
|
136
148
|
# Whitelist
|
137
149
|
options = HashWithIndifferentAccess.new(options)
|
@@ -150,7 +162,9 @@ module ActiveRecord
|
|
150
162
|
options[flag] = flag_config[:default] if flag_config.key?(:default) && !options.key?(flag)
|
151
163
|
end
|
152
164
|
|
153
|
-
|
165
|
+
command_parts = command + [run_mode_flag(options)] + command_flags(options)
|
166
|
+
|
167
|
+
command_parts.shelljoin
|
154
168
|
end
|
155
169
|
|
156
170
|
def self.tool_version
|
@@ -158,7 +172,7 @@ module ActiveRecord
|
|
158
172
|
end
|
159
173
|
|
160
174
|
def database_config
|
161
|
-
@db_config ||=
|
175
|
+
@db_config ||= raw_database_config.with_indifferent_access
|
162
176
|
end
|
163
177
|
|
164
178
|
def percona_config
|
@@ -178,8 +192,12 @@ module ActiveRecord
|
|
178
192
|
end
|
179
193
|
|
180
194
|
private
|
195
|
+
def raw_database_config
|
196
|
+
connection.pool.spec.config || ActiveRecord::Base.connection_config
|
197
|
+
end
|
198
|
+
|
181
199
|
def command_flags(options)
|
182
|
-
options.
|
200
|
+
options.flat_map do |key, value|
|
183
201
|
next if key == 'execute'
|
184
202
|
flag_options = self.class.percona_flags[key]
|
185
203
|
|
@@ -190,7 +208,8 @@ module ActiveRecord
|
|
190
208
|
|
191
209
|
# Mutate the value if needed
|
192
210
|
if flag_options.try(:key?, :mutator)
|
193
|
-
value = send(flag_options[:mutator], value, all_options: options, flag_name: key)
|
211
|
+
value = send(flag_options[:mutator], value, { all_options: options, flag_name: key }.merge(flag_options[:arguments] || {}))
|
212
|
+
next if value.nil? # Allow a mutator to determine the flag shouldn't be used
|
194
213
|
end
|
195
214
|
|
196
215
|
# Handle boolean flags
|
@@ -199,18 +218,25 @@ module ActiveRecord
|
|
199
218
|
value = nil
|
200
219
|
end
|
201
220
|
|
202
|
-
"
|
203
|
-
end.
|
221
|
+
["--#{key}", value]
|
222
|
+
end.compact
|
204
223
|
end
|
205
224
|
|
206
225
|
def run_mode_flag(options)
|
207
|
-
options[:execute] ? '
|
226
|
+
options[:execute] ? '--execute' : '--dry-run'
|
208
227
|
end
|
209
228
|
|
210
229
|
def self.get_tool_version
|
211
230
|
`pt-online-schema-change --version`
|
212
231
|
end
|
213
232
|
|
233
|
+
def self.sanitize_command(command)
|
234
|
+
command_parts = command.shellsplit
|
235
|
+
password_index = command_parts.find_index('--password')
|
236
|
+
command_parts[password_index + 1] = '_hidden_' unless password_index.nil? || command_parts.length == password_index + 1
|
237
|
+
command_parts.shelljoin
|
238
|
+
end
|
239
|
+
|
214
240
|
# Flag mutators
|
215
241
|
def make_path_absolute(path, _ = {})
|
216
242
|
return path if path[0] == '/'
|
@@ -221,5 +247,16 @@ module ActiveRecord
|
|
221
247
|
def execute_only(flag, options = {})
|
222
248
|
options[:all_options][:execute] ? flag : self.class.percona_flags[options[:flag_name]][:default]
|
223
249
|
end
|
250
|
+
|
251
|
+
def get_from_config(flag, options = {})
|
252
|
+
case flag
|
253
|
+
when nil
|
254
|
+
database_config[options[:key_name] || options[:flag_name]]
|
255
|
+
when false
|
256
|
+
nil
|
257
|
+
else
|
258
|
+
flag
|
259
|
+
end
|
260
|
+
end
|
224
261
|
end
|
225
262
|
end
|
data/lib/pt-osc/version.rb
CHANGED
@@ -24,7 +24,6 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
|
|
24
24
|
@table_name = Faker::Lorem.word
|
25
25
|
@column_name = Faker::Lorem.word
|
26
26
|
@index_name = Faker::Lorem.words.join('_')
|
27
|
-
@index_name_2 = "#{@index_name}_2"
|
28
27
|
|
29
28
|
ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
|
30
29
|
ActiveRecord::Base.connection.execute <<-SQL
|
@@ -41,14 +40,22 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
|
|
41
40
|
|
42
41
|
context 'a migration with only ALTER statements' do
|
43
42
|
setup do
|
43
|
+
@renamed_column_name = Faker::Lorem.word
|
44
|
+
@new_column_name = Faker::Lorem.word
|
45
|
+
@new_table_name = Faker::Lorem.word
|
46
|
+
@index_name_2 = "#{@index_name}_2"
|
47
|
+
@index_name_3 = "#{@index_name}_3"
|
48
|
+
|
44
49
|
TestMigration.class_eval <<-EVAL
|
45
50
|
def change
|
46
|
-
rename_table :#{@table_name}, :#{
|
47
|
-
add_column :#{@table_name}, :#{
|
51
|
+
rename_table :#{@table_name}, :#{@new_table_name}
|
52
|
+
add_column :#{@table_name}, :#{@new_column_name}, :integer
|
48
53
|
change_column :#{@table_name}, :#{@column_name}, :varchar, default: 'newthing'
|
49
|
-
|
54
|
+
change_column :#{@table_name}, :#{@column_name}, :varchar, default: :newsymbol
|
55
|
+
rename_column :#{@table_name}, :#{@column_name}, :#{@renamed_column_name}
|
50
56
|
remove_column :#{@table_name}, :#{@column_name}
|
51
57
|
add_index :#{@table_name}, :#{@column_name}, name: :#{@index_name_2}
|
58
|
+
add_index :#{@table_name}, [:#{@new_column_name}, :#{@renamed_column_name}], name: :#{@index_name_3}, unique: true
|
52
59
|
remove_index :#{@table_name}, name: :#{@index_name}
|
53
60
|
end
|
54
61
|
EVAL
|
@@ -58,29 +65,29 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
|
|
58
65
|
TestMigration.send(:remove_method, :change)
|
59
66
|
end
|
60
67
|
|
61
|
-
context '
|
68
|
+
context 'with suppressed output' do
|
62
69
|
setup do
|
63
|
-
|
64
|
-
|
65
|
-
/^SHOW FULL FIELDS FROM/,
|
66
|
-
/^SHOW COLUMNS FROM/,
|
67
|
-
/^SHOW KEYS FROM/,
|
68
|
-
]
|
69
|
-
ActiveRecord::SQLCounter.any_instance.stubs(:ignore).returns(ignored_sql)
|
70
|
+
@migration.stubs(:write)
|
71
|
+
@migration.stubs(:announce)
|
70
72
|
end
|
71
73
|
|
72
74
|
teardown do
|
73
|
-
|
75
|
+
@migration.unstub(:write, :announce)
|
74
76
|
end
|
75
77
|
|
76
|
-
context '
|
78
|
+
context 'ignoring schema lookups' do
|
77
79
|
setup do
|
78
|
-
|
79
|
-
|
80
|
+
# Kind of a hacky way to do this
|
81
|
+
ignored_sql = ActiveRecord::SQLCounter.ignored_sql + [
|
82
|
+
/^SHOW FULL FIELDS FROM/,
|
83
|
+
/^SHOW COLUMNS FROM/,
|
84
|
+
/^SHOW KEYS FROM/,
|
85
|
+
]
|
86
|
+
ActiveRecord::SQLCounter.any_instance.stubs(:ignore).returns(ignored_sql)
|
80
87
|
end
|
81
88
|
|
82
89
|
teardown do
|
83
|
-
|
90
|
+
ActiveRecord::SQLCounter.any_instance.unstub(:ignore)
|
84
91
|
end
|
85
92
|
|
86
93
|
should 'not execute any queries immediately' do
|
@@ -101,6 +108,26 @@ class PtOscMigrationFunctionalTest < ActiveRecord::TestCase
|
|
101
108
|
end
|
102
109
|
end
|
103
110
|
end
|
111
|
+
|
112
|
+
context 'the resulting command' do
|
113
|
+
should 'have the correct pt-osc ALTER statement' do
|
114
|
+
expected_alter = <<-ALTER
|
115
|
+
RENAME TO `#{@new_table_name}`
|
116
|
+
ADD `#{@new_column_name}` int(11)
|
117
|
+
CHANGE `#{@column_name}` `#{@column_name}` varchar DEFAULT 'newthing'
|
118
|
+
CHANGE `#{@column_name}` `#{@column_name}` varchar DEFAULT 'newsymbol'
|
119
|
+
CHANGE `#{@column_name}` `#{@renamed_column_name}` varchar(255) DEFAULT NULL
|
120
|
+
DROP COLUMN `#{@column_name}`
|
121
|
+
ADD INDEX `#{@index_name_2}` (`#{@column_name}`)
|
122
|
+
ADD UNIQUE INDEX `#{@index_name_3}` (`#{@new_column_name}`, `#{@renamed_column_name}`)
|
123
|
+
DROP INDEX `#{@index_name}`
|
124
|
+
ALTER
|
125
|
+
expected_alter.strip!.gsub!(/^\s*/, '').gsub!("\n", ',')
|
126
|
+
|
127
|
+
@migration.change
|
128
|
+
assert_equal expected_alter, @migration.connection.get_commands_string(@table_name)
|
129
|
+
end
|
130
|
+
end
|
104
131
|
end
|
105
132
|
end
|
106
133
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class PtOscMigrationIntegrationTest < ActiveRecord::TestCase
|
4
|
+
class TestMigration < ActiveRecord::PtOscMigration; end
|
5
|
+
|
6
|
+
context 'a migration' do
|
7
|
+
setup do
|
8
|
+
@migration = TestMigration.new
|
9
|
+
@migration.stubs(:logger).returns(stub_everything)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'connected to a pt-osc database' do
|
13
|
+
setup do
|
14
|
+
ActiveRecord::Base.establish_connection(test_spec('test_execute'))
|
15
|
+
@migration.instance_variable_set(:@connection, ActiveRecord::Base.connection)
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'on an existing table with an existing column' do
|
19
|
+
setup do
|
20
|
+
@table_name = Faker::Lorem.word
|
21
|
+
@index_name = Faker::Lorem.words.join('_')
|
22
|
+
|
23
|
+
ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
|
24
|
+
# can't use "id" because it's generated by Faker::Lorem.word :)
|
25
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
26
|
+
CREATE TABLE `#{@table_name}` (
|
27
|
+
`primary` int(11) NOT NULL AUTO_INCREMENT,
|
28
|
+
PRIMARY KEY (`primary`)
|
29
|
+
);
|
30
|
+
SQL
|
31
|
+
end
|
32
|
+
|
33
|
+
teardown do
|
34
|
+
ActiveRecord::Base.connection.execute "DROP TABLE IF EXISTS `#{@table_name}`;"
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'new columns' do
|
38
|
+
setup do
|
39
|
+
@new_column_name = Faker::Lorem.word
|
40
|
+
@arguments = [
|
41
|
+
nil,
|
42
|
+
@migration,
|
43
|
+
@table_name,
|
44
|
+
@new_column_name,
|
45
|
+
{}
|
46
|
+
]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Rails' "magic" time, for Time columns
|
50
|
+
# https://github.com/rails/rails/blob/fcf9b712b1dbbcb8f48644e6f20676ad9480ba66/activerecord/lib/active_record/type/time.rb#L16
|
51
|
+
base_date = Time.utc(2000, 1, 1, 0, 0, 0)
|
52
|
+
|
53
|
+
datetime_value = Time.at(Time.now.to_i).utc # Date types we're testing don't have sub-second precision
|
54
|
+
date_value = Time.utc(datetime_value.year, datetime_value.month, datetime_value.day)
|
55
|
+
rails_time_value = Time.utc(base_date.year, base_date.month, base_date.day, datetime_value.hour, datetime_value.min, datetime_value.sec)
|
56
|
+
|
57
|
+
[
|
58
|
+
{ type: :integer, default: 42 },
|
59
|
+
{ type: :string, default: ["'foobar'", ':bazqux'], expected_default: ['foobar', 'bazqux'] },
|
60
|
+
{ type: :text, default: nil }, # TEXT columns cannot have a default http://dev.mysql.com/doc/refman/5.7/en/blob.html#idm140380410069472
|
61
|
+
{ type: :float, default: 3.14159 },
|
62
|
+
{ type: :datetime, default: "'#{datetime_value.strftime('%F %T')}'", expected_default: datetime_value },
|
63
|
+
{ type: :time, default: "'#{datetime_value.strftime('%T')}'", expected_default: rails_time_value },
|
64
|
+
{ type: :date, default: "'#{datetime_value.strftime('%F')}'", expected_default: date_value },
|
65
|
+
{ type: :binary, default: nil }, # BLOB columns cannot have a default http://dev.mysql.com/doc/refman/5.7/en/blob.html#idm140380410069472
|
66
|
+
{ type: :boolean, default: [false, true] },
|
67
|
+
].each do |test|
|
68
|
+
|
69
|
+
defaults = [test[:default]].flatten(1).compact # remove nils for columns that can't have a default
|
70
|
+
expected_defaults = test[:expected_default] ? [test[:expected_default]].flatten(1) : defaults
|
71
|
+
|
72
|
+
should "add a nullable #{test[:type]} column with default null" do
|
73
|
+
column_name = "#{@new_column_name}_#{test[:type]}"
|
74
|
+
@arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: nil, null: true"
|
75
|
+
@arguments[-2] = column_name
|
76
|
+
@arguments[-1] = { type: test[:type], null: true, default: nil }
|
77
|
+
migrate_and_test_field *@arguments
|
78
|
+
end
|
79
|
+
|
80
|
+
defaults.each_with_index do |default, index|
|
81
|
+
should "add a nullable #{test[:type]} column with default value #{default}" do
|
82
|
+
column_name = "#{@new_column_name}_#{test[:type]}"
|
83
|
+
@arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: #{default}, null: true"
|
84
|
+
@arguments[-2] = column_name
|
85
|
+
@arguments[-1] = { type: test[:type], null: true, default: expected_defaults[index] }
|
86
|
+
migrate_and_test_field *@arguments
|
87
|
+
end
|
88
|
+
|
89
|
+
should "add a not-nullable #{test[:type]} column with default value #{default}" do
|
90
|
+
column_name = "#{@new_column_name}_#{test[:type]}"
|
91
|
+
@arguments[0] = "add_column :#{@table_name}, :#{column_name}, :#{test[:type]}, default: #{default}, null: false"
|
92
|
+
@arguments[-2] = column_name
|
93
|
+
@arguments[-1] = { type: test[:type], null: false, default: expected_defaults[index] }
|
94
|
+
migrate_and_test_field *@arguments
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -18,6 +18,29 @@ def test_spec(key = 'test')
|
|
18
18
|
test_spec
|
19
19
|
end
|
20
20
|
|
21
|
+
def migrate_and_test_field(command, migration, table_name, field_name, assertions = {})
|
22
|
+
migration.class.class_eval "def change; #{command}; end"
|
23
|
+
|
24
|
+
migration.stubs(:write)
|
25
|
+
migration.stubs(:announce)
|
26
|
+
migration.migrate(:up)
|
27
|
+
migration.unstub(:write, :announce)
|
28
|
+
|
29
|
+
field = ActiveRecord::Base.connection.columns(table_name).find { |f| f.name == field_name }
|
30
|
+
if assertions.delete(:exists) == false
|
31
|
+
assert_nil field
|
32
|
+
else
|
33
|
+
assert_not_nil field
|
34
|
+
assert_equal field_name, field.name
|
35
|
+
assertions.each do |test, expected|
|
36
|
+
actual = field.send(test)
|
37
|
+
assert_equal expected, actual, "Expected #{command} to produce a field of #{test} #{expected}, but it was #{actual}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
migration.class.send(:remove_method, :change)
|
42
|
+
end
|
43
|
+
|
21
44
|
# SQLCounter is part of ActiveRecord but is not distributed with the gem (used for internal tests only)
|
22
45
|
# see https://github.com/rails/rails/blob/3-2-stable/activerecord/test/cases/helper.rb#L59
|
23
46
|
module ActiveRecord
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
set -ox
|
3
|
+
|
4
|
+
VERSION=$(lsb_release -c -s)
|
5
|
+
sudo apt-key adv --keyserver keys.gnupg.net --recv-keys 1C4CBDCDCD2EFD2A
|
6
|
+
|
7
|
+
# Add the right repository
|
8
|
+
case $PT_OSC_VERSION in
|
9
|
+
DEVELOPMENT) echo "deb http://repo.percona.com/apt $VERSION main testing" | sudo tee -a /etc/apt/sources.list; ;;
|
10
|
+
*) echo "deb http://repo.percona.com/apt $VERSION main" | sudo tee -a /etc/apt/sources.list; ;;
|
11
|
+
esac
|
12
|
+
|
13
|
+
# Update
|
14
|
+
sudo apt-get update -qq
|
15
|
+
|
16
|
+
# Install the right version
|
17
|
+
case $PT_OSC_VERSION in
|
18
|
+
LATEST|DEVELOPMENT) sudo apt-get install -y percona-toolkit; ;;
|
19
|
+
2.2.*)
|
20
|
+
wget http://www.percona.com/downloads/percona-toolkit/"$PT_OSC_VERSION"/deb/percona-toolkit_"$PT_OSC_VERSION"_all.deb
|
21
|
+
sudo dpkg -i percona-toolkit_"$PT_OSC_VERSION"_all.deb
|
22
|
+
sudo apt-get install -f -y
|
23
|
+
;;
|
24
|
+
*)
|
25
|
+
wget http://www.percona.com/downloads/percona-toolkit/"$PT_OSC_VERSION"/percona-toolkit_"$PT_OSC_VERSION"_all.deb
|
26
|
+
sudo dpkg -i percona-toolkit_"$PT_OSC_VERSION"_all.deb
|
27
|
+
sudo apt-get install -f -y
|
28
|
+
;;
|
29
|
+
esac
|
30
|
+
|
@@ -6,10 +6,13 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
6
6
|
@migration = ActiveRecord::PtOscMigration.new
|
7
7
|
@tool_version = states('tool_version').starts_as('100')
|
8
8
|
ActiveRecord::PtOscMigration.stubs(:tool_version).returns(Gem::Version.new('100')).when(@tool_version.is('100'))
|
9
|
+
@migration.stubs(:write)
|
10
|
+
@migration.stubs(:announce)
|
9
11
|
end
|
10
12
|
|
11
13
|
teardown do
|
12
14
|
ActiveRecord::PtOscMigration.unstub(:tool_version)
|
15
|
+
@migration.unstub(:write, :announce)
|
13
16
|
end
|
14
17
|
|
15
18
|
context '#percona_command' do
|
@@ -53,7 +56,7 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
53
56
|
|
54
57
|
should 'set missing flags to default values' do
|
55
58
|
flags_with_defaults = ActiveRecord::PtOscMigration.percona_flags.select do |flag, config|
|
56
|
-
config.
|
59
|
+
!config[:default].nil? && flag != 'execute' && !config[:boolean]
|
57
60
|
end
|
58
61
|
|
59
62
|
command = @migration.send(:percona_command, '', '', '')
|
@@ -158,6 +161,98 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
158
161
|
@migration.send(:percona_command, '', '', '', @options)
|
159
162
|
end
|
160
163
|
end
|
164
|
+
|
165
|
+
context 'user flag' do
|
166
|
+
context 'with normal database username specified' do
|
167
|
+
setup do
|
168
|
+
@migration.stubs(:raw_database_config).returns(username: 'foobar')
|
169
|
+
end
|
170
|
+
|
171
|
+
should 'add the user to the command' do
|
172
|
+
command = @migration.send(:percona_command, '', '', '')
|
173
|
+
assert command.include?('--user foobar'), "command #{command} did not include user"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'with user specified in percona config' do
|
178
|
+
setup do
|
179
|
+
@migration.stubs(:raw_database_config).returns(percona: { user: 'foobar' })
|
180
|
+
end
|
181
|
+
|
182
|
+
should 'add the user to the command' do
|
183
|
+
command = @migration.send(:percona_command, '', '', '')
|
184
|
+
assert command.include?('--user foobar'), "command #{command} did not include user"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context 'with user specified in database and percona config' do
|
189
|
+
setup do
|
190
|
+
@migration.stubs(:raw_database_config).returns(username: 'foobar', percona: { user: 'bazqux' })
|
191
|
+
end
|
192
|
+
|
193
|
+
should 'add the percona user to the command' do
|
194
|
+
command = @migration.send(:percona_command, '', '', '')
|
195
|
+
assert command.include?('--user bazqux'), "command #{command} did not include the right user"
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
context 'with user forbidden in percona config' do
|
200
|
+
setup do
|
201
|
+
@migration.stubs(:raw_database_config).returns(username: 'foobar', percona: { user: false })
|
202
|
+
end
|
203
|
+
|
204
|
+
should 'not add a user to the command' do
|
205
|
+
command = @migration.send(:percona_command, '', '', '')
|
206
|
+
assert !command.include?('--user foobar'), "command #{command} included user but should not have"
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
context 'password flag' do
|
212
|
+
context 'with normal database password specified' do
|
213
|
+
setup do
|
214
|
+
@migration.stubs(:raw_database_config).returns(password: 'foobar')
|
215
|
+
end
|
216
|
+
|
217
|
+
should 'add the password to the command' do
|
218
|
+
command = @migration.send(:percona_command, '', '', '')
|
219
|
+
assert command.include?('--password foobar'), "command #{command} did not include password"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'with password specified in percona config' do
|
224
|
+
setup do
|
225
|
+
@migration.stubs(:raw_database_config).returns(percona: { password: 'foobar' })
|
226
|
+
end
|
227
|
+
|
228
|
+
should 'add the password to the command' do
|
229
|
+
command = @migration.send(:percona_command, '', '', '')
|
230
|
+
assert command.include?('--password foobar'), "command #{command} did not include password"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
context 'with password specified in database and percona config' do
|
235
|
+
setup do
|
236
|
+
@migration.stubs(:raw_database_config).returns(password: 'foobar', percona: { password: 'bazqux' })
|
237
|
+
end
|
238
|
+
|
239
|
+
should 'add the percona password to the command' do
|
240
|
+
command = @migration.send(:percona_command, '', '', '')
|
241
|
+
assert command.include?('--password bazqux'), "command #{command} did not include the right password"
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'with password forbidden in percona config' do
|
246
|
+
setup do
|
247
|
+
@migration.stubs(:raw_database_config).returns(password: 'foobar', percona: { password: false })
|
248
|
+
end
|
249
|
+
|
250
|
+
should 'not add a password to the command' do
|
251
|
+
command = @migration.send(:percona_command, '', '', '')
|
252
|
+
assert !command.include?('--password foobar'), "command #{command} included password but should not have"
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
161
256
|
end
|
162
257
|
|
163
258
|
context 'connected to a pt-osc database in print mode' do
|
@@ -249,6 +344,17 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
249
344
|
end
|
250
345
|
end
|
251
346
|
|
347
|
+
context '#sanitize_command' do
|
348
|
+
should 'strip password out of commands' do
|
349
|
+
assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password supersecret')
|
350
|
+
assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password %&$^*(!(#)CD&`+')
|
351
|
+
assert_equal 'pt-online-schema-change --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password "who uses spaces in passwords?"')
|
352
|
+
assert_equal 'pt-online-schema-change --other --password _hidden_ --fields', @migration.class.send(:sanitize_command, 'pt-online-schema-change --other --password supersecret --fields')
|
353
|
+
assert_equal 'pt-online-schema-change --other --fields --password _hidden_', @migration.class.send(:sanitize_command, 'pt-online-schema-change --other --fields --password supersecret')
|
354
|
+
assert_equal 'pt-online-schema-change --password', @migration.class.send(:sanitize_command, 'pt-online-schema-change --password')
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
252
358
|
context '#make_path_absolute' do
|
253
359
|
context 'with an absolute path' do
|
254
360
|
setup do
|
@@ -289,6 +395,28 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
289
395
|
end
|
290
396
|
end
|
291
397
|
|
398
|
+
context '#get_from_config' do
|
399
|
+
setup do
|
400
|
+
@migration.stubs(:raw_database_config).returns(flag: 'foobar', other_flag: 'mooshu')
|
401
|
+
end
|
402
|
+
|
403
|
+
should 'return nil if false is specified' do
|
404
|
+
assert_nil @migration.send(:get_from_config, false)
|
405
|
+
end
|
406
|
+
|
407
|
+
should 'get the value from the config if it was not specified' do
|
408
|
+
assert_equal 'foobar', @migration.send(:get_from_config, nil, flag_name: 'flag')
|
409
|
+
end
|
410
|
+
|
411
|
+
should 'prefer the key name if given explicitly' do
|
412
|
+
assert_equal 'mooshu', @migration.send(:get_from_config, nil, flag_name: 'flag', key_name: 'other_flag')
|
413
|
+
end
|
414
|
+
|
415
|
+
should 'pass string values through' do
|
416
|
+
assert_equal 'bazqux', @migration.send(:get_from_config, 'bazqux')
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
292
420
|
context '#execute_pt_osc' do
|
293
421
|
context 'with a pt-osc connection' do
|
294
422
|
setup do
|
@@ -306,7 +434,7 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
306
434
|
context 'connected to a database' do
|
307
435
|
setup do
|
308
436
|
@database_name = Faker::Lorem.word
|
309
|
-
@migration.stubs(:
|
437
|
+
@migration.stubs(:raw_database_config).returns(database: @database_name)
|
310
438
|
end
|
311
439
|
|
312
440
|
teardown do
|
@@ -424,9 +552,9 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
424
552
|
end
|
425
553
|
|
426
554
|
should 'log the command' do
|
427
|
-
@migration.expects(:percona_command).returns('
|
555
|
+
@migration.expects(:percona_command).returns('percona command')
|
428
556
|
@migration.send(:execute_sql_for_table, nil, nil, nil)
|
429
|
-
assert '
|
557
|
+
assert 'percona command'.in?(@dummy_log.string), 'Log entry did not contain percona command'
|
430
558
|
end
|
431
559
|
|
432
560
|
context 'with successful execution' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pt-osc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-02-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -229,7 +229,9 @@ files:
|
|
229
229
|
- test/dummy/script/rails
|
230
230
|
- test/functional/mysql_pt_osc_adapter_test.rb
|
231
231
|
- test/functional/pt_osc_migration_functional_test.rb
|
232
|
+
- test/integration/pt_osc_migration_integration_test.rb
|
232
233
|
- test/test_helper.rb
|
234
|
+
- test/travisci/install_pt_osc.sh
|
233
235
|
- test/unit/mysql_pt_osc_adapter_test.rb
|
234
236
|
- test/unit/pt_osc_migration_unit_test.rb
|
235
237
|
homepage: https://github.com/steverice/pt-osc
|
@@ -246,7 +248,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
246
248
|
version: '0'
|
247
249
|
segments:
|
248
250
|
- 0
|
249
|
-
hash:
|
251
|
+
hash: 999232518907264640
|
250
252
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
251
253
|
none: false
|
252
254
|
requirements:
|
@@ -255,7 +257,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
255
257
|
version: '0'
|
256
258
|
segments:
|
257
259
|
- 0
|
258
|
-
hash:
|
260
|
+
hash: 999232518907264640
|
259
261
|
requirements: []
|
260
262
|
rubyforge_project:
|
261
263
|
rubygems_version: 1.8.23
|
@@ -301,6 +303,8 @@ test_files:
|
|
301
303
|
- test/dummy/script/rails
|
302
304
|
- test/functional/mysql_pt_osc_adapter_test.rb
|
303
305
|
- test/functional/pt_osc_migration_functional_test.rb
|
306
|
+
- test/integration/pt_osc_migration_integration_test.rb
|
304
307
|
- test/test_helper.rb
|
308
|
+
- test/travisci/install_pt_osc.sh
|
305
309
|
- test/unit/mysql_pt_osc_adapter_test.rb
|
306
310
|
- test/unit/pt_osc_migration_unit_test.rb
|