mysql_framework 2.1.8 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mysql_framework/connector.rb +124 -85
- data/lib/mysql_framework/mysql_connection_pool.rb +176 -0
- data/lib/mysql_framework/scripts/manager.rb +1 -1
- data/lib/mysql_framework/sql_query.rb +7 -1
- data/lib/mysql_framework/stats/aws_metric_publisher.rb +124 -0
- data/lib/mysql_framework/stats/dimension_map.rb +51 -0
- data/lib/mysql_framework/version.rb +1 -1
- data/spec/lib/mysql_framework/connector_spec.rb +125 -148
- data/spec/lib/mysql_framework/mysql_connection_pool_spec.rb +239 -0
- data/spec/lib/mysql_framework/sql_query_spec.rb +27 -0
- data/spec/lib/mysql_framework/stats/aws_metric_publisher_spec.rb +89 -0
- data/spec/support/fixtures.rb +10 -0
- metadata +47 -14
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
describe MysqlFramework::Connector do
|
|
4
|
-
let(:start_pool_size) { Integer(ENV.fetch('MYSQL_START_POOL_SIZE')) }
|
|
5
4
|
let(:max_pool_size) { Integer(ENV.fetch('MYSQL_MAX_POOL_SIZE')) }
|
|
6
5
|
let(:default_options) do
|
|
7
6
|
{
|
|
@@ -11,8 +10,8 @@ describe MysqlFramework::Connector do
|
|
|
11
10
|
username: ENV.fetch('MYSQL_USERNAME'),
|
|
12
11
|
password: ENV.fetch('MYSQL_PASSWORD'),
|
|
13
12
|
reconnect: true,
|
|
14
|
-
read_timeout: ENV.fetch('MYSQL_READ_TIMEOUT', 30),
|
|
15
|
-
write_timeout: ENV.fetch('MYSQL_WRITE_TIMEOUT', 10)
|
|
13
|
+
read_timeout: Integer(ENV.fetch('MYSQL_READ_TIMEOUT', 30)),
|
|
14
|
+
write_timeout: Integer(ENV.fetch('MYSQL_WRITE_TIMEOUT', 10))
|
|
16
15
|
}
|
|
17
16
|
end
|
|
18
17
|
let(:options) do
|
|
@@ -25,28 +24,24 @@ describe MysqlFramework::Connector do
|
|
|
25
24
|
reconnect: false
|
|
26
25
|
}
|
|
27
26
|
end
|
|
28
|
-
let(:client) { double(close: true, ping: true, closed?: false) }
|
|
27
|
+
let(:client) { double(close: true, ping: true, closed?: false, abandon_results!: nil) }
|
|
29
28
|
let(:gems) { MysqlFramework::SqlTable.new('gems') }
|
|
30
29
|
let(:existing_client) { Mysql2::Client.new(default_options) }
|
|
31
30
|
let(:connection_pooling_enabled) { 'true' }
|
|
32
31
|
|
|
33
32
|
subject { described_class.new }
|
|
34
33
|
|
|
35
|
-
before
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if var == 'MYSQL_CONNECTION_POOL_ENABLED'
|
|
40
|
-
connection_pooling_enabled
|
|
41
|
-
else
|
|
42
|
-
original_fetch.call(var, default)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
34
|
+
before do
|
|
35
|
+
allow(ENV).to receive(:fetch).and_call_original
|
|
36
|
+
allow(ENV).to receive(:fetch).with('MYSQL_CONNECTION_POOL_ENABLED', 'true')
|
|
37
|
+
.and_return(connection_pooling_enabled)
|
|
45
38
|
|
|
46
39
|
subject.setup
|
|
47
40
|
end
|
|
48
41
|
|
|
49
|
-
after
|
|
42
|
+
after do
|
|
43
|
+
subject.dispose
|
|
44
|
+
end
|
|
50
45
|
|
|
51
46
|
describe '#initialize' do
|
|
52
47
|
context 'when options are not provided' do
|
|
@@ -66,8 +61,8 @@ describe MysqlFramework::Connector do
|
|
|
66
61
|
username: ENV.fetch('MYSQL_USERNAME'),
|
|
67
62
|
password: ENV.fetch('MYSQL_PASSWORD'),
|
|
68
63
|
reconnect: false,
|
|
69
|
-
read_timeout: ENV.fetch('MYSQL_READ_TIMEOUT', 30),
|
|
70
|
-
write_timeout: ENV.fetch('MYSQL_WRITE_TIMEOUT', 10)
|
|
64
|
+
read_timeout: Integer(ENV.fetch('MYSQL_READ_TIMEOUT', 30)),
|
|
65
|
+
write_timeout: Integer(ENV.fetch('MYSQL_WRITE_TIMEOUT', 10))
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
expect(subject.instance_variable_get(:@options)).to eq(expected)
|
|
@@ -84,10 +79,13 @@ describe MysqlFramework::Connector do
|
|
|
84
79
|
|
|
85
80
|
describe '#setup' do
|
|
86
81
|
context 'when connection pooling is enabled' do
|
|
87
|
-
it 'creates a connection pool with
|
|
82
|
+
it 'creates a connection pool with expected stats' do
|
|
88
83
|
subject.setup
|
|
89
84
|
|
|
90
|
-
expect(subject.
|
|
85
|
+
expect(subject.connection_pool).to be_a(MysqlFramework::MysqlConnectionPool)
|
|
86
|
+
expect(subject.connection_pool.pool_stats[:size]).to eq(max_pool_size)
|
|
87
|
+
expect(subject.connection_pool.pool_stats[:available]).to be >= 0
|
|
88
|
+
expect(subject.connection_pool.pool_stats[:idle]).to be >= 0
|
|
91
89
|
end
|
|
92
90
|
end
|
|
93
91
|
|
|
@@ -97,182 +95,147 @@ describe MysqlFramework::Connector do
|
|
|
97
95
|
it "doesn't create a connection pool" do
|
|
98
96
|
subject.setup
|
|
99
97
|
|
|
100
|
-
expect(subject.
|
|
98
|
+
expect(subject.connection_pool).to be_nil
|
|
101
99
|
end
|
|
102
100
|
end
|
|
103
101
|
end
|
|
104
102
|
|
|
105
103
|
describe '#dispose' do
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
it 'closes the idle connections and disposes of the queue' do
|
|
112
|
-
expect(client).to receive(:close)
|
|
104
|
+
context 'when connection pooling is enabled' do
|
|
105
|
+
it 'disposes of the pool and clears connector reference' do
|
|
106
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
107
|
+
allow(pool).to receive(:dispose)
|
|
108
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
113
109
|
|
|
114
|
-
|
|
110
|
+
expect(pool).to receive(:dispose)
|
|
111
|
+
subject.dispose
|
|
115
112
|
|
|
116
|
-
|
|
113
|
+
expect(subject.connection_pool).to be_nil
|
|
114
|
+
end
|
|
117
115
|
end
|
|
118
|
-
end
|
|
119
116
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(subject.instance_variable_get(:@mutex)).to receive(:synchronize)
|
|
117
|
+
context 'when connection pooling is disabled' do
|
|
118
|
+
let(:connection_pooling_enabled) { 'false' }
|
|
123
119
|
|
|
124
|
-
|
|
120
|
+
it 'does not perform more actions' do
|
|
121
|
+
expect { subject.dispose }.not_to raise_error
|
|
122
|
+
expect(subject.connection_pool).to be_nil
|
|
123
|
+
end
|
|
125
124
|
end
|
|
125
|
+
end
|
|
126
126
|
|
|
127
|
+
describe '#check_out' do
|
|
127
128
|
context 'when connection pooling is enabled' do
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
it 'returns a client instance from the pool' do
|
|
135
|
-
expect(subject.check_out).to eq(client)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
context 'and :reconnect is set to true' do
|
|
139
|
-
let(:options) do
|
|
140
|
-
{
|
|
141
|
-
host: ENV.fetch('MYSQL_HOST'),
|
|
142
|
-
port: ENV.fetch('MYSQL_PORT'),
|
|
143
|
-
database: "#{ENV.fetch('MYSQL_DATABASE')}_2",
|
|
144
|
-
username: ENV.fetch('MYSQL_USERNAME'),
|
|
145
|
-
password: ENV.fetch('MYSQL_PASSWORD'),
|
|
146
|
-
reconnect: true
|
|
147
|
-
}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
subject { described_class.new(options) }
|
|
151
|
-
|
|
152
|
-
it 'pings the server to force a reconnect' do
|
|
153
|
-
expect(client).to receive(:ping)
|
|
154
|
-
|
|
155
|
-
subject.check_out
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
context 'and :reconnect is set to false' do
|
|
160
|
-
subject { described_class.new(options) }
|
|
161
|
-
|
|
162
|
-
it 'pings the server to force a reconnect' do
|
|
163
|
-
expect(client).not_to receive(:ping)
|
|
129
|
+
it 'checks out pooled connection' do
|
|
130
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
131
|
+
client = instance_double(Mysql2::Client)
|
|
132
|
+
allow(pool).to receive(:check_out).and_return(client)
|
|
133
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
164
134
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
end
|
|
135
|
+
expect(pool).to receive(:check_out)
|
|
136
|
+
expect(subject.check_out).to eq(client)
|
|
168
137
|
end
|
|
138
|
+
end
|
|
169
139
|
|
|
170
|
-
|
|
171
|
-
|
|
140
|
+
context 'when pooling is disabled' do
|
|
141
|
+
let(:connection_pooling_enabled) { 'false' }
|
|
172
142
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
143
|
+
it 'returns a new client directly' do
|
|
144
|
+
new_client = instance_double(Mysql2::Client)
|
|
145
|
+
allow(Mysql2::Client).to receive(:new).and_return(new_client)
|
|
176
146
|
|
|
177
|
-
|
|
178
|
-
end
|
|
147
|
+
expect(subject.check_out).to eq(new_client)
|
|
179
148
|
end
|
|
180
149
|
end
|
|
150
|
+
end
|
|
181
151
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
it 'instantiates a new connection and returns it' do
|
|
189
|
-
subject.check_out
|
|
152
|
+
describe '#check_in' do
|
|
153
|
+
context 'when connection pooling is enabled' do
|
|
154
|
+
it 'checks in a pooled connection' do
|
|
155
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
156
|
+
allow(pool).to receive(:check_in)
|
|
157
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
190
158
|
|
|
191
|
-
expect(
|
|
192
|
-
|
|
159
|
+
expect(pool).to receive(:check_in)
|
|
160
|
+
subject.check_in(client)
|
|
193
161
|
end
|
|
194
162
|
end
|
|
195
163
|
|
|
196
|
-
context
|
|
197
|
-
|
|
198
|
-
subject.connections.clear
|
|
199
|
-
subject.instance_variable_set(:@created_connections, 5)
|
|
164
|
+
context 'when pooling is disabled' do
|
|
165
|
+
let(:connection_pooling_enabled) { 'false' }
|
|
200
166
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
end
|
|
167
|
+
it 'closes the provided client' do
|
|
168
|
+
new_client = instance_double(Mysql2::Client, close: nil)
|
|
204
169
|
|
|
205
|
-
|
|
206
|
-
|
|
170
|
+
expect(new_client).to receive(:close)
|
|
171
|
+
subject.check_in(new_client)
|
|
207
172
|
end
|
|
208
173
|
end
|
|
209
174
|
end
|
|
210
175
|
|
|
211
|
-
describe '#
|
|
212
|
-
|
|
213
|
-
|
|
176
|
+
describe '#with_client' do
|
|
177
|
+
context 'when a provided_client is given' do
|
|
178
|
+
it 'yields the provided client directly' do
|
|
179
|
+
expect { |b| subject.with_client(client, &b) }.to yield_with_args(client)
|
|
180
|
+
end
|
|
214
181
|
|
|
215
|
-
|
|
216
|
-
|
|
182
|
+
it 'does not interact with the connection pool' do
|
|
183
|
+
expect(subject).not_to receive(:with_new_client)
|
|
184
|
+
expect(subject.connection_pool).not_to receive(:with_client) if subject.connection_pool
|
|
217
185
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
expect(subject.connections).to receive(:push).with(client)
|
|
186
|
+
subject.with_client(client) { |_c| nil }
|
|
187
|
+
end
|
|
221
188
|
|
|
222
|
-
|
|
189
|
+
it 'returns the block result' do
|
|
190
|
+
result = subject.with_client(client) { |_c| :expected }
|
|
191
|
+
expect(result).to eq(:expected)
|
|
223
192
|
end
|
|
193
|
+
end
|
|
224
194
|
|
|
225
|
-
|
|
226
|
-
|
|
195
|
+
context 'when no provided_client is given' do
|
|
196
|
+
context 'when connection pooling is disabled' do
|
|
197
|
+
let(:connection_pooling_enabled) { 'false' }
|
|
227
198
|
|
|
228
|
-
it '
|
|
229
|
-
expect(
|
|
230
|
-
expect(subject.connections).to receive(:push).with(client)
|
|
199
|
+
it 'delegates to with_new_client' do
|
|
200
|
+
expect(subject).to receive(:with_new_client).and_yield(client)
|
|
231
201
|
|
|
232
|
-
subject.
|
|
202
|
+
expect { |b| subject.with_client(&b) }.to yield_with_args(client)
|
|
233
203
|
end
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
204
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
it 'closes the connection and does not add it to the connection pool' do
|
|
241
|
-
expect(client).to receive(:close)
|
|
242
|
-
expect(subject.connections).not_to receive(:push)
|
|
205
|
+
it 'returns the block result' do
|
|
206
|
+
allow(subject).to receive(:with_new_client).and_yield(client)
|
|
243
207
|
|
|
244
|
-
|
|
208
|
+
result = subject.with_client { |_c| :expected }
|
|
209
|
+
expect(result).to eq(:expected)
|
|
210
|
+
end
|
|
245
211
|
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
context 'when client is nil' do
|
|
249
|
-
let(:client) { nil }
|
|
250
212
|
|
|
251
213
|
context 'when connection pooling is enabled' do
|
|
252
|
-
it '
|
|
253
|
-
|
|
214
|
+
it 'delegates to the connection pool' do
|
|
215
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
216
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
217
|
+
|
|
218
|
+
expect(pool).to receive(:with_client).with(discard_current_pool_connection: false).and_yield(client)
|
|
219
|
+
expect { |b| subject.with_client(&b) }.to yield_with_args(client)
|
|
254
220
|
end
|
|
255
|
-
end
|
|
256
221
|
|
|
257
|
-
|
|
258
|
-
|
|
222
|
+
it 'passes discard_current_pool_connection: true to the pool when requested' do
|
|
223
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
224
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
259
225
|
|
|
260
|
-
|
|
261
|
-
|
|
226
|
+
expect(pool).to receive(:with_client).with(discard_current_pool_connection: true).and_yield(client)
|
|
227
|
+
subject.with_client(discard_current_pool_connection: true) { |_c| nil }
|
|
262
228
|
end
|
|
263
|
-
end
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
229
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
end
|
|
230
|
+
it 'returns the block result' do
|
|
231
|
+
pool = instance_double(MysqlFramework::MysqlConnectionPool, dispose: true)
|
|
232
|
+
subject.instance_variable_set(:@connection_pool, pool)
|
|
233
|
+
allow(pool).to receive(:with_client).and_yield(client)
|
|
272
234
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
235
|
+
result = subject.with_client { |_c| :expected }
|
|
236
|
+
expect(result).to eq(:expected)
|
|
237
|
+
end
|
|
238
|
+
end
|
|
276
239
|
end
|
|
277
240
|
end
|
|
278
241
|
|
|
@@ -317,6 +280,7 @@ describe MysqlFramework::Connector do
|
|
|
317
280
|
allow(mock_statement).to receive(:execute).and_return(mock_result)
|
|
318
281
|
|
|
319
282
|
allow(mock_client).to receive(:prepare).and_return(mock_statement)
|
|
283
|
+
allow(mock_client).to receive(:abandon_results!)
|
|
320
284
|
end
|
|
321
285
|
|
|
322
286
|
it 'frees the result' do
|
|
@@ -361,7 +325,7 @@ describe MysqlFramework::Connector do
|
|
|
361
325
|
end
|
|
362
326
|
|
|
363
327
|
describe '#query' do
|
|
364
|
-
before(:each) { allow(subject).to receive(:
|
|
328
|
+
before(:each) { allow(subject).to receive(:with_client).and_yield(client) }
|
|
365
329
|
|
|
366
330
|
it 'retrieves a client and calls query' do
|
|
367
331
|
expect(client).to receive(:query).with('SELECT 1')
|
|
@@ -370,7 +334,7 @@ describe MysqlFramework::Connector do
|
|
|
370
334
|
end
|
|
371
335
|
|
|
372
336
|
it 'does not check out a new client when one is provided' do
|
|
373
|
-
expect(subject).
|
|
337
|
+
expect(subject).to receive(:with_client).with(existing_client).and_yield(existing_client)
|
|
374
338
|
expect(existing_client).to receive(:query).with('SELECT 1')
|
|
375
339
|
|
|
376
340
|
subject.query('SELECT 1', existing_client)
|
|
@@ -378,6 +342,19 @@ describe MysqlFramework::Connector do
|
|
|
378
342
|
end
|
|
379
343
|
|
|
380
344
|
describe '#query_multiple_results' do
|
|
345
|
+
it 'uses with_client with discard_current_pool_connection enabled' do
|
|
346
|
+
query_call = instance_double(Mysql2::Result, to_a: [], free: true)
|
|
347
|
+
allow(client).to receive(:query).and_return(query_call)
|
|
348
|
+
allow(client).to receive(:more_results?).and_return(false)
|
|
349
|
+
allow(client).to receive(:abandon_results!)
|
|
350
|
+
|
|
351
|
+
expect(subject).to receive(:with_client)
|
|
352
|
+
.with(nil, discard_current_pool_connection: true)
|
|
353
|
+
.and_yield(client)
|
|
354
|
+
|
|
355
|
+
subject.query_multiple_results('call test_procedure')
|
|
356
|
+
end
|
|
357
|
+
|
|
381
358
|
it 'returns the results from the stored procedure' do
|
|
382
359
|
query = 'call test_procedure'
|
|
383
360
|
result = subject.query_multiple_results(query)
|
|
@@ -402,7 +379,7 @@ describe MysqlFramework::Connector do
|
|
|
402
379
|
end
|
|
403
380
|
|
|
404
381
|
describe '#transaction' do
|
|
405
|
-
before(:each) { allow(subject).to receive(:
|
|
382
|
+
before(:each) { allow(subject).to receive(:with_client).and_yield(client) }
|
|
406
383
|
|
|
407
384
|
it 'wraps the client call with BEGIN and COMMIT statements' do
|
|
408
385
|
expect(client).to receive(:query).with('BEGIN')
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
describe MysqlFramework::MysqlConnectionPool do
|
|
4
|
+
let(:options) do
|
|
5
|
+
{
|
|
6
|
+
host: ENV.fetch('MYSQL_HOST'),
|
|
7
|
+
port: ENV.fetch('MYSQL_PORT'),
|
|
8
|
+
database: ENV.fetch('MYSQL_DATABASE'),
|
|
9
|
+
username: ENV.fetch('MYSQL_USERNAME'),
|
|
10
|
+
password: ENV.fetch('MYSQL_PASSWORD'),
|
|
11
|
+
reconnect: true
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
let(:conn) do
|
|
15
|
+
double('Mysql2::Client',
|
|
16
|
+
ping: true,
|
|
17
|
+
abandon_results!: nil,
|
|
18
|
+
query: nil,
|
|
19
|
+
close: nil,
|
|
20
|
+
closed?: false)
|
|
21
|
+
end
|
|
22
|
+
let(:pool) do
|
|
23
|
+
double('ConnectionPool',
|
|
24
|
+
checkout: conn,
|
|
25
|
+
checkin: nil,
|
|
26
|
+
discard_current_connection: nil,
|
|
27
|
+
shutdown: nil,
|
|
28
|
+
size: 5,
|
|
29
|
+
available: 4,
|
|
30
|
+
idle: 1)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
subject { described_class.new(options) }
|
|
34
|
+
|
|
35
|
+
after { subject.dispose }
|
|
36
|
+
|
|
37
|
+
describe '#initialize' do
|
|
38
|
+
it 'stores the provided options' do
|
|
39
|
+
expect(subject.instance_variable_get(:@options)).to eq(options)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'creates a setup mutex' do
|
|
43
|
+
expect(subject.instance_variable_get(:@setup_mutex)).to be_a(Mutex)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
describe '#setup' do
|
|
48
|
+
it 'creates a ConnectionPool' do
|
|
49
|
+
subject.setup
|
|
50
|
+
expect(subject.connections).to be_a(ConnectionPool)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'is idempotent — calling setup twice keeps the same pool' do
|
|
54
|
+
subject.setup
|
|
55
|
+
first_pool = subject.connections
|
|
56
|
+
subject.setup
|
|
57
|
+
expect(subject.connections).to equal(first_pool)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
describe '#dispose' do
|
|
62
|
+
before { subject.setup }
|
|
63
|
+
|
|
64
|
+
it 'shuts down the connection pool' do
|
|
65
|
+
subject.dispose
|
|
66
|
+
expect(subject.connections).to be_nil
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'is safe to call when already disposed' do
|
|
70
|
+
subject.dispose
|
|
71
|
+
expect { subject.dispose }.not_to raise_error
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
describe '#pool_stats' do
|
|
76
|
+
context 'when connections have not been set up' do
|
|
77
|
+
it 'returns zero stats' do
|
|
78
|
+
expect(subject.pool_stats).to eq(size: 0, available: 0, idle: 0)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
context 'when connections are set up' do
|
|
83
|
+
before { subject.instance_variable_set(:@connections, pool) }
|
|
84
|
+
|
|
85
|
+
it 'returns size, available, and idle metrics from the pool' do
|
|
86
|
+
expect(subject.pool_stats).to eq(size: 5, available: 4, idle: 1)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
describe '#check_out' do
|
|
92
|
+
before do
|
|
93
|
+
subject.instance_variable_set(:@connections, pool)
|
|
94
|
+
allow(conn).to receive(:query).with('ROLLBACK')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'returns a sanitized connection from the pool' do
|
|
98
|
+
expect(subject.check_out).to eq(conn)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
it 'sanitizes the connection before returning it' do
|
|
102
|
+
expect(conn).to receive(:ping)
|
|
103
|
+
expect(conn).to receive(:abandon_results!)
|
|
104
|
+
expect(conn).to receive(:query).with('ROLLBACK')
|
|
105
|
+
subject.check_out
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
context 'when sanitization raises ConnectionSanitizationError' do
|
|
109
|
+
before { allow(conn).to receive(:ping).and_raise(Mysql2::Error.new('gone away')) }
|
|
110
|
+
|
|
111
|
+
it 'retries the checkout once before raising' do
|
|
112
|
+
expect(pool).to receive(:checkout).twice.and_return(conn)
|
|
113
|
+
expect { subject.check_out }.to raise_error(MysqlFramework::MysqlConnectionPool::ConnectionSanitizationError)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'discards the connection on each sanitization failure' do
|
|
117
|
+
expect(pool).to receive(:discard_current_connection).at_least(:twice)
|
|
118
|
+
expect { subject.check_out }.to raise_error(MysqlFramework::MysqlConnectionPool::ConnectionSanitizationError)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
context 'when checkout raises Mysql2::Error' do
|
|
123
|
+
before { allow(pool).to receive(:checkout).and_raise(Mysql2::Error.new('connection refused')) }
|
|
124
|
+
|
|
125
|
+
it 'discards the current connection' do
|
|
126
|
+
expect(pool).to receive(:discard_current_connection)
|
|
127
|
+
expect { subject.check_out }.to raise_error(Mysql2::Error)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 're-raises the error' do
|
|
131
|
+
allow(pool).to receive(:discard_current_connection)
|
|
132
|
+
expect { subject.check_out }.to raise_error(Mysql2::Error)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
describe '#check_in' do
|
|
138
|
+
before { subject.instance_variable_set(:@connections, pool) }
|
|
139
|
+
|
|
140
|
+
it 'returns immediately and does not interact with the pool when client is nil' do
|
|
141
|
+
expect(pool).not_to receive(:checkin)
|
|
142
|
+
expect(pool).not_to receive(:discard_current_connection)
|
|
143
|
+
subject.check_in(nil)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
context 'when the client is closed' do
|
|
147
|
+
let(:closed_conn) { double('Mysql2::Client', closed?: true) }
|
|
148
|
+
|
|
149
|
+
it 'discards the current pool connection' do
|
|
150
|
+
expect(pool).to receive(:discard_current_connection)
|
|
151
|
+
allow(pool).to receive(:checkin)
|
|
152
|
+
subject.check_in(closed_conn)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context 'when the client is open' do
|
|
157
|
+
it 'checks the connection back into the pool' do
|
|
158
|
+
expect(pool).to receive(:checkin)
|
|
159
|
+
subject.check_in(conn)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it 'does not discard the connection' do
|
|
163
|
+
expect(pool).not_to receive(:discard_current_connection)
|
|
164
|
+
subject.check_in(conn)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
describe '#with_client' do
|
|
170
|
+
before do
|
|
171
|
+
subject.instance_variable_set(:@connections, pool)
|
|
172
|
+
allow(conn).to receive(:query).with('ROLLBACK')
|
|
173
|
+
allow(pool).to receive(:with).and_yield(conn)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
it 'yields a sanitized connection' do
|
|
177
|
+
expect { |b| subject.with_client(&b) }.to yield_with_args(conn)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'returns the block result' do
|
|
181
|
+
result = subject.with_client { |_c| :expected }
|
|
182
|
+
expect(result).to eq(:expected)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'sanitizes the connection before yielding' do
|
|
186
|
+
expect(conn).to receive(:ping)
|
|
187
|
+
expect(conn).to receive(:abandon_results!)
|
|
188
|
+
expect(conn).to receive(:query).with('ROLLBACK')
|
|
189
|
+
subject.with_client { |_c| nil }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context 'when discard_current_pool_connection is false (default)' do
|
|
193
|
+
it 'does not discard the connection after a successful block' do
|
|
194
|
+
expect(pool).not_to receive(:discard_current_connection)
|
|
195
|
+
subject.with_client { |_c| nil }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context 'when discard_current_pool_connection is true' do
|
|
200
|
+
it 'discards the current connection after the block completes' do
|
|
201
|
+
expect(pool).to receive(:discard_current_connection)
|
|
202
|
+
subject.with_client(discard_current_pool_connection: true) { |_c| nil }
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
context 'when sanitization raises ConnectionSanitizationError' do
|
|
207
|
+
before { allow(conn).to receive(:ping).and_raise(Mysql2::Error.new('gone away')) }
|
|
208
|
+
|
|
209
|
+
it 'retries the pool checkout once before raising' do
|
|
210
|
+
expect(pool).to receive(:with).twice.and_yield(conn)
|
|
211
|
+
expect { subject.with_client { |_c| nil } }.to raise_error(MysqlFramework::MysqlConnectionPool::ConnectionSanitizationError)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
it 'discards the connection on each sanitization failure' do
|
|
215
|
+
allow(pool).to receive(:with).and_yield(conn)
|
|
216
|
+
expect(pool).to receive(:discard_current_connection).at_least(:once)
|
|
217
|
+
expect { subject.with_client { |_c| nil } }.to raise_error(MysqlFramework::MysqlConnectionPool::ConnectionSanitizationError)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
context 'when the block raises Mysql2::Error' do
|
|
222
|
+
it 'discards the current connection' do
|
|
223
|
+
expect(pool).to receive(:discard_current_connection)
|
|
224
|
+
expect { subject.with_client { |_c| raise Mysql2::Error.new('lost connection') } }.to raise_error(Mysql2::Error)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it 're-raises the error' do
|
|
228
|
+
allow(pool).to receive(:discard_current_connection)
|
|
229
|
+
expect { subject.with_client { |_c| raise Mysql2::Error.new('lost connection') } }.to raise_error(Mysql2::Error)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'does not retry for Mysql2::Error raised in the block' do
|
|
233
|
+
expect(pool).to receive(:with).once.and_yield(conn)
|
|
234
|
+
allow(pool).to receive(:discard_current_connection)
|
|
235
|
+
expect { subject.with_client { |_c| raise Mysql2::Error.new('lost connection') } }.to raise_error(Mysql2::Error)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
@@ -233,6 +233,33 @@ describe MysqlFramework::SqlQuery do
|
|
|
233
233
|
expect(subject.sql).to eq('WHERE (`gems`.`author` = ? AND `gems`.`created_at` > ?) AND (`gems`.`name` IN (?, ?))')
|
|
234
234
|
end
|
|
235
235
|
end
|
|
236
|
+
|
|
237
|
+
context 'when condition is nil' do
|
|
238
|
+
context 'when MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION is not present' do
|
|
239
|
+
it 'concats the parameter collections' do
|
|
240
|
+
expect { subject.where(gems[:created_at].eq(nil)) }.to raise_error ArgumentError
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context 'when MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION is present' do
|
|
245
|
+
after(:each) { ENV.delete('MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION') }
|
|
246
|
+
|
|
247
|
+
context 'when value is false' do
|
|
248
|
+
it 'concats the parameter collections' do
|
|
249
|
+
ENV['MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION'] = 'false'
|
|
250
|
+
expect { subject.where(gems[:created_at].eq(nil)) }.to raise_error ArgumentError
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
context 'when value is true' do
|
|
255
|
+
it 'concats the parameter collections' do
|
|
256
|
+
ENV['MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION'] = 'true'
|
|
257
|
+
query = subject.where(gems[:updated_at].eq(nil))
|
|
258
|
+
expect(query.params).to eq ['sage', '2018-01-01 00:00:00', nil]
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
236
263
|
end
|
|
237
264
|
|
|
238
265
|
describe '#and' do
|