pt-osc 0.0.4 → 0.0.5
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/CHANGELOG.md +5 -0
- data/README.md +1 -0
- data/lib/active_record/connection_adapters/pt_osc_adapter.rb +7 -9
- data/lib/active_record/pt_osc_migration.rb +36 -21
- data/lib/pt-osc/version.rb +1 -1
- data/test/unit/pt_osc_adapter_test.rb +19 -0
- data/test/unit/pt_osc_migration_unit_test.rb +232 -0
- metadata +3 -3
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -39,6 +39,7 @@ environment:
|
|
39
39
|
Additional/modified options for the `percona` hash include:
|
40
40
|
- `defaults-file`: Can be specified as an absolute path (with leading `/`) or relative (without). Relative paths will be treated as relative to your project's working directory.
|
41
41
|
- `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'`.
|
42
|
+
- `log`: Specify the file used for logging activity. Can be a relative or absolute path.
|
42
43
|
|
43
44
|
## Caveats
|
44
45
|
|
@@ -109,25 +109,23 @@ module ActiveRecord
|
|
109
109
|
end
|
110
110
|
|
111
111
|
def get_commands_string(table_name)
|
112
|
-
|
113
|
-
|
114
|
-
@osc_commands[table_name].join(',')
|
112
|
+
get_commands[table_name] ||= []
|
113
|
+
get_commands[table_name].join(',')
|
115
114
|
end
|
116
115
|
|
117
116
|
def get_commanded_tables
|
118
|
-
|
117
|
+
get_commands.keys
|
119
118
|
end
|
120
119
|
|
121
120
|
protected
|
122
121
|
def add_command(table_name, command)
|
123
|
-
|
124
|
-
|
125
|
-
@osc_commands[table_name] << command
|
122
|
+
get_commands[table_name] ||= []
|
123
|
+
get_commands[table_name] << command
|
126
124
|
end
|
127
125
|
|
128
|
-
def get_commands(table_name)
|
126
|
+
def get_commands(table_name = nil)
|
129
127
|
@osc_commands ||= {}
|
130
|
-
@osc_commands[table_name]
|
128
|
+
table_name.nil? ? @osc_commands : @osc_commands[table_name]
|
131
129
|
end
|
132
130
|
end
|
133
131
|
end
|
@@ -71,33 +71,36 @@ module ActiveRecord
|
|
71
71
|
protected
|
72
72
|
def execute_pt_osc
|
73
73
|
return unless @connection.is_a? ActiveRecord::ConnectionAdapters::PtOscAdapter
|
74
|
+
return if @connection.get_commanded_tables.empty?
|
74
75
|
|
75
|
-
|
76
|
-
|
76
|
+
database_name = database_config[:database]
|
77
|
+
announce 'running pt-online-schema-change'
|
77
78
|
|
78
|
-
|
79
|
+
@connection.get_commanded_tables.each { |table| migrate_table(database_name, table) }
|
80
|
+
@connection.clear_commands
|
81
|
+
end
|
79
82
|
|
80
|
-
|
83
|
+
def migrate_table(database_name, table_name)
|
84
|
+
execute_sql = @connection.get_commands_string(table_name)
|
81
85
|
|
82
|
-
|
83
|
-
announce 'running pt-online-schema-change'
|
86
|
+
logger.info "Running on #{database_name}|#{table_name}: #{execute_sql}"
|
84
87
|
|
85
|
-
|
86
|
-
|
87
|
-
logger.info "Command is #{command}"
|
88
|
-
success = Kernel.system command
|
89
|
-
if success
|
90
|
-
logger.info "Successfully #{dry_run ? 'dry ran' : 'executed'} on #{database_name}|#{table_name}: #{execute_sql}"
|
91
|
-
else
|
92
|
-
failure_message = "Unable to #{dry_run ? 'dry run' : 'execute'} query on #{database_name}|#{table_name}: #{execute_sql}"
|
93
|
-
logger.error failure_message
|
94
|
-
raise RuntimeError.new(failure_message)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
88
|
+
[true, false].each { |dry_run| execute_sql_for_table(execute_sql, database_name, table_name, dry_run) }
|
89
|
+
end
|
99
90
|
|
100
|
-
|
91
|
+
def execute_sql_for_table(execute_sql, database_name, table_name, dry_run = true)
|
92
|
+
command = percona_command(execute_sql, database_name, table_name, execute: !dry_run)
|
93
|
+
logger.info "Command is #{command}"
|
94
|
+
|
95
|
+
success = Kernel.system command
|
96
|
+
|
97
|
+
if success
|
98
|
+
logger.info "Successfully #{dry_run ? 'dry ran' : 'executed'} on #{database_name}|#{table_name}: #{execute_sql}"
|
99
|
+
else
|
100
|
+
failure_message = "Unable to #{dry_run ? 'dry run' : 'execute'} query on #{database_name}|#{table_name}: #{execute_sql}"
|
101
|
+
logger.error failure_message
|
102
|
+
raise RuntimeError.new(failure_message)
|
103
|
+
end
|
101
104
|
end
|
102
105
|
|
103
106
|
def print_pt_osc
|
@@ -159,6 +162,18 @@ module ActiveRecord
|
|
159
162
|
database_config[:percona] || {}
|
160
163
|
end
|
161
164
|
|
165
|
+
def logfile
|
166
|
+
File.open(make_path_absolute(percona_config[:log] || 'log/pt_osc.log'), 'a')
|
167
|
+
end
|
168
|
+
|
169
|
+
def logger
|
170
|
+
return @logger if @logger
|
171
|
+
@logger = Logger.new(logfile)
|
172
|
+
@logger.formatter = Logger::Formatter.new # Don't let ActiveSupport override with SimpleFormatter
|
173
|
+
@logger.progname = 'pt-osc'
|
174
|
+
@logger
|
175
|
+
end
|
176
|
+
|
162
177
|
private
|
163
178
|
# Flag mutators
|
164
179
|
def make_path_absolute(path)
|
data/lib/pt-osc/version.rb
CHANGED
@@ -191,6 +191,25 @@ class PtOscAdapterTest < Test::Unit::TestCase
|
|
191
191
|
assert_nil @adapter.send(:get_commands, table_name)
|
192
192
|
end
|
193
193
|
end
|
194
|
+
|
195
|
+
context 'with existing commands' do
|
196
|
+
setup do
|
197
|
+
@commands_hash = 3.times.inject({}) do |hash|
|
198
|
+
hash[Faker::Lorem.word] = [Faker::Lorem.sentence]
|
199
|
+
hash
|
200
|
+
end
|
201
|
+
@adapter.instance_variable_set(:@osc_commands, @commands_hash)
|
202
|
+
end
|
203
|
+
|
204
|
+
should 'return the entire commands hash when no table is given' do
|
205
|
+
assert_equal @commands_hash, @adapter.send(:get_commands)
|
206
|
+
end
|
207
|
+
|
208
|
+
should "return only the given table's command array" do
|
209
|
+
table = @commands_hash.keys.first
|
210
|
+
assert_equal @commands_hash[table], @adapter.send(:get_commands, table)
|
211
|
+
end
|
212
|
+
end
|
194
213
|
end
|
195
214
|
|
196
215
|
context '#get_commands_string' do
|
@@ -99,5 +99,237 @@ class PtOscMigrationUnitTest < Test::Unit::TestCase
|
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
|
+
|
103
|
+
context '#execute_pt_osc' do
|
104
|
+
context 'with a pt-osc connection' do
|
105
|
+
setup do
|
106
|
+
@mock_connection = mock
|
107
|
+
@mock_connection.stubs(:is_a?).with(ActiveRecord::ConnectionAdapters::PtOscAdapter).returns(true)
|
108
|
+
|
109
|
+
@old_connection = @migration.instance_variable_get(:@connection)
|
110
|
+
@migration.instance_variable_set(:@connection, @mock_connection)
|
111
|
+
end
|
112
|
+
|
113
|
+
teardown do
|
114
|
+
@migration.instance_variable_set(:@connection, @old_connection)
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'connected to a database' do
|
118
|
+
setup do
|
119
|
+
@database_name = Faker::Lorem.word
|
120
|
+
@migration.stubs(:database_config).returns(database: @database_name)
|
121
|
+
end
|
122
|
+
|
123
|
+
teardown do
|
124
|
+
@migration.unstub(:database_config)
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'with no tables or commands' do
|
128
|
+
setup do
|
129
|
+
fake_not_empty_array = []
|
130
|
+
fake_not_empty_array.stubs(:empty?).returns(false)
|
131
|
+
@mock_connection.stubs(:get_commanded_tables).returns(fake_not_empty_array)
|
132
|
+
@mock_connection.stubs(:clear_commands)
|
133
|
+
end
|
134
|
+
|
135
|
+
teardown do
|
136
|
+
@mock_connection.unstub(:get_commanded_tables)
|
137
|
+
@mock_connection.unstub(:clear_commands)
|
138
|
+
end
|
139
|
+
|
140
|
+
should 'announce its intention' do
|
141
|
+
@migration.expects(:announce).with('running pt-online-schema-change')
|
142
|
+
@migration.send(:execute_pt_osc)
|
143
|
+
end
|
144
|
+
|
145
|
+
should 'call migrate_table for each table' do
|
146
|
+
num_tables = (0..5).to_a.sample
|
147
|
+
@mock_connection.unstub(:get_commanded_tables)
|
148
|
+
@mock_connection.expects(:get_commanded_tables).twice.returns(num_tables.times.map { Faker::Lorem.word })
|
149
|
+
|
150
|
+
@migration.expects(:migrate_table).times(num_tables)
|
151
|
+
quietly { @migration.send(:execute_pt_osc) }
|
152
|
+
end
|
153
|
+
|
154
|
+
should 'clear commands when finished' do
|
155
|
+
@mock_connection.expects(:clear_commands)
|
156
|
+
quietly { @migration.send(:execute_pt_osc) }
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context '#migrate_table' do
|
164
|
+
context 'with a pt-osc connection' do
|
165
|
+
setup do
|
166
|
+
@mock_connection = mock
|
167
|
+
|
168
|
+
@old_connection = @migration.instance_variable_get(:@connection)
|
169
|
+
@migration.instance_variable_set(:@connection, @mock_connection)
|
170
|
+
end
|
171
|
+
|
172
|
+
teardown do
|
173
|
+
@migration.instance_variable_set(:@connection, @old_connection)
|
174
|
+
end
|
175
|
+
|
176
|
+
context 'with a command string' do
|
177
|
+
setup do
|
178
|
+
@mock_connection.stubs(:get_commands_string).returns('<<command string>>')
|
179
|
+
end
|
180
|
+
|
181
|
+
teardown do
|
182
|
+
@mock_connection.unstub(:get_commands_string)
|
183
|
+
end
|
184
|
+
|
185
|
+
context 'with stubbed log' do
|
186
|
+
setup do
|
187
|
+
@dummy_log = StringIO.new
|
188
|
+
@migration.stubs(:logfile).returns(@dummy_log)
|
189
|
+
end
|
190
|
+
|
191
|
+
teardown do
|
192
|
+
@migration.unstub(:logfile)
|
193
|
+
end
|
194
|
+
|
195
|
+
should 'log the database, table, and SQL command' do
|
196
|
+
database_name = Faker::Lorem.word
|
197
|
+
table_name = Faker::Lorem.word
|
198
|
+
|
199
|
+
@migration.stubs(:execute_sql_for_table)
|
200
|
+
@migration.send(:migrate_table, database_name, table_name)
|
201
|
+
|
202
|
+
assert database_name.in?(@dummy_log.string), 'Log entry did not contain database name'
|
203
|
+
assert table_name.in?(@dummy_log.string), 'Log entry did not contain table name'
|
204
|
+
assert '<<command string>>'.in?(@dummy_log.string), 'Log entry did not contain command string'
|
205
|
+
end
|
206
|
+
|
207
|
+
should 'call execute twice (dry run and execute)' do
|
208
|
+
@migration.expects(:execute_sql_for_table).with(anything, anything, anything, true).once
|
209
|
+
@migration.expects(:execute_sql_for_table).with(anything, anything, anything, false).once
|
210
|
+
@migration.send(:migrate_table, nil, nil)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context '#execute_sql_for_table' do
|
218
|
+
context 'with stubbed command' do
|
219
|
+
setup do
|
220
|
+
Kernel.stubs(:system).returns(true)
|
221
|
+
end
|
222
|
+
|
223
|
+
teardown do
|
224
|
+
Kernel.unstub(:system)
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'with stubbed log' do
|
228
|
+
setup do
|
229
|
+
@dummy_log = StringIO.new
|
230
|
+
@migration.stubs(:logfile).returns(@dummy_log)
|
231
|
+
end
|
232
|
+
|
233
|
+
teardown do
|
234
|
+
@migration.unstub(:logfile)
|
235
|
+
end
|
236
|
+
|
237
|
+
should 'log the command' do
|
238
|
+
@migration.expects(:percona_command).returns('<<percona command>>')
|
239
|
+
@migration.send(:execute_sql_for_table, nil, nil, nil)
|
240
|
+
assert '<<percona command>>'.in?(@dummy_log.string), 'Log entry did not contain percona command'
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'with successful execution' do
|
244
|
+
setup do
|
245
|
+
Kernel.expects(:system).returns(true)
|
246
|
+
end
|
247
|
+
|
248
|
+
teardown do
|
249
|
+
Kernel.unstub(:system)
|
250
|
+
end
|
251
|
+
|
252
|
+
should 'log success' do
|
253
|
+
@migration.send(:execute_sql_for_table, nil, nil, nil)
|
254
|
+
assert 'Success'.in?(@dummy_log.string), 'Success not mentioned in log'
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context 'with failed execution' do
|
259
|
+
setup do
|
260
|
+
Kernel.expects(:system).returns(false)
|
261
|
+
end
|
262
|
+
|
263
|
+
teardown do
|
264
|
+
Kernel.unstub(:system)
|
265
|
+
end
|
266
|
+
|
267
|
+
should 'log failure' do
|
268
|
+
@migration.send(:execute_sql_for_table, nil, nil, nil) rescue nil
|
269
|
+
assert 'Unable to'.in?(@dummy_log.string), 'Failure not mentioned in log'
|
270
|
+
end
|
271
|
+
|
272
|
+
should 'raise a RuntimeError' do
|
273
|
+
assert_raises(RuntimeError) { @migration.send(:execute_sql_for_table, nil, nil, nil) }
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context '#logger' do
|
281
|
+
context 'with stubbed log' do
|
282
|
+
setup do
|
283
|
+
@dummy_log = StringIO.new
|
284
|
+
@migration.stubs(:logfile).returns(@dummy_log)
|
285
|
+
end
|
286
|
+
|
287
|
+
teardown do
|
288
|
+
@migration.unstub(:logfile)
|
289
|
+
end
|
290
|
+
|
291
|
+
should 'log entries with "pt-osc"' do
|
292
|
+
logger = @migration.send(:logger)
|
293
|
+
logger.info 'test'
|
294
|
+
assert 'pt-osc'.in?(@dummy_log.string), "Log entry did not contain 'pt-osc': #{@dummy_log.string}"
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
context '#logfile' do
|
300
|
+
context 'with nothing in config' do
|
301
|
+
setup do
|
302
|
+
@migration.stubs(:percona_config).returns({})
|
303
|
+
end
|
304
|
+
|
305
|
+
teardown do
|
306
|
+
@migration.unstub(:percona_config)
|
307
|
+
end
|
308
|
+
|
309
|
+
should 'use log/pt_osc.log' do
|
310
|
+
@migration.stubs(:make_path_absolute).with('log/pt_osc.log')
|
311
|
+
.returns(File.expand_path('../dummy/log/pt_osc.log', File.dirname(__FILE__)))
|
312
|
+
logfile = @migration.send(:logfile)
|
313
|
+
assert 'log/pt_osc.log'.in?(logfile.path), 'Default log file not found in path'
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
context 'with a logfile specified in config' do
|
318
|
+
setup do
|
319
|
+
@migration.stubs(:percona_config).returns(log: 'log/fakelog.file')
|
320
|
+
end
|
321
|
+
|
322
|
+
teardown do
|
323
|
+
@migration.unstub(:percona_config)
|
324
|
+
end
|
325
|
+
|
326
|
+
should 'use log/pt_osc.log' do
|
327
|
+
@migration.stubs(:make_path_absolute).with('log/fakelog.file')
|
328
|
+
.returns(File.expand_path('../dummy/log/fakelog.file', File.dirname(__FILE__)))
|
329
|
+
logfile = @migration.send(:logfile)
|
330
|
+
assert 'log/fakelog.file'.in?(logfile.path), 'Configured log file not found in path'
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|
102
334
|
end
|
103
335
|
end
|
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.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -229,7 +229,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
229
229
|
version: '0'
|
230
230
|
segments:
|
231
231
|
- 0
|
232
|
-
hash: -
|
232
|
+
hash: -1631463669037670360
|
233
233
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
234
234
|
none: false
|
235
235
|
requirements:
|
@@ -238,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
238
|
version: '0'
|
239
239
|
segments:
|
240
240
|
- 0
|
241
|
-
hash: -
|
241
|
+
hash: -1631463669037670360
|
242
242
|
requirements: []
|
243
243
|
rubyforge_project:
|
244
244
|
rubygems_version: 1.8.23
|