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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +0 -6595
- data/History.rdoc +70 -0
- data/Manifest.txt +3 -2
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +43 -8
- data/Rakefile +4 -4
- data/Rakefile.cross +7 -4
- data/ext/errorcodes.def +68 -0
- data/ext/errorcodes.txt +19 -2
- data/ext/extconf.rb +6 -6
- data/ext/pg.c +132 -95
- data/ext/pg.h +24 -17
- data/ext/pg_binary_decoder.c +20 -16
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +5 -5
- data/ext/pg_connection.c +395 -301
- data/ext/pg_copy_coder.c +5 -3
- data/ext/pg_record_coder.c +490 -0
- data/ext/pg_result.c +272 -124
- data/ext/pg_text_decoder.c +14 -8
- data/ext/pg_text_encoder.c +180 -48
- data/ext/pg_tuple.c +14 -6
- data/ext/pg_type_map.c +1 -1
- data/ext/pg_type_map_all_strings.c +4 -4
- data/ext/pg_type_map_by_class.c +4 -3
- data/ext/pg_type_map_by_column.c +7 -6
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -2
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/ext/{util.c → pg_util.c} +5 -5
- data/ext/{util.h → pg_util.h} +0 -0
- data/lib/pg.rb +2 -3
- data/lib/pg/basic_type_mapping.rb +79 -16
- data/lib/pg/binary_decoder.rb +1 -0
- data/lib/pg/coder.rb +22 -1
- data/lib/pg/connection.rb +2 -2
- data/lib/pg/constants.rb +1 -0
- data/lib/pg/exceptions.rb +1 -0
- data/lib/pg/result.rb +13 -1
- data/lib/pg/text_decoder.rb +2 -3
- data/lib/pg/text_encoder.rb +8 -18
- data/lib/pg/type_map_by_column.rb +2 -1
- data/spec/helpers.rb +17 -16
- data/spec/pg/basic_type_mapping_spec.rb +151 -14
- data/spec/pg/connection_spec.rb +117 -55
- data/spec/pg/result_spec.rb +193 -3
- data/spec/pg/tuple_spec.rb +55 -2
- data/spec/pg/type_map_by_column_spec.rb +5 -1
- data/spec/pg/type_spec.rb +180 -6
- metadata +40 -45
- metadata.gz.sig +0 -0
data/spec/pg/connection_spec.rb
CHANGED
@@ -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
|
-
|
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(
|
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
|
-
|
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(
|
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 '
|
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("
|
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.
|
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
|
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"
|
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
|
-
|
1218
|
-
@conn.
|
1219
|
-
|
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
|
-
|
1229
|
-
@conn.
|
1230
|
-
|
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
|
-
|
1240
|
-
|
1241
|
-
|
1242
|
-
|
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
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
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"
|
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
|
data/spec/pg/result_spec.rb
CHANGED
@@ -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(
|
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
|
|