lhm-shopify 4.0.0 → 4.1.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +20 -18
  3. data/Appraisals +5 -11
  4. data/CHANGELOG.md +6 -0
  5. data/Gemfile.lock +22 -7
  6. data/README.md +7 -7
  7. data/dev.yml +4 -1
  8. data/docker-compose-mysql-5.7.yml +1 -0
  9. data/docker-compose-mysql-8.0.yml +63 -0
  10. data/docker-compose.yml +3 -3
  11. data/gemfiles/activerecord_6.1.gemfile +1 -0
  12. data/gemfiles/activerecord_6.1.gemfile.lock +8 -2
  13. data/gemfiles/activerecord_7.0.gemfile +1 -0
  14. data/gemfiles/activerecord_7.0.gemfile.lock +7 -1
  15. data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +1 -1
  16. data/gemfiles/{activerecord_7.1.0.beta1.gemfile.lock → activerecord_7.1.gemfile.lock} +10 -8
  17. data/lhm.gemspec +1 -0
  18. data/lib/lhm/atomic_switcher.rb +3 -3
  19. data/lib/lhm/chunker.rb +4 -4
  20. data/lib/lhm/connection.rb +9 -1
  21. data/lib/lhm/sql_retry.rb +36 -18
  22. data/lib/lhm/table.rb +3 -4
  23. data/lib/lhm/throttler/replica_lag.rb +17 -13
  24. data/lib/lhm/version.rb +1 -1
  25. data/scripts/helpers/wait-for-dbs.sh +3 -3
  26. data/scripts/mysql/writer/create_users.sql +1 -1
  27. data/spec/integration/atomic_switcher_spec.rb +4 -8
  28. data/spec/integration/chunker_spec.rb +21 -6
  29. data/spec/integration/database.yml +3 -3
  30. data/spec/integration/integration_helper.rb +11 -3
  31. data/spec/integration/lhm_spec.rb +29 -13
  32. data/spec/integration/proxysql_spec.rb +10 -10
  33. data/spec/integration/sql_retry/db_connection_helper.rb +2 -4
  34. data/spec/integration/sql_retry/lock_wait_spec.rb +7 -8
  35. data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +18 -10
  36. data/spec/integration/sql_retry/proxysql_helper.rb +1 -1
  37. data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +1 -2
  38. data/spec/integration/table_spec.rb +1 -1
  39. data/spec/test_helper.rb +27 -3
  40. data/spec/unit/atomic_switcher_spec.rb +2 -2
  41. data/spec/unit/chunker_spec.rb +43 -43
  42. data/spec/unit/connection_spec.rb +2 -2
  43. data/spec/unit/entangler_spec.rb +14 -24
  44. data/spec/unit/throttler/replica_lag_spec.rb +6 -14
  45. metadata +21 -8
  46. data/.travis.yml +0 -21
  47. data/gemfiles/activerecord_6.0.gemfile.lock +0 -71
  48. data/gemfiles/activerecord_7.1.0.beta1.gemfile +0 -7
data/lib/lhm/table.rb CHANGED
@@ -39,10 +39,9 @@ module Lhm
39
39
  end
40
40
 
41
41
  def ddl
42
- sql = "show create table `#{ @table_name }`"
43
- specification = nil
44
- @connection.execute(sql).each { |row| specification = row.last }
45
- specification
42
+ query = "SHOW CREATE TABLE #{ @connection.quote_table_name(@table_name) }"
43
+
44
+ @connection.select_one(query)["Create Table"]
46
45
  end
47
46
 
48
47
  def parse
@@ -91,6 +91,14 @@ module Lhm
91
91
 
92
92
  attr_reader :host, :connection
93
93
 
94
+ def self.client
95
+ defined?(Mysql2::Client) ? Mysql2::Client : Trilogy
96
+ end
97
+
98
+ def self.client_error
99
+ defined?(Mysql2::Error) ? Mysql2::Error : Trilogy::Error
100
+ end
101
+
94
102
  def initialize(host, connection_config = nil)
95
103
  @host = host
96
104
  @connection_config = prepare_connection_config(connection_config)
@@ -108,13 +116,11 @@ module Lhm
108
116
  private
109
117
 
110
118
  def client(config)
111
- begin
112
- Lhm.logger.info "Connecting to #{@host} on database: #{config[:database]}"
113
- Mysql2::Client.new(config)
114
- rescue Mysql2::Error => e
115
- Lhm.logger.info "Error connecting to #{@host}: #{e}"
116
- nil
117
- end
119
+ Lhm.logger.info "Connecting to #{@host} on database: #{config[:database]}"
120
+ self.class.client.new(config)
121
+ rescue self.class.client_error => e
122
+ Lhm.logger.info "Error connecting to #{@host}: #{e}"
123
+ nil
118
124
  end
119
125
 
120
126
  def prepare_connection_config(config_proc)
@@ -133,12 +139,10 @@ module Lhm
133
139
  end
134
140
 
135
141
  def query_connection(query, result)
136
- begin
137
- @connection.query(query).map { |row| row[result] }
138
- rescue Mysql2::Error => e
139
- Lhm.logger.info "Unable to connect and/or query #{host}: #{e}"
140
- [nil]
141
- end
142
+ @connection.query(query).map { |row| row[result] }
143
+ rescue self.class.client_error => e
144
+ Lhm.logger.info "Unable to connect and/or query #{host}: #{e}"
145
+ [nil]
142
146
  end
143
147
 
144
148
  private
data/lib/lhm/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Schmidt
3
3
 
4
4
  module Lhm
5
- VERSION = '4.0.0'
5
+ VERSION = '4.1.0'
6
6
  end
@@ -1,19 +1,19 @@
1
1
  #!/bin/bash
2
2
  # Wait for writer
3
3
  echo "Waiting for MySQL-1: "
4
- while ! (mysqladmin ping --host="127.0.0.1" --port=33006 --user=root --password=password --silent 2> /dev/null); do
4
+ while ! (mysqladmin ping --host="127.0.0.1" --port=13006 --user=root --password=password --silent 2> /dev/null); do
5
5
  echo -ne "."
6
6
  sleep 1
7
7
  done
8
8
  # Wait for reader
9
9
  echo "Waiting for MySQL-2: "
10
- while ! (mysqladmin ping --host="127.0.0.1" --port=33007 --user=root --password=password --silent 2> /dev/null); do
10
+ while ! (mysqladmin ping --host="127.0.0.1" --port=13007 --user=root --password=password --silent 2> /dev/null); do
11
11
  echo -ne "."
12
12
  sleep 1
13
13
  done
14
14
  # Wait for proxysql
15
15
  echo "Waiting for ProxySQL:"
16
- while ! (mysqladmin ping --host="127.0.0.1" --port=33005 --user=root --password=password --silent 2> /dev/null); do
16
+ while ! (mysqladmin ping --host="127.0.0.1" --port=13005 --user=root --password=password --silent 2> /dev/null); do
17
17
  echo -ne "."
18
18
  sleep 1
19
19
  done
@@ -3,4 +3,4 @@ CREATE USER IF NOT EXISTS 'writer'@'%' IDENTIFIED BY 'password';
3
3
  CREATE USER IF NOT EXISTS 'reader'@'%' IDENTIFIED BY 'password';
4
4
 
5
5
  CREATE USER IF NOT EXISTS 'replication'@'%' IDENTIFIED BY 'password';
6
- GRANT REPLICATION SLAVE ON *.* TO' replication'@'%' IDENTIFIED BY 'password';
6
+ GRANT REPLICATION SLAVE ON *.* TO' replication'@'%';
@@ -33,8 +33,8 @@ describe Lhm::AtomicSwitcher do
33
33
  ar_connection = mock()
34
34
  ar_connection.stubs(:data_source_exists?).returns(true)
35
35
  ar_connection.stubs(:active?).returns(true)
36
- ar_connection.stubs(:execute).returns([["dummy"]], [["dummy"]], [["dummy"]])
37
- .then
36
+ ar_connection.stubs(:select_value).returns("dummy")
37
+ ar_connection.stubs(:execute)
38
38
  .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
39
39
  .then
40
40
  .returns([["dummy"]]) # Matches initial host -> triggers retry
@@ -62,16 +62,12 @@ describe Lhm::AtomicSwitcher do
62
62
  ar_connection = mock()
63
63
  ar_connection.stubs(:data_source_exists?).returns(true)
64
64
  ar_connection.stubs(:active?).returns(true)
65
- ar_connection.stubs(:execute).returns([["dummy"]], [["dummy"]], [["dummy"]])
66
- .then
65
+ ar_connection.stubs(:select_value).returns("dummy")
66
+ ar_connection.stubs(:execute)
67
67
  .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
68
68
  .then
69
- .returns([["dummy"]]) # triggers retry 1
70
- .then
71
69
  .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.')
72
70
  .then
73
- .returns([["dummy"]]) # triggers retry 2
74
- .then
75
71
  .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction.') # triggers retry 2
76
72
 
77
73
  connection = Lhm::Connection.new(connection: ar_connection, options: {
@@ -74,7 +74,8 @@ describe Lhm::Chunker do
74
74
  Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
75
75
  end
76
76
 
77
- assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key 'index_custom_primary_key_on_id'", exception.message
77
+ error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
78
+ assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'", exception.message
78
79
  end
79
80
 
80
81
  it 'should copy and warn on unexpected warnings by default' do
@@ -87,8 +88,10 @@ describe Lhm::Chunker do
87
88
 
88
89
  Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
89
90
 
91
+ error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
92
+
90
93
  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
94
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
92
95
  end
93
96
 
94
97
  it 'should log two times for two unexpected warnings' do
@@ -103,9 +106,11 @@ describe Lhm::Chunker do
103
106
 
104
107
  Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
105
108
 
109
+ error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
110
+
106
111
  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
112
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
113
+ assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key '#{error_key}'"), log_messages
109
114
  end
110
115
 
111
116
  it 'should copy and warn on unexpected warnings' do
@@ -118,8 +123,10 @@ describe Lhm::Chunker do
118
123
 
119
124
  Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer} ).run
120
125
 
126
+ error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
127
+
121
128
  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
129
+ assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
123
130
  end
124
131
 
125
132
  it 'should create the modified destination, even if the source is empty' do
@@ -222,7 +229,7 @@ describe Lhm::Chunker do
222
229
  def throttler.replica_connection(replica)
223
230
  config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
224
231
  config[:host] = replica
225
- config[:port] = 33007
232
+ config[:port] = 13007
226
233
  ActiveRecord::Base.send('mysql2_connection', config)
227
234
  end
228
235
  end
@@ -261,4 +268,12 @@ describe Lhm::Chunker do
261
268
  end
262
269
  end
263
270
  end
271
+
272
+ def index_key(table_name, index_name)
273
+ if mysql_version.start_with?("8")
274
+ "#{table_name}.#{index_name}"
275
+ else
276
+ index_name
277
+ end
278
+ end
264
279
  end
@@ -2,17 +2,17 @@ master:
2
2
  host: mysql-1
3
3
  user: root
4
4
  password: password
5
- port: 33006
5
+ port: 13006
6
6
  replica:
7
7
  host: mysql-2
8
8
  user: root
9
9
  password: password
10
- port: 33007
10
+ port: 13007
11
11
  proxysql:
12
12
  host: proxysql
13
13
  user: root
14
14
  password: password
15
- port: 33005
15
+ port: 13005
16
16
  master_toxic:
17
17
  host: toxiproxy
18
18
  user: root
@@ -22,7 +22,7 @@ module IntegrationHelper
22
22
  def self.included(base)
23
23
  base.after(:each) do
24
24
  cleanup_connection = new_mysql_connection
25
- results = cleanup_connection.query("SELECT table_name FROM information_schema.tables WHERE table_schema = '#{$db_name}';")
25
+ results = DATABASE.query(cleanup_connection, "SELECT table_name FROM information_schema.tables WHERE table_schema = '#{$db_name}';")
26
26
  table_names_for_cleanup = results.map { |row| "#{$db_name}." + row.values.first }
27
27
  cleanup_connection.query("DROP TABLE IF EXISTS #{table_names_for_cleanup.join(', ')};") if table_names_for_cleanup.length > 0
28
28
  end
@@ -35,6 +35,14 @@ module IntegrationHelper
35
35
  @connection
36
36
  end
37
37
 
38
+ def mysql_version
39
+ @mysql_version ||= begin
40
+ # This SQL returns a value of shape: X.Y.ZZ-AA-log
41
+ result = connection.query("SELECT VERSION()")
42
+ result.dig(0, 0).split("-", 2)[0]
43
+ end
44
+ end
45
+
38
46
  def connect_proxysql!
39
47
  connect!(
40
48
  '127.0.0.1',
@@ -81,7 +89,7 @@ module IntegrationHelper
81
89
 
82
90
  def ar_conn(host, port, user, password)
83
91
  ActiveRecord::Base.establish_connection(
84
- :adapter => 'mysql2',
92
+ :adapter => DATABASE.adapter,
85
93
  :host => host,
86
94
  :username => user,
87
95
  :port => port,
@@ -171,7 +179,7 @@ module IntegrationHelper
171
179
  end
172
180
 
173
181
  def new_mysql_connection(role='master')
174
- Mysql2::Client.new(
182
+ DATABASE.client.new(
175
183
  host: '127.0.0.1',
176
184
  database: $db_name,
177
185
  username: $db_config[role]['user'],
@@ -9,6 +9,10 @@ describe Lhm do
9
9
 
10
10
  before(:each) { connect_master!; Lhm.cleanup(true) }
11
11
 
12
+ let(:collation) do
13
+ mysql_version.start_with?("8.0") ? "utf8mb3_general_ci" : "utf8_general_ci"
14
+ end
15
+
12
16
  describe 'id column requirement' do
13
17
  it 'should migrate the table when id is pk' do
14
18
  table_create(:users)
@@ -17,9 +21,11 @@ describe Lhm do
17
21
  t.add_column(:logins, "int(12) default '0'")
18
22
  end
19
23
 
24
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
25
+
20
26
  replica do
21
27
  value(table_read(:users).columns['logins']).must_equal({
22
- :type => 'int(12)',
28
+ :type => expected_type,
23
29
  :is_nullable => 'YES',
24
30
  :column_default => '0',
25
31
  :comment => '',
@@ -35,9 +41,11 @@ describe Lhm do
35
41
  t.add_column(:logins, "int(12) default '0'")
36
42
  end
37
43
 
44
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
45
+
38
46
  replica do
39
47
  value(table_read(:custom_primary_key).columns['logins']).must_equal({
40
- :type => 'int(12)',
48
+ :type => expected_type,
41
49
  :is_nullable => 'YES',
42
50
  :column_default => '0',
43
51
  :comment => '',
@@ -53,9 +61,11 @@ describe Lhm do
53
61
  t.add_column(:logins, "int(12) default '0'")
54
62
  end
55
63
 
64
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
65
+
56
66
  replica do
57
67
  value(table_read(:composite_primary_key).columns['logins']).must_equal({
58
- :type => 'int(12)',
68
+ :type => expected_type,
59
69
  :is_nullable => 'YES',
60
70
  :column_default => '0',
61
71
  :comment => '',
@@ -132,9 +142,11 @@ describe Lhm do
132
142
  t.add_column(:logins, "INT(12) DEFAULT '0'")
133
143
  end
134
144
 
145
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(12)"
146
+
135
147
  replica do
136
148
  value(table_read(:users).columns['logins']).must_equal({
137
- :type => 'int(12)',
149
+ :type => expected_type,
138
150
  :is_nullable => 'YES',
139
151
  :column_default => '0',
140
152
  :comment => '',
@@ -272,7 +284,7 @@ describe Lhm do
272
284
  :is_nullable => 'NO',
273
285
  :column_default => 'none',
274
286
  :comment => '',
275
- :collate => 'utf8_general_ci',
287
+ :collate => collation,
276
288
  })
277
289
  end
278
290
  end
@@ -284,9 +296,11 @@ describe Lhm do
284
296
  t.change_column(:id, 'int(5)')
285
297
  end
286
298
 
299
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(5)"
300
+
287
301
  replica do
288
302
  value(table_read(:small_table).columns['id']).must_equal({
289
- :type => 'int(5)',
303
+ :type => expected_type,
290
304
  :is_nullable => 'NO',
291
305
  :column_default => nil,
292
306
  :comment => '',
@@ -311,7 +325,7 @@ describe Lhm do
311
325
  :is_nullable => 'YES',
312
326
  :column_default => nil,
313
327
  :comment => '',
314
- :collate => 'utf8_general_ci',
328
+ :collate => collation,
315
329
  })
316
330
 
317
331
  result = select_one('SELECT login from users')
@@ -336,7 +350,7 @@ describe Lhm do
336
350
  :is_nullable => 'YES',
337
351
  :column_default => 'Superfriends',
338
352
  :comment => '',
339
- :collate => 'utf8_general_ci',
353
+ :collate => collation,
340
354
  })
341
355
 
342
356
  result = select_one('SELECT `fnord` from users')
@@ -383,11 +397,13 @@ describe Lhm do
383
397
  t.rename_column(:reference, :ref)
384
398
  end
385
399
 
400
+ expected_type = mysql_version.start_with?("8.0") ? "int" : "int(11)"
401
+
386
402
  replica do
387
403
  table_data = table_read(:users)
388
404
  assert_nil table_data.columns['reference']
389
405
  value(table_read(:users).columns['ref']).must_equal({
390
- :type => 'int(11)',
406
+ :type => expected_type,
391
407
  :is_nullable => 'YES',
392
408
  :column_default => nil,
393
409
  :comment => 'RefComment',
@@ -418,7 +434,7 @@ describe Lhm do
418
434
  :is_nullable => 'YES',
419
435
  :column_default => nil,
420
436
  :comment => '',
421
- :collate => 'utf8_general_ci',
437
+ :collate => collation,
422
438
  })
423
439
 
424
440
  result = select_one('SELECT `fnord` from users')
@@ -443,7 +459,7 @@ describe Lhm do
443
459
  :is_nullable => 'YES',
444
460
  :column_default => nil,
445
461
  :comment => '',
446
- :collate => 'utf8_general_ci',
462
+ :collate => collation,
447
463
  })
448
464
 
449
465
  result = select_one('SELECT `user_name` from users')
@@ -470,7 +486,7 @@ describe Lhm do
470
486
  :is_nullable => 'NO',
471
487
  :column_default => nil,
472
488
  :comment => '',
473
- :collate => 'utf8_general_ci',
489
+ :collate => collation,
474
490
  })
475
491
 
476
492
  result = select_one('SELECT `user_name` from users')
@@ -517,7 +533,7 @@ describe Lhm do
517
533
  :is_nullable => 'YES',
518
534
  :column_default => 'Superfriends',
519
535
  :comment => '',
520
- :collate => 'utf8_general_ci',
536
+ :collate => collation,
521
537
  })
522
538
  end
523
539
  end
@@ -1,34 +1,34 @@
1
1
  describe "ProxySQL integration" do
2
2
  it "Should contact the writer" do
3
- conn = Mysql2::Client.new(
3
+ conn = DATABASE.client.new(
4
4
  host: '127.0.0.1',
5
5
  username: "writer",
6
6
  password: "password",
7
- port: "33005",
7
+ port: "13005",
8
8
  )
9
9
 
10
- assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-1"
10
+ assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host").each.first["host"], "mysql-1"
11
11
  end
12
12
 
13
13
  it "Should contact the reader" do
14
- conn = Mysql2::Client.new(
14
+ conn = DATABASE.client.new(
15
15
  host: '127.0.0.1',
16
16
  username: "reader",
17
17
  password: "password",
18
- port: "33005",
18
+ port: "13005",
19
19
  )
20
20
 
21
- assert_equal conn.query("SELECT @@global.hostname as host").each.first["host"], "mysql-2"
21
+ assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host").each.first["host"], "mysql-2"
22
22
  end
23
23
 
24
24
  it "Should override default hostgroup from user if rule matches" do
25
- conn = Mysql2::Client.new(
25
+ conn = DATABASE.client.new(
26
26
  host: '127.0.0.1',
27
27
  username: "reader",
28
28
  password: "password",
29
- port: "33005",
29
+ port: "13005",
30
30
  )
31
31
 
32
- assert_equal conn.query("SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
32
+ assert_equal DATABASE.query(conn, "SELECT @@global.hostname as host #{Lhm::ProxySQLHelper::ANNOTATION}").each.first["host"], "mysql-1"
33
33
  end
34
- end
34
+ end
@@ -1,5 +1,4 @@
1
1
  require 'yaml'
2
- require 'mysql2'
3
2
 
4
3
  class DBConnectionHelper
5
4
 
@@ -11,12 +10,11 @@ class DBConnectionHelper
11
10
  end
12
11
 
13
12
  def new_mysql_connection(role = :master, with_data = false, toxic = false)
14
-
15
13
  key = role.to_s + toxic_postfix(toxic)
16
14
 
17
15
  conn = ActiveRecord::Base.establish_connection(
18
16
  :host => '127.0.0.1',
19
- :adapter => "mysql2",
17
+ :adapter => DATABASE.adapter,
20
18
  :username => db_config[key]['user'],
21
19
  :password => db_config[key]['password'],
22
20
  :database => test_db_name,
@@ -49,4 +47,4 @@ class DBConnectionHelper
49
47
  end
50
48
  end
51
49
  end
52
- end
50
+ end
@@ -1,5 +1,4 @@
1
1
  require 'minitest/autorun'
2
- require 'mysql2'
3
2
  require 'integration/sql_retry/lock_wait_timeout_test_helper'
4
3
  require 'lhm'
5
4
 
@@ -22,7 +21,7 @@ describe Lhm::SqlRetry do
22
21
  # Assert our pre-conditions
23
22
  assert_equal 2, @helper.record_count
24
23
 
25
- Mysql2::Client.any_instance.stubs(:active?).returns(true)
24
+ DATABASE.client.any_instance.stubs(:active?).returns(true)
26
25
  end
27
26
 
28
27
  after(:each) do
@@ -43,8 +42,8 @@ describe Lhm::SqlRetry do
43
42
 
44
43
  exception = assert_raises { @helper.trigger_wait_lock }
45
44
 
46
- assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
47
- assert_equal Mysql2::Error::TimeoutError, exception.class
45
+ assert_match Regexp.new("Lock wait timeout exceeded; try restarting transaction"), exception.message
46
+ assert_equal DATABASE.timeout_error, exception.class
48
47
 
49
48
  assert_equal 2, @helper.record_count # no records inserted
50
49
  puts "*" * 64
@@ -82,10 +81,10 @@ describe Lhm::SqlRetry do
82
81
  logs = @logger.string.split("\n")
83
82
  assert_equal 2, logs.length
84
83
 
85
- assert logs.first.include?("Mysql2::Error::TimeoutError: 'Lock wait timeout exceeded; try restarting transaction' - 1 tries")
84
+ assert logs.first.include?("Lock wait timeout exceeded; try restarting transaction' - 1 tries")
86
85
  assert logs.first.include?("0.2 seconds until the next try")
87
86
 
88
- assert logs.last.include?("Mysql2::Error::TimeoutError: 'Lock wait timeout exceeded; try restarting transaction' - 2 tries")
87
+ assert logs.last.include?("Lock wait timeout exceeded; try restarting transaction' - 2 tries")
89
88
  assert logs.last.include?("0.2 seconds until the next try")
90
89
  end
91
90
 
@@ -118,8 +117,8 @@ describe Lhm::SqlRetry do
118
117
 
119
118
  exception = assert_raises { @helper.trigger_wait_lock }
120
119
 
121
- assert_match /Lock wait timeout exceeded; try restarting transaction/, exception.message
122
- assert_equal Mysql2::Error::TimeoutError, exception.class
120
+ assert_match Regexp.new("Lock wait timeout exceeded; try restarting transaction"), exception.message
121
+ assert_equal DATABASE.timeout_error, exception.class
123
122
 
124
123
  assert_equal 2, @helper.record_count # no records inserted
125
124
  puts "*" * 64
@@ -14,13 +14,21 @@ class LockWaitTimeoutTestHelper
14
14
 
15
15
  @lock_duration = lock_duration
16
16
 
17
- # While implementing this, I discovered that MySQL seems to have an off-by-one
18
- # bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
19
- # In order to avoid surprisingly the user, let's account for that here, but also
20
- # guard against a case where we go below 1, the minimum value.
21
- raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
22
17
  raise ArgumentError, "innodb_lock_wait_timeout must be an integer" if innodb_lock_wait_timeout.class != Integer
23
- @innodb_lock_wait_timeout = innodb_lock_wait_timeout - 1
18
+
19
+ result = DATABASE.query(@main_conn, "SELECT VERSION()")
20
+ mysql_version = result.to_a.dig(0, "VERSION()").split("-", 2)[0]
21
+
22
+ if mysql_version.start_with?("8")
23
+ @innodb_lock_wait_timeout = innodb_lock_wait_timeout
24
+ else
25
+ # While implementing this, I discovered that MySQL seems to have an off-by-one
26
+ # bug with the innodb_lock_wait_timeout. If you ask it to wait 2 seconds, it will wait 3.
27
+ # In order to avoid surprisingly the user, let's account for that here, but also
28
+ # guard against a case where we go below 1, the minimum value.
29
+ raise ArgumentError, "innodb_lock_wait_timeout must be greater than or equal to 2" unless innodb_lock_wait_timeout >= 2
30
+ @innodb_lock_wait_timeout = innodb_lock_wait_timeout - 1
31
+ end
24
32
 
25
33
  @threads = []
26
34
  @queue = Queue.new
@@ -51,7 +59,7 @@ class LockWaitTimeoutTestHelper
51
59
  end
52
60
 
53
61
  def record_count(connection = main_conn)
54
- response = connection.query("SELECT COUNT(id) FROM #{test_table_name}")
62
+ response = mysql_exec(connection, "SELECT COUNT(id) FROM #{test_table_name}")
55
63
  response.first.values.first
56
64
  end
57
65
 
@@ -79,7 +87,7 @@ class LockWaitTimeoutTestHelper
79
87
  attr_reader :main_conn, :lock_duration, :innodb_lock_wait_timeout
80
88
 
81
89
  def new_mysql_connection
82
- Mysql2::Client.new(
90
+ DATABASE.client.new(
83
91
  host: '127.0.0.1',
84
92
  username: db_config['master']['user'],
85
93
  password: db_config['master']['password'],
@@ -103,8 +111,8 @@ class LockWaitTimeoutTestHelper
103
111
  private
104
112
 
105
113
  def mysql_exec(connection, statement)
106
- if connection.class == Mysql2::Client
107
- connection.query(statement)
114
+ if connection.class == DATABASE.client
115
+ DATABASE.query(connection, statement)
108
116
  elsif connection.class.to_s.include?("ActiveRecord")
109
117
  connection.execute(statement)
110
118
  else
@@ -2,7 +2,7 @@ class ProxySQLHelper
2
2
  class << self
3
3
  # Flips the destination hostgroup for /maintenance:lhm/ from 0 (i.e. writer) to 1 (i.e. reader)
4
4
  def with_lhm_hostgroup_flip
5
- conn = Mysql2::Client.new(
5
+ conn = DATABASE.client.new(
6
6
  host: '127.0.0.1',
7
7
  username: "remote-admin",
8
8
  password: "password",
@@ -1,5 +1,4 @@
1
1
  require 'minitest/autorun'
2
- require 'mysql2'
3
2
  require 'lhm'
4
3
  require 'toxiproxy'
5
4
 
@@ -50,7 +49,7 @@ describe Lhm::SqlRetry, "ProxiSQL tests for LHM retry" do
50
49
  end
51
50
  end
52
51
 
53
- assert_equal @connection.execute("Select * from #{DBConnectionHelper.test_table_name} WHERE id=2000").to_a.first.first, 2000
52
+ assert_equal 2000, @connection.select_one("SELECT * FROM #{DBConnectionHelper.test_table_name} WHERE id=2000")["id"]
54
53
 
55
54
  logs = @logger.string.split("\n")
56
55
 
@@ -23,7 +23,7 @@ describe Lhm::Table do
23
23
  end
24
24
 
25
25
  it 'should parse columns' do
26
- value(@table.columns['id'][:type]).must_match(/(bigint|int)\(\d+\)/)
26
+ value(@table.columns['id'][:type]).must_match(/(bigint|int)(\(\d+\))?/)
27
27
  end
28
28
 
29
29
  it 'should return true for method that should be renamed' do