pg 0.18.4 → 1.2.3

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 (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -5911
  6. data/History.rdoc +240 -0
  7. data/Manifest.txt +8 -20
  8. data/README-Windows.rdoc +4 -4
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +64 -15
  11. data/Rakefile +20 -21
  12. data/Rakefile.cross +67 -69
  13. data/ext/errorcodes.def +101 -0
  14. data/ext/errorcodes.rb +1 -1
  15. data/ext/errorcodes.txt +33 -2
  16. data/ext/extconf.rb +26 -36
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +27 -39
  19. data/ext/pg.c +156 -145
  20. data/ext/pg.h +74 -98
  21. data/ext/pg_binary_decoder.c +82 -15
  22. data/ext/pg_binary_encoder.c +20 -19
  23. data/ext/pg_coder.c +103 -21
  24. data/ext/pg_connection.c +917 -523
  25. data/ext/pg_copy_coder.c +50 -12
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +590 -208
  28. data/ext/pg_text_decoder.c +606 -40
  29. data/ext/pg_text_encoder.c +245 -94
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +14 -7
  32. data/ext/pg_type_map_all_strings.c +4 -4
  33. data/ext/pg_type_map_by_class.c +9 -4
  34. data/ext/pg_type_map_by_column.c +7 -6
  35. data/ext/pg_type_map_by_mri_type.c +1 -1
  36. data/ext/pg_type_map_by_oid.c +3 -2
  37. data/ext/pg_type_map_in_ruby.c +1 -1
  38. data/ext/{util.c → pg_util.c} +10 -10
  39. data/ext/{util.h → pg_util.h} +2 -2
  40. data/lib/pg.rb +23 -13
  41. data/lib/pg/basic_type_mapping.rb +155 -32
  42. data/lib/pg/binary_decoder.rb +23 -0
  43. data/lib/pg/coder.rb +23 -2
  44. data/lib/pg/connection.rb +73 -13
  45. data/lib/pg/constants.rb +2 -1
  46. data/lib/pg/exceptions.rb +2 -1
  47. data/lib/pg/result.rb +24 -7
  48. data/lib/pg/text_decoder.rb +24 -22
  49. data/lib/pg/text_encoder.rb +40 -8
  50. data/lib/pg/tuple.rb +30 -0
  51. data/lib/pg/type_map_by_column.rb +3 -2
  52. data/spec/helpers.rb +61 -36
  53. data/spec/pg/basic_type_mapping_spec.rb +415 -36
  54. data/spec/pg/connection_spec.rb +732 -327
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +253 -21
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +4 -4
  59. data/spec/pg/type_map_by_column_spec.rb +6 -2
  60. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  61. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  62. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  63. data/spec/pg/type_map_spec.rb +1 -1
  64. data/spec/pg/type_spec.rb +446 -20
  65. data/spec/pg_spec.rb +2 -2
  66. metadata +63 -72
  67. metadata.gz.sig +0 -0
  68. data/sample/array_insert.rb +0 -20
  69. data/sample/async_api.rb +0 -106
  70. data/sample/async_copyto.rb +0 -39
  71. data/sample/async_mixed.rb +0 -56
  72. data/sample/check_conn.rb +0 -21
  73. data/sample/copyfrom.rb +0 -81
  74. data/sample/copyto.rb +0 -19
  75. data/sample/cursor.rb +0 -21
  76. data/sample/disk_usage_report.rb +0 -186
  77. data/sample/issue-119.rb +0 -94
  78. data/sample/losample.rb +0 -69
  79. data/sample/minimal-testcase.rb +0 -17
  80. data/sample/notify_wait.rb +0 -72
  81. data/sample/pg_statistics.rb +0 -294
  82. data/sample/replication_monitor.rb +0 -231
  83. data/sample/test_binary_values.rb +0 -33
  84. data/sample/wal_shipper.rb +0 -434
  85. 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'
@@ -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,
@@ -160,56 +173,94 @@ describe PG::Connection do
160
173
  end
161
174
  end
162
175
 
163
- it "can connect asynchronously", :socket_io do
176
+ it "can connect asynchronously" do
164
177
  tmpconn = described_class.connect_start( @conninfo )
165
178
  expect( tmpconn ).to be_a( described_class )
166
- socket = tmpconn.socket_io
167
- status = tmpconn.connect_poll
168
-
169
- while status != PG::PGRES_POLLING_OK
170
- if status == PG::PGRES_POLLING_READING
171
- select( [socket], [], [], 5.0 ) or
172
- raise "Asynchronous connection timed out!"
173
-
174
- elsif status == PG::PGRES_POLLING_WRITING
175
- select( [], [socket], [], 5.0 ) or
176
- raise "Asynchronous connection timed out!"
177
- end
178
- status = tmpconn.connect_poll
179
- end
180
179
 
180
+ wait_for_polling_ok(tmpconn)
181
181
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
182
182
  tmpconn.finish
183
183
  end
184
184
 
185
- it "can connect asynchronously for the duration of a block", :socket_io do
185
+ it "can connect asynchronously for the duration of a block" do
186
186
  conn = nil
187
187
 
188
188
  described_class.connect_start(@conninfo) do |tmpconn|
189
189
  expect( tmpconn ).to be_a( described_class )
190
190
  conn = tmpconn
191
- socket = tmpconn.socket_io
192
- status = tmpconn.connect_poll
193
-
194
- while status != PG::PGRES_POLLING_OK
195
- if status == PG::PGRES_POLLING_READING
196
- if(not select([socket],[],[],5.0))
197
- raise "Asynchronous connection timed out!"
198
- end
199
- elsif(status == PG::PGRES_POLLING_WRITING)
200
- if(not select([],[socket],[],5.0))
201
- raise "Asynchronous connection timed out!"
202
- end
203
- end
204
- status = tmpconn.connect_poll
205
- end
206
191
 
192
+ wait_for_polling_ok(tmpconn)
207
193
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
208
194
  end
209
195
 
210
196
  expect( conn ).to be_finished()
211
197
  end
212
198
 
199
+ context "with async established connection" 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
+
213
264
  it "raises proper error when sending fails" do
214
265
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
215
266
  expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
@@ -219,7 +270,7 @@ describe PG::Connection do
219
270
  described_class.connect(@conninfo).finish
220
271
  sleep 0.5
221
272
  res = @conn.exec(%[SELECT COUNT(*) AS n FROM pg_stat_activity
222
- WHERE usename IS NOT NULL])
273
+ WHERE usename IS NOT NULL AND application_name != ''])
223
274
  # there's still the global @conn, but should be no more
224
275
  expect( res[0]['n'] ).to eq( '1' )
225
276
  end
@@ -228,7 +279,7 @@ describe PG::Connection do
228
279
  expect( @conn.db ).to eq( "test" )
229
280
  expect( @conn.user ).to be_a_kind_of( String )
230
281
  expect( @conn.pass ).to eq( "" )
231
- expect( @conn.port ).to eq( 54321 )
282
+ expect( @conn.port ).to eq( @port )
232
283
  expect( @conn.tty ).to eq( "" )
233
284
  expect( @conn.options ).to eq( "" )
234
285
  end
@@ -237,7 +288,20 @@ describe PG::Connection do
237
288
  expect( @conn.host ).to eq( "localhost" )
238
289
  end
239
290
 
240
- EXPECTED_TRACE_OUTPUT = %{
291
+ it "can set error verbosity" do
292
+ old = @conn.set_error_verbosity( PG::PQERRORS_TERSE )
293
+ new = @conn.set_error_verbosity( old )
294
+ expect( new ).to eq( PG::PQERRORS_TERSE )
295
+ end
296
+
297
+ it "can set error context visibility", :postgresql_96 do
298
+ old = @conn.set_error_context_visibility( PG::PQSHOW_CONTEXT_NEVER )
299
+ new = @conn.set_error_context_visibility( old )
300
+ expect( new ).to eq( PG::PQSHOW_CONTEXT_NEVER )
301
+ end
302
+
303
+ let(:expected_trace_output) do
304
+ %{
241
305
  To backend> Msg Q
242
306
  To backend> "SELECT 1 AS one"
243
307
  To backend> Msg complete, length 21
@@ -265,6 +329,7 @@ describe PG::Connection do
265
329
  From backend (#4)> 5
266
330
  From backend> T
267
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
268
333
 
269
334
  it "trace and untrace client-server communication", :unix do
270
335
  # be careful to explicitly close files so that the
@@ -275,23 +340,20 @@ describe PG::Connection do
275
340
  @conn.trace( trace_io )
276
341
  trace_io.close
277
342
 
278
- res = @conn.exec("SELECT 1 AS one")
343
+ @conn.exec("SELECT 1 AS one")
279
344
  @conn.untrace
280
345
 
281
- res = @conn.exec("SELECT 2 AS two")
346
+ @conn.exec("SELECT 2 AS two")
282
347
 
283
348
  trace_data = trace_file.read
284
349
 
285
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
286
- # For PostgreSQL < 9.0, the output will be different:
287
- # -From backend (#4)> 13
288
- # -From backend> "SELECT 1"
289
- # +From backend (#4)> 11
290
- # +From backend> "SELECT"
291
- if @conn.server_version < 90000
292
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
293
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
294
- end
350
+ # For async_exec the output will be different:
351
+ # From backend> Z
352
+ # From backend (#4)> 5
353
+ # +From backend> Z
354
+ # +From backend (#4)> 5
355
+ # From backend> T
356
+ trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
295
357
 
296
358
  expect( trace_data ).to eq( expected_trace_output )
297
359
  end
@@ -308,8 +370,6 @@ describe PG::Connection do
308
370
  end
309
371
 
310
372
  it "can stop a thread that runs a blocking query with async_exec" do
311
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
312
-
313
373
  start = Time.now
314
374
  t = Thread.new do
315
375
  @conn.async_exec( 'select pg_sleep(10)' )
@@ -323,24 +383,16 @@ describe PG::Connection do
323
383
 
324
384
  it "should work together with signal handlers", :unix do
325
385
  signal_received = false
326
- trap 'USR1' do
386
+ trap 'USR2' do
327
387
  signal_received = true
328
388
  end
329
389
 
330
390
  Thread.new do
331
391
  sleep 0.1
332
- Process.kill("USR1", Process.pid)
392
+ Process.kill("USR2", Process.pid)
333
393
  end
334
394
  @conn.exec("select pg_sleep(0.3)")
335
395
  expect( signal_received ).to be_truthy
336
-
337
- signal_received = false
338
- Thread.new do
339
- sleep 0.1
340
- Process.kill("USR1", Process.pid)
341
- end
342
- @conn.async_exec("select pg_sleep(0.3)")
343
- expect( signal_received ).to be_truthy
344
396
  end
345
397
 
346
398
 
@@ -388,22 +440,11 @@ describe PG::Connection do
388
440
  end
389
441
  end
390
442
 
391
-
392
- it "supports parameters passed to #exec (backward compatibility)" do
393
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
394
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
395
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
396
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
397
-
398
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
399
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
400
- end
401
-
402
443
  it "supports explicitly calling #exec_params" do
403
444
  @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
404
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
405
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
406
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
445
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
446
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
447
+ @conn.exec_params( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
407
448
 
408
449
  res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
409
450
  expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
@@ -524,7 +565,7 @@ describe PG::Connection do
524
565
  @conn.exec( 'UNLISTEN woo' )
525
566
  end
526
567
 
527
- it "can receive notices while waiting for NOTIFY without exceeding the timeout", :postgresql_90 do
568
+ it "can receive notices while waiting for NOTIFY without exceeding the timeout" do
528
569
  notices = []
529
570
  @conn.set_notice_processor do |msg|
530
571
  notices << [msg, Time.now]
@@ -534,7 +575,7 @@ describe PG::Connection do
534
575
  expect( @conn.wait_for_notify( 1 ) ).to be_nil
535
576
  expect( notices.first ).to_not be_nil
536
577
  et = Time.now
537
- expect( (et - notices.first[1]) ).to be >= 0.4
578
+ expect( (et - notices.first[1]) ).to be >= 0.3
538
579
  expect( (et - st) ).to be >= 0.9
539
580
  expect( (et - st) ).to be < 1.4
540
581
  end
@@ -586,7 +627,7 @@ describe PG::Connection do
586
627
  expect( @conn ).to still_be_usable
587
628
  end
588
629
 
589
- it "can handle server errors in #copy_data for output", :postgresql_90 do
630
+ it "can handle server errors in #copy_data for output" do
590
631
  @conn.exec "ROLLBACK"
591
632
  @conn.transaction do
592
633
  @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
@@ -638,7 +679,32 @@ describe PG::Connection do
638
679
  @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
639
680
  @conn.put_copy_data "xyz\n"
640
681
  end
641
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
682
+ }.to raise_error(PG::Error, /invalid input syntax for .*integer/)
683
+ end
684
+ expect( @conn ).to still_be_usable
685
+ end
686
+
687
+ it "gracefully handle SQL statements while in #copy_data for input" do
688
+ @conn.exec "ROLLBACK"
689
+ @conn.transaction do
690
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 INT)" )
691
+ expect {
692
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
693
+ @conn.exec "SELECT 1"
694
+ end
695
+ }.to raise_error(PG::Error, /no COPY in progress/)
696
+ end
697
+ expect( @conn ).to still_be_usable
698
+ end
699
+
700
+ it "gracefully handle SQL statements while in #copy_data for output" do
701
+ @conn.exec "ROLLBACK"
702
+ @conn.transaction do
703
+ expect {
704
+ @conn.copy_data( "COPY (VALUES(1), (2)) TO STDOUT" ) do |res|
705
+ @conn.exec "SELECT 3"
706
+ end
707
+ }.to raise_error(PG::Error, /no COPY in progress/)
642
708
  end
643
709
  expect( @conn ).to still_be_usable
644
710
  end
@@ -684,18 +750,13 @@ describe PG::Connection do
684
750
  end
685
751
 
686
752
  it "described_class#block should allow a timeout" do
687
- @conn.send_query( "select pg_sleep(3)" )
753
+ @conn.send_query( "select pg_sleep(1)" )
688
754
 
689
755
  start = Time.now
690
- @conn.block( 0.1 )
756
+ @conn.block( 0.3 )
691
757
  finish = Time.now
692
758
 
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+/ )
759
+ expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
699
760
  end
700
761
 
701
762
  it "can return the default connection options" do
@@ -708,7 +769,7 @@ describe PG::Connection do
708
769
  it "can return the default connection options as a Hash" do
709
770
  expect( described_class.conndefaults_hash ).to be_a( Hash )
710
771
  expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
711
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
772
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
712
773
  expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
713
774
  end
714
775
 
@@ -725,6 +786,25 @@ describe PG::Connection do
725
786
  expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
726
787
  end
727
788
 
789
+ describe "connection information related to SSL" do
790
+
791
+ it "can retrieve connection's ssl state", :postgresql_95 do
792
+ expect( @conn.ssl_in_use? ).to be false
793
+ end
794
+
795
+ it "can retrieve connection's ssl attribute_names", :postgresql_95 do
796
+ expect( @conn.ssl_attribute_names ).to be_a(Array)
797
+ end
798
+
799
+ it "can retrieve a single ssl connection attribute", :postgresql_95 do
800
+ expect( @conn.ssl_attribute('dbname') ).to eq( nil )
801
+ end
802
+
803
+ it "can retrieve all connection's ssl attributes", :postgresql_95 do
804
+ expect( @conn.ssl_attributes ).to be_a_kind_of( Hash )
805
+ end
806
+ end
807
+
728
808
 
729
809
  it "honors the connect_timeout connection parameter", :postgresql_93 do
730
810
  conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
@@ -735,18 +815,52 @@ describe PG::Connection do
735
815
  end
736
816
  end
737
817
 
818
+ describe "deprecated password encryption method" do
819
+ it "can encrypt password for a given user" do
820
+ expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
821
+ end
738
822
 
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 )
823
+ it "raises an appropriate error if either of the required arguments is not valid" do
824
+ expect {
825
+ described_class.encrypt_password( nil, nil )
826
+ }.to raise_error( TypeError )
827
+ expect {
828
+ described_class.encrypt_password( "postgres", nil )
829
+ }.to raise_error( TypeError )
830
+ expect {
831
+ described_class.encrypt_password( nil, "postgres" )
832
+ }.to raise_error( TypeError )
833
+ end
834
+ end
835
+
836
+ describe "password encryption method", :postgresql_10 do
837
+ it "can encrypt without algorithm" do
838
+ expect( @conn.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
839
+ expect( @conn.encrypt_password("postgres", "postgres", nil) ).to match( /\S+/ )
840
+ end
841
+
842
+ it "can encrypt with algorithm" do
843
+ expect( @conn.encrypt_password("postgres", "postgres", "md5") ).to match( /md5\S+/i )
844
+ expect( @conn.encrypt_password("postgres", "postgres", "scram-sha-256") ).to match( /SCRAM-SHA-256\S+/i )
845
+ end
846
+
847
+ it "raises an appropriate error if either of the required arguments is not valid" do
848
+ expect {
849
+ @conn.encrypt_password( nil, nil )
850
+ }.to raise_error( TypeError )
851
+ expect {
852
+ @conn.encrypt_password( "postgres", nil )
853
+ }.to raise_error( TypeError )
854
+ expect {
855
+ @conn.encrypt_password( nil, "postgres" )
856
+ }.to raise_error( TypeError )
857
+ expect {
858
+ @conn.encrypt_password( "postgres", "postgres", :invalid )
859
+ }.to raise_error( TypeError )
860
+ expect {
861
+ @conn.encrypt_password( "postgres", "postgres", "invalid" )
862
+ }.to raise_error( PG::Error, /unrecognized/ )
863
+ end
750
864
  end
751
865
 
752
866
 
@@ -788,7 +902,7 @@ describe PG::Connection do
788
902
  end
789
903
 
790
904
 
791
- it "can connect asynchronously", :socket_io do
905
+ it "handles server close while asynchronous connect" do
792
906
  serv = TCPServer.new( '127.0.0.1', 54320 )
793
907
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
794
908
  expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
@@ -800,11 +914,29 @@ describe PG::Connection do
800
914
  expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
801
915
  end
802
916
 
803
- it "discards previous results (if any) before waiting on an #async_exec"
917
+ it "discards previous results at #discard_results" do
918
+ @conn.send_query( "select 1" )
919
+ @conn.discard_results
920
+ @conn.send_query( "select 41 as one" )
921
+ res = @conn.get_last_result
922
+ expect( res.to_a ).to eq( [{ 'one' => '41' }] )
923
+ end
924
+
925
+ it "discards previous results (if any) before waiting on #exec" do
926
+ @conn.send_query( "select 1" )
927
+ res = @conn.exec( "select 42 as one" )
928
+ expect( res.to_a ).to eq( [{ 'one' => '42' }] )
929
+ end
930
+
931
+ it "discards previous errors before waiting on #exec", :without_transaction do
932
+ @conn.send_query( "ERROR" )
933
+ res = @conn.exec( "select 43 as one" )
934
+ expect( res.to_a ).to eq( [{ 'one' => '43' }] )
935
+ end
804
936
 
805
- it "calls the block if one is provided to #async_exec" do
937
+ it "calls the block if one is provided to #exec" do
806
938
  result = nil
807
- @conn.async_exec( "select 47 as one" ) do |pg_res|
939
+ @conn.exec( "select 47 as one" ) do |pg_res|
808
940
  result = pg_res[0]
809
941
  end
810
942
  expect( result ).to eq( { 'one' => '47' } )
@@ -817,7 +949,21 @@ describe PG::Connection do
817
949
  expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
818
950
  end
819
951
 
820
- it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction, :socket_io do
952
+ it "can use conn.reset to restart the connection" do
953
+ ios = IO.pipe
954
+ conn = PG.connect( @conninfo )
955
+
956
+ # Close the two pipe file descriptors, so that the file descriptor of
957
+ # newly established connection is probably distinct from the previous one.
958
+ ios.each(&:close)
959
+ conn.reset
960
+
961
+ # The new connection should work even when the file descriptor has changed.
962
+ expect( conn.exec("SELECT 1").values ).to eq([["1"]])
963
+ conn.close
964
+ end
965
+
966
+ it "closes the IO fetched from #socket_io when the connection is closed", :without_transaction do
821
967
  conn = PG.connect( @conninfo )
822
968
  io = conn.socket_io
823
969
  conn.finish
@@ -825,7 +971,7 @@ describe PG::Connection do
825
971
  expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
826
972
  end
827
973
 
828
- it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction, :socket_io do
974
+ it "closes the IO fetched from #socket_io when the connection is reset", :without_transaction do
829
975
  conn = PG.connect( @conninfo )
830
976
  io = conn.socket_io
831
977
  conn.reset
@@ -845,155 +991,147 @@ describe PG::Connection do
845
991
  expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
846
992
  end
847
993
 
848
- context "under PostgreSQL 9", :postgresql_90 do
994
+ it "sets the fallback_application_name on new connections" do
995
+ conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
849
996
 
850
- before( :each ) do
851
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
852
- end
997
+ conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
998
+ expect( conn_name ).to include( $0[0..10] )
999
+ expect( conn_name ).to include( $0[-10..-1] )
1000
+ expect( conn_name.length ).to be <= 64
1001
+ end
853
1002
 
854
- it "sets the fallback_application_name on new connections" do
1003
+ it "sets a shortened fallback_application_name on new connections" do
1004
+ old_0 = $0
1005
+ begin
1006
+ $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
855
1007
  conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
856
-
857
1008
  conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
858
1009
  expect( conn_name ).to include( $0[0..10] )
859
1010
  expect( conn_name ).to include( $0[-10..-1] )
860
1011
  expect( conn_name.length ).to be <= 64
1012
+ ensure
1013
+ $0 = old_0
861
1014
  end
1015
+ end
862
1016
 
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
879
-
880
- @conn.exec( 'ROLLBACK' )
881
- @conn.exec( 'LISTEN knees' )
1017
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1018
+ "any number of arguments" do
882
1019
 
883
- conn = described_class.connect( @conninfo )
884
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
885
- conn.finish
1020
+ @conn.exec( 'ROLLBACK' )
1021
+ @conn.exec( 'LISTEN knees' )
886
1022
 
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' )
1023
+ conn = described_class.connect( @conninfo )
1024
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
+ conn.finish
892
1026
 
893
- expect( event ).to eq( 'knees' )
894
- expect( pid ).to be_a_kind_of( Integer )
895
- expect( msg ).to eq( 'skirt and boots' )
1027
+ event, pid, msg = nil
1028
+ @conn.wait_for_notify( 10 ) do |*args|
1029
+ event, pid, msg = *args
896
1030
  end
1031
+ @conn.exec( 'UNLISTEN knees' )
897
1032
 
898
- it "accepts nil as the timeout in #wait_for_notify " do
899
- @conn.exec( 'ROLLBACK' )
900
- @conn.exec( 'LISTEN knees' )
1033
+ expect( event ).to eq( 'knees' )
1034
+ expect( pid ).to be_a_kind_of( Integer )
1035
+ expect( msg ).to eq( 'skirt and boots' )
1036
+ end
901
1037
 
902
- conn = described_class.connect( @conninfo )
903
- conn.exec( %Q{NOTIFY knees} )
904
- conn.finish
1038
+ it "accepts nil as the timeout in #wait_for_notify " do
1039
+ @conn.exec( 'ROLLBACK' )
1040
+ @conn.exec( 'LISTEN knees' )
905
1041
 
906
- event, pid = nil
907
- @conn.wait_for_notify( nil ) do |*args|
908
- event, pid = *args
909
- end
910
- @conn.exec( 'UNLISTEN knees' )
1042
+ conn = described_class.connect( @conninfo )
1043
+ conn.exec( %Q{NOTIFY knees} )
1044
+ conn.finish
911
1045
 
912
- expect( event ).to eq( 'knees' )
913
- expect( pid ).to be_a_kind_of( Integer )
1046
+ event, pid = nil
1047
+ @conn.wait_for_notify( nil ) do |*args|
1048
+ event, pid = *args
914
1049
  end
1050
+ @conn.exec( 'UNLISTEN knees' )
915
1051
 
916
- it "sends nil as the payload if the notification wasn't given one" do
917
- @conn.exec( 'ROLLBACK' )
918
- @conn.exec( 'LISTEN knees' )
1052
+ expect( event ).to eq( 'knees' )
1053
+ expect( pid ).to be_a_kind_of( Integer )
1054
+ end
919
1055
 
920
- conn = described_class.connect( @conninfo )
921
- conn.exec( %Q{NOTIFY knees} )
922
- conn.finish
1056
+ it "sends nil as the payload if the notification wasn't given one" do
1057
+ @conn.exec( 'ROLLBACK' )
1058
+ @conn.exec( 'LISTEN knees' )
923
1059
 
924
- payload = :notnil
925
- @conn.wait_for_notify( nil ) do |*args|
926
- payload = args[ 2 ]
927
- end
928
- @conn.exec( 'UNLISTEN knees' )
1060
+ conn = described_class.connect( @conninfo )
1061
+ conn.exec( %Q{NOTIFY knees} )
1062
+ conn.finish
929
1063
 
930
- expect( payload ).to be_nil()
1064
+ payload = :notnil
1065
+ @conn.wait_for_notify( nil ) do |*args|
1066
+ payload = args[ 2 ]
931
1067
  end
1068
+ @conn.exec( 'UNLISTEN knees' )
932
1069
 
933
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
934
- "two arguments" do
1070
+ expect( payload ).to be_nil()
1071
+ end
935
1072
 
936
- @conn.exec( 'ROLLBACK' )
937
- @conn.exec( 'LISTEN knees' )
1073
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1074
+ "two arguments" do
938
1075
 
939
- conn = described_class.connect( @conninfo )
940
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
941
- conn.finish
1076
+ @conn.exec( 'ROLLBACK' )
1077
+ @conn.exec( 'LISTEN knees' )
942
1078
 
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' )
1079
+ conn = described_class.connect( @conninfo )
1080
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1081
+ conn.finish
948
1082
 
949
- expect( event ).to eq( 'knees' )
950
- expect( pid ).to be_a_kind_of( Integer )
951
- expect( msg ).to be_nil()
1083
+ event, pid, msg = nil
1084
+ @conn.wait_for_notify( 10 ) do |arg1, arg2|
1085
+ event, pid, msg = arg1, arg2
952
1086
  end
1087
+ @conn.exec( 'UNLISTEN knees' )
953
1088
 
954
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
955
- "doesn't accept arguments" do
1089
+ expect( event ).to eq( 'knees' )
1090
+ expect( pid ).to be_a_kind_of( Integer )
1091
+ expect( msg ).to be_nil()
1092
+ end
956
1093
 
957
- @conn.exec( 'ROLLBACK' )
958
- @conn.exec( 'LISTEN knees' )
1094
+ it "calls the block supplied to wait_for_notify with the notify payload if it " +
1095
+ "doesn't accept arguments" do
959
1096
 
960
- conn = described_class.connect( @conninfo )
961
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
962
- conn.finish
1097
+ @conn.exec( 'ROLLBACK' )
1098
+ @conn.exec( 'LISTEN knees' )
963
1099
 
964
- notification_received = false
965
- @conn.wait_for_notify( 10 ) do
966
- notification_received = true
967
- end
968
- @conn.exec( 'UNLISTEN knees' )
1100
+ conn = described_class.connect( @conninfo )
1101
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1102
+ conn.finish
969
1103
 
970
- expect( notification_received ).to be_truthy()
1104
+ notification_received = false
1105
+ @conn.wait_for_notify( 10 ) do
1106
+ notification_received = true
971
1107
  end
1108
+ @conn.exec( 'UNLISTEN knees' )
972
1109
 
973
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
974
- "three arguments" do
1110
+ expect( notification_received ).to be_truthy()
1111
+ end
975
1112
 
976
- @conn.exec( 'ROLLBACK' )
977
- @conn.exec( 'LISTEN knees' )
1113
+ it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1114
+ "three arguments" do
978
1115
 
979
- conn = described_class.connect( @conninfo )
980
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
981
- conn.finish
1116
+ @conn.exec( 'ROLLBACK' )
1117
+ @conn.exec( 'LISTEN knees' )
982
1118
 
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' )
1119
+ conn = described_class.connect( @conninfo )
1120
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1121
+ conn.finish
988
1122
 
989
- expect( event ).to eq( 'knees' )
990
- expect( pid ).to be_a_kind_of( Integer )
991
- expect( msg ).to eq( 'skirt and boots' )
1123
+ event, pid, msg = nil
1124
+ @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1125
+ event, pid, msg = arg1, arg2, arg3
992
1126
  end
1127
+ @conn.exec( 'UNLISTEN knees' )
993
1128
 
1129
+ expect( event ).to eq( 'knees' )
1130
+ expect( pid ).to be_a_kind_of( Integer )
1131
+ expect( msg ).to eq( 'skirt and boots' )
994
1132
  end
995
1133
 
996
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1134
+ context "server ping", :without_transaction do
997
1135
 
998
1136
  it "pings successfully with connection string" do
999
1137
  ping = described_class.ping(@conninfo)
@@ -1021,125 +1159,119 @@ describe PG::Connection do
1021
1159
  expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1022
1160
  end
1023
1161
 
1024
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
1025
1163
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1026
- expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1164
+ expect( ping ).to_not eq( PG::PQPING_OK )
1027
1165
  end
1028
1166
 
1167
+ it "returns correct response when ping connection arguments are wrong" do
1168
+ ping = described_class.ping(
1169
+ :host => 'localhost',
1170
+ :invalid_option => 9999,
1171
+ :dbname => :test)
1172
+ expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1173
+ end
1029
1174
 
1030
1175
  end
1031
1176
 
1032
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
1033
- describe "set_single_row_mode" do
1177
+ describe "set_single_row_mode" do
1034
1178
 
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)
1179
+ it "raises an error when called at the wrong time" do
1180
+ expect {
1181
+ @conn.set_single_row_mode
1182
+ }.to raise_error(PG::Error)
1183
+ end
1184
+
1185
+ it "should work in single row mode" do
1186
+ @conn.send_query( "SELECT generate_series(1,10)" )
1187
+ @conn.set_single_row_mode
1188
+
1189
+ results = []
1190
+ loop do
1191
+ @conn.block
1192
+ res = @conn.get_result or break
1193
+ results << res
1039
1194
  end
1195
+ expect( results.length ).to eq( 11 )
1196
+ results[0..-2].each do |res|
1197
+ expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1198
+ values = res.field_values('generate_series')
1199
+ expect( values.length ).to eq( 1 )
1200
+ expect( values.first.to_i ).to be > 0
1201
+ end
1202
+ expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1203
+ expect( results.last.ntuples ).to eq( 0 )
1204
+ end
1040
1205
 
1041
- it "should work in single row mode" do
1042
- @conn.send_query( "SELECT generate_series(1,10)" )
1043
- @conn.set_single_row_mode
1206
+ it "should receive rows before entire query is finished" do
1207
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1208
+ @conn.set_single_row_mode
1044
1209
 
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 )
1210
+ start_time = Time.now
1211
+ first_row_time = nil
1212
+ loop do
1213
+ res = @conn.get_result or break
1214
+ res.check
1215
+ first_row_time = Time.now unless first_row_time
1060
1216
  end
1217
+ expect( (Time.now - start_time) ).to be >= 0.9
1218
+ expect( (first_row_time - start_time) ).to be < 0.9
1219
+ end
1061
1220
 
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
1221
+ it "should receive rows before entire query fails" do
1222
+ @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1223
+ @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1224
+ @conn.set_single_row_mode
1065
1225
 
1066
- start_time = Time.now
1067
- first_row_time = nil
1226
+ first_result = nil
1227
+ expect do
1068
1228
  loop do
1069
1229
  res = @conn.get_result or break
1070
1230
  res.check
1071
- first_row_time = Time.now unless first_row_time
1231
+ first_result ||= res
1072
1232
  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
1233
+ end.to raise_error(PG::Error)
1234
+ expect( first_result.kind_of?(PG::Result) ).to be_truthy
1235
+ expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1093
1236
  end
1237
+
1094
1238
  end
1095
1239
 
1096
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
1097
1241
 
1098
1242
  describe "rubyforge #22925: m17n support" do
1099
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
1100
- out_string = nil
1101
- @conn.transaction do |conn|
1102
- conn.internal_encoding = 'iso8859-1'
1103
- res = conn.exec("VALUES ('fantasia')", [], 0)
1104
- out_string = res[0]['column1']
1105
- end
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1106
1247
  expect( out_string ).to eq( 'fantasia' )
1107
1248
  expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1108
1249
  end
1109
1250
 
1110
1251
  it "should return results in the same encoding as the client (utf-8)" do
1111
- out_string = nil
1112
- @conn.transaction do |conn|
1113
- conn.internal_encoding = 'utf-8'
1114
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1115
- out_string = res[0]['column1']
1116
- end
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1117
1255
  expect( out_string ).to eq( '世界線航跡蔵' )
1118
1256
  expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1119
1257
  end
1120
1258
 
1121
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1122
- out_string = nil
1123
- @conn.transaction do |conn|
1124
- conn.internal_encoding = 'EUC-JP'
1125
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1126
- res = conn.exec(stmt, [], 0)
1127
- out_string = res[0]['column1']
1128
- end
1260
+ @conn.internal_encoding = 'EUC-JP'
1261
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1262
+ res = @conn.exec_params(stmt, [], 0)
1263
+ out_string = res[0]['column1']
1129
1264
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1130
1265
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1131
1266
  end
1132
1267
 
1133
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1134
1269
  "changed since the results were fetched" do
1135
- out_string = nil
1136
- @conn.transaction do |conn|
1137
- conn.internal_encoding = 'EUC-JP'
1138
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1139
- res = conn.exec(stmt, [], 0)
1140
- conn.internal_encoding = 'utf-8'
1141
- out_string = res[0]['column1']
1142
- end
1270
+ @conn.internal_encoding = 'EUC-JP'
1271
+ stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1272
+ res = @conn.exec_params(stmt, [], 0)
1273
+ @conn.internal_encoding = 'utf-8'
1274
+ out_string = res[0]['column1']
1143
1275
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1144
1276
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1145
1277
  end
@@ -1149,58 +1281,215 @@ describe PG::Connection do
1149
1281
  expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1150
1282
  end
1151
1283
 
1284
+ it "the connection should use JOHAB dummy encoding when it's set to JOHAB" do
1285
+ @conn.set_client_encoding "JOHAB"
1286
+ val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
1287
+ expect( val.encoding.name ).to eq( "JOHAB" )
1288
+ expect( val.unpack("H*")[0] ).to eq( "dc65" )
1289
+ end
1290
+
1291
+ it "can retrieve server encoding as text" do
1292
+ enc = @conn.parameter_status "server_encoding"
1293
+ expect( enc ).to eq( "UTF8" )
1294
+ end
1295
+
1296
+ it "can retrieve server encoding as ruby encoding" do
1297
+ expect( @conn.external_encoding ).to eq( Encoding::UTF_8 )
1298
+ end
1299
+
1152
1300
  it "uses the client encoding for escaped string" do
1153
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1301
+ original = "Möhre to 'scape".encode( "utf-16be" )
1154
1302
  @conn.set_client_encoding( "euc_jp" )
1155
1303
  escaped = @conn.escape( original )
1156
1304
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1157
- expect( escaped ).to eq( "string to" )
1305
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1158
1306
  end
1159
1307
 
1160
- it "uses the client encoding for escaped literal", :postgresql_90 do
1161
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1308
+ it "uses the client encoding for escaped literal" do
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1162
1310
  @conn.set_client_encoding( "euc_jp" )
1163
1311
  escaped = @conn.escape_literal( original )
1164
1312
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1165
- expect( escaped ).to eq( "'string to'" )
1313
+ expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
1166
1314
  end
1167
1315
 
1168
- it "uses the client encoding for escaped identifier", :postgresql_90 do
1169
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1316
+ it "uses the client encoding for escaped identifier" do
1317
+ original = "Möhre to 'scape".encode( "utf-16le" )
1170
1318
  @conn.set_client_encoding( "euc_jp" )
1171
1319
  escaped = @conn.escape_identifier( original )
1172
1320
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1173
- expect( escaped ).to eq( "\"string to\"" )
1321
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1174
1322
  end
1175
1323
 
1176
1324
  it "uses the client encoding for quote_ident" do
1177
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1178
1326
  @conn.set_client_encoding( "euc_jp" )
1179
1327
  escaped = @conn.quote_ident( original )
1180
1328
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1181
- expect( escaped ).to eq( "\"string to\"" )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1182
1330
  end
1183
1331
 
1184
1332
  it "uses the previous string encoding for escaped string" do
1185
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1333
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1186
1334
  @conn.set_client_encoding( "euc_jp" )
1187
1335
  escaped = described_class.escape( original )
1188
1336
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1189
- expect( escaped ).to eq( "string to" )
1337
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1190
1338
  end
1191
1339
 
1192
1340
  it "uses the previous string encoding for quote_ident" do
1193
- original = "string to\0 escape".force_encoding( "iso8859-1" )
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1194
1342
  @conn.set_client_encoding( "euc_jp" )
1195
1343
  escaped = described_class.quote_ident( original )
1196
1344
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1197
- expect( escaped ).to eq( "\"string to\"" )
1345
+ expect( escaped.encode ).to eq( "\"Möhre to 'scape\"".encode(Encoding::ISO8859_1) )
1346
+ end
1347
+
1348
+ it "raises appropriate error if set_client_encoding is called with invalid arguments" do
1349
+ expect { @conn.set_client_encoding( "invalid" ) }.to raise_error(PG::Error, /invalid value/)
1350
+ expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1351
+ expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1352
+ end
1353
+
1354
+ it "can use an encoding with high index for client encoding" do
1355
+ # Allocate a lot of encoding indices, so that MRI's ENCODING_INLINE_MAX is exceeded
1356
+ unless Encoding.name_list.include?("pgtest-0")
1357
+ 256.times do |eidx|
1358
+ Encoding::UTF_8.replicate("pgtest-#{eidx}")
1359
+ end
1360
+ end
1361
+
1362
+ # Now allocate the JOHAB encoding with an unusual high index
1363
+ @conn.set_client_encoding "JOHAB"
1364
+ val = @conn.exec("SELECT chr(x'3391'::int)").values[0][0]
1365
+ expect( val.encoding.name ).to eq( "JOHAB" )
1366
+ end
1367
+
1368
+ end
1369
+
1370
+ describe "respect and convert character encoding of input strings" do
1371
+ before :each do
1372
+ @conn.internal_encoding = __ENCODING__
1198
1373
  end
1374
+
1375
+ it "should convert query string and parameters to #exec_params" do
1376
+ r = @conn.exec_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1377
+ ['grün'.encode('utf-16be'), 'grün'.encode('iso-8859-1')])
1378
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1379
+ end
1380
+
1381
+ it "should convert query string to #exec" do
1382
+ r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1383
+ expect( r.values ).to eq( [['grün']] )
1384
+ end
1385
+
1386
+ it "should convert strings and parameters to #prepare and #exec_prepared" do
1387
+ @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1388
+ r = @conn.exec_prepared("weiß1".encode("utf-32le"),
1389
+ ['grün'.encode('cp936'), 'grün'.encode('utf-16le')])
1390
+ expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1391
+ end
1392
+
1393
+ it "should convert strings to #describe_prepared" do
1394
+ @conn.prepare("weiß2", "VALUES(123)")
1395
+ r = @conn.describe_prepared("weiß2".encode("utf-16be"))
1396
+ expect( r.nfields ).to eq( 1 )
1397
+ end
1398
+
1399
+ it "should convert strings to #describe_portal" do
1400
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1401
+ r = @conn.describe_portal("cörsör".encode("utf-16le"))
1402
+ expect( r.nfields ).to eq( 3 )
1403
+ end
1404
+
1405
+ it "should convert query string to #send_query" do
1406
+ @conn.send_query("VALUES('grün')".encode("utf-16be"))
1407
+ expect( @conn.get_last_result.values ).to eq( [['grün']] )
1408
+ end
1409
+
1410
+ it "should convert query string and parameters to #send_query_params" do
1411
+ @conn.send_query_params("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
1412
+ ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1413
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1414
+ end
1415
+
1416
+ it "should convert strings and parameters to #send_prepare and #send_query_prepared" do
1417
+ @conn.send_prepare("weiß3".encode("iso-8859-1"), "VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16be"))
1418
+ @conn.get_last_result
1419
+ @conn.send_query_prepared("weiß3".encode("utf-32le"),
1420
+ ['grün'.encode('utf-16le'), 'grün'.encode('iso-8859-1')])
1421
+ expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1422
+ end
1423
+
1424
+ it "should convert strings to #send_describe_prepared" do
1425
+ @conn.prepare("weiß4", "VALUES(123)")
1426
+ @conn.send_describe_prepared("weiß4".encode("utf-16be"))
1427
+ expect( @conn.get_last_result.nfields ).to eq( 1 )
1428
+ end
1429
+
1430
+ it "should convert strings to #send_describe_portal" do
1431
+ @conn.exec "DECLARE cörsör CURSOR FOR VALUES(1,2,3)"
1432
+ @conn.send_describe_portal("cörsör".encode("utf-16le"))
1433
+ expect( @conn.get_last_result.nfields ).to eq( 3 )
1434
+ end
1435
+
1436
+ it "should convert error string to #put_copy_end" do
1437
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1438
+ @conn.exec( "COPY copytable FROM STDIN" )
1439
+ @conn.put_copy_end("grün".encode("utf-16be"))
1440
+ expect( @conn.get_result.error_message ).to match(/grün/)
1441
+ @conn.get_result
1442
+ end
1443
+ end
1444
+
1445
+ it "rejects command strings with zero bytes" do
1446
+ expect{ @conn.exec( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1447
+ expect{ @conn.exec_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1448
+ expect{ @conn.prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1449
+ expect{ @conn.prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1450
+ expect{ @conn.exec_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1451
+ expect{ @conn.describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1452
+ expect{ @conn.describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1453
+ expect{ @conn.send_query( "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1454
+ expect{ @conn.send_query_params( "SELECT 1;\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1455
+ expect{ @conn.send_prepare( "abc\x00", "SELECT 1;" ) }.to raise_error(ArgumentError, /null byte/)
1456
+ expect{ @conn.send_prepare( "abc", "SELECT 1;\x00" ) }.to raise_error(ArgumentError, /null byte/)
1457
+ expect{ @conn.send_query_prepared( "abc\x00", [] ) }.to raise_error(ArgumentError, /null byte/)
1458
+ expect{ @conn.send_describe_prepared( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1459
+ expect{ @conn.send_describe_portal( "abc\x00" ) }.to raise_error(ArgumentError, /null byte/)
1460
+ end
1461
+
1462
+ it "rejects query params with zero bytes" do
1463
+ expect{ @conn.exec_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1464
+ expect{ @conn.exec_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1465
+ expect{ @conn.send_query_params( "SELECT 1;\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1466
+ expect{ @conn.send_query_prepared( "abc\x00", ["ab\x00"] ) }.to raise_error(ArgumentError, /null byte/)
1467
+ end
1468
+
1469
+ it "rejects string with zero bytes in escape" do
1470
+ expect{ @conn.escape( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1471
+ end
1472
+
1473
+ it "rejects string with zero bytes in escape_literal" do
1474
+ expect{ @conn.escape_literal( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1475
+ end
1476
+
1477
+ it "rejects string with zero bytes in escape_identifier" do
1478
+ expect{ @conn.escape_identifier( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1479
+ end
1480
+
1481
+ it "rejects string with zero bytes in quote_ident" do
1482
+ expect{ described_class.quote_ident( "ab\x00cd" ) }.to raise_error(ArgumentError, /null byte/)
1483
+ end
1484
+
1485
+ it "rejects Array with string with zero bytes" do
1486
+ original = ["xyz", "2\x00"]
1487
+ expect{ described_class.quote_ident( original ) }.to raise_error(ArgumentError, /null byte/)
1199
1488
  end
1200
1489
 
1201
1490
  it "can quote bigger strings with quote_ident" do
1202
1491
  original = "'01234567\"" * 100
1203
- escaped = described_class.quote_ident( original + "\0afterzero" )
1492
+ escaped = described_class.quote_ident( original )
1204
1493
  expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1205
1494
  end
1206
1495
 
@@ -1219,7 +1508,7 @@ describe PG::Connection do
1219
1508
 
1220
1509
  describe "Ruby 1.9.x default_internal encoding" do
1221
1510
 
1222
- it "honors the Encoding.default_internal if it's set and the synchronous interface is used" do
1511
+ it "honors the Encoding.default_internal if it's set and the synchronous interface is used", :without_transaction do
1223
1512
  @conn.transaction do |txn_conn|
1224
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1225
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1228,12 +1517,12 @@ describe PG::Connection do
1228
1517
 
1229
1518
  begin
1230
1519
  prev_encoding = Encoding.default_internal
1231
- Encoding.default_internal = Encoding::UTF_8
1520
+ Encoding.default_internal = Encoding::ISO8859_2
1232
1521
 
1233
1522
  conn = PG.connect( @conninfo )
1234
- expect( conn.internal_encoding ).to eq( Encoding::UTF_8 )
1523
+ expect( conn.internal_encoding ).to eq( Encoding::ISO8859_2 )
1235
1524
  res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1236
- expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
1525
+ expect( res[0]['foo'].encoding ).to eq( Encoding::ISO8859_2 )
1237
1526
  ensure
1238
1527
  conn.exec( "DROP TABLE defaultinternaltest" )
1239
1528
  conn.finish if conn
@@ -1278,7 +1567,7 @@ describe PG::Connection do
1278
1567
  conn.finish if conn
1279
1568
  end
1280
1569
 
1281
- it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1570
+ it "handles clearing result in or after set_notice_receiver" do
1282
1571
  r = nil
1283
1572
  @conn.set_notice_receiver do |result|
1284
1573
  r = result
@@ -1293,7 +1582,7 @@ describe PG::Connection do
1293
1582
  @conn.set_notice_receiver
1294
1583
  end
1295
1584
 
1296
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1585
+ it "receives properly encoded messages in the notice callbacks" do
1297
1586
  [:receiver, :processor].each do |kind|
1298
1587
  notices = []
1299
1588
  @conn.internal_encoding = 'utf-8'
@@ -1321,9 +1610,8 @@ describe PG::Connection do
1321
1610
  end
1322
1611
  end
1323
1612
 
1324
- it "receives properly encoded text from wait_for_notify", :postgresql_90 do
1613
+ it "receives properly encoded text from wait_for_notify", :without_transaction do
1325
1614
  @conn.internal_encoding = 'utf-8'
1326
- @conn.exec( 'ROLLBACK' )
1327
1615
  @conn.exec( 'LISTEN "Möhre"' )
1328
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1329
1617
  event, pid, msg = nil
@@ -1334,13 +1622,13 @@ describe PG::Connection do
1334
1622
 
1335
1623
  expect( event ).to eq( "Möhre" )
1336
1624
  expect( event.encoding ).to eq( Encoding::UTF_8 )
1625
+ expect( pid ).to be_a_kind_of(Integer)
1337
1626
  expect( msg ).to eq( '世界線航跡蔵' )
1338
1627
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1339
1628
  end
1340
1629
 
1341
- it "returns properly encoded text from notifies", :postgresql_90 do
1630
+ it "returns properly encoded text from notifies", :without_transaction do
1342
1631
  @conn.internal_encoding = 'utf-8'
1343
- @conn.exec( 'ROLLBACK' )
1344
1632
  @conn.exec( 'LISTEN "Möhre"' )
1345
1633
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1346
1634
  @conn.exec( 'UNLISTEN "Möhre"' )
@@ -1354,7 +1642,7 @@ describe PG::Connection do
1354
1642
  end
1355
1643
  end
1356
1644
 
1357
- context "OS thread support", :ruby_19 do
1645
+ context "OS thread support" do
1358
1646
  it "Connection#exec shouldn't block a second thread" do
1359
1647
  t = Thread.new do
1360
1648
  @conn.exec( "select pg_sleep(1)" )
@@ -1394,9 +1682,14 @@ describe PG::Connection do
1394
1682
  end
1395
1683
 
1396
1684
  it "shouldn't type map params unless requested" do
1397
- expect{
1398
- @conn.exec_params( "SELECT $1", [5] )
1399
- }.to raise_error(PG::IndeterminateDatatype)
1685
+ if @conn.server_version < 100000
1686
+ expect{
1687
+ @conn.exec_params( "SELECT $1", [5] )
1688
+ }.to raise_error(PG::IndeterminateDatatype)
1689
+ else
1690
+ # PostgreSQL-10 maps to TEXT type (OID 25)
1691
+ expect( @conn.exec_params( "SELECT $1", [5] ).ftype(0)).to eq(25)
1692
+ end
1400
1693
  end
1401
1694
 
1402
1695
  it "should raise an error on invalid encoder to put_copy_data" do
@@ -1410,12 +1703,12 @@ describe PG::Connection do
1410
1703
  row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1411
1704
 
1412
1705
  @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1413
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1706
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1414
1707
  @conn.put_copy_data [1], row_encoder
1415
1708
  @conn.put_copy_data ["2"], row_encoder
1416
1709
  end
1417
1710
 
1418
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1711
+ @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1419
1712
  @conn.put_copy_data [3]
1420
1713
  @conn.put_copy_data ["4"]
1421
1714
  end
@@ -1463,15 +1756,15 @@ describe PG::Connection do
1463
1756
  end
1464
1757
  end
1465
1758
 
1466
- it "can process #copy_data input queries with row encoder" do
1759
+ it "can process #copy_data input queries with row encoder and respects character encoding" do
1467
1760
  @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1468
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1761
+ @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1469
1762
  @conn2.put_copy_data [1]
1470
- @conn2.put_copy_data ["2"]
1763
+ @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1471
1764
  end
1472
1765
 
1473
1766
  res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1474
- expect( res.values ).to eq( [["1"], ["2"]] )
1767
+ expect( res.values ).to eq( [["1"], ["Möhre"]] )
1475
1768
  end
1476
1769
  end
1477
1770
 
@@ -1513,14 +1806,16 @@ describe PG::Connection do
1513
1806
  end
1514
1807
  end
1515
1808
 
1516
- it "can process #copy_data output with row decoder" do
1809
+ it "can process #copy_data output with row decoder and respects character encoding" do
1810
+ @conn2.internal_encoding = Encoding::ISO8859_1
1517
1811
  rows = []
1518
- res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
1812
+ @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1519
1813
  while row=@conn2.get_copy_data
1520
1814
  rows << row
1521
1815
  end
1522
1816
  end
1523
- expect( rows ).to eq( [["1"], ["2"]] )
1817
+ expect( rows.last.last.encoding ).to eq( Encoding::ISO8859_1 )
1818
+ expect( rows ).to eq( [["1"], ["Möhre".encode("iso-8859-1")]] )
1524
1819
  end
1525
1820
 
1526
1821
  it "can type cast #copy_data output with explicit decoder" do
@@ -1541,4 +1836,114 @@ describe PG::Connection do
1541
1836
  end
1542
1837
  end
1543
1838
  end
1839
+
1840
+ describe :field_name_type do
1841
+ before :each do
1842
+ @conn2 = PG.connect(@conninfo)
1843
+ end
1844
+ after :each do
1845
+ @conn2.close
1846
+ end
1847
+
1848
+ it "uses string field names per default" do
1849
+ expect(@conn2.field_name_type).to eq(:string)
1850
+ end
1851
+
1852
+ it "can set string field names" do
1853
+ @conn2.field_name_type = :string
1854
+ expect(@conn2.field_name_type).to eq(:string)
1855
+ res = @conn2.exec("SELECT 1 as az")
1856
+ expect(res.field_name_type).to eq(:string)
1857
+ expect(res.fields).to eq(["az"])
1858
+ end
1859
+
1860
+ it "can set symbol field names" do
1861
+ @conn2.field_name_type = :symbol
1862
+ expect(@conn2.field_name_type).to eq(:symbol)
1863
+ res = @conn2.exec("SELECT 1 as az")
1864
+ expect(res.field_name_type).to eq(:symbol)
1865
+ expect(res.fields).to eq([:az])
1866
+ end
1867
+
1868
+ it "can't set invalid values" do
1869
+ expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
1870
+ expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
1871
+ end
1872
+ end
1873
+
1874
+ describe "deprecated forms of methods" do
1875
+ if PG::VERSION < "2"
1876
+ it "should forward exec to exec_params" do
1877
+ res = @conn.exec("VALUES($1::INT)", [7]).values
1878
+ expect(res).to eq( [["7"]] )
1879
+ res = @conn.exec("VALUES($1::INT)", [7], 1).values
1880
+ expect(res).to eq( [[[7].pack("N")]] )
1881
+ res = @conn.exec("VALUES(8)", [], 1).values
1882
+ expect(res).to eq( [[[8].pack("N")]] )
1883
+ end
1884
+
1885
+ it "should forward exec_params to exec" do
1886
+ res = @conn.exec_params("VALUES(3); VALUES(4)").values
1887
+ expect(res).to eq( [["4"]] )
1888
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil).values
1889
+ expect(res).to eq( [["4"]] )
1890
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil).values
1891
+ expect(res).to eq( [["4"]] )
1892
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, 1).values
1893
+ expect(res).to eq( [["4"]] )
1894
+ res = @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil).values
1895
+ expect(res).to eq( [["4"]] )
1896
+ expect{
1897
+ @conn.exec_params("VALUES(3); VALUES(4)", nil, nil, nil, nil).values
1898
+ }.to raise_error(ArgumentError)
1899
+ end
1900
+
1901
+ it "should forward send_query to send_query_params" do
1902
+ @conn.send_query("VALUES($1)", [5])
1903
+ expect(@conn.get_last_result.values).to eq( [["5"]] )
1904
+ end
1905
+
1906
+ it "should respond_to socket", :unix do
1907
+ expect( @conn.socket ).to eq( @conn.socket_io.fileno )
1908
+ end
1909
+ else
1910
+ # Method forwarding removed by PG::VERSION >= "2"
1911
+ it "shouldn't forward exec to exec_params" do
1912
+ expect do
1913
+ @conn.exec("VALUES($1::INT)", [7])
1914
+ end.to raise_error(ArgumentError)
1915
+ end
1916
+
1917
+ it "shouldn't forward exec_params to exec" do
1918
+ expect do
1919
+ @conn.exec_params("VALUES(3); VALUES(4)")
1920
+ end.to raise_error(ArgumentError)
1921
+ end
1922
+
1923
+ it "shouldn't forward send_query to send_query_params" do
1924
+ expect do
1925
+ @conn.send_query("VALUES($1)", [5])
1926
+ end.to raise_error(ArgumentError)
1927
+ end
1928
+
1929
+ it "shouldn't forward async_exec_params to async_exec" do
1930
+ expect do
1931
+ @conn.async_exec_params("VALUES(1)")
1932
+ end.to raise_error(ArgumentError)
1933
+ end
1934
+
1935
+ it "shouldn't respond_to socket" do
1936
+ expect do
1937
+ @conn.socket
1938
+ end.to raise_error(ArgumentError)
1939
+ end
1940
+ end
1941
+
1942
+ it "shouldn't forward send_query_params to send_query" do
1943
+ expect{ @conn.send_query_params("VALUES(4)").values }
1944
+ .to raise_error(ArgumentError)
1945
+ expect{ @conn.send_query_params("VALUES(4)", nil).values }
1946
+ .to raise_error(TypeError)
1947
+ end
1948
+ end
1544
1949
  end