lhm-shopify 3.3.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.
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