lhm-teak 3.6.0 → 3.6.1
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/lhm/chunker.rb +1 -1
- data/lib/lhm/id_set_chunk_finder.rb +50 -0
- data/lib/lhm/id_set_chunk_insert.rb +60 -0
- data/lib/lhm/version.rb +1 -1
- data/spec/integration/id_set_chunk_finder_spec.rb +270 -0
- data/spec/integration/id_set_chunk_insert_spec.rb +31 -0
- data/spec/test_helper.rb +3 -3
- data/spec/unit/id_set_chunk_finder_spec.rb +145 -0
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cb61bdc9210d61dff6a96cf2975604f7f67d5e6d053acb5b84f1a016d8bd127
|
4
|
+
data.tar.gz: eb107d91175df6109f9b0640e7e0191cea7c946da2d64b49f4f95b86cc08e606
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b59f1aa113f91e6ff974a87b9fb019a6307278302110b3a99ccc58757f299acd99c6eaee3f9d6d19416cfcf6ffd9a8cdfac17a21fa29fe8b7491956eb7921f82
|
7
|
+
data.tar.gz: 05f12dcc9e251b77e624c101dd7e032d5c693f293abf2a3e8f34f8679d0997df0203885273aab8bc74f017fec8e0fa24ecf769e9288703d56fbeda7d6f084e8e
|
data/Gemfile.lock
CHANGED
data/lib/lhm/chunker.rb
CHANGED
@@ -20,7 +20,7 @@ module Lhm
|
|
20
20
|
def initialize(migration, connection = nil, options = {})
|
21
21
|
@migration = migration
|
22
22
|
@connection = connection
|
23
|
-
@chunk_finder = options.fetch(:
|
23
|
+
@chunk_finder = options.fetch(:chunk_finder, ChunkFinder).new(migration, connection, options)
|
24
24
|
@options = options
|
25
25
|
@raise_on_warnings = options.fetch(:raise_on_warnings, false)
|
26
26
|
@verifier = options[:verifier]
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'lhm/id_set_chunk_insert'
|
2
|
+
|
3
|
+
module Lhm
|
4
|
+
class IdSetChunkFinder
|
5
|
+
LOG_PREFIX = "Chunker"
|
6
|
+
|
7
|
+
def initialize(migration, connection = nil, options = {})
|
8
|
+
@migration = migration
|
9
|
+
@connection = connection
|
10
|
+
@ids = options[:ids]
|
11
|
+
@throttler = options[:throttler]
|
12
|
+
@processed_rows = 0
|
13
|
+
end
|
14
|
+
|
15
|
+
def table_empty?
|
16
|
+
ids.nil? || ids.empty?
|
17
|
+
end
|
18
|
+
|
19
|
+
def validate
|
20
|
+
end
|
21
|
+
|
22
|
+
def each_chunk
|
23
|
+
@processed_rows = 0
|
24
|
+
while @processed_rows < ids.length
|
25
|
+
next_idx = [@processed_rows + @throttler.stride, @ids.length].min
|
26
|
+
ids_to_insert = ids[@processed_rows...next_idx]
|
27
|
+
@processed_rows = next_idx
|
28
|
+
yield IdSetChunkInsert.new(@migration, @connection, ids_to_insert)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def max_rows
|
33
|
+
ids.length
|
34
|
+
end
|
35
|
+
|
36
|
+
def processed_rows
|
37
|
+
@processed_rows
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def ids
|
43
|
+
@ids ||= select_ids_from_db
|
44
|
+
end
|
45
|
+
|
46
|
+
def select_ids_from_db
|
47
|
+
@connection.select_values("select id from `#{ @migration.origin_name }` order by id asc", should_retry: true, log_prefix: LOG_PREFIX)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'lhm/sql_retry'
|
2
|
+
require 'lhm/proxysql_helper'
|
3
|
+
|
4
|
+
module Lhm
|
5
|
+
class IdSetChunkInsert
|
6
|
+
|
7
|
+
LOG_PREFIX = "ChunkInsert"
|
8
|
+
|
9
|
+
def initialize(migration, connection, ids, retry_options = {})
|
10
|
+
@migration = migration
|
11
|
+
@connection = connection
|
12
|
+
@ids = ids
|
13
|
+
@retry_options = retry_options
|
14
|
+
end
|
15
|
+
|
16
|
+
def insert_and_return_count_of_rows_created
|
17
|
+
@connection.update(sql, should_retry: true, log_prefix: LOG_PREFIX)
|
18
|
+
end
|
19
|
+
|
20
|
+
def bottom
|
21
|
+
@ids[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def top
|
25
|
+
@ids[-1]
|
26
|
+
end
|
27
|
+
|
28
|
+
def expected_rows
|
29
|
+
@ids.length
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def sql
|
35
|
+
"insert ignore into `#{ @migration.destination_name }` (#{ @migration.destination_columns }) " \
|
36
|
+
"select #{ @migration.origin_columns } from `#{ @migration.origin_name }` " \
|
37
|
+
"#{ conditions } `#{ @migration.origin_name }`.`id` in (#{@ids.join(',')})"
|
38
|
+
end
|
39
|
+
|
40
|
+
# XXX this is extremely brittle and doesn't work when filter contains more
|
41
|
+
# than one SQL clause, e.g. "where ... group by foo". Before making any
|
42
|
+
# more changes here, please consider either:
|
43
|
+
#
|
44
|
+
# 1. Letting users only specify part of defined clauses (i.e. don't allow
|
45
|
+
# `filter` on Migrator to accept both WHERE and INNER JOIN
|
46
|
+
# 2. Changing query building so that it uses structured data rather than
|
47
|
+
# strings until the last possible moment.
|
48
|
+
def conditions
|
49
|
+
if @migration.conditions
|
50
|
+
@migration.conditions.
|
51
|
+
# strip ending paren
|
52
|
+
sub(/\)\Z/, '').
|
53
|
+
# put any where conditions in parens
|
54
|
+
sub(/where\s(\w.*)\Z/, 'where (\\1)') + ' and'
|
55
|
+
else
|
56
|
+
'where'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/lhm/version.rb
CHANGED
@@ -0,0 +1,270 @@
|
|
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 'lhm/table'
|
6
|
+
require 'lhm/migration'
|
7
|
+
require 'lhm/id_set_chunk_finder'
|
8
|
+
|
9
|
+
describe Lhm::IdSetChunkFinder do
|
10
|
+
include IntegrationHelper
|
11
|
+
|
12
|
+
before(:each) { connect_master! }
|
13
|
+
|
14
|
+
describe 'copying' do
|
15
|
+
before(:each) do
|
16
|
+
@origin = table_create(:origin)
|
17
|
+
@destination = table_create(:destination)
|
18
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
19
|
+
@logs = StringIO.new
|
20
|
+
Lhm.logger = Logger.new(@logs)
|
21
|
+
end
|
22
|
+
|
23
|
+
def log_messages
|
24
|
+
@logs.string.split("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should copy 1 row from origin to destination even if the id of the single row does not start at 1' do
|
28
|
+
execute("insert into origin set id = 1001 ")
|
29
|
+
|
30
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
31
|
+
|
32
|
+
slave do
|
33
|
+
value(count_all(@destination.name)).must_equal(1)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should copy and ignore duplicate primary key' do
|
39
|
+
execute("insert into origin set id = 1001 ")
|
40
|
+
execute("insert into origin set id = 1002 ")
|
41
|
+
execute("insert into destination set id = 1002 ")
|
42
|
+
|
43
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
44
|
+
|
45
|
+
slave do
|
46
|
+
value(count_all(@destination.name)).must_equal(2)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should copy and ignore duplicate composite primary key' do
|
51
|
+
origin = table_create(:composite_primary_key)
|
52
|
+
destination = table_create(:composite_primary_key_dest)
|
53
|
+
migration = Lhm::Migration.new(origin, destination)
|
54
|
+
|
55
|
+
execute("insert into composite_primary_key set id = 1001, shop_id = 1")
|
56
|
+
execute("insert into composite_primary_key set id = 1002, shop_id = 1")
|
57
|
+
execute("insert into composite_primary_key_dest set id = 1002, shop_id = 1")
|
58
|
+
|
59
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
60
|
+
|
61
|
+
slave do
|
62
|
+
value(count_all(destination.name)).must_equal(2)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should copy and raise on unexpected warnings' do
|
67
|
+
origin = table_create(:custom_primary_key)
|
68
|
+
destination = table_create(:custom_primary_key_dest)
|
69
|
+
migration = Lhm::Migration.new(origin, destination)
|
70
|
+
|
71
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
72
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
73
|
+
|
74
|
+
exception = assert_raises(Lhm::Error) do
|
75
|
+
Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
76
|
+
end
|
77
|
+
|
78
|
+
assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'", exception.message
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should copy and warn on unexpected warnings by default' do
|
82
|
+
origin = table_create(:custom_primary_key)
|
83
|
+
destination = table_create(:custom_primary_key_dest)
|
84
|
+
migration = Lhm::Migration.new(origin, destination)
|
85
|
+
|
86
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
87
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
88
|
+
|
89
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
90
|
+
|
91
|
+
assert_equal 2, log_messages.length
|
92
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'should log two times for two unexpected warnings' do
|
96
|
+
origin = table_create(:custom_primary_key)
|
97
|
+
destination = table_create(:custom_primary_key_dest)
|
98
|
+
migration = Lhm::Migration.new(origin, destination)
|
99
|
+
|
100
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
101
|
+
execute("insert into custom_primary_key set id = 1002, pk = 2")
|
102
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 3")
|
103
|
+
execute("insert into custom_primary_key_dest set id = 1002, pk = 4")
|
104
|
+
|
105
|
+
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
106
|
+
|
107
|
+
assert_equal 3, log_messages.length
|
108
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
109
|
+
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key 'index_custom_primary_key_on_id'"), log_messages
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should copy and warn on unexpected warnings' do
|
113
|
+
origin = table_create(:custom_primary_key)
|
114
|
+
destination = table_create(:custom_primary_key_dest)
|
115
|
+
migration = Lhm::Migration.new(origin, destination)
|
116
|
+
|
117
|
+
execute("insert into custom_primary_key set id = 1001, pk = 1")
|
118
|
+
execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
|
119
|
+
|
120
|
+
Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
121
|
+
|
122
|
+
assert_equal 2, log_messages.length
|
123
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should create the modified destination, even if the source is empty' do
|
127
|
+
execute("truncate origin ")
|
128
|
+
|
129
|
+
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder} ).run
|
130
|
+
|
131
|
+
slave do
|
132
|
+
value(count_all(@destination.name)).must_equal(0)
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'should copy 23 rows from origin to destination in one shot, regardless of the value of the id' do
|
138
|
+
23.times { |n| execute("insert into origin set id = '#{ n * n + 23 }'") }
|
139
|
+
|
140
|
+
printer = MiniTest::Mock.new
|
141
|
+
printer.expect(:notify, :return_value, [Integer, Integer])
|
142
|
+
printer.expect(:end, :return_value, [])
|
143
|
+
|
144
|
+
Lhm::Chunker.new(
|
145
|
+
@migration, connection, { throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder }
|
146
|
+
).run
|
147
|
+
|
148
|
+
slave do
|
149
|
+
value(count_all(@destination.name)).must_equal(23)
|
150
|
+
end
|
151
|
+
|
152
|
+
printer.verify
|
153
|
+
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should copy all the records of a table, even if the last chunk starts with the last record of it.' do
|
157
|
+
11.times { |n| execute("insert into origin set id = '#{ n + 1 }'") }
|
158
|
+
|
159
|
+
|
160
|
+
Lhm::Chunker.new(
|
161
|
+
@migration, connection, { throttler: Lhm::Throttler::Time.new(stride: 10), printer: printer, chunk_finder: Lhm::IdSetChunkFinder }
|
162
|
+
).run
|
163
|
+
|
164
|
+
slave do
|
165
|
+
value(count_all(@destination.name)).must_equal(11)
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'should copy 23 rows from origin to destination in one shot with slave lag based throttler, regardless of the value of the id' do
|
171
|
+
23.times { |n| execute("insert into origin set id = '#{ 100000 + n * n + 23 }'") }
|
172
|
+
|
173
|
+
printer = MiniTest::Mock.new
|
174
|
+
printer.expect(:notify, :return_value, [Integer, Integer])
|
175
|
+
printer.expect(:end, :return_value, [])
|
176
|
+
|
177
|
+
Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
|
178
|
+
Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
|
179
|
+
|
180
|
+
Lhm::Chunker.new(
|
181
|
+
@migration, connection, { throttler: Lhm::Throttler::SlaveLag.new(stride: 100), printer: printer, chunk_finder: Lhm::IdSetChunkFinder }
|
182
|
+
).run
|
183
|
+
|
184
|
+
slave do
|
185
|
+
value(count_all(@destination.name)).must_equal(23)
|
186
|
+
end
|
187
|
+
|
188
|
+
printer.verify
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should throttle work stride based on slave lag' do
|
192
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
193
|
+
|
194
|
+
printer = mock()
|
195
|
+
printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
|
196
|
+
printer.expects(:end)
|
197
|
+
|
198
|
+
throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
|
199
|
+
def throttler.max_current_slave_lag
|
200
|
+
1
|
201
|
+
end
|
202
|
+
|
203
|
+
Lhm::Chunker.new(
|
204
|
+
@migration, connection, { throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder }
|
205
|
+
).run
|
206
|
+
|
207
|
+
assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
|
208
|
+
|
209
|
+
slave do
|
210
|
+
value(count_all(@destination.name)).must_equal(15)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should detect a single slave with no lag in the default configuration' do
|
215
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
216
|
+
|
217
|
+
printer = mock()
|
218
|
+
printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
|
219
|
+
printer.expects(:verify)
|
220
|
+
printer.expects(:end)
|
221
|
+
|
222
|
+
Lhm::Throttler::Slave.any_instance.stubs(:slave_hosts).returns(['127.0.0.1'])
|
223
|
+
Lhm::Throttler::SlaveLag.any_instance.stubs(:master_slave_hosts).returns(['127.0.0.1'])
|
224
|
+
|
225
|
+
throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
|
226
|
+
|
227
|
+
if master_slave_mode?
|
228
|
+
def throttler.slave_connection(slave)
|
229
|
+
config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
230
|
+
config[:host] = slave
|
231
|
+
config[:port] = 33007
|
232
|
+
ActiveRecord::Base.send('mysql2_connection', config)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
Lhm::Chunker.new(
|
237
|
+
@migration, connection, { throttler: throttler, printer: printer, chunk_finder: Lhm::IdSetChunkFinder }
|
238
|
+
).run
|
239
|
+
|
240
|
+
assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT, throttler.timeout_seconds)
|
241
|
+
assert_equal(0, throttler.send(:max_current_slave_lag))
|
242
|
+
|
243
|
+
slave do
|
244
|
+
value(count_all(@destination.name)).must_equal(15)
|
245
|
+
end
|
246
|
+
|
247
|
+
printer.verify
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should abort early if the triggers are removed' do
|
251
|
+
15.times { |n| execute("insert into origin set id = '#{ (n * n) + 1 }'") }
|
252
|
+
|
253
|
+
printer = mock()
|
254
|
+
|
255
|
+
failer = Proc.new { false }
|
256
|
+
|
257
|
+
exception = assert_raises do
|
258
|
+
Lhm::Chunker.new(
|
259
|
+
@migration, connection, { verifier: failer, printer: printer, throttler: throttler, chunk_finder: Lhm::IdSetChunkFinder }
|
260
|
+
).run
|
261
|
+
end
|
262
|
+
|
263
|
+
assert_match "Verification failed, aborting early", exception.message
|
264
|
+
|
265
|
+
slave do
|
266
|
+
value(count_all(@destination.name)).must_equal(0)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__)) + '/integration_helper'
|
2
|
+
require 'lhm/migration'
|
3
|
+
require 'lhm/id_set_chunk_insert'
|
4
|
+
|
5
|
+
describe Lhm::IdSetChunkInsert do
|
6
|
+
include IntegrationHelper
|
7
|
+
|
8
|
+
describe 'insert_and_return_count_of_rows_created' do
|
9
|
+
before(:each) do
|
10
|
+
connect_master!
|
11
|
+
@origin = table_create(:origin)
|
12
|
+
@destination = table_create(:destination)
|
13
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
14
|
+
execute("insert into origin set id = 1001")
|
15
|
+
@connection = Lhm::Connection.new(connection: connection)
|
16
|
+
@instance = Lhm::IdSetChunkInsert.new(@migration, @connection, [1001])
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns the count" do
|
20
|
+
assert_equal 1, @instance.insert_and_return_count_of_rows_created
|
21
|
+
end
|
22
|
+
|
23
|
+
it "inserts the record into the slave" do
|
24
|
+
@instance.insert_and_return_count_of_rows_created
|
25
|
+
|
26
|
+
slave do
|
27
|
+
value(count_all(@destination.name)).must_equal(1)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/test_helper.rb
CHANGED
@@ -0,0 +1,145 @@
|
|
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
|
+
require 'lhm/connection'
|
11
|
+
require 'lhm/id_set_chunk_finder'
|
12
|
+
|
13
|
+
describe Lhm::IdSetChunkFinder do
|
14
|
+
include UnitHelper
|
15
|
+
|
16
|
+
EXPECTED_RETRY_FLAGS_CHUNKER = {:should_retry => true, :log_prefix => "Chunker"}
|
17
|
+
EXPECTED_RETRY_FLAGS_CHUNK_INSERT = {:should_retry => true, :log_prefix => "ChunkInsert"}
|
18
|
+
|
19
|
+
before(:each) do
|
20
|
+
@origin = Lhm::Table.new('foo')
|
21
|
+
@destination = Lhm::Table.new('bar')
|
22
|
+
@migration = Lhm::Migration.new(@origin, @destination)
|
23
|
+
@connection = mock()
|
24
|
+
@connection.stubs(:execute).returns([["dummy"]])
|
25
|
+
# This is a poor man's stub
|
26
|
+
@throttler = Object.new
|
27
|
+
def @throttler.run
|
28
|
+
# noop
|
29
|
+
end
|
30
|
+
def @throttler.stride
|
31
|
+
1
|
32
|
+
end
|
33
|
+
|
34
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
35
|
+
:chunk_finder => Lhm::IdSetChunkFinder)
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#run' do
|
39
|
+
it 'chunks the result set according to the stride size' do
|
40
|
+
def @throttler.stride
|
41
|
+
2
|
42
|
+
end
|
43
|
+
|
44
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns((1..20).select(&:odd?))
|
45
|
+
|
46
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(1,3\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
47
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(5,7\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
48
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(9,11\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
49
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(13,15\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
50
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(17,19\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
51
|
+
|
52
|
+
@chunker.run
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'handles stride changes during execution' do
|
56
|
+
# roll our own stubbing
|
57
|
+
def @throttler.stride
|
58
|
+
@run_count ||= 0
|
59
|
+
@run_count = @run_count + 1
|
60
|
+
if @run_count > 1
|
61
|
+
3
|
62
|
+
else
|
63
|
+
2
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns((1..20).select(&:odd?))
|
68
|
+
|
69
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(1,3\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
70
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(5,7,9\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
71
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(11,13,15\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
72
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(17,19\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
73
|
+
|
74
|
+
@connection.expects(:execute).twice.with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
75
|
+
|
76
|
+
@chunker.run
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'correctly copies single record tables' do
|
80
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
81
|
+
:chunk_finder => Lhm::IdSetChunkFinder)
|
82
|
+
|
83
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([1])
|
84
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(1\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
85
|
+
|
86
|
+
@chunker.run
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'copies the last record of a table, even it is the start of the last chunk' do
|
90
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
91
|
+
:chunk_finder => Lhm::IdSetChunkFinder)
|
92
|
+
def @throttler.stride
|
93
|
+
2
|
94
|
+
end
|
95
|
+
|
96
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns((2..10).to_a)
|
97
|
+
|
98
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(2,3\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
99
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(4,5\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
100
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(6,7\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
101
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(8,9\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
|
102
|
+
@connection.expects(:update).with(regexp_matches(/`id` in \(10\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
103
|
+
|
104
|
+
@chunker.run
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
it 'separates filter conditions from chunking conditions' do
|
109
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
110
|
+
:chunk_finder => Lhm::IdSetChunkFinder)
|
111
|
+
def @throttler.stride
|
112
|
+
2
|
113
|
+
end
|
114
|
+
|
115
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([1, 2])
|
116
|
+
@connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`.*`id` in \(1,2\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
117
|
+
@connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
118
|
+
|
119
|
+
def @migration.conditions
|
120
|
+
"where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
|
121
|
+
end
|
122
|
+
|
123
|
+
@chunker.run
|
124
|
+
end
|
125
|
+
|
126
|
+
it "doesn't mess with inner join filters" do
|
127
|
+
@chunker = Lhm::Chunker.new(@migration, @connection, :throttler => @throttler,
|
128
|
+
:chunk_finder => Lhm::IdSetChunkFinder)
|
129
|
+
|
130
|
+
def @throttler.stride
|
131
|
+
2
|
132
|
+
end
|
133
|
+
|
134
|
+
@connection.expects(:select_values).with(regexp_matches(/order by id asc/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([1,2])
|
135
|
+
@connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and.*`id` in \(1,2\)/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
|
136
|
+
@connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
|
137
|
+
|
138
|
+
def @migration.conditions
|
139
|
+
'inner join bar on foo.id = bar.foo_id'
|
140
|
+
end
|
141
|
+
|
142
|
+
@chunker.run
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lhm-teak
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.6.
|
4
|
+
version: 3.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- SoundCloud
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date: 2023-06-
|
16
|
+
date: 2023-06-07 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: retriable
|
@@ -209,6 +209,8 @@ files:
|
|
209
209
|
- lib/lhm/command.rb
|
210
210
|
- lib/lhm/connection.rb
|
211
211
|
- lib/lhm/entangler.rb
|
212
|
+
- lib/lhm/id_set_chunk_finder.rb
|
213
|
+
- lib/lhm/id_set_chunk_insert.rb
|
212
214
|
- lib/lhm/intersection.rb
|
213
215
|
- lib/lhm/invoker.rb
|
214
216
|
- lib/lhm/locked_switcher.rb
|
@@ -255,6 +257,8 @@ files:
|
|
255
257
|
- spec/integration/cleanup_spec.rb
|
256
258
|
- spec/integration/database.yml
|
257
259
|
- spec/integration/entangler_spec.rb
|
260
|
+
- spec/integration/id_set_chunk_finder_spec.rb
|
261
|
+
- spec/integration/id_set_chunk_insert_spec.rb
|
258
262
|
- spec/integration/integration_helper.rb
|
259
263
|
- spec/integration/invoker_spec.rb
|
260
264
|
- spec/integration/lhm_spec.rb
|
@@ -275,6 +279,7 @@ files:
|
|
275
279
|
- spec/unit/chunker_spec.rb
|
276
280
|
- spec/unit/connection_spec.rb
|
277
281
|
- spec/unit/entangler_spec.rb
|
282
|
+
- spec/unit/id_set_chunk_finder_spec.rb
|
278
283
|
- spec/unit/intersection_spec.rb
|
279
284
|
- spec/unit/lhm_spec.rb
|
280
285
|
- spec/unit/locked_switcher_spec.rb
|
@@ -332,6 +337,8 @@ test_files:
|
|
332
337
|
- spec/integration/cleanup_spec.rb
|
333
338
|
- spec/integration/database.yml
|
334
339
|
- spec/integration/entangler_spec.rb
|
340
|
+
- spec/integration/id_set_chunk_finder_spec.rb
|
341
|
+
- spec/integration/id_set_chunk_insert_spec.rb
|
335
342
|
- spec/integration/integration_helper.rb
|
336
343
|
- spec/integration/invoker_spec.rb
|
337
344
|
- spec/integration/lhm_spec.rb
|
@@ -352,6 +359,7 @@ test_files:
|
|
352
359
|
- spec/unit/chunker_spec.rb
|
353
360
|
- spec/unit/connection_spec.rb
|
354
361
|
- spec/unit/entangler_spec.rb
|
362
|
+
- spec/unit/id_set_chunk_finder_spec.rb
|
355
363
|
- spec/unit/intersection_spec.rb
|
356
364
|
- spec/unit/lhm_spec.rb
|
357
365
|
- spec/unit/locked_switcher_spec.rb
|