lhm-shopify 4.0.0 → 4.1.1
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 +4 -4
- data/.github/workflows/test.yml +20 -18
- data/Appraisals +5 -11
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +22 -7
- data/README.md +7 -7
- data/dev.yml +4 -1
- data/docker-compose-mysql-5.7.yml +1 -0
- data/docker-compose-mysql-8.0.yml +63 -0
- data/docker-compose.yml +3 -3
- data/gemfiles/activerecord_6.1.gemfile +1 -0
- data/gemfiles/activerecord_6.1.gemfile.lock +8 -2
- data/gemfiles/activerecord_7.0.gemfile +1 -0
- data/gemfiles/activerecord_7.0.gemfile.lock +7 -1
- data/gemfiles/{activerecord_6.0.gemfile → activerecord_7.1.gemfile} +1 -1
- data/gemfiles/{activerecord_7.1.0.beta1.gemfile.lock → activerecord_7.1.gemfile.lock} +10 -8
- data/lhm.gemspec +1 -0
- data/lib/lhm/atomic_switcher.rb +3 -3
- data/lib/lhm/chunker.rb +8 -7
- data/lib/lhm/connection.rb +9 -1
- data/lib/lhm/sql_retry.rb +36 -18
- data/lib/lhm/table.rb +3 -4
- data/lib/lhm/throttler/replica_lag.rb +17 -13
- data/lib/lhm/version.rb +1 -1
- data/scripts/helpers/wait-for-dbs.sh +3 -3
- data/scripts/mysql/writer/create_users.sql +1 -1
- data/spec/integration/atomic_switcher_spec.rb +4 -8
- data/spec/integration/chunker_spec.rb +23 -9
- data/spec/integration/database.yml +3 -3
- data/spec/integration/integration_helper.rb +11 -3
- data/spec/integration/lhm_spec.rb +29 -13
- data/spec/integration/proxysql_spec.rb +10 -10
- data/spec/integration/sql_retry/db_connection_helper.rb +2 -4
- data/spec/integration/sql_retry/lock_wait_spec.rb +7 -8
- data/spec/integration/sql_retry/lock_wait_timeout_test_helper.rb +18 -10
- data/spec/integration/sql_retry/proxysql_helper.rb +1 -1
- data/spec/integration/sql_retry/retry_with_proxysql_spec.rb +1 -2
- data/spec/integration/table_spec.rb +1 -1
- data/spec/test_helper.rb +27 -3
- data/spec/unit/atomic_switcher_spec.rb +2 -2
- data/spec/unit/chunker_spec.rb +43 -43
- data/spec/unit/connection_spec.rb +2 -2
- data/spec/unit/entangler_spec.rb +14 -24
- data/spec/unit/throttler/replica_lag_spec.rb +6 -14
- metadata +21 -8
- data/.travis.yml +0 -21
- data/gemfiles/activerecord_6.0.gemfile.lock +0 -71
- data/gemfiles/activerecord_7.1.0.beta1.gemfile +0 -7
data/lib/lhm/sql_retry.rb
CHANGED
@@ -105,9 +105,7 @@ module Lhm
|
|
105
105
|
def mysql_single_value(name)
|
106
106
|
query = Lhm::ProxySQLHelper.tagged("SELECT #{name} LIMIT 1")
|
107
107
|
|
108
|
-
@connection.
|
109
|
-
return record&.first
|
110
|
-
end
|
108
|
+
@connection.select_value(query)
|
111
109
|
end
|
112
110
|
|
113
111
|
def same_host_as_initial?
|
@@ -140,32 +138,22 @@ module Lhm
|
|
140
138
|
log_with_prefix("Reconnected to wrong host. Started migration on: #{@initial_hostname} (server_id: #{@initial_server_id}), but reconnected to: #{hostname} (server_id: #{server_id}).", :error)
|
141
139
|
return false
|
142
140
|
end
|
143
|
-
rescue
|
141
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
144
142
|
# Retry if ActiveRecord cannot reach host
|
145
|
-
next
|
143
|
+
next
|
144
|
+
rescue StandardError => e
|
146
145
|
log_with_prefix("Encountered error: [#{e.class}] #{e.message}. Will stop reconnection procedure.", :info)
|
147
146
|
return false
|
148
147
|
end
|
149
148
|
end
|
149
|
+
|
150
150
|
false
|
151
151
|
end
|
152
152
|
|
153
153
|
# For a full list of configuration options see https://github.com/kamui/retriable
|
154
154
|
def default_retry_config
|
155
155
|
{
|
156
|
-
on:
|
157
|
-
StandardError => [
|
158
|
-
/Lock wait timeout exceeded/,
|
159
|
-
/Timeout waiting for a response from the last query/,
|
160
|
-
/Deadlock found when trying to get lock/,
|
161
|
-
/Query execution was interrupted/,
|
162
|
-
/Lost connection to MySQL server during query/,
|
163
|
-
/Max connect timeout reached/,
|
164
|
-
/Unknown MySQL server host/,
|
165
|
-
/connection is locked to hostgroup/,
|
166
|
-
/The MySQL server is running with the --read-only option so it cannot execute this statement/,
|
167
|
-
]
|
168
|
-
},
|
156
|
+
on: retriable_mysql2_errors || retriable_trilogy_errors,
|
169
157
|
multiplier: 1, # each successive interval grows by this factor
|
170
158
|
base_interval: 1, # the initial interval in seconds between tries.
|
171
159
|
tries: 20, # Number of attempts to make at running your code block (includes initial attempt).
|
@@ -176,5 +164,35 @@ module Lhm
|
|
176
164
|
end
|
177
165
|
}.freeze
|
178
166
|
end
|
167
|
+
|
168
|
+
def retriable_mysql2_errors
|
169
|
+
return unless defined?(Mysql2::Error)
|
170
|
+
|
171
|
+
{
|
172
|
+
StandardError => [
|
173
|
+
/Lock wait timeout exceeded/,
|
174
|
+
/Timeout waiting for a response from the last query/,
|
175
|
+
/Deadlock found when trying to get lock/,
|
176
|
+
/Query execution was interrupted/,
|
177
|
+
/Lost connection to MySQL server during query/,
|
178
|
+
/Max connect timeout reached/,
|
179
|
+
/Unknown MySQL server host/,
|
180
|
+
/connection is locked to hostgroup/,
|
181
|
+
/The MySQL server is running with the --read-only option so it cannot execute this statement/,
|
182
|
+
],
|
183
|
+
}
|
184
|
+
end
|
185
|
+
|
186
|
+
def retriable_trilogy_errors
|
187
|
+
return unless defined?(Trilogy::BaseError)
|
188
|
+
|
189
|
+
{
|
190
|
+
ActiveRecord::StatementInvalid => [
|
191
|
+
# Those sometimes appear as Trilogy::TimeoutError, and sometimes as ActiveRecord::StatementInvalid
|
192
|
+
/Lock wait timeout exceeded/,
|
193
|
+
],
|
194
|
+
Trilogy::ConnectionError => nil,
|
195
|
+
}
|
196
|
+
end
|
179
197
|
end
|
180
198
|
end
|
data/lib/lhm/table.rb
CHANGED
@@ -39,10 +39,9 @@ module Lhm
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def ddl
|
42
|
-
|
43
|
-
|
44
|
-
@connection.
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
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
@@ -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=
|
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=
|
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=
|
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'@'%'
|
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(:
|
37
|
-
|
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(:
|
66
|
-
|
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: {
|
@@ -31,7 +31,6 @@ describe Lhm::Chunker do
|
|
31
31
|
replica do
|
32
32
|
value(count_all(@destination.name)).must_equal(1)
|
33
33
|
end
|
34
|
-
|
35
34
|
end
|
36
35
|
|
37
36
|
it 'should copy and ignore duplicate primary key' do
|
@@ -39,7 +38,7 @@ describe Lhm::Chunker do
|
|
39
38
|
execute("insert into origin set id = 1002 ")
|
40
39
|
execute("insert into destination set id = 1002 ")
|
41
40
|
|
42
|
-
Lhm::Chunker.new(@migration, connection, {throttler: throttler, printer: printer} ).run
|
41
|
+
Lhm::Chunker.new(@migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
|
43
42
|
|
44
43
|
replica do
|
45
44
|
value(count_all(@destination.name)).must_equal(2)
|
@@ -55,7 +54,7 @@ describe Lhm::Chunker do
|
|
55
54
|
execute("insert into composite_primary_key set id = 1002, shop_id = 1")
|
56
55
|
execute("insert into composite_primary_key_dest set id = 1002, shop_id = 1")
|
57
56
|
|
58
|
-
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
57
|
+
Lhm::Chunker.new(migration, connection, {raise_on_warning: true, throttler: throttler, printer: printer} ).run
|
59
58
|
|
60
59
|
replica do
|
61
60
|
value(count_all(destination.name)).must_equal(2)
|
@@ -74,7 +73,8 @@ describe Lhm::Chunker do
|
|
74
73
|
Lhm::Chunker.new(migration, connection, {raise_on_warnings: true, throttler: throttler, printer: printer} ).run
|
75
74
|
end
|
76
75
|
|
77
|
-
|
76
|
+
error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
|
77
|
+
assert_match "Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'", exception.message
|
78
78
|
end
|
79
79
|
|
80
80
|
it 'should copy and warn on unexpected warnings by default' do
|
@@ -87,8 +87,10 @@ describe Lhm::Chunker do
|
|
87
87
|
|
88
88
|
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
89
89
|
|
90
|
+
error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
|
91
|
+
|
90
92
|
assert_equal 2, log_messages.length
|
91
|
-
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '
|
93
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
|
92
94
|
end
|
93
95
|
|
94
96
|
it 'should log two times for two unexpected warnings' do
|
@@ -103,9 +105,11 @@ describe Lhm::Chunker do
|
|
103
105
|
|
104
106
|
Lhm::Chunker.new(migration, connection, {throttler: throttler, printer: printer} ).run
|
105
107
|
|
108
|
+
error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
|
109
|
+
|
106
110
|
assert_equal 3, log_messages.length
|
107
|
-
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '
|
108
|
-
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key '
|
111
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
|
112
|
+
assert log_messages[2].include?("Unexpected warning found for inserted row: Duplicate entry '1002' for key '#{error_key}'"), log_messages
|
109
113
|
end
|
110
114
|
|
111
115
|
it 'should copy and warn on unexpected warnings' do
|
@@ -118,8 +122,10 @@ describe Lhm::Chunker do
|
|
118
122
|
|
119
123
|
Lhm::Chunker.new(migration, connection, {raise_on_warnings: false, throttler: throttler, printer: printer} ).run
|
120
124
|
|
125
|
+
error_key = index_key("custom_primary_key_dest", "index_custom_primary_key_on_id")
|
126
|
+
|
121
127
|
assert_equal 2, log_messages.length
|
122
|
-
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '
|
128
|
+
assert log_messages[1].include?("Unexpected warning found for inserted row: Duplicate entry '1001' for key '#{error_key}'"), log_messages
|
123
129
|
end
|
124
130
|
|
125
131
|
it 'should create the modified destination, even if the source is empty' do
|
@@ -222,7 +228,7 @@ describe Lhm::Chunker do
|
|
222
228
|
def throttler.replica_connection(replica)
|
223
229
|
config = ActiveRecord::Base.connection_pool.db_config.configuration_hash.dup
|
224
230
|
config[:host] = replica
|
225
|
-
config[:port] =
|
231
|
+
config[:port] = 13007
|
226
232
|
ActiveRecord::Base.send('mysql2_connection', config)
|
227
233
|
end
|
228
234
|
end
|
@@ -261,4 +267,12 @@ describe Lhm::Chunker do
|
|
261
267
|
end
|
262
268
|
end
|
263
269
|
end
|
270
|
+
|
271
|
+
def index_key(table_name, index_name)
|
272
|
+
if mysql_version.start_with?("8")
|
273
|
+
"#{table_name}.#{index_name}"
|
274
|
+
else
|
275
|
+
index_name
|
276
|
+
end
|
277
|
+
end
|
264
278
|
end
|
@@ -2,17 +2,17 @@ master:
|
|
2
2
|
host: mysql-1
|
3
3
|
user: root
|
4
4
|
password: password
|
5
|
-
port:
|
5
|
+
port: 13006
|
6
6
|
replica:
|
7
7
|
host: mysql-2
|
8
8
|
user: root
|
9
9
|
password: password
|
10
|
-
port:
|
10
|
+
port: 13007
|
11
11
|
proxysql:
|
12
12
|
host: proxysql
|
13
13
|
user: root
|
14
14
|
password: password
|
15
|
-
port:
|
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 =
|
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 =>
|
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
|
-
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =>
|
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 =
|
3
|
+
conn = DATABASE.client.new(
|
4
4
|
host: '127.0.0.1',
|
5
5
|
username: "writer",
|
6
6
|
password: "password",
|
7
|
-
port: "
|
7
|
+
port: "13005",
|
8
8
|
)
|
9
9
|
|
10
|
-
assert_equal
|
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 =
|
14
|
+
conn = DATABASE.client.new(
|
15
15
|
host: '127.0.0.1',
|
16
16
|
username: "reader",
|
17
17
|
password: "password",
|
18
|
-
port: "
|
18
|
+
port: "13005",
|
19
19
|
)
|
20
20
|
|
21
|
-
assert_equal
|
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 =
|
25
|
+
conn = DATABASE.client.new(
|
26
26
|
host: '127.0.0.1',
|
27
27
|
username: "reader",
|
28
28
|
password: "password",
|
29
|
-
port: "
|
29
|
+
port: "13005",
|
30
30
|
)
|
31
31
|
|
32
|
-
assert_equal
|
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 =>
|
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
|
-
|
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
|
47
|
-
assert_equal
|
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?("
|
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?("
|
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
|
122
|
-
assert_equal
|
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
|