pg 0.21.0 → 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 (67) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +184 -0
  6. data/Manifest.txt +8 -3
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +58 -13
  10. data/Rakefile +10 -9
  11. data/Rakefile.cross +68 -71
  12. data/ext/errorcodes.def +76 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +21 -2
  15. data/ext/extconf.rb +18 -36
  16. data/ext/gvl_wrappers.c +4 -0
  17. data/ext/gvl_wrappers.h +23 -39
  18. data/ext/pg.c +154 -144
  19. data/ext/pg.h +68 -95
  20. data/ext/pg_binary_decoder.c +82 -15
  21. data/ext/pg_binary_encoder.c +13 -12
  22. data/ext/pg_coder.c +73 -12
  23. data/ext/pg_connection.c +699 -459
  24. data/ext/pg_copy_coder.c +16 -8
  25. data/ext/pg_record_coder.c +491 -0
  26. data/ext/pg_result.c +571 -195
  27. data/ext/pg_text_decoder.c +606 -40
  28. data/ext/pg_text_encoder.c +185 -54
  29. data/ext/pg_tuple.c +549 -0
  30. data/ext/pg_type_map.c +1 -1
  31. data/ext/pg_type_map_all_strings.c +4 -4
  32. data/ext/pg_type_map_by_class.c +9 -4
  33. data/ext/pg_type_map_by_column.c +7 -6
  34. data/ext/pg_type_map_by_mri_type.c +1 -1
  35. data/ext/pg_type_map_by_oid.c +3 -2
  36. data/ext/pg_type_map_in_ruby.c +1 -1
  37. data/ext/{util.c → pg_util.c} +10 -10
  38. data/ext/{util.h → pg_util.h} +2 -2
  39. data/lib/pg.rb +8 -10
  40. data/lib/pg/basic_type_mapping.rb +121 -25
  41. data/lib/pg/binary_decoder.rb +23 -0
  42. data/lib/pg/coder.rb +23 -2
  43. data/lib/pg/connection.rb +28 -4
  44. data/lib/pg/constants.rb +2 -1
  45. data/lib/pg/exceptions.rb +2 -1
  46. data/lib/pg/result.rb +14 -2
  47. data/lib/pg/text_decoder.rb +21 -26
  48. data/lib/pg/text_encoder.rb +32 -8
  49. data/lib/pg/tuple.rb +30 -0
  50. data/lib/pg/type_map_by_column.rb +3 -2
  51. data/spec/helpers.rb +61 -33
  52. data/spec/pg/basic_type_mapping_spec.rb +362 -37
  53. data/spec/pg/connection_spec.rb +602 -329
  54. data/spec/pg/connection_sync_spec.rb +41 -0
  55. data/spec/pg/result_spec.rb +242 -17
  56. data/spec/pg/tuple_spec.rb +333 -0
  57. data/spec/pg/type_map_by_class_spec.rb +2 -2
  58. data/spec/pg/type_map_by_column_spec.rb +6 -2
  59. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  60. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  61. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  62. data/spec/pg/type_map_spec.rb +1 -1
  63. data/spec/pg/type_spec.rb +364 -18
  64. data/spec/pg_spec.rb +2 -2
  65. metadata +48 -43
  66. metadata.gz.sig +0 -0
  67. data/lib/pg/deprecated_constants.rb +0 -21
@@ -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,7 @@ 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/)
642
683
  end
643
684
  expect( @conn ).to still_be_usable
644
685
  end
@@ -718,11 +759,6 @@ describe PG::Connection do
718
759
  expect( (finish - start) ).to be_within( 0.2 ).of( 0.3 )
719
760
  end
720
761
 
721
-
722
- it "can encrypt a string given a password and username" do
723
- expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
724
- end
725
-
726
762
  it "can return the default connection options" do
727
763
  expect( described_class.conndefaults ).to be_a( Array )
728
764
  expect( described_class.conndefaults ).to all( be_a(Hash) )
@@ -733,7 +769,7 @@ describe PG::Connection do
733
769
  it "can return the default connection options as a Hash" do
734
770
  expect( described_class.conndefaults_hash ).to be_a( Hash )
735
771
  expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
736
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
772
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
737
773
  expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
738
774
  end
739
775
 
@@ -779,18 +815,52 @@ describe PG::Connection do
779
815
  end
780
816
  end
781
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
782
822
 
783
- it "raises an appropriate error if either of the required arguments for encrypt_password " +
784
- "is not valid" do
785
- expect {
786
- described_class.encrypt_password( nil, nil )
787
- }.to raise_error( TypeError )
788
- expect {
789
- described_class.encrypt_password( "postgres", nil )
790
- }.to raise_error( TypeError )
791
- expect {
792
- described_class.encrypt_password( nil, "postgres" )
793
- }.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
794
864
  end
795
865
 
796
866
 
@@ -832,7 +902,7 @@ describe PG::Connection do
832
902
  end
833
903
 
834
904
 
835
- it "can connect asynchronously", :socket_io do
905
+ it "handles server close while asynchronous connect" do
836
906
  serv = TCPServer.new( '127.0.0.1', 54320 )
837
907
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
838
908
  expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
@@ -844,11 +914,29 @@ describe PG::Connection do
844
914
  expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
845
915
  end
846
916
 
847
- 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
848
936
 
849
- it "calls the block if one is provided to #async_exec" do
937
+ it "calls the block if one is provided to #exec" do
850
938
  result = nil
851
- @conn.async_exec( "select 47 as one" ) do |pg_res|
939
+ @conn.exec( "select 47 as one" ) do |pg_res|
852
940
  result = pg_res[0]
853
941
  end
854
942
  expect( result ).to eq( { 'one' => '47' } )
@@ -861,7 +949,21 @@ describe PG::Connection do
861
949
  expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
862
950
  end
863
951
 
864
- 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
865
967
  conn = PG.connect( @conninfo )
866
968
  io = conn.socket_io
867
969
  conn.finish
@@ -869,7 +971,7 @@ describe PG::Connection do
869
971
  expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
870
972
  end
871
973
 
872
- 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
873
975
  conn = PG.connect( @conninfo )
874
976
  io = conn.socket_io
875
977
  conn.reset
@@ -889,155 +991,147 @@ describe PG::Connection do
889
991
  expect{ conn.block }.to raise_error(PG::ConnectionBad, /can't get socket descriptor/)
890
992
  end
891
993
 
892
- 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' )
893
996
 
894
- before( :each ) do
895
- pending "only works with a PostgreSQL >= 9.0 server" if @conn.server_version < 9_00_00
896
- 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
897
1002
 
898
- 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"
899
1007
  conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
900
-
901
1008
  conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
902
1009
  expect( conn_name ).to include( $0[0..10] )
903
1010
  expect( conn_name ).to include( $0[-10..-1] )
904
1011
  expect( conn_name.length ).to be <= 64
1012
+ ensure
1013
+ $0 = old_0
905
1014
  end
1015
+ end
906
1016
 
907
- it "sets a shortened fallback_application_name on new connections" do
908
- old_0 = $0
909
- begin
910
- $0 = "/this/is/a/very/long/path/with/many/directories/to/our/beloved/ruby"
911
- conn_string = PG::Connection.parse_connect_args( 'dbname=test' )
912
- conn_name = conn_string[ /application_name='(.*?)'/, 1 ]
913
- expect( conn_name ).to include( $0[0..10] )
914
- expect( conn_name ).to include( $0[-10..-1] )
915
- expect( conn_name.length ).to be <= 64
916
- ensure
917
- $0 = old_0
918
- end
919
- end
920
-
921
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
922
- "any number of arguments" do
923
-
924
- @conn.exec( 'ROLLBACK' )
925
- @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
926
1019
 
927
- conn = described_class.connect( @conninfo )
928
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
929
- conn.finish
1020
+ @conn.exec( 'ROLLBACK' )
1021
+ @conn.exec( 'LISTEN knees' )
930
1022
 
931
- event, pid, msg = nil
932
- @conn.wait_for_notify( 10 ) do |*args|
933
- event, pid, msg = *args
934
- end
935
- @conn.exec( 'UNLISTEN knees' )
1023
+ conn = described_class.connect( @conninfo )
1024
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
+ conn.finish
936
1026
 
937
- expect( event ).to eq( 'knees' )
938
- expect( pid ).to be_a_kind_of( Integer )
939
- 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
940
1030
  end
1031
+ @conn.exec( 'UNLISTEN knees' )
941
1032
 
942
- it "accepts nil as the timeout in #wait_for_notify " do
943
- @conn.exec( 'ROLLBACK' )
944
- @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
945
1037
 
946
- conn = described_class.connect( @conninfo )
947
- conn.exec( %Q{NOTIFY knees} )
948
- conn.finish
1038
+ it "accepts nil as the timeout in #wait_for_notify " do
1039
+ @conn.exec( 'ROLLBACK' )
1040
+ @conn.exec( 'LISTEN knees' )
949
1041
 
950
- event, pid = nil
951
- @conn.wait_for_notify( nil ) do |*args|
952
- event, pid = *args
953
- end
954
- @conn.exec( 'UNLISTEN knees' )
1042
+ conn = described_class.connect( @conninfo )
1043
+ conn.exec( %Q{NOTIFY knees} )
1044
+ conn.finish
955
1045
 
956
- expect( event ).to eq( 'knees' )
957
- expect( pid ).to be_a_kind_of( Integer )
1046
+ event, pid = nil
1047
+ @conn.wait_for_notify( nil ) do |*args|
1048
+ event, pid = *args
958
1049
  end
1050
+ @conn.exec( 'UNLISTEN knees' )
959
1051
 
960
- it "sends nil as the payload if the notification wasn't given one" do
961
- @conn.exec( 'ROLLBACK' )
962
- @conn.exec( 'LISTEN knees' )
1052
+ expect( event ).to eq( 'knees' )
1053
+ expect( pid ).to be_a_kind_of( Integer )
1054
+ end
963
1055
 
964
- conn = described_class.connect( @conninfo )
965
- conn.exec( %Q{NOTIFY knees} )
966
- 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' )
967
1059
 
968
- payload = :notnil
969
- @conn.wait_for_notify( nil ) do |*args|
970
- payload = args[ 2 ]
971
- end
972
- @conn.exec( 'UNLISTEN knees' )
1060
+ conn = described_class.connect( @conninfo )
1061
+ conn.exec( %Q{NOTIFY knees} )
1062
+ conn.finish
973
1063
 
974
- expect( payload ).to be_nil()
1064
+ payload = :notnil
1065
+ @conn.wait_for_notify( nil ) do |*args|
1066
+ payload = args[ 2 ]
975
1067
  end
1068
+ @conn.exec( 'UNLISTEN knees' )
976
1069
 
977
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
978
- "two arguments" do
1070
+ expect( payload ).to be_nil()
1071
+ end
979
1072
 
980
- @conn.exec( 'ROLLBACK' )
981
- @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
982
1075
 
983
- conn = described_class.connect( @conninfo )
984
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
985
- conn.finish
1076
+ @conn.exec( 'ROLLBACK' )
1077
+ @conn.exec( 'LISTEN knees' )
986
1078
 
987
- event, pid, msg = nil
988
- @conn.wait_for_notify( 10 ) do |arg1, arg2|
989
- event, pid, msg = arg1, arg2
990
- end
991
- @conn.exec( 'UNLISTEN knees' )
1079
+ conn = described_class.connect( @conninfo )
1080
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1081
+ conn.finish
992
1082
 
993
- expect( event ).to eq( 'knees' )
994
- expect( pid ).to be_a_kind_of( Integer )
995
- 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
996
1086
  end
1087
+ @conn.exec( 'UNLISTEN knees' )
997
1088
 
998
- it "calls the block supplied to wait_for_notify with the notify payload if it " +
999
- "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
1000
1093
 
1001
- @conn.exec( 'ROLLBACK' )
1002
- @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
1003
1096
 
1004
- conn = described_class.connect( @conninfo )
1005
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1006
- conn.finish
1097
+ @conn.exec( 'ROLLBACK' )
1098
+ @conn.exec( 'LISTEN knees' )
1007
1099
 
1008
- notification_received = false
1009
- @conn.wait_for_notify( 10 ) do
1010
- notification_received = true
1011
- end
1012
- @conn.exec( 'UNLISTEN knees' )
1100
+ conn = described_class.connect( @conninfo )
1101
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1102
+ conn.finish
1013
1103
 
1014
- expect( notification_received ).to be_truthy()
1104
+ notification_received = false
1105
+ @conn.wait_for_notify( 10 ) do
1106
+ notification_received = true
1015
1107
  end
1108
+ @conn.exec( 'UNLISTEN knees' )
1016
1109
 
1017
- it "calls the block supplied to wait_for_notify with the notify payload if it accepts " +
1018
- "three arguments" do
1110
+ expect( notification_received ).to be_truthy()
1111
+ end
1019
1112
 
1020
- @conn.exec( 'ROLLBACK' )
1021
- @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
1022
1115
 
1023
- conn = described_class.connect( @conninfo )
1024
- conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1025
- conn.finish
1116
+ @conn.exec( 'ROLLBACK' )
1117
+ @conn.exec( 'LISTEN knees' )
1026
1118
 
1027
- event, pid, msg = nil
1028
- @conn.wait_for_notify( 10 ) do |arg1, arg2, arg3|
1029
- event, pid, msg = arg1, arg2, arg3
1030
- end
1031
- @conn.exec( 'UNLISTEN knees' )
1119
+ conn = described_class.connect( @conninfo )
1120
+ conn.exec( %Q{NOTIFY knees, 'skirt and boots'} )
1121
+ conn.finish
1032
1122
 
1033
- expect( event ).to eq( 'knees' )
1034
- expect( pid ).to be_a_kind_of( Integer )
1035
- 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
1036
1126
  end
1127
+ @conn.exec( 'UNLISTEN knees' )
1037
1128
 
1129
+ expect( event ).to eq( 'knees' )
1130
+ expect( pid ).to be_a_kind_of( Integer )
1131
+ expect( msg ).to eq( 'skirt and boots' )
1038
1132
  end
1039
1133
 
1040
- context "under PostgreSQL 9.1 client library", :postgresql_91, :without_transaction do
1134
+ context "server ping", :without_transaction do
1041
1135
 
1042
1136
  it "pings successfully with connection string" do
1043
1137
  ping = described_class.ping(@conninfo)
@@ -1065,125 +1159,119 @@ describe PG::Connection do
1065
1159
  expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1066
1160
  end
1067
1161
 
1068
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
1069
1163
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1070
- expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1164
+ expect( ping ).to_not eq( PG::PQPING_OK )
1071
1165
  end
1072
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
1073
1174
 
1074
1175
  end
1075
1176
 
1076
- context "under PostgreSQL 9.2 client library", :postgresql_92 do
1077
- describe "set_single_row_mode" do
1177
+ describe "set_single_row_mode" do
1078
1178
 
1079
- it "raises an error when called at the wrong time" do
1080
- expect {
1081
- @conn.set_single_row_mode
1082
- }.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
1083
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
1084
1205
 
1085
- it "should work in single row mode" do
1086
- @conn.send_query( "SELECT generate_series(1,10)" )
1087
- @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
1088
1209
 
1089
- results = []
1090
- loop do
1091
- @conn.block
1092
- res = @conn.get_result or break
1093
- results << res
1094
- end
1095
- expect( results.length ).to eq( 11 )
1096
- results[0..-2].each do |res|
1097
- expect( res.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1098
- values = res.field_values('generate_series')
1099
- expect( values.length ).to eq( 1 )
1100
- expect( values.first.to_i ).to be > 0
1101
- end
1102
- expect( results.last.result_status ).to eq( PG::PGRES_TUPLES_OK )
1103
- 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
1104
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
1105
1220
 
1106
- it "should receive rows before entire query is finished" do
1107
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, pg_sleep(1);" )
1108
- @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
1109
1225
 
1110
- start_time = Time.now
1111
- first_row_time = nil
1226
+ first_result = nil
1227
+ expect do
1112
1228
  loop do
1113
1229
  res = @conn.get_result or break
1114
1230
  res.check
1115
- first_row_time = Time.now unless first_row_time
1231
+ first_result ||= res
1116
1232
  end
1117
- expect( (Time.now - start_time) ).to be >= 0.9
1118
- expect( (first_row_time - start_time) ).to be < 0.9
1119
- end
1120
-
1121
- it "should receive rows before entire query fails" do
1122
- @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
1123
- @conn.send_query( "SELECT generate_series(0,999), NULL UNION ALL SELECT 1000, errfunc();" )
1124
- @conn.set_single_row_mode
1125
-
1126
- first_result = nil
1127
- expect do
1128
- loop do
1129
- res = @conn.get_result or break
1130
- res.check
1131
- first_result ||= res
1132
- end
1133
- end.to raise_error(PG::Error)
1134
- expect( first_result.kind_of?(PG::Result) ).to be_truthy
1135
- expect( first_result.result_status ).to eq( PG::PGRES_SINGLE_TUPLE )
1136
- 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 )
1137
1236
  end
1237
+
1138
1238
  end
1139
1239
 
1140
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
1141
1241
 
1142
1242
  describe "rubyforge #22925: m17n support" do
1143
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
1144
- out_string = nil
1145
- @conn.transaction do |conn|
1146
- conn.internal_encoding = 'iso8859-1'
1147
- res = conn.exec("VALUES ('fantasia')", [], 0)
1148
- out_string = res[0]['column1']
1149
- end
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1150
1247
  expect( out_string ).to eq( 'fantasia' )
1151
1248
  expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1152
1249
  end
1153
1250
 
1154
1251
  it "should return results in the same encoding as the client (utf-8)" do
1155
- out_string = nil
1156
- @conn.transaction do |conn|
1157
- conn.internal_encoding = 'utf-8'
1158
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1159
- out_string = res[0]['column1']
1160
- end
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1161
1255
  expect( out_string ).to eq( '世界線航跡蔵' )
1162
1256
  expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1163
1257
  end
1164
1258
 
1165
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1166
- out_string = nil
1167
- @conn.transaction do |conn|
1168
- conn.internal_encoding = 'EUC-JP'
1169
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1170
- res = conn.exec(stmt, [], 0)
1171
- out_string = res[0]['column1']
1172
- 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']
1173
1264
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1174
1265
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1175
1266
  end
1176
1267
 
1177
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1178
1269
  "changed since the results were fetched" do
1179
- out_string = nil
1180
- @conn.transaction do |conn|
1181
- conn.internal_encoding = 'EUC-JP'
1182
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1183
- res = conn.exec(stmt, [], 0)
1184
- conn.internal_encoding = 'utf-8'
1185
- out_string = res[0]['column1']
1186
- 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']
1187
1275
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1188
1276
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1189
1277
  end
@@ -1193,53 +1281,90 @@ describe PG::Connection do
1193
1281
  expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1194
1282
  end
1195
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
+
1196
1300
  it "uses the client encoding for escaped string" do
1197
- original = "Möhre to\0 escape".encode( "utf-16be" )
1301
+ original = "Möhre to 'scape".encode( "utf-16be" )
1198
1302
  @conn.set_client_encoding( "euc_jp" )
1199
1303
  escaped = @conn.escape( original )
1200
1304
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1201
- expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
1305
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1202
1306
  end
1203
1307
 
1204
- it "uses the client encoding for escaped literal", :postgresql_90 do
1205
- original = "Möhre to\0 escape".encode( "utf-16be" )
1308
+ it "uses the client encoding for escaped literal" do
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1206
1310
  @conn.set_client_encoding( "euc_jp" )
1207
1311
  escaped = @conn.escape_literal( original )
1208
1312
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1209
- expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
1313
+ expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
1210
1314
  end
1211
1315
 
1212
- it "uses the client encoding for escaped identifier", :postgresql_90 do
1213
- original = "Möhre to\0 escape".encode( "utf-16le" )
1316
+ it "uses the client encoding for escaped identifier" do
1317
+ original = "Möhre to 'scape".encode( "utf-16le" )
1214
1318
  @conn.set_client_encoding( "euc_jp" )
1215
1319
  escaped = @conn.escape_identifier( original )
1216
1320
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1217
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1321
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1218
1322
  end
1219
1323
 
1220
1324
  it "uses the client encoding for quote_ident" do
1221
- original = "Möhre to\0 escape".encode( "utf-16le" )
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1222
1326
  @conn.set_client_encoding( "euc_jp" )
1223
1327
  escaped = @conn.quote_ident( original )
1224
1328
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1225
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1226
1330
  end
1227
1331
 
1228
1332
  it "uses the previous string encoding for escaped string" do
1229
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1333
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1230
1334
  @conn.set_client_encoding( "euc_jp" )
1231
1335
  escaped = described_class.escape( original )
1232
1336
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1233
- expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
1337
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1234
1338
  end
1235
1339
 
1236
1340
  it "uses the previous string encoding for quote_ident" do
1237
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1238
1342
  @conn.set_client_encoding( "euc_jp" )
1239
1343
  escaped = described_class.quote_ident( original )
1240
1344
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1241
- expect( escaped.encode ).to eq( "\"Möhre to\"".encode(Encoding::ISO8859_1) )
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" )
1242
1366
  end
1367
+
1243
1368
  end
1244
1369
 
1245
1370
  describe "respect and convert character encoding of input strings" do
@@ -1253,22 +1378,11 @@ describe PG::Connection do
1253
1378
  expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1254
1379
  end
1255
1380
 
1256
- it "should convert query string and parameters to #async_exec" do
1257
- r = @conn.async_exec("VALUES( $1, $2, $1=$2, 'grün')".encode("cp936"),
1258
- ['grün'.encode('cp850'), 'grün'.encode('utf-16le')])
1259
- expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1260
- end
1261
-
1262
1381
  it "should convert query string to #exec" do
1263
1382
  r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1264
1383
  expect( r.values ).to eq( [['grün']] )
1265
1384
  end
1266
1385
 
1267
- it "should convert query string to #async_exec" do
1268
- r = @conn.async_exec("SELECT 'grün'".encode("utf-16le"))
1269
- expect( r.values ).to eq( [['grün']] )
1270
- end
1271
-
1272
1386
  it "should convert strings and parameters to #prepare and #exec_prepared" do
1273
1387
  @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1274
1388
  r = @conn.exec_prepared("weiß1".encode("utf-32le"),
@@ -1293,8 +1407,8 @@ describe PG::Connection do
1293
1407
  expect( @conn.get_last_result.values ).to eq( [['grün']] )
1294
1408
  end
1295
1409
 
1296
- it "should convert query string and parameters to #send_query" do
1297
- @conn.send_query("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
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"),
1298
1412
  ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1299
1413
  expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1300
1414
  end
@@ -1328,9 +1442,54 @@ describe PG::Connection do
1328
1442
  end
1329
1443
  end
1330
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/)
1488
+ end
1489
+
1331
1490
  it "can quote bigger strings with quote_ident" do
1332
1491
  original = "'01234567\"" * 100
1333
- escaped = described_class.quote_ident( original + "\0afterzero" )
1492
+ escaped = described_class.quote_ident( original )
1334
1493
  expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1335
1494
  end
1336
1495
 
@@ -1349,7 +1508,7 @@ describe PG::Connection do
1349
1508
 
1350
1509
  describe "Ruby 1.9.x default_internal encoding" do
1351
1510
 
1352
- 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
1353
1512
  @conn.transaction do |txn_conn|
1354
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1355
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1408,7 +1567,7 @@ describe PG::Connection do
1408
1567
  conn.finish if conn
1409
1568
  end
1410
1569
 
1411
- 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
1412
1571
  r = nil
1413
1572
  @conn.set_notice_receiver do |result|
1414
1573
  r = result
@@ -1423,7 +1582,7 @@ describe PG::Connection do
1423
1582
  @conn.set_notice_receiver
1424
1583
  end
1425
1584
 
1426
- it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1585
+ it "receives properly encoded messages in the notice callbacks" do
1427
1586
  [:receiver, :processor].each do |kind|
1428
1587
  notices = []
1429
1588
  @conn.internal_encoding = 'utf-8'
@@ -1451,9 +1610,8 @@ describe PG::Connection do
1451
1610
  end
1452
1611
  end
1453
1612
 
1454
- 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
1455
1614
  @conn.internal_encoding = 'utf-8'
1456
- @conn.exec( 'ROLLBACK' )
1457
1615
  @conn.exec( 'LISTEN "Möhre"' )
1458
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1459
1617
  event, pid, msg = nil
@@ -1464,13 +1622,13 @@ describe PG::Connection do
1464
1622
 
1465
1623
  expect( event ).to eq( "Möhre" )
1466
1624
  expect( event.encoding ).to eq( Encoding::UTF_8 )
1625
+ expect( pid ).to be_a_kind_of(Integer)
1467
1626
  expect( msg ).to eq( '世界線航跡蔵' )
1468
1627
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1469
1628
  end
1470
1629
 
1471
- it "returns properly encoded text from notifies", :postgresql_90 do
1630
+ it "returns properly encoded text from notifies", :without_transaction do
1472
1631
  @conn.internal_encoding = 'utf-8'
1473
- @conn.exec( 'ROLLBACK' )
1474
1632
  @conn.exec( 'LISTEN "Möhre"' )
1475
1633
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1476
1634
  @conn.exec( 'UNLISTEN "Möhre"' )
@@ -1484,7 +1642,7 @@ describe PG::Connection do
1484
1642
  end
1485
1643
  end
1486
1644
 
1487
- context "OS thread support", :ruby_19 do
1645
+ context "OS thread support" do
1488
1646
  it "Connection#exec shouldn't block a second thread" do
1489
1647
  t = Thread.new do
1490
1648
  @conn.exec( "select pg_sleep(1)" )
@@ -1524,9 +1682,14 @@ describe PG::Connection do
1524
1682
  end
1525
1683
 
1526
1684
  it "shouldn't type map params unless requested" do
1527
- expect{
1528
- @conn.exec_params( "SELECT $1", [5] )
1529
- }.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
1530
1693
  end
1531
1694
 
1532
1695
  it "should raise an error on invalid encoder to put_copy_data" do
@@ -1540,12 +1703,12 @@ describe PG::Connection do
1540
1703
  row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1541
1704
 
1542
1705
  @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1543
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1706
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1544
1707
  @conn.put_copy_data [1], row_encoder
1545
1708
  @conn.put_copy_data ["2"], row_encoder
1546
1709
  end
1547
1710
 
1548
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1711
+ @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1549
1712
  @conn.put_copy_data [3]
1550
1713
  @conn.put_copy_data ["4"]
1551
1714
  end
@@ -1595,7 +1758,7 @@ describe PG::Connection do
1595
1758
 
1596
1759
  it "can process #copy_data input queries with row encoder and respects character encoding" do
1597
1760
  @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1598
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1761
+ @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1599
1762
  @conn2.put_copy_data [1]
1600
1763
  @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1601
1764
  end
@@ -1646,7 +1809,7 @@ describe PG::Connection do
1646
1809
  it "can process #copy_data output with row decoder and respects character encoding" do
1647
1810
  @conn2.internal_encoding = Encoding::ISO8859_1
1648
1811
  rows = []
1649
- res2 = @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1812
+ @conn2.copy_data( "COPY (VALUES('1'), ('Möhre')) TO STDOUT".encode("utf-16le") ) do |res|
1650
1813
  while row=@conn2.get_copy_data
1651
1814
  rows << row
1652
1815
  end
@@ -1673,4 +1836,114 @@ describe PG::Connection do
1673
1836
  end
1674
1837
  end
1675
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
1676
1949
  end