lhm-teak 3.6.0 → 3.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b84f8808e479b9588bcd03e14dd67ef885b058d2abec860e174a947545ff2af0
4
- data.tar.gz: 644b6c7e3565d091a8a24d05d35d4077c63846c62761ff637ebc754cd1be846a
3
+ metadata.gz: 0cb61bdc9210d61dff6a96cf2975604f7f67d5e6d053acb5b84f1a016d8bd127
4
+ data.tar.gz: eb107d91175df6109f9b0640e7e0191cea7c946da2d64b49f4f95b86cc08e606
5
5
  SHA512:
6
- metadata.gz: 046cf2c7d36a0ca272bcb0b0130bfd115cc3723610b6b4f9e8d74fe9757a96c3ab3a2e0be779810a6d7a2894ca660e518ffb407b21371de60e8f31ec45113c61
7
- data.tar.gz: e60d5ad405e2a7cbc2f9d057a13ad7fc85bd89d0f6472685a5d72a33cc19b8878688e94a8f2968466028db4947cadcdaaa7fb415f442f7140d803d373ee3e21e
6
+ metadata.gz: b59f1aa113f91e6ff974a87b9fb019a6307278302110b3a99ccc58757f299acd99c6eaee3f9d6d19416cfcf6ffd9a8cdfac17a21fa29fe8b7491956eb7921f82
7
+ data.tar.gz: 05f12dcc9e251b77e624c101dd7e032d5c693f293abf2a3e8f34f8679d0997df0203885273aab8bc74f017fec8e0fa24ecf769e9288703d56fbeda7d6f084e8e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lhm-teak (3.6.0)
4
+ lhm-teak (3.6.1)
5
5
  retriable (>= 3.0.0)
6
6
 
7
7
  GEM
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(:chuck_finder, ChunkFinder).new(migration, connection, options)
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
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '3.6.0'
5
+ VERSION = '3.6.1'
6
6
  end
@@ -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
@@ -3,7 +3,9 @@
3
3
 
4
4
  if ENV['COV']
5
5
  require 'simplecov'
6
- SimpleCov.start
6
+ SimpleCov.start do
7
+ enable_coverage :branch
8
+ end
7
9
  end
8
10
 
9
11
  require 'minitest/autorun'
@@ -65,5 +67,3 @@ def init_test_db
65
67
  end
66
68
 
67
69
  init_test_db
68
-
69
-
@@ -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.0
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-06 00:00:00.000000000 Z
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