pg_jruby 0.14.1.rc2-java → 0.17.1-java

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +2 -0
  4. data/BSDL +22 -0
  5. data/ChangeLog +0 -0
  6. data/Contributors.rdoc +45 -0
  7. data/History.rdoc +270 -0
  8. data/LICENSE +56 -0
  9. data/Manifest.txt +51 -5
  10. data/POSTGRES +23 -0
  11. data/README-OS_X.rdoc +68 -0
  12. data/README-Windows.rdoc +67 -0
  13. data/README.ja.rdoc +14 -0
  14. data/README.rdoc +52 -94
  15. data/Rakefile +181 -73
  16. data/Rakefile.cross +273 -0
  17. data/ext/errorcodes.def +931 -0
  18. data/ext/errorcodes.rb +99 -0
  19. data/ext/errorcodes.txt +463 -0
  20. data/ext/extconf.rb +97 -0
  21. data/ext/gvl_wrappers.c +13 -0
  22. data/ext/gvl_wrappers.h +253 -0
  23. data/ext/pg.c +545 -0
  24. data/ext/pg.h +156 -0
  25. data/ext/pg_connection.c +3643 -0
  26. data/ext/pg_errors.c +89 -0
  27. data/ext/pg_result.c +920 -0
  28. data/ext/vc/pg.sln +26 -0
  29. data/ext/vc/pg_18/pg.vcproj +216 -0
  30. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  31. data/lib/pg.rb +12 -18
  32. data/lib/pg/connection.rb +117 -10
  33. data/lib/pg/exceptions.rb +2 -8
  34. data/lib/pg/result.rb +6 -1
  35. data/lib/pg_ext.jar +0 -0
  36. data/sample/array_insert.rb +20 -0
  37. data/sample/async_api.rb +106 -0
  38. data/sample/async_copyto.rb +39 -0
  39. data/sample/async_mixed.rb +56 -0
  40. data/sample/check_conn.rb +21 -0
  41. data/sample/copyfrom.rb +81 -0
  42. data/sample/copyto.rb +19 -0
  43. data/sample/cursor.rb +21 -0
  44. data/sample/disk_usage_report.rb +186 -0
  45. data/sample/issue-119.rb +94 -0
  46. data/sample/losample.rb +69 -0
  47. data/sample/minimal-testcase.rb +17 -0
  48. data/sample/notify_wait.rb +72 -0
  49. data/sample/pg_statistics.rb +294 -0
  50. data/sample/replication_monitor.rb +231 -0
  51. data/sample/test_binary_values.rb +33 -0
  52. data/sample/wal_shipper.rb +434 -0
  53. data/sample/warehouse_partitions.rb +320 -0
  54. data/spec/data/expected_trace.out +26 -0
  55. data/spec/data/random_binary_data +0 -0
  56. data/spec/lib/helpers.rb +350 -0
  57. data/spec/pg/connection_spec.rb +1276 -0
  58. data/spec/pg/result_spec.rb +345 -0
  59. data/spec/pg_spec.rb +44 -0
  60. metadata +136 -90
  61. metadata.gz.sig +0 -0
  62. data/CHANGELOG.rdoc +0 -7
  63. data/bin/pg +0 -3
@@ -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