pg 0.18.0 → 1.1.4

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 (80) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +1221 -4
  5. data/History.rdoc +200 -0
  6. data/Manifest.txt +5 -18
  7. data/README-Windows.rdoc +15 -26
  8. data/README.rdoc +27 -10
  9. data/Rakefile +33 -24
  10. data/Rakefile.cross +57 -39
  11. data/ext/errorcodes.def +37 -0
  12. data/ext/errorcodes.rb +1 -1
  13. data/ext/errorcodes.txt +16 -1
  14. data/ext/extconf.rb +29 -35
  15. data/ext/gvl_wrappers.c +4 -0
  16. data/ext/gvl_wrappers.h +27 -39
  17. data/ext/pg.c +27 -53
  18. data/ext/pg.h +66 -83
  19. data/ext/pg_binary_decoder.c +75 -6
  20. data/ext/pg_binary_encoder.c +14 -12
  21. data/ext/pg_coder.c +83 -13
  22. data/ext/pg_connection.c +627 -351
  23. data/ext/pg_copy_coder.c +44 -9
  24. data/ext/pg_result.c +364 -134
  25. data/ext/pg_text_decoder.c +605 -46
  26. data/ext/pg_text_encoder.c +95 -76
  27. data/ext/pg_tuple.c +541 -0
  28. data/ext/pg_type_map.c +20 -13
  29. data/ext/pg_type_map_by_column.c +7 -7
  30. data/ext/pg_type_map_by_mri_type.c +2 -2
  31. data/ext/pg_type_map_in_ruby.c +4 -7
  32. data/ext/util.c +7 -7
  33. data/ext/util.h +3 -3
  34. data/lib/pg/basic_type_mapping.rb +105 -45
  35. data/lib/pg/binary_decoder.rb +22 -0
  36. data/lib/pg/coder.rb +1 -1
  37. data/lib/pg/connection.rb +109 -39
  38. data/lib/pg/constants.rb +1 -1
  39. data/lib/pg/exceptions.rb +1 -1
  40. data/lib/pg/result.rb +11 -6
  41. data/lib/pg/text_decoder.rb +25 -20
  42. data/lib/pg/text_encoder.rb +43 -1
  43. data/lib/pg/tuple.rb +30 -0
  44. data/lib/pg/type_map_by_column.rb +1 -1
  45. data/lib/pg.rb +21 -11
  46. data/spec/helpers.rb +50 -25
  47. data/spec/pg/basic_type_mapping_spec.rb +287 -30
  48. data/spec/pg/connection_spec.rb +695 -282
  49. data/spec/pg/connection_sync_spec.rb +41 -0
  50. data/spec/pg/result_spec.rb +59 -17
  51. data/spec/pg/tuple_spec.rb +280 -0
  52. data/spec/pg/type_map_by_class_spec.rb +3 -3
  53. data/spec/pg/type_map_by_column_spec.rb +1 -1
  54. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  55. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  56. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  57. data/spec/pg/type_map_spec.rb +1 -1
  58. data/spec/pg/type_spec.rb +319 -35
  59. data/spec/pg_spec.rb +2 -2
  60. data.tar.gz.sig +0 -0
  61. metadata +68 -68
  62. metadata.gz.sig +0 -0
  63. data/sample/array_insert.rb +0 -20
  64. data/sample/async_api.rb +0 -106
  65. data/sample/async_copyto.rb +0 -39
  66. data/sample/async_mixed.rb +0 -56
  67. data/sample/check_conn.rb +0 -21
  68. data/sample/copyfrom.rb +0 -81
  69. data/sample/copyto.rb +0 -19
  70. data/sample/cursor.rb +0 -21
  71. data/sample/disk_usage_report.rb +0 -186
  72. data/sample/issue-119.rb +0 -94
  73. data/sample/losample.rb +0 -69
  74. data/sample/minimal-testcase.rb +0 -17
  75. data/sample/notify_wait.rb +0 -72
  76. data/sample/pg_statistics.rb +0 -294
  77. data/sample/replication_monitor.rb +0 -231
  78. data/sample/test_binary_values.rb +0 -33
  79. data/sample/wal_shipper.rb +0 -434
  80. data/sample/warehouse_partitions.rb +0 -320
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  #encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -45,6 +45,14 @@ describe PG::Connection do
45
45
  expect( optstring ).to match( /(^|\s)user='jrandom'/ )
46
46
  end
47
47
 
48
+ it "can create a connection option string from an option string and a hash" do
49
+ optstring = described_class.parse_connect_args( 'dbname=original', :user => 'jrandom' )
50
+
51
+ expect( optstring ).to be_a( String )
52
+ expect( optstring ).to match( /(^|\s)dbname=original/ )
53
+ expect( optstring ).to match( /(^|\s)user='jrandom'/ )
54
+ end
55
+
48
56
  it "escapes single quotes and backslashes in connection parameters" do
49
57
  expect(
50
58
  described_class.parse_connect_args( "DB 'browser' \\" )
@@ -52,18 +60,85 @@ describe PG::Connection do
52
60
 
53
61
  end
54
62
 
63
+ let(:uri) { 'postgresql://user:pass@pgsql.example.com:222/db01?sslmode=require' }
64
+
65
+ it "can connect using a URI" do
66
+ string = described_class.parse_connect_args( uri )
67
+
68
+ expect( string ).to be_a( String )
69
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
70
+ expect( string ).to match( %r{\?.*sslmode=require} )
71
+
72
+ string = described_class.parse_connect_args( URI.parse(uri) )
73
+
74
+ expect( string ).to be_a( String )
75
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
76
+ expect( string ).to match( %r{\?.*sslmode=require} )
77
+ end
78
+
79
+ it "can create a connection URI from a URI and a hash" do
80
+ string = described_class.parse_connect_args( uri, :connect_timeout => 2 )
81
+
82
+ expect( string ).to be_a( String )
83
+ expect( string ).to match( %r{^postgresql://user:pass@pgsql.example.com:222/db01\?} )
84
+ expect( string ).to match( %r{\?.*sslmode=require} )
85
+ expect( string ).to match( %r{\?.*connect_timeout=2} )
86
+
87
+ string = described_class.parse_connect_args( uri,
88
+ :user => 'a',
89
+ :password => 'b',
90
+ :host => 'localhost',
91
+ :port => 555,
92
+ :dbname => 'x' )
93
+
94
+ expect( string ).to be_a( String )
95
+ expect( string ).to match( %r{^postgresql://\?} )
96
+ expect( string ).to match( %r{\?.*user=a} )
97
+ expect( string ).to match( %r{\?.*password=b} )
98
+ expect( string ).to match( %r{\?.*host=localhost} )
99
+ expect( string ).to match( %r{\?.*port=555} )
100
+ expect( string ).to match( %r{\?.*dbname=x} )
101
+ end
102
+
103
+ it "can create a connection URI with a non-standard domain socket directory" do
104
+ string = described_class.parse_connect_args( 'postgresql://%2Fvar%2Flib%2Fpostgresql/dbname' )
105
+
106
+ expect( string ).to be_a( String )
107
+ expect( string ).to match( %r{^postgresql://%2Fvar%2Flib%2Fpostgresql/dbname} )
108
+
109
+ string = described_class.
110
+ parse_connect_args( 'postgresql:///dbname', :host => '/var/lib/postgresql' )
111
+
112
+ expect( string ).to be_a( String )
113
+ expect( string ).to match( %r{^postgresql:///dbname\?} )
114
+ expect( string ).to match( %r{\?.*host=%2Fvar%2Flib%2Fpostgresql} )
115
+ end
116
+
55
117
  it "connects with defaults if no connection parameters are given" do
56
118
  expect( described_class.parse_connect_args ).to eq( '' )
57
119
  end
58
120
 
59
121
  it "connects successfully with connection string" do
60
- tmpconn = described_class.connect(@conninfo)
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
+
134
+ it "connects successfully with connection string" do
135
+ tmpconn = described_class.connect( @conninfo )
61
136
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
62
137
  tmpconn.finish
63
138
  end
64
139
 
65
140
  it "connects using 7 arguments converted to strings" do
66
- tmpconn = described_class.connect('localhost', @port, nil, nil, :test, nil, nil)
141
+ tmpconn = described_class.connect( 'localhost', @port, nil, nil, :test, nil, nil )
67
142
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
68
143
  tmpconn.finish
69
144
  end
@@ -77,7 +152,7 @@ describe PG::Connection do
77
152
  tmpconn.finish
78
153
  end
79
154
 
80
- it "connects using a hash of optional connection parameters", :postgresql_90 do
155
+ it "connects using a hash of optional connection parameters" do
81
156
  tmpconn = described_class.connect(
82
157
  :host => 'localhost',
83
158
  :port => @port,
@@ -89,28 +164,20 @@ describe PG::Connection do
89
164
 
90
165
  it "raises an exception when connecting with an invalid number of arguments" do
91
166
  expect {
92
- described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'extra' )
93
- }.to raise_error( ArgumentError, /extra positional parameter/i )
167
+ described_class.connect( 1, 2, 3, 4, 5, 6, 7, 'the-extra-arg' )
168
+ }.to raise_error do |error|
169
+ expect( error ).to be_an( ArgumentError )
170
+ expect( error.message ).to match( /extra positional parameter/i )
171
+ expect( error.message ).to match( /8/ )
172
+ expect( error.message ).to match( /the-extra-arg/ )
173
+ end
94
174
  end
95
175
 
96
176
  it "can connect asynchronously", :socket_io do
97
177
  tmpconn = described_class.connect_start( @conninfo )
98
178
  expect( tmpconn ).to be_a( described_class )
99
- socket = tmpconn.socket_io
100
- status = tmpconn.connect_poll
101
-
102
- while status != PG::PGRES_POLLING_OK
103
- if status == PG::PGRES_POLLING_READING
104
- select( [socket], [], [], 5.0 ) or
105
- raise "Asynchronous connection timed out!"
106
-
107
- elsif status == PG::PGRES_POLLING_WRITING
108
- select( [], [socket], [], 5.0 ) or
109
- raise "Asynchronous connection timed out!"
110
- end
111
- status = tmpconn.connect_poll
112
- end
113
179
 
180
+ wait_for_polling_ok(tmpconn)
114
181
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
115
182
  tmpconn.finish
116
183
  end
@@ -121,28 +188,79 @@ describe PG::Connection do
121
188
  described_class.connect_start(@conninfo) do |tmpconn|
122
189
  expect( tmpconn ).to be_a( described_class )
123
190
  conn = tmpconn
124
- socket = tmpconn.socket_io
125
- status = tmpconn.connect_poll
126
-
127
- while status != PG::PGRES_POLLING_OK
128
- if status == PG::PGRES_POLLING_READING
129
- if(not select([socket],[],[],5.0))
130
- raise "Asynchronous connection timed out!"
131
- end
132
- elsif(status == PG::PGRES_POLLING_WRITING)
133
- if(not select([],[socket],[],5.0))
134
- raise "Asynchronous connection timed out!"
135
- end
136
- end
137
- status = tmpconn.connect_poll
138
- end
139
191
 
192
+ wait_for_polling_ok(tmpconn)
140
193
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
141
194
  end
142
195
 
143
196
  expect( conn ).to be_finished()
144
197
  end
145
198
 
199
+ context "with async established connection", :socket_io do
200
+ before :each do
201
+ @conn2 = described_class.connect_start( @conninfo )
202
+ wait_for_polling_ok(@conn2)
203
+ expect( @conn2 ).to still_be_usable
204
+ end
205
+
206
+ after :each do
207
+ expect( @conn2 ).to still_be_usable
208
+ @conn2.close
209
+ end
210
+
211
+ it "conn.send_query and IO.select work" do
212
+ @conn2.send_query("SELECT 1")
213
+ res = wait_for_query_result(@conn2)
214
+ expect( res.values ).to eq([["1"]])
215
+ end
216
+
217
+ it "conn.send_query and conn.block work" do
218
+ @conn2.send_query("SELECT 2")
219
+ @conn2.block
220
+ res = @conn2.get_last_result
221
+ expect( res.values ).to eq([["2"]])
222
+ end
223
+
224
+ it "conn.async_query works" do
225
+ res = @conn2.async_query("SELECT 3")
226
+ expect( res.values ).to eq([["3"]])
227
+ expect( @conn2 ).to still_be_usable
228
+
229
+ res = @conn2.query("SELECT 4")
230
+ end
231
+
232
+ it "can use conn.reset_start to restart the connection" do
233
+ ios = IO.pipe
234
+ conn = described_class.connect_start( @conninfo )
235
+ wait_for_polling_ok(conn)
236
+
237
+ # Close the two pipe file descriptors, so that the file descriptor of
238
+ # newly established connection is probably distinct from the previous one.
239
+ ios.each(&:close)
240
+ conn.reset_start
241
+ wait_for_polling_ok(conn, :reset_poll)
242
+
243
+ # The new connection should work even when the file descriptor has changed.
244
+ conn.send_query("SELECT 1")
245
+ res = wait_for_query_result(conn)
246
+ expect( res.values ).to eq([["1"]])
247
+
248
+ conn.close
249
+ end
250
+
251
+ it "should properly close a socket IO when GC'ed" do
252
+ # This results in
253
+ # Errno::ENOTSOCK: An operation was attempted on something that is not a socket.
254
+ # on Windows when rb_w32_unwrap_io_handle() isn't called in pgconn_gc_free().
255
+ 5.times do
256
+ conn = described_class.connect( @conninfo )
257
+ conn.socket_io.close
258
+ end
259
+ GC.start
260
+ IO.pipe.each(&:close)
261
+ end
262
+ end
263
+
146
264
  it "raises proper error when sending fails" do
147
265
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
148
266
  expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
@@ -152,7 +270,7 @@ describe PG::Connection do
152
270
  described_class.connect(@conninfo).finish
153
271
  sleep 0.5
154
272
  res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
155
- WHERE usename IS NOT NULL])
273
+ WHERE usename IS NOT NULL AND application_name != ''])
156
274
  # there's still the global @conn, but should be no more
157
275
  expect( res[0]['n'] ).to eq( '1' )
158
276
  end
@@ -161,13 +279,14 @@ describe PG::Connection do
161
279
  expect( @conn.db ).to eq( "test" )
162
280
  expect( @conn.user ).to be_a_kind_of( String )
163
281
  expect( @conn.pass ).to eq( "" )
164
- expect( @conn.host ).to eq( "localhost" )
165
- # TODO: Not sure why libpq returns a NULL ptr instead of "127.0.0.1"
166
- expect( @conn.hostaddr ).to eq( nil ) if @conn.server_version >= 9_04_00
167
- expect( @conn.port ).to eq( 54321 )
282
+ expect( @conn.port ).to eq( @port )
168
283
  expect( @conn.tty ).to eq( "" )
169
284
  expect( @conn.options ).to eq( "" )
170
285
  end
286
+ it "can retrieve it's connection parameters for the established connection",
287
+ skip: RUBY_PLATFORM=~/x64-mingw/ ? "host segfaults on Windows-x64" : false do
288
+ expect( @conn.host ).to eq( "localhost" )
289
+ end
171
290
 
172
291
  EXPECTED_TRACE_OUTPUT = %{
173
292
  To backend> Msg Q
@@ -214,18 +333,15 @@ describe PG::Connection do
214
333
 
215
334
  trace_data = trace_file.read
216
335
 
217
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
218
- # For PostgreSQL < 9.0, the output will be different:
219
- # -From backend (#4)> 13
220
- # -From backend> "SELECT 1"
221
- # +From backend (#4)> 11
222
- # +From backend> "SELECT"
223
- if @conn.server_version < 90000
224
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
225
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
226
- end
336
+ # For async_exec the output will be different:
337
+ # From backend> Z
338
+ # From backend (#4)> 5
339
+ # +From backend> Z
340
+ # +From backend (#4)> 5
341
+ # From backend> T
342
+ trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
227
343
 
228
- expect( trace_data ).to eq( expected_trace_output )
344
+ expect( trace_data ).to eq( EXPECTED_TRACE_OUTPUT )
229
345
  end
230
346
 
231
347
  it "allows a query to be cancelled" do
@@ -320,22 +436,11 @@ describe PG::Connection do
320
436
  end
321
437
  end
322
438
 
323
-
324
- it "supports parameters passed to #exec (backward compatibility)" do
325
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
326
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
327
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
328
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
329
-
330
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
331
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
332
- end
333
-
334
439
  it "supports explicitly calling #exec_params" do
335
440
  @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
336
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
337
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
338
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
441
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
442
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
443
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
339
444
 
340
445
  res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
341
446
  expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
@@ -456,7 +561,7 @@ describe PG::Connection do
456
561
  @conn.exec( 'UNLISTEN woo' )
457
562
  end
458
563
 
459
- it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
564
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
460
565
  notices = []
461
566
  @conn.set_notice_processor do |msg|
462
567
  notices << [msg, Time.now]
@@ -518,7 +623,7 @@ describe PG::Connection do
518
623
  expect( @conn ).to still_be_usable
519
624
  end
520
625
 
521
- it "can handle server errors in #copy_data for output", :postgresql_90 do
626
+ it "can handle server errors in #copy_data for output" do
522
627
  @conn.exec "ROLLBACK"
523
628
  @conn.transaction do
524
629
  @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
@@ -575,6 +680,31 @@ describe PG::Connection do
575
680
  expect( @conn ).to still_be_usable
576
681
  end
577
682
 
683
+ it "gracefully handle SQL statements while in #copy_data for input" do
684
+ @conn.exec "ROLLBACK"
685
+ @conn.transaction do
686
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
687
+ expect {
688
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
689
+ @conn.exec "SELECT 1"
690
+ end
691
+ }.to raise_error(PG::Error, /no COPY in progress/)
692
+ end
693
+ expect( @conn ).to still_be_usable
694
+ end
695
+
696
+ it "gracefully handle SQL statements while in #copy_data for output" do
697
+ @conn.exec "ROLLBACK"
698
+ @conn.transaction do
699
+ expect {
700
+ @conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
701
+ @conn.exec "SELECT 3"
702
+ end
703
+ }.to raise_error(PG::Error, /no COPY in progress/)
704
+ end
705
+ expect( @conn ).to still_be_usable
706
+ end
707
+
578
708
  it "should raise an error for non copy statements in #copy_data" do
579
709
  expect {
580
710
  @conn.copy_data( "SELECT 1" ){}
@@ -616,18 +746,13 @@ describe PG::Connection do
616
746
  end
617
747
 
618
748
  it "described_class#block should allow a timeout" do
619
- @conn.send_query( "select pg_sleep(3)" )
749
+ @conn.send_query( "select pg_sleep(1)" )
620
750
 
621
751
  start = Time.now
622
- @conn.block( 0.1 )
752
+ @conn.block( 0.3 )
623
753
  finish = Time.now
624
754
 
625
- expect( (finish - start) ).to be_within( 0.05 ).of( 0.1 )
626
- end
627
-
628
-
629
- it "can encrypt a string given a password and username" do
630
- expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
755
+ expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
631
756
  end
632
757
 
633
758
  it "can return the default connection options" do
@@ -640,7 +765,7 @@ describe PG::Connection do
640
765
  it "can return the default connection options as a Hash" do
641
766
  expect( described_class.conndefaults_hash ).to be_a( Hash )
642
767
  expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
643
- expect( described_class.conndefaults_hash[:port] ).to eq( '54321' )
768
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
644
769
  expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
645
770
  end
646
771
 
@@ -657,6 +782,25 @@ describe PG::Connection do
657
782
  expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
658
783
  end
659
784
 
785
+ describe "connection information related to SSL" do
786
+
787
+ it "can retrieve connection's ssl state", :postgresql_95 do
788
+ expect( @conn.ssl_in_use? ).to be false
789
+ end
790
+
791
+ it "can retrieve connection's ssl attribute_names", :postgresql_95 do
792
+ expect( @conn.ssl_attribute_names ).to be_a(Array)
793
+ end
794
+
795
+ it "can retrieve a single ssl connection attribute", :postgresql_95 do
796
+ expect( @conn.ssl_attribute('dbname') ).to eq( nil )
797
+ end
798
+
799
+ it "can retrieve all connection's ssl attributes", :postgresql_95 do
800
+ expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
801
+ end
802
+ end
803
+
660
804
 
661
805
  it "honors the connect_timeout connection parameter", :postgresql_93 do
662
806
  conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
@@ -667,18 +811,52 @@ describe PG::Connection do
667
811
  end
668
812
  end
669
813
 
814
+ describe "deprecated password encryption method" do
815
+ it "can encrypt password for a given user" do
816
+ expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
817
+ end
670
818
 
671
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
672
- "is not valid" do
673
- expect {
674
- described_class.encrypt_password( nil, nil )
675
- }.to raise_error( TypeError )
676
- expect {
677
- described_class.encrypt_password( "postgres", nil )
678
- }.to raise_error( TypeError )
679
- expect {
680
- described_class.encrypt_password( nil, "postgres" )
681
- }.to raise_error( TypeError )
819
+ it "raises an appropriate error if either of the required arguments is not valid" do
820
+ expect {
821
+ described_class.encrypt_password( nil, nil )
822
+ }.to raise_error( TypeError )
823
+ expect {
824
+ described_class.encrypt_password( "postgres", nil )
825
+ }.to raise_error( TypeError )
826
+ expect {
827
+ described_class.encrypt_password( nil, "postgres" )
828
+ }.to raise_error( TypeError )
829
+ end
830
+ end
831
+
832
+ describe "password encryption method", :postgresql_10 do
833
+ it "can encrypt without algorithm" do
834
+ expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
835
+ expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
836
+ end
837
+
838
+ it "can encrypt with algorithm" do
839
+ expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
840
+ expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
841
+ end
842
+
843
+ it "raises an appropriate error if either of the required arguments is not valid" do
844
+ expect {
845
+ @conn.encrypt_password( nil, nil )
846
+ }.to raise_error( TypeError )
847
+ expect {
848
+ @conn.encrypt_password( "postgres", nil )
849
+ }.to raise_error( TypeError )
850
+ expect {
851
+ @conn.encrypt_password( nil, "postgres" )
852
+ }.to raise_error( TypeError )
853
+ expect {
854
+ @conn.encrypt_password( "postgres", "postgres", :invalid )
855
+ }.to raise_error( TypeError )
856
+ expect {
857
+ @conn.encrypt_password( "postgres", "postgres", "invalid" )
858
+ }.to raise_error( PG::Error, /unrecognized/ )
859
+ end
682
860
  end
683
861
 
684
862
 
@@ -720,7 +898,7 @@ describe PG::Connection do
720
898
  end
721
899
 
722
900
 
723
- it "can connect asynchronously", :socket_io do
901
+ it "handles server close while asynchronous connect", :socket_io do
724
902
  serv = TCPServer.new( '127.0.0.1', 54320 )
725
903
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
726
904
  expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
@@ -732,11 +910,29 @@ describe PG::Connection do
732
910
  expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
733
911
  end
734
912
 
735
- it "discards previous results (if any) before waiting on an #async_exec"
913
+ it "discards previous results at #discard_results" do
914
+ @conn.send_query( "select 1" )
915
+ @conn.discard_results
916
+ @conn.send_query( "select 41 as one" )
917
+ res = @conn.get_last_result
918
+ expect( res.to_a ).to eq( [{ 'one' => '41' }] )
919
+ end
920
+
921
+ it "discards previous results (if any) before waiting on #exec" do
922
+ @conn.send_query( "select 1" )
923
+ res = @conn.exec( "select 42 as one" )
924
+ expect( res.to_a ).to eq( [{ 'one' => '42' }] )
925
+ end
926
+
927
+ it "discards previous errors before waiting on #exec", :without_transaction do
928
+ @conn.send_query( "ERROR" )
929
+ res = @conn.exec( "select 43 as one" )
930
+ expect( res.to_a ).to eq( [{ 'one' => '43' }] )
931
+ end
736
932
 
737
- it "calls the block if one is provided to #async_exec" do
933
+ it "calls the block if one is provided to #exec" do
738
934
  result = nil
739
- @conn.async_exec( "select 47 as one" ) do |pg_res|
935
+ @conn.exec( "select 47 as one" ) do |pg_res|
740
936
  result = pg_res[0]
741
937
  end
742
938
  expect( result ).to eq( { 'one' => '47' } )
@@ -749,6 +945,20 @@ describe PG::Connection do
749
945
  expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
750
946
  end
751
947
 
948
+ it "can use conn.reset to restart the connection" do
949
+ ios = IO.pipe
950
+ conn = PG.connect( @conninfo )
951
+
952
+ # Close the two pipe file descriptors, so that the file descriptor of
953
+ # newly established connection is probably distinct from the previous one.
954
+ ios.each(&:close)
955
+ conn.reset
956
+
957
+ # The new connection should work even when the file descriptor has changed.
958
+ expect( conn.exec("SELECT 1").values ).to eq([["1"]])
959
+ conn.close
960
+ end
961
+
752
962
  it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
753
963
  conn = PG.connect( @conninfo )
754
964
  io = conn.socket_io
@@ -777,155 +987,147 @@ describe PG::Connection do
777
987
  expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
778
988
  end
779
989
 
780
- context "under PostgreSQL 9", :postgresql_90 do
990
+ it "sets the fallback_application_name on new connections" do
991
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
781
992
 
782
- before( :each ) do
783
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
784
- end
993
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
994
+ expect( conn_name ).to include( $0[0..10] )
995
+ expect( conn_name ).to include( $0[-10..-1] )
996
+ expect( conn_name.length ).to be <= 64
997
+ end
785
998
 
786
- it "sets the fallback_application_name on new connections" do
999
+ it "sets a shortened fallback_application_name on new connections" do
1000
+ old_0 = $0
1001
+ begin
1002
+ $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
787
1003
  conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
788
-
789
1004
  conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
790
1005
  expect( conn_name ).to include( $0[0..10] )
791
1006
  expect( conn_name ).to include( $0[-10..-1] )
792
1007
  expect( conn_name.length ).to be <= 64
1008
+ ensure
1009
+ $0 = old_0
793
1010
  end
1011
+ end
794
1012
 
795
- it "sets a shortened fallback_application_name on new connections" do
796
- old_0 = $0
797
- begin
798
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
799
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
800
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
801
- expect( conn_name ).to include( $0[0..10] )
802
- expect( conn_name ).to include( $0[-10..-1] )
803
- expect( conn_name.length ).to be <= 64
804
- ensure
805
- $0 = old_0
806
- end
807
- end
808
-
809
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
810
- "any number of arguments" do
811
-
812
- @conn.exec( 'ROLLBACK' )
813
- @conn.exec( 'LISTEN knees' )
1013
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1014
+ "any number of arguments" do
814
1015
 
815
- conn = described_class.connect( @conninfo )
816
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
817
- conn.finish
1016
+ @conn.exec( 'ROLLBACK' )
1017
+ @conn.exec( 'LISTEN knees' )
818
1018
 
819
- event, pid, msg = nil
820
- @conn.wait_for_notify( 10 ) do |*args|
821
- event, pid, msg = *args
822
- end
823
- @conn.exec( 'UNLISTEN knees' )
1019
+ conn = described_class.connect( @conninfo )
1020
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1021
+ conn.finish
824
1022
 
825
- expect( event ).to eq( 'knees' )
826
- expect( pid ).to be_a_kind_of( Integer )
827
- expect( msg ).to eq( 'skirt and boots' )
1023
+ event, pid, msg = nil
1024
+ @conn.wait_for_notify( 10 ) do |*args|
1025
+ event, pid, msg = *args
828
1026
  end
1027
+ @conn.exec( 'UNLISTEN knees' )
829
1028
 
830
- it "accepts nil as the timeout in #wait_for_notify " do
831
- @conn.exec( 'ROLLBACK' )
832
- @conn.exec( 'LISTEN knees' )
1029
+ expect( event ).to eq( 'knees' )
1030
+ expect( pid ).to be_a_kind_of( Integer )
1031
+ expect( msg ).to eq( 'skirt and boots' )
1032
+ end
833
1033
 
834
- conn = described_class.connect( @conninfo )
835
- conn.exec( %Q{NOTIFY knees} )
836
- conn.finish
1034
+ it "accepts nil as the timeout in #wait_for_notify " do
1035
+ @conn.exec( 'ROLLBACK' )
1036
+ @conn.exec( 'LISTEN knees' )
837
1037
 
838
- event, pid = nil
839
- @conn.wait_for_notify( nil ) do |*args|
840
- event, pid = *args
841
- end
842
- @conn.exec( 'UNLISTEN knees' )
1038
+ conn = described_class.connect( @conninfo )
1039
+ conn.exec( %Q{NOTIFY knees} )
1040
+ conn.finish
843
1041
 
844
- expect( event ).to eq( 'knees' )
845
- expect( pid ).to be_a_kind_of( Integer )
1042
+ event, pid = nil
1043
+ @conn.wait_for_notify( nil ) do |*args|
1044
+ event, pid = *args
846
1045
  end
1046
+ @conn.exec( 'UNLISTEN knees' )
847
1047
 
848
- it "sends nil as the payload if the notification wasn't given one" do
849
- @conn.exec( 'ROLLBACK' )
850
- @conn.exec( 'LISTEN knees' )
1048
+ expect( event ).to eq( 'knees' )
1049
+ expect( pid ).to be_a_kind_of( Integer )
1050
+ end
851
1051
 
852
- conn = described_class.connect( @conninfo )
853
- conn.exec( %Q{NOTIFY knees} )
854
- conn.finish
1052
+ it "sends nil as the payload if the notification wasn't given one" do
1053
+ @conn.exec( 'ROLLBACK' )
1054
+ @conn.exec( 'LISTEN knees' )
855
1055
 
856
- payload = :notnil
857
- @conn.wait_for_notify( nil ) do |*args|
858
- payload = args[ 2 ]
859
- end
860
- @conn.exec( 'UNLISTEN knees' )
1056
+ conn = described_class.connect( @conninfo )
1057
+ conn.exec( %Q{NOTIFY knees} )
1058
+ conn.finish
861
1059
 
862
- expect( payload ).to be_nil()
1060
+ payload = :notnil
1061
+ @conn.wait_for_notify( nil ) do |*args|
1062
+ payload = args[ 2 ]
863
1063
  end
1064
+ @conn.exec( 'UNLISTEN knees' )
864
1065
 
865
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
866
- "two arguments" do
1066
+ expect( payload ).to be_nil()
1067
+ end
867
1068
 
868
- @conn.exec( 'ROLLBACK' )
869
- @conn.exec( 'LISTEN knees' )
1069
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1070
+ "two arguments" do
870
1071
 
871
- conn = described_class.connect( @conninfo )
872
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
873
- conn.finish
1072
+ @conn.exec( 'ROLLBACK' )
1073
+ @conn.exec( 'LISTEN knees' )
874
1074
 
875
- event, pid, msg = nil
876
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
877
- event, pid, msg = arg1, arg2
878
- end
879
- @conn.exec( 'UNLISTEN knees' )
1075
+ conn = described_class.connect( @conninfo )
1076
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1077
+ conn.finish
880
1078
 
881
- expect( event ).to eq( 'knees' )
882
- expect( pid ).to be_a_kind_of( Integer )
883
- expect( msg ).to be_nil()
1079
+ event, pid, msg = nil
1080
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
1081
+ event, pid, msg = arg1, arg2
884
1082
  end
1083
+ @conn.exec( 'UNLISTEN knees' )
885
1084
 
886
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
887
- "doesn't accept arguments" do
1085
+ expect( event ).to eq( 'knees' )
1086
+ expect( pid ).to be_a_kind_of( Integer )
1087
+ expect( msg ).to be_nil()
1088
+ end
888
1089
 
889
- @conn.exec( 'ROLLBACK' )
890
- @conn.exec( 'LISTEN knees' )
1090
+ it "calls the block supplied to wait_for_notify with the notify payload if it " +
1091
+ "doesn't accept arguments" do
891
1092
 
892
- conn = described_class.connect( @conninfo )
893
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
894
- conn.finish
1093
+ @conn.exec( 'ROLLBACK' )
1094
+ @conn.exec( 'LISTEN knees' )
895
1095
 
896
- notification_received = false
897
- @conn.wait_for_notify( 10 ) do
898
- notification_received = true
899
- end
900
- @conn.exec( 'UNLISTEN knees' )
1096
+ conn = described_class.connect( @conninfo )
1097
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1098
+ conn.finish
901
1099
 
902
- expect( notification_received ).to be_truthy()
1100
+ notification_received = false
1101
+ @conn.wait_for_notify( 10 ) do
1102
+ notification_received = true
903
1103
  end
1104
+ @conn.exec( 'UNLISTEN knees' )
904
1105
 
905
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
906
- "three arguments" do
1106
+ expect( notification_received ).to be_truthy()
1107
+ end
907
1108
 
908
- @conn.exec( 'ROLLBACK' )
909
- @conn.exec( 'LISTEN knees' )
1109
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1110
+ "three arguments" do
910
1111
 
911
- conn = described_class.connect( @conninfo )
912
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
913
- conn.finish
1112
+ @conn.exec( 'ROLLBACK' )
1113
+ @conn.exec( 'LISTEN knees' )
914
1114
 
915
- event, pid, msg = nil
916
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
917
- event, pid, msg = arg1, arg2, arg3
918
- end
919
- @conn.exec( 'UNLISTEN knees' )
1115
+ conn = described_class.connect( @conninfo )
1116
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1117
+ conn.finish
920
1118
 
921
- expect( event ).to eq( 'knees' )
922
- expect( pid ).to be_a_kind_of( Integer )
923
- expect( msg ).to eq( 'skirt and boots' )
1119
+ event, pid, msg = nil
1120
+ @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1121
+ event, pid, msg = arg1, arg2, arg3
924
1122
  end
1123
+ @conn.exec( 'UNLISTEN knees' )
925
1124
 
1125
+ expect( event ).to eq( 'knees' )
1126
+ expect( pid ).to be_a_kind_of( Integer )
1127
+ expect( msg ).to eq( 'skirt and boots' )
926
1128
  end
927
1129
 
928
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1130
+ context "server ping", :without_transaction do
929
1131
 
930
1132
  it "pings successfully with connection string" do
931
1133
  ping = described_class.ping(@conninfo)
@@ -953,76 +1155,82 @@ describe PG::Connection do
953
1155
  expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
954
1156
  end
955
1157
 
956
- it "returns correct response when ping connection arguments are wrong" do
1158
+ it "returns error when ping connection arguments are wrong" do
957
1159
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
958
- expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1160
+ expect( ping ).to_not eq( PG::PQPING_OK )
959
1161
  end
960
1162
 
1163
+ it "returns correct response when ping connection arguments are wrong" do
1164
+ ping = described_class.ping(
1165
+ :host => 'localhost',
1166
+ :invalid_option => 9999,
1167
+ :dbname => :test)
1168
+ expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1169
+ end
961
1170
 
962
1171
  end
963
1172
 
964
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
965
- describe "set_single_row_mode" do
1173
+ describe "set_single_row_mode" do
966
1174
 
967
- it "raises an error when called at the wrong time" do
968
- expect {
969
- @conn.set_single_row_mode
970
- }.to raise_error(PG::Error)
1175
+ it "raises an error when called at the wrong time" do
1176
+ expect {
1177
+ @conn.set_single_row_mode
1178
+ }.to raise_error(PG::Error)
1179
+ end
1180
+
1181
+ it "should work in single row mode" do
1182
+ @conn.send_query( "SELECT generate_series(1,10)" )
1183
+ @conn.set_single_row_mode
1184
+
1185
+ results = []
1186
+ loop do
1187
+ @conn.block
1188
+ res = @conn.get_result or break
1189
+ results << res
1190
+ end
1191
+ expect( results.length ).to eq( 11 )
1192
+ results[0..-2].each do |res|
1193
+ expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1194
+ values = res.field_values('generate_series')
1195
+ expect( values.length ).to eq( 1 )
1196
+ expect( values.first.to_i ).to be > 0
971
1197
  end
1198
+ expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1199
+ expect( results.last.ntuples ).to eq( 0 )
1200
+ end
972
1201
 
973
- it "should work in single row mode" do
974
- @conn.send_query( "SELECT generate_series(1,10)" )
975
- @conn.set_single_row_mode
1202
+ it "should receive rows before entire query is finished" do
1203
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1204
+ @conn.set_single_row_mode
976
1205
 
977
- results = []
978
- loop do
979
- @conn.block
980
- res = @conn.get_result or break
981
- results << res
982
- end
983
- expect( results.length ).to eq( 11 )
984
- results[0..-2].each do |res|
985
- expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
986
- values = res.field_values('generate_series')
987
- expect( values.length ).to eq( 1 )
988
- expect( values.first.to_i ).to be > 0
989
- end
990
- expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
991
- expect( results.last.ntuples ).to eq( 0 )
1206
+ start_time = Time.now
1207
+ first_row_time = nil
1208
+ loop do
1209
+ res = @conn.get_result or break
1210
+ res.check
1211
+ first_row_time = Time.now unless first_row_time
992
1212
  end
1213
+ expect( (Time.now - start_time) ).to be >= 0.9
1214
+ expect( (first_row_time - start_time) ).to be < 0.9
1215
+ end
993
1216
 
994
- it "should receive rows before entire query is finished" do
995
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
996
- @conn.set_single_row_mode
1217
+ it "should receive rows before entire query fails" do
1218
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1219
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1220
+ @conn.set_single_row_mode
997
1221
 
998
- start_time = Time.now
999
- first_row_time = nil
1222
+ first_result = nil
1223
+ expect do
1000
1224
  loop do
1001
1225
  res = @conn.get_result or break
1002
1226
  res.check
1003
- first_row_time = Time.now unless first_row_time
1227
+ first_result ||= res
1004
1228
  end
1005
- expect( (Time.now - start_time) ).to be >= 1.0
1006
- expect( (first_row_time - start_time) ).to be < 1.0
1007
- end
1008
-
1009
- it "should receive rows before entire query fails" do
1010
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1011
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1012
- @conn.set_single_row_mode
1013
-
1014
- first_result = nil
1015
- expect do
1016
- loop do
1017
- res = @conn.get_result or break
1018
- res.check
1019
- first_result ||= res
1020
- end
1021
- end.to raise_error(PG::Error)
1022
- expect( first_result.kind_of?(PG::Result) ).to be_truthy
1023
- expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1024
- end
1229
+ end.to raise_error(PG::Error)
1230
+ expect( first_result.kind_of?(PG::Result) ).to be_truthy
1231
+ expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1025
1232
  end
1233
+
1026
1234
  end
1027
1235
 
1028
1236
  context "multinationalization support", :ruby_19 do
@@ -1032,7 +1240,7 @@ describe PG::Connection do
1032
1240
  out_string = nil
1033
1241
  @conn.transaction do |conn|
1034
1242
  conn.internal_encoding = 'iso8859-1'
1035
- res = conn.exec("VALUES ('fantasia')", [], 0)
1243
+ res = conn.exec_params("VALUES ('fantasia')", [], 0)
1036
1244
  out_string = res[0]['column1']
1037
1245
  end
1038
1246
  expect( out_string ).to eq( 'fantasia' )
@@ -1043,7 +1251,7 @@ describe PG::Connection do
1043
1251
  out_string = nil
1044
1252
  @conn.transaction do |conn|
1045
1253
  conn.internal_encoding = 'utf-8'
1046
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1254
+ res = conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1047
1255
  out_string = res[0]['column1']
1048
1256
  end
1049
1257
  expect( out_string ).to eq( '世界線航跡蔵' )
@@ -1055,7 +1263,7 @@ describe PG::Connection do
1055
1263
  @conn.transaction do |conn|
1056
1264
  conn.internal_encoding = 'EUC-JP'
1057
1265
  stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1058
- res = conn.exec(stmt, [], 0)
1266
+ res = conn.exec_params(stmt, [], 0)
1059
1267
  out_string = res[0]['column1']
1060
1268
  end
1061
1269
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
@@ -1068,7 +1276,7 @@ describe PG::Connection do
1068
1276
  @conn.transaction do |conn|
1069
1277
  conn.internal_encoding = 'EUC-JP'
1070
1278
  stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1071
- res = conn.exec(stmt, [], 0)
1279
+ res = conn.exec_params(stmt, [], 0)
1072
1280
  conn.internal_encoding = 'utf-8'
1073
1281
  out_string = res[0]['column1']
1074
1282
  end
@@ -1081,56 +1289,215 @@ describe PG::Connection do
1081
1289
  expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1082
1290
  end
1083
1291
 
1292
+ it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
1293
+ @conn.set_client_encoding "JOHAB"
1294
+ val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
1295
+ expect( val.encoding.name ).to eq( "JOHAB" )
1296
+ expect( val.unpack("H*")[0] ).to eq( "dc65" )
1297
+ end
1298
+
1299
+ it "can retrieve server encoding as text" do
1300
+ enc = @conn.parameter_status "server_encoding"
1301
+ expect( enc ).to eq( "UTF8" )
1302
+ end
1303
+
1304
+ it "can retrieve server encoding as ruby encoding" do
1305
+ expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
1306
+ end
1307
+
1084
1308
  it "uses the client encoding for escaped string" do
1085
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1086
1310
  @conn.set_client_encoding( "euc_jp" )
1087
1311
  escaped = @conn.escape( original )
1088
1312
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1089
- expect( escaped ).to eq( "string to" )
1313
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1090
1314
  end
1091
1315
 
1092
- it "uses the client encoding for escaped literal", :postgresql_90 do
1093
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1316
+ it "uses the client encoding for escaped literal" do
1317
+ original = "Möhre to 'scape".encode( "utf-16be" )
1094
1318
  @conn.set_client_encoding( "euc_jp" )
1095
1319
  escaped = @conn.escape_literal( original )
1096
1320
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1097
- expect( escaped ).to eq( "'string to'" )
1321
+ expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
1098
1322
  end
1099
1323
 
1100
- it "uses the client encoding for escaped identifier", :postgresql_90 do
1101
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1324
+ it "uses the client encoding for escaped identifier" do
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1102
1326
  @conn.set_client_encoding( "euc_jp" )
1103
1327
  escaped = @conn.escape_identifier( original )
1104
1328
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1105
- expect( escaped ).to eq( "\"string to\"" )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1106
1330
  end
1107
1331
 
1108
1332
  it "uses the client encoding for quote_ident" do
1109
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1333
+ original = "Möhre to 'scape".encode( "utf-16le" )
1110
1334
  @conn.set_client_encoding( "euc_jp" )
1111
1335
  escaped = @conn.quote_ident( original )
1112
1336
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1113
- expect( escaped ).to eq( "\"string to\"" )
1337
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1114
1338
  end
1115
1339
 
1116
1340
  it "uses the previous string encoding for escaped string" do
1117
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1118
1342
  @conn.set_client_encoding( "euc_jp" )
1119
1343
  escaped = described_class.escape( original )
1120
1344
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1121
- expect( escaped ).to eq( "string to" )
1345
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1122
1346
  end
1123
1347
 
1124
1348
  it "uses the previous string encoding for quote_ident" do
1125
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1349
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1126
1350
  @conn.set_client_encoding( "euc_jp" )
1127
1351
  escaped = described_class.quote_ident( original )
1128
1352
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1129
- expect( escaped ).to eq( "\"string to\"" )
1353
+ expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
1130
1354
  end
1131
1355
 
1356
+ it "raises appropriate error if set_client_encoding is called with invalid arguments" do
1357
+ expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
1358
+ expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1359
+ expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1360
+ end
1132
1361
  end
1133
1362
 
1363
+ describe "respect and convert character encoding of input strings" do
1364
+ before :each do
1365
+ @conn.internal_encoding = __ENCODING__
1366
+ end
1367
+
1368
+ it "should convert query string and parameters to #exec_params" do
1369
+ r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1370
+ ['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
1371
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1372
+ end
1373
+
1374
+ it "should convert query string to #exec" do
1375
+ r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1376
+ expect( r.values ).to eq( [['grün']] )
1377
+ end
1378
+
1379
+ it "should convert strings and parameters to #prepare and #exec_prepared" do
1380
+ @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1381
+ r = @conn.exec_prepared("weiß1".encode("utf-32le"),
1382
+ ['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
1383
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1384
+ end
1385
+
1386
+ it "should convert strings to #describe_prepared" do
1387
+ @conn.prepare("weiß2", "VALUES(123)")
1388
+ r = @conn.describe_prepared("weiß2".encode("utf-16be"))
1389
+ expect( r.nfields ).to eq( 1 )
1390
+ end
1391
+
1392
+ it "should convert strings to #describe_portal" do
1393
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1394
+ r = @conn.describe_portal("cörsör".encode("utf-16le"))
1395
+ expect( r.nfields ).to eq( 3 )
1396
+ end
1397
+
1398
+ it "should convert query string to #send_query" do
1399
+ @conn.send_query("VALUES('grün')".encode("utf-16be"))
1400
+ expect( @conn.get_last_result.values ).to eq( [['grün']] )
1401
+ end
1402
+
1403
+ it "should convert query string and parameters to #send_query_params" do
1404
+ @conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1405
+ ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1406
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1407
+ end
1408
+
1409
+ it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
1410
+ @conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
1411
+ @conn.get_last_result
1412
+ @conn.send_query_prepared("weiß3".encode("utf-32le"),
1413
+ ['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
1414
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1415
+ end
1416
+
1417
+ it "should convert strings to #send_describe_prepared" do
1418
+ @conn.prepare("weiß4", "VALUES(123)")
1419
+ @conn.send_describe_prepared("weiß4".encode("utf-16be"))
1420
+ expect( @conn.get_last_result.nfields ).to eq( 1 )
1421
+ end
1422
+
1423
+ it "should convert strings to #send_describe_portal" do
1424
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1425
+ @conn.send_describe_portal("cörsör".encode("utf-16le"))
1426
+ expect( @conn.get_last_result.nfields ).to eq( 3 )
1427
+ end
1428
+
1429
+ it "should convert error string to #put_copy_end" do
1430
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1431
+ @conn.exec( "COPY copytable FROM STDIN" )
1432
+ @conn.put_copy_end("grün".encode("utf-16be"))
1433
+ expect( @conn.get_result.error_message ).to match(/grün/)
1434
+ @conn.get_result
1435
+ end
1436
+ end
1437
+
1438
+ it "rejects command strings with zero bytes" do
1439
+ expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1440
+ expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1441
+ expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1442
+ expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1443
+ expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1444
+ expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1445
+ expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1446
+ expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1447
+ expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1448
+ expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1449
+ expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1450
+ expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1451
+ expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1452
+ expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1453
+ end
1454
+
1455
+ it "rejects query params with zero bytes" do
1456
+ expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1457
+ expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1458
+ expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1459
+ expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1460
+ end
1461
+
1462
+ it "rejects string with zero bytes in escape" do
1463
+ expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1464
+ end
1465
+
1466
+ it "rejects string with zero bytes in escape_literal" do
1467
+ expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1468
+ end
1469
+
1470
+ it "rejects string with zero bytes in escape_identifier" do
1471
+ expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1472
+ end
1473
+
1474
+ it "rejects string with zero bytes in quote_ident" do
1475
+ expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1476
+ end
1477
+
1478
+ it "rejects Array with string with zero bytes" do
1479
+ original = ["xyz", "2\x00"]
1480
+ expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
1481
+ end
1482
+
1483
+ it "can quote bigger strings with quote_ident" do
1484
+ original = "'01234567\"" * 100
1485
+ escaped = described_class.quote_ident( original )
1486
+ expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1487
+ end
1488
+
1489
+ it "can quote Arrays with quote_ident" do
1490
+ original = "'01234567\""
1491
+ escaped = described_class.quote_ident( [original]*3 )
1492
+ expected = ["\"" + original.gsub("\"", "\"\"") + "\""] * 3
1493
+ expect( escaped ).to eq( expected.join(".") )
1494
+ end
1495
+
1496
+ it "will raise a TypeError for invalid arguments to quote_ident" do
1497
+ expect{ described_class.quote_ident( nil ) }.to raise_error(TypeError)
1498
+ expect{ described_class.quote_ident( [nil] ) }.to raise_error(TypeError)
1499
+ expect{ described_class.quote_ident( [['a']] ) }.to raise_error(TypeError)
1500
+ end
1134
1501
 
1135
1502
  describe "Ruby 1.9.x default_internal encoding" do
1136
1503
 
@@ -1143,12 +1510,12 @@ describe PG::Connection do
1143
1510
 
1144
1511
  begin
1145
1512
  prev_encoding = Encoding.default_internal
1146
- Encoding.default_internal = Encoding::UTF_8
1513
+ Encoding.default_internal = Encoding::ISO8859_2
1147
1514
 
1148
1515
  conn = PG.connect( @conninfo )
1149
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
1516
+ expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1150
1517
  res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1151
- expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
1518
+ expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1152
1519
  ensure
1153
1520
  conn.exec( "DROP TABLE defaultinternaltest" )
1154
1521
  conn.finish if conn
@@ -1193,7 +1560,7 @@ describe PG::Connection do
1193
1560
  conn.finish if conn
1194
1561
  end
1195
1562
 
1196
- it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1563
+ it "handles clearing result in or after set_notice_receiver" do
1197
1564
  r = nil
1198
1565
  @conn.set_notice_receiver do |result|
1199
1566
  r = result
@@ -1208,7 +1575,7 @@ describe PG::Connection do
1208
1575
  @conn.set_notice_receiver
1209
1576
  end
1210
1577
 
1211
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1578
+ it "receives properly encoded messages in the notice callbacks" do
1212
1579
  [:receiver, :processor].each do |kind|
1213
1580
  notices = []
1214
1581
  @conn.internal_encoding = 'utf-8'
@@ -1236,7 +1603,7 @@ describe PG::Connection do
1236
1603
  end
1237
1604
  end
1238
1605
 
1239
- it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1606
+ it "receives properly encoded text from wait_for_notify" do
1240
1607
  @conn.internal_encoding = 'utf-8'
1241
1608
  @conn.exec( 'ROLLBACK' )
1242
1609
  @conn.exec( 'LISTEN "Möhre"' )
@@ -1253,7 +1620,7 @@ describe PG::Connection do
1253
1620
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1254
1621
  end
1255
1622
 
1256
- it "returns properly encoded text from notifies", :postgresql_90 do
1623
+ it "returns properly encoded text from notifies" do
1257
1624
  @conn.internal_encoding = 'utf-8'
1258
1625
  @conn.exec( 'ROLLBACK' )
1259
1626
  @conn.exec( 'LISTEN "Möhre"' )
@@ -1309,9 +1676,14 @@ describe PG::Connection do
1309
1676
  end
1310
1677
 
1311
1678
  it "shouldn't type map params unless requested" do
1312
- expect{
1313
- @conn.exec_params( "SELECT $1", [5] )
1314
- }.to raise_error(PG::IndeterminateDatatype)
1679
+ if @conn.server_version < 100000
1680
+ expect{
1681
+ @conn.exec_params( "SELECT $1", [5] )
1682
+ }.to raise_error(PG::IndeterminateDatatype)
1683
+ else
1684
+ # PostgreSQL-10 maps to TEXT type (OID 25)
1685
+ expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
1686
+ end
1315
1687
  end
1316
1688
 
1317
1689
  it "should raise an error on invalid encoder to put_copy_data" do
@@ -1378,15 +1750,15 @@ describe PG::Connection do
1378
1750
  end
1379
1751
  end
1380
1752
 
1381
- it "can process #copy_data input queries with row encoder" do
1753
+ it "can process #copy_data input queries with row encoder and respects character encoding" do
1382
1754
  @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1383
1755
  res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1384
1756
  @conn2.put_copy_data [1]
1385
- @conn2.put_copy_data ["2"]
1757
+ @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1386
1758
  end
1387
1759
 
1388
1760
  res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1389
- expect( res.values ).to eq( [["1"], ["2"]] )
1761
+ expect( res.values ).to eq( [["1"], ["Möhre"]] )
1390
1762
  end
1391
1763
  end
1392
1764
 
@@ -1428,14 +1800,16 @@ describe PG::Connection do
1428
1800
  end
1429
1801
  end
1430
1802
 
1431
- it "can process #copy_data output with row decoder" do
1803
+ it "can process #copy_data output with row decoder and respects character encoding" do
1804
+ @conn2.internal_encoding = Encoding::ISO8859_1
1432
1805
  rows = []
1433
- res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
1806
+ res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1434
1807
  while row=@conn2.get_copy_data
1435
1808
  rows << row
1436
1809
  end
1437
1810
  end
1438
- expect( rows ).to eq( [["1"], ["2"]] )
1811
+ expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1812
+ expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1439
1813
  end
1440
1814
 
1441
1815
  it "can type cast #copy_data output with explicit decoder" do
@@ -1456,4 +1830,43 @@ describe PG::Connection do
1456
1830
  end
1457
1831
  end
1458
1832
  end
1833
+
1834
+ describe "deprecated forms of methods" do
1835
+ it "should forward exec to exec_params" do
1836
+ res = @conn.exec("VALUES($1::INT)", [7]).values
1837
+ expect(res).to eq( [["7"]] )
1838
+ res = @conn.exec("VALUES($1::INT)", [7], 1).values
1839
+ expect(res).to eq( [[[7].pack("N")]] )
1840
+ res = @conn.exec("VALUES(8)", [], 1).values
1841
+ expect(res).to eq( [[[8].pack("N")]] )
1842
+ end
1843
+
1844
+ it "should forward exec_params to exec" do
1845
+ res = @conn.exec_params("VALUES(3); VALUES(4)").values
1846
+ expect(res).to eq( [["4"]] )
1847
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
1848
+ expect(res).to eq( [["4"]] )
1849
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
1850
+ expect(res).to eq( [["4"]] )
1851
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
1852
+ expect(res).to eq( [["4"]] )
1853
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
1854
+ expect(res).to eq( [["4"]] )
1855
+ expect{
1856
+ @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
1857
+ }.to raise_error(ArgumentError)
1858
+ end
1859
+
1860
+ it "should forward send_query to send_query_params" do
1861
+ @conn.send_query("VALUES($1)", [5])
1862
+ expect(@conn.get_last_result.values).to eq( [["5"]] )
1863
+ end
1864
+
1865
+ it "shouldn't forward send_query_params to send_query" do
1866
+ expect{ @conn.send_query_params("VALUES(4)").values }
1867
+ .to raise_error(ArgumentError)
1868
+ expect{ @conn.send_query_params("VALUES(4)", nil).values }
1869
+ .to raise_error(TypeError)
1870
+ end
1871
+ end
1459
1872
  end