mysql_framework 2.0.0.rc1 → 2.1.2

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: 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.