pg_helper 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -0
- data/Gemfile +1 -1
- data/HISTORY.md +3 -0
- data/README.md +4 -0
- data/lib/pg_helper.rb +5 -4
- data/lib/pg_helper/connection_pool.rb +361 -0
- data/lib/pg_helper/query_builder.rb +80 -0
- data/lib/pg_helper/query_helper.rb +137 -148
- data/lib/pg_helper/support_classes.rb +44 -0
- data/lib/pg_helper/version.rb +2 -2
- data/pg_helper.gemspec +4 -2
- data/spec/lib/connection_pool_spec.rb +50 -0
- data/spec/lib/connection_pool_test.rb +310 -0
- data/spec/lib/pg_helper_spec.rb +147 -89
- data/spec/lib/query_builder_spec.rb +91 -0
- data/spec/spec_helper.rb +6 -0
- metadata +58 -40
data/spec/lib/pg_helper_spec.rb
CHANGED
@@ -1,192 +1,238 @@
|
|
1
1
|
require File.expand_path('../../spec_helper', __FILE__)
|
2
2
|
include PgHelper
|
3
|
-
|
3
|
+
|
4
|
+
require 'pg'
|
5
|
+
UNIQUE_TABLE_NAME = "pg_helper_test_#{(rand * 100_000_000).to_i}"
|
6
|
+
REAL_PARAMS = { host: 'localhost' }
|
7
|
+
RSpec.describe QueryHelper do
|
8
|
+
after(:all) do
|
9
|
+
conn = PGconn.open(REAL_PARAMS)
|
10
|
+
conn.exec("drop table if exists #{UNIQUE_TABLE_NAME} ") { true }
|
11
|
+
conn.finish
|
12
|
+
end
|
13
|
+
|
4
14
|
describe 'connection params' do
|
5
|
-
let(:params)
|
15
|
+
let(:params) do
|
6
16
|
{
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
17
|
+
host: 'my host',
|
18
|
+
port: 1234,
|
19
|
+
username: 'my user',
|
20
|
+
password: 'my password',
|
21
|
+
dbname: 'my base'
|
12
22
|
}
|
13
|
-
|
23
|
+
end
|
14
24
|
|
15
|
-
it 'are stored upon
|
16
|
-
PGconn.
|
17
|
-
QueryHelper.new(params).connection_params.
|
25
|
+
it 'are stored upon initialization' do
|
26
|
+
allow(PGconn).to receive(:open).and_return(true)
|
27
|
+
expect(QueryHelper.new(params).connection_params).to eq params
|
18
28
|
end
|
19
29
|
|
20
30
|
it 'are passed to pgconn' do
|
21
|
-
|
22
|
-
PGconn.
|
23
|
-
QueryHelper.new(params).pg_connection.
|
31
|
+
double(:conn).tap do |conn|
|
32
|
+
allow(PGconn).to receive(:open).with(params).and_return(conn)
|
33
|
+
expect(QueryHelper.new(params).pg_connection).to eq conn
|
24
34
|
end
|
25
35
|
end
|
26
36
|
end
|
27
37
|
|
28
|
-
def build_result(params={})
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
38
|
+
def build_result(params = {})
|
39
|
+
double(:result,
|
40
|
+
{
|
41
|
+
nfields: 1,
|
42
|
+
ntuples: 1
|
43
|
+
}.merge(params)
|
34
44
|
).as_null_object
|
35
45
|
end
|
36
|
-
|
37
|
-
def
|
38
|
-
build_result(params).tap do |
|
46
|
+
|
47
|
+
def double_result(params = {}, *args)
|
48
|
+
build_result(params).tap do |result_double|
|
39
49
|
if args.empty?
|
40
|
-
pg_helper.pg_connection
|
50
|
+
allow(pg_helper.pg_connection)
|
51
|
+
.to receive(:exec).and_return(result_double)
|
41
52
|
else
|
42
|
-
pg_helper.pg_connection
|
53
|
+
allow(pg_helper.pg_connection)
|
54
|
+
.to receive(:exec).with(*args).and_return(result_double)
|
43
55
|
end
|
44
56
|
end
|
45
57
|
end
|
46
58
|
|
47
|
-
|
59
|
+
def pg_helper
|
60
|
+
@pg_helper ||= QueryHelper.new(REAL_PARAMS)
|
61
|
+
end
|
48
62
|
|
49
63
|
describe 'single value' do
|
50
64
|
it 'is returned as string' do
|
51
|
-
pg_helper.value('select 1').
|
65
|
+
expect(pg_helper.value('select 1')).to eq '1'
|
52
66
|
end
|
53
67
|
|
54
68
|
it 'raises error if gets non array as params' do
|
55
|
-
|
69
|
+
expect do
|
56
70
|
pg_helper.value('select 1', 'bar')
|
57
|
-
|
71
|
+
end.to raise_error(PgHelperErrorParamsMustBeArrayOfStrings)
|
58
72
|
end
|
59
73
|
|
60
74
|
it 'allows to pass params to query' do
|
61
75
|
param = ["foo; > 'bar'"]
|
62
|
-
pg_helper.value('select $1::text', param).
|
76
|
+
expect(pg_helper.value('select $1::text', param)).to eq "foo; > 'bar'"
|
63
77
|
end
|
64
78
|
|
65
79
|
it 'raises error if more than 1 row returned' do
|
66
|
-
|
80
|
+
expect do
|
67
81
|
pg_helper.value('select 1, 2')
|
68
|
-
|
82
|
+
end.to raise_error(PgHelperErrorInvalidColumnCount)
|
69
83
|
end
|
70
84
|
|
71
85
|
it 'raises error if more than 1 row returned' do
|
72
|
-
|
86
|
+
expect do
|
73
87
|
pg_helper.value('select 1 union select 2')
|
74
|
-
|
88
|
+
end.to raise_error(PgHelperErrorInvalidRowCount)
|
75
89
|
end
|
76
90
|
|
77
91
|
it 'clears pg result on success' do
|
78
|
-
|
79
|
-
pg_helper.value('
|
92
|
+
expect(double_result).to receive(:clear)
|
93
|
+
pg_helper.value('select 1')
|
80
94
|
end
|
81
95
|
|
82
96
|
it 'clears pg result on failure' do
|
83
|
-
|
84
|
-
|
97
|
+
expect(double_result(nfields: 2))
|
98
|
+
.to receive(:clear).and_return(true)
|
99
|
+
expect {
|
85
100
|
pg_helper.value('foo')
|
86
|
-
}
|
101
|
+
}.to raise_error
|
87
102
|
end
|
88
103
|
end
|
89
104
|
|
90
105
|
describe 'array of column values' do
|
91
106
|
it 'returns values of column as array of stirngs' do
|
92
|
-
|
107
|
+
expect(
|
108
|
+
pg_helper.get_column(
|
109
|
+
'select 1 union (select 2 union select 3)'
|
110
|
+
)
|
111
|
+
).to eq %w(1 2 3)
|
93
112
|
end
|
94
113
|
|
95
114
|
it 'raises error if gets non array as params' do
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
115
|
+
expect do
|
116
|
+
pg_helper.get_column('select 1', 'bar')
|
117
|
+
end.to raise_error(PgHelperErrorParamsMustBeArrayOfStrings)
|
118
|
+
end
|
100
119
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
120
|
+
it 'allows to pass params to query' do
|
121
|
+
param = ['foo', ";'bar'"]
|
122
|
+
expect(
|
123
|
+
pg_helper.get_column(
|
124
|
+
'select $1::text as str union select $2::text as str order by str',
|
125
|
+
param
|
126
|
+
)
|
127
|
+
).to eq [";'bar'", 'foo']
|
128
|
+
end
|
105
129
|
|
106
130
|
it 'raises error if more than one column returned' do
|
107
|
-
|
131
|
+
expect do
|
108
132
|
pg_helper.get_column('select 1, 2')
|
109
|
-
|
133
|
+
end.to raise_error(PgHelperErrorInvalidColumnCount)
|
110
134
|
end
|
111
135
|
|
112
136
|
it 'clears pg result on success' do
|
113
|
-
|
137
|
+
expect(double_result).to receive(:clear)
|
114
138
|
pg_helper.get_column('foo')
|
115
139
|
end
|
116
140
|
|
117
141
|
it 'clears pg result on failure' do
|
118
|
-
lambda
|
119
|
-
|
142
|
+
lambda do
|
143
|
+
expect(double_result(nfields: 2)).to receive(:clear)
|
120
144
|
pg_helper.get_column('foo')
|
121
|
-
|
145
|
+
end
|
122
146
|
end
|
123
147
|
end
|
124
148
|
|
125
149
|
describe 'executing operation' do
|
126
|
-
|
127
|
-
|
150
|
+
before(:all) do
|
151
|
+
helper = pg_helper
|
152
|
+
helper.modify(<<-SQL)
|
153
|
+
CREATE TABLE IF NOT EXISTS #{UNIQUE_TABLE_NAME}
|
154
|
+
(
|
155
|
+
test_text text
|
156
|
+
)
|
157
|
+
SQL
|
158
|
+
helper.modify(
|
159
|
+
"INSERT INTO #{UNIQUE_TABLE_NAME} (test_text) values ('b')"
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns number of rows inserted' do
|
164
|
+
expect(pg_helper.modify(<<-SQL)).to eq 12
|
165
|
+
INSERT INTO #{UNIQUE_TABLE_NAME} (test_text)
|
166
|
+
select n::text
|
167
|
+
FROM generate_series(1,12,1) as n
|
168
|
+
SQL
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'returns number of rows updated by query' do
|
172
|
+
expect(pg_helper.modify(<<-SQL)).to eq 1
|
173
|
+
UPDATE #{UNIQUE_TABLE_NAME} SET test_text = 'c' where test_text = 'b'
|
174
|
+
SQL
|
128
175
|
end
|
129
176
|
|
130
177
|
it 'uses cmd_tuples of pg_result internally' do
|
131
|
-
|
132
|
-
|
133
|
-
pg_helper.modify('foo').
|
178
|
+
double(:value).tap do |value|
|
179
|
+
double_result(cmd_tuples: value)
|
180
|
+
expect(pg_helper.modify('foo')).to eq value
|
134
181
|
end
|
135
182
|
end
|
136
183
|
|
137
184
|
it 'raises error if gets non array as params' do
|
138
|
-
|
185
|
+
expect do
|
139
186
|
pg_helper.modify('select 1', 'bar')
|
140
|
-
|
187
|
+
end.to raise_error(PgHelperErrorParamsMustBeArrayOfStrings)
|
141
188
|
end
|
142
189
|
|
143
190
|
it 'allows to pass params to query' do
|
144
191
|
sql = 'update foo set test_text = $1::text'
|
145
|
-
|
146
|
-
|
147
|
-
pg_helper.modify(sql, [
|
192
|
+
double(:value).tap do |result|
|
193
|
+
double_result({ cmd_tuples: result }, sql, ['foo'])
|
194
|
+
expect(pg_helper.modify(sql, ['foo'])).to eq result
|
148
195
|
end
|
149
196
|
end
|
150
197
|
|
151
|
-
|
152
198
|
it 'clears pg result on success' do
|
153
|
-
|
199
|
+
expect(double_result).to receive(:clear)
|
154
200
|
pg_helper.modify('foo')
|
155
201
|
end
|
156
202
|
|
157
203
|
it 'clears pg result on failure' do
|
158
|
-
lambda
|
159
|
-
|
160
|
-
result.
|
161
|
-
result.
|
204
|
+
lambda do
|
205
|
+
double_result.tap do |result|
|
206
|
+
expect(result).to receive(:cmd_tuples).and_raise(Exception)
|
207
|
+
expect(result).to receive(:clear)
|
162
208
|
end
|
163
209
|
pg_helper.modify('foo')
|
164
|
-
|
210
|
+
end
|
165
211
|
end
|
166
212
|
end
|
167
213
|
|
168
214
|
describe 'transaction' do
|
169
215
|
it 'raises error if no block given' do
|
170
|
-
|
216
|
+
expect { pg_helper.transaction }.to raise_error(ArgumentError)
|
171
217
|
end
|
172
218
|
|
173
219
|
it 'allows to rollback' do
|
174
|
-
pg_helper.transaction
|
175
|
-
t.rollback!
|
176
|
-
end
|
220
|
+
pg_helper.transaction(&:rollback!)
|
177
221
|
end
|
178
222
|
|
179
223
|
it 'does not allow rollback if not in transaction' do
|
180
|
-
|
224
|
+
expect { pg_helper.rollback! }
|
225
|
+
.to raise_error(PgHelperErrorInvalidOutsideTransaction)
|
181
226
|
end
|
182
227
|
|
183
|
-
|
184
228
|
describe 'using temporary table' do
|
185
|
-
|
229
|
+
def test_table_name
|
230
|
+
@test_table_name ||= 'pg_helper_test_' + Time.now.to_i.to_s
|
231
|
+
end
|
186
232
|
|
187
233
|
before(:all) do
|
188
234
|
sql = <<SQL
|
189
|
-
CREATE
|
235
|
+
CREATE TABLE #{test_table_name}
|
190
236
|
(
|
191
237
|
test_text text
|
192
238
|
)
|
@@ -199,14 +245,22 @@ SQL
|
|
199
245
|
end
|
200
246
|
|
201
247
|
it 'will rollback on failure' do
|
202
|
-
|
248
|
+
expect do
|
203
249
|
pg_helper.transaction do |t|
|
204
|
-
t.modify(
|
205
|
-
|
206
|
-
|
250
|
+
t.modify(
|
251
|
+
"INSERT INTO #{test_table_name} "\
|
252
|
+
"VALUES ('one'), ('two'), ('three')"
|
253
|
+
)
|
254
|
+
expect(
|
255
|
+
t.value("SELECT COUNT(*) FROM #{test_table_name}")
|
256
|
+
).to eq '3'
|
257
|
+
fail Exception, 'roll it back'
|
207
258
|
end
|
208
|
-
|
209
|
-
|
259
|
+
end.to raise_error('roll it back')
|
260
|
+
|
261
|
+
expect(
|
262
|
+
pg_helper.value("SELECT COUNT(*) FROM #{test_table_name}")
|
263
|
+
).to eq '0'
|
210
264
|
end
|
211
265
|
|
212
266
|
it 'will commit in the end' do
|
@@ -214,16 +268,20 @@ SQL
|
|
214
268
|
t.modify("INSERT INTO #{test_table_name} VALUES ('pass')")
|
215
269
|
t.modify("INSERT INTO #{test_table_name} VALUES ('correct')")
|
216
270
|
end
|
217
|
-
|
271
|
+
expect(
|
272
|
+
pg_helper.get_column(
|
273
|
+
"SELECT test_text FROM #{test_table_name} order by test_text"
|
274
|
+
)
|
275
|
+
).to eq %w(correct pass)
|
218
276
|
end
|
219
277
|
end
|
220
278
|
|
221
279
|
it 'will not allow nested transaction' do
|
222
|
-
|
280
|
+
expect do
|
223
281
|
pg_helper.transaction do |trans|
|
224
|
-
trans.transaction {nil}
|
282
|
+
trans.transaction { nil }
|
225
283
|
end
|
226
|
-
|
284
|
+
end.to raise_error(PgHelperErrorNestedTransactionNotAllowed)
|
227
285
|
end
|
228
286
|
end
|
229
287
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
require 'pg_helper/query_builder.rb'
|
3
|
+
|
4
|
+
include PgHelper
|
5
|
+
RSpec.describe QueryBuilder do
|
6
|
+
subject { QueryBuilder.new('table_name') }
|
7
|
+
|
8
|
+
describe '#to_sql' do
|
9
|
+
|
10
|
+
it 'builds default' do
|
11
|
+
expect(subject.to_sql).to eq('SELECT * FROM table_name')
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#select' do
|
15
|
+
it 'for specific column' do
|
16
|
+
expect(
|
17
|
+
subject.select('a').to_sql
|
18
|
+
).to eq 'SELECT a FROM table_name'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'for multiple columns' do
|
22
|
+
expect(
|
23
|
+
subject
|
24
|
+
.select('a as foo')
|
25
|
+
.select('b as bar, c')
|
26
|
+
.to_sql
|
27
|
+
).to eq('SELECT a as foo,b as bar, c FROM table_name')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#where' do
|
32
|
+
it 'using single condition' do
|
33
|
+
expect(
|
34
|
+
subject.where('a = b').to_sql
|
35
|
+
).to eq('SELECT * FROM table_name WHERE a = b')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'ands multiple conditions' do
|
39
|
+
expect(
|
40
|
+
subject
|
41
|
+
.where('a = 1')
|
42
|
+
.where('b = 2')
|
43
|
+
.to_sql
|
44
|
+
).to eq('SELECT * FROM table_name WHERE a = 1 AND b = 2')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#with' do
|
49
|
+
it 'single CTE' do
|
50
|
+
expect(
|
51
|
+
subject
|
52
|
+
.with('foo', 'select bar from foo')
|
53
|
+
.to_sql
|
54
|
+
).to eq('WITH foo AS (select bar from foo) SELECT * FROM table_name')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'multiple CTEs' do
|
58
|
+
expect(
|
59
|
+
subject
|
60
|
+
.with('foo', 'select 1')
|
61
|
+
.with('bar', 'select a')
|
62
|
+
.to_sql
|
63
|
+
).to eq('WITH foo AS (select 1),'\
|
64
|
+
'bar AS (select a) '\
|
65
|
+
'SELECT * FROM table_name')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#join' do
|
70
|
+
it 'can join single table' do
|
71
|
+
expect(
|
72
|
+
subject
|
73
|
+
.join('LEFT JOIN bar on bar.id = table_name.id')
|
74
|
+
.to_sql
|
75
|
+
).to eq('SELECT * FROM table_name ' \
|
76
|
+
'LEFT JOIN bar on bar.id = table_name.id')
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'can do multiple joins' do
|
80
|
+
expect(
|
81
|
+
subject
|
82
|
+
.join('CROSS JOIN bar')
|
83
|
+
.join('INNER JOIN foo using (buz)')
|
84
|
+
.to_sql
|
85
|
+
).to eq('SELECT * FROM table_name '\
|
86
|
+
'CROSS JOIN bar ' \
|
87
|
+
'INNER JOIN foo using (buz)')
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|