mysql_framework 2.0.1 → 2.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3694d868584973bdad19a924b7b50961e2c5fcca8ea45e7222605b5145152c24
4
- data.tar.gz: d92fe8d7004501fab5e05cd0277f04287bafd3a2352439a1ec4fa5d2cedefe10
3
+ metadata.gz: 87c4479177595f0b81fd81122c23473b63416ecf3a6427620c3cef4ba25d3e58
4
+ data.tar.gz: c668c3276dfe1120f7be7f8d30238bd1b8123c5559221ec175a1d3d844cc8e80
5
5
  SHA512:
6
- metadata.gz: a7c05de40f9bcecbbef52444d78a3d07cde007aa7122da4eebc94d411deee65e9805c42bf2f4ead59150668346108348eca7cb04e264aa36882e925a20bd8c60
7
- data.tar.gz: abbc47dbbd4a95b3c3e1956089308b7e4f052185698cd36e00d0f887594e17e5491a5ee28e00988cabc24831b76e8a1c5e2ccc2d4fd0dc7517a4ac1bb8bf2598
6
+ metadata.gz: fc44ac34b35b2c615ce87b8a1a627edc5d9f97c50f14ba3da1cae08e4089b0dcea405c495e48efbba95c852f2418334b3e1c32d029ae3fc4ec09f5d4028cf68d
7
+ data.tar.gz: 2eba7935da06a55d525b96126655f6a30415a62cf31225f184a0a305571f4e2f83a292a7214c446b63c01cbdd005b652bf30a2796bcb2e24f8644e8a8182830c
@@ -4,6 +4,7 @@ module MysqlFramework
4
4
  class Connector
5
5
  def initialize(options = {})
6
6
  @options = default_options.merge(options)
7
+ @mutex = Mutex.new
7
8
 
8
9
  Mysql2::Client.default_query_options.merge!(symbolize_keys: true, cast_booleans: true)
9
10
  end
@@ -25,7 +26,7 @@ module MysqlFramework
25
26
 
26
27
  until @connection_pool.empty?
27
28
  conn = @connection_pool.pop(true)
28
- conn.close
29
+ conn&.close
29
30
  end
30
31
 
31
32
  @connection_pool = nil
@@ -38,32 +39,37 @@ module MysqlFramework
38
39
 
39
40
  # This method is called to fetch a client from the connection pool.
40
41
  def check_out
41
- return new_client unless connection_pool_enabled?
42
+ @mutex.synchronize do
43
+ begin
44
+ return new_client unless connection_pool_enabled?
42
45
 
43
- client = @connection_pool.pop(true)
46
+ client = @connection_pool.pop(true)
44
47
 
45
- client.ping if @options[:reconnect]
48
+ client.ping if @options[:reconnect]
46
49
 
47
- client
48
- rescue ThreadError
49
- if @created_connections < max_pool_size
50
- client = new_client
51
- @created_connections += 1
52
- return client
53
- end
50
+ client
51
+ rescue ThreadError
52
+ if @created_connections < max_pool_size
53
+ client = new_client
54
+ @created_connections += 1
55
+ return client
56
+ end
54
57
 
55
- MysqlFramework.logger.error { "[#{self.class}] - Database connection pool depleted." }
58
+ MysqlFramework.logger.error { "[#{self.class}] - Database connection pool depleted." }
56
59
 
57
- raise 'Database connection pool depleted.'
60
+ raise 'Database connection pool depleted.'
61
+ end
62
+ end
58
63
  end
59
64
 
60
65
  # This method is called to check a client back in to the connection when no longer needed.
61
66
  def check_in(client)
62
- return client.close unless connection_pool_enabled?
67
+ @mutex.synchronize do
68
+ return client&.close unless connection_pool_enabled?
63
69
 
64
- client = new_client if client.closed?
65
-
66
- @connection_pool.push(client)
70
+ client = new_client if client&.closed?
71
+ @connection_pool.push(client)
72
+ end
67
73
  end
68
74
 
69
75
  # This method is called to use a client from the connection pool.
@@ -75,10 +81,20 @@ module MysqlFramework
75
81
  end
76
82
 
77
83
  # This method is called to execute a prepared statement
84
+ #
85
+ # @note Ensure we free any result and close each statement, otherwise we
86
+ # can run into a 'Commands out of sync' error if multiple threads are
87
+ # running different queries at the same time.
78
88
  def execute(query, provided_client = nil)
79
89
  with_client(provided_client) do |client|
80
- statement = client.prepare(query.sql)
81
- statement.execute(*query.params)
90
+ begin
91
+ statement = client.prepare(query.sql)
92
+ result = statement.execute(*query.params)
93
+ result&.to_a
94
+ ensure
95
+ result&.free
96
+ statement&.close
97
+ end
82
98
  end
83
99
  end
84
100
 
@@ -50,7 +50,12 @@ module MysqlFramework
50
50
  end
51
51
 
52
52
  def invalid_nil_value?(value)
53
+ return false if skip_nil_validation?
53
54
  nil_comparison? == false && value.nil?
54
55
  end
56
+
57
+ def skip_nil_validation?
58
+ ENV.fetch('MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION', 'false').downcase == 'true'
59
+ end
55
60
  end
56
61
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MysqlFramework
4
- VERSION = '2.0.1'
4
+ VERSION = '2.1.4'
5
5
  end
@@ -118,6 +118,12 @@ describe MysqlFramework::Connector do
118
118
  end
119
119
 
120
120
  describe '#check_out' do
121
+ it 'calls synchronize on the mutex' do
122
+ expect(subject.instance_variable_get(:@mutex)).to receive(:synchronize)
123
+
124
+ subject.check_out
125
+ end
126
+
121
127
  context 'when connection pooling is enabled' do
122
128
  context 'when there are available connections' do
123
129
  before do
@@ -203,6 +209,12 @@ describe MysqlFramework::Connector do
203
209
  end
204
210
 
205
211
  describe '#check_in' do
212
+ it 'calls synchronize on the mutex' do
213
+ expect(subject.instance_variable_get(:@mutex)).to receive(:synchronize)
214
+
215
+ subject.check_out
216
+ end
217
+
206
218
  context 'when connection pooling is enabled' do
207
219
  it 'returns the provided client to the connection pool' do
208
220
  expect(subject.connections).to receive(:push).with(client)
@@ -232,6 +244,24 @@ describe MysqlFramework::Connector do
232
244
  subject.check_in(client)
233
245
  end
234
246
  end
247
+
248
+ context 'when client is nil' do
249
+ let(:client) { nil }
250
+
251
+ context 'when connection pooling is enabled' do
252
+ it 'does not raise an error' do
253
+ expect { subject.check_in(client) }.not_to raise_error
254
+ end
255
+ end
256
+
257
+ context 'when connection pooling is disabled' do
258
+ let(:connection_pooling_enabled) { 'false' }
259
+
260
+ it 'does not raise an error' do
261
+ expect { subject.check_in(client) }.not_to raise_error
262
+ end
263
+ end
264
+ end
235
265
  end
236
266
 
237
267
  describe '#with_client' do
@@ -272,6 +302,62 @@ describe MysqlFramework::Connector do
272
302
  expect(results.length).to eq(1)
273
303
  expect(results[0][:id]).to eq(guid)
274
304
  end
305
+
306
+ context 'when cleaning up resources' do
307
+ let(:mock_client) { double('client') }
308
+ let(:mock_statement) { double('statement') }
309
+ let(:mock_result) { double('result') }
310
+ let(:select_query) { MysqlFramework::SqlQuery.new.select('*').from('demo') }
311
+
312
+ before do
313
+ allow(mock_result).to receive(:to_a)
314
+ allow(mock_result).to receive(:free)
315
+
316
+ allow(mock_statement).to receive(:close)
317
+ allow(mock_statement).to receive(:execute).and_return(mock_result)
318
+
319
+ allow(mock_client).to receive(:prepare).and_return(mock_statement)
320
+ end
321
+
322
+ it 'frees the result' do
323
+ expect(mock_result).to receive(:free)
324
+
325
+ subject.execute(select_query, mock_client)
326
+ end
327
+
328
+ it 'closes the statement' do
329
+ expect(mock_statement).to receive(:close)
330
+
331
+ subject.execute(select_query, mock_client)
332
+ end
333
+ end
334
+
335
+ it 'does not raise a commands out of sync error' do
336
+ threads = []
337
+ threads << Thread.new do
338
+ 350.times do
339
+ update_query = MysqlFramework::SqlQuery.new.update('gems')
340
+ .set(updated_at: Time.now)
341
+ expect { subject.execute(update_query) }.not_to raise_error
342
+ end
343
+ end
344
+
345
+ threads << Thread.new do
346
+ 350.times do
347
+ select_query = MysqlFramework::SqlQuery.new.select('*').from('demo')
348
+ expect { subject.execute(select_query) }.not_to raise_error
349
+ end
350
+ end
351
+
352
+ threads << Thread.new do
353
+ 350.times do
354
+ select_query = MysqlFramework::SqlQuery.new.select('*').from('test')
355
+ expect { subject.execute(select_query) }.not_to raise_error
356
+ end
357
+ end
358
+
359
+ threads.each(&:join)
360
+ end
275
361
  end
276
362
 
277
363
  describe '#query' do
@@ -3,6 +3,12 @@
3
3
  describe MysqlFramework::SqlCondition do
4
4
  subject { described_class.new(column: 'version', comparison: '=', value: '1.0.0') }
5
5
 
6
+ before :each do
7
+ allow_any_instance_of(MysqlFramework::SqlCondition).to receive(:skip_nil_validation?).and_return(skip_nil_validation)
8
+ end
9
+
10
+ let(:skip_nil_validation) { false }
11
+
6
12
  describe '#to_s' do
7
13
  it 'returns the condition as a string for a prepared statement' do
8
14
  expect(subject.to_s).to eq('version = ?')
@@ -16,6 +22,14 @@ describe MysqlFramework::SqlCondition do
16
22
  it 'does raises an ArgumentError' do
17
23
  expect { subject }.to raise_error(ArgumentError, "Comparison of = requires value to be not nil")
18
24
  end
25
+
26
+ context 'when skip_nil_validation? is true' do
27
+ let(:skip_nil_validation) { true }
28
+
29
+ it 'does not raise an ArgumentError' do
30
+ expect(subject.value).to be_nil
31
+ end
32
+ end
19
33
  end
20
34
  end
21
35
 
@@ -33,6 +47,14 @@ describe MysqlFramework::SqlCondition do
33
47
  it 'raises an ArgumentError if value is set' do
34
48
  expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NULL')
35
49
  end
50
+
51
+ context 'when skip_nil_validation? is true' do
52
+ let(:skip_nil_validation) { true }
53
+
54
+ it 'raises an ArgumentError if value is set' do
55
+ expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NULL')
56
+ end
57
+ end
36
58
  end
37
59
  end
38
60
 
@@ -67,6 +89,14 @@ describe MysqlFramework::SqlCondition do
67
89
  it 'raises an ArgumentError if value is set' do
68
90
  expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NOT NULL')
69
91
  end
92
+
93
+ context 'when skip_nil_validation? is true' do
94
+ let(:skip_nil_validation) { true }
95
+
96
+ it 'raises an ArgumentError if value is set' do
97
+ expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NOT NULL')
98
+ end
99
+ end
70
100
  end
71
101
  end
72
102
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sage
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-06-23 00:00:00.000000000 Z
11
+ date: 2021-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -137,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  requirements: []
140
- rubyforge_project:
141
- rubygems_version: 2.7.7
140
+ rubygems_version: 3.0.8
142
141
  signing_key:
143
142
  specification_version: 4
144
143
  summary: A lightweight framework to provide managers for working with MySQL.