lhm-teak 3.6.0
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.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +43 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +183 -0
- data/.travis.yml +21 -0
- data/Appraisals +24 -0
- data/CHANGELOG.md +254 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +67 -0
- data/LICENSE +27 -0
- data/README.md +335 -0
- data/Rakefile +33 -0
- data/dev.yml +45 -0
- data/docker-compose.yml +60 -0
- data/gemfiles/activerecord_5.2.gemfile +9 -0
- data/gemfiles/activerecord_5.2.gemfile.lock +66 -0
- data/gemfiles/activerecord_6.0.gemfile +7 -0
- data/gemfiles/activerecord_6.0.gemfile.lock +68 -0
- data/gemfiles/activerecord_6.1.gemfile +7 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +67 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile +7 -0
- data/gemfiles/activerecord_7.0.0.alpha2.gemfile.lock +65 -0
- data/lhm.gemspec +38 -0
- data/lib/lhm/atomic_switcher.rb +46 -0
- data/lib/lhm/chunk_finder.rb +62 -0
- data/lib/lhm/chunk_insert.rb +61 -0
- data/lib/lhm/chunker.rb +95 -0
- data/lib/lhm/cleanup/current.rb +71 -0
- data/lib/lhm/command.rb +48 -0
- data/lib/lhm/connection.rb +108 -0
- data/lib/lhm/entangler.rb +112 -0
- data/lib/lhm/intersection.rb +51 -0
- data/lib/lhm/invoker.rb +100 -0
- data/lib/lhm/locked_switcher.rb +76 -0
- data/lib/lhm/migration.rb +51 -0
- data/lib/lhm/migrator.rb +244 -0
- data/lib/lhm/printer.rb +63 -0
- data/lib/lhm/proxysql_helper.rb +10 -0
- data/lib/lhm/railtie.rb +9 -0
- data/lib/lhm/sql_helper.rb +77 -0
- data/lib/lhm/sql_retry.rb +180 -0
- data/lib/lhm/table.rb +121 -0
- data/lib/lhm/table_name.rb +23 -0
- data/lib/lhm/test_support.rb +35 -0
- data/lib/lhm/throttler/slave_lag.rb +162 -0
- data/lib/lhm/throttler/threads_running.rb +53 -0
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/throttler.rb +36 -0
- data/lib/lhm/timestamp.rb +11 -0
- data/lib/lhm/version.rb +6 -0
- data/lib/lhm-shopify.rb +1 -0
- data/lib/lhm.rb +156 -0
- data/scripts/helpers/wait-for-dbs.sh +21 -0
- data/scripts/mysql/reader/create_replication.sql +10 -0
- data/scripts/mysql/writer/create_test_db.sql +1 -0
- data/scripts/mysql/writer/create_users.sql +6 -0
- data/scripts/proxysql/proxysql.cnf +117 -0
- data/shipit.rubygems.yml +0 -0
- data/spec/.lhm.example +4 -0
- data/spec/README.md +58 -0
- data/spec/fixtures/bigint_table.ddl +4 -0
- data/spec/fixtures/composite_primary_key.ddl +6 -0
- data/spec/fixtures/composite_primary_key_dest.ddl +6 -0
- data/spec/fixtures/custom_primary_key.ddl +6 -0
- data/spec/fixtures/custom_primary_key_dest.ddl +6 -0
- data/spec/fixtures/destination.ddl +6 -0
- data/spec/fixtures/lines.ddl +7 -0
- data/spec/fixtures/origin.ddl +6 -0
- data/spec/fixtures/permissions.ddl +5 -0
- data/spec/fixtures/small_table.ddl +4 -0
- data/spec/fixtures/tracks.ddl +5 -0
- data/spec/fixtures/users.ddl +14 -0
- data/spec/fixtures/wo_id_int_column.ddl +6 -0
- data/spec/integration/atomic_switcher_spec.rb +129 -0
- data/spec/integration/chunk_insert_spec.rb +30 -0
- data/spec/integration/chunker_spec.rb +269 -0
- data/spec/integration/cleanup_spec.rb +147 -0
- data/spec/integration/database.yml +25 -0
- data/spec/integration/entangler_spec.rb +68 -0
- data/spec/integration/integration_helper.rb +252 -0
- data/spec/integration/invoker_spec.rb +33 -0
- data/spec/integration/lhm_spec.rb +659 -0
- data/spec/integration/lock_wait_timeout_spec.rb +30 -0
- data/spec/integration/locked_switcher_spec.rb +50 -0
- data/spec/integration/proxysql_spec.rb +34 -0
- data/spec/integration/sql_retry/db_connection_helper.rb +52 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +127 -0
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +114 -0
- data/spec/integration/sql_retry/proxysql_helper.rb +22 -0
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +109 -0
- data/spec/integration/table_spec.rb +83 -0
- data/spec/integration/toxiproxy_helper.rb +40 -0
- data/spec/test_helper.rb +69 -0
- data/spec/unit/atomic_switcher_spec.rb +29 -0
- data/spec/unit/chunk_finder_spec.rb +73 -0
- data/spec/unit/chunk_insert_spec.rb +67 -0
- data/spec/unit/chunker_spec.rb +176 -0
- data/spec/unit/connection_spec.rb +111 -0
- data/spec/unit/entangler_spec.rb +187 -0
- data/spec/unit/intersection_spec.rb +51 -0
- data/spec/unit/lhm_spec.rb +46 -0
- data/spec/unit/locked_switcher_spec.rb +46 -0
- data/spec/unit/migrator_spec.rb +144 -0
- data/spec/unit/printer_spec.rb +85 -0
- data/spec/unit/sql_helper_spec.rb +28 -0
- data/spec/unit/table_name_spec.rb +39 -0
- data/spec/unit/table_spec.rb +47 -0
- data/spec/unit/throttler/slave_lag_spec.rb +322 -0
- data/spec/unit/throttler/threads_running_spec.rb +64 -0
- data/spec/unit/throttler_spec.rb +124 -0
- data/spec/unit/unit_helper.rb +26 -0
- metadata +366 -0
|
@@ -0,0 +1,659 @@
|
|
|
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
|
+
require 'integration/toxiproxy_helper'
|
|
6
|
+
|
|
7
|
+
describe Lhm do
|
|
8
|
+
include IntegrationHelper
|
|
9
|
+
|
|
10
|
+
before(:each) { connect_master!; Lhm.cleanup(true) }
|
|
11
|
+
|
|
12
|
+
describe 'id column requirement' do
|
|
13
|
+
it 'should migrate the table when id is pk' do
|
|
14
|
+
table_create(:users)
|
|
15
|
+
|
|
16
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
17
|
+
t.add_column(:logins, "int(12) default '0'")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
slave do
|
|
21
|
+
value(table_read(:users).columns['logins']).must_equal({
|
|
22
|
+
:type => 'int(12)',
|
|
23
|
+
:is_nullable => 'YES',
|
|
24
|
+
:column_default => '0',
|
|
25
|
+
:comment => '',
|
|
26
|
+
:collate => nil,
|
|
27
|
+
})
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'should migrate the table when id is not pk' do
|
|
32
|
+
table_create(:custom_primary_key)
|
|
33
|
+
|
|
34
|
+
Lhm.change_table(:custom_primary_key, :atomic_switch => false) do |t|
|
|
35
|
+
t.add_column(:logins, "int(12) default '0'")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
slave do
|
|
39
|
+
value(table_read(:custom_primary_key).columns['logins']).must_equal({
|
|
40
|
+
:type => 'int(12)',
|
|
41
|
+
:is_nullable => 'YES',
|
|
42
|
+
:column_default => '0',
|
|
43
|
+
:comment => '',
|
|
44
|
+
:collate => nil,
|
|
45
|
+
})
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'should migrate the table when using a composite primary key if id column exists' do
|
|
50
|
+
table_create(:composite_primary_key)
|
|
51
|
+
|
|
52
|
+
Lhm.change_table(:composite_primary_key, :atomic_switch => false) do |t|
|
|
53
|
+
t.add_column(:logins, "int(12) default '0'")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
slave do
|
|
57
|
+
value(table_read(:composite_primary_key).columns['logins']).must_equal({
|
|
58
|
+
:type => 'int(12)',
|
|
59
|
+
:is_nullable => 'YES',
|
|
60
|
+
:column_default => '0',
|
|
61
|
+
:comment => '',
|
|
62
|
+
:collate => nil,
|
|
63
|
+
})
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
describe 'changes' do
|
|
69
|
+
before(:each) do
|
|
70
|
+
table_create(:users)
|
|
71
|
+
table_create(:tracks)
|
|
72
|
+
table_create(:permissions)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe 'when changing to a composite primary key' do
|
|
76
|
+
it 'should be able to use ddl statement to create composite keys' do
|
|
77
|
+
|
|
78
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
79
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
|
80
|
+
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
|
81
|
+
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
|
82
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
slave do
|
|
86
|
+
value(connection.primary_key('users')).must_equal(['username', 'id'])
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe 'when providing a subset of data to copy' do
|
|
93
|
+
|
|
94
|
+
before do
|
|
95
|
+
execute('insert into tracks set id = 13, public = 0')
|
|
96
|
+
11.times { |n| execute("insert into tracks set id = #{n + 1}, public = 1") }
|
|
97
|
+
11.times { |n| execute("insert into permissions set track_id = #{n + 1}") }
|
|
98
|
+
|
|
99
|
+
Lhm.change_table(:permissions, :atomic_switch => false) do |t|
|
|
100
|
+
t.filter('inner join tracks on tracks.`id` = permissions.`track_id` and tracks.`public` = 1')
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe 'when no additional data is inserted into the table' do
|
|
105
|
+
|
|
106
|
+
it 'migrates the existing data' do
|
|
107
|
+
slave do
|
|
108
|
+
value(count_all(:permissions)).must_equal(11)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
describe 'when additional data is inserted' do
|
|
114
|
+
|
|
115
|
+
before do
|
|
116
|
+
execute('insert into tracks set id = 14, public = 0')
|
|
117
|
+
execute('insert into tracks set id = 15, public = 1')
|
|
118
|
+
execute('insert into permissions set track_id = 14')
|
|
119
|
+
execute('insert into permissions set track_id = 15')
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'migrates all data' do
|
|
123
|
+
slave do
|
|
124
|
+
value(count_all(:permissions)).must_equal(13)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'should add a column' do
|
|
131
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
132
|
+
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
slave do
|
|
136
|
+
value(table_read(:users).columns['logins']).must_equal({
|
|
137
|
+
:type => 'int(12)',
|
|
138
|
+
:is_nullable => 'YES',
|
|
139
|
+
:column_default => '0',
|
|
140
|
+
:comment => '',
|
|
141
|
+
:collate => nil,
|
|
142
|
+
})
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'should copy all rows' do
|
|
147
|
+
23.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
148
|
+
|
|
149
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
150
|
+
t.add_column(:logins, "INT(12) DEFAULT '0'")
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
slave do
|
|
154
|
+
value(count_all(:users)).must_equal(23)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
it 'should remove a column' do
|
|
159
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
160
|
+
t.remove_column(:comment)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
slave do
|
|
164
|
+
assert_nil table_read(:users).columns['comment']
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'should add an index' do
|
|
169
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
170
|
+
t.add_index([:comment, :created_at])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
slave do
|
|
174
|
+
value(index_on_columns?(:users, [:comment, :created_at])).must_equal(true)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'should add an index with a custom name' do
|
|
179
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
180
|
+
t.add_index([:comment, :created_at], :my_index_name)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
slave do
|
|
184
|
+
value(index?(:users, :my_index_name)).must_equal(true)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'should add an index on a column with a reserved name' do
|
|
189
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
190
|
+
t.add_index(:group)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
slave do
|
|
194
|
+
value(index_on_columns?(:users, :group)).must_equal(true)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'should add a unique index' do
|
|
199
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
200
|
+
t.add_unique_index(:comment)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
slave do
|
|
204
|
+
value(index_on_columns?(:users, :comment, :unique)).must_equal(true)
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
it 'should remove an index' do
|
|
209
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
210
|
+
t.remove_index([:username, :created_at])
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
slave do
|
|
214
|
+
value(index_on_columns?(:users, [:username, :created_at])).must_equal(false)
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
it 'should remove an index with a custom name' do
|
|
219
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
220
|
+
t.remove_index([:username, :group])
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
slave do
|
|
224
|
+
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it 'should remove an index with a custom name by name' do
|
|
229
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
230
|
+
t.remove_index(:irrelevant_column_name, :index_with_a_custom_name)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
slave do
|
|
234
|
+
value(index?(:users, :index_with_a_custom_name)).must_equal(false)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
it 'should apply a ddl statement' do
|
|
239
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
240
|
+
t.ddl('alter table %s add column flag tinyint(1)' % t.name)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
slave do
|
|
244
|
+
value(table_read(:users).columns['flag']).must_equal({
|
|
245
|
+
:type => 'tinyint(1)',
|
|
246
|
+
:is_nullable => 'YES',
|
|
247
|
+
:column_default => nil,
|
|
248
|
+
:comment => '',
|
|
249
|
+
:collate => nil,
|
|
250
|
+
})
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
it 'should change a column' do
|
|
255
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
256
|
+
t.change_column(:comment, "varchar(20) DEFAULT 'none' NOT NULL")
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
slave do
|
|
260
|
+
value(table_read(:users).columns['comment']).must_equal({
|
|
261
|
+
:type => 'varchar(20)',
|
|
262
|
+
:is_nullable => 'NO',
|
|
263
|
+
:column_default => 'none',
|
|
264
|
+
:comment => '',
|
|
265
|
+
:collate => 'utf8_general_ci',
|
|
266
|
+
})
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it 'should change the last column in a table' do
|
|
271
|
+
table_create(:small_table)
|
|
272
|
+
|
|
273
|
+
Lhm.change_table(:small_table, :atomic_switch => false) do |t|
|
|
274
|
+
t.change_column(:id, 'int(5)')
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
slave do
|
|
278
|
+
value(table_read(:small_table).columns['id']).must_equal({
|
|
279
|
+
:type => 'int(5)',
|
|
280
|
+
:is_nullable => 'NO',
|
|
281
|
+
:column_default => nil,
|
|
282
|
+
:comment => '',
|
|
283
|
+
:collate => nil,
|
|
284
|
+
})
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it 'should rename a column' do
|
|
289
|
+
table_create(:users)
|
|
290
|
+
|
|
291
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
292
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
293
|
+
t.rename_column(:username, :login)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
slave do
|
|
297
|
+
table_data = table_read(:users)
|
|
298
|
+
assert_nil table_data.columns['username']
|
|
299
|
+
value(table_read(:users).columns['login']).must_equal({
|
|
300
|
+
:type => 'varchar(255)',
|
|
301
|
+
:is_nullable => 'YES',
|
|
302
|
+
:column_default => nil,
|
|
303
|
+
:comment => '',
|
|
304
|
+
:collate => 'utf8_general_ci',
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
result = select_one('SELECT login from users')
|
|
308
|
+
result = result['login'] if result.respond_to?(:has_key?)
|
|
309
|
+
value(result).must_equal('a user')
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
it 'should rename a column with a default' do
|
|
314
|
+
table_create(:users)
|
|
315
|
+
|
|
316
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
317
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
318
|
+
t.rename_column(:group, :fnord)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
slave do
|
|
322
|
+
table_data = table_read(:users)
|
|
323
|
+
assert_nil table_data.columns['group']
|
|
324
|
+
value(table_read(:users).columns['fnord']).must_equal({
|
|
325
|
+
:type => 'varchar(255)',
|
|
326
|
+
:is_nullable => 'YES',
|
|
327
|
+
:column_default => 'Superfriends',
|
|
328
|
+
:comment => '',
|
|
329
|
+
:collate => 'utf8_general_ci',
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
result = select_one('SELECT `fnord` from users')
|
|
333
|
+
result = result['fnord'] if result.respond_to?(:has_key?)
|
|
334
|
+
value(result).must_equal('Superfriends')
|
|
335
|
+
end
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'should rename a column with a collate' do
|
|
339
|
+
table_create(:users)
|
|
340
|
+
|
|
341
|
+
execute("ALTER TABLE users MODIFY `username` varchar(255) COLLATE utf8mb4_unicode_ci NULL")
|
|
342
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
343
|
+
|
|
344
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
345
|
+
t.rename_column(:username, :user_name)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
slave do
|
|
349
|
+
table_data = table_read(:users)
|
|
350
|
+
assert_nil table_data.columns['username']
|
|
351
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
|
352
|
+
:type => 'varchar(255)',
|
|
353
|
+
:is_nullable => 'YES',
|
|
354
|
+
:column_default => nil,
|
|
355
|
+
:comment => '',
|
|
356
|
+
:collate => 'utf8mb4_unicode_ci',
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
result = select_one('SELECT `user_name` from users')
|
|
360
|
+
result = result['user_name'] if result.respond_to?(:has_key?)
|
|
361
|
+
value(result).must_equal('a user')
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
it 'should rename a column with a comment' do
|
|
367
|
+
table_create(:users)
|
|
368
|
+
|
|
369
|
+
execute("ALTER TABLE users MODIFY `reference` int(11) DEFAULT NULL COMMENT 'RefComment'")
|
|
370
|
+
execute("INSERT INTO users (username,reference) VALUES ('a user', 10)")
|
|
371
|
+
|
|
372
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
373
|
+
t.rename_column(:reference, :ref)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
slave do
|
|
377
|
+
table_data = table_read(:users)
|
|
378
|
+
assert_nil table_data.columns['reference']
|
|
379
|
+
value(table_read(:users).columns['ref']).must_equal({
|
|
380
|
+
:type => 'int(11)',
|
|
381
|
+
:is_nullable => 'YES',
|
|
382
|
+
:column_default => nil,
|
|
383
|
+
:comment => 'RefComment',
|
|
384
|
+
:collate => nil,
|
|
385
|
+
})
|
|
386
|
+
|
|
387
|
+
result = select_one('SELECT `ref` from users')
|
|
388
|
+
result = result['ref'] if result.respond_to?(:has_key?)
|
|
389
|
+
value(result).must_equal(10)
|
|
390
|
+
end
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
it 'should rename a column with a default null' do
|
|
394
|
+
table_create(:users)
|
|
395
|
+
|
|
396
|
+
execute("ALTER TABLE users MODIFY `group` varchar(255) NULL DEFAULT NULL")
|
|
397
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
398
|
+
|
|
399
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
400
|
+
t.rename_column(:group, :fnord)
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
slave do
|
|
404
|
+
table_data = table_read(:users)
|
|
405
|
+
assert_nil table_data.columns['group']
|
|
406
|
+
value(table_read(:users).columns['fnord']).must_equal({
|
|
407
|
+
:type => 'varchar(255)',
|
|
408
|
+
:is_nullable => 'YES',
|
|
409
|
+
:column_default => nil,
|
|
410
|
+
:comment => '',
|
|
411
|
+
:collate => 'utf8_general_ci',
|
|
412
|
+
})
|
|
413
|
+
|
|
414
|
+
result = select_one('SELECT `fnord` from users')
|
|
415
|
+
result = result['fnord'] if result.respond_to?(:has_key?)
|
|
416
|
+
assert_nil(result)
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
|
|
420
|
+
it 'should rename a colmn with nullable' do
|
|
421
|
+
table_create(:users)
|
|
422
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
423
|
+
|
|
424
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
425
|
+
t.rename_column(:username, :user_name)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
slave do
|
|
429
|
+
table_data = table_read(:users)
|
|
430
|
+
assert_nil table_data.columns['username']
|
|
431
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
|
432
|
+
:type => 'varchar(255)',
|
|
433
|
+
:is_nullable => 'YES',
|
|
434
|
+
:column_default => nil,
|
|
435
|
+
:comment => '',
|
|
436
|
+
:collate => 'utf8_general_ci',
|
|
437
|
+
})
|
|
438
|
+
|
|
439
|
+
result = select_one('SELECT `user_name` from users')
|
|
440
|
+
result = result['user_name'] if result.respond_to?(:has_key?)
|
|
441
|
+
value(result).must_equal('a user')
|
|
442
|
+
end
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
it 'should rename a column with a not null' do
|
|
446
|
+
table_create(:users)
|
|
447
|
+
|
|
448
|
+
execute("ALTER TABLE users MODIFY username varchar(255) NOT NULL")
|
|
449
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
450
|
+
|
|
451
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
452
|
+
t.rename_column(:username, :user_name)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
slave do
|
|
456
|
+
table_data = table_read(:users)
|
|
457
|
+
assert_nil table_data.columns['username']
|
|
458
|
+
value(table_read(:users).columns['user_name']).must_equal({
|
|
459
|
+
:type => 'varchar(255)',
|
|
460
|
+
:is_nullable => 'NO',
|
|
461
|
+
:column_default => nil,
|
|
462
|
+
:comment => '',
|
|
463
|
+
:collate => 'utf8_general_ci',
|
|
464
|
+
})
|
|
465
|
+
|
|
466
|
+
result = select_one('SELECT `user_name` from users')
|
|
467
|
+
result = result['user_name'] if result.respond_to?(:has_key?)
|
|
468
|
+
value(result).must_equal('a user')
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
it 'should raise an exception if the triggers do not exist after copying all rows' do
|
|
473
|
+
table_create(:users)
|
|
474
|
+
|
|
475
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
476
|
+
|
|
477
|
+
Lhm::Invoker.any_instance.stubs(:triggers_still_exist?).returns(false)
|
|
478
|
+
|
|
479
|
+
exception = assert_raises do
|
|
480
|
+
Lhm.change_table(:users) do |t|
|
|
481
|
+
t.rename_column(:group, :fnord)
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
assert_match "Verification failed, aborting early", exception.message
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
it 'should not perform the table rename if the triggers do not exist after copying all rows' do
|
|
489
|
+
table_create(:users)
|
|
490
|
+
|
|
491
|
+
execute("INSERT INTO users (username) VALUES ('a user')")
|
|
492
|
+
|
|
493
|
+
Lhm::Invoker.any_instance.stubs(:triggers_still_exist?).returns(false)
|
|
494
|
+
Lhm::LockedSwitcher.any_instance.expects(:run).never
|
|
495
|
+
|
|
496
|
+
assert_raises do
|
|
497
|
+
Lhm.change_table(:users) do |t|
|
|
498
|
+
t.rename_column(:group, :fnord)
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
slave do
|
|
503
|
+
table_data = table_read(:users)
|
|
504
|
+
assert_nil table_data.columns['fnord']
|
|
505
|
+
value(table_read(:users).columns['group']).must_equal({
|
|
506
|
+
:type => 'varchar(255)',
|
|
507
|
+
:is_nullable => 'YES',
|
|
508
|
+
:column_default => 'Superfriends',
|
|
509
|
+
:comment => '',
|
|
510
|
+
:collate => 'utf8_general_ci',
|
|
511
|
+
})
|
|
512
|
+
end
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it 'works when mysql reserved words are used' do
|
|
516
|
+
table_create(:lines)
|
|
517
|
+
execute("insert into `lines` set id = 1, `between` = 'foo'")
|
|
518
|
+
execute("insert into `lines` set id = 2, `between` = 'bar'")
|
|
519
|
+
|
|
520
|
+
Lhm.change_table(:lines) do |t|
|
|
521
|
+
t.add_column('by', 'varchar(10)')
|
|
522
|
+
t.remove_column('lines')
|
|
523
|
+
t.add_index('by')
|
|
524
|
+
t.add_unique_index('between')
|
|
525
|
+
t.remove_index('by')
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
slave do
|
|
529
|
+
value(table_read(:lines).columns).must_include 'by'
|
|
530
|
+
value(table_read(:lines).columns).wont_include 'lines'
|
|
531
|
+
value(index_on_columns?(:lines, ['between'], :unique)).must_equal true
|
|
532
|
+
value(index_on_columns?(:lines, ['by'])).must_equal false
|
|
533
|
+
value(count_all(:lines)).must_equal(2)
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
describe 'parallel' do
|
|
538
|
+
it 'should perserve inserts during migration' do
|
|
539
|
+
50.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
540
|
+
|
|
541
|
+
insert = Thread.new do
|
|
542
|
+
10.times do |n|
|
|
543
|
+
connect_master!
|
|
544
|
+
execute("insert into users set reference = '#{ 100 + n }'")
|
|
545
|
+
sleep(0.17)
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
sleep 2
|
|
549
|
+
|
|
550
|
+
options = { :stride => 10, :throttle => 97, :atomic_switch => false }
|
|
551
|
+
Lhm.change_table(:users, options) do |t|
|
|
552
|
+
t.add_column(:parallel, "INT(10) DEFAULT '0'")
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
insert.join
|
|
556
|
+
|
|
557
|
+
slave do
|
|
558
|
+
value(count_all(:users)).must_equal(60)
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
it 'should perserve deletes during migration' do
|
|
563
|
+
50.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
564
|
+
|
|
565
|
+
delete = Thread.new do
|
|
566
|
+
10.times do |n|
|
|
567
|
+
execute("delete from users where reference = '#{ n }'")
|
|
568
|
+
sleep(0.17)
|
|
569
|
+
end
|
|
570
|
+
end
|
|
571
|
+
sleep 2
|
|
572
|
+
|
|
573
|
+
options = { :stride => 10, :throttle => 97, :atomic_switch => false }
|
|
574
|
+
Lhm.change_table(:users, options) do |t|
|
|
575
|
+
t.add_column(:parallel, "INT(10) DEFAULT '0'")
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
delete.join
|
|
579
|
+
|
|
580
|
+
slave do
|
|
581
|
+
value(count_all(:users)).must_equal(40)
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
describe 'connection' do
|
|
587
|
+
include ToxiproxyHelper
|
|
588
|
+
|
|
589
|
+
before(:each) do
|
|
590
|
+
@logs = StringIO.new
|
|
591
|
+
Lhm.logger = Logger.new(@logs)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
it " should not try to reconnect if reconnect_with_consistent_host is not provided" do
|
|
595
|
+
connect_master_with_toxiproxy!
|
|
596
|
+
|
|
597
|
+
table_create(:users)
|
|
598
|
+
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
599
|
+
|
|
600
|
+
assert_raises ActiveRecord::StatementInvalid do
|
|
601
|
+
Toxiproxy[:mysql_master].down do
|
|
602
|
+
Lhm.change_table(:users, :atomic_switch => false) do |t|
|
|
603
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
|
604
|
+
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
|
605
|
+
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
|
606
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
|
607
|
+
end
|
|
608
|
+
end
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
it "should reconnect if reconnect_with_consistent_host is true" do
|
|
613
|
+
connect_master_with_toxiproxy!
|
|
614
|
+
mysql_disabled = false
|
|
615
|
+
|
|
616
|
+
table_create(:users)
|
|
617
|
+
100.times { |n| execute("insert into users set reference = '#{ n }'") }
|
|
618
|
+
|
|
619
|
+
# Redeclare Lhm::ChunkInsert to use Hook to disable MySQL writer for 3 seconds before first insert
|
|
620
|
+
Lhm::ChunkInsert.class_eval do
|
|
621
|
+
extend AfterDo
|
|
622
|
+
|
|
623
|
+
before(:insert_and_return_count_of_rows_created) do
|
|
624
|
+
unless mysql_disabled
|
|
625
|
+
mysql_disabled = true
|
|
626
|
+
Thread.new do
|
|
627
|
+
Toxiproxy[:mysql_master].down do
|
|
628
|
+
sleep 3
|
|
629
|
+
end
|
|
630
|
+
end
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
# Need to call `#method_added` manually to have the hooks take into effect
|
|
635
|
+
method_added(:insert_and_return_count_of_rows_created)
|
|
636
|
+
end
|
|
637
|
+
|
|
638
|
+
Lhm.change_table(:users, atomic_switch: false, reconnect_with_consistent_host: true) do |t|
|
|
639
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL")
|
|
640
|
+
t.ddl("ALTER TABLE #{t.name} DROP PRIMARY KEY, ADD PRIMARY KEY (username, id)")
|
|
641
|
+
t.ddl("ALTER TABLE #{t.name} ADD INDEX (id)")
|
|
642
|
+
t.ddl("ALTER TABLE #{t.name} CHANGE id id bigint (20) NOT NULL AUTO_INCREMENT")
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
log_lines = @logs.string.split("\n")
|
|
646
|
+
|
|
647
|
+
assert log_lines.one?{ |line| line.include?("Lost connection to MySQL, will retry to connect to same host")}
|
|
648
|
+
assert log_lines.one?{ |line| line.include?("LHM successfully reconnected to initial host")}
|
|
649
|
+
assert log_lines.one?{ |line| line.include?("100% complete")}
|
|
650
|
+
|
|
651
|
+
Lhm::ChunkInsert.remove_all_callbacks
|
|
652
|
+
|
|
653
|
+
slave do
|
|
654
|
+
value(count_all(:users)).must_equal(100)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
end
|
|
659
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
|
2
|
+
|
|
3
|
+
describe Lhm do
|
|
4
|
+
include IntegrationHelper
|
|
5
|
+
|
|
6
|
+
before(:each) do
|
|
7
|
+
connect_master!
|
|
8
|
+
table_create(:users)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'set_session_lock_wait_timeouts should set the sessions lock wait timeouts to less than the global values by a delta' do
|
|
12
|
+
connection = Lhm.send(:connection)
|
|
13
|
+
connection.execute('SET GLOBAL innodb_lock_wait_timeout=11')
|
|
14
|
+
connection.execute('SET GLOBAL lock_wait_timeout=11')
|
|
15
|
+
connection.execute('SET SESSION innodb_lock_wait_timeout=1')
|
|
16
|
+
connection.execute('SET SESSION lock_wait_timeout=1')
|
|
17
|
+
|
|
18
|
+
global_innodb_lock_wait_timeout = connection.select_one("SHOW GLOBAL VARIABLES LIKE 'innodb_lock_wait_timeout'")['Value'].to_i
|
|
19
|
+
global_lock_wait_timeout = connection.select_one("SHOW GLOBAL VARIABLES LIKE 'lock_wait_timeout'")['Value'].to_i
|
|
20
|
+
|
|
21
|
+
invoker = Lhm::Invoker.new(Lhm::Table.parse(:users, connection), connection)
|
|
22
|
+
invoker.set_session_lock_wait_timeouts
|
|
23
|
+
|
|
24
|
+
session_innodb_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'innodb_lock_wait_timeout'")['Value'].to_i
|
|
25
|
+
session_lock_wait_timeout = connection.select_one("SHOW SESSION VARIABLES LIKE 'lock_wait_timeout'")['Value'].to_i
|
|
26
|
+
|
|
27
|
+
value(session_lock_wait_timeout).must_equal global_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
|
28
|
+
value(session_innodb_lock_wait_timeout).must_equal global_innodb_lock_wait_timeout + Lhm::Invoker::LOCK_WAIT_TIMEOUT_DELTA
|
|
29
|
+
end
|
|
30
|
+
end
|