lhm-shopify 3.3.6 → 3.4.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 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