lhm-shopify 3.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +34 -0
  3. data/.gitignore +17 -0
  4. data/.rubocop.yml +183 -0
  5. data/.travis.yml +21 -0
  6. data/CHANGELOG.md +216 -0
  7. data/Gemfile +5 -0
  8. data/LICENSE +27 -0
  9. data/README.md +284 -0
  10. data/Rakefile +22 -0
  11. data/bin/.gitkeep +0 -0
  12. data/dbdeployer/config.json +32 -0
  13. data/dbdeployer/install.sh +64 -0
  14. data/dev.yml +20 -0
  15. data/gemfiles/ar-2.3_mysql.gemfile +6 -0
  16. data/gemfiles/ar-3.2_mysql.gemfile +5 -0
  17. data/gemfiles/ar-3.2_mysql2.gemfile +5 -0
  18. data/gemfiles/ar-4.0_mysql2.gemfile +5 -0
  19. data/gemfiles/ar-4.1_mysql2.gemfile +5 -0
  20. data/gemfiles/ar-4.2_mysql2.gemfile +5 -0
  21. data/gemfiles/ar-5.0_mysql2.gemfile +5 -0
  22. data/lhm.gemspec +34 -0
  23. data/lib/lhm.rb +131 -0
  24. data/lib/lhm/atomic_switcher.rb +52 -0
  25. data/lib/lhm/chunk_finder.rb +32 -0
  26. data/lib/lhm/chunk_insert.rb +51 -0
  27. data/lib/lhm/chunker.rb +87 -0
  28. data/lib/lhm/cleanup/current.rb +74 -0
  29. data/lib/lhm/command.rb +48 -0
  30. data/lib/lhm/entangler.rb +117 -0
  31. data/lib/lhm/intersection.rb +51 -0
  32. data/lib/lhm/invoker.rb +98 -0
  33. data/lib/lhm/locked_switcher.rb +74 -0
  34. data/lib/lhm/migration.rb +43 -0
  35. data/lib/lhm/migrator.rb +237 -0
  36. data/lib/lhm/printer.rb +59 -0
  37. data/lib/lhm/railtie.rb +9 -0
  38. data/lib/lhm/sql_helper.rb +77 -0
  39. data/lib/lhm/sql_retry.rb +61 -0
  40. data/lib/lhm/table.rb +121 -0
  41. data/lib/lhm/table_name.rb +23 -0
  42. data/lib/lhm/test_support.rb +35 -0
  43. data/lib/lhm/throttler.rb +36 -0
  44. data/lib/lhm/throttler/slave_lag.rb +145 -0
  45. data/lib/lhm/throttler/threads_running.rb +53 -0
  46. data/lib/lhm/throttler/time.rb +29 -0
  47. data/lib/lhm/timestamp.rb +11 -0
  48. data/lib/lhm/version.rb +6 -0
  49. data/shipit.rubygems.yml +0 -0
  50. data/spec/.lhm.example +4 -0
  51. data/spec/README.md +58 -0
  52. data/spec/fixtures/bigint_table.ddl +4 -0
  53. data/spec/fixtures/composite_primary_key.ddl +7 -0
  54. data/spec/fixtures/custom_primary_key.ddl +6 -0
  55. data/spec/fixtures/destination.ddl +6 -0
  56. data/spec/fixtures/lines.ddl +7 -0
  57. data/spec/fixtures/origin.ddl +6 -0
  58. data/spec/fixtures/permissions.ddl +5 -0
  59. data/spec/fixtures/small_table.ddl +4 -0
  60. data/spec/fixtures/tracks.ddl +5 -0
  61. data/spec/fixtures/users.ddl +14 -0
  62. data/spec/fixtures/wo_id_int_column.ddl +6 -0
  63. data/spec/integration/atomic_switcher_spec.rb +93 -0
  64. data/spec/integration/chunk_insert_spec.rb +29 -0
  65. data/spec/integration/chunker_spec.rb +185 -0
  66. data/spec/integration/cleanup_spec.rb +136 -0
  67. data/spec/integration/entangler_spec.rb +66 -0
  68. data/spec/integration/integration_helper.rb +237 -0
  69. data/spec/integration/invoker_spec.rb +33 -0
  70. data/spec/integration/lhm_spec.rb +585 -0
  71. data/spec/integration/lock_wait_timeout_spec.rb +30 -0
  72. data/spec/integration/locked_switcher_spec.rb +50 -0
  73. data/spec/integration/sql_retry/lock_wait_spec.rb +125 -0
  74. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +101 -0
  75. data/spec/integration/table_spec.rb +91 -0
  76. data/spec/test_helper.rb +32 -0
  77. data/spec/unit/atomic_switcher_spec.rb +31 -0
  78. data/spec/unit/chunk_finder_spec.rb +73 -0
  79. data/spec/unit/chunk_insert_spec.rb +44 -0
  80. data/spec/unit/chunker_spec.rb +166 -0
  81. data/spec/unit/entangler_spec.rb +124 -0
  82. data/spec/unit/intersection_spec.rb +51 -0
  83. data/spec/unit/lhm_spec.rb +29 -0
  84. data/spec/unit/locked_switcher_spec.rb +51 -0
  85. data/spec/unit/migrator_spec.rb +146 -0
  86. data/spec/unit/printer_spec.rb +97 -0
  87. data/spec/unit/sql_helper_spec.rb +32 -0
  88. data/spec/unit/table_name_spec.rb +39 -0
  89. data/spec/unit/table_spec.rb +47 -0
  90. data/spec/unit/throttler/slave_lag_spec.rb +317 -0
  91. data/spec/unit/throttler/threads_running_spec.rb +64 -0
  92. data/spec/unit/throttler_spec.rb +124 -0
  93. data/spec/unit/unit_helper.rb +13 -0
  94. metadata +239 -0
@@ -0,0 +1,585 @@
1
+ # Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
2
+ # Schmidt
3
+
4
+ require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
5
+
6
+ describe Lhm do
7
+ include IntegrationHelper
8
+
9
+ before(:each) { connect_master!; Lhm.cleanup(true) }
10
+
11
+ describe 'id column requirement' do
12
+ it 'should migrate the table when id is pk' do
13
+ table_create(:users)
14
+
15
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
16
+ t.add_column(:logins, "int(12) default '0'")
17
+ end
18
+
19
+ slave do
20
+ table_read(:users).columns['logins'].must_equal({
21
+ :type => 'int(12)',
22
+ :is_nullable => 'YES',
23
+ :column_default => '0',
24
+ :comment => '',
25
+ :collate => nil,
26
+ })
27
+ end
28
+ end
29
+
30
+ it 'should migrate the table when id is not pk' do
31
+ table_create(:custom_primary_key)
32
+
33
+ Lhm.change_table(:custom_primary_key, :atomic_switch => false) do |t|
34
+ t.add_column(:logins, "int(12) default '0'")
35
+ end
36
+
37
+ slave do
38
+ table_read(:custom_primary_key).columns['logins'].must_equal({
39
+ :type => 'int(12)',
40
+ :is_nullable => 'YES',
41
+ :column_default => '0',
42
+ :comment => '',
43
+ :collate => nil,
44
+ })
45
+ end
46
+ end
47
+
48
+ it 'should migrate the table when using a composite primary key if id column exists' do
49
+ table_create(:composite_primary_key)
50
+
51
+ Lhm.change_table(:composite_primary_key, :atomic_switch => false) do |t|
52
+ t.add_column(:logins, "int(12) default '0'")
53
+ end
54
+
55
+ slave do
56
+ table_read(:composite_primary_key).columns['logins'].must_equal({
57
+ :type => 'int(12)',
58
+ :is_nullable => 'YES',
59
+ :column_default => '0',
60
+ :comment => '',
61
+ :collate => nil,
62
+ })
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'changes' do
68
+ before(:each) do
69
+ table_create(:users)
70
+ table_create(:tracks)
71
+ table_create(:permissions)
72
+ end
73
+
74
+ describe 'when changing to a composite primary key' do
75
+ it 'should be able to use ddl statement to create composite keys' do
76
+
77
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
78
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
79
+ t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
80
+ t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
81
+ t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
82
+ end
83
+
84
+ slave do
85
+ connection.primary_key('users').must_equal(['username', 'id'])
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ describe 'when providing a subset of data to copy' do
92
+
93
+ before do
94
+ execute('insert into tracks set id = 13, public = 0')
95
+ 11.times { |n| execute("insert into tracks set id = #{n + 1}, public = 1") }
96
+ 11.times { |n| execute("insert into permissions set track_id = #{n + 1}") }
97
+
98
+ Lhm.change_table(:permissions, :atomic_switch => false) do |t|
99
+ t.filter('inner join tracks on tracks.`id` = permissions.`track_id` and tracks.`public` = 1')
100
+ end
101
+ end
102
+
103
+ describe 'when no additional data is inserted into the table' do
104
+
105
+ it 'migrates the existing data' do
106
+ slave do
107
+ count_all(:permissions).must_equal(11)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe 'when additional data is inserted' do
113
+
114
+ before do
115
+ execute('insert into tracks set id = 14, public = 0')
116
+ execute('insert into tracks set id = 15, public = 1')
117
+ execute('insert into permissions set track_id = 14')
118
+ execute('insert into permissions set track_id = 15')
119
+ end
120
+
121
+ it 'migrates all data' do
122
+ slave do
123
+ count_all(:permissions).must_equal(13)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ it 'should add a column' do
130
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
131
+ t.add_column(:logins, "INT(12) DEFAULT '0'")
132
+ end
133
+
134
+ slave do
135
+ table_read(:users).columns['logins'].must_equal({
136
+ :type => 'int(12)',
137
+ :is_nullable => 'YES',
138
+ :column_default => '0',
139
+ :comment => '',
140
+ :collate => nil,
141
+ })
142
+ end
143
+ end
144
+
145
+ it 'should copy all rows' do
146
+ 23.times { |n| execute("insert into users set reference = '#{ n }'") }
147
+
148
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
149
+ t.add_column(:logins, "INT(12) DEFAULT '0'")
150
+ end
151
+
152
+ slave do
153
+ count_all(:users).must_equal(23)
154
+ end
155
+ end
156
+
157
+ it 'should remove a column' do
158
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
159
+ t.remove_column(:comment)
160
+ end
161
+
162
+ slave do
163
+ assert_nil table_read(:users).columns['comment']
164
+ end
165
+ end
166
+
167
+ it 'should add an index' do
168
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
169
+ t.add_index([:comment, :created_at])
170
+ end
171
+
172
+ slave do
173
+ index_on_columns?(:users, [:comment, :created_at]).must_equal(true)
174
+ end
175
+ end
176
+
177
+ it 'should add an index with a custom name' do
178
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
179
+ t.add_index([:comment, :created_at], :my_index_name)
180
+ end
181
+
182
+ slave do
183
+ index?(:users, :my_index_name).must_equal(true)
184
+ end
185
+ end
186
+
187
+ it 'should add an index on a column with a reserved name' do
188
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
189
+ t.add_index(:group)
190
+ end
191
+
192
+ slave do
193
+ index_on_columns?(:users, :group).must_equal(true)
194
+ end
195
+ end
196
+
197
+ it 'should add a unique index' do
198
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
199
+ t.add_unique_index(:comment)
200
+ end
201
+
202
+ slave do
203
+ index_on_columns?(:users, :comment, :unique).must_equal(true)
204
+ end
205
+ end
206
+
207
+ it 'should remove an index' do
208
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
209
+ t.remove_index([:username, :created_at])
210
+ end
211
+
212
+ slave do
213
+ index_on_columns?(:users, [:username, :created_at]).must_equal(false)
214
+ end
215
+ end
216
+
217
+ it 'should remove an index with a custom name' do
218
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
219
+ t.remove_index([:username, :group])
220
+ end
221
+
222
+ slave do
223
+ index?(:users, :index_with_a_custom_name).must_equal(false)
224
+ end
225
+ end
226
+
227
+ it 'should remove an index with a custom name by name' do
228
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
229
+ t.remove_index(:irrelevant_column_name, :index_with_a_custom_name)
230
+ end
231
+
232
+ slave do
233
+ index?(:users, :index_with_a_custom_name).must_equal(false)
234
+ end
235
+ end
236
+
237
+ it 'should apply a ddl statement' do
238
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
239
+ t.ddl('alter table %s add column flag tinyint(1)' % t.name)
240
+ end
241
+
242
+ slave do
243
+ table_read(:users).columns['flag'].must_equal({
244
+ :type => 'tinyint(1)',
245
+ :is_nullable => 'YES',
246
+ :column_default => nil,
247
+ :comment => '',
248
+ :collate => nil,
249
+ })
250
+ end
251
+ end
252
+
253
+ it 'should change a column' do
254
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
255
+ t.change_column(:comment, "varchar(20) DEFAULT 'none' NOT NULL")
256
+ end
257
+
258
+ slave do
259
+ table_read(:users).columns['comment'].must_equal({
260
+ :type => 'varchar(20)',
261
+ :is_nullable => 'NO',
262
+ :column_default => 'none',
263
+ :comment => '',
264
+ :collate => 'utf8_general_ci',
265
+ })
266
+ end
267
+ end
268
+
269
+ it 'should change the last column in a table' do
270
+ table_create(:small_table)
271
+
272
+ Lhm.change_table(:small_table, :atomic_switch => false) do |t|
273
+ t.change_column(:id, 'int(5)')
274
+ end
275
+
276
+ slave do
277
+ table_read(:small_table).columns['id'].must_equal({
278
+ :type => 'int(5)',
279
+ :is_nullable => 'NO',
280
+ :column_default => nil,
281
+ :comment => '',
282
+ :collate => nil,
283
+ })
284
+ end
285
+ end
286
+
287
+ it 'should rename a column' do
288
+ table_create(:users)
289
+
290
+ execute("INSERT INTO users (username) VALUES ('a user')")
291
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
292
+ t.rename_column(:username, :login)
293
+ end
294
+
295
+ slave do
296
+ table_data = table_read(:users)
297
+ assert_nil table_data.columns['username']
298
+ table_read(:users).columns['login'].must_equal({
299
+ :type => 'varchar(255)',
300
+ :is_nullable => 'YES',
301
+ :column_default => nil,
302
+ :comment => '',
303
+ :collate => 'utf8_general_ci',
304
+ })
305
+
306
+ result = select_one('SELECT login from users')
307
+ result = result['login'] if result.respond_to?(:has_key?)
308
+ result.must_equal('a user')
309
+ end
310
+ end
311
+
312
+ it 'should rename a column with a default' do
313
+ table_create(:users)
314
+
315
+ execute("INSERT INTO users (username) VALUES ('a user')")
316
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
317
+ t.rename_column(:group, :fnord)
318
+ end
319
+
320
+ slave do
321
+ table_data = table_read(:users)
322
+ assert_nil table_data.columns['group']
323
+ table_read(:users).columns['fnord'].must_equal({
324
+ :type => 'varchar(255)',
325
+ :is_nullable => 'YES',
326
+ :column_default => 'Superfriends',
327
+ :comment => '',
328
+ :collate => 'utf8_general_ci',
329
+ })
330
+
331
+ result = select_one('SELECT `fnord` from users')
332
+ result = result['fnord'] if result.respond_to?(:has_key?)
333
+ result.must_equal('Superfriends')
334
+ end
335
+ end
336
+
337
+ it 'should rename a column with a collate' do
338
+ table_create(:users)
339
+
340
+ execute("ALTER TABLE users MODIFY `username` varchar(255) COLLATE utf8mb4_unicode_ci NULL")
341
+ execute("INSERT INTO users (username) VALUES ('a user')")
342
+
343
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
344
+ t.rename_column(:username, :user_name)
345
+ end
346
+
347
+ slave do
348
+ table_data = table_read(:users)
349
+ assert_nil table_data.columns['username']
350
+ table_read(:users).columns['user_name'].must_equal({
351
+ :type => 'varchar(255)',
352
+ :is_nullable => 'YES',
353
+ :column_default => nil,
354
+ :comment => '',
355
+ :collate => 'utf8mb4_unicode_ci',
356
+ })
357
+
358
+ result = select_one('SELECT `user_name` from users')
359
+ result = result['user_name'] if result.respond_to?(:has_key?)
360
+ result.must_equal('a user')
361
+ end
362
+ end
363
+
364
+
365
+ it 'should rename a column with a comment' do
366
+ table_create(:users)
367
+
368
+ execute("ALTER TABLE users MODIFY `reference` int(11) DEFAULT NULL COMMENT 'RefComment'")
369
+ execute("INSERT INTO users (username,reference) VALUES ('a user', 10)")
370
+
371
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
372
+ t.rename_column(:reference, :ref)
373
+ end
374
+
375
+ slave do
376
+ table_data = table_read(:users)
377
+ assert_nil table_data.columns['reference']
378
+ table_read(:users).columns['ref'].must_equal({
379
+ :type => 'int(11)',
380
+ :is_nullable => 'YES',
381
+ :column_default => nil,
382
+ :comment => 'RefComment',
383
+ :collate => nil,
384
+ })
385
+
386
+ result = select_one('SELECT `ref` from users')
387
+ result = result['ref'] if result.respond_to?(:has_key?)
388
+ result.must_equal(10)
389
+ end
390
+ end
391
+
392
+ it 'should rename a column with a default null' do
393
+ table_create(:users)
394
+
395
+ execute("ALTER TABLE users MODIFY `group` varchar(255) NULL DEFAULT NULL")
396
+ execute("INSERT INTO users (username) VALUES ('a user')")
397
+
398
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
399
+ t.rename_column(:group, :fnord)
400
+ end
401
+
402
+ slave do
403
+ table_data = table_read(:users)
404
+ assert_nil table_data.columns['group']
405
+ table_read(:users).columns['fnord'].must_equal({
406
+ :type => 'varchar(255)',
407
+ :is_nullable => 'YES',
408
+ :column_default => nil,
409
+ :comment => '',
410
+ :collate => 'utf8_general_ci',
411
+ })
412
+
413
+ result = select_one('SELECT `fnord` from users')
414
+ result = result['fnord'] if result.respond_to?(:has_key?)
415
+ result.must_equal(nil)
416
+ end
417
+ end
418
+
419
+ it 'should rename a colmn with nullable' do
420
+ table_create(:users)
421
+ execute("INSERT INTO users (username) VALUES ('a user')")
422
+
423
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
424
+ t.rename_column(:username, :user_name)
425
+ end
426
+
427
+ slave do
428
+ table_data = table_read(:users)
429
+ assert_nil table_data.columns['username']
430
+ table_read(:users).columns['user_name'].must_equal({
431
+ :type => 'varchar(255)',
432
+ :is_nullable => 'YES',
433
+ :column_default => nil,
434
+ :comment => '',
435
+ :collate => 'utf8_general_ci',
436
+ })
437
+
438
+ result = select_one('SELECT `user_name` from users')
439
+ result = result['user_name'] if result.respond_to?(:has_key?)
440
+ result.must_equal('a user')
441
+ end
442
+ end
443
+
444
+ it 'should rename a column with a not null' do
445
+ table_create(:users)
446
+
447
+ execute("ALTER TABLE users MODIFY username varchar(255) NOT NULL")
448
+ execute("INSERT INTO users (username) VALUES ('a user')")
449
+
450
+ Lhm.change_table(:users, :atomic_switch => false) do |t|
451
+ t.rename_column(:username, :user_name)
452
+ end
453
+
454
+ slave do
455
+ table_data = table_read(:users)
456
+ assert_nil table_data.columns['username']
457
+ table_read(:users).columns['user_name'].must_equal({
458
+ :type => 'varchar(255)',
459
+ :is_nullable => 'NO',
460
+ :column_default => nil,
461
+ :comment => '',
462
+ :collate => 'utf8_general_ci',
463
+ })
464
+
465
+ result = select_one('SELECT `user_name` from users')
466
+ result = result['user_name'] if result.respond_to?(:has_key?)
467
+ result.must_equal('a user')
468
+ end
469
+ end
470
+
471
+ it 'should raise an exception if the triggers do not exist after copying all rows' do
472
+ table_create(:users)
473
+
474
+ execute("INSERT INTO users (username) VALUES ('a user')")
475
+
476
+ Lhm::Invoker.any_instance.stubs(:triggers_still_exist?).returns(false)
477
+
478
+ exception = assert_raises do
479
+ Lhm.change_table(:users) do |t|
480
+ t.rename_column(:group, :fnord)
481
+ end
482
+ end
483
+
484
+ assert_match "Verification failed, aborting early", exception.message
485
+ end
486
+
487
+ it 'should not perform the table rename if the triggers do not exist after copying all rows' do
488
+ table_create(:users)
489
+
490
+ execute("INSERT INTO users (username) VALUES ('a user')")
491
+
492
+ Lhm::Invoker.any_instance.stubs(:triggers_still_exist?).returns(false)
493
+ Lhm::LockedSwitcher.any_instance.expects(:run).never
494
+
495
+ assert_raises do
496
+ Lhm.change_table(:users) do |t|
497
+ t.rename_column(:group, :fnord)
498
+ end
499
+ end
500
+
501
+ slave do
502
+ table_data = table_read(:users)
503
+ assert_nil table_data.columns['fnord']
504
+ table_read(:users).columns['group'].must_equal({
505
+ :type => 'varchar(255)',
506
+ :is_nullable => 'YES',
507
+ :column_default => 'Superfriends',
508
+ :comment => '',
509
+ :collate => 'utf8_general_ci',
510
+ })
511
+ end
512
+ end
513
+
514
+ it 'works when mysql reserved words are used' do
515
+ table_create(:lines)
516
+ execute("insert into `lines` set id = 1, `between` = 'foo'")
517
+ execute("insert into `lines` set id = 2, `between` = 'bar'")
518
+
519
+ Lhm.change_table(:lines) do |t|
520
+ t.add_column('by', 'varchar(10)')
521
+ t.remove_column('lines')
522
+ t.add_index('by')
523
+ t.add_unique_index('between')
524
+ t.remove_index('by')
525
+ end
526
+
527
+ slave do
528
+ table_read(:lines).columns.must_include 'by'
529
+ table_read(:lines).columns.wont_include 'lines'
530
+ index_on_columns?(:lines, ['between'], :unique).must_equal true
531
+ index_on_columns?(:lines, ['by']).must_equal false
532
+ count_all(:lines).must_equal(2)
533
+ end
534
+ end
535
+
536
+ describe 'parallel' do
537
+ it 'should perserve inserts during migration' do
538
+ 50.times { |n| execute("insert into users set reference = '#{ n }'") }
539
+
540
+ insert = Thread.new do
541
+ 10.times do |n|
542
+ connect_master!
543
+ execute("insert into users set reference = '#{ 100 + n }'")
544
+ sleep(0.17)
545
+ end
546
+ end
547
+ sleep 2
548
+
549
+ options = { :stride => 10, :throttle => 97, :atomic_switch => false }
550
+ Lhm.change_table(:users, options) do |t|
551
+ t.add_column(:parallel, "INT(10) DEFAULT '0'")
552
+ end
553
+
554
+ insert.join
555
+
556
+ slave do
557
+ count_all(:users).must_equal(60)
558
+ end
559
+ end
560
+
561
+ it 'should perserve deletes during migration' do
562
+ 50.times { |n| execute("insert into users set reference = '#{ n }'") }
563
+
564
+ delete = Thread.new do
565
+ 10.times do |n|
566
+ execute("delete from users where reference = '#{ n }'")
567
+ sleep(0.17)
568
+ end
569
+ end
570
+ sleep 2
571
+
572
+ options = { :stride => 10, :throttle => 97, :atomic_switch => false }
573
+ Lhm.change_table(:users, options) do |t|
574
+ t.add_column(:parallel, "INT(10) DEFAULT '0'")
575
+ end
576
+
577
+ delete.join
578
+
579
+ slave do
580
+ count_all(:users).must_equal(40)
581
+ end
582
+ end
583
+ end
584
+ end
585
+ end