pg 0.15.0.pre.454-x64-mingw32
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.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/BSDL +22 -0
- data/ChangeLog +2945 -0
- data/Contributors.rdoc +46 -0
- data/History.rdoc +205 -0
- data/LICENSE +56 -0
- data/Manifest.txt +53 -0
- data/POSTGRES +23 -0
- data/README-OS_X.rdoc +68 -0
- data/README-Windows.rdoc +67 -0
- data/README.ja.rdoc +14 -0
- data/README.rdoc +111 -0
- data/Rakefile +156 -0
- data/Rakefile.cross +271 -0
- data/ext/extconf.rb +91 -0
- data/ext/gvl_wrappers.c +13 -0
- data/ext/gvl_wrappers.h +185 -0
- data/ext/pg.c +525 -0
- data/ext/pg.h +126 -0
- data/ext/pg_connection.c +3600 -0
- data/ext/pg_result.c +939 -0
- data/ext/vc/pg.sln +26 -0
- data/ext/vc/pg_18/pg.vcproj +216 -0
- data/ext/vc/pg_19/pg_19.vcproj +209 -0
- data/lib/2.0/pg_ext.so +0 -0
- data/lib/pg.rb +52 -0
- data/lib/pg/connection.rb +71 -0
- data/lib/pg/constants.rb +11 -0
- data/lib/pg/exceptions.rb +11 -0
- data/lib/pg/result.rb +16 -0
- data/sample/array_insert.rb +20 -0
- data/sample/async_api.rb +106 -0
- data/sample/async_copyto.rb +39 -0
- data/sample/async_mixed.rb +56 -0
- data/sample/check_conn.rb +21 -0
- data/sample/copyfrom.rb +81 -0
- data/sample/copyto.rb +19 -0
- data/sample/cursor.rb +21 -0
- data/sample/disk_usage_report.rb +186 -0
- data/sample/issue-119.rb +94 -0
- data/sample/losample.rb +69 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/notify_wait.rb +72 -0
- data/sample/pg_statistics.rb +294 -0
- data/sample/replication_monitor.rb +231 -0
- data/sample/test_binary_values.rb +33 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +320 -0
- data/spec/data/expected_trace.out +26 -0
- data/spec/data/random_binary_data +0 -0
- data/spec/lib/helpers.rb +279 -0
- data/spec/pg/connection_spec.rb +1013 -0
- data/spec/pg/result_spec.rb +278 -0
- data/spec/pg_spec.rb +31 -0
- metadata +275 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,1013 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
BEGIN {
|
5
|
+
require 'pathname'
|
6
|
+
|
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
|
+
require 'timeout'
|
17
|
+
require 'socket'
|
18
|
+
require 'pg'
|
19
|
+
|
20
|
+
describe PG::Connection do
|
21
|
+
|
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
|
+
it "can create a connection option string from a Hash of options" do
|
48
|
+
optstring = described_class.parse_connect_args(
|
49
|
+
:host => 'pgsql.example.com',
|
50
|
+
:dbname => 'db01',
|
51
|
+
'sslmode' => 'require'
|
52
|
+
)
|
53
|
+
|
54
|
+
optstring.should be_a( String )
|
55
|
+
optstring.should =~ /(^|\s)host='pgsql.example.com'/
|
56
|
+
optstring.should =~ /(^|\s)dbname='db01'/
|
57
|
+
optstring.should =~ /(^|\s)sslmode='require'/
|
58
|
+
end
|
59
|
+
|
60
|
+
it "can create a connection option string from positional parameters" do
|
61
|
+
optstring = described_class.parse_connect_args( 'pgsql.example.com', nil, '-c geqo=off', nil,
|
62
|
+
'sales' )
|
63
|
+
|
64
|
+
optstring.should be_a( String )
|
65
|
+
optstring.should =~ /(^|\s)host='pgsql.example.com'/
|
66
|
+
optstring.should =~ /(^|\s)dbname='sales'/
|
67
|
+
optstring.should =~ /(^|\s)options='-c geqo=off'/
|
68
|
+
|
69
|
+
optstring.should_not =~ /port=/
|
70
|
+
optstring.should_not =~ /tty=/
|
71
|
+
end
|
72
|
+
|
73
|
+
it "can create a connection option string from a mix of positional and hash parameters" do
|
74
|
+
optstring = described_class.parse_connect_args( 'pgsql.example.com',
|
75
|
+
:dbname => 'licensing', :user => 'jrandom' )
|
76
|
+
|
77
|
+
optstring.should be_a( String )
|
78
|
+
optstring.should =~ /(^|\s)host='pgsql.example.com'/
|
79
|
+
optstring.should =~ /(^|\s)dbname='licensing'/
|
80
|
+
optstring.should =~ /(^|\s)user='jrandom'/
|
81
|
+
end
|
82
|
+
|
83
|
+
it "escapes single quotes and backslashes in connection parameters" do
|
84
|
+
described_class.parse_connect_args( "DB 'browser' \\" ).
|
85
|
+
should =~ /host='DB \\'browser\\' \\\\'/
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
it "connects with defaults if no connection parameters are given" do
|
90
|
+
described_class.parse_connect_args.should == ''
|
91
|
+
end
|
92
|
+
|
93
|
+
it "connects successfully with connection string" do
|
94
|
+
tmpconn = described_class.connect(@conninfo)
|
95
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
96
|
+
tmpconn.finish
|
97
|
+
end
|
98
|
+
|
99
|
+
it "connects using 7 arguments converted to strings" do
|
100
|
+
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
101
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
102
|
+
tmpconn.finish
|
103
|
+
end
|
104
|
+
|
105
|
+
it "connects using a hash of connection parameters" do
|
106
|
+
tmpconn = described_class.connect(
|
107
|
+
:host => 'localhost',
|
108
|
+
:port => @port,
|
109
|
+
:dbname => :test)
|
110
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
111
|
+
tmpconn.finish
|
112
|
+
end
|
113
|
+
|
114
|
+
it "connects using a hash of optional connection parameters", :postgresql_90 do
|
115
|
+
tmpconn = described_class.connect(
|
116
|
+
:host => 'localhost',
|
117
|
+
:port => @port,
|
118
|
+
:dbname => :test,
|
119
|
+
:keepalives => 1)
|
120
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
121
|
+
tmpconn.finish
|
122
|
+
end
|
123
|
+
|
124
|
+
it "raises an exception when connecting with an invalid number of arguments" do
|
125
|
+
expect {
|
126
|
+
described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
|
127
|
+
}.to raise_error( ArgumentError, /extra positional parameter/i )
|
128
|
+
end
|
129
|
+
|
130
|
+
it "can connect asynchronously", :socket_io do
|
131
|
+
tmpconn = described_class.connect_start( @conninfo )
|
132
|
+
tmpconn.should be_a( described_class )
|
133
|
+
socket = tmpconn.socket_io
|
134
|
+
status = tmpconn.connect_poll
|
135
|
+
|
136
|
+
while status != PG::PGRES_POLLING_OK
|
137
|
+
if status == PG::PGRES_POLLING_READING
|
138
|
+
select( [socket], [], [], 5.0 ) or
|
139
|
+
raise "Asynchronous connection timed out!"
|
140
|
+
|
141
|
+
elsif status == PG::PGRES_POLLING_WRITING
|
142
|
+
select( [], [socket], [], 5.0 ) or
|
143
|
+
raise "Asynchronous connection timed out!"
|
144
|
+
end
|
145
|
+
status = tmpconn.connect_poll
|
146
|
+
end
|
147
|
+
|
148
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
149
|
+
tmpconn.finish
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can connect asynchronously for the duration of a block", :socket_io do
|
153
|
+
conn = nil
|
154
|
+
|
155
|
+
described_class.connect_start(@conninfo) do |tmpconn|
|
156
|
+
tmpconn.should be_a( described_class )
|
157
|
+
conn = tmpconn
|
158
|
+
socket = tmpconn.socket_io
|
159
|
+
status = tmpconn.connect_poll
|
160
|
+
|
161
|
+
while status != PG::PGRES_POLLING_OK
|
162
|
+
if status == PG::PGRES_POLLING_READING
|
163
|
+
if(not select([socket],[],[],5.0))
|
164
|
+
raise "Asynchronous connection timed out!"
|
165
|
+
end
|
166
|
+
elsif(status == PG::PGRES_POLLING_WRITING)
|
167
|
+
if(not select([],[socket],[],5.0))
|
168
|
+
raise "Asynchronous connection timed out!"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
status = tmpconn.connect_poll
|
172
|
+
end
|
173
|
+
|
174
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
175
|
+
end
|
176
|
+
|
177
|
+
conn.should be_finished()
|
178
|
+
end
|
179
|
+
|
180
|
+
it "doesn't leave stale server connections after finish" do
|
181
|
+
described_class.connect(@conninfo).finish
|
182
|
+
sleep 0.5
|
183
|
+
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
184
|
+
WHERE usename IS NOT NULL])
|
185
|
+
# there's still the global @conn, but should be no more
|
186
|
+
res[0]['n'].should == '1'
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
EXPECTED_TRACE_OUTPUT = %{
|
191
|
+
To backend> Msg Q
|
192
|
+
To backend> "SELECT 1 AS one"
|
193
|
+
To backend> Msg complete, length 21
|
194
|
+
From backend> T
|
195
|
+
From backend (#4)> 28
|
196
|
+
From backend (#2)> 1
|
197
|
+
From backend> "one"
|
198
|
+
From backend (#4)> 0
|
199
|
+
From backend (#2)> 0
|
200
|
+
From backend (#4)> 23
|
201
|
+
From backend (#2)> 4
|
202
|
+
From backend (#4)> -1
|
203
|
+
From backend (#2)> 0
|
204
|
+
From backend> D
|
205
|
+
From backend (#4)> 11
|
206
|
+
From backend (#2)> 1
|
207
|
+
From backend (#4)> 1
|
208
|
+
From backend (1)> 1
|
209
|
+
From backend> C
|
210
|
+
From backend (#4)> 13
|
211
|
+
From backend> "SELECT 1"
|
212
|
+
From backend> Z
|
213
|
+
From backend (#4)> 5
|
214
|
+
From backend> Z
|
215
|
+
From backend (#4)> 5
|
216
|
+
From backend> T
|
217
|
+
}.gsub( /^\t{2}/, '' ).lstrip
|
218
|
+
|
219
|
+
it "trace and untrace client-server communication", :unix do
|
220
|
+
# be careful to explicitly close files so that the
|
221
|
+
# directory can be removed and we don't have to wait for
|
222
|
+
# the GC to run.
|
223
|
+
trace_file = TEST_DIRECTORY + "test_trace.out"
|
224
|
+
trace_io = trace_file.open( 'w', 0600 )
|
225
|
+
@conn.trace( trace_io )
|
226
|
+
trace_io.close
|
227
|
+
|
228
|
+
res = @conn.exec("SELECT 1 AS one")
|
229
|
+
@conn.untrace
|
230
|
+
|
231
|
+
res = @conn.exec("SELECT 2 AS two")
|
232
|
+
|
233
|
+
trace_data = trace_file.read
|
234
|
+
|
235
|
+
expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
|
236
|
+
# For PostgreSQL < 9.0, the output will be different:
|
237
|
+
# -From backend (#4)> 13
|
238
|
+
# -From backend> "SELECT 1"
|
239
|
+
# +From backend (#4)> 11
|
240
|
+
# +From backend> "SELECT"
|
241
|
+
if @conn.server_version < 90000
|
242
|
+
expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
|
243
|
+
expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
|
244
|
+
end
|
245
|
+
|
246
|
+
trace_data.should == expected_trace_output
|
247
|
+
end
|
248
|
+
|
249
|
+
it "allows a query to be cancelled" do
|
250
|
+
error = false
|
251
|
+
@conn.send_query("SELECT pg_sleep(1000)")
|
252
|
+
@conn.cancel
|
253
|
+
tmpres = @conn.get_result
|
254
|
+
if(tmpres.result_status != PG::PGRES_TUPLES_OK)
|
255
|
+
error = true
|
256
|
+
end
|
257
|
+
error.should == true
|
258
|
+
end
|
259
|
+
|
260
|
+
it "automatically rolls back a transaction started with Connection#transaction if an exception " +
|
261
|
+
"is raised" do
|
262
|
+
# abort the per-example transaction so we can test our own
|
263
|
+
@conn.exec( 'ROLLBACK' )
|
264
|
+
|
265
|
+
res = nil
|
266
|
+
@conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
|
267
|
+
|
268
|
+
expect {
|
269
|
+
res = @conn.transaction do
|
270
|
+
@conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
|
271
|
+
raise "Oh noes! All pie is gone!"
|
272
|
+
end
|
273
|
+
}.to raise_exception( RuntimeError, /all pie is gone/i )
|
274
|
+
|
275
|
+
res = @conn.exec( "SELECT * FROM pie" )
|
276
|
+
res.ntuples.should == 0
|
277
|
+
end
|
278
|
+
|
279
|
+
it "not read past the end of a large object" do
|
280
|
+
@conn.transaction do
|
281
|
+
oid = @conn.lo_create( 0 )
|
282
|
+
fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
|
283
|
+
@conn.lo_write( fd, "foobar" )
|
284
|
+
@conn.lo_read( fd, 10 ).should be_nil()
|
285
|
+
@conn.lo_lseek( fd, 0, PG::SEEK_SET )
|
286
|
+
@conn.lo_read( fd, 10 ).should == 'foobar'
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
|
291
|
+
it "supports parameters passed to #exec (backward compatibility)" do
|
292
|
+
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
293
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
294
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
295
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
296
|
+
|
297
|
+
res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
|
298
|
+
res.values.should == [ ['Wally'], ['Sally'] ]
|
299
|
+
end
|
300
|
+
|
301
|
+
it "supports explicitly calling #exec_params" do
|
302
|
+
@conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
|
303
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
|
304
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
|
305
|
+
@conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
|
306
|
+
|
307
|
+
res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
|
308
|
+
res.values.should == [ ['Wally'], ['Sally'] ]
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
it "can wait for NOTIFY events" do
|
313
|
+
@conn.exec( 'ROLLBACK' )
|
314
|
+
@conn.exec( 'LISTEN woo' )
|
315
|
+
|
316
|
+
t = Thread.new do
|
317
|
+
begin
|
318
|
+
conn = described_class.connect( @conninfo )
|
319
|
+
sleep 1
|
320
|
+
conn.async_exec( 'NOTIFY woo' )
|
321
|
+
ensure
|
322
|
+
conn.finish
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
@conn.wait_for_notify( 10 ).should == 'woo'
|
327
|
+
@conn.exec( 'UNLISTEN woo' )
|
328
|
+
|
329
|
+
t.join
|
330
|
+
end
|
331
|
+
|
332
|
+
it "calls a block for NOTIFY events if one is given" do
|
333
|
+
@conn.exec( 'ROLLBACK' )
|
334
|
+
@conn.exec( 'LISTEN woo' )
|
335
|
+
|
336
|
+
t = Thread.new do
|
337
|
+
begin
|
338
|
+
conn = described_class.connect( @conninfo )
|
339
|
+
sleep 1
|
340
|
+
conn.async_exec( 'NOTIFY woo' )
|
341
|
+
ensure
|
342
|
+
conn.finish
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
eventpid = event = nil
|
347
|
+
@conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
|
348
|
+
event.should == 'woo'
|
349
|
+
eventpid.should be_an( Integer )
|
350
|
+
|
351
|
+
@conn.exec( 'UNLISTEN woo' )
|
352
|
+
|
353
|
+
t.join
|
354
|
+
end
|
355
|
+
|
356
|
+
it "doesn't collapse sequential notifications" do
|
357
|
+
@conn.exec( 'ROLLBACK' )
|
358
|
+
@conn.exec( 'LISTEN woo' )
|
359
|
+
@conn.exec( 'LISTEN war' )
|
360
|
+
@conn.exec( 'LISTEN woz' )
|
361
|
+
|
362
|
+
begin
|
363
|
+
conn = described_class.connect( @conninfo )
|
364
|
+
conn.exec( 'NOTIFY woo' )
|
365
|
+
conn.exec( 'NOTIFY war' )
|
366
|
+
conn.exec( 'NOTIFY woz' )
|
367
|
+
ensure
|
368
|
+
conn.finish
|
369
|
+
end
|
370
|
+
|
371
|
+
channels = []
|
372
|
+
3.times do
|
373
|
+
channels << @conn.wait_for_notify( 2 )
|
374
|
+
end
|
375
|
+
|
376
|
+
channels.should have( 3 ).members
|
377
|
+
channels.should include( 'woo', 'war', 'woz' )
|
378
|
+
|
379
|
+
@conn.exec( 'UNLISTEN woz' )
|
380
|
+
@conn.exec( 'UNLISTEN war' )
|
381
|
+
@conn.exec( 'UNLISTEN woo' )
|
382
|
+
end
|
383
|
+
|
384
|
+
it "returns notifications which are already in the queue before wait_for_notify is called " +
|
385
|
+
"without waiting for the socket to become readable" do
|
386
|
+
@conn.exec( 'ROLLBACK' )
|
387
|
+
@conn.exec( 'LISTEN woo' )
|
388
|
+
|
389
|
+
begin
|
390
|
+
conn = described_class.connect( @conninfo )
|
391
|
+
conn.exec( 'NOTIFY woo' )
|
392
|
+
ensure
|
393
|
+
conn.finish
|
394
|
+
end
|
395
|
+
|
396
|
+
# Cause the notification to buffer, but not be read yet
|
397
|
+
@conn.exec( 'SELECT 1' )
|
398
|
+
|
399
|
+
@conn.wait_for_notify( 10 ).should == 'woo'
|
400
|
+
@conn.exec( 'UNLISTEN woo' )
|
401
|
+
end
|
402
|
+
|
403
|
+
it "yields the result if block is given to exec" do
|
404
|
+
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
405
|
+
values = []
|
406
|
+
result.should be_kind_of( PG::Result )
|
407
|
+
result.ntuples.should == 2
|
408
|
+
result.each do |tuple|
|
409
|
+
values << tuple['a']
|
410
|
+
end
|
411
|
+
values
|
412
|
+
end
|
413
|
+
|
414
|
+
rval.should have( 2 ).members
|
415
|
+
rval.should include( '5678', '1234' )
|
416
|
+
end
|
417
|
+
|
418
|
+
|
419
|
+
it "correctly finishes COPY queries passed to #async_exec" do
|
420
|
+
@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
|
421
|
+
|
422
|
+
results = []
|
423
|
+
begin
|
424
|
+
data = @conn.get_copy_data( true )
|
425
|
+
if false == data
|
426
|
+
@conn.block( 2.0 )
|
427
|
+
data = @conn.get_copy_data( true )
|
428
|
+
end
|
429
|
+
results << data if data
|
430
|
+
end until data.nil?
|
431
|
+
|
432
|
+
results.should have( 2 ).members
|
433
|
+
results.should include( "1\n", "2\n" )
|
434
|
+
end
|
435
|
+
|
436
|
+
|
437
|
+
it "described_class#block shouldn't block a second thread" do
|
438
|
+
start = Time.now
|
439
|
+
t = Thread.new do
|
440
|
+
@conn.send_query( "select pg_sleep(3)" )
|
441
|
+
@conn.block
|
442
|
+
end
|
443
|
+
|
444
|
+
sleep 0.5
|
445
|
+
t.should be_alive()
|
446
|
+
@conn.cancel
|
447
|
+
t.join
|
448
|
+
(Time.now - start).should < 3
|
449
|
+
end
|
450
|
+
|
451
|
+
it "described_class#block should allow a timeout" do
|
452
|
+
@conn.send_query( "select pg_sleep(3)" )
|
453
|
+
|
454
|
+
start = Time.now
|
455
|
+
@conn.block( 0.1 )
|
456
|
+
finish = Time.now
|
457
|
+
|
458
|
+
(finish - start).should be_within( 0.05 ).of( 0.1 )
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
it "can encrypt a string given a password and username" do
|
463
|
+
described_class.encrypt_password("postgres", "postgres").
|
464
|
+
should =~ /\S+/
|
465
|
+
end
|
466
|
+
|
467
|
+
|
468
|
+
it "raises an appropriate error if either of the required arguments for encrypt_password " +
|
469
|
+
"is not valid" do
|
470
|
+
expect {
|
471
|
+
described_class.encrypt_password( nil, nil )
|
472
|
+
}.to raise_error( TypeError )
|
473
|
+
expect {
|
474
|
+
described_class.encrypt_password( "postgres", nil )
|
475
|
+
}.to raise_error( TypeError )
|
476
|
+
expect {
|
477
|
+
described_class.encrypt_password( nil, "postgres" )
|
478
|
+
}.to raise_error( TypeError )
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
it "allows fetching a column of values from a result by column number" do
|
483
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
484
|
+
res.column_values( 0 ).should == %w[1 2 3]
|
485
|
+
res.column_values( 1 ).should == %w[2 3 4]
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
it "allows fetching a column of values from a result by field name" do
|
490
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
491
|
+
res.field_values( 'column1' ).should == %w[1 2 3]
|
492
|
+
res.field_values( 'column2' ).should == %w[2 3 4]
|
493
|
+
end
|
494
|
+
|
495
|
+
|
496
|
+
it "raises an error if selecting an invalid column index" do
|
497
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
498
|
+
expect {
|
499
|
+
res.column_values( 20 )
|
500
|
+
}.to raise_error( IndexError )
|
501
|
+
end
|
502
|
+
|
503
|
+
|
504
|
+
it "raises an error if selecting an invalid field name" do
|
505
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
506
|
+
expect {
|
507
|
+
res.field_values( 'hUUuurrg' )
|
508
|
+
}.to raise_error( IndexError )
|
509
|
+
end
|
510
|
+
|
511
|
+
|
512
|
+
it "raises an error if column index is not a number" do
|
513
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
514
|
+
expect {
|
515
|
+
res.column_values( 'hUUuurrg' )
|
516
|
+
}.to raise_error( TypeError )
|
517
|
+
end
|
518
|
+
|
519
|
+
|
520
|
+
it "can connect asynchronously", :socket_io do
|
521
|
+
serv = TCPServer.new( '127.0.0.1', 54320 )
|
522
|
+
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
523
|
+
[PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].should include conn.connect_poll
|
524
|
+
select( nil, [conn.socket_io], nil, 0.2 )
|
525
|
+
serv.close
|
526
|
+
if conn.connect_poll == PG::PGRES_POLLING_READING
|
527
|
+
select( [conn.socket_io], nil, nil, 0.2 )
|
528
|
+
end
|
529
|
+
conn.connect_poll.should == PG::PGRES_POLLING_FAILED
|
530
|
+
end
|
531
|
+
|
532
|
+
it "discards previous results (if any) before waiting on an #async_exec"
|
533
|
+
|
534
|
+
it "calls the block if one is provided to #async_exec" do
|
535
|
+
result = nil
|
536
|
+
@conn.async_exec( "select 47 as one" ) do |pg_res|
|
537
|
+
result = pg_res[0]
|
538
|
+
end
|
539
|
+
result.should == { 'one' => '47' }
|
540
|
+
end
|
541
|
+
|
542
|
+
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
543
|
+
conn = PG.connect( @conninfo )
|
544
|
+
|
545
|
+
conn.finish
|
546
|
+
expect { conn.finish }.to raise_error( PG::Error, /connection is closed/i )
|
547
|
+
end
|
548
|
+
|
549
|
+
it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
|
550
|
+
conn = PG.connect( @conninfo )
|
551
|
+
io = conn.socket_io
|
552
|
+
conn.finish
|
553
|
+
io.should be_closed()
|
554
|
+
expect { conn.socket_io }.to raise_error( PG::Error, /connection is closed/i )
|
555
|
+
end
|
556
|
+
|
557
|
+
it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
|
558
|
+
conn = PG.connect( @conninfo )
|
559
|
+
io = conn.socket_io
|
560
|
+
conn.reset
|
561
|
+
io.should be_closed()
|
562
|
+
conn.socket_io.should_not equal( io )
|
563
|
+
conn.finish
|
564
|
+
end
|
565
|
+
|
566
|
+
|
567
|
+
context "under PostgreSQL 9", :postgresql_90 do
|
568
|
+
|
569
|
+
before( :each ) do
|
570
|
+
pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
|
571
|
+
end
|
572
|
+
|
573
|
+
it "sets the fallback_application_name on new connections" do
|
574
|
+
PG::Connection.parse_connect_args( 'dbname=test' ).should include( $0 )
|
575
|
+
end
|
576
|
+
|
577
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
578
|
+
"any number of arguments" do
|
579
|
+
|
580
|
+
@conn.exec( 'ROLLBACK' )
|
581
|
+
@conn.exec( 'LISTEN knees' )
|
582
|
+
|
583
|
+
conn = described_class.connect( @conninfo )
|
584
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
585
|
+
conn.finish
|
586
|
+
|
587
|
+
event, pid, msg = nil
|
588
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
589
|
+
event, pid, msg = *args
|
590
|
+
end
|
591
|
+
@conn.exec( 'UNLISTEN knees' )
|
592
|
+
|
593
|
+
event.should == 'knees'
|
594
|
+
pid.should be_a_kind_of( Integer )
|
595
|
+
msg.should == 'skirt and boots'
|
596
|
+
end
|
597
|
+
|
598
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
599
|
+
@conn.exec( 'ROLLBACK' )
|
600
|
+
@conn.exec( 'LISTEN knees' )
|
601
|
+
|
602
|
+
conn = described_class.connect( @conninfo )
|
603
|
+
conn.exec( %Q{NOTIFY knees} )
|
604
|
+
conn.finish
|
605
|
+
|
606
|
+
event, pid = nil
|
607
|
+
@conn.wait_for_notify( nil ) do |*args|
|
608
|
+
event, pid = *args
|
609
|
+
end
|
610
|
+
@conn.exec( 'UNLISTEN knees' )
|
611
|
+
|
612
|
+
event.should == 'knees'
|
613
|
+
pid.should be_a_kind_of( Integer )
|
614
|
+
end
|
615
|
+
|
616
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
617
|
+
@conn.exec( 'ROLLBACK' )
|
618
|
+
@conn.exec( 'LISTEN knees' )
|
619
|
+
|
620
|
+
conn = described_class.connect( @conninfo )
|
621
|
+
conn.exec( %Q{NOTIFY knees} )
|
622
|
+
conn.finish
|
623
|
+
|
624
|
+
payload = :notnil
|
625
|
+
@conn.wait_for_notify( nil ) do |*args|
|
626
|
+
payload = args[ 2 ]
|
627
|
+
end
|
628
|
+
@conn.exec( 'UNLISTEN knees' )
|
629
|
+
|
630
|
+
payload.should be_nil()
|
631
|
+
end
|
632
|
+
|
633
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
634
|
+
"two arguments" do
|
635
|
+
|
636
|
+
@conn.exec( 'ROLLBACK' )
|
637
|
+
@conn.exec( 'LISTEN knees' )
|
638
|
+
|
639
|
+
conn = described_class.connect( @conninfo )
|
640
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
641
|
+
conn.finish
|
642
|
+
|
643
|
+
event, pid, msg = nil
|
644
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
645
|
+
event, pid, msg = arg1, arg2
|
646
|
+
end
|
647
|
+
@conn.exec( 'UNLISTEN knees' )
|
648
|
+
|
649
|
+
event.should == 'knees'
|
650
|
+
pid.should be_a_kind_of( Integer )
|
651
|
+
msg.should be_nil()
|
652
|
+
end
|
653
|
+
|
654
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
655
|
+
"doesn't accept arguments" do
|
656
|
+
|
657
|
+
@conn.exec( 'ROLLBACK' )
|
658
|
+
@conn.exec( 'LISTEN knees' )
|
659
|
+
|
660
|
+
conn = described_class.connect( @conninfo )
|
661
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
662
|
+
conn.finish
|
663
|
+
|
664
|
+
notification_received = false
|
665
|
+
@conn.wait_for_notify( 10 ) do
|
666
|
+
notification_received = true
|
667
|
+
end
|
668
|
+
@conn.exec( 'UNLISTEN knees' )
|
669
|
+
|
670
|
+
notification_received.should be_true()
|
671
|
+
end
|
672
|
+
|
673
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
674
|
+
"three arguments" do
|
675
|
+
|
676
|
+
@conn.exec( 'ROLLBACK' )
|
677
|
+
@conn.exec( 'LISTEN knees' )
|
678
|
+
|
679
|
+
conn = described_class.connect( @conninfo )
|
680
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
681
|
+
conn.finish
|
682
|
+
|
683
|
+
event, pid, msg = nil
|
684
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
685
|
+
event, pid, msg = arg1, arg2, arg3
|
686
|
+
end
|
687
|
+
@conn.exec( 'UNLISTEN knees' )
|
688
|
+
|
689
|
+
event.should == 'knees'
|
690
|
+
pid.should be_a_kind_of( Integer )
|
691
|
+
msg.should == 'skirt and boots'
|
692
|
+
end
|
693
|
+
|
694
|
+
end
|
695
|
+
|
696
|
+
context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
|
697
|
+
|
698
|
+
it "pings successfully with connection string" do
|
699
|
+
ping = described_class.ping(@conninfo)
|
700
|
+
ping.should == PG::PQPING_OK
|
701
|
+
end
|
702
|
+
|
703
|
+
it "pings using 7 arguments converted to strings" do
|
704
|
+
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
705
|
+
ping.should == PG::PQPING_OK
|
706
|
+
end
|
707
|
+
|
708
|
+
it "pings using a hash of connection parameters" do
|
709
|
+
ping = described_class.ping(
|
710
|
+
:host => 'localhost',
|
711
|
+
:port => @port,
|
712
|
+
:dbname => :test)
|
713
|
+
ping.should == PG::PQPING_OK
|
714
|
+
end
|
715
|
+
|
716
|
+
it "returns correct response when ping connection cannot be established" do
|
717
|
+
ping = described_class.ping(
|
718
|
+
:host => 'localhost',
|
719
|
+
:port => 9999,
|
720
|
+
:dbname => :test)
|
721
|
+
ping.should == PG::PQPING_NO_RESPONSE
|
722
|
+
end
|
723
|
+
|
724
|
+
it "returns correct response when ping connection arguments are wrong" do
|
725
|
+
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
726
|
+
ping.should == PG::PQPING_NO_ATTEMPT
|
727
|
+
end
|
728
|
+
|
729
|
+
|
730
|
+
end
|
731
|
+
|
732
|
+
context "under PostgreSQL 9.2 client library", :postgresql_92 do
|
733
|
+
describe "set_single_row_mode" do
|
734
|
+
|
735
|
+
it "raises an error when called at the wrong time" do
|
736
|
+
expect {
|
737
|
+
@conn.set_single_row_mode
|
738
|
+
}.to raise_error(PG::Error)
|
739
|
+
end
|
740
|
+
|
741
|
+
it "should work in single row mode" do
|
742
|
+
@conn.send_query( "SELECT generate_series(1,10)" )
|
743
|
+
@conn.set_single_row_mode
|
744
|
+
|
745
|
+
results = []
|
746
|
+
loop do
|
747
|
+
@conn.block
|
748
|
+
res = @conn.get_result or break
|
749
|
+
results << res
|
750
|
+
end
|
751
|
+
results.length.should == 11
|
752
|
+
results[0..-2].each do |res|
|
753
|
+
res.result_status.should == PG::PGRES_SINGLE_TUPLE
|
754
|
+
values = res.field_values('generate_series')
|
755
|
+
values.length.should == 1
|
756
|
+
values.first.to_i.should > 0
|
757
|
+
end
|
758
|
+
results.last.result_status.should == PG::PGRES_TUPLES_OK
|
759
|
+
results.last.ntuples.should == 0
|
760
|
+
end
|
761
|
+
|
762
|
+
it "should receive rows before entire query is finished" do
|
763
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
|
764
|
+
@conn.set_single_row_mode
|
765
|
+
|
766
|
+
start_time = Time.now
|
767
|
+
first_row_time = nil
|
768
|
+
loop do
|
769
|
+
res = @conn.get_result or break
|
770
|
+
res.check
|
771
|
+
first_row_time = Time.now unless first_row_time
|
772
|
+
end
|
773
|
+
(Time.now - start_time).should >= 1.0
|
774
|
+
(first_row_time - start_time).should < 1.0
|
775
|
+
end
|
776
|
+
|
777
|
+
it "should receive rows before entire query fails" do
|
778
|
+
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
779
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
780
|
+
@conn.set_single_row_mode
|
781
|
+
|
782
|
+
first_result = nil
|
783
|
+
expect do
|
784
|
+
loop do
|
785
|
+
res = @conn.get_result or break
|
786
|
+
res.check
|
787
|
+
first_result ||= res
|
788
|
+
end
|
789
|
+
end.to raise_error(PG::Error)
|
790
|
+
first_result.kind_of?(PG::Result).should be_true
|
791
|
+
first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
|
792
|
+
end
|
793
|
+
end
|
794
|
+
end
|
795
|
+
|
796
|
+
context "multinationalization support", :ruby_19 do
|
797
|
+
|
798
|
+
describe "rubyforge #22925: m17n support" do
|
799
|
+
it "should return results in the same encoding as the client (iso-8859-1)" do
|
800
|
+
out_string = nil
|
801
|
+
@conn.transaction do |conn|
|
802
|
+
conn.internal_encoding = 'iso8859-1'
|
803
|
+
res = conn.exec("VALUES ('fantasia')", [], 0)
|
804
|
+
out_string = res[0]['column1']
|
805
|
+
end
|
806
|
+
out_string.should == 'fantasia'
|
807
|
+
out_string.encoding.should == Encoding::ISO8859_1
|
808
|
+
end
|
809
|
+
|
810
|
+
it "should return results in the same encoding as the client (utf-8)" do
|
811
|
+
out_string = nil
|
812
|
+
@conn.transaction do |conn|
|
813
|
+
conn.internal_encoding = 'utf-8'
|
814
|
+
res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
|
815
|
+
out_string = res[0]['column1']
|
816
|
+
end
|
817
|
+
out_string.should == '世界線航跡蔵'
|
818
|
+
out_string.encoding.should == Encoding::UTF_8
|
819
|
+
end
|
820
|
+
|
821
|
+
it "should return results in the same encoding as the client (EUC-JP)" do
|
822
|
+
out_string = nil
|
823
|
+
@conn.transaction do |conn|
|
824
|
+
conn.internal_encoding = 'EUC-JP'
|
825
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
826
|
+
res = conn.exec(stmt, [], 0)
|
827
|
+
out_string = res[0]['column1']
|
828
|
+
end
|
829
|
+
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
830
|
+
out_string.encoding.should == Encoding::EUC_JP
|
831
|
+
end
|
832
|
+
|
833
|
+
it "returns the results in the correct encoding even if the client_encoding has " +
|
834
|
+
"changed since the results were fetched" do
|
835
|
+
out_string = nil
|
836
|
+
@conn.transaction do |conn|
|
837
|
+
conn.internal_encoding = 'EUC-JP'
|
838
|
+
stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
|
839
|
+
res = conn.exec(stmt, [], 0)
|
840
|
+
conn.internal_encoding = 'utf-8'
|
841
|
+
out_string = res[0]['column1']
|
842
|
+
end
|
843
|
+
out_string.should == '世界線航跡蔵'.encode('EUC-JP')
|
844
|
+
out_string.encoding.should == Encoding::EUC_JP
|
845
|
+
end
|
846
|
+
|
847
|
+
it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
|
848
|
+
@conn.exec "SET client_encoding TO SQL_ASCII"
|
849
|
+
@conn.internal_encoding.should == Encoding::ASCII_8BIT
|
850
|
+
end
|
851
|
+
|
852
|
+
it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
|
853
|
+
pending "figuring out how to create a string in the JOHAB encoding" do
|
854
|
+
out_string = nil
|
855
|
+
@conn.transaction do |conn|
|
856
|
+
conn.exec( "set client_encoding = 'JOHAB';" )
|
857
|
+
stmt = "VALUES ('foo')".encode('JOHAB')
|
858
|
+
res = conn.exec( stmt, [], 0 )
|
859
|
+
out_string = res[0]['column1']
|
860
|
+
end
|
861
|
+
out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
|
862
|
+
out_string.encoding.should == Encoding::ASCII_8BIT
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
it "uses the client encoding for escaped string" do
|
867
|
+
original = "string to escape".force_encoding( "euc-jp" )
|
868
|
+
@conn.set_client_encoding( "euc_jp" )
|
869
|
+
escaped = @conn.escape( original )
|
870
|
+
escaped.encoding.should == Encoding::EUC_JP
|
871
|
+
end
|
872
|
+
|
873
|
+
it "escapes string as literal", :postgresql_90 do
|
874
|
+
original = "string to\0 escape"
|
875
|
+
escaped = @conn.escape_literal( original )
|
876
|
+
escaped.should == "'string to'"
|
877
|
+
end
|
878
|
+
end
|
879
|
+
|
880
|
+
|
881
|
+
describe "Ruby 1.9.x default_internal encoding" do
|
882
|
+
|
883
|
+
it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
|
884
|
+
@conn.transaction do |txn_conn|
|
885
|
+
txn_conn.internal_encoding = Encoding::ISO8859_1
|
886
|
+
txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
|
887
|
+
txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
|
888
|
+
end
|
889
|
+
|
890
|
+
begin
|
891
|
+
prev_encoding = Encoding.default_internal
|
892
|
+
Encoding.default_internal = Encoding::UTF_8
|
893
|
+
|
894
|
+
conn = PG.connect( @conninfo )
|
895
|
+
conn.internal_encoding.should == Encoding::UTF_8
|
896
|
+
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
897
|
+
res[0]['foo'].encoding.should == Encoding::UTF_8
|
898
|
+
ensure
|
899
|
+
conn.finish if conn
|
900
|
+
Encoding.default_internal = prev_encoding
|
901
|
+
end
|
902
|
+
end
|
903
|
+
|
904
|
+
it "allows users of the async interface to set the client_encoding to the default_internal" do
|
905
|
+
begin
|
906
|
+
prev_encoding = Encoding.default_internal
|
907
|
+
Encoding.default_internal = Encoding::KOI8_R
|
908
|
+
|
909
|
+
@conn.set_default_encoding
|
910
|
+
|
911
|
+
@conn.internal_encoding.should == Encoding::KOI8_R
|
912
|
+
ensure
|
913
|
+
Encoding.default_internal = prev_encoding
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
end
|
918
|
+
|
919
|
+
|
920
|
+
it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
|
921
|
+
# Use a new connection so the client_encoding isn't set outside of this example
|
922
|
+
conn = PG.connect( @conninfo )
|
923
|
+
conn.client_encoding = 'iso-8859-15'
|
924
|
+
|
925
|
+
conn.transaction do
|
926
|
+
conn.exec "CREATE TABLE foo (bar TEXT)"
|
927
|
+
|
928
|
+
begin
|
929
|
+
query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
|
930
|
+
conn.exec( query )
|
931
|
+
rescue => err
|
932
|
+
err.message.encoding.should == Encoding::ISO8859_15
|
933
|
+
else
|
934
|
+
fail "No exception raised?!"
|
935
|
+
end
|
936
|
+
end
|
937
|
+
|
938
|
+
conn.finish if conn
|
939
|
+
end
|
940
|
+
|
941
|
+
it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
|
942
|
+
[:receiver, :processor].each do |kind|
|
943
|
+
notices = []
|
944
|
+
@conn.internal_encoding = 'utf-8'
|
945
|
+
if kind == :processor
|
946
|
+
@conn.set_notice_processor do |msg|
|
947
|
+
notices << msg
|
948
|
+
end
|
949
|
+
else
|
950
|
+
@conn.set_notice_receiver do |result|
|
951
|
+
notices << result.error_message
|
952
|
+
end
|
953
|
+
end
|
954
|
+
|
955
|
+
3.times do
|
956
|
+
@conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
|
957
|
+
end
|
958
|
+
|
959
|
+
notices.length.should == 3
|
960
|
+
notices.each do |notice|
|
961
|
+
notice.should =~ /^NOTICE:.*世界線航跡蔵/
|
962
|
+
notice.encoding.should == Encoding::UTF_8
|
963
|
+
end
|
964
|
+
@conn.set_notice_receiver
|
965
|
+
@conn.set_notice_processor
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
it "receives properly encoded text from wait_for_notify", :postgresql_90 do
|
970
|
+
@conn.internal_encoding = 'utf-8'
|
971
|
+
@conn.exec( 'ROLLBACK' )
|
972
|
+
@conn.exec( 'LISTEN "Möhre"' )
|
973
|
+
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
974
|
+
event, pid, msg = nil
|
975
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
976
|
+
event, pid, msg = *args
|
977
|
+
end
|
978
|
+
@conn.exec( 'UNLISTEN "Möhre"' )
|
979
|
+
|
980
|
+
event.should == "Möhre"
|
981
|
+
event.encoding.should == Encoding::UTF_8
|
982
|
+
msg.should == '世界線航跡蔵'
|
983
|
+
msg.encoding.should == Encoding::UTF_8
|
984
|
+
end
|
985
|
+
|
986
|
+
it "returns properly encoded text from notifies", :postgresql_90 do
|
987
|
+
@conn.internal_encoding = 'utf-8'
|
988
|
+
@conn.exec( 'ROLLBACK' )
|
989
|
+
@conn.exec( 'LISTEN "Möhre"' )
|
990
|
+
@conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
|
991
|
+
@conn.exec( 'UNLISTEN "Möhre"' )
|
992
|
+
|
993
|
+
notification = @conn.notifies
|
994
|
+
notification[:relname].should == "Möhre"
|
995
|
+
notification[:relname].encoding.should == Encoding::UTF_8
|
996
|
+
notification[:extra].should == '世界線航跡蔵'
|
997
|
+
notification[:extra].encoding.should == Encoding::UTF_8
|
998
|
+
notification[:be_pid].should > 0
|
999
|
+
end
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
context "OS thread support", :ruby_19 do
|
1003
|
+
it "described_class#exec shouldn't block a second thread" do
|
1004
|
+
t = Thread.new do
|
1005
|
+
@conn.exec( "select pg_sleep(1)" )
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
sleep 0.5
|
1009
|
+
t.should be_alive()
|
1010
|
+
t.join
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
end
|