pg 1.1.3 → 1.2.0

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 (55) 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 +70 -0
  6. data/Manifest.txt +3 -2
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +43 -8
  10. data/Rakefile +4 -4
  11. data/Rakefile.cross +7 -4
  12. data/ext/errorcodes.def +68 -0
  13. data/ext/errorcodes.txt +19 -2
  14. data/ext/extconf.rb +6 -6
  15. data/ext/pg.c +132 -95
  16. data/ext/pg.h +24 -17
  17. data/ext/pg_binary_decoder.c +20 -16
  18. data/ext/pg_binary_encoder.c +13 -12
  19. data/ext/pg_coder.c +5 -5
  20. data/ext/pg_connection.c +395 -301
  21. data/ext/pg_copy_coder.c +5 -3
  22. data/ext/pg_record_coder.c +490 -0
  23. data/ext/pg_result.c +272 -124
  24. data/ext/pg_text_decoder.c +14 -8
  25. data/ext/pg_text_encoder.c +180 -48
  26. data/ext/pg_tuple.c +14 -6
  27. data/ext/pg_type_map.c +1 -1
  28. data/ext/pg_type_map_all_strings.c +4 -4
  29. data/ext/pg_type_map_by_class.c +4 -3
  30. data/ext/pg_type_map_by_column.c +7 -6
  31. data/ext/pg_type_map_by_mri_type.c +1 -1
  32. data/ext/pg_type_map_by_oid.c +3 -2
  33. data/ext/pg_type_map_in_ruby.c +1 -1
  34. data/ext/{util.c → pg_util.c} +5 -5
  35. data/ext/{util.h → pg_util.h} +0 -0
  36. data/lib/pg.rb +2 -3
  37. data/lib/pg/basic_type_mapping.rb +79 -16
  38. data/lib/pg/binary_decoder.rb +1 -0
  39. data/lib/pg/coder.rb +22 -1
  40. data/lib/pg/connection.rb +2 -2
  41. data/lib/pg/constants.rb +1 -0
  42. data/lib/pg/exceptions.rb +1 -0
  43. data/lib/pg/result.rb +13 -1
  44. data/lib/pg/text_decoder.rb +2 -3
  45. data/lib/pg/text_encoder.rb +8 -18
  46. data/lib/pg/type_map_by_column.rb +2 -1
  47. data/spec/helpers.rb +17 -16
  48. data/spec/pg/basic_type_mapping_spec.rb +151 -14
  49. data/spec/pg/connection_spec.rb +117 -55
  50. data/spec/pg/result_spec.rb +193 -3
  51. data/spec/pg/tuple_spec.rb +55 -2
  52. data/spec/pg/type_map_by_column_spec.rb +5 -1
  53. data/spec/pg/type_spec.rb +180 -6
  54. metadata +40 -45
  55. metadata.gz.sig +0 -0
@@ -238,12 +238,27 @@ describe PG::Connection do
238
238
  # newly established connection is probably distinct from the previous one.
239
239
  ios.each(&:close)
240
240
  conn.reset_start
241
- wait_for_polling_ok(conn)
241
+ wait_for_polling_ok(conn, :reset_poll)
242
242
 
243
243
  # The new connection should work even when the file descriptor has changed.
244
- expect( conn.exec("SELECT 1").values ).to eq([["1"]])
244
+ conn.send_query("SELECT 1")
245
+ res = wait_for_query_result(conn)
246
+ expect( res.values ).to eq([["1"]])
247
+
245
248
  conn.close
246
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
247
262
  end
248
263
 
249
264
  it "raises proper error when sending fails" do
@@ -264,7 +279,7 @@ describe PG::Connection do
264
279
  expect( @conn.db ).to eq( "test" )
265
280
  expect( @conn.user ).to be_a_kind_of( String )
266
281
  expect( @conn.pass ).to eq( "" )
267
- expect( @conn.port ).to eq( 54321 )
282
+ expect( @conn.port ).to eq( @port )
268
283
  expect( @conn.tty ).to eq( "" )
269
284
  expect( @conn.options ).to eq( "" )
270
285
  end
@@ -273,7 +288,20 @@ describe PG::Connection do
273
288
  expect( @conn.host ).to eq( "localhost" )
274
289
  end
275
290
 
276
- 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
+ %{
277
305
  To backend> Msg Q
278
306
  To backend> "SELECT 1 AS one"
279
307
  To backend> Msg complete, length 21
@@ -301,6 +329,7 @@ describe PG::Connection do
301
329
  From backend (#4)> 5
302
330
  From backend> T
303
331
  }.gsub( /^\t{2}/, '' ).lstrip
332
+ end
304
333
 
305
334
  it "trace and untrace client-server communication", :unix do
306
335
  # be careful to explicitly close files so that the
@@ -326,7 +355,7 @@ describe PG::Connection do
326
355
  # From backend> T
327
356
  trace_data.sub!( /(From backend> Z\nFrom backend \(#4\)> 5\n){3}/m, '\\1\\1' )
328
357
 
329
- expect( trace_data ).to eq( EXPECTED_TRACE_OUTPUT )
358
+ expect( trace_data ).to eq( expected_trace_output )
330
359
  end
331
360
 
332
361
  it "allows a query to be cancelled" do
@@ -341,8 +370,6 @@ describe PG::Connection do
341
370
  end
342
371
 
343
372
  it "can stop a thread that runs a blocking query with async_exec" do
344
- pending "this does not work on Rubinius" if RUBY_ENGINE=='rbx'
345
-
346
373
  start = Time.now
347
374
  t = Thread.new do
348
375
  @conn.async_exec( 'select pg_sleep(10)' )
@@ -356,24 +383,16 @@ describe PG::Connection do
356
383
 
357
384
  it "should work together with signal handlers", :unix do
358
385
  signal_received = false
359
- trap 'USR1' do
386
+ trap 'USR2' do
360
387
  signal_received = true
361
388
  end
362
389
 
363
390
  Thread.new do
364
391
  sleep 0.1
365
- Process.kill("USR1", Process.pid)
392
+ Process.kill("USR2", Process.pid)
366
393
  end
367
394
  @conn.exec("select pg_sleep(0.3)")
368
395
  expect( signal_received ).to be_truthy
369
-
370
- signal_received = false
371
- Thread.new do
372
- sleep 0.1
373
- Process.kill("USR1", Process.pid)
374
- end
375
- @conn.async_exec("select pg_sleep(0.3)")
376
- expect( signal_received ).to be_truthy
377
396
  end
378
397
 
379
398
 
@@ -556,7 +575,7 @@ describe PG::Connection do
556
575
  expect( @conn.wait_for_notify( 1 ) ).to be_nil
557
576
  expect( notices.first ).to_not be_nil
558
577
  et = Time.now
559
- expect( (et - notices.first[1]) ).to be >= 0.4
578
+ expect( (et - notices.first[1]) ).to be >= 0.3
560
579
  expect( (et - st) ).to be >= 0.9
561
580
  expect( (et - st) ).to be < 1.4
562
581
  end
@@ -660,7 +679,7 @@ describe PG::Connection do
660
679
  @conn.copy_data( "COPY copytable FROM STDOUT" ) do |res|
661
680
  @conn.put_copy_data "xyz\n"
662
681
  end
663
- }.to raise_error(PG::Error, /invalid input syntax for integer/)
682
+ }.to raise_error(PG::Error, /invalid input syntax for .*integer/)
664
683
  end
665
684
  expect( @conn ).to still_be_usable
666
685
  end
@@ -750,7 +769,7 @@ describe PG::Connection do
750
769
  it "can return the default connection options as a Hash" do
751
770
  expect( described_class.conndefaults_hash ).to be_a( Hash )
752
771
  expect( described_class.conndefaults_hash ).to include( :user, :password, :dbname, :host, :port )
753
- expect( ['5432', '54321'] ).to include( described_class.conndefaults_hash[:port] )
772
+ expect( ['5432', '54321', @port.to_s] ).to include( described_class.conndefaults_hash[:port] )
754
773
  expect( @conn.conndefaults_hash ).to eq( described_class.conndefaults_hash )
755
774
  end
756
775
 
@@ -1140,8 +1159,16 @@ describe PG::Connection do
1140
1159
  expect( ping ).to eq( PG::PQPING_NO_RESPONSE )
1141
1160
  end
1142
1161
 
1143
- it "returns correct response when ping connection arguments are wrong" do
1162
+ it "returns error when ping connection arguments are wrong" do
1144
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)
1145
1172
  expect( ping ).to eq( PG::PQPING_NO_ATTEMPT )
1146
1173
  end
1147
1174
 
@@ -1210,53 +1237,41 @@ describe PG::Connection do
1210
1237
 
1211
1238
  end
1212
1239
 
1213
- context "multinationalization support", :ruby_19 do
1240
+ context "multinationalization support" do
1214
1241
 
1215
1242
  describe "rubyforge #22925: m17n support" do
1216
1243
  it "should return results in the same encoding as the client (iso-8859-1)" do
1217
- out_string = nil
1218
- @conn.transaction do |conn|
1219
- conn.internal_encoding = 'iso8859-1'
1220
- res = conn.exec_params("VALUES ('fantasia')", [], 0)
1221
- out_string = res[0]['column1']
1222
- end
1244
+ @conn.internal_encoding = 'iso8859-1'
1245
+ res = @conn.exec_params("VALUES ('fantasia')", [], 0)
1246
+ out_string = res[0]['column1']
1223
1247
  expect( out_string ).to eq( 'fantasia' )
1224
1248
  expect( out_string.encoding ).to eq( Encoding::ISO8859_1 )
1225
1249
  end
1226
1250
 
1227
1251
  it "should return results in the same encoding as the client (utf-8)" do
1228
- out_string = nil
1229
- @conn.transaction do |conn|
1230
- conn.internal_encoding = 'utf-8'
1231
- res = conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1232
- out_string = res[0]['column1']
1233
- end
1252
+ @conn.internal_encoding = 'utf-8'
1253
+ res = @conn.exec_params("VALUES ('世界線航跡蔵')", [], 0)
1254
+ out_string = res[0]['column1']
1234
1255
  expect( out_string ).to eq( '世界線航跡蔵' )
1235
1256
  expect( out_string.encoding ).to eq( Encoding::UTF_8 )
1236
1257
  end
1237
1258
 
1238
1259
  it "should return results in the same encoding as the client (EUC-JP)" do
1239
- out_string = nil
1240
- @conn.transaction do |conn|
1241
- conn.internal_encoding = 'EUC-JP'
1242
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1243
- res = conn.exec_params(stmt, [], 0)
1244
- out_string = res[0]['column1']
1245
- 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']
1246
1264
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1247
1265
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1248
1266
  end
1249
1267
 
1250
1268
  it "returns the results in the correct encoding even if the client_encoding has " +
1251
1269
  "changed since the results were fetched" do
1252
- out_string = nil
1253
- @conn.transaction do |conn|
1254
- conn.internal_encoding = 'EUC-JP'
1255
- stmt = "VALUES ('世界線航跡蔵')".encode('EUC-JP')
1256
- res = conn.exec_params(stmt, [], 0)
1257
- conn.internal_encoding = 'utf-8'
1258
- out_string = res[0]['column1']
1259
- 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']
1260
1275
  expect( out_string ).to eq( '世界線航跡蔵'.encode('EUC-JP') )
1261
1276
  expect( out_string.encoding ).to eq( Encoding::EUC_JP )
1262
1277
  end
@@ -1335,6 +1350,21 @@ describe PG::Connection do
1335
1350
  expect { @conn.set_client_encoding( :invalid ) }.to raise_error(TypeError)
1336
1351
  expect { @conn.set_client_encoding( nil ) }.to raise_error(TypeError)
1337
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
+
1338
1368
  end
1339
1369
 
1340
1370
  describe "respect and convert character encoding of input strings" do
@@ -1478,7 +1508,7 @@ describe PG::Connection do
1478
1508
 
1479
1509
  describe "Ruby 1.9.x default_internal encoding" do
1480
1510
 
1481
- 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
1482
1512
  @conn.transaction do |txn_conn|
1483
1513
  txn_conn.internal_encoding = Encoding::ISO8859_1
1484
1514
  txn_conn.exec( "CREATE TABLE defaultinternaltest ( foo text )" )
@@ -1580,9 +1610,8 @@ describe PG::Connection do
1580
1610
  end
1581
1611
  end
1582
1612
 
1583
- it "receives properly encoded text from wait_for_notify" do
1613
+ it "receives properly encoded text from wait_for_notify", :without_transaction do
1584
1614
  @conn.internal_encoding = 'utf-8'
1585
- @conn.exec( 'ROLLBACK' )
1586
1615
  @conn.exec( 'LISTEN "Möhre"' )
1587
1616
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1588
1617
  event, pid, msg = nil
@@ -1597,9 +1626,8 @@ describe PG::Connection do
1597
1626
  expect( msg.encoding ).to eq( Encoding::UTF_8 )
1598
1627
  end
1599
1628
 
1600
- it "returns properly encoded text from notifies" do
1629
+ it "returns properly encoded text from notifies", :without_transaction do
1601
1630
  @conn.internal_encoding = 'utf-8'
1602
- @conn.exec( 'ROLLBACK' )
1603
1631
  @conn.exec( 'LISTEN "Möhre"' )
1604
1632
  @conn.exec( %Q{NOTIFY "Möhre", '世界線航跡蔵'} )
1605
1633
  @conn.exec( 'UNLISTEN "Möhre"' )
@@ -1613,7 +1641,7 @@ describe PG::Connection do
1613
1641
  end
1614
1642
  end
1615
1643
 
1616
- context "OS thread support", :ruby_19 do
1644
+ context "OS thread support" do
1617
1645
  it "Connection#exec shouldn't block a second thread" do
1618
1646
  t = Thread.new do
1619
1647
  @conn.exec( "select pg_sleep(1)" )
@@ -1808,6 +1836,40 @@ describe PG::Connection do
1808
1836
  end
1809
1837
  end
1810
1838
 
1839
+ describe :field_name_type do
1840
+ before :each do
1841
+ @conn2 = PG.connect(@conninfo)
1842
+ end
1843
+ after :each do
1844
+ @conn2.close
1845
+ end
1846
+
1847
+ it "uses string field names per default" do
1848
+ expect(@conn2.field_name_type).to eq(:string)
1849
+ end
1850
+
1851
+ it "can set string field names" do
1852
+ @conn2.field_name_type = :string
1853
+ expect(@conn2.field_name_type).to eq(:string)
1854
+ res = @conn2.exec("SELECT 1 as az")
1855
+ expect(res.field_name_type).to eq(:string)
1856
+ expect(res.fields).to eq(["az"])
1857
+ end
1858
+
1859
+ it "can set symbol field names" do
1860
+ @conn2.field_name_type = :symbol
1861
+ expect(@conn2.field_name_type).to eq(:symbol)
1862
+ res = @conn2.exec("SELECT 1 as az")
1863
+ expect(res.field_name_type).to eq(:symbol)
1864
+ expect(res.fields).to eq([:az])
1865
+ end
1866
+
1867
+ it "can't set invalid values" do
1868
+ expect{ @conn2.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
1869
+ expect{ @conn2.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
1870
+ end
1871
+ end
1872
+
1811
1873
  describe "deprecated forms of methods" do
1812
1874
  it "should forward exec to exec_params" do
1813
1875
  res = @conn.exec("VALUES($1::INT)", [7]).values
@@ -4,16 +4,65 @@
4
4
  require_relative '../helpers'
5
5
 
6
6
  require 'pg'
7
+ require 'objspace'
7
8
 
8
9
 
9
10
  describe PG::Result do
10
11
 
12
+ describe :field_name_type do
13
+ let!(:res) { @conn.exec('SELECT 1 AS a, 2 AS "B"') }
14
+
15
+ it "uses string field names per default" do
16
+ expect(res.field_name_type).to eq(:string)
17
+ end
18
+
19
+ it "can set string field names" do
20
+ res.field_name_type = :string
21
+ expect(res.field_name_type).to eq(:string)
22
+ end
23
+
24
+ it "can set symbol field names" do
25
+ res.field_name_type = :symbol
26
+ expect(res.field_name_type).to eq(:symbol)
27
+ end
28
+
29
+ it "can set static_symbol field names" do
30
+ res.field_name_type = :static_symbol
31
+ expect(res.field_name_type).to eq(:static_symbol)
32
+ end
33
+
34
+ it "can't set symbol field names after #fields" do
35
+ res.fields
36
+ expect{ res.field_name_type = :symbol }.to raise_error(ArgumentError, /already materialized/)
37
+ expect(res.field_name_type).to eq(:string)
38
+ end
39
+
40
+ it "can't set invalid values" do
41
+ expect{ res.field_name_type = :sym }.to raise_error(ArgumentError, /invalid argument :sym/)
42
+ expect{ res.field_name_type = "symbol" }.to raise_error(ArgumentError, /invalid argument "symbol"/)
43
+ end
44
+ end
45
+
11
46
  it "acts as an array of hashes" do
12
47
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
13
48
  expect( res[0]['a'] ).to eq( '1' )
14
49
  expect( res[0]['b'] ).to eq( '2' )
15
50
  end
16
51
 
52
+ it "acts as an array of hashes with symbols" do
53
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
54
+ res.field_name_type = :symbol
55
+ expect( res[0][:a] ).to eq( '1' )
56
+ expect( res[0][:b] ).to eq( '2' )
57
+ end
58
+
59
+ it "acts as an array of hashes with static_symbols" do
60
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
61
+ res.field_name_type = :static_symbol
62
+ expect( res[0][:a] ).to eq( '1' )
63
+ expect( res[0][:b] ).to eq( '2' )
64
+ end
65
+
17
66
  it "yields a row as an array" do
18
67
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
19
68
  list = []
@@ -25,7 +74,6 @@ describe PG::Result do
25
74
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
26
75
  e = res.each_row
27
76
  expect( e ).to be_a_kind_of(Enumerator)
28
- pending "Rubinius doesn't define RETURN_SIZED_ENUMERATOR()" if RUBY_ENGINE=='rbx'
29
77
  expect( e.size ).to eq( 1 )
30
78
  expect( e.to_a ).to eq [['1', '2']]
31
79
  end
@@ -34,12 +82,19 @@ describe PG::Result do
34
82
  res = @conn.exec("SELECT 1 AS a, 2 AS b")
35
83
  e = res.each
36
84
  expect( e ).to be_a_kind_of(Enumerator)
37
- pending "Rubinius doesn't define RETURN_SIZED_ENUMERATOR()" if RUBY_ENGINE=='rbx'
38
85
  expect( e.size ).to eq( 1 )
39
86
  expect( e.to_a ).to eq [{'a'=>'1', 'b'=>'2'}]
40
87
  end
41
88
 
89
+ it "yields a row as an Enumerator of hashs with symbols" do
90
+ res = @conn.exec("SELECT 1 AS a, 2 AS b")
91
+ res.field_name_type = :symbol
92
+ expect( res.each.to_a ).to eq [{:a=>'1', :b=>'2'}]
93
+ end
94
+
42
95
  context "result streaming in single row mode" do
96
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
97
+
43
98
  it "can iterate over all rows as Hash" do
44
99
  @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
45
100
  @conn.set_single_row_mode
@@ -56,6 +111,31 @@ describe PG::Result do
56
111
  expect( @conn.get_result ).to be_nil
57
112
  end
58
113
 
114
+ it "can iterate over all rows as Hash with symbols and typemap" do
115
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
116
+ @conn.set_single_row_mode
117
+ res = @conn.get_result.field_names_as(:symbol)
118
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
119
+ expect(
120
+ res.stream_each.to_a
121
+ ).to eq(
122
+ [{:a=>2}, {:a=>3}, {:a=>4}]
123
+ )
124
+ expect( @conn.get_result ).to be_nil
125
+ end
126
+
127
+ it "keeps last result on error while iterating stream_each" do
128
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
129
+ @conn.set_single_row_mode
130
+ res = @conn.get_result
131
+ expect do
132
+ res.stream_each_row do
133
+ raise ZeroDivisionError
134
+ end
135
+ end.to raise_error(ZeroDivisionError)
136
+ expect( res.values ).to eq([["2"]])
137
+ end
138
+
59
139
  it "can iterate over all rows as Array" do
60
140
  @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
61
141
  @conn.set_single_row_mode
@@ -72,6 +152,18 @@ describe PG::Result do
72
152
  expect( @conn.get_result ).to be_nil
73
153
  end
74
154
 
155
+ it "keeps last result on error while iterating stream_each_row" do
156
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
157
+ @conn.set_single_row_mode
158
+ res = @conn.get_result
159
+ expect do
160
+ res.stream_each_row do
161
+ raise ZeroDivisionError
162
+ end
163
+ end.to raise_error(ZeroDivisionError)
164
+ expect( res.values ).to eq([["2"]])
165
+ end
166
+
75
167
  it "can iterate over all rows as PG::Tuple" do
76
168
  @conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
77
169
  @conn.set_single_row_mode
@@ -88,6 +180,36 @@ describe PG::Result do
88
180
  expect( @conn.get_result ).to be_nil
89
181
  end
90
182
 
183
+ it "clears result on error while iterating stream_each_tuple" do
184
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
185
+ @conn.set_single_row_mode
186
+ res = @conn.get_result
187
+ expect do
188
+ res.stream_each_tuple do
189
+ raise ZeroDivisionError
190
+ end
191
+ end.to raise_error(ZeroDivisionError)
192
+ expect( res.cleared? ).to eq(true)
193
+ end
194
+
195
+ it "should reuse field names in stream_each_tuple" do
196
+ @conn.send_query( "SELECT generate_series(2,3) AS a" )
197
+ @conn.set_single_row_mode
198
+ tuple1, tuple2 = *@conn.get_result.stream_each_tuple.to_a
199
+ expect( tuple1.keys[0].object_id ).to eq(tuple2.keys[0].object_id)
200
+ end
201
+
202
+ it "can iterate over all rows as PG::Tuple with symbols and typemap" do
203
+ @conn.send_query( "SELECT generate_series(2,4) AS a" )
204
+ @conn.set_single_row_mode
205
+ res = @conn.get_result.field_names_as(:symbol)
206
+ res.type_map = PG::TypeMapByColumn.new [textdec_int]
207
+ tuples = res.stream_each_tuple.to_a
208
+ expect( tuples[0][0] ).to eq( 2 )
209
+ expect( tuples[1][:a] ).to eq( 3 )
210
+ expect( @conn.get_result ).to be_nil
211
+ end
212
+
91
213
  it "complains when not in single row mode" do
92
214
  @conn.send_query( "SELECT generate_series(2,4)" )
93
215
  expect{
@@ -147,6 +269,17 @@ describe PG::Result do
147
269
  ).to match( /^parserOpenTable$|^RangeVarGetRelid$/ )
148
270
  end
149
271
 
272
+ it "encapsulates PG_DIAG_SEVERITY_NONLOCALIZED error in a PG::Error object", :postgresql_96 do
273
+ result = nil
274
+ begin
275
+ @conn.exec( "SELECT * FROM nonexistant_table" )
276
+ rescue PG::Error => err
277
+ result = err.result
278
+ end
279
+
280
+ expect( result.error_field(PG::PG_DIAG_SEVERITY_NONLOCALIZED) ).to eq( 'ERROR' )
281
+ end
282
+
150
283
  it "encapsulates database object names for integrity constraint violations", :postgresql_93 do
151
284
  @conn.exec( "CREATE TABLE integrity (id SERIAL PRIMARY KEY)" )
152
285
  exception = nil
@@ -174,6 +307,28 @@ describe PG::Result do
174
307
  expect( sqlstate ).to eq( 22012 )
175
308
  end
176
309
 
310
+ it "provides the error message" do
311
+ @conn.send_query("SELECT xyz")
312
+ res = @conn.get_result; @conn.get_result
313
+ expect( res.error_message ).to match(/"xyz"/)
314
+ expect( res.result_error_message ).to match(/"xyz"/)
315
+ end
316
+
317
+ it "provides a verbose error message", :postgresql_96 do
318
+ @conn.send_query("SELECT xyz")
319
+ res = @conn.get_result; @conn.get_result
320
+ # PQERRORS_TERSE should give a single line result
321
+ expect( res.verbose_error_message(PG::PQERRORS_TERSE, PG::PQSHOW_CONTEXT_ALWAYS) ).to match(/\A.*\n\z/)
322
+ # PQERRORS_VERBOSE should give a multi line result
323
+ expect( res.result_verbose_error_message(PG::PQERRORS_VERBOSE, PG::PQSHOW_CONTEXT_NEVER) ).to match(/\n.*\n/)
324
+ end
325
+
326
+ it "provides a verbose error message with SQLSTATE", :postgresql_12 do
327
+ @conn.send_query("SELECT xyz")
328
+ res = @conn.get_result; @conn.get_result
329
+ expect( res.verbose_error_message(PG::PQERRORS_SQLSTATE, PG::PQSHOW_CONTEXT_NEVER) ).to match(/42703/)
330
+ end
331
+
177
332
  it "returns the same bytes in binary format that are sent in binary format" do
178
333
  binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
179
334
  bytes = File.open(binary_file, 'rb').read
@@ -258,6 +413,32 @@ describe PG::Result do
258
413
  expect( res.values ).to eq( [ ["bar"], ["bar2"] ] )
259
414
  end
260
415
 
416
+ it "can retrieve field names" do
417
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
418
+ expect(res.fields).to eq(["a", "B"])
419
+ end
420
+
421
+ it "can retrieve field names as symbols" do
422
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
423
+ res.field_name_type = :symbol
424
+ expect(res.fields).to eq([:a, :B])
425
+ end
426
+
427
+ it "can retrieve single field names" do
428
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
429
+ expect(res.fname(0)).to eq("a")
430
+ expect(res.fname(1)).to eq("B")
431
+ expect{res.fname(2)}.to raise_error(ArgumentError)
432
+ end
433
+
434
+ it "can retrieve single field names as symbol" do
435
+ res = @conn.exec('SELECT 1 AS a, 2 AS "B"')
436
+ res.field_name_type = :symbol
437
+ expect(res.fname(0)).to eq(:a)
438
+ expect(res.fname(1)).to eq(:B)
439
+ expect{res.fname(2)}.to raise_error(ArgumentError)
440
+ end
441
+
261
442
  # PQfmod
262
443
  it "can return the type modifier for a result column" do
263
444
  @conn.exec( 'CREATE TABLE fmodtest ( foo varchar(33) )' )
@@ -355,8 +536,9 @@ describe PG::Result do
355
536
  res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
356
537
  expect( res.field_values('x') ).to eq( ['1', '2'] )
357
538
  expect( res.field_values('y') ).to eq( ['a', 'b'] )
539
+ expect( res.field_values(:x) ).to eq( ['1', '2'] )
358
540
  expect{ res.field_values('') }.to raise_error(IndexError)
359
- expect{ res.field_values(:x) }.to raise_error(TypeError)
541
+ expect{ res.field_values(0) }.to raise_error(TypeError)
360
542
  end
361
543
 
362
544
  it "can return the values of a single tuple" do
@@ -444,6 +626,13 @@ describe PG::Result do
444
626
  expect( r.inspect ).to match(/cleared/)
445
627
  end
446
628
 
629
+ it "should give account about memory usage" do
630
+ r = @conn.exec "select 1"
631
+ expect( ObjectSpace.memsize_of(r) ).to be > 1000
632
+ r.clear
633
+ expect( ObjectSpace.memsize_of(r) ).to be < 100
634
+ end
635
+
447
636
  context 'result value conversions with TypeMapByColumn' do
448
637
  let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
449
638
  let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
@@ -470,6 +659,7 @@ describe PG::Result do
470
659
  expect( res.enum_for(:each).to_a ).to eq( [{'f' => 123}] )
471
660
  expect( res.column_values(0) ).to eq( [123] )
472
661
  expect( res.field_values('f') ).to eq( [123] )
662
+ expect( res.field_values(:f) ).to eq( [123] )
473
663
  expect( res.tuple_values(0) ).to eq( [123] )
474
664
  end
475
665