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