pg 0.13.2-x86-mingw32 → 0.14.0.pre.353-x86-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.
- data/ChangeLog +230 -1
- data/History.rdoc +28 -0
- data/Manifest.txt +8 -0
- data/Rakefile +8 -2
- data/Rakefile.cross +11 -4
- data/ext/extconf.rb +7 -0
- data/ext/pg.c +76 -2
- data/ext/pg.h +2 -2
- data/ext/pg_connection.c +196 -81
- data/ext/pg_result.c +77 -58
- data/lib/1.8/pg_ext.so +0 -0
- data/lib/1.9/pg_ext.so +0 -0
- data/lib/pg.rb +1 -1
- data/lib/pg/connection.rb +7 -0
- data/sample/async_api.rb +2 -0
- data/sample/check_conn.rb +21 -0
- data/sample/disk_usage_report.rb +186 -0
- data/sample/issue-119.rb +94 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/pg_statistics.rb +294 -0
- data/sample/replication_monitor.rb +231 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +320 -0
- data/spec/lib/helpers.rb +6 -1
- data/spec/pg/connection_spec.rb +205 -141
- data/spec/pg/result_spec.rb +18 -6
- data/spec/pg_spec.rb +9 -0
- metadata +50 -34
data/spec/lib/helpers.rb
CHANGED
@@ -239,6 +239,11 @@ RSpec.configure do |config|
|
|
239
239
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
240
240
|
|
241
241
|
config.mock_with :rspec
|
242
|
-
config.filter_run_excluding :ruby_19
|
242
|
+
config.filter_run_excluding :ruby_19 if ruby_version_vec <= [1,9,1].pack( "N*" )
|
243
|
+
|
244
|
+
config.filter_run_excluding :postgresql_90 unless
|
245
|
+
PG::Connection.instance_methods.map( &:to_sym ).include?( :escape_literal )
|
246
|
+
config.filter_run_excluding :postgresql_91 unless
|
247
|
+
PG.respond_to?( :library_version )
|
243
248
|
end
|
244
249
|
|
data/spec/pg/connection_spec.rb
CHANGED
@@ -86,13 +86,13 @@ describe PG::Connection do
|
|
86
86
|
|
87
87
|
it "connects successfully with connection string" do
|
88
88
|
tmpconn = described_class.connect(@conninfo)
|
89
|
-
tmpconn.status.should== PG::CONNECTION_OK
|
89
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
90
90
|
tmpconn.finish
|
91
91
|
end
|
92
92
|
|
93
93
|
it "connects using 7 arguments converted to strings" do
|
94
94
|
tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
|
95
|
-
tmpconn.status.should== PG::CONNECTION_OK
|
95
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
96
96
|
tmpconn.finish
|
97
97
|
end
|
98
98
|
|
@@ -101,7 +101,17 @@ describe PG::Connection do
|
|
101
101
|
:host => 'localhost',
|
102
102
|
:port => @port,
|
103
103
|
:dbname => :test)
|
104
|
-
tmpconn.status.should== PG::CONNECTION_OK
|
104
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
105
|
+
tmpconn.finish
|
106
|
+
end
|
107
|
+
|
108
|
+
it "connects using a hash of optional connection parameters" do
|
109
|
+
tmpconn = described_class.connect(
|
110
|
+
:host => 'localhost',
|
111
|
+
:port => @port,
|
112
|
+
:dbname => :test,
|
113
|
+
:keepalives => 1)
|
114
|
+
tmpconn.status.should == PG::CONNECTION_OK
|
105
115
|
tmpconn.finish
|
106
116
|
end
|
107
117
|
|
@@ -111,7 +121,6 @@ describe PG::Connection do
|
|
111
121
|
}.to raise_error( ArgumentError, /extra positional parameter/i )
|
112
122
|
end
|
113
123
|
|
114
|
-
|
115
124
|
it "can connect asynchronously" do
|
116
125
|
tmpconn = described_class.connect_start( @conninfo )
|
117
126
|
tmpconn.should be_a( described_class )
|
@@ -379,10 +388,156 @@ describe PG::Connection do
|
|
379
388
|
@conn.exec( 'UNLISTEN woo' )
|
380
389
|
end
|
381
390
|
|
382
|
-
|
391
|
+
it "yields the result if block is given to exec" do
|
392
|
+
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
393
|
+
values = []
|
394
|
+
result.should be_kind_of( PG::Result )
|
395
|
+
result.ntuples.should == 2
|
396
|
+
result.each do |tuple|
|
397
|
+
values << tuple['a']
|
398
|
+
end
|
399
|
+
values
|
400
|
+
end
|
401
|
+
|
402
|
+
rval.should have( 2 ).members
|
403
|
+
rval.should include( '5678', '1234' )
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
it "correctly finishes COPY queries passed to #async_exec" do
|
408
|
+
@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
|
409
|
+
|
410
|
+
results = []
|
411
|
+
begin
|
412
|
+
data = @conn.get_copy_data( true )
|
413
|
+
if false == data
|
414
|
+
@conn.block( 2.0 )
|
415
|
+
data = @conn.get_copy_data( true )
|
416
|
+
end
|
417
|
+
results << data if data
|
418
|
+
end until data.nil?
|
419
|
+
|
420
|
+
results.should have( 2 ).members
|
421
|
+
results.should include( "1\n", "2\n" )
|
422
|
+
end
|
423
|
+
|
424
|
+
|
425
|
+
it "described_class#block shouldn't block a second thread" do
|
426
|
+
t = Thread.new do
|
427
|
+
@conn.send_query( "select pg_sleep(3)" )
|
428
|
+
@conn.block
|
429
|
+
end
|
430
|
+
|
431
|
+
# :FIXME: There's a race here, but hopefully it's pretty small.
|
432
|
+
t.should be_alive()
|
433
|
+
|
434
|
+
@conn.cancel
|
435
|
+
t.join
|
436
|
+
end
|
437
|
+
|
438
|
+
it "described_class#block should allow a timeout" do
|
439
|
+
@conn.send_query( "select pg_sleep(3)" )
|
440
|
+
|
441
|
+
start = Time.now
|
442
|
+
@conn.block( 0.1 )
|
443
|
+
finish = Time.now
|
444
|
+
|
445
|
+
(finish - start).should be_within( 0.05 ).of( 0.1 )
|
446
|
+
end
|
447
|
+
|
448
|
+
|
449
|
+
it "can encrypt a string given a password and username" do
|
450
|
+
described_class.encrypt_password("postgres", "postgres").
|
451
|
+
should =~ /\S+/
|
452
|
+
end
|
453
|
+
|
454
|
+
|
455
|
+
it "raises an appropriate error if either of the required arguments for encrypt_password " +
|
456
|
+
"is not valid" do
|
457
|
+
expect {
|
458
|
+
described_class.encrypt_password( nil, nil )
|
459
|
+
}.to raise_error( TypeError )
|
460
|
+
expect {
|
461
|
+
described_class.encrypt_password( "postgres", nil )
|
462
|
+
}.to raise_error( TypeError )
|
463
|
+
expect {
|
464
|
+
described_class.encrypt_password( nil, "postgres" )
|
465
|
+
}.to raise_error( TypeError )
|
466
|
+
end
|
467
|
+
|
468
|
+
|
469
|
+
it "allows fetching a column of values from a result by column number" do
|
470
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
471
|
+
res.column_values( 0 ).should == %w[1 2 3]
|
472
|
+
res.column_values( 1 ).should == %w[2 3 4]
|
473
|
+
end
|
474
|
+
|
475
|
+
|
476
|
+
it "allows fetching a column of values from a result by field name" do
|
477
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
478
|
+
res.field_values( 'column1' ).should == %w[1 2 3]
|
479
|
+
res.field_values( 'column2' ).should == %w[2 3 4]
|
480
|
+
end
|
481
|
+
|
482
|
+
|
483
|
+
it "raises an error if selecting an invalid column index" do
|
484
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
485
|
+
expect {
|
486
|
+
res.column_values( 20 )
|
487
|
+
}.to raise_error( IndexError )
|
488
|
+
end
|
489
|
+
|
490
|
+
|
491
|
+
it "raises an error if selecting an invalid field name" do
|
492
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
493
|
+
expect {
|
494
|
+
res.field_values( 'hUUuurrg' )
|
495
|
+
}.to raise_error( IndexError )
|
496
|
+
end
|
497
|
+
|
498
|
+
|
499
|
+
it "raises an error if column index is not a number" do
|
500
|
+
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
501
|
+
expect {
|
502
|
+
res.column_values( 'hUUuurrg' )
|
503
|
+
}.to raise_error( TypeError )
|
504
|
+
end
|
505
|
+
|
506
|
+
|
507
|
+
it "can connect asynchronously" do
|
508
|
+
serv = TCPServer.new( '127.0.0.1', 54320 )
|
509
|
+
conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
510
|
+
conn.connect_poll.should == PG::PGRES_POLLING_WRITING
|
511
|
+
select( nil, [IO.for_fd(conn.socket)], nil, 0.2 )
|
512
|
+
serv.close
|
513
|
+
if conn.connect_poll == PG::PGRES_POLLING_READING
|
514
|
+
select( [IO.for_fd(conn.socket)], nil, nil, 0.2 )
|
515
|
+
end
|
516
|
+
conn.connect_poll.should == PG::PGRES_POLLING_FAILED
|
517
|
+
end
|
518
|
+
|
519
|
+
it "discards previous results (if any) before waiting on an #async_exec"
|
520
|
+
|
521
|
+
it "calls the block if one is provided to #async_exec" do
|
522
|
+
result = nil
|
523
|
+
@conn.async_exec( "select 47 as one" ) do |pg_res|
|
524
|
+
result = pg_res[0]
|
525
|
+
end
|
526
|
+
result.should == { 'one' => '47' }
|
527
|
+
end
|
528
|
+
|
529
|
+
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
530
|
+
conn = PG.connect( @conninfo )
|
531
|
+
|
532
|
+
conn.finish
|
533
|
+
expect { conn.finish }.to raise_error( PG::Error, /connection is closed/i )
|
534
|
+
end
|
535
|
+
|
536
|
+
|
537
|
+
context "under PostgreSQL 9", :postgresql_90 do
|
383
538
|
|
384
539
|
before( :each ) do
|
385
|
-
pending "only works
|
540
|
+
pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
|
386
541
|
end
|
387
542
|
|
388
543
|
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
@@ -534,153 +689,43 @@ describe PG::Connection do
|
|
534
689
|
|
535
690
|
end
|
536
691
|
|
537
|
-
|
538
|
-
rval = @conn.exec( "select 1234::int as a union select 5678::int as a" ) do |result|
|
539
|
-
values = []
|
540
|
-
result.should be_kind_of( PG::Result )
|
541
|
-
result.ntuples.should == 2
|
542
|
-
result.each do |tuple|
|
543
|
-
values << tuple['a']
|
544
|
-
end
|
545
|
-
values
|
546
|
-
end
|
547
|
-
|
548
|
-
rval.should have( 2 ).members
|
549
|
-
rval.should include( '5678', '1234' )
|
550
|
-
end
|
551
|
-
|
552
|
-
|
553
|
-
it "correctly finishes COPY queries passed to #async_exec" do
|
554
|
-
@conn.async_exec( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" )
|
555
|
-
|
556
|
-
results = []
|
557
|
-
begin
|
558
|
-
data = @conn.get_copy_data( true )
|
559
|
-
if false == data
|
560
|
-
@conn.block( 2.0 )
|
561
|
-
data = @conn.get_copy_data( true )
|
562
|
-
end
|
563
|
-
results << data if data
|
564
|
-
end until data.nil?
|
565
|
-
|
566
|
-
results.should have( 2 ).members
|
567
|
-
results.should include( "1\n", "2\n" )
|
568
|
-
end
|
569
|
-
|
692
|
+
context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
|
570
693
|
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
@conn.block
|
694
|
+
it "pings successfully with connection string" do
|
695
|
+
ping = described_class.ping(@conninfo)
|
696
|
+
ping.should == PG::PQPING_OK
|
575
697
|
end
|
576
698
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
t.join
|
582
|
-
end
|
583
|
-
|
584
|
-
it "described_class#block should allow a timeout" do
|
585
|
-
@conn.send_query( "select pg_sleep(3)" )
|
586
|
-
|
587
|
-
start = Time.now
|
588
|
-
@conn.block( 0.1 )
|
589
|
-
finish = Time.now
|
590
|
-
|
591
|
-
(finish - start).should be_within( 0.05 ).of( 0.1 )
|
592
|
-
end
|
593
|
-
|
594
|
-
|
595
|
-
it "can encrypt a string given a password and username" do
|
596
|
-
described_class.encrypt_password("postgres", "postgres").
|
597
|
-
should =~ /\S+/
|
598
|
-
end
|
599
|
-
|
600
|
-
|
601
|
-
it "raises an appropriate error if either of the required arguments for encrypt_password " +
|
602
|
-
"is not valid" do
|
603
|
-
expect {
|
604
|
-
described_class.encrypt_password( nil, nil )
|
605
|
-
}.to raise_error( TypeError )
|
606
|
-
expect {
|
607
|
-
described_class.encrypt_password( "postgres", nil )
|
608
|
-
}.to raise_error( TypeError )
|
609
|
-
expect {
|
610
|
-
described_class.encrypt_password( nil, "postgres" )
|
611
|
-
}.to raise_error( TypeError )
|
612
|
-
end
|
613
|
-
|
614
|
-
|
615
|
-
it "allows fetching a column of values from a result by column number" do
|
616
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
617
|
-
res.column_values( 0 ).should == %w[1 2 3]
|
618
|
-
res.column_values( 1 ).should == %w[2 3 4]
|
619
|
-
end
|
620
|
-
|
621
|
-
|
622
|
-
it "allows fetching a column of values from a result by field name" do
|
623
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
624
|
-
res.field_values( 'column1' ).should == %w[1 2 3]
|
625
|
-
res.field_values( 'column2' ).should == %w[2 3 4]
|
626
|
-
end
|
627
|
-
|
628
|
-
|
629
|
-
it "raises an error if selecting an invalid column index" do
|
630
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
631
|
-
expect {
|
632
|
-
res.column_values( 20 )
|
633
|
-
}.to raise_error( IndexError )
|
634
|
-
end
|
635
|
-
|
636
|
-
|
637
|
-
it "raises an error if selecting an invalid field name" do
|
638
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
639
|
-
expect {
|
640
|
-
res.field_values( 'hUUuurrg' )
|
641
|
-
}.to raise_error( IndexError )
|
642
|
-
end
|
643
|
-
|
644
|
-
|
645
|
-
it "raises an error if column index is not a number" do
|
646
|
-
res = @conn.exec( 'VALUES (1,2),(2,3),(3,4)' )
|
647
|
-
expect {
|
648
|
-
res.column_values( 'hUUuurrg' )
|
649
|
-
}.to raise_error( TypeError )
|
650
|
-
end
|
651
|
-
|
699
|
+
it "pings using 7 arguments converted to strings" do
|
700
|
+
ping = described_class.ping('localhost', @port, nil, nil, :test, nil, nil)
|
701
|
+
ping.should == PG::PQPING_OK
|
702
|
+
end
|
652
703
|
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
if conn.connect_poll == PG::PGRES_POLLING_READING
|
660
|
-
select( [IO.for_fd(conn.socket)], nil, nil, 0.2 )
|
704
|
+
it "pings using a hash of connection parameters" do
|
705
|
+
ping = described_class.ping(
|
706
|
+
:host => 'localhost',
|
707
|
+
:port => @port,
|
708
|
+
:dbname => :test)
|
709
|
+
ping.should == PG::PQPING_OK
|
661
710
|
end
|
662
|
-
conn.connect_poll.should == PG::PGRES_POLLING_FAILED
|
663
|
-
end
|
664
711
|
|
665
|
-
|
712
|
+
it "returns correct response when ping connection cannot be established" do
|
713
|
+
ping = described_class.ping(
|
714
|
+
:host => 'localhost',
|
715
|
+
:port => 9999,
|
716
|
+
:dbname => :test)
|
717
|
+
ping.should == PG::PQPING_NO_RESPONSE
|
718
|
+
end
|
666
719
|
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
result = pg_res[0]
|
720
|
+
it "returns correct response when ping connection arguments are wrong" do
|
721
|
+
ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
|
722
|
+
ping.should == PG::PQPING_NO_ATTEMPT
|
671
723
|
end
|
672
|
-
result.should == { 'one' => '47' }
|
673
|
-
end
|
674
724
|
|
675
|
-
it "raises a rescue-able error if #finish is called twice", :without_transaction do
|
676
|
-
conn = PG.connect( @conninfo )
|
677
725
|
|
678
|
-
conn.finish
|
679
|
-
expect { conn.finish }.to raise_error( PG::Error, /connection is closed/i )
|
680
726
|
end
|
681
727
|
|
682
|
-
|
683
|
-
describe "multinationalization support", :ruby_19 => true do
|
728
|
+
context "multinationalization support", :ruby_19 do
|
684
729
|
|
685
730
|
it "should return the same bytes in text format that are sent as inline text" do
|
686
731
|
binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
|
@@ -771,6 +816,12 @@ describe PG::Connection do
|
|
771
816
|
escaped = @conn.escape( original )
|
772
817
|
escaped.encoding.should == Encoding::EUC_JP
|
773
818
|
end
|
819
|
+
|
820
|
+
it "escapes string as literal" do
|
821
|
+
original = "string to\0 escape"
|
822
|
+
escaped = @conn.escape_literal( original )
|
823
|
+
escaped.should == "'string to'"
|
824
|
+
end
|
774
825
|
end
|
775
826
|
|
776
827
|
|
@@ -797,6 +848,19 @@ describe PG::Connection do
|
|
797
848
|
end
|
798
849
|
end
|
799
850
|
|
851
|
+
it "allows users of the async interface to set the client_encoding to the default_internal" do
|
852
|
+
begin
|
853
|
+
prev_encoding = Encoding.default_internal
|
854
|
+
Encoding.default_internal = Encoding::KOI8_U
|
855
|
+
|
856
|
+
@conn.set_default_encoding
|
857
|
+
|
858
|
+
@conn.internal_encoding.should == Encoding::KOI8_U
|
859
|
+
ensure
|
860
|
+
Encoding.default_internal = prev_encoding
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
800
864
|
end
|
801
865
|
|
802
866
|
|
data/spec/pg/result_spec.rb
CHANGED
@@ -92,16 +92,20 @@ describe PG::Result do
|
|
92
92
|
res = @conn.exec('VALUES ($1::bytea)',
|
93
93
|
[ { :value => bytes, :format => 1 } ], 1)
|
94
94
|
res[0]['column1'].should== bytes
|
95
|
+
res.getvalue(0,0).should == bytes
|
96
|
+
res.values[0][0].should == bytes
|
97
|
+
res.column_values(0)[0].should == bytes
|
95
98
|
end
|
96
99
|
|
97
100
|
it "should return the same bytes in binary format that are sent as inline text" do
|
98
101
|
binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
|
99
|
-
|
100
|
-
out_bytes = nil
|
102
|
+
bytes = File.open(binary_file, 'rb').read
|
101
103
|
@conn.exec("SET standard_conforming_strings=on")
|
102
|
-
res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(
|
103
|
-
|
104
|
-
|
104
|
+
res = @conn.exec("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
|
105
|
+
res[0]['column1'].should == bytes
|
106
|
+
res.getvalue(0,0).should == bytes
|
107
|
+
res.values[0][0].should == bytes
|
108
|
+
res.column_values(0)[0].should == bytes
|
105
109
|
end
|
106
110
|
|
107
111
|
it "should return the same bytes in text format that are sent in binary format" do
|
@@ -123,7 +127,7 @@ describe PG::Result do
|
|
123
127
|
out_bytes.should == in_bytes
|
124
128
|
end
|
125
129
|
|
126
|
-
it "should return the parameter type of the specified prepared
|
130
|
+
it "should return the parameter type of the specified prepared statement parameter" do
|
127
131
|
query = 'SELECT * FROM pg_stat_activity WHERE user = $1::name AND current_query = $2::text'
|
128
132
|
@conn.prepare( 'queryfinder', query )
|
129
133
|
res = @conn.describe_prepared( 'queryfinder' )
|
@@ -248,4 +252,12 @@ describe PG::Result do
|
|
248
252
|
res.ftablecol(1).should == 0 # and it shouldn't raise an exception, either
|
249
253
|
end
|
250
254
|
|
255
|
+
it "can be manually checked for failed result status (async API)" do
|
256
|
+
@conn.send_query( "SELECT * FROM nonexistant_table" )
|
257
|
+
res = @conn.get_result
|
258
|
+
expect {
|
259
|
+
res.check
|
260
|
+
}.to raise_error( PG::Error, /relation "nonexistant_table" does not exist/ )
|
261
|
+
end
|
262
|
+
|
251
263
|
end
|