pg 1.0.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 (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +156 -0
  6. data/Manifest.txt +8 -2
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +55 -9
  10. data/Rakefile +9 -7
  11. data/Rakefile.cross +58 -57
  12. data/ext/errorcodes.def +68 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +19 -2
  15. data/ext/extconf.rb +7 -5
  16. data/ext/pg.c +141 -98
  17. data/ext/pg.h +64 -21
  18. data/ext/pg_binary_decoder.c +82 -15
  19. data/ext/pg_binary_encoder.c +13 -12
  20. data/ext/pg_coder.c +73 -12
  21. data/ext/pg_connection.c +625 -346
  22. data/ext/pg_copy_coder.c +16 -8
  23. data/ext/pg_record_coder.c +491 -0
  24. data/ext/pg_result.c +571 -191
  25. data/ext/pg_text_decoder.c +606 -40
  26. data/ext/pg_text_encoder.c +185 -54
  27. data/ext/pg_tuple.c +549 -0
  28. data/ext/pg_type_map.c +1 -1
  29. data/ext/pg_type_map_all_strings.c +4 -4
  30. data/ext/pg_type_map_by_class.c +9 -4
  31. data/ext/pg_type_map_by_column.c +7 -6
  32. data/ext/pg_type_map_by_mri_type.c +1 -1
  33. data/ext/pg_type_map_by_oid.c +3 -2
  34. data/ext/pg_type_map_in_ruby.c +1 -1
  35. data/ext/{util.c → pg_util.c} +10 -10
  36. data/ext/{util.h → pg_util.h} +2 -2
  37. data/lib/pg.rb +8 -6
  38. data/lib/pg/basic_type_mapping.rb +121 -25
  39. data/lib/pg/binary_decoder.rb +23 -0
  40. data/lib/pg/coder.rb +23 -2
  41. data/lib/pg/connection.rb +22 -3
  42. data/lib/pg/constants.rb +2 -1
  43. data/lib/pg/exceptions.rb +2 -1
  44. data/lib/pg/result.rb +14 -2
  45. data/lib/pg/text_decoder.rb +21 -26
  46. data/lib/pg/text_encoder.rb +32 -8
  47. data/lib/pg/tuple.rb +30 -0
  48. data/lib/pg/type_map_by_column.rb +3 -2
  49. data/spec/helpers.rb +52 -20
  50. data/spec/pg/basic_type_mapping_spec.rb +362 -37
  51. data/spec/pg/connection_spec.rb +376 -146
  52. data/spec/pg/connection_sync_spec.rb +41 -0
  53. data/spec/pg/result_spec.rb +240 -15
  54. data/spec/pg/tuple_spec.rb +333 -0
  55. data/spec/pg/type_map_by_class_spec.rb +2 -2
  56. data/spec/pg/type_map_by_column_spec.rb +6 -2
  57. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  58. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  59. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  60. data/spec/pg/type_map_spec.rb +1 -1
  61. data/spec/pg/type_spec.rb +363 -17
  62. data/spec/pg_spec.rb +1 -1
  63. metadata +47 -47
  64. metadata.gz.sig +0 -0
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  #encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -173,56 +173,94 @@ describe PG::Connection do
173
173
  end
174
174
  end
175
175
 
176
- it "can connect asynchronously", :socket_io do
176
+ it "can connect asynchronously" do
177
177
  tmpconn = described_class.connect_start( @conninfo )
178
178
  expect( tmpconn ).to be_a( described_class )
179
- socket = tmpconn.socket_io
180
- status = tmpconn.connect_poll
181
-
182
- while status != PG::PGRES_POLLING_OK
183
- if status == PG::PGRES_POLLING_READING
184
- select( [socket], [], [], 5.0 ) or
185
- raise "Asynchronous connection timed out!"
186
-
187
- elsif status == PG::PGRES_POLLING_WRITING
188
- select( [], [socket], [], 5.0 ) or
189
- raise "Asynchronous connection timed out!"
190
- end
191
- status = tmpconn.connect_poll
192
- end
193
179
 
180
+ wait_for_polling_ok(tmpconn)
194
181
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
195
182
  tmpconn.finish
196
183
  end
197
184
 
198
- 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
199
186
  conn = nil
200
187
 
201
188
  described_class.connect_start(@conninfo) do |tmpconn|
202
189
  expect( tmpconn ).to be_a( described_class )
203
190
  conn = tmpconn
204
- socket = tmpconn.socket_io
205
- status = tmpconn.connect_poll
206
-
207
- while status != PG::PGRES_POLLING_OK
208
- if status == PG::PGRES_POLLING_READING
209
- if(not select([socket],[],[],5.0))
210
- raise "Asynchronous connection timed out!"
211
- end
212
- elsif(status == PG::PGRES_POLLING_WRITING)
213
- if(not select([],[socket],[],5.0))
214
- raise "Asynchronous connection timed out!"
215
- end
216
- end
217
- status = tmpconn.connect_poll
218
- end
219
191
 
192
+ wait_for_polling_ok(tmpconn)
220
193
  expect( tmpconn.status ).to eq( PG::CONNECTION_OK )
221
194
  end
222
195
 
223
196
  expect( conn ).to be_finished()
224
197
  end
225
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
+
226
264
  it "raises proper error when sending fails" do
227
265
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
228
266
  expect{ conn.exec 'SELECT 1' }.to raise_error(PG::UnableToSend, /no connection/)
@@ -241,7 +279,7 @@ describe PG::Connection do
241
279
  expect( @conn.db ).to eq( "test" )
242
280
  expect( @conn.user ).to be_a_kind_of( String )
243
281
  expect( @conn.pass ).to eq( "" )
244
- expect( @conn.port ).to eq( 54321 )
282
+ expect( @conn.port ).to eq( @port )
245
283
  expect( @conn.tty ).to eq( "" )
246
284
  expect( @conn.options ).to eq( "" )
247
285
  end
@@ -250,7 +288,20 @@ describe PG::Connection do
250
288
  expect( @conn.host ).to eq( "localhost" )
251
289
  end
252
290
 
253
- 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
+ %{
254
305
  To backend> Msg Q
255
306
  To backend> "SELECT 1 AS one"
256
307
  To backend> Msg complete, length 21
@@ -278,6 +329,7 @@ describe PG::Connection do
278
329
  From backend (#4)> 5
279
330
  From backend> T
280
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
281
333
 
282
334
  it "trace and untrace client-server communication", :unix do
283
335
  # be careful to explicitly close files so that the
@@ -288,23 +340,20 @@ describe PG::Connection do
288
340
  @conn.trace( trace_io )
289
341
  trace_io.close
290
342
 
291
- res = @conn.exec("SELECT 1 AS one")
343
+ @conn.exec("SELECT 1 AS one")
292
344
  @conn.untrace
293
345
 
294
- res = @conn.exec("SELECT 2 AS two")
346
+ @conn.exec("SELECT 2 AS two")
295
347
 
296
348
  trace_data = trace_file.read
297
349
 
298
- expected_trace_output = EXPECTED_TRACE_OUTPUT.dup
299
- # For PostgreSQL < 9.0, the output will be different:
300
- # -From backend (#4)> 13
301
- # -From backend> "SELECT 1"
302
- # +From backend (#4)> 11
303
- # +From backend> "SELECT"
304
- if @conn.server_version < 90000
305
- expected_trace_output.sub!( /From backend \(#4\)> 13/, 'From backend (#4)> 11' )
306
- expected_trace_output.sub!( /From backend> "SELECT 1"/, 'From backend> "SELECT"' )
307
- 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' )
308
357
 
309
358
  expect( trace_data ).to eq( expected_trace_output )
310
359
  end
@@ -321,8 +370,6 @@ describe PG::Connection do
321
370
  end
322
371
 
323
372
  it "can stop a thread that runs a blocking query with async_exec" do
324
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
325
-
326
373
  start = Time.now
327
374
  t = Thread.new do
328
375
  @conn.async_exec( 'select pg_sleep(10)' )
@@ -336,24 +383,16 @@ describe PG::Connection do
336
383
 
337
384
  it "should work together with signal handlers", :unix do
338
385
  signal_received = false
339
- trap 'USR1' do
386
+ trap 'USR2' do
340
387
  signal_received = true
341
388
  end
342
389
 
343
390
  Thread.new do
344
391
  sleep 0.1
345
- Process.kill("USR1", Process.pid)
392
+ Process.kill("USR2", Process.pid)
346
393
  end
347
394
  @conn.exec("select pg_sleep(0.3)")
348
395
  expect( signal_received ).to be_truthy
349
-
350
- signal_received = false
351
- Thread.new do
352
- sleep 0.1
353
- Process.kill("USR1", Process.pid)
354
- end
355
- @conn.async_exec("select pg_sleep(0.3)")
356
- expect( signal_received ).to be_truthy
357
396
  end
358
397
 
359
398
 
@@ -401,22 +440,11 @@ describe PG::Connection do
401
440
  end
402
441
  end
403
442
 
404
-
405
- it "supports parameters passed to #exec (backward compatibility)" do
406
- @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
407
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
408
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
409
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Dorothy', 4] )
410
-
411
- res = @conn.exec( "SELECT name FROM students WHERE age >= $1", [6] )
412
- expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
413
- end
414
-
415
443
  it "supports explicitly calling #exec_params" do
416
444
  @conn.exec( "CREATE TABLE students ( name TEXT, age INTEGER )" )
417
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Wally', 8] )
418
- @conn.exec( "INSERT INTO students VALUES( $1, $2 )", ['Sally', 6] )
419
- @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] )
420
448
 
421
449
  res = @conn.exec_params( "SELECT name FROM students WHERE age >= $1", [6] )
422
450
  expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
@@ -547,7 +575,7 @@ describe PG::Connection do
547
575
  expect( @conn.wait_for_notify( 1 ) ).to be_nil
548
576
  expect( notices.first ).to_not be_nil
549
577
  et = Time.now
550
- expect( (et - notices.first[1]) ).to be >= 0.4
578
+ expect( (et - notices.first[1]) ).to be >= 0.3
551
579
  expect( (et - st) ).to be >= 0.9
552
580
  expect( (et - st) ).to be < 1.4
553
581
  end
@@ -651,7 +679,7 @@ describe PG::Connection do
651
679
  @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
652
680
  @conn.put_copy_data "xyz\n"
653
681
  end
654
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
682
+ }.to raise_error(PG::Error, /invalid input syntax for .*integer/)
655
683
  end
656
684
  expect( @conn ).to still_be_usable
657
685
  end
@@ -741,7 +769,7 @@ describe PG::Connection do
741
769
  it "can return the default connection options as a Hash" do
742
770
  expect( described_class.conndefaults_hash ).to be_a( Hash )
743
771
  expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
744
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
772
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
745
773
  expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
746
774
  end
747
775
 
@@ -874,7 +902,7 @@ describe PG::Connection do
874
902
  end
875
903
 
876
904
 
877
- it "can connect asynchronously", :socket_io do
905
+ it "handles server close while asynchronous connect" do
878
906
  serv = TCPServer.new( '127.0.0.1', 54320 )
879
907
  conn = described_class.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
880
908
  expect( [PG::PGRES_POLLING_WRITING, PG::CONNECTION_OK] ).to include conn.connect_poll
@@ -886,11 +914,29 @@ describe PG::Connection do
886
914
  expect( conn.connect_poll ).to eq( PG::PGRES_POLLING_FAILED )
887
915
  end
888
916
 
889
- 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
890
930
 
891
- it "calls the block if one is provided to #async_exec" do
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
936
+
937
+ it "calls the block if one is provided to #exec" do
892
938
  result = nil
893
- @conn.async_exec( "select 47 as one" ) do |pg_res|
939
+ @conn.exec( "select 47 as one" ) do |pg_res|
894
940
  result = pg_res[0]
895
941
  end
896
942
  expect( result ).to eq( { 'one' => '47' } )
@@ -903,7 +949,21 @@ describe PG::Connection do
903
949
  expect { conn.finish }.to raise_error( PG::ConnectionBad, /connection is closed/i )
904
950
  end
905
951
 
906
- 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
907
967
  conn = PG.connect( @conninfo )
908
968
  io = conn.socket_io
909
969
  conn.finish
@@ -911,7 +971,7 @@ describe PG::Connection do
911
971
  expect { conn.socket_io }.to raise_error( PG::ConnectionBad, /connection is closed/i )
912
972
  end
913
973
 
914
- 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
915
975
  conn = PG.connect( @conninfo )
916
976
  io = conn.socket_io
917
977
  conn.reset
@@ -1099,8 +1159,16 @@ describe PG::Connection do
1099
1159
  expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1100
1160
  end
1101
1161
 
1102
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
1103
1163
  ping = described_class.ping('localhost', 'localhost', nil, nil, :test, nil, nil)
1164
+ expect( ping ).to_not eq( PG::PQPING_OK )
1165
+ end
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)
1104
1172
  expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1105
1173
  end
1106
1174
 
@@ -1169,53 +1237,41 @@ describe PG::Connection do
1169
1237
 
1170
1238
  end
1171
1239
 
1172
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
1173
1241
 
1174
1242
  describe "rubyforge #22925: m17n support" do
1175
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
1176
- out_string = nil
1177
- @conn.transaction do |conn|
1178
- conn.internal_encoding = 'iso8859-1'
1179
- res = conn.exec("VALUES ('fantasia')", [], 0)
1180
- out_string = res[0]['column1']
1181
- end
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1182
1247
  expect( out_string ).to eq( 'fantasia' )
1183
1248
  expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1184
1249
  end
1185
1250
 
1186
1251
  it "should return results in the same encoding as the client (utf-8)" do
1187
- out_string = nil
1188
- @conn.transaction do |conn|
1189
- conn.internal_encoding = 'utf-8'
1190
- res = conn.exec("VALUES ('世界線航跡蔵')", [], 0)
1191
- out_string = res[0]['column1']
1192
- end
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1193
1255
  expect( out_string ).to eq( '世界線航跡蔵' )
1194
1256
  expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1195
1257
  end
1196
1258
 
1197
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1198
- out_string = nil
1199
- @conn.transaction do |conn|
1200
- conn.internal_encoding = 'EUC-JP'
1201
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1202
- res = conn.exec(stmt, [], 0)
1203
- out_string = res[0]['column1']
1204
- 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']
1205
1264
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1206
1265
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1207
1266
  end
1208
1267
 
1209
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1210
1269
  "changed since the results were fetched" do
1211
- out_string = nil
1212
- @conn.transaction do |conn|
1213
- conn.internal_encoding = 'EUC-JP'
1214
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1215
- res = conn.exec(stmt, [], 0)
1216
- conn.internal_encoding = 'utf-8'
1217
- out_string = res[0]['column1']
1218
- 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']
1219
1275
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1220
1276
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1221
1277
  end
@@ -1225,52 +1281,68 @@ describe PG::Connection do
1225
1281
  expect( @conn.internal_encoding ).to eq( Encoding::ASCII_8BIT )
1226
1282
  end
1227
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
+
1228
1300
  it "uses the client encoding for escaped string" do
1229
- original = "Möhre to\0 escape".encode( "utf-16be" )
1301
+ original = "Möhre to 'scape".encode( "utf-16be" )
1230
1302
  @conn.set_client_encoding( "euc_jp" )
1231
1303
  escaped = @conn.escape( original )
1232
1304
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1233
- expect( escaped ).to eq( "Möhre to".encode(Encoding::EUC_JP) )
1305
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::EUC_JP) )
1234
1306
  end
1235
1307
 
1236
1308
  it "uses the client encoding for escaped literal" do
1237
- original = "Möhre to\0 escape".encode( "utf-16be" )
1309
+ original = "Möhre to 'scape".encode( "utf-16be" )
1238
1310
  @conn.set_client_encoding( "euc_jp" )
1239
1311
  escaped = @conn.escape_literal( original )
1240
1312
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1241
- expect( escaped ).to eq( "'Möhre to'".encode(Encoding::EUC_JP) )
1313
+ expect( escaped ).to eq( "'Möhre to ''scape'".encode(Encoding::EUC_JP) )
1242
1314
  end
1243
1315
 
1244
1316
  it "uses the client encoding for escaped identifier" do
1245
- original = "Möhre to\0 escape".encode( "utf-16le" )
1317
+ original = "Möhre to 'scape".encode( "utf-16le" )
1246
1318
  @conn.set_client_encoding( "euc_jp" )
1247
1319
  escaped = @conn.escape_identifier( original )
1248
1320
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1249
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1321
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1250
1322
  end
1251
1323
 
1252
1324
  it "uses the client encoding for quote_ident" do
1253
- original = "Möhre to\0 escape".encode( "utf-16le" )
1325
+ original = "Möhre to 'scape".encode( "utf-16le" )
1254
1326
  @conn.set_client_encoding( "euc_jp" )
1255
1327
  escaped = @conn.quote_ident( original )
1256
1328
  expect( escaped.encoding ).to eq( Encoding::EUC_JP )
1257
- expect( escaped ).to eq( "\"Möhre to\"".encode(Encoding::EUC_JP) )
1329
+ expect( escaped ).to eq( "\"Möhre to 'scape\"".encode(Encoding::EUC_JP) )
1258
1330
  end
1259
1331
 
1260
1332
  it "uses the previous string encoding for escaped string" do
1261
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1333
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1262
1334
  @conn.set_client_encoding( "euc_jp" )
1263
1335
  escaped = described_class.escape( original )
1264
1336
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1265
- expect( escaped ).to eq( "Möhre to".encode(Encoding::ISO8859_1) )
1337
+ expect( escaped ).to eq( "Möhre to ''scape".encode(Encoding::ISO8859_1) )
1266
1338
  end
1267
1339
 
1268
1340
  it "uses the previous string encoding for quote_ident" do
1269
- original = "Möhre to\0 escape".encode( "iso-8859-1" )
1341
+ original = "Möhre to 'scape".encode( "iso-8859-1" )
1270
1342
  @conn.set_client_encoding( "euc_jp" )
1271
1343
  escaped = described_class.quote_ident( original )
1272
1344
  expect( escaped.encoding ).to eq( Encoding::ISO8859_1 )
1273
- 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) )
1274
1346
  end
1275
1347
 
1276
1348
  it "raises appropriate error if set_client_encoding is called with invalid arguments" do
@@ -1278,6 +1350,21 @@ describe PG::Connection do
1278
1350
  expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1279
1351
  expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1280
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
+
1281
1368
  end
1282
1369
 
1283
1370
  describe "respect and convert character encoding of input strings" do
@@ -1291,22 +1378,11 @@ describe PG::Connection do
1291
1378
  expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1292
1379
  end
1293
1380
 
1294
- it "should convert query string and parameters to #async_exec" do
1295
- r = @conn.async_exec("VALUES( $1, $2, $1=$2, 'grün')".encode("cp936"),
1296
- ['grün'.encode('cp850'), 'grün'.encode('utf-16le')])
1297
- expect( r.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1298
- end
1299
-
1300
1381
  it "should convert query string to #exec" do
1301
1382
  r = @conn.exec("SELECT 'grün'".encode("utf-16be"))
1302
1383
  expect( r.values ).to eq( [['grün']] )
1303
1384
  end
1304
1385
 
1305
- it "should convert query string to #async_exec" do
1306
- r = @conn.async_exec("SELECT 'grün'".encode("utf-16le"))
1307
- expect( r.values ).to eq( [['grün']] )
1308
- end
1309
-
1310
1386
  it "should convert strings and parameters to #prepare and #exec_prepared" do
1311
1387
  @conn.prepare("weiß1".encode("utf-16be"), "VALUES( $1, $2, $1=$2, 'grün')".encode("cp850"))
1312
1388
  r = @conn.exec_prepared("weiß1".encode("utf-32le"),
@@ -1331,8 +1407,8 @@ describe PG::Connection do
1331
1407
  expect( @conn.get_last_result.values ).to eq( [['grün']] )
1332
1408
  end
1333
1409
 
1334
- it "should convert query string and parameters to #send_query" do
1335
- @conn.send_query("VALUES( $1, $2, $1=$2, 'grün')".encode("utf-16le"),
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"),
1336
1412
  ['grün'.encode('utf-32be'), 'grün'.encode('iso-8859-1')])
1337
1413
  expect( @conn.get_last_result.values ).to eq( [['grün', 'grün', 't', 'grün']] )
1338
1414
  end
@@ -1366,9 +1442,54 @@ describe PG::Connection do
1366
1442
  end
1367
1443
  end
1368
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
+
1369
1490
  it "can quote bigger strings with quote_ident" do
1370
1491
  original = "'01234567\"" * 100
1371
- escaped = described_class.quote_ident( original + "\0afterzero" )
1492
+ escaped = described_class.quote_ident( original )
1372
1493
  expect( escaped ).to eq( "\"" + original.gsub("\"", "\"\"") + "\"" )
1373
1494
  end
1374
1495
 
@@ -1387,7 +1508,7 @@ describe PG::Connection do
1387
1508
 
1388
1509
  describe "Ruby 1.9.x default_internal encoding" do
1389
1510
 
1390
- 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
1391
1512
  @conn.transaction do |txn_conn|
1392
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1393
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1489,9 +1610,8 @@ describe PG::Connection do
1489
1610
  end
1490
1611
  end
1491
1612
 
1492
- it "receives properly encoded text from wait_for_notify" do
1613
+ it "receives properly encoded text from wait_for_notify", :without_transaction do
1493
1614
  @conn.internal_encoding = 'utf-8'
1494
- @conn.exec( 'ROLLBACK' )
1495
1615
  @conn.exec( 'LISTEN "Möhre"' )
1496
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1497
1617
  event, pid, msg = nil
@@ -1502,13 +1622,13 @@ describe PG::Connection do
1502
1622
 
1503
1623
  expect( event ).to eq( "Möhre" )
1504
1624
  expect( event.encoding ).to eq( Encoding::UTF_8 )
1625
+ expect( pid ).to be_a_kind_of(Integer)
1505
1626
  expect( msg ).to eq( '世界線航跡蔵' )
1506
1627
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1507
1628
  end
1508
1629
 
1509
- it "returns properly encoded text from notifies" do
1630
+ it "returns properly encoded text from notifies", :without_transaction do
1510
1631
  @conn.internal_encoding = 'utf-8'
1511
- @conn.exec( 'ROLLBACK' )
1512
1632
  @conn.exec( 'LISTEN "Möhre"' )
1513
1633
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1514
1634
  @conn.exec( 'UNLISTEN "Möhre"' )
@@ -1522,7 +1642,7 @@ describe PG::Connection do
1522
1642
  end
1523
1643
  end
1524
1644
 
1525
- context "OS thread support", :ruby_19 do
1645
+ context "OS thread support" do
1526
1646
  it "Connection#exec shouldn't block a second thread" do
1527
1647
  t = Thread.new do
1528
1648
  @conn.exec( "select pg_sleep(1)" )
@@ -1583,12 +1703,12 @@ describe PG::Connection do
1583
1703
  row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1584
1704
 
1585
1705
  @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1586
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1706
+ @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1587
1707
  @conn.put_copy_data [1], row_encoder
1588
1708
  @conn.put_copy_data ["2"], row_encoder
1589
1709
  end
1590
1710
 
1591
- res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1711
+ @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1592
1712
  @conn.put_copy_data [3]
1593
1713
  @conn.put_copy_data ["4"]
1594
1714
  end
@@ -1638,7 +1758,7 @@ describe PG::Connection do
1638
1758
 
1639
1759
  it "can process #copy_data input queries with row encoder and respects character encoding" do
1640
1760
  @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1641
- res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1761
+ @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1642
1762
  @conn2.put_copy_data [1]
1643
1763
  @conn2.put_copy_data ["Möhre".encode("utf-16le")]
1644
1764
  end
@@ -1689,7 +1809,7 @@ describe PG::Connection do
1689
1809
  it "can process #copy_data output with row decoder and respects character encoding" do
1690
1810
  @conn2.internal_encoding = Encoding::ISO8859_1
1691
1811
  rows = []
1692
- 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|
1693
1813
  while row=@conn2.get_copy_data
1694
1814
  rows << row
1695
1815
  end
@@ -1716,4 +1836,114 @@ describe PG::Connection do
1716
1836
  end
1717
1837
  end
1718
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
1719
1949
  end