pg 0.12.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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