mysql_framework 2.0.0.rc1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 79a94352053f85513e65fe768c6a2dbe420c14dd84e483e6f5ee276029b3c494
4
- data.tar.gz: 794c2b7cafbdfa11d9be23e924a71cec74140003396266c2cc2dec023f2c7c26
3
+ metadata.gz: e296a8fc2b6937d61814f1b2722600dddcc767b88d234cdffe312137698fd0b5
4
+ data.tar.gz: 29a3f6549675f886369c5b42be8722fd026100d1717cb1d58ccce11624fc8088
5
5
  SHA512:
6
- metadata.gz: affb71d82cf2c8446d72b2a898abbade1d62a04dbebc97502ccef5a92c6c1e0377a9acc9dccf2a08ce6d7d9da03b64eb0b1d1a861715ca15977c2b53f6aa8c89
7
- data.tar.gz: 8f48457c4dbc815b62406ab8c64fa14a4cc572d0f30c683d66a22a0e9cc7f68112d364edc0bf24d6569fabfaef5869e0606282a9a998910cf1c78bf2f785f264
6
+ metadata.gz: 4133713152e216029f6c5e1a91d9911fb4df4eb62fe13bad814eb1f7530c6483dde53c3f55e2f19cc92f04758a72ac7d1cf8e7e9e39981b64554e0df2d7f783b
7
+ data.tar.gz: 62ce8b6bfbead9defc37117c9f5385df533e85051e8515ccecf6d3cf247f12afa95a2ef37627b96f109219e8ac2154f68ce5918e0960430014e51da8237bb762
@@ -59,10 +59,9 @@ module MysqlFramework
59
59
 
60
60
  # This method is called to check a client back in to the connection when no longer needed.
61
61
  def check_in(client)
62
- return client.close unless connection_pool_enabled?
63
-
64
- client = new_client if client.closed?
62
+ return client&.close unless connection_pool_enabled?
65
63
 
64
+ client = new_client if client.nil? || client.closed?
66
65
  @connection_pool.push(client)
67
66
  end
68
67
 
@@ -75,10 +74,19 @@ module MysqlFramework
75
74
  end
76
75
 
77
76
  # This method is called to execute a prepared statement
77
+ #
78
+ # @note Ensure we close each statement, otherwise we can run into
79
+ # a 'Commands out of sync' error if multiple threads are running different
80
+ # queries at the same time.
78
81
  def execute(query, provided_client = nil)
79
82
  with_client(provided_client) do |client|
80
- statement = client.prepare(query.sql)
81
- statement.execute(*query.params)
83
+ begin
84
+ statement = client.prepare(query.sql)
85
+ result = statement.execute(*query.params)
86
+ result.to_a if result
87
+ ensure
88
+ statement.close if statement
89
+ end
82
90
  end
83
91
  end
84
92
 
@@ -3,18 +3,59 @@
3
3
  module MysqlFramework
4
4
  # This class is used to represent a Sql Condition for a column.
5
5
  class SqlCondition
6
+ NIL_COMPARISONS = ['IS NULL', 'IS NOT NULL'].freeze
7
+
6
8
  # This method is called to get the value of this condition for prepared statements.
7
9
  attr_reader :value
8
10
 
9
- def initialize(column:, comparison:, value:)
11
+ # Creates a new SqlCondition using the given parameters.
12
+ #
13
+ # @raise ArgumentError if comparison is 'IS NULL' and value is not nil
14
+ # @raise ArgumentError if comparison is 'IS NOT NULL' and value is not nil
15
+ # @raise ArgumentError if comparison is neither 'IS NULL' or 'IS NOT NULL' and value is nil
16
+ #
17
+ # @param column [String] - the name of the column to use in the comparison
18
+ # @param comparison [String] - the MySQL comparison operator to use
19
+ # @param value [Object] - the value to use in the comparison (default nil)
20
+ def initialize(column:, comparison:, value: nil)
10
21
  @column = column
11
22
  @comparison = comparison
23
+
24
+ validate(value)
12
25
  @value = value
13
26
  end
14
27
 
15
28
  # This method is called to get the condition as a string for a sql prepared statement
29
+ #
30
+ # @return [String]
16
31
  def to_s
32
+ return "#{@column} #{@comparison.upcase}" if nil_comparison?
33
+
17
34
  "#{@column} #{@comparison} ?"
18
35
  end
36
+
37
+ private
38
+
39
+ def nil_comparison?
40
+ NIL_COMPARISONS.include?(@comparison.upcase)
41
+ end
42
+
43
+ def validate(value)
44
+ raise ArgumentError, "Cannot set value when comparison is #{@comparison}" if invalid_null_condition?(value)
45
+ raise ArgumentError, "Comparison of #{@comparison} requires value to be not nil" if invalid_nil_value?(value)
46
+ end
47
+
48
+ def invalid_null_condition?(value)
49
+ nil_comparison? && value != nil
50
+ end
51
+
52
+ def invalid_nil_value?(value)
53
+ return false if skip_nil_validation?
54
+ nil_comparison? == false && value.nil?
55
+ end
56
+
57
+ def skip_nil_validation?
58
+ ENV.fetch('MYSQL_FRAMEWORK_SKIP_NIL_VALUE_VALIDATION', 'false').downcase == 'true'
59
+ end
19
60
  end
20
61
  end
@@ -120,11 +120,14 @@ module MysqlFramework
120
120
  end
121
121
 
122
122
  # This method is called to specify a where clause for a query.
123
+ #
124
+ # Condition values are added to @params unless the value is nil.
123
125
  def where(*conditions)
124
126
  @sql += ' WHERE' unless @sql.include?('WHERE')
125
127
  @sql += " (#{conditions.join(' AND ')}) "
126
128
 
127
129
  conditions.each do |condition|
130
+ next if condition.value.nil?
128
131
  if condition.value.is_a?(Enumerable)
129
132
  @params.concat(condition.value)
130
133
  else
@@ -234,7 +237,7 @@ module MysqlFramework
234
237
  # query.insert('users')
235
238
  # .into('id', first_name', 'login_count')
236
239
  # .values(1, 'Bob', 1)
237
- # .duplicate_update(
240
+ # .on_duplicate(
238
241
  # {
239
242
  # first_name: nil,
240
243
  # login_count: 'login_count + 5'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MysqlFramework
4
- VERSION = '2.0.0.rc1'
4
+ VERSION = '2.1.2'
5
5
  end
@@ -232,6 +232,24 @@ describe MysqlFramework::Connector do
232
232
  subject.check_in(client)
233
233
  end
234
234
  end
235
+
236
+ context 'when client is nil' do
237
+ let(:client) { nil }
238
+
239
+ context 'when connection pooling is enabled' do
240
+ it 'does not raise an error' do
241
+ expect { subject.check_in(client) }.not_to raise_error
242
+ end
243
+ end
244
+
245
+ context 'when connection pooling is disabled' do
246
+ let(:connection_pooling_enabled) { 'false' }
247
+
248
+ it 'does not raise an error' do
249
+ expect { subject.check_in(client) }.not_to raise_error
250
+ end
251
+ end
252
+ end
235
253
  end
236
254
 
237
255
  describe '#with_client' do
@@ -272,6 +290,33 @@ describe MysqlFramework::Connector do
272
290
  expect(results.length).to eq(1)
273
291
  expect(results[0][:id]).to eq(guid)
274
292
  end
293
+
294
+ it 'does not raise a commands out of sync error' do
295
+ threads = []
296
+ threads << Thread.new do
297
+ 350.times do
298
+ update_query = MysqlFramework::SqlQuery.new.update('gems')
299
+ .set(updated_at: Time.now)
300
+ expect { subject.execute(update_query) }.not_to raise_error
301
+ end
302
+ end
303
+
304
+ threads << Thread.new do
305
+ 350.times do
306
+ select_query = MysqlFramework::SqlQuery.new.select('*').from('demo')
307
+ expect { subject.execute(select_query) }.not_to raise_error
308
+ end
309
+ end
310
+
311
+ threads << Thread.new do
312
+ 350.times do
313
+ select_query = MysqlFramework::SqlQuery.new.select('*').from('test')
314
+ expect { subject.execute(select_query) }.not_to raise_error
315
+ end
316
+ end
317
+
318
+ threads.each(&:join)
319
+ end
275
320
  end
276
321
 
277
322
  describe '#query' do
@@ -3,9 +3,117 @@
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 = ?')
9
15
  end
10
16
  end
17
+
18
+ context 'when comparison is neither IS NULL or IS NOT NULL' do
19
+ context 'when value is nil' do
20
+ subject { described_class.new(column: 'version', comparison: '=', value: nil) }
21
+
22
+ it 'does raises an ArgumentError' do
23
+ expect { subject }.to raise_error(ArgumentError, "Comparison of = requires value to be not nil")
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
33
+ end
34
+ end
35
+
36
+ context 'when comparison is IS NULL' do
37
+ subject { described_class.new(column: 'version', comparison: 'IS NULL') }
38
+
39
+ it 'has a nil value by default' do
40
+ expect(subject.value).to be_nil
41
+ end
42
+
43
+ context 'when a value is passed to the constructor' do
44
+ subject { described_class.new(column: 'version', comparison: 'IS NULL', value: 'foo') }
45
+
46
+ describe '#new' do
47
+ it 'raises an ArgumentError if value is set' do
48
+ expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NULL')
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
58
+ end
59
+ end
60
+
61
+ describe '#to_s' do
62
+ it 'does not include a value placeholder' do
63
+ expect(subject.to_s).to eq('version IS NULL')
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'when comparison is lowercase is null' do
69
+ subject { described_class.new(column: 'version', comparison: 'is null') }
70
+
71
+ describe '#to_s' do
72
+ it 'ignores case' do
73
+ expect(subject.to_s).to eq 'version IS NULL'
74
+ end
75
+ end
76
+ end
77
+
78
+ context 'when comparison is IS NOT NULL' do
79
+ subject { described_class.new(column: 'version', comparison: 'IS NOT NULL') }
80
+
81
+ it 'has a nil value by default' do
82
+ expect(subject.value).to be_nil
83
+ end
84
+
85
+ context 'when a value is passed to the constructor' do
86
+ subject { described_class.new(column: 'version', comparison: 'IS NOT NULL', value: 'foo') }
87
+
88
+ describe '#new' do
89
+ it 'raises an ArgumentError if value is set' do
90
+ expect { subject }.to raise_error(ArgumentError, 'Cannot set value when comparison is IS NOT NULL')
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
100
+ end
101
+ end
102
+
103
+ describe '#to_s' do
104
+ it 'does not include a value placeholder' do
105
+ expect(subject.to_s).to eq('version IS NOT NULL')
106
+ end
107
+ end
108
+ end
109
+
110
+ context 'when comparison is lowercase is not null' do
111
+ subject { described_class.new(column: 'version', comparison: 'is not null') }
112
+
113
+ describe '#to_s' do
114
+ it 'ignores case' do
115
+ expect(subject.to_s).to eq 'version IS NOT NULL'
116
+ end
117
+ end
118
+ end
11
119
  end
@@ -50,6 +50,19 @@ describe MysqlFramework::SqlQuery do
50
50
  expect(subject.params).to eq(['9876'])
51
51
  end
52
52
 
53
+ context 'when a select query contains conditions with nil values' do
54
+ it 'does not store them as parameters' do
55
+ subject.select('*')
56
+ .from(gems, 40)
57
+ .where(
58
+ MysqlFramework::SqlCondition.new(column: 'id', comparison: '=', value: 9876),
59
+ MysqlFramework::SqlCondition.new(column: 'foo', comparison: 'IS NOT NULL'),
60
+ )
61
+ expect(subject.sql).to eq('SELECT * FROM `gems` PARTITION (p40) WHERE (id = ? AND foo IS NOT NULL)')
62
+ expect(subject.params.size).to eq 1
63
+ end
64
+ end
65
+
53
66
  it 'builds a joined select query as expected' do
54
67
  subject.select('*')
55
68
  .from(gems, 40)
@@ -213,6 +226,13 @@ describe MysqlFramework::SqlQuery do
213
226
  expect(subject.sql).to eq('WHERE (`gems`.`author` = ? AND `gems`.`created_at` > ?) AND (`gems`.`name` = ?)')
214
227
  end
215
228
  end
229
+
230
+ context 'when the condition includes an array of parameters' do
231
+ it 'concats the parameter collections' do
232
+ subject.and.where(gems[:name].in('a','b'))
233
+ expect(subject.sql).to eq('WHERE (`gems`.`author` = ? AND `gems`.`created_at` > ?) AND (`gems`.`name` IN (?, ?))')
234
+ end
235
+ end
216
236
  end
217
237
 
218
238
  describe '#and' do
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.0.rc1
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sage
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-12 00:00:00.000000000 Z
11
+ date: 2021-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -133,12 +133,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
133
133
  version: '0'
134
134
  required_rubygems_version: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - ">"
136
+ - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: 1.3.1
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.