pg 0.17.1 → 0.18.4
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
- checksums.yaml.gz.sig +0 -0
- data/ChangeLog +2407 -2
- data/History.rdoc +68 -0
- data/Manifest.txt +29 -1
- data/README-Windows.rdoc +15 -26
- data/README.rdoc +52 -2
- data/Rakefile +56 -18
- data/Rakefile.cross +77 -49
- data/ext/extconf.rb +33 -26
- data/ext/pg.c +142 -21
- data/ext/pg.h +242 -6
- data/ext/pg_binary_decoder.c +162 -0
- data/ext/pg_binary_encoder.c +162 -0
- data/ext/pg_coder.c +479 -0
- data/ext/pg_connection.c +858 -553
- data/ext/pg_copy_coder.c +561 -0
- data/ext/pg_errors.c +6 -0
- data/ext/pg_result.c +479 -128
- data/ext/pg_text_decoder.c +421 -0
- data/ext/pg_text_encoder.c +663 -0
- data/ext/pg_type_map.c +159 -0
- data/ext/pg_type_map_all_strings.c +116 -0
- data/ext/pg_type_map_by_class.c +239 -0
- data/ext/pg_type_map_by_column.c +312 -0
- data/ext/pg_type_map_by_mri_type.c +284 -0
- data/ext/pg_type_map_by_oid.c +355 -0
- data/ext/pg_type_map_in_ruby.c +299 -0
- data/ext/util.c +149 -0
- data/ext/util.h +65 -0
- data/lib/pg/basic_type_mapping.rb +399 -0
- data/lib/pg/coder.rb +83 -0
- data/lib/pg/connection.rb +81 -29
- data/lib/pg/result.rb +13 -3
- data/lib/pg/text_decoder.rb +44 -0
- data/lib/pg/text_encoder.rb +27 -0
- data/lib/pg/type_map_by_column.rb +15 -0
- data/lib/pg.rb +12 -2
- data/spec/{lib/helpers.rb → helpers.rb} +101 -39
- data/spec/pg/basic_type_mapping_spec.rb +251 -0
- data/spec/pg/connection_spec.rb +516 -218
- data/spec/pg/result_spec.rb +216 -112
- data/spec/pg/type_map_by_class_spec.rb +138 -0
- data/spec/pg/type_map_by_column_spec.rb +222 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
- data/spec/pg/type_map_by_oid_spec.rb +149 -0
- data/spec/pg/type_map_in_ruby_spec.rb +164 -0
- data/spec/pg/type_map_spec.rb +22 -0
- data/spec/pg/type_spec.rb +697 -0
- data/spec/pg_spec.rb +24 -18
- data.tar.gz.sig +0 -0
- metadata +111 -45
- metadata.gz.sig +0 -0
data/spec/pg/connection_spec.rb
CHANGED
@@ -1,49 +1,14 @@
|
|
1
1
|
#!/usr/bin/env rspec
|
2
2
|
#encoding: utf-8
|
3
3
|
|
4
|
-
|
5
|
-
require 'pathname'
|
4
|
+
require_relative '../helpers'
|
6
5
|
|
7
|
-
basedir = Pathname( __FILE__ ).dirname.parent.parent
|
8
|
-
libdir = basedir + 'lib'
|
9
|
-
|
10
|
-
$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
|
11
|
-
$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
|
12
|
-
}
|
13
|
-
|
14
|
-
require 'rspec'
|
15
|
-
require 'spec/lib/helpers'
|
16
6
|
require 'timeout'
|
17
7
|
require 'socket'
|
18
8
|
require 'pg'
|
19
9
|
|
20
10
|
describe PG::Connection do
|
21
11
|
|
22
|
-
before( :all ) do
|
23
|
-
@conn = setup_testing_db( described_class.name )
|
24
|
-
end
|
25
|
-
|
26
|
-
before( :each ) do
|
27
|
-
@conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
|
28
|
-
if PG.respond_to?( :library_version )
|
29
|
-
@conn.exec_params %Q{SET application_name TO '%s'} %
|
30
|
-
[@conn.escape_string(example.description[0,60])]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
after( :each ) do
|
35
|
-
@conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
|
36
|
-
end
|
37
|
-
|
38
|
-
after( :all ) do
|
39
|
-
teardown_testing_db( @conn )
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
# Examples
|
45
|
-
#
|
46
|
-
|
47
12
|
it "can create a connection option string from a Hash of options" do
|
48
13
|
optstring = described_class.parse_connect_args(
|
49
14
|
:host => 'pgsql.example.com',
|
@@ -51,54 +16,117 @@ describe PG::Connection do
|
|
51
16
|
'sslmode' => 'require'
|
52
17
|
)
|
53
18
|
|
54
|
-
optstring.
|
55
|
-
optstring.
|
56
|
-
optstring.
|
57
|
-
optstring.
|
19
|
+
expect( optstring ).to be_a( String )
|
20
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
21
|
+
expect( optstring ).to match( /(^|\s)dbname='db01'/ )
|
22
|
+
expect( optstring ).to match( /(^|\s)sslmode='require'/ )
|
58
23
|
end
|
59
24
|
|
60
25
|
it "can create a connection option string from positional parameters" do
|
61
26
|
optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
|
62
27
|
'sales' )
|
63
28
|
|
64
|
-
optstring.
|
65
|
-
optstring.
|
66
|
-
optstring.
|
67
|
-
optstring.
|
29
|
+
expect( optstring ).to be_a( String )
|
30
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
31
|
+
expect( optstring ).to match( /(^|\s)dbname='sales'/ )
|
32
|
+
expect( optstring ).to match( /(^|\s)options='-c geqo=off'/ )
|
68
33
|
|
69
|
-
optstring.
|
70
|
-
optstring.
|
34
|
+
expect( optstring ).to_not match( /port=/ )
|
35
|
+
expect( optstring ).to_not match( /tty=/ )
|
71
36
|
end
|
72
37
|
|
73
38
|
it "can create a connection option string from a mix of positional and hash parameters" do
|
74
39
|
optstring = described_class.parse_connect_args( 'pgsql.example.com',
|
75
40
|
:dbname => 'licensing', :user => 'jrandom' )
|
76
41
|
|
77
|
-
optstring.
|
78
|
-
optstring.
|
79
|
-
optstring.
|
80
|
-
optstring.
|
42
|
+
expect( optstring ).to be_a( String )
|
43
|
+
expect( optstring ).to match( /(^|\s)host='pgsql.example.com'/ )
|
44
|
+
expect( optstring ).to match( /(^|\s)dbname='licensing'/ )
|
45
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
46
|
+
end
|
47
|
+
|
48
|
+
it "can create a connection option string from an option string and a hash" do
|
49
|
+
optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
|
50
|
+
|
51
|
+
expect( optstring ).to be_a( String )
|
52
|
+
expect( optstring ).to match( /(^|\s)dbname=original/ )
|
53
|
+
expect( optstring ).to match( /(^|\s)user='jrandom'/ )
|
81
54
|
end
|
82
55
|
|
83
56
|
it "escapes single quotes and backslashes in connection parameters" do
|
84
|
-
|
85
|
-
|
57
|
+
expect(
|
58
|
+
described_class.parse_connect_args( "DB 'browser' \\" )
|
59
|
+
).to match( /host='DB \\'browser\\' \\\\'/ )
|
86
60
|
|
87
61
|
end
|
88
62
|
|
63
|
+
let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
|
64
|
+
|
65
|
+
it "can connect using a URI" do
|
66
|
+
string = described_class.parse_connect_args( uri )
|
67
|
+
|
68
|
+
expect( string ).to be_a( String )
|
69
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
70
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
71
|
+
|
72
|
+
string = described_class.parse_connect_args( URI.parse(uri) )
|
73
|
+
|
74
|
+
expect( string ).to be_a( String )
|
75
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
76
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
77
|
+
end
|
78
|
+
|
79
|
+
it "can create a connection URI from a URI and a hash" do
|
80
|
+
string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
|
81
|
+
|
82
|
+
expect( string ).to be_a( String )
|
83
|
+
expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
|
84
|
+
expect( string ).to match( %r{\?.*sslmode=require} )
|
85
|
+
expect( string ).to match( %r{\?.*connect_timeout=2} )
|
86
|
+
|
87
|
+
string = described_class.parse_connect_args( uri,
|
88
|
+
:user => 'a',
|
89
|
+
:password => 'b',
|
90
|
+
:host => 'localhost',
|
91
|
+
:port => 555,
|
92
|
+
:dbname => 'x' )
|
93
|
+
|
94
|
+
expect( string ).to be_a( String )
|
95
|
+
expect( string ).to match( %r{^postgresql://\?} )
|
96
|
+
expect( string ).to match( %r{\?.*user=a} )
|
97
|
+
expect( string ).to match( %r{\?.*password=b} )
|
98
|
+
expect( string ).to match( %r{\?.*host=localhost} )
|
99
|
+
expect( string ).to match( %r{\?.*port=555} )
|
100
|
+
expect( string ).to match( %r{\?.*dbname=x} )
|
101
|
+
end
|
102
|
+
|
103
|
+
it "can create a connection URI with a non-standard domain socket directory" do
|
104
|
+
string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
|
105
|
+
|
106
|
+
expect( string ).to be_a( String )
|
107
|
+
expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
|
108
|
+
|
109
|
+
string = described_class.
|
110
|
+
parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
|
111
|
+
|
112
|
+
expect( string ).to be_a( String )
|
113
|
+
expect( string ).to match( %r{^postgresql:///dbname\?} )
|
114
|
+
expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
|
115
|
+
end
|
116
|
+
|
89
117
|
it "connects with defaults if no connection parameters are given" do
|
90
|
-
described_class.parse_connect_args.
|
118
|
+
expect( described_class.parse_connect_args ).to eq( '' )
|
91
119
|
end
|
92
120
|
|
93
121
|
it "connects successfully with connection string" do
|
94
|
-
tmpconn = described_class.connect(@conninfo)
|
95
|
-
tmpconn.status.
|
122
|
+
tmpconn = described_class.connect( @conninfo )
|
123
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
96
124
|
tmpconn.finish
|
97
125
|
end
|
98
126
|
|
99
127
|
it "connects using 7 arguments converted to strings" do
|
100
|
-
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
101
|
-
tmpconn.status.
|
128
|
+
tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
|
129
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
102
130
|
tmpconn.finish
|
103
131
|
end
|
104
132
|
|
@@ -107,7 +135,7 @@ describe PG::Connection do
|
|
107
135
|
:host => 'localhost',
|
108
136
|
:port => @port,
|
109
137
|
:dbname => :test)
|
110
|
-
tmpconn.status.
|
138
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
111
139
|
tmpconn.finish
|
112
140
|
end
|
113
141
|
|
@@ -117,19 +145,24 @@ describe PG::Connection do
|
|
117
145
|
:port => @port,
|
118
146
|
:dbname => :test,
|
119
147
|
:keepalives => 1)
|
120
|
-
tmpconn.status.
|
148
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
121
149
|
tmpconn.finish
|
122
150
|
end
|
123
151
|
|
124
152
|
it "raises an exception when connecting with an invalid number of arguments" do
|
125
153
|
expect {
|
126
|
-
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
|
127
|
-
}.to raise_error
|
154
|
+
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
|
155
|
+
}.to raise_error do |error|
|
156
|
+
expect( error ).to be_an( ArgumentError )
|
157
|
+
expect( error.message ).to match( /extra positional parameter/i )
|
158
|
+
expect( error.message ).to match( /8/ )
|
159
|
+
expect( error.message ).to match( /the-extra-arg/ )
|
160
|
+
end
|
128
161
|
end
|
129
162
|
|
130
163
|
it "can connect asynchronously", :socket_io do
|
131
164
|
tmpconn = described_class.connect_start( @conninfo )
|
132
|
-
tmpconn.
|
165
|
+
expect( tmpconn ).to be_a( described_class )
|
133
166
|
socket = tmpconn.socket_io
|
134
167
|
status = tmpconn.connect_poll
|
135
168
|
|
@@ -145,7 +178,7 @@ describe PG::Connection do
|
|
145
178
|
status = tmpconn.connect_poll
|
146
179
|
end
|
147
180
|
|
148
|
-
tmpconn.status.
|
181
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
149
182
|
tmpconn.finish
|
150
183
|
end
|
151
184
|
|
@@ -153,7 +186,7 @@ describe PG::Connection do
|
|
153
186
|
conn = nil
|
154
187
|
|
155
188
|
described_class.connect_start(@conninfo) do |tmpconn|
|
156
|
-
tmpconn.
|
189
|
+
expect( tmpconn ).to be_a( described_class )
|
157
190
|
conn = tmpconn
|
158
191
|
socket = tmpconn.socket_io
|
159
192
|
status = tmpconn.connect_poll
|
@@ -171,10 +204,10 @@ describe PG::Connection do
|
|
171
204
|
status = tmpconn.connect_poll
|
172
205
|
end
|
173
206
|
|
174
|
-
tmpconn.status.
|
207
|
+
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
175
208
|
end
|
176
209
|
|
177
|
-
conn.
|
210
|
+
expect( conn ).to be_finished()
|
178
211
|
end
|
179
212
|
|
180
213
|
it "raises proper error when sending fails" do
|
@@ -188,9 +221,21 @@ describe PG::Connection do
|
|
188
221
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
189
222
|
WHERE usename IS NOT NULL])
|
190
223
|
# there's still the global @conn, but should be no more
|
191
|
-
res[0]['n'].
|
224
|
+
expect( res[0]['n'] ).to eq( '1' )
|
192
225
|
end
|
193
226
|
|
227
|
+
it "can retrieve it's connection parameters for the established connection" do
|
228
|
+
expect( @conn.db ).to eq( "test" )
|
229
|
+
expect( @conn.user ).to be_a_kind_of( String )
|
230
|
+
expect( @conn.pass ).to eq( "" )
|
231
|
+
expect( @conn.port ).to eq( 54321 )
|
232
|
+
expect( @conn.tty ).to eq( "" )
|
233
|
+
expect( @conn.options ).to eq( "" )
|
234
|
+
end
|
235
|
+
it "can retrieve it's connection parameters for the established connection",
|
236
|
+
skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
|
237
|
+
expect( @conn.host ).to eq( "localhost" )
|
238
|
+
end
|
194
239
|
|
195
240
|
EXPECTED_TRACE_OUTPUT = %{
|
196
241
|
To backend> Msg Q
|
@@ -248,7 +293,7 @@ describe PG::Connection do
|
|
248
293
|
expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
|
249
294
|
end
|
250
295
|
|
251
|
-
trace_data.
|
296
|
+
expect( trace_data ).to eq( expected_trace_output )
|
252
297
|
end
|
253
298
|
|
254
299
|
it "allows a query to be cancelled" do
|
@@ -259,10 +304,12 @@ describe PG::Connection do
|
|
259
304
|
if(tmpres.result_status != PG::PGRES_TUPLES_OK)
|
260
305
|
error = true
|
261
306
|
end
|
262
|
-
error.
|
307
|
+
expect( error ).to eq( true )
|
263
308
|
end
|
264
309
|
|
265
310
|
it "can stop a thread that runs a blocking query with async_exec" do
|
311
|
+
pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
|
312
|
+
|
266
313
|
start = Time.now
|
267
314
|
t = Thread.new do
|
268
315
|
@conn.async_exec( 'select pg_sleep(10)' )
|
@@ -271,10 +318,10 @@ describe PG::Connection do
|
|
271
318
|
|
272
319
|
t.kill
|
273
320
|
t.join
|
274
|
-
(Time.now - start).
|
321
|
+
expect( (Time.now - start) ).to be < 10
|
275
322
|
end
|
276
323
|
|
277
|
-
it "should work together with signal handlers" do
|
324
|
+
it "should work together with signal handlers", :unix do
|
278
325
|
signal_received = false
|
279
326
|
trap 'USR1' do
|
280
327
|
signal_received = true
|
@@ -285,7 +332,7 @@ describe PG::Connection do
|
|
285
332
|
Process.kill("USR1", Process.pid)
|
286
333
|
end
|
287
334
|
@conn.exec("select pg_sleep(0.3)")
|
288
|
-
signal_received.
|
335
|
+
expect( signal_received ).to be_truthy
|
289
336
|
|
290
337
|
signal_received = false
|
291
338
|
Thread.new do
|
@@ -293,7 +340,7 @@ describe PG::Connection do
|
|
293
340
|
Process.kill("USR1", Process.pid)
|
294
341
|
end
|
295
342
|
@conn.async_exec("select pg_sleep(0.3)")
|
296
|
-
signal_received.
|
343
|
+
expect( signal_received ).to be_truthy
|
297
344
|
end
|
298
345
|
|
299
346
|
|
@@ -305,15 +352,19 @@ describe PG::Connection do
|
|
305
352
|
res = nil
|
306
353
|
@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
|
307
354
|
|
308
|
-
|
309
|
-
|
310
|
-
@conn.
|
311
|
-
|
312
|
-
|
313
|
-
|
355
|
+
begin
|
356
|
+
expect {
|
357
|
+
res = @conn.transaction do
|
358
|
+
@conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
|
359
|
+
raise "Oh noes! All pie is gone!"
|
360
|
+
end
|
361
|
+
}.to raise_exception( RuntimeError, /all pie is gone/i )
|
314
362
|
|
315
|
-
|
316
|
-
|
363
|
+
res = @conn.exec( "SELECT * FROM pie" )
|
364
|
+
expect( res.ntuples ).to eq( 0 )
|
365
|
+
ensure
|
366
|
+
@conn.exec( "DROP TABLE pie" )
|
367
|
+
end
|
317
368
|
end
|
318
369
|
|
319
370
|
it "returns the block result from Connection#transaction" do
|
@@ -323,7 +374,7 @@ describe PG::Connection do
|
|
323
374
|
res = @conn.transaction do
|
324
375
|
"transaction result"
|
325
376
|
end
|
326
|
-
res.
|
377
|
+
expect( res ).to eq( "transaction result" )
|
327
378
|
end
|
328
379
|
|
329
380
|
it "not read past the end of a large object" do
|
@@ -331,9 +382,9 @@ describe PG::Connection do
|
|
331
382
|
oid = @conn.lo_create( 0 )
|
332
383
|
fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
|
333
384
|
@conn.lo_write( fd, "foobar" )
|
334
|
-
@conn.lo_read( fd, 10 ).
|
385
|
+
expect( @conn.lo_read( fd, 10 ) ).to be_nil()
|
335
386
|
@conn.lo_lseek( fd, 0, PG::SEEK_SET )
|
336
|
-
@conn.lo_read( fd, 10 ).
|
387
|
+
expect( @conn.lo_read( fd, 10 ) ).to eq( 'foobar' )
|
337
388
|
end
|
338
389
|
end
|
339
390
|
|
@@ -345,7 +396,7 @@ describe PG::Connection do
|
|
345
396
|
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
346
397
|
|
347
398
|
res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
|
348
|
-
res.values.
|
399
|
+
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
349
400
|
end
|
350
401
|
|
351
402
|
it "supports explicitly calling #exec_params" do
|
@@ -355,9 +406,32 @@ describe PG::Connection do
|
|
355
406
|
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
356
407
|
|
357
408
|
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
358
|
-
res.values.
|
409
|
+
expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
|
359
410
|
end
|
360
411
|
|
412
|
+
it "supports hash form parameters for #exec_params" do
|
413
|
+
hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
|
414
|
+
hash_param_nil = { value: nil, type: 17, format: 1 }
|
415
|
+
res = @conn.exec_params( "SELECT $1, $2",
|
416
|
+
[ hash_param_bin, hash_param_nil ] )
|
417
|
+
expect( res.values ).to eq( [["\\x00ff", nil]] )
|
418
|
+
expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
|
419
|
+
end
|
420
|
+
|
421
|
+
it "should work with arbitrary number of params" do
|
422
|
+
begin
|
423
|
+
3.step( 12, 0.2 ) do |exp|
|
424
|
+
num_params = (2 ** exp).to_i
|
425
|
+
sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
|
426
|
+
params = num_params.times.to_a
|
427
|
+
res = @conn.exec_params( "SELECT #{sql}", params )
|
428
|
+
expect( res.nfields ).to eq( num_params )
|
429
|
+
expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
|
430
|
+
end
|
431
|
+
rescue PG::ProgramLimitExceeded
|
432
|
+
# Stop silently if the server complains about too many params
|
433
|
+
end
|
434
|
+
end
|
361
435
|
|
362
436
|
it "can wait for NOTIFY events" do
|
363
437
|
@conn.exec( 'ROLLBACK' )
|
@@ -373,7 +447,7 @@ describe PG::Connection do
|
|
373
447
|
end
|
374
448
|
end
|
375
449
|
|
376
|
-
@conn.wait_for_notify( 10 ).
|
450
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
377
451
|
@conn.exec( 'UNLISTEN woo' )
|
378
452
|
|
379
453
|
t.join
|
@@ -395,8 +469,8 @@ describe PG::Connection do
|
|
395
469
|
|
396
470
|
eventpid = event = nil
|
397
471
|
@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
|
398
|
-
event.
|
399
|
-
eventpid.
|
472
|
+
expect( event ).to eq( 'woo' )
|
473
|
+
expect( eventpid ).to be_an( Integer )
|
400
474
|
|
401
475
|
@conn.exec( 'UNLISTEN woo' )
|
402
476
|
|
@@ -423,8 +497,8 @@ describe PG::Connection do
|
|
423
497
|
channels << @conn.wait_for_notify( 2 )
|
424
498
|
end
|
425
499
|
|
426
|
-
channels.
|
427
|
-
channels.
|
500
|
+
expect( channels.size ).to eq( 3 )
|
501
|
+
expect( channels ).to include( 'woo', 'war', 'woz' )
|
428
502
|
|
429
503
|
@conn.exec( 'UNLISTEN woz' )
|
430
504
|
@conn.exec( 'UNLISTEN war' )
|
@@ -446,7 +520,7 @@ describe PG::Connection do
|
|
446
520
|
# Cause the notification to buffer, but not be read yet
|
447
521
|
@conn.exec( 'SELECT 1' )
|
448
522
|
|
449
|
-
@conn.wait_for_notify( 10 ).
|
523
|
+
expect( @conn.wait_for_notify( 10 ) ).to eq( 'woo' )
|
450
524
|
@conn.exec( 'UNLISTEN woo' )
|
451
525
|
end
|
452
526
|
|
@@ -457,41 +531,41 @@ describe PG::Connection do
|
|
457
531
|
end
|
458
532
|
st = Time.now
|
459
533
|
@conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
|
460
|
-
@conn.wait_for_notify( 1 ).
|
461
|
-
notices.first.
|
534
|
+
expect( @conn.wait_for_notify( 1 ) ).to be_nil
|
535
|
+
expect( notices.first ).to_not be_nil
|
462
536
|
et = Time.now
|
463
|
-
(et - notices.first[1]).
|
464
|
-
(et - st).
|
465
|
-
(et - st).
|
537
|
+
expect( (et - notices.first[1]) ).to be >= 0.4
|
538
|
+
expect( (et - st) ).to be >= 0.9
|
539
|
+
expect( (et - st) ).to be < 1.4
|
466
540
|
end
|
467
541
|
|
468
542
|
it "yields the result if block is given to exec" do
|
469
543
|
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
470
544
|
values = []
|
471
|
-
result.
|
472
|
-
result.ntuples.
|
545
|
+
expect( result ).to be_kind_of( PG::Result )
|
546
|
+
expect( result.ntuples ).to eq( 2 )
|
473
547
|
result.each do |tuple|
|
474
548
|
values << tuple['a']
|
475
549
|
end
|
476
550
|
values
|
477
551
|
end
|
478
552
|
|
479
|
-
rval.
|
480
|
-
rval.
|
553
|
+
expect( rval.size ).to eq( 2 )
|
554
|
+
expect( rval ).to include( '5678', '1234' )
|
481
555
|
end
|
482
556
|
|
483
557
|
it "can process #copy_data output queries" do
|
484
558
|
rows = []
|
485
559
|
res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
|
486
|
-
res.result_status.
|
487
|
-
res.nfields.
|
560
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_OUT )
|
561
|
+
expect( res.nfields ).to eq( 1 )
|
488
562
|
while row=@conn.get_copy_data
|
489
563
|
rows << row
|
490
564
|
end
|
491
565
|
end
|
492
|
-
rows.
|
493
|
-
res2.result_status.
|
494
|
-
|
566
|
+
expect( rows ).to eq( ["1\n", "2\n"] )
|
567
|
+
expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
|
568
|
+
expect( @conn ).to still_be_usable
|
495
569
|
end
|
496
570
|
|
497
571
|
it "can handle incomplete #copy_data output queries" do
|
@@ -500,7 +574,7 @@ describe PG::Connection do
|
|
500
574
|
@conn.get_copy_data
|
501
575
|
end
|
502
576
|
}.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
|
503
|
-
|
577
|
+
expect( @conn ).to still_be_usable
|
504
578
|
end
|
505
579
|
|
506
580
|
it "can handle client errors in #copy_data for output" do
|
@@ -509,10 +583,10 @@ describe PG::Connection do
|
|
509
583
|
raise "boom"
|
510
584
|
end
|
511
585
|
}.to raise_error(RuntimeError, "boom")
|
512
|
-
|
586
|
+
expect( @conn ).to still_be_usable
|
513
587
|
end
|
514
588
|
|
515
|
-
it "can handle server errors in #copy_data for output" do
|
589
|
+
it "can handle server errors in #copy_data for output", :postgresql_90 do
|
516
590
|
@conn.exec "ROLLBACK"
|
517
591
|
@conn.transaction do
|
518
592
|
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
@@ -523,23 +597,23 @@ describe PG::Connection do
|
|
523
597
|
end
|
524
598
|
}.to raise_error(PG::Error, /test-error/)
|
525
599
|
end
|
526
|
-
|
600
|
+
expect( @conn ).to still_be_usable
|
527
601
|
end
|
528
602
|
|
529
603
|
it "can process #copy_data input queries" do
|
530
604
|
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
531
605
|
res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
532
|
-
res.result_status.
|
533
|
-
res.nfields.
|
606
|
+
expect( res.result_status ).to eq( PG::PGRES_COPY_IN )
|
607
|
+
expect( res.nfields ).to eq( 1 )
|
534
608
|
@conn.put_copy_data "1\n"
|
535
609
|
@conn.put_copy_data "2\n"
|
536
610
|
end
|
537
|
-
res2.result_status.
|
611
|
+
expect( res2.result_status ).to eq( PG::PGRES_COMMAND_OK )
|
538
612
|
|
539
|
-
|
613
|
+
expect( @conn ).to still_be_usable
|
540
614
|
|
541
615
|
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
542
|
-
res.values.
|
616
|
+
expect( res.values ).to eq( [["1"], ["2"]] )
|
543
617
|
end
|
544
618
|
|
545
619
|
it "can handle client errors in #copy_data for input" do
|
@@ -552,7 +626,8 @@ describe PG::Connection do
|
|
552
626
|
end
|
553
627
|
}.to raise_error(RuntimeError, "boom")
|
554
628
|
end
|
555
|
-
|
629
|
+
|
630
|
+
expect( @conn ).to still_be_usable
|
556
631
|
end
|
557
632
|
|
558
633
|
it "can handle server errors in #copy_data for input" do
|
@@ -565,7 +640,7 @@ describe PG::Connection do
|
|
565
640
|
end
|
566
641
|
}.to raise_error(PG::Error, /invalid input syntax for integer/)
|
567
642
|
end
|
568
|
-
|
643
|
+
expect( @conn ).to still_be_usable
|
569
644
|
end
|
570
645
|
|
571
646
|
it "should raise an error for non copy statements in #copy_data" do
|
@@ -573,7 +648,7 @@ describe PG::Connection do
|
|
573
648
|
@conn.copy_data( "SELECT 1" ){}
|
574
649
|
}.to raise_error(ArgumentError, /no COPY/)
|
575
650
|
|
576
|
-
|
651
|
+
expect( @conn ).to still_be_usable
|
577
652
|
end
|
578
653
|
|
579
654
|
it "correctly finishes COPY queries passed to #async_exec" do
|
@@ -589,8 +664,8 @@ describe PG::Connection do
|
|
589
664
|
results << data if data
|
590
665
|
end until data.nil?
|
591
666
|
|
592
|
-
results.
|
593
|
-
results.
|
667
|
+
expect( results.size ).to eq( 2 )
|
668
|
+
expect( results ).to include( "1\n", "2\n" )
|
594
669
|
end
|
595
670
|
|
596
671
|
|
@@ -602,10 +677,10 @@ describe PG::Connection do
|
|
602
677
|
end
|
603
678
|
|
604
679
|
sleep 0.5
|
605
|
-
t.
|
680
|
+
expect( t ).to be_alive()
|
606
681
|
@conn.cancel
|
607
682
|
t.join
|
608
|
-
(Time.now - start).
|
683
|
+
expect( (Time.now - start) ).to be < 3
|
609
684
|
end
|
610
685
|
|
611
686
|
it "described_class#block should allow a timeout" do
|
@@ -615,13 +690,49 @@ describe PG::Connection do
|
|
615
690
|
@conn.block( 0.1 )
|
616
691
|
finish = Time.now
|
617
692
|
|
618
|
-
(finish - start).
|
693
|
+
expect( (finish - start) ).to be_within( 0.05 ).of( 0.1 )
|
619
694
|
end
|
620
695
|
|
621
696
|
|
622
697
|
it "can encrypt a string given a password and username" do
|
623
|
-
described_class.encrypt_password("postgres", "postgres").
|
624
|
-
|
698
|
+
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
699
|
+
end
|
700
|
+
|
701
|
+
it "can return the default connection options" do
|
702
|
+
expect( described_class.conndefaults ).to be_a( Array )
|
703
|
+
expect( described_class.conndefaults ).to all( be_a(Hash) )
|
704
|
+
expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
|
705
|
+
expect( @conn.conndefaults ).to eq( described_class.conndefaults )
|
706
|
+
end
|
707
|
+
|
708
|
+
it "can return the default connection options as a Hash" do
|
709
|
+
expect( described_class.conndefaults_hash ).to be_a( Hash )
|
710
|
+
expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
|
711
|
+
expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
|
712
|
+
expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
|
713
|
+
end
|
714
|
+
|
715
|
+
it "can return the connection's connection options", :postgresql_93 do
|
716
|
+
expect( @conn.conninfo ).to be_a( Array )
|
717
|
+
expect( @conn.conninfo ).to all( be_a(Hash) )
|
718
|
+
expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
|
719
|
+
end
|
720
|
+
|
721
|
+
|
722
|
+
it "can return the connection's connection options as a Hash", :postgresql_93 do
|
723
|
+
expect( @conn.conninfo_hash ).to be_a( Hash )
|
724
|
+
expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
|
725
|
+
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
726
|
+
end
|
727
|
+
|
728
|
+
|
729
|
+
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
730
|
+
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
731
|
+
begin
|
732
|
+
expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
|
733
|
+
ensure
|
734
|
+
conn.finish
|
735
|
+
end
|
625
736
|
end
|
626
737
|
|
627
738
|
|
@@ -641,15 +752,15 @@ describe PG::Connection do
|
|
641
752
|
|
642
753
|
it "allows fetching a column of values from a result by column number" do
|
643
754
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
644
|
-
res.column_values( 0 ).
|
645
|
-
res.column_values( 1 ).
|
755
|
+
expect( res.column_values( 0 ) ).to eq( %w[1 2 3] )
|
756
|
+
expect( res.column_values( 1 ) ).to eq( %w[2 3 4] )
|
646
757
|
end
|
647
758
|
|
648
759
|
|
649
760
|
it "allows fetching a column of values from a result by field name" do
|
650
761
|
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
651
|
-
res.field_values( 'column1' ).
|
652
|
-
res.field_values( 'column2' ).
|
762
|
+
expect( res.field_values( 'column1' ) ).to eq( %w[1 2 3] )
|
763
|
+
expect( res.field_values( 'column2' ) ).to eq( %w[2 3 4] )
|
653
764
|
end
|
654
765
|
|
655
766
|
|
@@ -680,13 +791,13 @@ describe PG::Connection do
|
|
680
791
|
it "can connect asynchronously", :socket_io do
|
681
792
|
serv = TCPServer.new( '127.0.0.1', 54320 )
|
682
793
|
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
683
|
-
[PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].
|
794
|
+
expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
|
684
795
|
select( nil, [conn.socket_io], nil, 0.2 )
|
685
796
|
serv.close
|
686
797
|
if conn.connect_poll == PG::PGRES_POLLING_READING
|
687
798
|
select( [conn.socket_io], nil, nil, 0.2 )
|
688
799
|
end
|
689
|
-
conn.connect_poll.
|
800
|
+
expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
|
690
801
|
end
|
691
802
|
|
692
803
|
it "discards previous results (if any) before waiting on an #async_exec"
|
@@ -696,7 +807,7 @@ describe PG::Connection do
|
|
696
807
|
@conn.async_exec( "select 47 as one" ) do |pg_res|
|
697
808
|
result = pg_res[0]
|
698
809
|
end
|
699
|
-
result.
|
810
|
+
expect( result ).to eq( { 'one' => '47' } )
|
700
811
|
end
|
701
812
|
|
702
813
|
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
@@ -710,7 +821,7 @@ describe PG::Connection do
|
|
710
821
|
conn = PG.connect( @conninfo )
|
711
822
|
io = conn.socket_io
|
712
823
|
conn.finish
|
713
|
-
io.
|
824
|
+
expect( io ).to be_closed()
|
714
825
|
expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
|
715
826
|
end
|
716
827
|
|
@@ -718,8 +829,8 @@ describe PG::Connection do
|
|
718
829
|
conn = PG.connect( @conninfo )
|
719
830
|
io = conn.socket_io
|
720
831
|
conn.reset
|
721
|
-
io.
|
722
|
-
conn.socket_io.
|
832
|
+
expect( io ).to be_closed()
|
833
|
+
expect( conn.socket_io ).to_not equal( io )
|
723
834
|
conn.finish
|
724
835
|
end
|
725
836
|
|
@@ -742,7 +853,11 @@ describe PG::Connection do
|
|
742
853
|
|
743
854
|
it "sets the fallback_application_name on new connections" do
|
744
855
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
745
|
-
|
856
|
+
|
857
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
858
|
+
expect( conn_name ).to include( $0[0..10] )
|
859
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
860
|
+
expect( conn_name.length ).to be <= 64
|
746
861
|
end
|
747
862
|
|
748
863
|
it "sets a shortened fallback_application_name on new connections" do
|
@@ -750,7 +865,10 @@ describe PG::Connection do
|
|
750
865
|
begin
|
751
866
|
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
752
867
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
753
|
-
|
868
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
869
|
+
expect( conn_name ).to include( $0[0..10] )
|
870
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
871
|
+
expect( conn_name.length ).to be <= 64
|
754
872
|
ensure
|
755
873
|
$0 = old_0
|
756
874
|
end
|
@@ -772,9 +890,9 @@ describe PG::Connection do
|
|
772
890
|
end
|
773
891
|
@conn.exec( 'UNLISTEN knees' )
|
774
892
|
|
775
|
-
event.
|
776
|
-
pid.
|
777
|
-
msg.
|
893
|
+
expect( event ).to eq( 'knees' )
|
894
|
+
expect( pid ).to be_a_kind_of( Integer )
|
895
|
+
expect( msg ).to eq( 'skirt and boots' )
|
778
896
|
end
|
779
897
|
|
780
898
|
it "accepts nil as the timeout in #wait_for_notify " do
|
@@ -791,8 +909,8 @@ describe PG::Connection do
|
|
791
909
|
end
|
792
910
|
@conn.exec( 'UNLISTEN knees' )
|
793
911
|
|
794
|
-
event.
|
795
|
-
pid.
|
912
|
+
expect( event ).to eq( 'knees' )
|
913
|
+
expect( pid ).to be_a_kind_of( Integer )
|
796
914
|
end
|
797
915
|
|
798
916
|
it "sends nil as the payload if the notification wasn't given one" do
|
@@ -809,7 +927,7 @@ describe PG::Connection do
|
|
809
927
|
end
|
810
928
|
@conn.exec( 'UNLISTEN knees' )
|
811
929
|
|
812
|
-
payload.
|
930
|
+
expect( payload ).to be_nil()
|
813
931
|
end
|
814
932
|
|
815
933
|
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
@@ -828,9 +946,9 @@ describe PG::Connection do
|
|
828
946
|
end
|
829
947
|
@conn.exec( 'UNLISTEN knees' )
|
830
948
|
|
831
|
-
event.
|
832
|
-
pid.
|
833
|
-
msg.
|
949
|
+
expect( event ).to eq( 'knees' )
|
950
|
+
expect( pid ).to be_a_kind_of( Integer )
|
951
|
+
expect( msg ).to be_nil()
|
834
952
|
end
|
835
953
|
|
836
954
|
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
@@ -849,7 +967,7 @@ describe PG::Connection do
|
|
849
967
|
end
|
850
968
|
@conn.exec( 'UNLISTEN knees' )
|
851
969
|
|
852
|
-
notification_received.
|
970
|
+
expect( notification_received ).to be_truthy()
|
853
971
|
end
|
854
972
|
|
855
973
|
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
@@ -868,9 +986,9 @@ describe PG::Connection do
|
|
868
986
|
end
|
869
987
|
@conn.exec( 'UNLISTEN knees' )
|
870
988
|
|
871
|
-
event.
|
872
|
-
pid.
|
873
|
-
msg.
|
989
|
+
expect( event ).to eq( 'knees' )
|
990
|
+
expect( pid ).to be_a_kind_of( Integer )
|
991
|
+
expect( msg ).to eq( 'skirt and boots' )
|
874
992
|
end
|
875
993
|
|
876
994
|
end
|
@@ -879,12 +997,12 @@ describe PG::Connection do
|
|
879
997
|
|
880
998
|
it "pings successfully with connection string" do
|
881
999
|
ping = described_class.ping(@conninfo)
|
882
|
-
ping.
|
1000
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
883
1001
|
end
|
884
1002
|
|
885
1003
|
it "pings using 7 arguments converted to strings" do
|
886
1004
|
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
887
|
-
ping.
|
1005
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
888
1006
|
end
|
889
1007
|
|
890
1008
|
it "pings using a hash of connection parameters" do
|
@@ -892,7 +1010,7 @@ describe PG::Connection do
|
|
892
1010
|
:host => 'localhost',
|
893
1011
|
:port => @port,
|
894
1012
|
:dbname => :test)
|
895
|
-
ping.
|
1013
|
+
expect( ping ).to eq( PG::PQPING_OK )
|
896
1014
|
end
|
897
1015
|
|
898
1016
|
it "returns correct response when ping connection cannot be established" do
|
@@ -900,12 +1018,12 @@ describe PG::Connection do
|
|
900
1018
|
:host => 'localhost',
|
901
1019
|
:port => 9999,
|
902
1020
|
:dbname => :test)
|
903
|
-
ping.
|
1021
|
+
expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
|
904
1022
|
end
|
905
1023
|
|
906
1024
|
it "returns correct response when ping connection arguments are wrong" do
|
907
1025
|
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
908
|
-
ping.
|
1026
|
+
expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
|
909
1027
|
end
|
910
1028
|
|
911
1029
|
|
@@ -930,15 +1048,15 @@ describe PG::Connection do
|
|
930
1048
|
res = @conn.get_result or break
|
931
1049
|
results << res
|
932
1050
|
end
|
933
|
-
results.length.
|
1051
|
+
expect( results.length ).to eq( 11 )
|
934
1052
|
results[0..-2].each do |res|
|
935
|
-
res.result_status.
|
1053
|
+
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
936
1054
|
values = res.field_values('generate_series')
|
937
|
-
values.length.
|
938
|
-
values.first.to_i.
|
1055
|
+
expect( values.length ).to eq( 1 )
|
1056
|
+
expect( values.first.to_i ).to be > 0
|
939
1057
|
end
|
940
|
-
results.last.result_status.
|
941
|
-
results.last.ntuples.
|
1058
|
+
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1059
|
+
expect( results.last.ntuples ).to eq( 0 )
|
942
1060
|
end
|
943
1061
|
|
944
1062
|
it "should receive rows before entire query is finished" do
|
@@ -952,8 +1070,8 @@ describe PG::Connection do
|
|
952
1070
|
res.check
|
953
1071
|
first_row_time = Time.now unless first_row_time
|
954
1072
|
end
|
955
|
-
(Time.now - start_time).
|
956
|
-
(first_row_time - start_time).
|
1073
|
+
expect( (Time.now - start_time) ).to be >= 1.0
|
1074
|
+
expect( (first_row_time - start_time) ).to be < 1.0
|
957
1075
|
end
|
958
1076
|
|
959
1077
|
it "should receive rows before entire query fails" do
|
@@ -969,8 +1087,8 @@ describe PG::Connection do
|
|
969
1087
|
first_result ||= res
|
970
1088
|
end
|
971
1089
|
end.to raise_error(PG::Error)
|
972
|
-
first_result.kind_of?(PG::Result).
|
973
|
-
first_result.result_status.
|
1090
|
+
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1091
|
+
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
974
1092
|
end
|
975
1093
|
end
|
976
1094
|
end
|
@@ -985,8 +1103,8 @@ describe PG::Connection do
|
|
985
1103
|
res = conn.exec("VALUES ('fantasia')", [], 0)
|
986
1104
|
out_string = res[0]['column1']
|
987
1105
|
end
|
988
|
-
out_string.
|
989
|
-
out_string.encoding.
|
1106
|
+
expect( out_string ).to eq( 'fantasia' )
|
1107
|
+
expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
|
990
1108
|
end
|
991
1109
|
|
992
1110
|
it "should return results in the same encoding as the client (utf-8)" do
|
@@ -996,8 +1114,8 @@ describe PG::Connection do
|
|
996
1114
|
res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
|
997
1115
|
out_string = res[0]['column1']
|
998
1116
|
end
|
999
|
-
out_string.
|
1000
|
-
out_string.encoding.
|
1117
|
+
expect( out_string ).to eq( '世界線航跡蔵' )
|
1118
|
+
expect( out_string.encoding ).to eq( Encoding::UTF_8 )
|
1001
1119
|
end
|
1002
1120
|
|
1003
1121
|
it "should return results in the same encoding as the client (EUC-JP)" do
|
@@ -1008,8 +1126,8 @@ describe PG::Connection do
|
|
1008
1126
|
res = conn.exec(stmt, [], 0)
|
1009
1127
|
out_string = res[0]['column1']
|
1010
1128
|
end
|
1011
|
-
out_string.
|
1012
|
-
out_string.encoding.
|
1129
|
+
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1130
|
+
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1013
1131
|
end
|
1014
1132
|
|
1015
1133
|
it "returns the results in the correct encoding even if the client_encoding has " +
|
@@ -1022,79 +1140,82 @@ describe PG::Connection do
|
|
1022
1140
|
conn.internal_encoding = 'utf-8'
|
1023
1141
|
out_string = res[0]['column1']
|
1024
1142
|
end
|
1025
|
-
out_string.
|
1026
|
-
out_string.encoding.
|
1143
|
+
expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
|
1144
|
+
expect( out_string.encoding ).to eq( Encoding::EUC_JP )
|
1027
1145
|
end
|
1028
1146
|
|
1029
1147
|
it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
|
1030
1148
|
@conn.exec "SET client_encoding TO SQL_ASCII"
|
1031
|
-
@conn.internal_encoding.
|
1032
|
-
end
|
1033
|
-
|
1034
|
-
it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
|
1035
|
-
pending "figuring out how to create a string in the JOHAB encoding" do
|
1036
|
-
out_string = nil
|
1037
|
-
@conn.transaction do |conn|
|
1038
|
-
conn.exec( "set client_encoding = 'JOHAB';" )
|
1039
|
-
stmt = "VALUES ('foo')".encode('JOHAB')
|
1040
|
-
res = conn.exec( stmt, [], 0 )
|
1041
|
-
out_string = res[0]['column1']
|
1042
|
-
end
|
1043
|
-
out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
|
1044
|
-
out_string.encoding.should == Encoding::ASCII_8BIT
|
1045
|
-
end
|
1149
|
+
expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
|
1046
1150
|
end
|
1047
1151
|
|
1048
1152
|
it "uses the client encoding for escaped string" do
|
1049
1153
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1050
1154
|
@conn.set_client_encoding( "euc_jp" )
|
1051
1155
|
escaped = @conn.escape( original )
|
1052
|
-
escaped.encoding.
|
1053
|
-
escaped.
|
1156
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1157
|
+
expect( escaped ).to eq( "string to" )
|
1054
1158
|
end
|
1055
1159
|
|
1056
1160
|
it "uses the client encoding for escaped literal", :postgresql_90 do
|
1057
1161
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1058
1162
|
@conn.set_client_encoding( "euc_jp" )
|
1059
1163
|
escaped = @conn.escape_literal( original )
|
1060
|
-
escaped.encoding.
|
1061
|
-
escaped.
|
1164
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1165
|
+
expect( escaped ).to eq( "'string to'" )
|
1062
1166
|
end
|
1063
1167
|
|
1064
1168
|
it "uses the client encoding for escaped identifier", :postgresql_90 do
|
1065
1169
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1066
1170
|
@conn.set_client_encoding( "euc_jp" )
|
1067
1171
|
escaped = @conn.escape_identifier( original )
|
1068
|
-
escaped.encoding.
|
1069
|
-
escaped.
|
1172
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1173
|
+
expect( escaped ).to eq( "\"string to\"" )
|
1070
1174
|
end
|
1071
1175
|
|
1072
1176
|
it "uses the client encoding for quote_ident" do
|
1073
1177
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1074
1178
|
@conn.set_client_encoding( "euc_jp" )
|
1075
1179
|
escaped = @conn.quote_ident( original )
|
1076
|
-
escaped.encoding.
|
1077
|
-
escaped.
|
1180
|
+
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1181
|
+
expect( escaped ).to eq( "\"string to\"" )
|
1078
1182
|
end
|
1079
1183
|
|
1080
1184
|
it "uses the previous string encoding for escaped string" do
|
1081
1185
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1082
1186
|
@conn.set_client_encoding( "euc_jp" )
|
1083
1187
|
escaped = described_class.escape( original )
|
1084
|
-
escaped.encoding.
|
1085
|
-
escaped.
|
1188
|
+
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1189
|
+
expect( escaped ).to eq( "string to" )
|
1086
1190
|
end
|
1087
1191
|
|
1088
1192
|
it "uses the previous string encoding for quote_ident" do
|
1089
1193
|
original = "string to\0 escape".force_encoding( "iso8859-1" )
|
1090
1194
|
@conn.set_client_encoding( "euc_jp" )
|
1091
1195
|
escaped = described_class.quote_ident( original )
|
1092
|
-
escaped.encoding.
|
1093
|
-
escaped.
|
1196
|
+
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1197
|
+
expect( escaped ).to eq( "\"string to\"" )
|
1094
1198
|
end
|
1199
|
+
end
|
1095
1200
|
|
1201
|
+
it "can quote bigger strings with quote_ident" do
|
1202
|
+
original = "'01234567\"" * 100
|
1203
|
+
escaped = described_class.quote_ident( original + "\0afterzero" )
|
1204
|
+
expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
|
1096
1205
|
end
|
1097
1206
|
|
1207
|
+
it "can quote Arrays with quote_ident" do
|
1208
|
+
original = "'01234567\""
|
1209
|
+
escaped = described_class.quote_ident( [original]*3 )
|
1210
|
+
expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
|
1211
|
+
expect( escaped ).to eq( expected.join(".") )
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
it "will raise a TypeError for invalid arguments to quote_ident" do
|
1215
|
+
expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
|
1216
|
+
expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
|
1217
|
+
expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
|
1218
|
+
end
|
1098
1219
|
|
1099
1220
|
describe "Ruby 1.9.x default_internal encoding" do
|
1100
1221
|
|
@@ -1110,10 +1231,11 @@ describe PG::Connection do
|
|
1110
1231
|
Encoding.default_internal = Encoding::UTF_8
|
1111
1232
|
|
1112
1233
|
conn = PG.connect( @conninfo )
|
1113
|
-
conn.internal_encoding.
|
1234
|
+
expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
|
1114
1235
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1115
|
-
res[0]['foo'].encoding.
|
1236
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
|
1116
1237
|
ensure
|
1238
|
+
conn.exec( "DROP TABLE defaultinternaltest" )
|
1117
1239
|
conn.finish if conn
|
1118
1240
|
Encoding.default_internal = prev_encoding
|
1119
1241
|
end
|
@@ -1126,7 +1248,7 @@ describe PG::Connection do
|
|
1126
1248
|
|
1127
1249
|
@conn.set_default_encoding
|
1128
1250
|
|
1129
|
-
@conn.internal_encoding.
|
1251
|
+
expect( @conn.internal_encoding ).to eq( Encoding::KOI8_R )
|
1130
1252
|
ensure
|
1131
1253
|
Encoding.default_internal = prev_encoding
|
1132
1254
|
end
|
@@ -1147,7 +1269,7 @@ describe PG::Connection do
|
|
1147
1269
|
query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
|
1148
1270
|
conn.exec( query )
|
1149
1271
|
rescue => err
|
1150
|
-
err.message.encoding.
|
1272
|
+
expect( err.message.encoding ).to eq( Encoding::ISO8859_15 )
|
1151
1273
|
else
|
1152
1274
|
fail "No exception raised?!"
|
1153
1275
|
end
|
@@ -1156,6 +1278,21 @@ describe PG::Connection do
|
|
1156
1278
|
conn.finish if conn
|
1157
1279
|
end
|
1158
1280
|
|
1281
|
+
it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
|
1282
|
+
r = nil
|
1283
|
+
@conn.set_notice_receiver do |result|
|
1284
|
+
r = result
|
1285
|
+
expect( r.cleared? ).to eq(false)
|
1286
|
+
end
|
1287
|
+
@conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
|
1288
|
+
sleep 0.2
|
1289
|
+
expect( r ).to be_a( PG::Result )
|
1290
|
+
expect( r.cleared? ).to eq(true)
|
1291
|
+
expect( r.autoclear? ).to eq(true)
|
1292
|
+
r.clear
|
1293
|
+
@conn.set_notice_receiver
|
1294
|
+
end
|
1295
|
+
|
1159
1296
|
it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
|
1160
1297
|
[:receiver, :processor].each do |kind|
|
1161
1298
|
notices = []
|
@@ -1174,10 +1311,10 @@ describe PG::Connection do
|
|
1174
1311
|
@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
|
1175
1312
|
end
|
1176
1313
|
|
1177
|
-
notices.length.
|
1314
|
+
expect( notices.length ).to eq( 3 )
|
1178
1315
|
notices.each do |notice|
|
1179
|
-
notice.
|
1180
|
-
notice.encoding.
|
1316
|
+
expect( notice ).to match( /^NOTICE:.*世界線航跡蔵/ )
|
1317
|
+
expect( notice.encoding ).to eq( Encoding::UTF_8 )
|
1181
1318
|
end
|
1182
1319
|
@conn.set_notice_receiver
|
1183
1320
|
@conn.set_notice_processor
|
@@ -1195,10 +1332,10 @@ describe PG::Connection do
|
|
1195
1332
|
end
|
1196
1333
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
1197
1334
|
|
1198
|
-
event.
|
1199
|
-
event.encoding.
|
1200
|
-
msg.
|
1201
|
-
msg.encoding.
|
1335
|
+
expect( event ).to eq( "Möhre" )
|
1336
|
+
expect( event.encoding ).to eq( Encoding::UTF_8 )
|
1337
|
+
expect( msg ).to eq( '世界線航跡蔵' )
|
1338
|
+
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1202
1339
|
end
|
1203
1340
|
|
1204
1341
|
it "returns properly encoded text from notifies", :postgresql_90 do
|
@@ -1209,11 +1346,11 @@ describe PG::Connection do
|
|
1209
1346
|
@conn.exec( 'UNLISTEN "Möhre"' )
|
1210
1347
|
|
1211
1348
|
notification = @conn.notifies
|
1212
|
-
notification[:relname].
|
1213
|
-
notification[:relname].encoding.
|
1214
|
-
notification[:extra].
|
1215
|
-
notification[:extra].encoding.
|
1216
|
-
notification[:be_pid].
|
1349
|
+
expect( notification[:relname] ).to eq( "Möhre" )
|
1350
|
+
expect( notification[:relname].encoding ).to eq( Encoding::UTF_8 )
|
1351
|
+
expect( notification[:extra] ).to eq( '世界線航跡蔵' )
|
1352
|
+
expect( notification[:extra].encoding ).to eq( Encoding::UTF_8 )
|
1353
|
+
expect( notification[:be_pid] ).to be > 0
|
1217
1354
|
end
|
1218
1355
|
end
|
1219
1356
|
|
@@ -1224,7 +1361,7 @@ describe PG::Connection do
|
|
1224
1361
|
end
|
1225
1362
|
|
1226
1363
|
sleep 0.5
|
1227
|
-
t.
|
1364
|
+
expect( t ).to be_alive()
|
1228
1365
|
t.join
|
1229
1366
|
end
|
1230
1367
|
|
@@ -1238,9 +1375,170 @@ describe PG::Connection do
|
|
1238
1375
|
end
|
1239
1376
|
|
1240
1377
|
sleep 0.5
|
1241
|
-
t.
|
1378
|
+
expect( t ).to be_alive()
|
1242
1379
|
serv.close
|
1243
1380
|
t.join
|
1244
1381
|
end
|
1245
1382
|
end
|
1383
|
+
|
1384
|
+
describe "type casting" do
|
1385
|
+
it "should raise an error on invalid param mapping" do
|
1386
|
+
expect{
|
1387
|
+
@conn.exec_params( "SELECT 1", [], nil, :invalid )
|
1388
|
+
}.to raise_error(TypeError)
|
1389
|
+
end
|
1390
|
+
|
1391
|
+
it "should return nil if no type mapping is set" do
|
1392
|
+
expect( @conn.type_map_for_queries ).to be_kind_of(PG::TypeMapAllStrings)
|
1393
|
+
expect( @conn.type_map_for_results ).to be_kind_of(PG::TypeMapAllStrings)
|
1394
|
+
end
|
1395
|
+
|
1396
|
+
it "shouldn't type map params unless requested" do
|
1397
|
+
expect{
|
1398
|
+
@conn.exec_params( "SELECT $1", [5] )
|
1399
|
+
}.to raise_error(PG::IndeterminateDatatype)
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
it "should raise an error on invalid encoder to put_copy_data" do
|
1403
|
+
expect{
|
1404
|
+
@conn.put_copy_data [1], :invalid
|
1405
|
+
}.to raise_error(TypeError)
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
it "can type cast parameters to put_copy_data with explicit encoder" do
|
1409
|
+
tm = PG::TypeMapByColumn.new [nil]
|
1410
|
+
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1411
|
+
|
1412
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1413
|
+
res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1414
|
+
@conn.put_copy_data [1], row_encoder
|
1415
|
+
@conn.put_copy_data ["2"], row_encoder
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
|
1419
|
+
@conn.put_copy_data [3]
|
1420
|
+
@conn.put_copy_data ["4"]
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1424
|
+
expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
|
1425
|
+
end
|
1426
|
+
|
1427
|
+
context "with default query type map" do
|
1428
|
+
before :each do
|
1429
|
+
@conn2 = described_class.new(@conninfo)
|
1430
|
+
tm = PG::TypeMapByClass.new
|
1431
|
+
tm[Integer] = PG::TextEncoder::Integer.new oid: 20
|
1432
|
+
@conn2.type_map_for_queries = tm
|
1433
|
+
|
1434
|
+
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
1435
|
+
@conn2.encoder_for_put_copy_data = row_encoder
|
1436
|
+
end
|
1437
|
+
after :each do
|
1438
|
+
@conn2.close
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
it "should respect a type mapping for params and it's OID and format code" do
|
1442
|
+
res = @conn2.exec_params( "SELECT $1", [5] )
|
1443
|
+
expect( res.values ).to eq( [["5"]] )
|
1444
|
+
expect( res.ftype(0) ).to eq( 20 )
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
it "should return the current type mapping" do
|
1448
|
+
expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByClass)
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
it "should work with arbitrary number of params in conjunction with type casting" do
|
1452
|
+
begin
|
1453
|
+
3.step( 12, 0.2 ) do |exp|
|
1454
|
+
num_params = (2 ** exp).to_i
|
1455
|
+
sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
|
1456
|
+
params = num_params.times.to_a
|
1457
|
+
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1458
|
+
expect( res.nfields ).to eq( num_params )
|
1459
|
+
expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
|
1460
|
+
end
|
1461
|
+
rescue PG::ProgramLimitExceeded
|
1462
|
+
# Stop silently as soon the server complains about too many params
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
|
1466
|
+
it "can process #copy_data input queries with row encoder" do
|
1467
|
+
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1468
|
+
res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1469
|
+
@conn2.put_copy_data [1]
|
1470
|
+
@conn2.put_copy_data ["2"]
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1474
|
+
expect( res.values ).to eq( [["1"], ["2"]] )
|
1475
|
+
end
|
1476
|
+
end
|
1477
|
+
|
1478
|
+
context "with default result type map" do
|
1479
|
+
before :each do
|
1480
|
+
@conn2 = described_class.new(@conninfo)
|
1481
|
+
tm = PG::TypeMapByOid.new
|
1482
|
+
tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
|
1483
|
+
@conn2.type_map_for_results = tm
|
1484
|
+
|
1485
|
+
row_decoder = PG::TextDecoder::CopyRow.new
|
1486
|
+
@conn2.decoder_for_get_copy_data = row_decoder
|
1487
|
+
end
|
1488
|
+
after :each do
|
1489
|
+
@conn2.close
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
it "should respect a type mapping for result" do
|
1493
|
+
res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
|
1494
|
+
expect( res.values ).to eq( [[5]] )
|
1495
|
+
end
|
1496
|
+
|
1497
|
+
it "should return the current type mapping" do
|
1498
|
+
expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
|
1499
|
+
end
|
1500
|
+
|
1501
|
+
it "should work with arbitrary number of params in conjunction with type casting" do
|
1502
|
+
begin
|
1503
|
+
3.step( 12, 0.2 ) do |exp|
|
1504
|
+
num_params = (2 ** exp).to_i
|
1505
|
+
sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
|
1506
|
+
params = num_params.times.to_a
|
1507
|
+
res = @conn2.exec_params( "SELECT #{sql}", params )
|
1508
|
+
expect( res.nfields ).to eq( num_params )
|
1509
|
+
expect( res.values ).to eq( [num_params.times.to_a] )
|
1510
|
+
end
|
1511
|
+
rescue PG::ProgramLimitExceeded
|
1512
|
+
# Stop silently as soon the server complains about too many params
|
1513
|
+
end
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
it "can process #copy_data output with row decoder" do
|
1517
|
+
rows = []
|
1518
|
+
res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
|
1519
|
+
while row=@conn2.get_copy_data
|
1520
|
+
rows << row
|
1521
|
+
end
|
1522
|
+
end
|
1523
|
+
expect( rows ).to eq( [["1"], ["2"]] )
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
it "can type cast #copy_data output with explicit decoder" do
|
1527
|
+
tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
|
1528
|
+
row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
|
1529
|
+
rows = []
|
1530
|
+
@conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
|
1531
|
+
while row=@conn.get_copy_data
|
1532
|
+
rows << row
|
1533
|
+
end
|
1534
|
+
end
|
1535
|
+
@conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
|
1536
|
+
while row=@conn.get_copy_data( false, row_decoder )
|
1537
|
+
rows << row
|
1538
|
+
end
|
1539
|
+
end
|
1540
|
+
expect( rows ).to eq( [[1], [2], [3], [4]] )
|
1541
|
+
end
|
1542
|
+
end
|
1543
|
+
end
|
1246
1544
|
end
|