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 +4 -4
- data/lib/mysql_framework/connector.rb +13 -5
- data/lib/mysql_framework/sql_condition.rb +42 -1
- data/lib/mysql_framework/sql_query.rb +4 -1
- data/lib/mysql_framework/version.rb +1 -1
- data/spec/lib/mysql_framework/connector_spec.rb +45 -0
- data/spec/lib/mysql_framework/sql_condition_spec.rb +108 -0
- data/spec/lib/mysql_framework/sql_query_spec.rb +20 -0
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e296a8fc2b6937d61814f1b2722600dddcc767b88d234cdffe312137698fd0b5
|
4
|
+
data.tar.gz: 29a3f6549675f886369c5b42be8722fd026100d1717cb1d58ccce11624fc8088
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
81
|
-
|
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
|
-
|
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
|
-
# .
|
240
|
+
# .on_duplicate(
|
238
241
|
# {
|
239
242
|
# first_name: nil,
|
240
243
|
# login_count: 'login_count + 5'
|
@@ -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.
|
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:
|
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:
|
138
|
+
version: '0'
|
139
139
|
requirements: []
|
140
|
-
|
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.
|