lhm-shopify 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: "../"