jruby-pg 0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/BSDL +22 -0
  4. data/ChangeLog +0 -0
  5. data/Contributors.rdoc +45 -0
  6. data/History.rdoc +270 -0
  7. data/LICENSE +56 -0
  8. data/Manifest.txt +44 -0
  9. data/POSTGRES +23 -0
  10. data/README-OS_X.rdoc +68 -0
  11. data/README-Windows.rdoc +67 -0
  12. data/README.ja.rdoc +14 -0
  13. data/README.rdoc +102 -0
  14. data/Rakefile +211 -0
  15. data/Rakefile.cross +273 -0
  16. data/ext/gvl_wrappers.c +13 -0
  17. data/ext/pg.c +545 -0
  18. data/ext/pg_connection.c +3643 -0
  19. data/ext/pg_errors.c +89 -0
  20. data/ext/pg_result.c +920 -0
  21. data/lib/pg.rb +52 -0
  22. data/lib/pg/connection.rb +179 -0
  23. data/lib/pg/constants.rb +11 -0
  24. data/lib/pg/exceptions.rb +11 -0
  25. data/lib/pg/result.rb +16 -0
  26. data/lib/pg_ext.jar +0 -0
  27. data/sample/array_insert.rb +20 -0
  28. data/sample/async_api.rb +106 -0
  29. data/sample/async_copyto.rb +39 -0
  30. data/sample/async_mixed.rb +56 -0
  31. data/sample/check_conn.rb +21 -0
  32. data/sample/copyfrom.rb +81 -0
  33. data/sample/copyto.rb +19 -0
  34. data/sample/cursor.rb +21 -0
  35. data/sample/disk_usage_report.rb +186 -0
  36. data/sample/issue-119.rb +94 -0
  37. data/sample/losample.rb +69 -0
  38. data/sample/minimal-testcase.rb +17 -0
  39. data/sample/notify_wait.rb +72 -0
  40. data/sample/pg_statistics.rb +294 -0
  41. data/sample/replication_monitor.rb +231 -0
  42. data/sample/test_binary_values.rb +33 -0
  43. data/sample/wal_shipper.rb +434 -0
  44. data/sample/warehouse_partitions.rb +320 -0
  45. data/spec/data/expected_trace.out +26 -0
  46. data/spec/data/random_binary_data +0 -0
  47. data/spec/lib/helpers.rb +350 -0
  48. data/spec/pg/connection_spec.rb +1276 -0
  49. data/spec/pg/result_spec.rb +345 -0
  50. data/spec/pg_spec.rb +44 -0
  51. metadata +190 -0
@@ -0,0 +1,1276 @@
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 |example|
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 |example|
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 "raises proper error when sending fails" do
181
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
182
+ expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
183
+ end
184
+
185
+ it "doesn't leave stale server connections after finish" do
186
+ described_class.connect(@conninfo).finish
187
+ sleep 0.5
188
+ res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
189
+ WHERE usename IS NOT NULL])
190
+ # there's still the global @conn, but should be no more
191
+ res[0]['n'].should == '1'
192
+ end
193
+
194
+
195
+ EXPECTED_TRACE_OUTPUT = %{
196
+ To backend> Msg Q
197
+ To backend> "SELECT 1 AS one"
198
+ To backend> Msg complete, length 21
199
+ From backend> T
200
+ From backend (#4)> 28
201
+ From backend (#2)> 1
202
+ From backend> "one"
203
+ From backend (#4)> 0
204
+ From backend (#2)> 0
205
+ From backend (#4)> 23
206
+ From backend (#2)> 4
207
+ From backend (#4)> -1
208
+ From backend (#2)> 0
209
+ From backend> D
210
+ From backend (#4)> 11
211
+ From backend (#2)> 1
212
+ From backend (#4)> 1
213
+ From backend (1)> 1
214
+ From backend> C
215
+ From backend (#4)> 13
216
+ From backend> "SELECT 1"
217
+ From backend> Z
218
+ From backend (#4)> 5} +
219
+ # JRuby will only print the msg header when it's ready to process
220
+ # the message on the other hand, libpq will print the
221
+ # ReadyForQuery msg header then realize it's in the Idle state
222
+ # stop processing and wait for the user to consume the Result.
223
+ # That's why we get a double 'Z' msg on MRI.
224
+ if jruby?
225
+ %{
226
+ From backend> T
227
+ }
228
+ else
229
+ %{
230
+ From backend> Z
231
+ From backend (#4)> 5
232
+ From backend> T
233
+ }
234
+ end
235
+
236
+
237
+ it "trace and untrace client-server communication", :unix do
238
+ # be careful to explicitly close files so that the
239
+ # directory can be removed and we don't have to wait for
240
+ # the GC to run.
241
+ trace_file = TEST_DIRECTORY + "test_trace.out"
242
+ trace_io = trace_file.open( 'w', 0600 )
243
+ @conn.trace( trace_io )
244
+ trace_io.close
245
+
246
+ res = @conn.exec("SELECT 1 AS one")
247
+ @conn.untrace
248
+
249
+ res = @conn.exec("SELECT 2 AS two")
250
+
251
+ trace_data = trace_file.read
252
+
253
+ expected_trace_output = EXPECTED_TRACE_OUTPUT.dup.gsub( /^\t+/, '' ).lstrip
254
+ # For PostgreSQL < 9.0, the output will be different:
255
+ # -From backend (#4)> 13
256
+ # -From backend> "SELECT 1"
257
+ # +From backend (#4)> 11
258
+ # +From backend> "SELECT"
259
+ if @conn.server_version < 90000
260
+ expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
261
+ expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
262
+ end
263
+
264
+ trace_data.should == expected_trace_output
265
+ end
266
+
267
+ it "allows a query to be cancelled" do
268
+ error = false
269
+ @conn.send_query("SELECT pg_sleep(1000)")
270
+ @conn.cancel
271
+ tmpres = @conn.get_result
272
+ if(tmpres.result_status != PG::PGRES_TUPLES_OK)
273
+ error = true
274
+ end
275
+ error.should == true
276
+ end
277
+
278
+ # We disable the following tests for JRuby. First because
279
+ # Thread#kill isn't safe and on JRuby it can't effectively stop a
280
+ # thread running native Java code. Second, JVM doesn't play nice
281
+ # with signals especially USR1. Sending the signal to the JVM will
282
+ # cause it to exit
283
+ unless jruby?
284
+ it "can stop a thread that runs a blocking query with async_exec" do
285
+ start = Time.now
286
+ t = Thread.new do
287
+ @conn.async_exec( 'select pg_sleep(10)' )
288
+ end
289
+ sleep 0.1
290
+
291
+ t.kill
292
+ t.join
293
+ (Time.now - start).should < 10
294
+ end
295
+
296
+ it "should work together with signal handlers" do
297
+ signal_received = false
298
+ trap 'USR1' do
299
+ signal_received = true
300
+ end
301
+
302
+ Thread.new do
303
+ sleep 0.1
304
+ Process.kill("USR1", Process.pid)
305
+ end
306
+ @conn.exec("select pg_sleep(0.3)")
307
+ signal_received.should be_true
308
+
309
+ signal_received = false
310
+ Thread.new do
311
+ sleep 0.1
312
+ Process.kill("USR1", Process.pid)
313
+ end
314
+ @conn.async_exec("select pg_sleep(0.3)")
315
+ signal_received.should be_true
316
+ end
317
+ end
318
+
319
+
320
+ it "automatically rolls back a transaction started with Connection#transaction if an exception " +
321
+ "is raised" do
322
+ # abort the per-example transaction so we can test our own
323
+ @conn.exec( 'ROLLBACK' )
324
+
325
+ res = nil
326
+ @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
327
+
328
+ expect {
329
+ res = @conn.transaction do
330
+ @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
331
+ raise "Oh noes! All pie is gone!"
332
+ end
333
+ }.to raise_exception( RuntimeError, /all pie is gone/i )
334
+
335
+ res = @conn.exec( "SELECT * FROM pie" )
336
+ res.ntuples.should == 0
337
+ end
338
+
339
+ it "returns the block result from Connection#transaction" do
340
+ # abort the per-example transaction so we can test our own
341
+ @conn.exec( 'ROLLBACK' )
342
+
343
+ res = @conn.transaction do
344
+ "transaction result"
345
+ end
346
+ res.should == "transaction result"
347
+ end
348
+
349
+ it "not read past the end of a large object" do
350
+ @conn.transaction do
351
+ oid = @conn.lo_create( 0 )
352
+ fd = @conn.lo_open( oid, PG::INV_READ|PG::INV_WRITE )
353
+ @conn.lo_write( fd, "foobar" )
354
+ @conn.lo_read( fd, 10 ).should be_nil()
355
+ @conn.lo_lseek( fd, 0, PG::SEEK_SET )
356
+ @conn.lo_read( fd, 10 ).should == 'foobar'
357
+ end
358
+ end
359
+
360
+
361
+ it "supports parameters passed to #exec (backward compatibility)" do
362
+ @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
363
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
364
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
365
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
366
+
367
+ res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
368
+ res.values.should == [ ['Wally'], ['Sally'] ]
369
+ end
370
+
371
+ it "supports explicitly calling #exec_params" do
372
+ @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
373
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
374
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
375
+ @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
376
+
377
+ res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
378
+ res.values.should == [ ['Wally'], ['Sally'] ]
379
+ end
380
+
381
+
382
+ it "can wait for NOTIFY events" do
383
+ @conn.exec( 'ROLLBACK' )
384
+ @conn.exec( 'LISTEN woo' )
385
+
386
+ t = Thread.new do
387
+ begin
388
+ conn = described_class.connect( @conninfo )
389
+ sleep 1
390
+ conn.async_exec( 'NOTIFY woo' )
391
+ ensure
392
+ conn.finish
393
+ end
394
+ end
395
+
396
+ @conn.wait_for_notify( 10 ).should == 'woo'
397
+ @conn.exec( 'UNLISTEN woo' )
398
+
399
+ t.join
400
+ end
401
+
402
+ it "calls a block for NOTIFY events if one is given" do
403
+ @conn.exec( 'ROLLBACK' )
404
+ @conn.exec( 'LISTEN woo' )
405
+
406
+ t = Thread.new do
407
+ begin
408
+ conn = described_class.connect( @conninfo )
409
+ sleep 1
410
+ conn.async_exec( 'NOTIFY woo' )
411
+ ensure
412
+ conn.finish
413
+ end
414
+ end
415
+
416
+ eventpid = event = nil
417
+ @conn.wait_for_notify( 10 ) {|*args| event, eventpid = args }
418
+ event.should == 'woo'
419
+ eventpid.should be_an( Integer )
420
+
421
+ @conn.exec( 'UNLISTEN woo' )
422
+
423
+ t.join
424
+ end
425
+
426
+ it "doesn't collapse sequential notifications" do
427
+ @conn.exec( 'ROLLBACK' )
428
+ @conn.exec( 'LISTEN woo' )
429
+ @conn.exec( 'LISTEN war' )
430
+ @conn.exec( 'LISTEN woz' )
431
+
432
+ begin
433
+ conn = described_class.connect( @conninfo )
434
+ conn.exec( 'NOTIFY woo' )
435
+ conn.exec( 'NOTIFY war' )
436
+ conn.exec( 'NOTIFY woz' )
437
+ ensure
438
+ conn.finish
439
+ end
440
+
441
+ channels = []
442
+ 3.times do
443
+ channels << @conn.wait_for_notify( 2 )
444
+ end
445
+
446
+ channels.should have( 3 ).members
447
+ channels.should include( 'woo', 'war', 'woz' )
448
+
449
+ @conn.exec( 'UNLISTEN woz' )
450
+ @conn.exec( 'UNLISTEN war' )
451
+ @conn.exec( 'UNLISTEN woo' )
452
+ end
453
+
454
+ it "returns notifications which are already in the queue before wait_for_notify is called " +
455
+ "without waiting for the socket to become readable" do
456
+ @conn.exec( 'ROLLBACK' )
457
+ @conn.exec( 'LISTEN woo' )
458
+
459
+ begin
460
+ conn = described_class.connect( @conninfo )
461
+ conn.exec( 'NOTIFY woo' )
462
+ ensure
463
+ conn.finish
464
+ end
465
+
466
+ # Cause the notification to buffer, but not be read yet
467
+ @conn.exec( 'SELECT 1' )
468
+
469
+ @conn.wait_for_notify( 10 ).should == 'woo'
470
+ @conn.exec( 'UNLISTEN woo' )
471
+ end
472
+
473
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
474
+ notices = []
475
+ @conn.set_notice_processor do |msg|
476
+ notices << [msg, Time.now]
477
+ end
478
+ st = Time.now
479
+ @conn.send_query "SELECT pg_sleep(0.5); do $$ BEGIN RAISE NOTICE 'woohoo'; END; $$ LANGUAGE plpgsql;"
480
+ @conn.wait_for_notify( 1 ).should be_nil
481
+ notices.first.should_not be_nil
482
+ et = Time.now
483
+ (et - notices.first[1]).should >= 0.4
484
+ (et - st).should >= 0.9
485
+ (et - st).should < 1.4
486
+ end
487
+
488
+ it "yields the result if block is given to exec" do
489
+ rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
490
+ values = []
491
+ result.should be_kind_of( PG::Result )
492
+ result.ntuples.should == 2
493
+ result.each do |tuple|
494
+ values << tuple['a']
495
+ end
496
+ values
497
+ end
498
+
499
+ rval.should have( 2 ).members
500
+ rval.should include( '5678', '1234' )
501
+ end
502
+
503
+ it "can process #copy_data output queries" do
504
+ rows = []
505
+ res2 = @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
506
+ res.result_status.should == PG::PGRES_COPY_OUT
507
+ res.nfields.should == 1
508
+ while row=@conn.get_copy_data
509
+ rows << row
510
+ end
511
+ end
512
+ rows.should == ["1\n", "2\n"]
513
+ res2.result_status.should == PG::PGRES_COMMAND_OK
514
+ verify_clean_exec_status
515
+ end
516
+
517
+ it "can handle incomplete #copy_data output queries" do
518
+ expect {
519
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
520
+ @conn.get_copy_data
521
+ end
522
+ }.to raise_error(PG::NotAllCopyDataRetrieved, /Not all/)
523
+ verify_clean_exec_status
524
+ end
525
+
526
+ it "can handle client errors in #copy_data for output" do
527
+ expect {
528
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do
529
+ raise "boom"
530
+ end
531
+ }.to raise_error(RuntimeError, "boom")
532
+ verify_clean_exec_status
533
+ end
534
+
535
+ it "can handle server errors in #copy_data for output" do
536
+ @conn.exec "ROLLBACK"
537
+ @conn.transaction do
538
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
539
+ expect {
540
+ @conn.copy_data( "COPY (SELECT errfunc()) TO STDOUT" ) do |res|
541
+ while @conn.get_copy_data
542
+ end
543
+ end
544
+ }.to raise_error(PG::Error, /test-error/)
545
+ end
546
+ verify_clean_exec_status
547
+ end
548
+
549
+ it "can process #copy_data input queries" do
550
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
551
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
552
+ res.result_status.should == PG::PGRES_COPY_IN
553
+ res.nfields.should == 1
554
+ @conn.put_copy_data "1\n"
555
+ @conn.put_copy_data "2\n"
556
+ end
557
+ res2.result_status.should == PG::PGRES_COMMAND_OK
558
+
559
+ verify_clean_exec_status
560
+
561
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
562
+ res.values.should == [["1"], ["2"]]
563
+ end
564
+
565
+ it "can handle client errors in #copy_data for input" do
566
+ @conn.exec "ROLLBACK"
567
+ @conn.transaction do
568
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
569
+ expect {
570
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
571
+ raise "boom"
572
+ end
573
+ }.to raise_error(RuntimeError, "boom")
574
+ end
575
+ verify_clean_exec_status
576
+ end
577
+
578
+ it "can handle server errors in #copy_data for input" do
579
+ @conn.exec "ROLLBACK"
580
+ @conn.transaction do
581
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
582
+ expect {
583
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
584
+ @conn.put_copy_data "xyz\n"
585
+ end
586
+ }.to raise_error(PG::Error, /invalid input syntax for integer/)
587
+ end
588
+ verify_clean_exec_status
589
+ end
590
+
591
+ it "should raise an error for non copy statements in #copy_data" do
592
+ expect {
593
+ @conn.copy_data( "SELECT 1" ){}
594
+ }.to raise_error(ArgumentError, /no COPY/)
595
+
596
+ verify_clean_exec_status
597
+ end
598
+
599
+ it "correctly finishes COPY queries passed to #async_exec" do
600
+ @conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
601
+
602
+ results = []
603
+ begin
604
+ data = @conn.get_copy_data( true )
605
+ if false == data
606
+ @conn.block( 2.0 )
607
+ data = @conn.get_copy_data( true )
608
+ end
609
+ results << data if data
610
+ end until data.nil?
611
+
612
+ results.should have( 2 ).members
613
+ results.should include( "1\n", "2\n" )
614
+ end
615
+
616
+
617
+ it "described_class#block shouldn't block a second thread" do
618
+ start = Time.now
619
+ t = Thread.new do
620
+ @conn.send_query( "select pg_sleep(3)" )
621
+ @conn.block
622
+ end
623
+
624
+ sleep 0.5
625
+ t.should be_alive()
626
+ @conn.cancel
627
+ t.join
628
+ (Time.now - start).should < 3
629
+ end
630
+
631
+ it "described_class#block should allow a timeout" do
632
+ @conn.send_query( "select pg_sleep(3)" )
633
+
634
+ start = Time.now
635
+ @conn.block( 0.1 )
636
+ finish = Time.now
637
+
638
+ (finish - start).should be_within( 0.05 ).of( 0.1 )
639
+ end
640
+
641
+
642
+ it "can encrypt a string given a password and username" do
643
+ described_class.encrypt_password("postgres", "postgres").
644
+ should =~ /\S+/
645
+ end
646
+
647
+
648
+ it "raises an appropriate error if either of the required arguments for encrypt_password " +
649
+ "is not valid" do
650
+ expect {
651
+ described_class.encrypt_password( nil, nil )
652
+ }.to raise_error( TypeError )
653
+ expect {
654
+ described_class.encrypt_password( "postgres", nil )
655
+ }.to raise_error( TypeError )
656
+ expect {
657
+ described_class.encrypt_password( nil, "postgres" )
658
+ }.to raise_error( TypeError )
659
+ end
660
+
661
+
662
+ it "allows fetching a column of values from a result by column number" do
663
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
664
+ res.column_values( 0 ).should == %w[1 2 3]
665
+ res.column_values( 1 ).should == %w[2 3 4]
666
+ end
667
+
668
+
669
+ it "allows fetching a column of values from a result by field name" do
670
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
671
+ res.field_values( 'column1' ).should == %w[1 2 3]
672
+ res.field_values( 'column2' ).should == %w[2 3 4]
673
+ end
674
+
675
+
676
+ it "raises an error if selecting an invalid column index" do
677
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
678
+ expect {
679
+ res.column_values( 20 )
680
+ }.to raise_error( IndexError )
681
+ end
682
+
683
+
684
+ it "raises an error if selecting an invalid field name" do
685
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
686
+ expect {
687
+ res.field_values( 'hUUuurrg' )
688
+ }.to raise_error( IndexError )
689
+ end
690
+
691
+
692
+ it "raises an error if column index is not a number" do
693
+ res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
694
+ expect {
695
+ res.column_values( 'hUUuurrg' )
696
+ }.to raise_error( TypeError )
697
+ end
698
+
699
+
700
+ it "can connect asynchronously", :socket_io do
701
+ serv = TCPServer.new( '127.0.0.1', 54320 )
702
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
703
+ [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK].should include conn.connect_poll
704
+ select( nil, [conn.socket_io], nil, 0.2 )
705
+ serv.close
706
+ if conn.connect_poll == PG::PGRES_POLLING_READING
707
+ select( [conn.socket_io], nil, nil, 0.2 )
708
+ end
709
+ conn.connect_poll.should == PG::PGRES_POLLING_FAILED
710
+ end
711
+
712
+ it "discards previous results (if any) before waiting on an #async_exec"
713
+
714
+ it "ignore nil params" do
715
+ res = @conn.async_exec 'select 1 as one', nil
716
+ res[0]['one'].should == '1'
717
+ end
718
+
719
+ it "ignore nil params" do
720
+ res = @conn.async_exec 'select $1::boolean as b', [false]
721
+ res[0]['b'].should == 'f'
722
+ end
723
+
724
+ it "calls the block if one is provided to #async_exec" do
725
+ result = nil
726
+ @conn.async_exec( "select 47 as one" ) do |pg_res|
727
+ result = pg_res[0]
728
+ end
729
+ result.should == { 'one' => '47' }
730
+ end
731
+
732
+ it "raises a rescue-able error if #finish is called twice", :without_transaction do
733
+ conn = PG.connect( @conninfo )
734
+
735
+ conn.finish
736
+ expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
737
+ end
738
+
739
+ it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
740
+ conn = PG.connect( @conninfo )
741
+ io = conn.socket_io
742
+ conn.finish
743
+ io.should be_closed()
744
+ expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
745
+ end
746
+
747
+ it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
748
+ conn = PG.connect( @conninfo )
749
+ io = conn.socket_io
750
+ conn.reset
751
+ io.should be_closed()
752
+ conn.socket_io.should_not equal( io )
753
+ conn.finish
754
+ end
755
+
756
+ it "block should raise ConnectionBad for a closed connection" do
757
+ serv = TCPServer.new( '127.0.0.1', 54320 )
758
+ conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
759
+ while [PG::CONNECTION_STARTED, PG::CONNECTION_MADE].include?(conn.connect_poll)
760
+ sleep 0.1
761
+ end
762
+ serv.close
763
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly|not connected/)
764
+ expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor|not connected/)
765
+ end
766
+
767
+ context "under PostgreSQL 9", :postgresql_90 do
768
+
769
+ before( :each ) do
770
+ pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
771
+ end
772
+
773
+ it "sets the fallback_application_name on new connections" do
774
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
775
+ connection_string_should_contain_application_name(conn_string, $0)
776
+ end
777
+
778
+ it "sets a shortened fallback_application_name on new connections" do
779
+ old_0 = $0
780
+ begin
781
+ $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
782
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
783
+ connection_string_should_contain_application_name(conn_string, $0)
784
+ ensure
785
+ $0 = old_0
786
+ end
787
+ end
788
+
789
+ it "calls the block supplied to wait_for_notify with the notify" +
790
+ " payload if it accepts any number of arguments" do
791
+
792
+ @conn.exec( 'ROLLBACK' )
793
+ @conn.exec( 'LISTEN knees' )
794
+
795
+ conn = described_class.connect( @conninfo )
796
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
797
+ conn.finish
798
+
799
+ event, pid, msg = nil
800
+ @conn.wait_for_notify( 10 ) do |*args|
801
+ event, pid, msg = *args
802
+ end
803
+ @conn.exec( 'UNLISTEN knees' )
804
+
805
+ event.should == 'knees'
806
+ pid.should be_a_kind_of( Integer )
807
+ msg.should == 'skirt and boots'
808
+ end
809
+
810
+ it "accepts nil as the timeout in #wait_for_notify " do
811
+ @conn.exec( 'ROLLBACK' )
812
+ @conn.exec( 'LISTEN knees' )
813
+
814
+ conn = described_class.connect( @conninfo )
815
+ conn.exec( %Q{NOTIFY knees} )
816
+ conn.finish
817
+
818
+ event, pid = nil
819
+ @conn.wait_for_notify( nil ) do |*args|
820
+ event, pid = *args
821
+ end
822
+ @conn.exec( 'UNLISTEN knees' )
823
+
824
+ event.should == 'knees'
825
+ pid.should be_a_kind_of( Integer )
826
+ end
827
+
828
+ it "sends nil as the payload if the notification wasn't given one" do
829
+ @conn.exec( 'ROLLBACK' )
830
+ @conn.exec( 'LISTEN knees' )
831
+
832
+ conn = described_class.connect( @conninfo )
833
+ conn.exec( %Q{NOTIFY knees} )
834
+ conn.finish
835
+
836
+ payload = :notnil
837
+ @conn.wait_for_notify( nil ) do |*args|
838
+ payload = args[ 2 ]
839
+ end
840
+ @conn.exec( 'UNLISTEN knees' )
841
+
842
+ payload.should be_nil()
843
+ end
844
+
845
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
846
+ "two arguments" do
847
+
848
+ @conn.exec( 'ROLLBACK' )
849
+ @conn.exec( 'LISTEN knees' )
850
+
851
+ conn = described_class.connect( @conninfo )
852
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
853
+ conn.finish
854
+
855
+ event, pid, msg = nil
856
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
857
+ event, pid, msg = arg1, arg2
858
+ end
859
+ @conn.exec( 'UNLISTEN knees' )
860
+
861
+ event.should == 'knees'
862
+ pid.should be_a_kind_of( Integer )
863
+ msg.should be_nil()
864
+ end
865
+
866
+ it "calls the block supplied to wait_for_notify with the notify payload if it " +
867
+ "doesn't accept arguments" do
868
+
869
+ @conn.exec( 'ROLLBACK' )
870
+ @conn.exec( 'LISTEN knees' )
871
+
872
+ conn = described_class.connect( @conninfo )
873
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
874
+ conn.finish
875
+
876
+ notification_received = false
877
+ @conn.wait_for_notify( 10 ) do
878
+ notification_received = true
879
+ end
880
+ @conn.exec( 'UNLISTEN knees' )
881
+
882
+ notification_received.should be_true()
883
+ end
884
+
885
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
886
+ "three arguments" do
887
+
888
+ @conn.exec( 'ROLLBACK' )
889
+ @conn.exec( 'LISTEN knees' )
890
+
891
+ conn = described_class.connect( @conninfo )
892
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
893
+ conn.finish
894
+
895
+ event, pid, msg = nil
896
+ @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
897
+ event, pid, msg = arg1, arg2, arg3
898
+ end
899
+ @conn.exec( 'UNLISTEN knees' )
900
+
901
+ event.should == 'knees'
902
+ pid.should be_a_kind_of( Integer )
903
+ msg.should == 'skirt and boots'
904
+ end
905
+
906
+ end
907
+
908
+ context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
909
+
910
+ it "pings successfully with connection string" do
911
+ ping = described_class.ping(@conninfo)
912
+ ping.should == PG::PQPING_OK
913
+ end
914
+
915
+ it "pings using 7 arguments converted to strings" do
916
+ ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
917
+ ping.should == PG::PQPING_OK
918
+ end
919
+
920
+ it "pings using a hash of connection parameters" do
921
+ ping = described_class.ping(
922
+ :host => 'localhost',
923
+ :port => @port,
924
+ :dbname => :test)
925
+ ping.should == PG::PQPING_OK
926
+ end
927
+
928
+ it "returns correct response when ping connection cannot be established" do
929
+ ping = described_class.ping(
930
+ :host => 'localhost',
931
+ :port => 9999,
932
+ :dbname => :test)
933
+ ping.should == PG::PQPING_NO_RESPONSE
934
+ end
935
+
936
+ it "returns correct response when ping connection arguments are wrong" do
937
+ ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
938
+ ping.should == PG::PQPING_NO_ATTEMPT
939
+ end
940
+
941
+
942
+ end
943
+
944
+ context "under PostgreSQL 9.2 client library", :postgresql_92 do
945
+ describe "set_single_row_mode" do
946
+
947
+ it "raises an error when called at the wrong time" do
948
+ expect {
949
+ @conn.set_single_row_mode
950
+ }.to raise_error(PG::Error)
951
+ end
952
+
953
+ it "should work in single row mode" do
954
+ @conn.send_query( "SELECT generate_series(1,10)" )
955
+ @conn.set_single_row_mode
956
+
957
+ results = []
958
+ loop do
959
+ @conn.block
960
+ res = @conn.get_result or break
961
+ results << res
962
+ end
963
+ results.length.should == 11
964
+ results[0..-2].each do |res|
965
+ res.result_status.should == PG::PGRES_SINGLE_TUPLE
966
+ values = res.field_values('generate_series')
967
+ values.length.should == 1
968
+ values.first.to_i.should > 0
969
+ end
970
+ results.last.result_status.should == PG::PGRES_TUPLES_OK
971
+ results.last.ntuples.should == 0
972
+ end
973
+
974
+ it "should receive rows before entire query is finished" do
975
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
976
+ @conn.set_single_row_mode
977
+
978
+ start_time = Time.now
979
+ first_row_time = nil
980
+ loop do
981
+ res = @conn.get_result or break
982
+ res.check
983
+ first_row_time = Time.now unless first_row_time
984
+ end
985
+ (Time.now - start_time).should >= 1.0
986
+ (first_row_time - start_time).should < 1.0
987
+ end
988
+
989
+ it "should receive rows before entire query fails" do
990
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
991
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
992
+ @conn.set_single_row_mode
993
+
994
+ first_result = nil
995
+ expect do
996
+ loop do
997
+ res = @conn.get_result or break
998
+ res.check
999
+ first_result ||= res
1000
+ end
1001
+ end.to raise_error(PG::Error)
1002
+ first_result.kind_of?(PG::Result).should be_true
1003
+ first_result.result_status.should == PG::PGRES_SINGLE_TUPLE
1004
+ end
1005
+ end
1006
+ end
1007
+
1008
+ context "multinationalization support", :ruby_19 do
1009
+
1010
+ describe "rubyforge #22925: m17n support" do
1011
+ it "should return results in the same encoding as the client (iso-8859-1)" do
1012
+ out_string = nil
1013
+ @conn.transaction do |conn|
1014
+ conn.internal_encoding = 'iso8859-1'
1015
+ res = conn.exec("VALUES ('fantasia')", [], 0)
1016
+ out_string = res[0]['column1']
1017
+ end
1018
+ out_string.should == 'fantasia'
1019
+ out_string.encoding.should == Encoding::ISO8859_1
1020
+ end
1021
+
1022
+ it "should return results in the same encoding as the client (utf-8)" do
1023
+ out_string = nil
1024
+ @conn.transaction do |conn|
1025
+ conn.internal_encoding = 'utf-8'
1026
+ res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1027
+ out_string = res[0]['column1']
1028
+ end
1029
+ out_string.encoding.should == Encoding::UTF_8
1030
+ out_string.should == '世界線航跡蔵'
1031
+ end
1032
+
1033
+ it "should return results in the same encoding as the client (EUC-JP)" do
1034
+ out_string = nil
1035
+ @conn.transaction do |conn|
1036
+ conn.internal_encoding = 'EUC-JP'
1037
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1038
+ res = conn.exec(stmt, [], 0)
1039
+ out_string = res[0]['column1']
1040
+ end
1041
+ out_string.should == '世界線航跡蔵'.encode('EUC-JP')
1042
+ out_string.encoding.should == Encoding::EUC_JP
1043
+ end
1044
+
1045
+ it "returns the results in the correct encoding even if the client_encoding has " +
1046
+ "changed since the results were fetched" do
1047
+ out_string = nil
1048
+ @conn.transaction do |conn|
1049
+ conn.internal_encoding = 'EUC-JP'
1050
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1051
+ res = conn.exec(stmt, [], 0)
1052
+ conn.internal_encoding = 'utf-8'
1053
+ out_string = res[0]['column1']
1054
+ end
1055
+ out_string.should == '世界線航跡蔵'.encode('EUC-JP')
1056
+ out_string.encoding.should == Encoding::EUC_JP
1057
+ end
1058
+
1059
+ it "the connection should return ASCII-8BIT when it's set to SQL_ASCII" do
1060
+ @conn.exec "SET client_encoding TO SQL_ASCII"
1061
+ @conn.internal_encoding.should == Encoding::ASCII_8BIT
1062
+ end
1063
+
1064
+ it "works around the unsupported JOHAB encoding by returning stuff in 'ASCII_8BIT'" do
1065
+ pending "figuring out how to create a string in the JOHAB encoding" do
1066
+ out_string = nil
1067
+ @conn.transaction do |conn|
1068
+ conn.exec( "set client_encoding = 'JOHAB';" )
1069
+ stmt = "VALUES ('foo')".encode('JOHAB')
1070
+ res = conn.exec( stmt, [], 0 )
1071
+ out_string = res[0]['column1']
1072
+ end
1073
+ out_string.should == 'foo'.encode( Encoding::ASCII_8BIT )
1074
+ out_string.encoding.should == Encoding::ASCII_8BIT
1075
+ end
1076
+ end
1077
+
1078
+ it "uses the client encoding for escaped string" do
1079
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1080
+ @conn.set_client_encoding( "euc_jp" )
1081
+ escaped = @conn.escape( original )
1082
+ escaped.encoding.should == Encoding::EUC_JP
1083
+ escaped.should == "string to"
1084
+ end
1085
+
1086
+ it "uses the client encoding for escaped literal", :postgresql_90 do
1087
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1088
+ @conn.set_client_encoding( "euc_jp" )
1089
+ escaped = @conn.escape_literal( original )
1090
+ escaped.encoding.should == Encoding::EUC_JP
1091
+ escaped.should == "'string to'"
1092
+ end
1093
+
1094
+ it "uses the client encoding for escaped identifier", :postgresql_90 do
1095
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1096
+ @conn.set_client_encoding( "euc_jp" )
1097
+ escaped = @conn.escape_identifier( original )
1098
+ escaped.encoding.should == Encoding::EUC_JP
1099
+ escaped.should == "\"string to\""
1100
+ end
1101
+
1102
+ it "uses the client encoding for quote_ident" do
1103
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1104
+ @conn.set_client_encoding( "euc_jp" )
1105
+ escaped = @conn.quote_ident( original )
1106
+ escaped.encoding.should == Encoding::EUC_JP
1107
+ escaped.should == "\"string to\""
1108
+ end
1109
+
1110
+ it "uses the previous string encoding for escaped string" do
1111
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1112
+ @conn.set_client_encoding( "euc_jp" )
1113
+ escaped = described_class.escape( original )
1114
+ escaped.encoding.should == Encoding::ISO8859_1
1115
+ escaped.should == "string to"
1116
+ end
1117
+
1118
+ it "uses the previous string encoding for quote_ident" do
1119
+ original = "string to\0 escape".force_encoding( "iso8859-1" )
1120
+ @conn.set_client_encoding( "euc_jp" )
1121
+ escaped = described_class.quote_ident( original )
1122
+ escaped.encoding.should == Encoding::ISO8859_1
1123
+ escaped.should == "\"string to\""
1124
+ end
1125
+
1126
+ end
1127
+
1128
+
1129
+ describe "Ruby 1.9.x default_internal encoding" do
1130
+
1131
+ it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1132
+ @conn.transaction do |txn_conn|
1133
+ txn_conn.internal_encoding = Encoding::ISO8859_1
1134
+ txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
1135
+ txn_conn.exec( "INSERT INTO defaultinternaltest VALUES ('Grün und Weiß')" )
1136
+ end
1137
+
1138
+ begin
1139
+ prev_encoding = Encoding.default_internal
1140
+ Encoding.default_internal = Encoding::UTF_8
1141
+
1142
+ conn = PG.connect( @conninfo )
1143
+ conn.internal_encoding.should == Encoding::UTF_8
1144
+ res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1145
+ res[0]['foo'].encoding.should == Encoding::UTF_8
1146
+ ensure
1147
+ conn.finish if conn
1148
+ Encoding.default_internal = prev_encoding
1149
+ end
1150
+ end
1151
+
1152
+ it "allows users of the async interface to set the client_encoding to the default_internal" do
1153
+ begin
1154
+ prev_encoding = Encoding.default_internal
1155
+ Encoding.default_internal = Encoding::KOI8_R
1156
+
1157
+ @conn.set_default_encoding
1158
+
1159
+ @conn.internal_encoding.should == Encoding::KOI8_R
1160
+ ensure
1161
+ Encoding.default_internal = prev_encoding
1162
+ end
1163
+ end
1164
+
1165
+ end
1166
+
1167
+
1168
+ it "encodes exception messages with the connection's encoding (#96)", :without_transaction do
1169
+ # Use a new connection so the client_encoding isn't set outside of this example
1170
+ conn = PG.connect( @conninfo )
1171
+ conn.client_encoding = 'iso-8859-15'
1172
+
1173
+ conn.transaction do
1174
+ conn.exec "CREATE TABLE foo (bar TEXT)"
1175
+
1176
+ begin
1177
+ query = "INSERT INTO foo VALUES ('Côte d'Ivoire')".encode( 'iso-8859-15' )
1178
+ conn.exec( query )
1179
+ rescue => err
1180
+ err.message.encoding.should == Encoding::ISO8859_15
1181
+ else
1182
+ fail "No exception raised?!"
1183
+ end
1184
+ end
1185
+
1186
+ conn.finish if conn
1187
+ end
1188
+
1189
+ it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1190
+ [:receiver, :processor].each do |kind|
1191
+ notices = []
1192
+ @conn.internal_encoding = 'utf-8'
1193
+ if kind == :processor
1194
+ @conn.set_notice_processor do |msg|
1195
+ notices << msg
1196
+ end
1197
+ else
1198
+ @conn.set_notice_receiver do |result|
1199
+ notices << result.error_message
1200
+ end
1201
+ end
1202
+
1203
+ 3.times do
1204
+ @conn.exec "do $$ BEGIN RAISE NOTICE '世界線航跡蔵'; END; $$ LANGUAGE plpgsql;"
1205
+ end
1206
+
1207
+ notices.length.should == 3
1208
+ notices.each do |notice|
1209
+ notice.should =~ /^NOTICE:.*世界線航跡蔵/
1210
+ notice.encoding.should == Encoding::UTF_8
1211
+ end
1212
+ @conn.set_notice_receiver
1213
+ @conn.set_notice_processor
1214
+ end
1215
+ end
1216
+
1217
+ it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1218
+ @conn.internal_encoding = 'utf-8'
1219
+ @conn.exec( 'ROLLBACK' )
1220
+ @conn.exec( 'LISTEN "Möhre"' )
1221
+ @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1222
+ event, pid, msg = nil
1223
+ @conn.wait_for_notify( 10 ) do |*args|
1224
+ event, pid, msg = *args
1225
+ end
1226
+ @conn.exec( 'UNLISTEN "Möhre"' )
1227
+
1228
+ event.should == "Möhre"
1229
+ event.encoding.should == Encoding::UTF_8
1230
+ msg.should == '世界線航跡蔵'
1231
+ msg.encoding.should == Encoding::UTF_8
1232
+ end
1233
+
1234
+ it "returns properly encoded text from notifies", :postgresql_90 do
1235
+ @conn.internal_encoding = 'utf-8'
1236
+ @conn.exec( 'ROLLBACK' )
1237
+ @conn.exec( 'LISTEN "Möhre"' )
1238
+ @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1239
+ @conn.exec( 'UNLISTEN "Möhre"' )
1240
+
1241
+ notification = @conn.notifies
1242
+ notification[:relname].should == "Möhre"
1243
+ notification[:relname].encoding.should == Encoding::UTF_8
1244
+ notification[:extra].should == '世界線航跡蔵'
1245
+ notification[:extra].encoding.should == Encoding::UTF_8
1246
+ notification[:be_pid].should > 0
1247
+ end
1248
+ end
1249
+
1250
+ context "OS thread support", :ruby_19 do
1251
+ it "Connection#exec shouldn't block a second thread" do
1252
+ t = Thread.new do
1253
+ @conn.exec( "select pg_sleep(1)" )
1254
+ end
1255
+
1256
+ sleep 0.5
1257
+ t.should be_alive()
1258
+ t.join
1259
+ end
1260
+
1261
+ it "Connection.new shouldn't block a second thread" do
1262
+ serv = nil
1263
+ t = Thread.new do
1264
+ serv = TCPServer.new( '127.0.0.1', 54320 )
1265
+ expect {
1266
+ described_class.new( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
1267
+ }.to raise_error(PG::ConnectionBad, /server closed the connection unexpectedly|reset by peer/)
1268
+ end
1269
+
1270
+ sleep 0.5
1271
+ t.should be_alive()
1272
+ serv.close
1273
+ t.join
1274
+ end
1275
+ end
1276
+ end