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.
Files changed (57) hide show
  1. data.tar.gz.sig +0 -0
  2. data/.gemtest +0 -0
  3. data/BSDL +22 -0
  4. data/ChangeLog +2945 -0
  5. data/Contributors.rdoc +46 -0
  6. data/History.rdoc +205 -0
  7. data/LICENSE +56 -0
  8. data/Manifest.txt +53 -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 +111 -0
  14. data/Rakefile +156 -0
  15. data/Rakefile.cross +271 -0
  16. data/ext/extconf.rb +91 -0
  17. data/ext/gvl_wrappers.c +13 -0
  18. data/ext/gvl_wrappers.h +185 -0
  19. data/ext/pg.c +525 -0
  20. data/ext/pg.h +126 -0
  21. data/ext/pg_connection.c +3600 -0
  22. data/ext/pg_result.c +939 -0
  23. data/ext/vc/pg.sln +26 -0
  24. data/ext/vc/pg_18/pg.vcproj +216 -0
  25. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  26. data/lib/2.0/pg_ext.so +0 -0
  27. data/lib/pg.rb +52 -0
  28. data/lib/pg/connection.rb +71 -0
  29. data/lib/pg/constants.rb +11 -0
  30. data/lib/pg/exceptions.rb +11 -0
  31. data/lib/pg/result.rb +16 -0
  32. data/sample/array_insert.rb +20 -0
  33. data/sample/async_api.rb +106 -0
  34. data/sample/async_copyto.rb +39 -0
  35. data/sample/async_mixed.rb +56 -0
  36. data/sample/check_conn.rb +21 -0
  37. data/sample/copyfrom.rb +81 -0
  38. data/sample/copyto.rb +19 -0
  39. data/sample/cursor.rb +21 -0
  40. data/sample/disk_usage_report.rb +186 -0
  41. data/sample/issue-119.rb +94 -0
  42. data/sample/losample.rb +69 -0
  43. data/sample/minimal-testcase.rb +17 -0
  44. data/sample/notify_wait.rb +72 -0
  45. data/sample/pg_statistics.rb +294 -0
  46. data/sample/replication_monitor.rb +231 -0
  47. data/sample/test_binary_values.rb +33 -0
  48. data/sample/wal_shipper.rb +434 -0
  49. data/sample/warehouse_partitions.rb +320 -0
  50. data/spec/data/expected_trace.out +26 -0
  51. data/spec/data/random_binary_data +0 -0
  52. data/spec/lib/helpers.rb +279 -0
  53. data/spec/pg/connection_spec.rb +1013 -0
  54. data/spec/pg/result_spec.rb +278 -0
  55. data/spec/pg_spec.rb +31 -0
  56. metadata +275 -0
  57. 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