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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +689 -5
  5. data/History.rdoc +84 -0
  6. data/Manifest.txt +0 -18
  7. data/README.rdoc +13 -10
  8. data/Rakefile +16 -19
  9. data/Rakefile.cross +21 -24
  10. data/ext/errorcodes.def +33 -0
  11. data/ext/errorcodes.txt +15 -1
  12. data/ext/extconf.rb +21 -33
  13. data/ext/gvl_wrappers.c +4 -0
  14. data/ext/gvl_wrappers.h +27 -39
  15. data/ext/pg.c +18 -50
  16. data/ext/pg.h +13 -80
  17. data/ext/pg_binary_encoder.c +8 -8
  18. data/ext/pg_coder.c +31 -10
  19. data/ext/pg_connection.c +340 -225
  20. data/ext/pg_copy_coder.c +34 -4
  21. data/ext/pg_result.c +24 -22
  22. data/ext/pg_text_encoder.c +62 -42
  23. data/ext/pg_type_map.c +14 -7
  24. data/lib/pg/basic_type_mapping.rb +35 -8
  25. data/lib/pg/connection.rb +53 -12
  26. data/lib/pg/result.rb +10 -5
  27. data/lib/pg/text_decoder.rb +7 -0
  28. data/lib/pg/text_encoder.rb +8 -0
  29. data/lib/pg.rb +18 -10
  30. data/spec/helpers.rb +8 -15
  31. data/spec/pg/basic_type_mapping_spec.rb +54 -0
  32. data/spec/pg/connection_spec.rb +384 -209
  33. data/spec/pg/result_spec.rb +14 -7
  34. data/spec/pg/type_map_by_class_spec.rb +2 -2
  35. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  36. data/spec/pg/type_spec.rb +83 -3
  37. data/spec/pg_spec.rb +1 -1
  38. data.tar.gz.sig +0 -0
  39. metadata +55 -64
  40. metadata.gz.sig +0 -0
  41. data/sample/array_insert.rb +0 -20
  42. data/sample/async_api.rb +0 -106
  43. data/sample/async_copyto.rb +0 -39
  44. data/sample/async_mixed.rb +0 -56
  45. data/sample/check_conn.rb +0 -21
  46. data/sample/copyfrom.rb +0 -81
  47. data/sample/copyto.rb +0 -19
  48. data/sample/cursor.rb +0 -21
  49. data/sample/disk_usage_report.rb +0 -186
  50. data/sample/issue-119.rb +0 -94
  51. data/sample/losample.rb +0 -69
  52. data/sample/minimal-testcase.rb +0 -17
  53. data/sample/notify_wait.rb +0 -72
  54. data/sample/pg_statistics.rb +0 -294
  55. data/sample/replication_monitor.rb +0 -231
  56. data/sample/test_binary_values.rb +0 -33
  57. data/sample/wal_shipper.rb +0 -434
  58. data/sample/warehouse_partitions.rb +0 -320
@@ -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", :postgresql_90 do
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", :postgresql_90 do
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", :postgresql_90 do
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(3)" )
725
+ @conn.send_query( "select pg_sleep(1)" )
688
726
 
689
727
  start = Time.now
690
- @conn.block( 0.1 )
728
+ @conn.block( 0.3 )
691
729
  finish = Time.now
692
730
 
693
- expect( (finish - start) ).to be_within( 0.05 ).of( 0.1 )
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
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
740
- "is not valid" do
741
- expect {
742
- described_class.encrypt_password( nil, nil )
743
- }.to raise_error( TypeError )
744
- expect {
745
- described_class.encrypt_password( "postgres", nil )
746
- }.to raise_error( TypeError )
747
- expect {
748
- described_class.encrypt_password( nil, "postgres" )
749
- }.to raise_error( TypeError )
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
- context "under PostgreSQL 9", :postgresql_90 do
934
+ it "sets the fallback_application_name on new connections" do
935
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
849
936
 
850
- before( :each ) do
851
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
852
- end
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
- it "sets the fallback_application_name on new connections" do
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
- it "sets a shortened fallback_application_name on new connections" do
864
- old_0 = $0
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
- @conn.exec( 'ROLLBACK' )
881
- @conn.exec( 'LISTEN knees' )
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
- event, pid, msg = nil
888
- @conn.wait_for_notify( 10 ) do |*args|
889
- event, pid, msg = *args
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
- expect( event ).to eq( 'knees' )
894
- expect( pid ).to be_a_kind_of( Integer )
895
- expect( msg ).to eq( 'skirt and boots' )
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
- it "accepts nil as the timeout in #wait_for_notify " do
899
- @conn.exec( 'ROLLBACK' )
900
- @conn.exec( 'LISTEN knees' )
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
- conn = described_class.connect( @conninfo )
903
- conn.exec( %Q{NOTIFY knees} )
904
- conn.finish
978
+ it "accepts nil as the timeout in #wait_for_notify " do
979
+ @conn.exec( 'ROLLBACK' )
980
+ @conn.exec( 'LISTEN knees' )
905
981
 
906
- event, pid = nil
907
- @conn.wait_for_notify( nil ) do |*args|
908
- event, pid = *args
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
- expect( event ).to eq( 'knees' )
913
- expect( pid ).to be_a_kind_of( Integer )
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
- it "sends nil as the payload if the notification wasn't given one" do
917
- @conn.exec( 'ROLLBACK' )
918
- @conn.exec( 'LISTEN knees' )
992
+ expect( event ).to eq( 'knees' )
993
+ expect( pid ).to be_a_kind_of( Integer )
994
+ end
919
995
 
920
- conn = described_class.connect( @conninfo )
921
- conn.exec( %Q{NOTIFY knees} )
922
- conn.finish
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
- payload = :notnil
925
- @conn.wait_for_notify( nil ) do |*args|
926
- payload = args[ 2 ]
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
- expect( payload ).to be_nil()
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
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
934
- "two arguments" do
1010
+ expect( payload ).to be_nil()
1011
+ end
935
1012
 
936
- @conn.exec( 'ROLLBACK' )
937
- @conn.exec( 'LISTEN knees' )
1013
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1014
+ "two arguments" do
938
1015
 
939
- conn = described_class.connect( @conninfo )
940
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
941
- conn.finish
1016
+ @conn.exec( 'ROLLBACK' )
1017
+ @conn.exec( 'LISTEN knees' )
942
1018
 
943
- event, pid, msg = nil
944
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
945
- event, pid, msg = arg1, arg2
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
- expect( event ).to eq( 'knees' )
950
- expect( pid ).to be_a_kind_of( Integer )
951
- expect( msg ).to be_nil()
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
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
955
- "doesn't accept arguments" do
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
- @conn.exec( 'ROLLBACK' )
958
- @conn.exec( 'LISTEN knees' )
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
- conn = described_class.connect( @conninfo )
961
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
962
- conn.finish
1037
+ @conn.exec( 'ROLLBACK' )
1038
+ @conn.exec( 'LISTEN knees' )
963
1039
 
964
- notification_received = false
965
- @conn.wait_for_notify( 10 ) do
966
- notification_received = true
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
- expect( notification_received ).to be_truthy()
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
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
974
- "three arguments" do
1050
+ expect( notification_received ).to be_truthy()
1051
+ end
975
1052
 
976
- @conn.exec( 'ROLLBACK' )
977
- @conn.exec( 'LISTEN knees' )
1053
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1054
+ "three arguments" do
978
1055
 
979
- conn = described_class.connect( @conninfo )
980
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
981
- conn.finish
1056
+ @conn.exec( 'ROLLBACK' )
1057
+ @conn.exec( 'LISTEN knees' )
982
1058
 
983
- event, pid, msg = nil
984
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
985
- event, pid, msg = arg1, arg2, arg3
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
- expect( event ).to eq( 'knees' )
990
- expect( pid ).to be_a_kind_of( Integer )
991
- expect( msg ).to eq( 'skirt and boots' )
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 "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
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
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
1033
- describe "set_single_row_mode" do
1109
+ describe "set_single_row_mode" do
1034
1110
 
1035
- it "raises an error when called at the wrong time" do
1036
- expect {
1037
- @conn.set_single_row_mode
1038
- }.to raise_error(PG::Error)
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
- it "should work in single row mode" do
1042
- @conn.send_query( "SELECT generate_series(1,10)" )
1043
- @conn.set_single_row_mode
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
- results = []
1046
- loop do
1047
- @conn.block
1048
- res = @conn.get_result or break
1049
- results << res
1050
- end
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
- it "should receive rows before entire query is finished" do
1063
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1064
- @conn.set_single_row_mode
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
- start_time = Time.now
1067
- first_row_time = nil
1158
+ first_result = nil
1159
+ expect do
1068
1160
  loop do
1069
1161
  res = @conn.get_result or break
1070
1162
  res.check
1071
- first_row_time = Time.now unless first_row_time
1163
+ first_result ||= res
1072
1164
  end
1073
- expect( (Time.now - start_time) ).to be >= 1.0
1074
- expect( (first_row_time - start_time) ).to be < 1.0
1075
- end
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "string to" )
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", :postgresql_90 do
1161
- original = "string to\0 escape".force_encoding( "iso8859-1" )
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( "'string to'" )
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", :postgresql_90 do
1169
- original = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "string to" )
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 = "string to\0 escape".force_encoding( "iso8859-1" )
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( "\"string to\"" )
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::UTF_8
1399
+ Encoding.default_internal = Encoding::ISO8859_2
1232
1400
 
1233
1401
  conn = PG.connect( @conninfo )
1234
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
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::UTF_8 )
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", :postgresql_90 do
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", :postgresql_90 do
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", :postgresql_90 do
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", :postgresql_90 do
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
- expect{
1398
- @conn.exec_params( "SELECT $1", [5] )
1399
- }.to raise_error(PG::IndeterminateDatatype)
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 ["2"]
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"], ["2"]] )
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 (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
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( [["1"], ["2"]] )
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