pt-osc 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|