pg 0.18.0.pre20140820094244 → 0.18.0.pre20141017155815

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1573 -2
  5. data/History.rdoc +3 -11
  6. data/Manifest.txt +24 -0
  7. data/README.rdoc +51 -4
  8. data/Rakefile +20 -14
  9. data/Rakefile.cross +39 -32
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +75 -21
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +454 -0
  16. data/ext/pg_connection.c +815 -518
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +258 -103
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +608 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +121 -0
  27. data/ext/util.h +63 -0
  28. data/lib/pg.rb +11 -1
  29. data/lib/pg/basic_type_mapping.rb +377 -0
  30. data/lib/pg/coder.rb +74 -0
  31. data/lib/pg/connection.rb +38 -5
  32. data/lib/pg/result.rb +13 -3
  33. data/lib/pg/text_decoder.rb +42 -0
  34. data/lib/pg/text_encoder.rb +27 -0
  35. data/lib/pg/type_map_by_column.rb +15 -0
  36. data/spec/helpers.rb +9 -1
  37. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  38. data/spec/pg/connection_spec.rb +232 -13
  39. data/spec/pg/result_spec.rb +52 -0
  40. data/spec/pg/type_map_by_column_spec.rb +135 -0
  41. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  42. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  43. data/spec/pg/type_map_spec.rb +39 -0
  44. data/spec/pg/type_spec.rb +620 -0
  45. metadata +40 -4
  46. metadata.gz.sig +0 -0
@@ -229,6 +229,8 @@ describe PG::Connection do
229
229
  end
230
230
 
231
231
  it "can stop a thread that runs a blocking query with async_exec" do
232
+ pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
233
+
232
234
  start = Time.now
233
235
  t = Thread.new do
234
236
  @conn.async_exec( 'select pg_sleep(10)' )
@@ -240,7 +242,7 @@ describe PG::Connection do
240
242
  expect( (Time.now - start) ).to be < 10
241
243
  end
242
244
 
243
- it "should work together with signal handlers" do
245
+ it "should work together with signal handlers", :unix do
244
246
  signal_received = false
245
247
  trap 'USR1' do
246
248
  signal_received = true
@@ -271,15 +273,19 @@ describe PG::Connection do
271
273
  res = nil
272
274
  @conn.exec( "CREATE TABLE pie ( flavor TEXT )" )
273
275
 
274
- expect {
275
- res = @conn.transaction do
276
- @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
277
- raise "Oh noes! All pie is gone!"
278
- end
279
- }.to raise_exception( RuntimeError, /all pie is gone/i )
276
+ begin
277
+ expect {
278
+ res = @conn.transaction do
279
+ @conn.exec( "INSERT INTO pie VALUES ('rhubarb'), ('cherry'), ('schizophrenia')" )
280
+ raise "Oh noes! All pie is gone!"
281
+ end
282
+ }.to raise_exception( RuntimeError, /all pie is gone/i )
280
283
 
281
- res = @conn.exec( "SELECT * FROM pie" )
282
- expect( res.ntuples ).to eq( 0 )
284
+ res = @conn.exec( "SELECT * FROM pie" )
285
+ expect( res.ntuples ).to eq( 0 )
286
+ ensure
287
+ @conn.exec( "DROP TABLE pie" )
288
+ end
283
289
  end
284
290
 
285
291
  it "returns the block result from Connection#transaction" do
@@ -324,6 +330,29 @@ describe PG::Connection do
324
330
  expect( res.values ).to eq( [ ['Wally'], ['Sally'] ] )
325
331
  end
326
332
 
333
+ it "supports hash form parameters for #exec_params" do
334
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
335
+ hash_param_nil = { value: nil, type: 17, format: 1 }
336
+ res = @conn.exec_params( "SELECT $1, $2",
337
+ [ hash_param_bin, hash_param_nil ] )
338
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
339
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
340
+ end
341
+
342
+ it "should work with arbitrary number of params" do
343
+ begin
344
+ 3.step( 12, 0.2 ) do |exp|
345
+ num_params = (2 ** exp).to_i
346
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
347
+ params = num_params.times.to_a
348
+ res = @conn.exec_params( "SELECT #{sql}", params )
349
+ expect( res.nfields ).to eq( num_params )
350
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
351
+ end
352
+ rescue PG::ProgramLimitExceeded
353
+ # Stop silently if the server complains about too many params
354
+ end
355
+ end
327
356
 
328
357
  it "can wait for NOTIFY events" do
329
358
  @conn.exec( 'ROLLBACK' )
@@ -478,7 +507,7 @@ describe PG::Connection do
478
507
  expect( @conn ).to still_be_usable
479
508
  end
480
509
 
481
- it "can handle server errors in #copy_data for output" do
510
+ it "can handle server errors in #copy_data for output", :postgresql_90 do
482
511
  @conn.exec "ROLLBACK"
483
512
  @conn.transaction do
484
513
  @conn.exec( "CREATE FUNCTION errfunc() RETURNS int AS $$ BEGIN RAISE 'test-error'; END; $$ LANGUAGE plpgsql;" )
@@ -590,22 +619,35 @@ describe PG::Connection do
590
619
  expect( described_class.encrypt_password("postgres", "postgres") ).to match( /\S+/ )
591
620
  end
592
621
 
622
+ it "can return the default connection options" do
623
+ expect( described_class.conndefaults ).to be_a( Array )
624
+ expect( described_class.conndefaults ).to all( be_a(Hash) )
625
+ expect( described_class.conndefaults[0] ).to include( :keyword, :label, :dispchar, :dispsize )
626
+ expect( @conn.conndefaults ).to eq( described_class.conndefaults )
627
+ end
628
+
629
+ it "can return the default connection options as a Hash" do
630
+ expect( described_class.conndefaults_hash ).to be_a( Hash )
631
+ expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
632
+ expect( described_class.conndefaults_hash[:port] ).to eq( '54321' )
633
+ expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
634
+ end
593
635
 
594
- it "can return the connection's connection options" do
636
+ it "can return the connection's connection options", :postgresql_93 do
595
637
  expect( @conn.conninfo ).to be_a( Array )
596
638
  expect( @conn.conninfo ).to all( be_a(Hash) )
597
639
  expect( @conn.conninfo[0] ).to include( :keyword, :label, :dispchar, :dispsize )
598
640
  end
599
641
 
600
642
 
601
- it "can return the connection's connection options as a Hash" do
643
+ it "can return the connection's connection options as a Hash", :postgresql_93 do
602
644
  expect( @conn.conninfo_hash ).to be_a( Hash )
603
645
  expect( @conn.conninfo_hash ).to include( :user, :password, :connect_timeout, :dbname, :host )
604
646
  expect( @conn.conninfo_hash[:dbname] ).to eq( 'test' )
605
647
  end
606
648
 
607
649
 
608
- it "honors the connect_timeout connection parameter" do
650
+ it "honors the connect_timeout connection parameter", :postgresql_93 do
609
651
  conn = PG.connect( port: @port, dbname: 'test', connect_timeout: 11 )
610
652
  begin
611
653
  expect( conn.conninfo_hash[:connect_timeout] ).to eq( "11" )
@@ -1097,6 +1139,7 @@ describe PG::Connection do
1097
1139
  res = conn.exec( "SELECT foo FROM defaultinternaltest" )
1098
1140
  expect( res[0]['foo'].encoding ).to eq( Encoding::UTF_8 )
1099
1141
  ensure
1142
+ conn.exec( "DROP TABLE defaultinternaltest" )
1100
1143
  conn.finish if conn
1101
1144
  Encoding.default_internal = prev_encoding
1102
1145
  end
@@ -1139,6 +1182,21 @@ describe PG::Connection do
1139
1182
  conn.finish if conn
1140
1183
  end
1141
1184
 
1185
+ it "handles clearing result in or after set_notice_receiver", :postgresql_90 do
1186
+ r = nil
1187
+ @conn.set_notice_receiver do |result|
1188
+ r = result
1189
+ expect( r.cleared? ).to eq(false)
1190
+ end
1191
+ @conn.exec "do $$ BEGIN RAISE NOTICE 'foo'; END; $$ LANGUAGE plpgsql;"
1192
+ sleep 0.2
1193
+ expect( r ).to be_a( PG::Result )
1194
+ expect( r.cleared? ).to eq(true)
1195
+ expect( r.autoclear? ).to eq(true)
1196
+ r.clear
1197
+ @conn.set_notice_receiver
1198
+ end
1199
+
1142
1200
  it "receives properly encoded messages in the notice callbacks", :postgresql_90 do
1143
1201
  [:receiver, :processor].each do |kind|
1144
1202
  notices = []
@@ -1226,4 +1284,165 @@ describe PG::Connection do
1226
1284
  t.join
1227
1285
  end
1228
1286
  end
1287
+
1288
+ describe "type casting" do
1289
+ it "should raise an error on invalid param mapping" do
1290
+ expect{
1291
+ @conn.exec_params( "SELECT 1", [], nil, :invalid )
1292
+ }.to raise_error(TypeError)
1293
+ end
1294
+
1295
+ it "should return nil if no type mapping is set" do
1296
+ expect( @conn.type_map_for_queries ).to be_nil
1297
+ expect( @conn.type_map_for_results ).to be_nil
1298
+ end
1299
+
1300
+ it "shouldn't type map params unless requested" do
1301
+ expect{
1302
+ @conn.exec_params( "SELECT $1", [5] )
1303
+ }.to raise_error(PG::IndeterminateDatatype)
1304
+ end
1305
+
1306
+ it "should raise an error on invalid encoder to put_copy_data" do
1307
+ expect{
1308
+ @conn.put_copy_data [1], :invalid
1309
+ }.to raise_error(TypeError)
1310
+ end
1311
+
1312
+ it "can type cast parameters to put_copy_data with explicit encoder" do
1313
+ tm = PG::TypeMapByColumn.new [nil]
1314
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1315
+
1316
+ @conn.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1317
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1318
+ @conn.put_copy_data [1], row_encoder
1319
+ @conn.put_copy_data ["2"], row_encoder
1320
+ end
1321
+
1322
+ res2 = @conn.copy_data( "COPY copytable FROM STDOUT", row_encoder ) do |res|
1323
+ @conn.put_copy_data [3]
1324
+ @conn.put_copy_data ["4"]
1325
+ end
1326
+
1327
+ res = @conn.exec( "SELECT * FROM copytable ORDER BY col1" )
1328
+ expect( res.values ).to eq( [["1"], ["2"], ["3"], ["4"]] )
1329
+ end
1330
+
1331
+ context "with default query type map" do
1332
+ before :each do
1333
+ @conn2 = described_class.new(@conninfo)
1334
+ tm = PG::TypeMapByMriType.new
1335
+ tm['T_FIXNUM'] = PG::TextEncoder::Integer.new oid: 20
1336
+ @conn2.type_map_for_queries = tm
1337
+
1338
+ row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
1339
+ @conn2.encoder_for_put_copy_data = row_encoder
1340
+ end
1341
+ after :each do
1342
+ @conn2.close
1343
+ end
1344
+
1345
+ it "should respect a type mapping for params and it's OID and format code" do
1346
+ res = @conn2.exec_params( "SELECT $1", [5] )
1347
+ expect( res.values ).to eq( [["5"]] )
1348
+ expect( res.ftype(0) ).to eq( 20 )
1349
+ end
1350
+
1351
+ it "should return the current type mapping" do
1352
+ expect( @conn2.type_map_for_queries ).to be_kind_of(PG::TypeMapByMriType)
1353
+ end
1354
+
1355
+ it "should work with arbitrary number of params in conjunction with type casting" do
1356
+ begin
1357
+ 3.step( 12, 0.2 ) do |exp|
1358
+ num_params = (2 ** exp).to_i
1359
+ sql = num_params.times.map{|n| "$#{n+1}" }.join(",")
1360
+ params = num_params.times.to_a
1361
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1362
+ expect( res.nfields ).to eq( num_params )
1363
+ expect( res.values ).to eq( [num_params.times.map(&:to_s)] )
1364
+ end
1365
+ rescue PG::ProgramLimitExceeded
1366
+ # Stop silently as soon the server complains about too many params
1367
+ end
1368
+ end
1369
+
1370
+ it "can process #copy_data input queries with row encoder" do
1371
+ @conn2.exec( "CREATE TEMP TABLE copytable (col1 TEXT)" )
1372
+ res2 = @conn2.copy_data( "COPY copytable FROM STDOUT" ) do |res|
1373
+ @conn2.put_copy_data [1]
1374
+ @conn2.put_copy_data ["2"]
1375
+ end
1376
+
1377
+ res = @conn2.exec( "SELECT * FROM copytable ORDER BY col1" )
1378
+ expect( res.values ).to eq( [["1"], ["2"]] )
1379
+ end
1380
+ end
1381
+
1382
+ context "with default result type map" do
1383
+ before :each do
1384
+ @conn2 = described_class.new(@conninfo)
1385
+ tm = PG::TypeMapByOid.new
1386
+ tm.add_coder PG::TextDecoder::Integer.new oid: 23, format: 0
1387
+ @conn2.type_map_for_results = tm
1388
+
1389
+ row_decoder = PG::TextDecoder::CopyRow.new
1390
+ @conn2.decoder_for_get_copy_data = row_decoder
1391
+ end
1392
+ after :each do
1393
+ @conn2.close
1394
+ end
1395
+
1396
+ it "should respect a type mapping for result" do
1397
+ res = @conn2.exec_params( "SELECT $1::INT", ["5"] )
1398
+ expect( res.values ).to eq( [[5]] )
1399
+ end
1400
+
1401
+ it "should return the current type mapping" do
1402
+ expect( @conn2.type_map_for_results ).to be_kind_of(PG::TypeMapByOid)
1403
+ end
1404
+
1405
+ it "should work with arbitrary number of params in conjunction with type casting" do
1406
+ begin
1407
+ 3.step( 12, 0.2 ) do |exp|
1408
+ num_params = (2 ** exp).to_i
1409
+ sql = num_params.times.map{|n| "$#{n+1}::INT" }.join(",")
1410
+ params = num_params.times.to_a
1411
+ res = @conn2.exec_params( "SELECT #{sql}", params )
1412
+ expect( res.nfields ).to eq( num_params )
1413
+ expect( res.values ).to eq( [num_params.times.to_a] )
1414
+ end
1415
+ rescue PG::ProgramLimitExceeded
1416
+ # Stop silently as soon the server complains about too many params
1417
+ end
1418
+ end
1419
+
1420
+ it "can process #copy_data output with row decoder" do
1421
+ rows = []
1422
+ res2 = @conn2.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT" ) do |res|
1423
+ while row=@conn2.get_copy_data
1424
+ rows << row
1425
+ end
1426
+ end
1427
+ expect( rows ).to eq( [["1"], ["2"]] )
1428
+ end
1429
+
1430
+ it "can type cast #copy_data output with explicit decoder" do
1431
+ tm = PG::TypeMapByColumn.new [PG::TextDecoder::Integer.new]
1432
+ row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
1433
+ rows = []
1434
+ @conn.copy_data( "COPY (SELECT 1 UNION ALL SELECT 2) TO STDOUT", row_decoder ) do |res|
1435
+ while row=@conn.get_copy_data
1436
+ rows << row
1437
+ end
1438
+ end
1439
+ @conn.copy_data( "COPY (SELECT 3 UNION ALL SELECT 4) TO STDOUT" ) do |res|
1440
+ while row=@conn.get_copy_data( false, row_decoder )
1441
+ rows << row
1442
+ end
1443
+ end
1444
+ expect( rows ).to eq( [[1], [2], [3], [4]] )
1445
+ end
1446
+ end
1447
+ end
1229
1448
  end
@@ -320,4 +320,56 @@ describe PG::Result do
320
320
  expect( error.result ).to eq( nil )
321
321
  }
322
322
  end
323
+
324
+ it "does not clear the result itself" do
325
+ r = @conn.exec "select 1"
326
+ expect( r.autoclear? ).to eq(false)
327
+ expect( r.cleared? ).to eq(false)
328
+ r.clear
329
+ expect( r.cleared? ).to eq(true)
330
+ end
331
+
332
+ context 'result value conversions with TypeMapByColumn' do
333
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
334
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
335
+
336
+ it "should allow reading, assigning and diabling type conversions" do
337
+ res = @conn.exec( "SELECT 123" )
338
+ expect( res.type_map ).to be_nil
339
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
340
+ expect( res.type_map ).to be_an_instance_of(PG::TypeMapByColumn)
341
+ expect( res.type_map.coders ).to eq( [textdec_int] )
342
+ res.type_map = PG::TypeMapByColumn.new [textdec_float]
343
+ expect( res.type_map.coders ).to eq( [textdec_float] )
344
+ res.type_map = nil
345
+ expect( res.type_map ).to be_nil
346
+ end
347
+
348
+ it "should be applied to all value retrieving methods" do
349
+ res = @conn.exec( "SELECT 123 as f" )
350
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
351
+ expect( res.values ).to eq( [[123]] )
352
+ expect( res.getvalue(0,0) ).to eq( 123 )
353
+ expect( res[0] ).to eq( {'f' => 123 } )
354
+ expect( res.enum_for(:each_row).to_a ).to eq( [[123]] )
355
+ expect( res.enum_for(:each).to_a ).to eq( [{'f' => 123}] )
356
+ expect( res.column_values(0) ).to eq( [123] )
357
+ expect( res.field_values('f') ).to eq( [123] )
358
+ end
359
+
360
+ it "should be usable for several querys" do
361
+ colmap = PG::TypeMapByColumn.new [textdec_int]
362
+ res = @conn.exec( "SELECT 123" )
363
+ res.type_map = colmap
364
+ expect( res.values ).to eq( [[123]] )
365
+ res = @conn.exec( "SELECT 456" )
366
+ res.type_map = colmap
367
+ expect( res.values ).to eq( [[456]] )
368
+ end
369
+
370
+ it "shouldn't allow invalid type maps" do
371
+ res = @conn.exec( "SELECT 1" )
372
+ expect{ res.type_map = 1 }.to raise_error(TypeError)
373
+ end
374
+ end
323
375
  end
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByColumn do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
13
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT4', oid: 700 }
14
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
15
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
16
+ let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
17
+ let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
18
+ let!(:binaryenc_bytea){ PG::BinaryEncoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
19
+ let!(:binarydec_bytea){ PG::BinaryDecoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
20
+ let!(:pass_through_type) do
21
+ type = Class.new(PG::SimpleDecoder) do
22
+ def decode(*v)
23
+ v
24
+ end
25
+ end.new
26
+ type.oid = 123456
27
+ type.format = 1
28
+ type.name = 'pass_through'
29
+ type
30
+ end
31
+
32
+ it "should retrieve it's conversions" do
33
+ cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, nil] )
34
+ expect( cm.coders ).to eq( [
35
+ textdec_int,
36
+ textenc_string,
37
+ textdec_float,
38
+ pass_through_type,
39
+ nil
40
+ ] )
41
+ expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:0 TEXT:0 FLOAT4:0 pass_through:1 nil>" )
42
+ end
43
+
44
+ it "should retrieve it's oids" do
45
+ cm = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
46
+ expect( cm.oids ).to eq( [23, 25, 700, 123456, nil] )
47
+ end
48
+
49
+
50
+ #
51
+ # Encoding Examples
52
+ #
53
+
54
+ it "should encode integer params" do
55
+ col_map = PG::TypeMapByColumn.new( [textenc_int]*3 )
56
+ res = @conn.exec_params( "SELECT $1, $2, $3", [ 0, nil, "-999" ], 0, col_map )
57
+ expect( res.values ).to eq( [
58
+ [ "0", nil, "-999" ],
59
+ ] )
60
+ end
61
+
62
+ it "should encode bytea params" do
63
+ data = "'\u001F\\"
64
+ col_map = PG::TypeMapByColumn.new( [binaryenc_bytea]*2 )
65
+ res = @conn.exec_params( "SELECT $1, $2", [ data, nil ], 0, col_map )
66
+ res.type_map = PG::TypeMapByColumn.new( [textdec_bytea]*2 )
67
+ expect( res.values ).to eq( [
68
+ [ data, nil ],
69
+ ] )
70
+ end
71
+
72
+
73
+ it "should allow hash form parameters for default encoder" do
74
+ col_map = PG::TypeMapByColumn.new( [nil, nil] )
75
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
76
+ hash_param_nil = { value: nil, type: 17, format: 1 }
77
+ res = @conn.exec_params( "SELECT $1, $2",
78
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
79
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
80
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
81
+ end
82
+
83
+ it "should convert hash form parameters to string when using string encoders" do
84
+ col_map = PG::TypeMapByColumn.new( [textenc_string, textenc_string] )
85
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
86
+ hash_param_nil = { value: nil, type: 17, format: 1 }
87
+ res = @conn.exec_params( "SELECT $1::text, $2::text",
88
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
89
+ expect( res.values ).to eq( [["{:value=>\"\\x00\\xFF\", :type=>17, :format=>1}", "{:value=>nil, :type=>17, :format=>1}"]] )
90
+ end
91
+
92
+ it "shouldn't allow param mappings with different number of fields" do
93
+ expect{
94
+ @conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([]) )
95
+ }.to raise_error(ArgumentError, /mapped columns/)
96
+ end
97
+
98
+ #
99
+ # Decoding Examples
100
+ #
101
+
102
+ class Exception_in_decode < PG::SimpleDecoder
103
+ def decode(res, tuple, field)
104
+ raise "no type decoder defined for tuple #{tuple} field #{field}"
105
+ end
106
+ end
107
+
108
+ it "should raise an error from decode method of type converter" do
109
+ res = @conn.exec( "SELECT now()" )
110
+ types = Array.new( res.nfields, Exception_in_decode.new )
111
+ res.type_map = PG::TypeMapByColumn.new( types )
112
+ expect{ res.values }.to raise_error(/no type decoder defined/)
113
+ end
114
+
115
+ it "should raise an error for invalid params" do
116
+ expect{ PG::TypeMapByColumn.new( :WrongType ) }.to raise_error(TypeError, /wrong argument type/)
117
+ expect{ PG::TypeMapByColumn.new( [123] ) }.to raise_error(ArgumentError, /invalid/)
118
+ end
119
+
120
+ it "shouldn't allow result mappings with different number of fields" do
121
+ res = @conn.exec( "SELECT 1" )
122
+ expect{ res.type_map = PG::TypeMapByColumn.new([]) }.to raise_error(ArgumentError, /mapped columns/)
123
+ end
124
+
125
+ #
126
+ # Decoding Examples text format
127
+ #
128
+
129
+ it "should allow mixed type conversions" do
130
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE, 3" )
131
+ res.type_map = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
132
+ expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3], '3' ]] )
133
+ end
134
+
135
+ end