lhm-shopify 3.3.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +34 -0
- data/.gitignore +17 -0
- data/.rubocop.yml +183 -0
- data/.travis.yml +21 -0
- data/CHANGELOG.md +216 -0
- data/Gemfile +5 -0
- data/LICENSE +27 -0
- data/README.md +284 -0
- data/Rakefile +22 -0
- data/bin/.gitkeep +0 -0
- data/dbdeployer/config.json +32 -0
- data/dbdeployer/install.sh +64 -0
- data/dev.yml +20 -0
- data/gemfiles/ar-2.3_mysql.gemfile +6 -0
- data/gemfiles/ar-3.2_mysql.gemfile +5 -0
- data/gemfiles/ar-3.2_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.0_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.1_mysql2.gemfile +5 -0
- data/gemfiles/ar-4.2_mysql2.gemfile +5 -0
- data/gemfiles/ar-5.0_mysql2.gemfile +5 -0
- data/lhm.gemspec +34 -0
- data/lib/lhm.rb +131 -0
- data/lib/lhm/atomic_switcher.rb +52 -0
- data/lib/lhm/chunk_finder.rb +32 -0
- data/lib/lhm/chunk_insert.rb +51 -0
- data/lib/lhm/chunker.rb +87 -0
- data/lib/lhm/cleanup/current.rb +74 -0
- data/lib/lhm/command.rb +48 -0
- data/lib/lhm/entangler.rb +117 -0
- data/lib/lhm/intersection.rb +51 -0
- data/lib/lhm/invoker.rb +98 -0
- data/lib/lhm/locked_switcher.rb +74 -0
- data/lib/lhm/migration.rb +43 -0
- data/lib/lhm/migrator.rb +237 -0
- data/lib/lhm/printer.rb +59 -0
- data/lib/lhm/railtie.rb +9 -0
- data/lib/lhm/sql_helper.rb +77 -0
- data/lib/lhm/sql_retry.rb +61 -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.rb +36 -0
- data/lib/lhm/throttler/slave_lag.rb +145 -0
- data/lib/lhm/throttler/threads_running.rb +53 -0
- data/lib/lhm/throttler/time.rb +29 -0
- data/lib/lhm/timestamp.rb +11 -0
- data/lib/lhm/version.rb +6 -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 +7 -0
- data/spec/fixtures/custom_primary_key.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 +93 -0
- data/spec/integration/chunk_insert_spec.rb +29 -0
- data/spec/integration/chunker_spec.rb +185 -0
- data/spec/integration/cleanup_spec.rb +136 -0
- data/spec/integration/entangler_spec.rb +66 -0
- data/spec/integration/integration_helper.rb +237 -0
- data/spec/integration/invoker_spec.rb +33 -0
- data/spec/integration/lhm_spec.rb +585 -0
- data/spec/integration/lock_wait_timeout_spec.rb +30 -0
- data/spec/integration/locked_switcher_spec.rb +50 -0
- data/spec/integration/sql_retry/lock_wait_spec.rb +125 -0
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +101 -0
- data/spec/integration/table_spec.rb +91 -0
- data/spec/test_helper.rb +32 -0
- data/spec/unit/atomic_switcher_spec.rb +31 -0
- data/spec/unit/chunk_finder_spec.rb +73 -0
- data/spec/unit/chunk_insert_spec.rb +44 -0
- data/spec/unit/chunker_spec.rb +166 -0
- data/spec/unit/entangler_spec.rb +124 -0
- data/spec/unit/intersection_spec.rb +51 -0
- data/spec/unit/lhm_spec.rb +29 -0
- data/spec/unit/locked_switcher_spec.rb +51 -0
- data/spec/unit/migrator_spec.rb +146 -0
- data/spec/unit/printer_spec.rb +97 -0
- data/spec/unit/sql_helper_spec.rb +32 -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 +317 -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 +13 -0
- metadata +239 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
|
+
|
6
|
+
describe Lhm::ChunkFinder do
|
7
|
+
before(:each) do
|
8
|
+
@origin = Lhm::Table.new('foo')
|
9
|
+
@destination = Lhm::Table.new('bar')
|
10
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
11
|
+
@connection = mock()
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#validate' do
|
15
|
+
describe 'when start is greater than limit' do
|
16
|
+
it 'raises' do
|
17
|
+
assert_raises { Lhm::ChunkFinder.new(@connection, @migration, {start: 2, limit: 1}).validate }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'when start is greater than limit' do
|
22
|
+
it 'does not raise' do
|
23
|
+
Lhm::ChunkFinder.new(@connection, @migration, {start: 1, limit: 2}).validate # does not raise
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#start' do
|
29
|
+
describe 'when initialized with 5' do
|
30
|
+
before(:each) do
|
31
|
+
@instance = Lhm::ChunkFinder.new(@connection, @migration, {start: 5, limit: 6})
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'returns 5' do
|
35
|
+
assert_equal @instance.start, 5
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'when initialized with nil and the min(id) is 22' do
|
40
|
+
before(:each) do
|
41
|
+
@connection.expects(:select_value).returns(22)
|
42
|
+
@instance = Lhm::ChunkFinder.new(@migration, @connection, {limit: 6})
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns 22' do
|
46
|
+
assert_equal @instance.start, 22
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe '#limit' do
|
52
|
+
describe 'when initialized with 6' do
|
53
|
+
before(:each) do
|
54
|
+
@instance = Lhm::ChunkFinder.new(@connection, @migration, {start: 5, limit: 6})
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns 6' do
|
58
|
+
assert_equal @instance.limit, 6
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'when initialized with nil and the max(id) is 33' do
|
63
|
+
before(:each) do
|
64
|
+
@connection.expects(:select_value).returns(33)
|
65
|
+
@instance = Lhm::ChunkFinder.new(@migration, @connection, {start: 5})
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'returns 33' do
|
69
|
+
assert_equal @instance.limit, 33
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
|
+
|
6
|
+
require 'lhm/chunk_insert'
|
7
|
+
|
8
|
+
describe Lhm::ChunkInsert do
|
9
|
+
before(:each) do
|
10
|
+
@connection = stub(:connection)
|
11
|
+
@origin = Lhm::Table.new('foo')
|
12
|
+
@destination = Lhm::Table.new('bar')
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#sql" do
|
16
|
+
describe "when migration has no conditions" do
|
17
|
+
before { @migration = Lhm::Migration.new(@origin, @destination) }
|
18
|
+
|
19
|
+
it "uses a simple where clause" do
|
20
|
+
assert_equal(
|
21
|
+
Lhm::ChunkInsert.new(@migration, @connection, 1, 2).sql,
|
22
|
+
"insert ignore into `bar` () select from `foo` where `foo`.`id` between 1 and 2"
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when migration has a WHERE condition" do
|
28
|
+
before do
|
29
|
+
@migration = Lhm::Migration.new(
|
30
|
+
@origin,
|
31
|
+
@destination,
|
32
|
+
"where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "combines the clause with the chunking WHERE condition" do
|
37
|
+
assert_equal(
|
38
|
+
Lhm::ChunkInsert.new(@migration, @connection, 1, 2).sql,
|
39
|
+
"insert ignore into `bar` () select from `foo` where (foo.created_at > '2013-07-10' or foo.baz = 'quux') and `foo`.`id` between 1 and 2"
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
|
+
|
6
|
+
require 'lhm/table'
|
7
|
+
require 'lhm/migration'
|
8
|
+
require 'lhm/chunker'
|
9
|
+
require 'lhm/throttler'
|
10
|
+
|
11
|
+
describe Lhm::Chunker do
|
12
|
+
include UnitHelper
|
13
|
+
|
14
|
+
before(:each) do
|
15
|
+
@origin = Lhm::Table.new('foo')
|
16
|
+
@destination = Lhm::Table.new('bar')
|
17
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
18
|
+
@connection = mock()
|
19
|
+
# This is a poor man's stub
|
20
|
+
@throttler = Object.new
|
21
|
+
def @throttler.run
|
22
|
+
# noop
|
23
|
+
end
|
24
|
+
def @throttler.stride
|
25
|
+
1
|
26
|
+
end
|
27
|
+
|
28
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
29
|
+
:start => 1,
|
30
|
+
:limit => 10)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#run' do
|
34
|
+
|
35
|
+
it 'detects the max id to use in the chunk using the stride and use it if it is lower than the limit' do
|
36
|
+
def @throttler.stride
|
37
|
+
5
|
38
|
+
end
|
39
|
+
|
40
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 4/)).returns(7)
|
41
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/)).returns(21)
|
42
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 7/)).returns(2)
|
43
|
+
@connection.expects(:update).with(regexp_matches(/between 8 and 10/)).returns(2)
|
44
|
+
|
45
|
+
@chunker.run
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
it 'chunks the result set according to the stride size' do
|
50
|
+
def @throttler.stride
|
51
|
+
2
|
52
|
+
end
|
53
|
+
|
54
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
55
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 1/)).returns(4)
|
56
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 5 order by id limit 1 offset 1/)).returns(6)
|
57
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 7 order by id limit 1 offset 1/)).returns(8)
|
58
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 1/)).returns(10)
|
59
|
+
|
60
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 2/)).returns(2)
|
61
|
+
@connection.expects(:update).with(regexp_matches(/between 3 and 4/)).returns(2)
|
62
|
+
@connection.expects(:update).with(regexp_matches(/between 5 and 6/)).returns(2)
|
63
|
+
@connection.expects(:update).with(regexp_matches(/between 7 and 8/)).returns(2)
|
64
|
+
@connection.expects(:update).with(regexp_matches(/between 9 and 10/)).returns(2)
|
65
|
+
|
66
|
+
@chunker.run
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'handles stride changes during execution' do
|
70
|
+
# roll our own stubbing
|
71
|
+
def @throttler.stride
|
72
|
+
@run_count ||= 0
|
73
|
+
@run_count = @run_count + 1
|
74
|
+
if @run_count > 1
|
75
|
+
3
|
76
|
+
else
|
77
|
+
2
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
82
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 2/)).returns(5)
|
83
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 2/)).returns(8)
|
84
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 2/)).returns(nil)
|
85
|
+
|
86
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 2/)).returns(2)
|
87
|
+
@connection.expects(:update).with(regexp_matches(/between 3 and 5/)).returns(2)
|
88
|
+
@connection.expects(:update).with(regexp_matches(/between 6 and 8/)).returns(2)
|
89
|
+
@connection.expects(:update).with(regexp_matches(/between 9 and 10/)).returns(2)
|
90
|
+
|
91
|
+
@chunker.run
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'correctly copies single record tables' do
|
95
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
96
|
+
:start => 1,
|
97
|
+
:limit => 1)
|
98
|
+
|
99
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 0/)).returns(nil)
|
100
|
+
@connection.expects(:update).with(regexp_matches(/between 1 and 1/)).returns(1)
|
101
|
+
|
102
|
+
@chunker.run
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'copies the last record of a table, even it is the start of the last chunk' do
|
106
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
107
|
+
:start => 2,
|
108
|
+
:limit => 10)
|
109
|
+
def @throttler.stride
|
110
|
+
2
|
111
|
+
end
|
112
|
+
|
113
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 2 order by id limit 1 offset 1/)).returns(3)
|
114
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 4 order by id limit 1 offset 1/)).returns(5)
|
115
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 1/)).returns(7)
|
116
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 1/)).returns(9)
|
117
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 10 order by id limit 1 offset 1/)).returns(nil)
|
118
|
+
|
119
|
+
@connection.expects(:update).with(regexp_matches(/between 2 and 3/)).returns(2)
|
120
|
+
@connection.expects(:update).with(regexp_matches(/between 4 and 5/)).returns(2)
|
121
|
+
@connection.expects(:update).with(regexp_matches(/between 6 and 7/)).returns(2)
|
122
|
+
@connection.expects(:update).with(regexp_matches(/between 8 and 9/)).returns(2)
|
123
|
+
@connection.expects(:update).with(regexp_matches(/between 10 and 10/)).returns(1)
|
124
|
+
|
125
|
+
@chunker.run
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
it 'separates filter conditions from chunking conditions' do
|
130
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
131
|
+
:start => 1,
|
132
|
+
:limit => 2)
|
133
|
+
def @throttler.stride
|
134
|
+
2
|
135
|
+
end
|
136
|
+
|
137
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
138
|
+
@connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/)).returns(1)
|
139
|
+
|
140
|
+
def @migration.conditions
|
141
|
+
"where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
|
142
|
+
end
|
143
|
+
|
144
|
+
@chunker.run
|
145
|
+
end
|
146
|
+
|
147
|
+
it "doesn't mess with inner join filters" do
|
148
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
149
|
+
:start => 1,
|
150
|
+
:limit => 2)
|
151
|
+
|
152
|
+
def @throttler.stride
|
153
|
+
2
|
154
|
+
end
|
155
|
+
|
156
|
+
@connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
|
157
|
+
@connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/)).returns(1)
|
158
|
+
|
159
|
+
def @migration.conditions
|
160
|
+
'inner join bar on foo.id = bar.foo_id'
|
161
|
+
end
|
162
|
+
|
163
|
+
@chunker.run
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
|
+
|
6
|
+
require 'lhm/table'
|
7
|
+
require 'lhm/migration'
|
8
|
+
require 'lhm/entangler'
|
9
|
+
|
10
|
+
describe Lhm::Entangler do
|
11
|
+
include UnitHelper
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@origin = Lhm::Table.new('origin')
|
15
|
+
@destination = Lhm::Table.new('destination')
|
16
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
17
|
+
@entangler = Lhm::Entangler.new(@migration)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'activation' do
|
21
|
+
before(:each) do
|
22
|
+
@origin.columns['info'] = { :type => 'varchar(255)' }
|
23
|
+
@origin.columns['tags'] = { :type => 'varchar(255)' }
|
24
|
+
|
25
|
+
@destination.columns['info'] = { :type => 'varchar(255)' }
|
26
|
+
@destination.columns['tags'] = { :type => 'varchar(255)' }
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should create insert trigger to destination table' do
|
30
|
+
ddl = %Q{
|
31
|
+
create trigger `lhmt_ins_origin`
|
32
|
+
after insert on `origin` for each row
|
33
|
+
replace into `destination` (`info`, `tags`) /* large hadron migration */
|
34
|
+
values (`NEW`.`info`, `NEW`.`tags`)
|
35
|
+
}
|
36
|
+
|
37
|
+
@entangler.entangle.must_include strip(ddl)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should create an update trigger to the destination table' do
|
41
|
+
ddl = %Q{
|
42
|
+
create trigger `lhmt_upd_origin`
|
43
|
+
after update on `origin` for each row
|
44
|
+
replace into `destination` (`info`, `tags`) /* large hadron migration */
|
45
|
+
values (`NEW`.`info`, `NEW`.`tags`)
|
46
|
+
}
|
47
|
+
|
48
|
+
@entangler.entangle.must_include strip(ddl)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should create a delete trigger to the destination table' do
|
52
|
+
ddl = %Q{
|
53
|
+
create trigger `lhmt_del_origin`
|
54
|
+
after delete on `origin` for each row
|
55
|
+
delete ignore from `destination` /* large hadron migration */
|
56
|
+
where `destination`.`id` = OLD.`id`
|
57
|
+
}
|
58
|
+
|
59
|
+
@entangler.entangle.must_include strip(ddl)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'should retry trigger creation when it hits a lock wait timeout' do
|
63
|
+
connection = mock()
|
64
|
+
tries = 1
|
65
|
+
@entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0, tries: tries})
|
66
|
+
connection.expects(:execute).times(tries).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
|
67
|
+
|
68
|
+
assert_raises(Mysql2::Error) { @entangler.before }
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should not retry trigger creation with other mysql errors' do
|
72
|
+
connection = mock()
|
73
|
+
connection.expects(:execute).once.raises(Mysql2::Error, 'The MySQL server is running with the --read-only option so it cannot execute this statement.')
|
74
|
+
|
75
|
+
@entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0})
|
76
|
+
assert_raises(Mysql2::Error) { @entangler.before }
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should succesfully finish after retrying' do
|
80
|
+
connection = mock()
|
81
|
+
connection.stubs(:execute).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction').then.returns(true)
|
82
|
+
@entangler = Lhm::Entangler.new(@migration, connection, retriable: {base_interval: 0})
|
83
|
+
|
84
|
+
assert @entangler.before
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should retry as many times as specified by configuration' do
|
88
|
+
connection = mock()
|
89
|
+
connection.expects(:execute).times(5).raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
|
90
|
+
@entangler = Lhm::Entangler.new(@migration, connection, retriable: {tries: 5, base_interval: 0})
|
91
|
+
|
92
|
+
assert_raises(Mysql2::Error) { @entangler.before }
|
93
|
+
end
|
94
|
+
|
95
|
+
describe 'super long table names' do
|
96
|
+
before(:each) do
|
97
|
+
@origin = Lhm::Table.new('a' * 64)
|
98
|
+
@destination = Lhm::Table.new('b' * 64)
|
99
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
100
|
+
@entangler = Lhm::Entangler.new(@migration)
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should use truncated names' do
|
104
|
+
@entangler.trigger(:ins).length.must_be :<=, 64
|
105
|
+
@entangler.trigger(:upd).length.must_be :<=, 64
|
106
|
+
@entangler.trigger(:del).length.must_be :<=, 64
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe 'removal' do
|
112
|
+
it 'should remove insert trigger' do
|
113
|
+
@entangler.untangle.must_include('drop trigger if exists `lhmt_ins_origin`')
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'should remove update trigger' do
|
117
|
+
@entangler.untangle.must_include('drop trigger if exists `lhmt_upd_origin`')
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should remove delete trigger' do
|
121
|
+
@entangler.untangle.must_include('drop trigger if exists `lhmt_del_origin`')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Copyright (c) 2011 - 2013, SoundCloud Ltd., Rany Keddo, Tobias Bielohlawek, Tobias
|
2
|
+
# Schmidt
|
3
|
+
|
4
|
+
require File.expand_path(File.dirname(__FILE__)) + '/unit_helper'
|
5
|
+
|
6
|
+
require 'lhm/table'
|
7
|
+
require 'lhm/migrator'
|
8
|
+
|
9
|
+
describe Lhm::Intersection do
|
10
|
+
include UnitHelper
|
11
|
+
|
12
|
+
it 'should not have dropped changes' do
|
13
|
+
origin = Lhm::Table.new('origin')
|
14
|
+
origin.columns['dropped'] = varchar
|
15
|
+
origin.columns['retained'] = varchar
|
16
|
+
|
17
|
+
destination = Lhm::Table.new('destination')
|
18
|
+
destination.columns['retained'] = varchar
|
19
|
+
|
20
|
+
intersection = Lhm::Intersection.new(origin, destination)
|
21
|
+
intersection.destination.include?('dropped').must_equal(false)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should have unchanged columns' do
|
25
|
+
origin = Lhm::Table.new('origin')
|
26
|
+
origin.columns['dropped'] = varchar
|
27
|
+
origin.columns['retained'] = varchar
|
28
|
+
|
29
|
+
destination = Lhm::Table.new('destination')
|
30
|
+
destination.columns['retained'] = varchar
|
31
|
+
|
32
|
+
intersection = Lhm::Intersection.new(origin, destination)
|
33
|
+
intersection.destination.must_equal(['retained'])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should have renamed columns' do
|
37
|
+
origin = Lhm::Table.new('origin')
|
38
|
+
origin.columns['old_name'] = varchar
|
39
|
+
|
40
|
+
destination = Lhm::Table.new('destination')
|
41
|
+
destination.columns['new_name'] = varchar
|
42
|
+
|
43
|
+
intersection = Lhm::Intersection.new(origin, destination, { 'old_name' => 'new_name' })
|
44
|
+
intersection.origin.must_equal(['old_name'])
|
45
|
+
intersection.destination.must_equal(['new_name'])
|
46
|
+
end
|
47
|
+
|
48
|
+
def varchar
|
49
|
+
{ :metadata => 'VARCHAR(255)' }
|
50
|
+
end
|
51
|
+
end
|