lhm-shopify 3.3.6 → 3.4.0

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: 73574a1b69152ceda698d97bda7b8ccc92e006fb8a8caedc756d9667a63e1f04
4
- data.tar.gz: 1b4df5f80d8fc05d857c04fd43beb6822424574fd95dbe259f32e2847d5dfe52
3
+ metadata.gz: c60de7019efd80463c07434cb2312197009347dc5e7b8d238c1f3ba0355b7a9e
4
+ data.tar.gz: 44658f6f74b6c98739c6fd58046dd3ffa20436949f379eb9607a070afabeee8e
5
5
  SHA512:
6
- metadata.gz: 5c3f86f66d4bb9ef14b001106bcf83ba91befa3cbbc3996f5e38688598b25ebf340eca23c0144bd1b17d0896ad04f978cc33b3a2c2a9bcadcdd39044930f12c1
7
- data.tar.gz: e937acda8252f5ae2aa9883ef60c83409ca92602312316eec11360b440b40d8c79bf36654c2dfcdd82ce745a04f328c8482ae304059b5730bca9ee6ca2501141
6
+ metadata.gz: 318c3a3f051c8bfd8cb8e30bdfef2bad0c0c61fcbc7081780adb4eaba60023e94ac9d86b4f203a950ad31c304cc3bdd3affb88eb29633ef115185aab92596d55
7
+ data.tar.gz: 644b728df70002076404e93133dbed4f53565bec70de02ca39640a482276ad7480de18a45a06a277bd55c1d6cad906026a7454bd05e7471efd055921da51708a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 3.4.0 (Jul 19, 2021)
2
+
3
+ * Log or raise on unexpected duplicated entry warnings during INSERT IGNORE (https://github.com/Shopify/lhm/pull/100)
4
+
1
5
  # 3.3.6 (Jul 7, 2021)
2
6
 
3
7
  * Add lhm-shopify.rb to require lhm
data/lib/lhm/chunker.rb CHANGED
@@ -20,6 +20,7 @@ module Lhm
20
20
  @connection = connection
21
21
  @chunk_finder = ChunkFinder.new(migration, connection, options)
22
22
  @options = options
23
+ @raise_on_warnings = options.fetch(:raise_on_warnings, false)
23
24
  @verifier = options[:verifier]
24
25
  if @throttler = options[:throttler]
25
26
  @throttler.connection = @connection if @throttler.respond_to?(:connection=)
@@ -44,6 +45,12 @@ module Lhm
44
45
  verify_can_run
45
46
 
46
47
  affected_rows = ChunkInsert.new(@migration, @connection, bottom, top, @options).insert_and_return_count_of_rows_created
48
+ expected_rows = top - bottom + 1
49
+
50
+ if affected_rows < expected_rows
51
+ raise_on_non_pk_duplicate_warning
52
+ end
53
+
47
54
  if @throttler && affected_rows > 0
48
55
  @throttler.run
49
56
  end
@@ -59,6 +66,16 @@ module Lhm
59
66
 
60
67
  private
61
68
 
69
+ def raise_on_non_pk_duplicate_warning
70
+ @connection.query("show warnings").each do |level, code, message|
71
+ unless message.match?(/Duplicate entry .+ for key 'PRIMARY'/)
72
+ m = "Unexpected warning found for inserted row: #{message}"
73
+ Lhm.logger.warn(m)
74
+ raise Error.new(m) if @raise_on_warnings
75
+ end
76
+ end
77
+ end
78
+
62
79
  def bottom
63
80
  @next_to_insert
64
81
  end
data/lib/lhm/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '3.3.6'
5
+ VERSION = '3.4.0'
6
6
  end
@@ -1,7 +1,6 @@
1
-
2
1
  CREATE TABLE `composite_primary_key` (
3
2
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
4
3
  `shop_id` bigint(20) NOT NULL,
5
- PRIMARY KEY (`shop_id`,`id`),
4
+ CONSTRAINT `pk_composite` PRIMARY KEY (`shop_id`,`id`),
6
5
  INDEX `index_key_id` (`id`)
7
6
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `composite_primary_key_dest` (
2
+ `id` bigint(20) NOT NULL AUTO_INCREMENT,
3
+ `shop_id` bigint(20) NOT NULL,
4
+ CONSTRAINT `pk_composite` PRIMARY KEY (`shop_id`,`id`),
5
+ INDEX `index_key_id` (`id`)
6
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -0,0 +1,6 @@
1
+ CREATE TABLE `custom_primary_key_dest` (
2
+ `id` int(11) NOT NULL AUTO_INCREMENT,
3
+ `pk` varchar(255),
4
+ PRIMARY KEY (`pk`),
5
+ UNIQUE KEY `index_custom_primary_key_on_id` (`id`)
6
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
@@ -15,16 +15,18 @@ describe Lhm::Chunker do
15
15
  @origin = table_create(:origin)
16
16
  @destination = table_create(:destination)
17
17
  @migration = Lhm::Migration.new(@origin, @destination)
18
+ @logs = StringIO.new
19
+ Lhm.logger = Logger.new(@logs)
20
+ end
21
+
22
+ def log_messages
23
+ @logs.string.split("\n")
18
24
  end
19
25
 
20
26
  it 'should copy 1 row from origin to destination even if the id of the single row does not start at 1' do
21
27
  execute("insert into origin set id = 1001 ")
22
- printer = Lhm::Printer::Base.new
23
-
24
- def printer.notify(*) ;end
25
- def printer.end(*) [] ;end
26
28
 
27
- Lhm::Chunker.new(@migration, connection, {:throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer} ).run
29
+ Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
28
30
 
29
31
  slave do
30
32
  count_all(@destination.name).must_equal(1)
@@ -32,14 +34,98 @@ describe Lhm::Chunker do
32
34
 
33
35
  end
34
36
 
37
+ it 'should copy and ignore duplicate primary key' do
38
+ execute("insert into origin set id = 1001 ")
39
+ execute("insert into origin set id = 1002 ")
40
+ execute("insert into destination set id = 1002 ")
41
+
42
+ Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
43
+
44
+ slave do
45
+ count_all(@destination.name).must_equal(2)
46
+ end
47
+ end
48
+
49
+ it 'should copy and ignore duplicate composite primary key' do
50
+ origin = table_create(:composite_primary_key)
51
+ destination = table_create(:composite_primary_key_dest)
52
+ migration = Lhm::Migration.new(origin, destination)
53
+
54
+ execute("insert into composite_primary_key set id = 1001, shop_id = 1")
55
+ execute("insert into composite_primary_key set id = 1002, shop_id = 1")
56
+ execute("insert into composite_primary_key_dest set id = 1002, shop_id = 1")
57
+
58
+ Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
59
+
60
+ slave do
61
+ count_all(destination.name).must_equal(2)
62
+ end
63
+ end
64
+
65
+ it 'should copy and raise on unexpected warnings' do
66
+ origin = table_create(:custom_primary_key)
67
+ destination = table_create(:custom_primary_key_dest)
68
+ migration = Lhm::Migration.new(origin, destination)
69
+
70
+ execute("insert into custom_primary_key set id = 1001, pk = 1")
71
+ execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
72
+
73
+ exception = assert_raises(Lhm::Error) do
74
+ Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
75
+ end
76
+
77
+ assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'", exception.message
78
+ end
79
+
80
+ it 'should copy and warn on unexpected warnings by default' do
81
+ origin = table_create(:custom_primary_key)
82
+ destination = table_create(:custom_primary_key_dest)
83
+ migration = Lhm::Migration.new(origin, destination)
84
+
85
+ execute("insert into custom_primary_key set id = 1001, pk = 1")
86
+ execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
87
+
88
+ Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
89
+
90
+ assert_equal 2, log_messages.length
91
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
92
+ end
93
+
94
+ it 'should log two times for two unexpected warnings' do
95
+ origin = table_create(:custom_primary_key)
96
+ destination = table_create(:custom_primary_key_dest)
97
+ migration = Lhm::Migration.new(origin, destination)
98
+
99
+ execute("insert into custom_primary_key set id = 1001, pk = 1")
100
+ execute("insert into custom_primary_key set id = 1002, pk = 2")
101
+ execute("insert into custom_primary_key_dest set id = 1001, pk = 3")
102
+ execute("insert into custom_primary_key_dest set id = 1002, pk = 4")
103
+
104
+ Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
105
+
106
+ assert_equal 3, log_messages.length
107
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
108
+ assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key 'index_custom_primary_key_on_id'"), log_messages
109
+ end
110
+
111
+ it 'should copy and warn on unexpected warnings' do
112
+ origin = table_create(:custom_primary_key)
113
+ destination = table_create(:custom_primary_key_dest)
114
+ migration = Lhm::Migration.new(origin, destination)
115
+
116
+ execute("insert into custom_primary_key set id = 1001, pk = 1")
117
+ execute("insert into custom_primary_key_dest set id = 1001, pk = 2")
118
+
119
+ Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer} ).run
120
+
121
+ assert_equal 2, log_messages.length
122
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'"), log_messages
123
+ end
124
+
35
125
  it 'should create the modified destination, even if the source is empty' do
36
126
  execute("truncate origin ")
37
- printer = Lhm::Printer::Base.new
38
127
 
39
- def printer.notify(*) ;end
40
- def printer.end(*) [] ;end
41
-
42
- Lhm::Chunker.new(@migration, connection, {:throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer} ).run
128
+ Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
43
129
 
44
130
  slave do
45
131
  count_all(@destination.name).must_equal(0)
@@ -55,7 +141,7 @@ describe Lhm::Chunker do
55
141
  printer.expect(:end, :return_value, [])
56
142
 
57
143
  Lhm::Chunker.new(
58
- @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 100), :printer => printer }
144
+ @migration, connection, { throttler: throttler, printer: printer }
59
145
  ).run
60
146
 
61
147
  slave do
@@ -69,13 +155,9 @@ describe Lhm::Chunker do
69
155
  it 'should copy all the records of a table, even if the last chunk starts with the last record of it.' do
70
156
  11.times { |n| execute("insert into origin set id = '#{ n + 1 }'") }
71
157
 
72
- printer = Lhm::Printer::Base.new
73
-
74
- def printer.notify(*) ;end
75
- def printer.end(*) [] ;end
76
158
 
77
159
  Lhm::Chunker.new(
78
- @migration, connection, { :throttler => Lhm::Throttler::Time.new(:stride => 10), :printer => printer }
160
+ @migration, connection, { throttler: Lhm::Throttler::Time.new(stride: 10), printer: printer }
79
161
  ).run
80
162
 
81
163
  slave do
@@ -92,7 +174,7 @@ describe Lhm::Chunker do
92
174
  printer.expect(:end, :return_value, [])
93
175
 
94
176
  Lhm::Chunker.new(
95
- @migration, connection, { :throttler => Lhm::Throttler::SlaveLag.new(:stride => 100), :printer => printer }
177
+ @migration, connection, { throttler: Lhm::Throttler::SlaveLag.new(stride: 100), printer: printer }
96
178
  ).run
97
179
 
98
180
  slave do
@@ -109,13 +191,13 @@ describe Lhm::Chunker do
109
191
  printer.expects(:notify).with(instance_of(Integer), instance_of(Integer)).twice
110
192
  printer.expects(:end)
111
193
 
112
- throttler = Lhm::Throttler::SlaveLag.new(:stride => 10, :allowed_lag => 0)
194
+ throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
113
195
  def throttler.max_current_slave_lag
114
196
  1
115
197
  end
116
198
 
117
199
  Lhm::Chunker.new(
118
- @migration, connection, { :throttler => throttler, :printer => printer }
200
+ @migration, connection, { throttler: throttler, printer: printer }
119
201
  ).run
120
202
 
121
203
  assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT * 2 * 2, throttler.timeout_seconds)
@@ -133,7 +215,7 @@ describe Lhm::Chunker do
133
215
  printer.expects(:verify)
134
216
  printer.expects(:end)
135
217
 
136
- throttler = Lhm::Throttler::SlaveLag.new(:stride => 10, :allowed_lag => 0)
218
+ throttler = Lhm::Throttler::SlaveLag.new(stride: 10, allowed_lag: 0)
137
219
 
138
220
  def throttler.slave_hosts
139
221
  ['127.0.0.1']
@@ -149,7 +231,7 @@ describe Lhm::Chunker do
149
231
  end
150
232
 
151
233
  Lhm::Chunker.new(
152
- @migration, connection, { :throttler => throttler, :printer => printer }
234
+ @migration, connection, { throttler: throttler, printer: printer }
153
235
  ).run
154
236
 
155
237
  assert_equal(Lhm::Throttler::SlaveLag::INITIAL_TIMEOUT, throttler.timeout_seconds)
@@ -171,7 +253,7 @@ describe Lhm::Chunker do
171
253
 
172
254
  exception = assert_raises do
173
255
  Lhm::Chunker.new(
174
- @migration, connection, { :verifier => failer, :printer => printer, :throttler => Lhm::Throttler::Time.new(:stride => 100) }
256
+ @migration, connection, { verifier: failer, printer: printer, throttler: throttler }
175
257
  ).run
176
258
  end
177
259
 
data/spec/test_helper.rb CHANGED
@@ -30,3 +30,16 @@ def without_verbose(&block)
30
30
  ensure
31
31
  $VERBOSE = old_verbose
32
32
  end
33
+
34
+ def printer
35
+ printer = Lhm::Printer::Base.new
36
+
37
+ def printer.notify(*) ;end
38
+ def printer.end(*) [] ;end
39
+
40
+ printer
41
+ end
42
+
43
+ def throttler
44
+ Lhm::Throttler::Time.new(:stride => 100)
45
+ end
@@ -41,6 +41,7 @@ describe Lhm::Chunker do
41
41
  @connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/)).returns(21)
42
42
  @connection.expects(:update).with(regexp_matches(/between 1 and 7/)).returns(2)
43
43
  @connection.expects(:update).with(regexp_matches(/between 8 and 10/)).returns(2)
44
+ @connection.expects(:query).twice.with(regexp_matches(/show warnings/)).returns([])
44
45
 
45
46
  @chunker.run
46
47
  end
@@ -88,6 +89,8 @@ describe Lhm::Chunker do
88
89
  @connection.expects(:update).with(regexp_matches(/between 6 and 8/)).returns(2)
89
90
  @connection.expects(:update).with(regexp_matches(/between 9 and 10/)).returns(2)
90
91
 
92
+ @connection.expects(:query).twice.with(regexp_matches(/show warnings/)).returns([])
93
+
91
94
  @chunker.run
92
95
  end
93
96
 
@@ -136,6 +139,7 @@ describe Lhm::Chunker do
136
139
 
137
140
  @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
138
141
  @connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/)).returns(1)
142
+ @connection.expects(:query).with(regexp_matches(/show warnings/)).returns([])
139
143
 
140
144
  def @migration.conditions
141
145
  "where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
@@ -155,6 +159,7 @@ describe Lhm::Chunker do
155
159
 
156
160
  @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/)).returns(2)
157
161
  @connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/)).returns(1)
162
+ @connection.expects(:query).with(regexp_matches(/show warnings/)).returns([])
158
163
 
159
164
  def @migration.conditions
160
165
  'inner join bar on foo.id = bar.foo_id'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lhm-shopify
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.6
4
+ version: 3.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SoundCloud
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2021-07-08 00:00:00.000000000 Z
15
+ date: 2021-07-19 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: retriable
@@ -173,7 +173,9 @@ files:
173
173
  - spec/README.md
174
174
  - spec/fixtures/bigint_table.ddl
175
175
  - spec/fixtures/composite_primary_key.ddl
176
+ - spec/fixtures/composite_primary_key_dest.ddl
176
177
  - spec/fixtures/custom_primary_key.ddl
178
+ - spec/fixtures/custom_primary_key_dest.ddl
177
179
  - spec/fixtures/destination.ddl
178
180
  - spec/fixtures/lines.ddl
179
181
  - spec/fixtures/origin.ddl