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/spec/test_helper.rb CHANGED
@@ -14,6 +14,7 @@ require 'after_do'
14
14
  require 'byebug'
15
15
  require 'pathname'
16
16
  require 'lhm'
17
+ require 'active_record'
17
18
 
18
19
  $project = Pathname.new(File.dirname(__FILE__) + '/..').cleanpath
19
20
  $spec = $project.join('spec')
@@ -21,8 +22,31 @@ $fixtures = $spec.join('fixtures')
21
22
 
22
23
  $db_name = 'test'
23
24
 
24
- require 'active_record'
25
- require 'mysql2'
25
+ Database = Struct.new(:adapter, :client, :error_class, :timeout_error) do
26
+ def query(connection, sql)
27
+ results = connection.query(sql)
28
+ results = results.each_hash if adapter == "trilogy"
29
+ results
30
+ end
31
+ end
32
+
33
+ DATABASE =
34
+ case ENV['DATABASE_ADAPTER']
35
+ when 'trilogy'
36
+ require 'trilogy'
37
+
38
+ if ActiveRecord.version < ::Gem::Version.new('7.1.0')
39
+ require 'activerecord-trilogy-adapter'
40
+ require 'trilogy_adapter/connection'
41
+
42
+ ActiveRecord::Base.public_send :extend, TrilogyAdapter::Connection
43
+ end
44
+
45
+ Database.new('trilogy', Trilogy, Trilogy::BaseError, Trilogy::TimeoutError)
46
+ else
47
+ require 'mysql2'
48
+ Database.new('mysql2', Mysql2::Client, Mysql2::Error, Mysql2::Error::TimeoutError)
49
+ end
26
50
 
27
51
  logger = Logger.new STDOUT
28
52
  logger.level = Logger::WARN
@@ -53,7 +77,7 @@ end
53
77
 
54
78
  def init_test_db
55
79
  db_config = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + '/integration/database.yml')
56
- conn = Mysql2::Client.new(
80
+ conn = DATABASE.client.new(
57
81
  :host => '127.0.0.1',
58
82
  :username => db_config['master']['user'],
59
83
  :password => db_config['master']['password'],
@@ -21,8 +21,8 @@ describe Lhm::AtomicSwitcher do
21
21
  describe 'atomic switch' do
22
22
  it 'should perform a single atomic rename' do
23
23
  value(@switcher.atomic_switch).must_equal(
24
- "rename table `origin` to `#{ @migration.archive_name }`, " \
25
- '`destination` to `origin`'
24
+ "RENAME TABLE `origin` TO `#{ @migration.archive_name }`, " \
25
+ '`destination` TO `origin`'
26
26
  )
27
27
  end
28
28
  end
@@ -20,7 +20,7 @@ describe Lhm::Chunker do
20
20
  @destination = Lhm::Table.new('bar')
21
21
  @migration = Lhm::Migration.new(@origin, @destination)
22
22
  @connection = mock()
23
- @connection.stubs(:execute).returns([["dummy"]])
23
+ @connection.stubs(:select_value).returns("dummy")
24
24
  # This is a poor man's stub
25
25
  @throttler = Object.new
26
26
  def @throttler.run
@@ -42,11 +42,11 @@ describe Lhm::Chunker do
42
42
  5
43
43
  end
44
44
 
45
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 4/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
46
- @connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(21)
47
- @connection.expects(:update).with(regexp_matches(/between 1 and 7/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
48
- @connection.expects(:update).with(regexp_matches(/between 8 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
49
- @connection.expects(:execute).twice.with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
45
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 4/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
46
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 4/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(21)
47
+ @connection.expects(:update).with(regexp_matches(/between 1 and 7/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
48
+ @connection.expects(:update).with(regexp_matches(/between 8 and 10/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
49
+ @connection.expects(:select_all).twice.with(regexp_matches(/show warnings/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
50
50
 
51
51
  @chunker.run
52
52
  end
@@ -57,17 +57,17 @@ describe Lhm::Chunker do
57
57
  2
58
58
  end
59
59
 
60
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
61
- @connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(4)
62
- @connection.expects(:select_value).with(regexp_matches(/where id >= 5 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(6)
63
- @connection.expects(:select_value).with(regexp_matches(/where id >= 7 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
64
- @connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(10)
60
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
61
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(4)
62
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 5 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(6)
63
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 7 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
64
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(10)
65
65
 
66
- @connection.expects(:update).with(regexp_matches(/between 1 and 2/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
67
- @connection.expects(:update).with(regexp_matches(/between 3 and 4/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
68
- @connection.expects(:update).with(regexp_matches(/between 5 and 6/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
69
- @connection.expects(:update).with(regexp_matches(/between 7 and 8/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
70
- @connection.expects(:update).with(regexp_matches(/between 9 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
66
+ @connection.expects(:update).with(regexp_matches(/between 1 and 2/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
67
+ @connection.expects(:update).with(regexp_matches(/between 3 and 4/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
68
+ @connection.expects(:update).with(regexp_matches(/between 5 and 6/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
69
+ @connection.expects(:update).with(regexp_matches(/between 7 and 8/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
70
+ @connection.expects(:update).with(regexp_matches(/between 9 and 10/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
71
71
 
72
72
  @chunker.run
73
73
  end
@@ -84,17 +84,17 @@ describe Lhm::Chunker do
84
84
  end
85
85
  end
86
86
 
87
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
88
- @connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
89
- @connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
90
- @connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 2/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
87
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
88
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 3 order by id limit 1 offset 2/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
89
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 2/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(8)
90
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 9 order by id limit 1 offset 2/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
91
91
 
92
- @connection.expects(:update).with(regexp_matches(/between 1 and 2/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
93
- @connection.expects(:update).with(regexp_matches(/between 3 and 5/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
94
- @connection.expects(:update).with(regexp_matches(/between 6 and 8/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
95
- @connection.expects(:update).with(regexp_matches(/between 9 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
92
+ @connection.expects(:update).with(regexp_matches(/between 1 and 2/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
93
+ @connection.expects(:update).with(regexp_matches(/between 3 and 5/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
94
+ @connection.expects(:update).with(regexp_matches(/between 6 and 8/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
95
+ @connection.expects(:update).with(regexp_matches(/between 9 and 10/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
96
96
 
97
- @connection.expects(:execute).twice.with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
97
+ @connection.expects(:select_all).twice.with(regexp_matches(/show warnings/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
98
98
 
99
99
  @chunker.run
100
100
  end
@@ -104,8 +104,8 @@ describe Lhm::Chunker do
104
104
  :start => 1,
105
105
  :limit => 1)
106
106
 
107
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 0/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
108
- @connection.expects(:update).with(regexp_matches(/between 1 and 1/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
107
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 0/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
108
+ @connection.expects(:update).with(regexp_matches(/between 1 and 1/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
109
109
 
110
110
  @chunker.run
111
111
  end
@@ -118,17 +118,17 @@ describe Lhm::Chunker do
118
118
  2
119
119
  end
120
120
 
121
- @connection.expects(:select_value).with(regexp_matches(/where id >= 2 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(3)
122
- @connection.expects(:select_value).with(regexp_matches(/where id >= 4 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
123
- @connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
124
- @connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(9)
125
- @connection.expects(:select_value).with(regexp_matches(/where id >= 10 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
121
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 2 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(3)
122
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 4 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(5)
123
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 6 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(7)
124
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 8 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(9)
125
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 10 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(nil)
126
126
 
127
- @connection.expects(:update).with(regexp_matches(/between 2 and 3/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
128
- @connection.expects(:update).with(regexp_matches(/between 4 and 5/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
129
- @connection.expects(:update).with(regexp_matches(/between 6 and 7/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
130
- @connection.expects(:update).with(regexp_matches(/between 8 and 9/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
131
- @connection.expects(:update).with(regexp_matches(/between 10 and 10/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
127
+ @connection.expects(:update).with(regexp_matches(/between 2 and 3/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
128
+ @connection.expects(:update).with(regexp_matches(/between 4 and 5/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
129
+ @connection.expects(:update).with(regexp_matches(/between 6 and 7/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
130
+ @connection.expects(:update).with(regexp_matches(/between 8 and 9/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(2)
131
+ @connection.expects(:update).with(regexp_matches(/between 10 and 10/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
132
132
 
133
133
  @chunker.run
134
134
  end
@@ -142,9 +142,9 @@ describe Lhm::Chunker do
142
142
  2
143
143
  end
144
144
 
145
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
146
- @connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
147
- @connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
145
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
146
+ @connection.expects(:update).with(regexp_matches(/where \(foo.created_at > '2013-07-10' or foo.baz = 'quux'\) and `foo`/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
147
+ @connection.expects(:select_all).with(regexp_matches(/show warnings/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
148
148
 
149
149
  def @migration.conditions
150
150
  "where foo.created_at > '2013-07-10' or foo.baz = 'quux'"
@@ -162,9 +162,9 @@ describe Lhm::Chunker do
162
162
  2
163
163
  end
164
164
 
165
- @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/),EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
166
- @connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/),EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
167
- @connection.expects(:execute).with(regexp_matches(/show warnings/),EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
165
+ @connection.expects(:select_value).with(regexp_matches(/where id >= 1 order by id limit 1 offset 1/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns(2)
166
+ @connection.expects(:update).with(regexp_matches(/inner join bar on foo.id = bar.foo_id and/i), EXPECTED_RETRY_FLAGS_CHUNK_INSERT).returns(1)
167
+ @connection.expects(:select_all).with(regexp_matches(/show warnings/i), EXPECTED_RETRY_FLAGS_CHUNKER).returns([])
168
168
 
169
169
  def @migration.conditions
170
170
  'inner join bar on foo.id = bar.foo_id'
@@ -93,7 +93,7 @@ describe Lhm::Connection do
93
93
  it "Queries should be tagged with ProxySQL tag if reconnect_with_consistent_host is enabled" do
94
94
  ar_connection = mock()
95
95
  ar_connection.expects(:public_send).with(:select_value, "SHOW TABLES #{Lhm::ProxySQLHelper::ANNOTATION}").returns("dummy")
96
- ar_connection.stubs(:execute).times(4).returns([["dummy"]])
96
+ ar_connection.stubs(:select_value).times(4).returns("dummy")
97
97
  ar_connection.stubs(:active?).returns(true)
98
98
 
99
99
  connection = Lhm::Connection.new(connection: ar_connection, options: {
@@ -108,4 +108,4 @@ describe Lhm::Connection do
108
108
 
109
109
  assert_equal val, "dummy"
110
110
  end
111
- end
111
+ end
@@ -63,10 +63,9 @@ describe Lhm::Entangler do
63
63
  it 'should retry trigger creation when it hits a lock wait timeout' do
64
64
  tries = 1
65
65
  ar_connection = mock()
66
+ ar_connection.stubs(:select_value).returns("dummy")
66
67
  ar_connection.stubs(:execute)
67
- .returns([["dummy"]], [["dummy"]], [["dummy"]])
68
- .then
69
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
68
+ .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction')
70
69
  ar_connection.stubs(:active?).returns(true)
71
70
 
72
71
  connection = Lhm::Connection.new(connection: ar_connection, options: {
@@ -79,15 +78,14 @@ describe Lhm::Entangler do
79
78
 
80
79
  @entangler = Lhm::Entangler.new(@migration, connection)
81
80
 
82
- assert_raises(Mysql2::Error) { @entangler.before }
81
+ assert_raises(ActiveRecord::StatementInvalid) { @entangler.before }
83
82
  end
84
83
 
85
84
  it 'should not retry trigger creation with other mysql errors' do
86
85
  ar_connection = mock()
86
+ ar_connection.stubs(:select_value).returns("dummy")
87
87
  ar_connection.stubs(:execute)
88
- .returns([["dummy"]], [["dummy"]], [["dummy"]])
89
- .then
90
- .raises(Mysql2::Error, 'The MySQL server is running with the --read-only option so it cannot execute this statement.')
88
+ .raises(DATABASE.error_class, 'The MySQL server is running with the --read-only option so it cannot execute this statement.')
91
89
  ar_connection.stubs(:active?).returns(true)
92
90
  connection = Lhm::Connection.new(connection: ar_connection, options: {
93
91
  reconnect_with_consistent_host: true,
@@ -97,15 +95,14 @@ describe Lhm::Entangler do
97
95
  })
98
96
 
99
97
  @entangler = Lhm::Entangler.new(@migration, connection)
100
- assert_raises(Mysql2::Error) { @entangler.before }
98
+ assert_raises(DATABASE.error_class) { @entangler.before }
101
99
  end
102
100
 
103
101
  it 'should succesfully finish after retrying' do
104
102
  ar_connection = mock()
103
+ ar_connection.stubs(:select_value).returns("dummy")
105
104
  ar_connection.stubs(:execute)
106
- .returns([["dummy"]], [["dummy"]], [["dummy"]])
107
- .then
108
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
105
+ .raises(ActiveRecord::StatementInvalid, 'Lock wait timeout exceeded; try restarting transaction')
109
106
  .then
110
107
  .returns([["dummy"]])
111
108
  ar_connection.stubs(:active?).returns(true)
@@ -124,22 +121,15 @@ describe Lhm::Entangler do
124
121
 
125
122
  it 'should retry as many times as specified by configuration' do
126
123
  ar_connection = mock()
124
+ ar_connection.stubs(:select_value).returns("dummy")
127
125
  ar_connection.stubs(:execute)
128
- .returns([["dummy"]], [["dummy"]], [["dummy"]]) # initial
129
- .then
130
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
131
- .then
132
- .returns([["dummy"]]) # reconnect 1
133
- .then
134
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
135
- .then
136
- .returns([["dummy"]]) # reconnect 2
126
+ .raises(DATABASE.error_class, 'Lock wait timeout exceeded; try restarting transaction')
137
127
  .then
138
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction')
128
+ .raises(DATABASE.error_class, 'Lock wait timeout exceeded; try restarting transaction')
139
129
  .then
140
- .returns([["dummy"]]) # reconnect 3
130
+ .raises(DATABASE.error_class, 'Lock wait timeout exceeded; try restarting transaction')
141
131
  .then
142
- .raises(Mysql2::Error, 'Lock wait timeout exceeded; try restarting transaction') # final error
132
+ .raises(DATABASE.error_class, 'Lock wait timeout exceeded; try restarting transaction') # final error
143
133
  ar_connection.stubs(:active?).returns(true)
144
134
 
145
135
  connection = Lhm::Connection.new(connection: ar_connection, options: {
@@ -152,7 +142,7 @@ describe Lhm::Entangler do
152
142
 
153
143
  @entangler = Lhm::Entangler.new(@migration, connection)
154
144
 
155
- assert_raises(Mysql2::Error) { @entangler.before }
145
+ assert_raises(DATABASE.error_class) { @entangler.before }
156
146
  end
157
147
 
158
148
  describe 'super long table names' do
@@ -43,14 +43,6 @@ describe Lhm::Throttler::Replica do
43
43
  end
44
44
 
45
45
  describe "#client" do
46
- before do
47
- class TestMysql2Client
48
- def initialize(config)
49
- raise Mysql2::Error.new("connection error")
50
- end
51
- end
52
- end
53
-
54
46
  describe 'on connection error' do
55
47
  it 'logs and returns nil' do
56
48
  assert_nil(Lhm::Throttler::Replica.new('replica', @dummy_mysql_client_config).connection)
@@ -58,14 +50,14 @@ describe Lhm::Throttler::Replica do
58
50
  log_messages = @logs.string.lines
59
51
  assert_equal(2, log_messages.length)
60
52
  assert log_messages[0].include? "Connecting to replica on database: db"
61
- assert log_messages[1].include? "Error connecting to replica: Unknown MySQL server host 'replica'"
53
+ assert log_messages[1].include? "Error connecting to replica"
62
54
  end
63
55
  end
64
56
 
65
57
  describe 'with proper config' do
66
- it "creates a new Mysql2::Client" do
58
+ it "creates a new database client" do
67
59
  expected_config = { username: 'user', password: 'pw', database: 'db', host: 'replica' }
68
- Mysql2::Client.stubs(:new).with(expected_config).returns(mock())
60
+ DATABASE.client.stubs(:new).with(expected_config).returns(mock())
69
61
 
70
62
  assert Lhm::Throttler::Replica.new('replica', @dummy_mysql_client_config).connection
71
63
  end
@@ -80,7 +72,7 @@ describe Lhm::Throttler::Replica do
80
72
  ActiveRecord::Base.stubs(:connection_pool).returns(stub(spec: stub(config: active_record_config)))
81
73
  end
82
74
 
83
- Mysql2::Client.stubs(:new).returns(mock())
75
+ DATABASE.client.stubs(:new).returns(mock())
84
76
 
85
77
  assert Lhm::Throttler::Replica.new('replica').connection
86
78
 
@@ -137,7 +129,7 @@ describe Lhm::Throttler::Replica do
137
129
  describe "#lag on connection error" do
138
130
  it "logs and returns 0 replica lag" do
139
131
  client = mock()
140
- client.stubs(:query).raises(Mysql2::Error, "Can't connect to MySQL server")
132
+ client.stubs(:query).raises(DATABASE.error_class, "Can't connect to MySQL server")
141
133
  Lhm::Throttler::Replica.any_instance.stubs(:client).returns(client)
142
134
  Lhm::Throttler::Replica.any_instance.stubs(:config).returns([])
143
135
 
@@ -224,7 +216,7 @@ describe Lhm::Throttler::ReplicaLag do
224
216
  describe 'with MySQL stopped on the replica' do
225
217
  it 'assumes 0 replica lag' do
226
218
  client = mock()
227
- client.stubs(:query).raises(Mysql2::Error, "Can't connect to MySQL server")
219
+ client.stubs(:query).raises(DATABASE.error_class, "Can't connect to MySQL server")
228
220
  Lhm::Throttler::Replica.any_instance.stubs(:client).returns(client)
229
221
 
230
222
  Lhm::Throttler::Replica.any_instance.stubs(:prepare_connection_config).returns([])
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: 4.0.0
4
+ version: 4.1.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: 2023-09-18 00:00:00.000000000 Z
15
+ date: 2023-10-12 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: retriable
@@ -112,6 +112,20 @@ dependencies:
112
112
  - - ">="
113
113
  - !ruby/object:Gem::Version
114
114
  version: '0'
115
+ - !ruby/object:Gem::Dependency
116
+ name: trilogy
117
+ requirement: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
115
129
  - !ruby/object:Gem::Dependency
116
130
  name: simplecov
117
131
  requirement: !ruby/object:Gem::Requirement
@@ -179,7 +193,6 @@ files:
179
193
  - ".github/workflows/test.yml"
180
194
  - ".gitignore"
181
195
  - ".rubocop.yml"
182
- - ".travis.yml"
183
196
  - Appraisals
184
197
  - CHANGELOG.md
185
198
  - Gemfile
@@ -188,15 +201,15 @@ files:
188
201
  - README.md
189
202
  - Rakefile
190
203
  - dev.yml
204
+ - docker-compose-mysql-5.7.yml
205
+ - docker-compose-mysql-8.0.yml
191
206
  - docker-compose.yml
192
- - gemfiles/activerecord_6.0.gemfile
193
- - gemfiles/activerecord_6.0.gemfile.lock
194
207
  - gemfiles/activerecord_6.1.gemfile
195
208
  - gemfiles/activerecord_6.1.gemfile.lock
196
209
  - gemfiles/activerecord_7.0.gemfile
197
210
  - gemfiles/activerecord_7.0.gemfile.lock
198
- - gemfiles/activerecord_7.1.0.beta1.gemfile
199
- - gemfiles/activerecord_7.1.0.beta1.gemfile.lock
211
+ - gemfiles/activerecord_7.1.gemfile
212
+ - gemfiles/activerecord_7.1.gemfile.lock
200
213
  - lhm.gemspec
201
214
  - lib/lhm-shopify.rb
202
215
  - lib/lhm.rb
@@ -307,7 +320,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
307
320
  - !ruby/object:Gem::Version
308
321
  version: '0'
309
322
  requirements: []
310
- rubygems_version: 3.4.19
323
+ rubygems_version: 3.4.20
311
324
  signing_key:
312
325
  specification_version: 4
313
326
  summary: online schema changer for mysql
data/.travis.yml DELETED
@@ -1,21 +0,0 @@
1
- language: ruby
2
- before_script:
3
- - "mysql -e 'create database lhm;'"
4
- rvm:
5
- - 2.0.0
6
- - 2.1
7
- - 2.2
8
- sudo: false
9
- gemfile:
10
- - gemfiles/ar-2.3_mysql.gemfile
11
- - gemfiles/ar-3.2_mysql.gemfile
12
- - gemfiles/ar-3.2_mysql2.gemfile
13
- - gemfiles/ar-4.0_mysql2.gemfile
14
- - gemfiles/ar-4.1_mysql2.gemfile
15
- - gemfiles/ar-4.2_mysql2.gemfile
16
- matrix:
17
- exclude:
18
- - rvm: 2.2
19
- gemfile: gemfiles/ar-3.2_mysql.gemfile
20
- - rvm: 2.2
21
- gemfile: gemfiles/ar-2.3_mysql.gemfile
@@ -1,71 +0,0 @@
1
- PATH
2
- remote: ..
3
- specs:
4
- lhm-shopify (4.0.0)
5
- retriable (>= 3.0.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activemodel (6.0.3)
11
- activesupport (= 6.0.3)
12
- activerecord (6.0.3)
13
- activemodel (= 6.0.3)
14
- activesupport (= 6.0.3)
15
- activesupport (6.0.3)
16
- concurrent-ruby (~> 1.0, >= 1.0.2)
17
- i18n (>= 0.7, < 2)
18
- minitest (~> 5.1)
19
- tzinfo (~> 1.1)
20
- zeitwerk (~> 2.2, >= 2.2.2)
21
- after_do (0.4.0)
22
- appraisal (2.5.0)
23
- bundler
24
- rake
25
- thor (>= 0.14.0)
26
- byebug (11.1.3)
27
- concurrent-ruby (1.2.2)
28
- docile (1.4.0)
29
- i18n (1.14.1)
30
- concurrent-ruby (~> 1.0)
31
- minitest (5.20.0)
32
- mocha (2.1.0)
33
- ruby2_keywords (>= 0.0.5)
34
- mysql2 (0.5.5)
35
- rake (13.0.6)
36
- retriable (3.1.2)
37
- ruby2_keywords (0.0.5)
38
- simplecov (0.22.0)
39
- docile (~> 1.1)
40
- simplecov-html (~> 0.11)
41
- simplecov_json_formatter (~> 0.1)
42
- simplecov-html (0.12.3)
43
- simplecov_json_formatter (0.1.4)
44
- thor (1.2.2)
45
- thread_safe (0.3.6)
46
- toxiproxy (2.0.2)
47
- tzinfo (1.2.11)
48
- thread_safe (~> 0.1)
49
- zeitwerk (2.6.11)
50
-
51
- PLATFORMS
52
- arm64-darwin-21
53
- arm64-darwin-22
54
- x86_64-darwin-20
55
- x86_64-linux
56
-
57
- DEPENDENCIES
58
- activerecord (= 6.0.3)
59
- after_do
60
- appraisal
61
- byebug
62
- lhm-shopify!
63
- minitest
64
- mocha
65
- mysql2
66
- rake
67
- simplecov
68
- toxiproxy
69
-
70
- BUNDLED WITH
71
- 2.2.22
@@ -1,7 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "7.1.0.beta1"
6
-
7
- gemspec path: "../"