pg 0.12.0 → 0.16.0

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