pg 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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