pg 0.18.4 → 1.0.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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +689 -5
- data/History.rdoc +84 -0
- data/Manifest.txt +0 -18
- data/README.rdoc +13 -10
- data/Rakefile +16 -19
- data/Rakefile.cross +21 -24
- data/ext/errorcodes.def +33 -0
- data/ext/errorcodes.txt +15 -1
- data/ext/extconf.rb +21 -33
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +18 -50
- data/ext/pg.h +13 -80
- data/ext/pg_binary_encoder.c +8 -8
- data/ext/pg_coder.c +31 -10
- data/ext/pg_connection.c +340 -225
- data/ext/pg_copy_coder.c +34 -4
- data/ext/pg_result.c +24 -22
- data/ext/pg_text_encoder.c +62 -42
- data/ext/pg_type_map.c +14 -7
- data/lib/pg/basic_type_mapping.rb +35 -8
- data/lib/pg/connection.rb +53 -12
- data/lib/pg/result.rb +10 -5
- data/lib/pg/text_decoder.rb +7 -0
- data/lib/pg/text_encoder.rb +8 -0
- data/lib/pg.rb +18 -10
- data/spec/helpers.rb +8 -15
- data/spec/pg/basic_type_mapping_spec.rb +54 -0
- data/spec/pg/connection_spec.rb +384 -209
- data/spec/pg/result_spec.rb +14 -7
- data/spec/pg/type_map_by_class_spec.rb +2 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_spec.rb +83 -3
- data/spec/pg_spec.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +55 -64
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
data/spec/pg/connection_spec.rb
CHANGED
@@ -118,6 +118,19 @@ describe PG::Connection do
|
|
118
118
|
expect( described_class.parse_connect_args ).to eq( '' )
|
119
119
|
end
|
120
120
|
|
121
|
+
it "connects successfully with connection string" do
|
122
|
+
conninfo_with_colon_in_password = "host=localhost user=a port=555 dbname=test password=a:a"
|
123
|
+
|
124
|
+
string = described_class.parse_connect_args( conninfo_with_colon_in_password )
|
125
|
+
|
126
|
+
expect( string ).to be_a( String )
|
127
|
+
expect( string ).to match( %r{(^|\s)user=a} )
|
128
|
+
expect( string ).to match( %r{(^|\s)password=a:a} )
|
129
|
+
expect( string ).to match( %r{(^|\s)host=localhost} )
|
130
|
+
expect( string ).to match( %r{(^|\s)port=555} )
|
131
|
+
expect( string ).to match( %r{(^|\s)dbname=test} )
|
132
|
+
end
|
133
|
+
|
121
134
|
it "connects successfully with connection string" do
|
122
135
|
tmpconn = described_class.connect( @conninfo )
|
123
136
|
expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
|
@@ -139,7 +152,7 @@ describe PG::Connection do
|
|
139
152
|
tmpconn.finish
|
140
153
|
end
|
141
154
|
|
142
|
-
it "connects using a hash of optional connection parameters"
|
155
|
+
it "connects using a hash of optional connection parameters" do
|
143
156
|
tmpconn = described_class.connect(
|
144
157
|
:host => 'localhost',
|
145
158
|
:port => @port,
|
@@ -219,7 +232,7 @@ describe PG::Connection do
|
|
219
232
|
described_class.connect(@conninfo).finish
|
220
233
|
sleep 0.5
|
221
234
|
res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
|
222
|
-
WHERE usename IS NOT NULL])
|
235
|
+
WHERE usename IS NOT NULL AND application_name != ''])
|
223
236
|
# there's still the global @conn, but should be no more
|
224
237
|
expect( res[0]['n'] ).to eq( '1' )
|
225
238
|
end
|
@@ -524,7 +537,7 @@ describe PG::Connection do
|
|
524
537
|
@conn.exec( 'UNLISTEN woo' )
|
525
538
|
end
|
526
539
|
|
527
|
-
it "can receive notices while waiting for NOTIFY without exceeding the timeout"
|
540
|
+
it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
|
528
541
|
notices = []
|
529
542
|
@conn.set_notice_processor do |msg|
|
530
543
|
notices << [msg, Time.now]
|
@@ -586,7 +599,7 @@ describe PG::Connection do
|
|
586
599
|
expect( @conn ).to still_be_usable
|
587
600
|
end
|
588
601
|
|
589
|
-
it "can handle server errors in #copy_data for output"
|
602
|
+
it "can handle server errors in #copy_data for output" do
|
590
603
|
@conn.exec "ROLLBACK"
|
591
604
|
@conn.transaction do
|
592
605
|
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
@@ -643,6 +656,31 @@ describe PG::Connection do
|
|
643
656
|
expect( @conn ).to still_be_usable
|
644
657
|
end
|
645
658
|
|
659
|
+
it "gracefully handle SQL statements while in #copy_data for input" do
|
660
|
+
@conn.exec "ROLLBACK"
|
661
|
+
@conn.transaction do
|
662
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
|
663
|
+
expect {
|
664
|
+
@conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
665
|
+
@conn.exec "SELECT 1"
|
666
|
+
end
|
667
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
668
|
+
end
|
669
|
+
expect( @conn ).to still_be_usable
|
670
|
+
end
|
671
|
+
|
672
|
+
it "gracefully handle SQL statements while in #copy_data for output" do
|
673
|
+
@conn.exec "ROLLBACK"
|
674
|
+
@conn.transaction do
|
675
|
+
expect {
|
676
|
+
@conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
|
677
|
+
@conn.exec "SELECT 3"
|
678
|
+
end
|
679
|
+
}.to raise_error(PG::Error, /no COPY in progress/)
|
680
|
+
end
|
681
|
+
expect( @conn ).to still_be_usable
|
682
|
+
end
|
683
|
+
|
646
684
|
it "should raise an error for non copy statements in #copy_data" do
|
647
685
|
expect {
|
648
686
|
@conn.copy_data( "SELECT 1" ){}
|
@@ -684,18 +722,13 @@ describe PG::Connection do
|
|
684
722
|
end
|
685
723
|
|
686
724
|
it "described_class#block should allow a timeout" do
|
687
|
-
@conn.send_query( "select pg_sleep(
|
725
|
+
@conn.send_query( "select pg_sleep(1)" )
|
688
726
|
|
689
727
|
start = Time.now
|
690
|
-
@conn.block( 0.
|
728
|
+
@conn.block( 0.3 )
|
691
729
|
finish = Time.now
|
692
730
|
|
693
|
-
expect( (finish - start) ).to be_within( 0.
|
694
|
-
end
|
695
|
-
|
696
|
-
|
697
|
-
it "can encrypt a string given a password and username" do
|
698
|
-
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
731
|
+
expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
|
699
732
|
end
|
700
733
|
|
701
734
|
it "can return the default connection options" do
|
@@ -725,6 +758,25 @@ describe PG::Connection do
|
|
725
758
|
expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
|
726
759
|
end
|
727
760
|
|
761
|
+
describe "connection information related to SSL" do
|
762
|
+
|
763
|
+
it "can retrieve connection's ssl state", :postgresql_95 do
|
764
|
+
expect( @conn.ssl_in_use? ).to be false
|
765
|
+
end
|
766
|
+
|
767
|
+
it "can retrieve connection's ssl attribute_names", :postgresql_95 do
|
768
|
+
expect( @conn.ssl_attribute_names ).to be_a(Array)
|
769
|
+
end
|
770
|
+
|
771
|
+
it "can retrieve a single ssl connection attribute", :postgresql_95 do
|
772
|
+
expect( @conn.ssl_attribute('dbname') ).to eq( nil )
|
773
|
+
end
|
774
|
+
|
775
|
+
it "can retrieve all connection's ssl attributes", :postgresql_95 do
|
776
|
+
expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
728
780
|
|
729
781
|
it "honors the connect_timeout connection parameter", :postgresql_93 do
|
730
782
|
conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
|
@@ -735,18 +787,52 @@ describe PG::Connection do
|
|
735
787
|
end
|
736
788
|
end
|
737
789
|
|
790
|
+
describe "deprecated password encryption method" do
|
791
|
+
it "can encrypt password for a given user" do
|
792
|
+
expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
793
|
+
end
|
738
794
|
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
795
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
796
|
+
expect {
|
797
|
+
described_class.encrypt_password( nil, nil )
|
798
|
+
}.to raise_error( TypeError )
|
799
|
+
expect {
|
800
|
+
described_class.encrypt_password( "postgres", nil )
|
801
|
+
}.to raise_error( TypeError )
|
802
|
+
expect {
|
803
|
+
described_class.encrypt_password( nil, "postgres" )
|
804
|
+
}.to raise_error( TypeError )
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
describe "password encryption method", :postgresql_10 do
|
809
|
+
it "can encrypt without algorithm" do
|
810
|
+
expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
|
811
|
+
expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
|
812
|
+
end
|
813
|
+
|
814
|
+
it "can encrypt with algorithm" do
|
815
|
+
expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
|
816
|
+
expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
|
817
|
+
end
|
818
|
+
|
819
|
+
it "raises an appropriate error if either of the required arguments is not valid" do
|
820
|
+
expect {
|
821
|
+
@conn.encrypt_password( nil, nil )
|
822
|
+
}.to raise_error( TypeError )
|
823
|
+
expect {
|
824
|
+
@conn.encrypt_password( "postgres", nil )
|
825
|
+
}.to raise_error( TypeError )
|
826
|
+
expect {
|
827
|
+
@conn.encrypt_password( nil, "postgres" )
|
828
|
+
}.to raise_error( TypeError )
|
829
|
+
expect {
|
830
|
+
@conn.encrypt_password( "postgres", "postgres", :invalid )
|
831
|
+
}.to raise_error( TypeError )
|
832
|
+
expect {
|
833
|
+
@conn.encrypt_password( "postgres", "postgres", "invalid" )
|
834
|
+
}.to raise_error( PG::Error, /unrecognized/ )
|
835
|
+
end
|
750
836
|
end
|
751
837
|
|
752
838
|
|
@@ -845,155 +931,147 @@ describe PG::Connection do
|
|
845
931
|
expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
|
846
932
|
end
|
847
933
|
|
848
|
-
|
934
|
+
it "sets the fallback_application_name on new connections" do
|
935
|
+
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
849
936
|
|
850
|
-
|
851
|
-
|
852
|
-
|
937
|
+
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
938
|
+
expect( conn_name ).to include( $0[0..10] )
|
939
|
+
expect( conn_name ).to include( $0[-10..-1] )
|
940
|
+
expect( conn_name.length ).to be <= 64
|
941
|
+
end
|
853
942
|
|
854
|
-
|
943
|
+
it "sets a shortened fallback_application_name on new connections" do
|
944
|
+
old_0 = $0
|
945
|
+
begin
|
946
|
+
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
855
947
|
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
856
|
-
|
857
948
|
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
858
949
|
expect( conn_name ).to include( $0[0..10] )
|
859
950
|
expect( conn_name ).to include( $0[-10..-1] )
|
860
951
|
expect( conn_name.length ).to be <= 64
|
952
|
+
ensure
|
953
|
+
$0 = old_0
|
861
954
|
end
|
955
|
+
end
|
862
956
|
|
863
|
-
|
864
|
-
|
865
|
-
begin
|
866
|
-
$0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
|
867
|
-
conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
|
868
|
-
conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
|
869
|
-
expect( conn_name ).to include( $0[0..10] )
|
870
|
-
expect( conn_name ).to include( $0[-10..-1] )
|
871
|
-
expect( conn_name.length ).to be <= 64
|
872
|
-
ensure
|
873
|
-
$0 = old_0
|
874
|
-
end
|
875
|
-
end
|
876
|
-
|
877
|
-
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
878
|
-
"any number of arguments" do
|
957
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
958
|
+
"any number of arguments" do
|
879
959
|
|
880
|
-
|
881
|
-
|
882
|
-
|
883
|
-
conn = described_class.connect( @conninfo )
|
884
|
-
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
885
|
-
conn.finish
|
960
|
+
@conn.exec( 'ROLLBACK' )
|
961
|
+
@conn.exec( 'LISTEN knees' )
|
886
962
|
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
end
|
891
|
-
@conn.exec( 'UNLISTEN knees' )
|
963
|
+
conn = described_class.connect( @conninfo )
|
964
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
965
|
+
conn.finish
|
892
966
|
|
893
|
-
|
894
|
-
|
895
|
-
|
967
|
+
event, pid, msg = nil
|
968
|
+
@conn.wait_for_notify( 10 ) do |*args|
|
969
|
+
event, pid, msg = *args
|
896
970
|
end
|
971
|
+
@conn.exec( 'UNLISTEN knees' )
|
897
972
|
|
898
|
-
|
899
|
-
|
900
|
-
|
973
|
+
expect( event ).to eq( 'knees' )
|
974
|
+
expect( pid ).to be_a_kind_of( Integer )
|
975
|
+
expect( msg ).to eq( 'skirt and boots' )
|
976
|
+
end
|
901
977
|
|
902
|
-
|
903
|
-
|
904
|
-
|
978
|
+
it "accepts nil as the timeout in #wait_for_notify " do
|
979
|
+
@conn.exec( 'ROLLBACK' )
|
980
|
+
@conn.exec( 'LISTEN knees' )
|
905
981
|
|
906
|
-
|
907
|
-
|
908
|
-
|
909
|
-
end
|
910
|
-
@conn.exec( 'UNLISTEN knees' )
|
982
|
+
conn = described_class.connect( @conninfo )
|
983
|
+
conn.exec( %Q{NOTIFY knees} )
|
984
|
+
conn.finish
|
911
985
|
|
912
|
-
|
913
|
-
|
986
|
+
event, pid = nil
|
987
|
+
@conn.wait_for_notify( nil ) do |*args|
|
988
|
+
event, pid = *args
|
914
989
|
end
|
990
|
+
@conn.exec( 'UNLISTEN knees' )
|
915
991
|
|
916
|
-
|
917
|
-
|
918
|
-
|
992
|
+
expect( event ).to eq( 'knees' )
|
993
|
+
expect( pid ).to be_a_kind_of( Integer )
|
994
|
+
end
|
919
995
|
|
920
|
-
|
921
|
-
|
922
|
-
|
996
|
+
it "sends nil as the payload if the notification wasn't given one" do
|
997
|
+
@conn.exec( 'ROLLBACK' )
|
998
|
+
@conn.exec( 'LISTEN knees' )
|
923
999
|
|
924
|
-
|
925
|
-
|
926
|
-
|
927
|
-
end
|
928
|
-
@conn.exec( 'UNLISTEN knees' )
|
1000
|
+
conn = described_class.connect( @conninfo )
|
1001
|
+
conn.exec( %Q{NOTIFY knees} )
|
1002
|
+
conn.finish
|
929
1003
|
|
930
|
-
|
1004
|
+
payload = :notnil
|
1005
|
+
@conn.wait_for_notify( nil ) do |*args|
|
1006
|
+
payload = args[ 2 ]
|
931
1007
|
end
|
1008
|
+
@conn.exec( 'UNLISTEN knees' )
|
932
1009
|
|
933
|
-
|
934
|
-
|
1010
|
+
expect( payload ).to be_nil()
|
1011
|
+
end
|
935
1012
|
|
936
|
-
|
937
|
-
|
1013
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1014
|
+
"two arguments" do
|
938
1015
|
|
939
|
-
|
940
|
-
|
941
|
-
conn.finish
|
1016
|
+
@conn.exec( 'ROLLBACK' )
|
1017
|
+
@conn.exec( 'LISTEN knees' )
|
942
1018
|
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
end
|
947
|
-
@conn.exec( 'UNLISTEN knees' )
|
1019
|
+
conn = described_class.connect( @conninfo )
|
1020
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1021
|
+
conn.finish
|
948
1022
|
|
949
|
-
|
950
|
-
|
951
|
-
|
1023
|
+
event, pid, msg = nil
|
1024
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2|
|
1025
|
+
event, pid, msg = arg1, arg2
|
952
1026
|
end
|
1027
|
+
@conn.exec( 'UNLISTEN knees' )
|
953
1028
|
|
954
|
-
|
955
|
-
|
1029
|
+
expect( event ).to eq( 'knees' )
|
1030
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1031
|
+
expect( msg ).to be_nil()
|
1032
|
+
end
|
956
1033
|
|
957
|
-
|
958
|
-
|
1034
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it " +
|
1035
|
+
"doesn't accept arguments" do
|
959
1036
|
|
960
|
-
|
961
|
-
|
962
|
-
conn.finish
|
1037
|
+
@conn.exec( 'ROLLBACK' )
|
1038
|
+
@conn.exec( 'LISTEN knees' )
|
963
1039
|
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
end
|
968
|
-
@conn.exec( 'UNLISTEN knees' )
|
1040
|
+
conn = described_class.connect( @conninfo )
|
1041
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1042
|
+
conn.finish
|
969
1043
|
|
970
|
-
|
1044
|
+
notification_received = false
|
1045
|
+
@conn.wait_for_notify( 10 ) do
|
1046
|
+
notification_received = true
|
971
1047
|
end
|
1048
|
+
@conn.exec( 'UNLISTEN knees' )
|
972
1049
|
|
973
|
-
|
974
|
-
|
1050
|
+
expect( notification_received ).to be_truthy()
|
1051
|
+
end
|
975
1052
|
|
976
|
-
|
977
|
-
|
1053
|
+
it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
|
1054
|
+
"three arguments" do
|
978
1055
|
|
979
|
-
|
980
|
-
|
981
|
-
conn.finish
|
1056
|
+
@conn.exec( 'ROLLBACK' )
|
1057
|
+
@conn.exec( 'LISTEN knees' )
|
982
1058
|
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
end
|
987
|
-
@conn.exec( 'UNLISTEN knees' )
|
1059
|
+
conn = described_class.connect( @conninfo )
|
1060
|
+
conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
|
1061
|
+
conn.finish
|
988
1062
|
|
989
|
-
|
990
|
-
|
991
|
-
|
1063
|
+
event, pid, msg = nil
|
1064
|
+
@conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
|
1065
|
+
event, pid, msg = arg1, arg2, arg3
|
992
1066
|
end
|
1067
|
+
@conn.exec( 'UNLISTEN knees' )
|
993
1068
|
|
1069
|
+
expect( event ).to eq( 'knees' )
|
1070
|
+
expect( pid ).to be_a_kind_of( Integer )
|
1071
|
+
expect( msg ).to eq( 'skirt and boots' )
|
994
1072
|
end
|
995
1073
|
|
996
|
-
context "
|
1074
|
+
context "server ping", :without_transaction do
|
997
1075
|
|
998
1076
|
it "pings successfully with connection string" do
|
999
1077
|
ping = described_class.ping(@conninfo)
|
@@ -1026,71 +1104,69 @@ describe PG::Connection do
|
|
1026
1104
|
expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
|
1027
1105
|
end
|
1028
1106
|
|
1029
|
-
|
1030
1107
|
end
|
1031
1108
|
|
1032
|
-
|
1033
|
-
describe "set_single_row_mode" do
|
1109
|
+
describe "set_single_row_mode" do
|
1034
1110
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1111
|
+
it "raises an error when called at the wrong time" do
|
1112
|
+
expect {
|
1113
|
+
@conn.set_single_row_mode
|
1114
|
+
}.to raise_error(PG::Error)
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
it "should work in single row mode" do
|
1118
|
+
@conn.send_query( "SELECT generate_series(1,10)" )
|
1119
|
+
@conn.set_single_row_mode
|
1120
|
+
|
1121
|
+
results = []
|
1122
|
+
loop do
|
1123
|
+
@conn.block
|
1124
|
+
res = @conn.get_result or break
|
1125
|
+
results << res
|
1126
|
+
end
|
1127
|
+
expect( results.length ).to eq( 11 )
|
1128
|
+
results[0..-2].each do |res|
|
1129
|
+
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1130
|
+
values = res.field_values('generate_series')
|
1131
|
+
expect( values.length ).to eq( 1 )
|
1132
|
+
expect( values.first.to_i ).to be > 0
|
1039
1133
|
end
|
1134
|
+
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1135
|
+
expect( results.last.ntuples ).to eq( 0 )
|
1136
|
+
end
|
1040
1137
|
|
1041
|
-
|
1042
|
-
|
1043
|
-
|
1138
|
+
it "should receive rows before entire query is finished" do
|
1139
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
|
1140
|
+
@conn.set_single_row_mode
|
1044
1141
|
|
1045
|
-
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1050
|
-
|
1051
|
-
expect( results.length ).to eq( 11 )
|
1052
|
-
results[0..-2].each do |res|
|
1053
|
-
expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1054
|
-
values = res.field_values('generate_series')
|
1055
|
-
expect( values.length ).to eq( 1 )
|
1056
|
-
expect( values.first.to_i ).to be > 0
|
1057
|
-
end
|
1058
|
-
expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
|
1059
|
-
expect( results.last.ntuples ).to eq( 0 )
|
1142
|
+
start_time = Time.now
|
1143
|
+
first_row_time = nil
|
1144
|
+
loop do
|
1145
|
+
res = @conn.get_result or break
|
1146
|
+
res.check
|
1147
|
+
first_row_time = Time.now unless first_row_time
|
1060
1148
|
end
|
1149
|
+
expect( (Time.now - start_time) ).to be >= 0.9
|
1150
|
+
expect( (first_row_time - start_time) ).to be < 0.9
|
1151
|
+
end
|
1061
1152
|
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1153
|
+
it "should receive rows before entire query fails" do
|
1154
|
+
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
1155
|
+
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
1156
|
+
@conn.set_single_row_mode
|
1065
1157
|
|
1066
|
-
|
1067
|
-
|
1158
|
+
first_result = nil
|
1159
|
+
expect do
|
1068
1160
|
loop do
|
1069
1161
|
res = @conn.get_result or break
|
1070
1162
|
res.check
|
1071
|
-
|
1163
|
+
first_result ||= res
|
1072
1164
|
end
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
it "should receive rows before entire query fails" do
|
1078
|
-
@conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
|
1079
|
-
@conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
|
1080
|
-
@conn.set_single_row_mode
|
1081
|
-
|
1082
|
-
first_result = nil
|
1083
|
-
expect do
|
1084
|
-
loop do
|
1085
|
-
res = @conn.get_result or break
|
1086
|
-
res.check
|
1087
|
-
first_result ||= res
|
1088
|
-
end
|
1089
|
-
end.to raise_error(PG::Error)
|
1090
|
-
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1091
|
-
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1092
|
-
end
|
1165
|
+
end.to raise_error(PG::Error)
|
1166
|
+
expect( first_result.kind_of?(PG::Result) ).to be_truthy
|
1167
|
+
expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
|
1093
1168
|
end
|
1169
|
+
|
1094
1170
|
end
|
1095
1171
|
|
1096
1172
|
context "multinationalization support", :ruby_19 do
|
@@ -1150,51 +1226,143 @@ describe PG::Connection do
|
|
1150
1226
|
end
|
1151
1227
|
|
1152
1228
|
it "uses the client encoding for escaped string" do
|
1153
|
-
original = "
|
1229
|
+
original = "Möhre to\0 escape".encode( "utf-16be" )
|
1154
1230
|
@conn.set_client_encoding( "euc_jp" )
|
1155
1231
|
escaped = @conn.escape( original )
|
1156
1232
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1157
|
-
expect( escaped ).to eq( "
|
1233
|
+
expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
|
1158
1234
|
end
|
1159
1235
|
|
1160
|
-
it "uses the client encoding for escaped literal"
|
1161
|
-
original = "
|
1236
|
+
it "uses the client encoding for escaped literal" do
|
1237
|
+
original = "Möhre to\0 escape".encode( "utf-16be" )
|
1162
1238
|
@conn.set_client_encoding( "euc_jp" )
|
1163
1239
|
escaped = @conn.escape_literal( original )
|
1164
1240
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1165
|
-
expect( escaped ).to eq( "'
|
1241
|
+
expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
|
1166
1242
|
end
|
1167
1243
|
|
1168
|
-
it "uses the client encoding for escaped identifier"
|
1169
|
-
original = "
|
1244
|
+
it "uses the client encoding for escaped identifier" do
|
1245
|
+
original = "Möhre to\0 escape".encode( "utf-16le" )
|
1170
1246
|
@conn.set_client_encoding( "euc_jp" )
|
1171
1247
|
escaped = @conn.escape_identifier( original )
|
1172
1248
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1173
|
-
expect( escaped ).to eq( "\"
|
1249
|
+
expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
|
1174
1250
|
end
|
1175
1251
|
|
1176
1252
|
it "uses the client encoding for quote_ident" do
|
1177
|
-
original = "
|
1253
|
+
original = "Möhre to\0 escape".encode( "utf-16le" )
|
1178
1254
|
@conn.set_client_encoding( "euc_jp" )
|
1179
1255
|
escaped = @conn.quote_ident( original )
|
1180
1256
|
expect( escaped.encoding ).to eq( Encoding::EUC_JP )
|
1181
|
-
expect( escaped ).to eq( "\"
|
1257
|
+
expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
|
1182
1258
|
end
|
1183
1259
|
|
1184
1260
|
it "uses the previous string encoding for escaped string" do
|
1185
|
-
original = "
|
1261
|
+
original = "Möhre to\0 escape".encode( "iso-8859-1" )
|
1186
1262
|
@conn.set_client_encoding( "euc_jp" )
|
1187
1263
|
escaped = described_class.escape( original )
|
1188
1264
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1189
|
-
expect( escaped ).to eq( "
|
1265
|
+
expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
|
1190
1266
|
end
|
1191
1267
|
|
1192
1268
|
it "uses the previous string encoding for quote_ident" do
|
1193
|
-
original = "
|
1269
|
+
original = "Möhre to\0 escape".encode( "iso-8859-1" )
|
1194
1270
|
@conn.set_client_encoding( "euc_jp" )
|
1195
1271
|
escaped = described_class.quote_ident( original )
|
1196
1272
|
expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
|
1197
|
-
expect( escaped ).to eq( "\"
|
1273
|
+
expect( escaped.encode ).to eq( "\"Möhre to\"".encode(Encoding::ISO8859_1) )
|
1274
|
+
end
|
1275
|
+
|
1276
|
+
it "raises appropriate error if set_client_encoding is called with invalid arguments" do
|
1277
|
+
expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
|
1278
|
+
expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
|
1279
|
+
expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
|
1280
|
+
end
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
describe "respect and convert character encoding of input strings" do
|
1284
|
+
before :each do
|
1285
|
+
@conn.internal_encoding = __ENCODING__
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
it "should convert query string and parameters to #exec_params" do
|
1289
|
+
r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1290
|
+
['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
|
1291
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1292
|
+
end
|
1293
|
+
|
1294
|
+
it "should convert query string and parameters to #async_exec" do
|
1295
|
+
r = @conn.async_exec("VALUES( $1, $2, $1=$2, 'grün')".encode("cp936"),
|
1296
|
+
['grün'.encode('cp850'), 'grün'.encode('utf-16le')])
|
1297
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1298
|
+
end
|
1299
|
+
|
1300
|
+
it "should convert query string to #exec" do
|
1301
|
+
r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
|
1302
|
+
expect( r.values ).to eq( [['grün']] )
|
1303
|
+
end
|
1304
|
+
|
1305
|
+
it "should convert query string to #async_exec" do
|
1306
|
+
r = @conn.async_exec("SELECT 'grün'".encode("utf-16le"))
|
1307
|
+
expect( r.values ).to eq( [['grün']] )
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
it "should convert strings and parameters to #prepare and #exec_prepared" do
|
1311
|
+
@conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
|
1312
|
+
r = @conn.exec_prepared("weiß1".encode("utf-32le"),
|
1313
|
+
['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
|
1314
|
+
expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1315
|
+
end
|
1316
|
+
|
1317
|
+
it "should convert strings to #describe_prepared" do
|
1318
|
+
@conn.prepare("weiß2", "VALUES(123)")
|
1319
|
+
r = @conn.describe_prepared("weiß2".encode("utf-16be"))
|
1320
|
+
expect( r.nfields ).to eq( 1 )
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
it "should convert strings to #describe_portal" do
|
1324
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1325
|
+
r = @conn.describe_portal("cörsör".encode("utf-16le"))
|
1326
|
+
expect( r.nfields ).to eq( 3 )
|
1327
|
+
end
|
1328
|
+
|
1329
|
+
it "should convert query string to #send_query" do
|
1330
|
+
@conn.send_query("VALUES('grün')".encode("utf-16be"))
|
1331
|
+
expect( @conn.get_last_result.values ).to eq( [['grün']] )
|
1332
|
+
end
|
1333
|
+
|
1334
|
+
it "should convert query string and parameters to #send_query" do
|
1335
|
+
@conn.send_query("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
|
1336
|
+
['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
|
1337
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
|
1341
|
+
@conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
|
1342
|
+
@conn.get_last_result
|
1343
|
+
@conn.send_query_prepared("weiß3".encode("utf-32le"),
|
1344
|
+
['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
|
1345
|
+
expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
it "should convert strings to #send_describe_prepared" do
|
1349
|
+
@conn.prepare("weiß4", "VALUES(123)")
|
1350
|
+
@conn.send_describe_prepared("weiß4".encode("utf-16be"))
|
1351
|
+
expect( @conn.get_last_result.nfields ).to eq( 1 )
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
it "should convert strings to #send_describe_portal" do
|
1355
|
+
@conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
|
1356
|
+
@conn.send_describe_portal("cörsör".encode("utf-16le"))
|
1357
|
+
expect( @conn.get_last_result.nfields ).to eq( 3 )
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
it "should convert error string to #put_copy_end" do
|
1361
|
+
@conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1362
|
+
@conn.exec( "COPY copytable FROM STDIN" )
|
1363
|
+
@conn.put_copy_end("grün".encode("utf-16be"))
|
1364
|
+
expect( @conn.get_result.error_message ).to match(/grün/)
|
1365
|
+
@conn.get_result
|
1198
1366
|
end
|
1199
1367
|
end
|
1200
1368
|
|
@@ -1228,12 +1396,12 @@ describe PG::Connection do
|
|
1228
1396
|
|
1229
1397
|
begin
|
1230
1398
|
prev_encoding = Encoding.default_internal
|
1231
|
-
Encoding.default_internal = Encoding::
|
1399
|
+
Encoding.default_internal = Encoding::ISO8859_2
|
1232
1400
|
|
1233
1401
|
conn = PG.connect( @conninfo )
|
1234
|
-
expect( conn.internal_encoding ).to eq( Encoding::
|
1402
|
+
expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
|
1235
1403
|
res = conn.exec( "SELECT foo FROM defaultinternaltest" )
|
1236
|
-
expect( res[0]['foo'].encoding ).to eq( Encoding::
|
1404
|
+
expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
|
1237
1405
|
ensure
|
1238
1406
|
conn.exec( "DROP TABLE defaultinternaltest" )
|
1239
1407
|
conn.finish if conn
|
@@ -1278,7 +1446,7 @@ describe PG::Connection do
|
|
1278
1446
|
conn.finish if conn
|
1279
1447
|
end
|
1280
1448
|
|
1281
|
-
it "handles clearing result in or after set_notice_receiver"
|
1449
|
+
it "handles clearing result in or after set_notice_receiver" do
|
1282
1450
|
r = nil
|
1283
1451
|
@conn.set_notice_receiver do |result|
|
1284
1452
|
r = result
|
@@ -1293,7 +1461,7 @@ describe PG::Connection do
|
|
1293
1461
|
@conn.set_notice_receiver
|
1294
1462
|
end
|
1295
1463
|
|
1296
|
-
it "receives properly encoded messages in the notice callbacks"
|
1464
|
+
it "receives properly encoded messages in the notice callbacks" do
|
1297
1465
|
[:receiver, :processor].each do |kind|
|
1298
1466
|
notices = []
|
1299
1467
|
@conn.internal_encoding = 'utf-8'
|
@@ -1321,7 +1489,7 @@ describe PG::Connection do
|
|
1321
1489
|
end
|
1322
1490
|
end
|
1323
1491
|
|
1324
|
-
it "receives properly encoded text from wait_for_notify"
|
1492
|
+
it "receives properly encoded text from wait_for_notify" do
|
1325
1493
|
@conn.internal_encoding = 'utf-8'
|
1326
1494
|
@conn.exec( 'ROLLBACK' )
|
1327
1495
|
@conn.exec( 'LISTEN "Möhre"' )
|
@@ -1338,7 +1506,7 @@ describe PG::Connection do
|
|
1338
1506
|
expect( msg.encoding ).to eq( Encoding::UTF_8 )
|
1339
1507
|
end
|
1340
1508
|
|
1341
|
-
it "returns properly encoded text from notifies"
|
1509
|
+
it "returns properly encoded text from notifies" do
|
1342
1510
|
@conn.internal_encoding = 'utf-8'
|
1343
1511
|
@conn.exec( 'ROLLBACK' )
|
1344
1512
|
@conn.exec( 'LISTEN "Möhre"' )
|
@@ -1394,9 +1562,14 @@ describe PG::Connection do
|
|
1394
1562
|
end
|
1395
1563
|
|
1396
1564
|
it "shouldn't type map params unless requested" do
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1565
|
+
if @conn.server_version < 100000
|
1566
|
+
expect{
|
1567
|
+
@conn.exec_params( "SELECT $1", [5] )
|
1568
|
+
}.to raise_error(PG::IndeterminateDatatype)
|
1569
|
+
else
|
1570
|
+
# PostgreSQL-10 maps to TEXT type (OID 25)
|
1571
|
+
expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
|
1572
|
+
end
|
1400
1573
|
end
|
1401
1574
|
|
1402
1575
|
it "should raise an error on invalid encoder to put_copy_data" do
|
@@ -1463,15 +1636,15 @@ describe PG::Connection do
|
|
1463
1636
|
end
|
1464
1637
|
end
|
1465
1638
|
|
1466
|
-
it "can process #copy_data input queries with row encoder" do
|
1639
|
+
it "can process #copy_data input queries with row encoder and respects character encoding" do
|
1467
1640
|
@conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
|
1468
1641
|
res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
|
1469
1642
|
@conn2.put_copy_data [1]
|
1470
|
-
@conn2.put_copy_data ["
|
1643
|
+
@conn2.put_copy_data ["Möhre".encode("utf-16le")]
|
1471
1644
|
end
|
1472
1645
|
|
1473
1646
|
res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
|
1474
|
-
expect( res.values ).to eq( [["1"], ["
|
1647
|
+
expect( res.values ).to eq( [["1"], ["Möhre"]] )
|
1475
1648
|
end
|
1476
1649
|
end
|
1477
1650
|
|
@@ -1513,14 +1686,16 @@ describe PG::Connection do
|
|
1513
1686
|
end
|
1514
1687
|
end
|
1515
1688
|
|
1516
|
-
it "can process #copy_data output with row decoder" do
|
1689
|
+
it "can process #copy_data output with row decoder and respects character encoding" do
|
1690
|
+
@conn2.internal_encoding = Encoding::ISO8859_1
|
1517
1691
|
rows = []
|
1518
|
-
res2 = @conn2.copy_data( "COPY (
|
1692
|
+
res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
|
1519
1693
|
while row=@conn2.get_copy_data
|
1520
1694
|
rows << row
|
1521
1695
|
end
|
1522
1696
|
end
|
1523
|
-
expect( rows ).to eq(
|
1697
|
+
expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
|
1698
|
+
expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
|
1524
1699
|
end
|
1525
1700
|
|
1526
1701
|
it "can type cast #copy_data output with explicit decoder" do
|