pg_helper 0.3.1 → 0.4.0
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 +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
|