pg 0.19.0 → 1.1.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 +5 -5
- checksums.yaml.gz.sig +0 -0
- data/ChangeLog +218 -1
- data/History.rdoc +106 -0
- data/Manifest.txt +5 -18
- data/README.rdoc +15 -5
- data/Rakefile +8 -9
- data/Rakefile.cross +19 -22
- data/ext/errorcodes.def +17 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +11 -1
- data/ext/extconf.rb +14 -32
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -39
- data/ext/pg.c +19 -48
- data/ext/pg.h +46 -81
- data/ext/pg_binary_decoder.c +69 -6
- data/ext/pg_coder.c +53 -4
- data/ext/pg_connection.c +401 -253
- data/ext/pg_copy_coder.c +10 -5
- data/ext/pg_result.c +359 -131
- data/ext/pg_text_decoder.c +597 -37
- data/ext/pg_text_encoder.c +6 -7
- data/ext/pg_tuple.c +541 -0
- data/ext/pg_type_map.c +14 -7
- data/ext/util.c +6 -6
- data/ext/util.h +2 -2
- data/lib/pg/basic_type_mapping.rb +40 -7
- data/lib/pg/binary_decoder.rb +22 -0
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +27 -7
- data/lib/pg/constants.rb +1 -1
- data/lib/pg/exceptions.rb +1 -1
- data/lib/pg/result.rb +6 -5
- data/lib/pg/text_decoder.rb +19 -23
- data/lib/pg/text_encoder.rb +36 -2
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +1 -1
- data/lib/pg.rb +21 -11
- data/spec/helpers.rb +47 -19
- data/spec/pg/basic_type_mapping_spec.rb +230 -27
- data/spec/pg/connection_spec.rb +402 -275
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +59 -17
- data/spec/pg/tuple_spec.rb +280 -0
- data/spec/pg/type_map_by_class_spec.rb +2 -2
- data/spec/pg/type_map_by_column_spec.rb +1 -1
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_map_by_oid_spec.rb +1 -1
- data/spec/pg/type_map_in_ruby_spec.rb +1 -1
- data/spec/pg/type_map_spec.rb +1 -1
- data/spec/pg/type_spec.rb +184 -12
- data/spec/pg_spec.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +47 -53
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,41 @@
|
|
1
|
+
# -*- rspec -*-
|
2
|
+
#encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../helpers'
|
5
|
+
|
6
|
+
context "running with sync_* methods" do
|
7
|
+
before :each do
|
8
|
+
PG::Connection.async_api = false
|
9
|
+
end
|
10
|
+
|
11
|
+
after :each do
|
12
|
+
PG::Connection.async_api = true
|
13
|
+
end
|
14
|
+
|
15
|
+
fname = File.expand_path("../connection_spec.rb", __FILE__)
|
16
|
+
eval File.read(fname, encoding: __ENCODING__), binding, fname
|
17
|
+
|
18
|
+
|
19
|
+
it "enables/disables async/sync methods by #async_api" do
|
20
|
+
[true, false].each do |async|
|
21
|
+
PG::Connection.async_api = async
|
22
|
+
|
23
|
+
start = Time.now
|
24
|
+
t = Thread.new do
|
25
|
+
@conn.exec( 'select pg_sleep(1)' )
|
26
|
+
end
|
27
|
+
sleep 0.1
|
28
|
+
|
29
|
+
t.kill
|
30
|
+
t.join
|
31
|
+
dt = Time.now - start
|
32
|
+
|
33
|
+
if async
|
34
|
+
expect( dt ).to be < 1.0
|
35
|
+
else
|
36
|
+
expect( dt ).to be >= 1.0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
data/spec/pg/result_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -39,8 +39,8 @@ describe PG::Result do
|
|
39
39
|
expect( e.to_a ).to eq [{'a'=>'1', 'b'=>'2'}]
|
40
40
|
end
|
41
41
|
|
42
|
-
context "result streaming"
|
43
|
-
it "can iterate over all
|
42
|
+
context "result streaming in single row mode" do
|
43
|
+
it "can iterate over all rows as Hash" do
|
44
44
|
@conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
|
45
45
|
@conn.set_single_row_mode
|
46
46
|
expect(
|
@@ -56,7 +56,7 @@ describe PG::Result do
|
|
56
56
|
expect( @conn.get_result ).to be_nil
|
57
57
|
end
|
58
58
|
|
59
|
-
it "can iterate over all rows
|
59
|
+
it "can iterate over all rows as Array" do
|
60
60
|
@conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
|
61
61
|
@conn.set_single_row_mode
|
62
62
|
expect(
|
@@ -72,6 +72,22 @@ describe PG::Result do
|
|
72
72
|
expect( @conn.get_result ).to be_nil
|
73
73
|
end
|
74
74
|
|
75
|
+
it "can iterate over all rows as PG::Tuple" do
|
76
|
+
@conn.send_query( "SELECT generate_series(2,4) AS a; SELECT 1 AS b, generate_series(5,6) AS c" )
|
77
|
+
@conn.set_single_row_mode
|
78
|
+
tuples = @conn.get_result.stream_each_tuple.to_a
|
79
|
+
expect( tuples[0][0] ).to eq( "2" )
|
80
|
+
expect( tuples[1]["a"] ).to eq( "3" )
|
81
|
+
expect( tuples.size ).to eq( 3 )
|
82
|
+
|
83
|
+
tuples = @conn.get_result.enum_for(:stream_each_tuple).to_a
|
84
|
+
expect( tuples[-1][-1] ).to eq( "6" )
|
85
|
+
expect( tuples[-2]["b"] ).to eq( "1" )
|
86
|
+
expect( tuples.size ).to eq( 2 )
|
87
|
+
|
88
|
+
expect( @conn.get_result ).to be_nil
|
89
|
+
end
|
90
|
+
|
75
91
|
it "complains when not in single row mode" do
|
76
92
|
@conn.send_query( "SELECT generate_series(2,4)" )
|
77
93
|
expect{
|
@@ -96,15 +112,15 @@ describe PG::Result do
|
|
96
112
|
end
|
97
113
|
|
98
114
|
it "inserts nil AS NULL and return NULL as nil" do
|
99
|
-
res = @conn.
|
115
|
+
res = @conn.exec_params("SELECT $1::int AS n", [nil])
|
100
116
|
expect( res[0]['n'] ).to be_nil()
|
101
117
|
end
|
102
118
|
|
103
|
-
it "encapsulates errors in a
|
119
|
+
it "encapsulates errors in a PG::Error object" do
|
104
120
|
exception = nil
|
105
121
|
begin
|
106
122
|
@conn.exec( "SELECT * FROM nonexistant_table" )
|
107
|
-
rescue
|
123
|
+
rescue PG::Error => err
|
108
124
|
exception = err
|
109
125
|
end
|
110
126
|
|
@@ -136,7 +152,7 @@ describe PG::Result do
|
|
136
152
|
exception = nil
|
137
153
|
begin
|
138
154
|
@conn.exec( "INSERT INTO integrity VALUES (NULL)" )
|
139
|
-
rescue
|
155
|
+
rescue PG::Error => err
|
140
156
|
exception = err
|
141
157
|
end
|
142
158
|
result = exception.result
|
@@ -152,7 +168,7 @@ describe PG::Result do
|
|
152
168
|
sqlstate = nil
|
153
169
|
begin
|
154
170
|
res = @conn.exec("SELECT 1/0")
|
155
|
-
rescue
|
171
|
+
rescue PG::Error => e
|
156
172
|
sqlstate = e.result.result_error_field( PG::PG_DIAG_SQLSTATE ).to_i
|
157
173
|
end
|
158
174
|
expect( sqlstate ).to eq( 22012 )
|
@@ -161,7 +177,7 @@ describe PG::Result do
|
|
161
177
|
it "returns the same bytes in binary format that are sent in binary format" do
|
162
178
|
binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
|
163
179
|
bytes = File.open(binary_file, 'rb').read
|
164
|
-
res = @conn.
|
180
|
+
res = @conn.exec_params('VALUES ($1::bytea)',
|
165
181
|
[ { :value => bytes, :format => 1 } ], 1)
|
166
182
|
expect( res[0]['column1'] ).to eq( bytes )
|
167
183
|
expect( res.getvalue(0,0) ).to eq( bytes )
|
@@ -173,7 +189,7 @@ describe PG::Result do
|
|
173
189
|
binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
|
174
190
|
bytes = File.open(binary_file, 'rb').read
|
175
191
|
@conn.exec("SET standard_conforming_strings=on")
|
176
|
-
res = @conn.
|
192
|
+
res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(bytes)}'::bytea)", [], 1)
|
177
193
|
expect( res[0]['column1'] ).to eq( bytes )
|
178
194
|
expect( res.getvalue(0,0) ).to eq( bytes )
|
179
195
|
expect( res.values[0][0] ).to eq( bytes )
|
@@ -183,7 +199,7 @@ describe PG::Result do
|
|
183
199
|
it "returns the same bytes in text format that are sent in binary format" do
|
184
200
|
binary_file = File.join(Dir.pwd, 'spec/data', 'random_binary_data')
|
185
201
|
bytes = File.open(binary_file, 'rb').read
|
186
|
-
res = @conn.
|
202
|
+
res = @conn.exec_params('VALUES ($1::bytea)',
|
187
203
|
[ { :value => bytes, :format => 1 } ])
|
188
204
|
expect( PG::Connection.unescape_bytea(res[0]['column1']) ).to eq( bytes )
|
189
205
|
end
|
@@ -194,21 +210,21 @@ describe PG::Result do
|
|
194
210
|
|
195
211
|
out_bytes = nil
|
196
212
|
@conn.exec("SET standard_conforming_strings=on")
|
197
|
-
res = @conn.
|
213
|
+
res = @conn.exec_params("VALUES ('#{PG::Connection.escape_bytea(in_bytes)}'::bytea)", [], 0)
|
198
214
|
out_bytes = PG::Connection.unescape_bytea(res[0]['column1'])
|
199
215
|
expect( out_bytes ).to eq( in_bytes )
|
200
216
|
end
|
201
217
|
|
202
|
-
it "returns the parameter type of the specified prepared statement parameter"
|
218
|
+
it "returns the parameter type of the specified prepared statement parameter" do
|
203
219
|
query = 'SELECT * FROM pg_stat_activity WHERE user = $1::name AND query = $2::text'
|
204
220
|
@conn.prepare( 'queryfinder', query )
|
205
221
|
res = @conn.describe_prepared( 'queryfinder' )
|
206
222
|
|
207
223
|
expect(
|
208
|
-
@conn.
|
224
|
+
@conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(0)] ).getvalue( 0, 0 )
|
209
225
|
).to eq( 'name' )
|
210
226
|
expect(
|
211
|
-
@conn.
|
227
|
+
@conn.exec_params( 'SELECT format_type($1, -1)', [res.paramtype(1)] ).getvalue( 0, 0 )
|
212
228
|
).to eq( 'text' )
|
213
229
|
end
|
214
230
|
|
@@ -343,6 +359,24 @@ describe PG::Result do
|
|
343
359
|
expect{ res.field_values(:x) }.to raise_error(TypeError)
|
344
360
|
end
|
345
361
|
|
362
|
+
it "can return the values of a single tuple" do
|
363
|
+
res = @conn.exec( "SELECT 1 AS x, 'a' AS y UNION ALL SELECT 2, 'b'" )
|
364
|
+
expect( res.tuple_values(0) ).to eq( ['1', 'a'] )
|
365
|
+
expect( res.tuple_values(1) ).to eq( ['2', 'b'] )
|
366
|
+
expect{ res.tuple_values(2) }.to raise_error(IndexError)
|
367
|
+
expect{ res.tuple_values(-1) }.to raise_error(IndexError)
|
368
|
+
expect{ res.tuple_values("x") }.to raise_error(TypeError)
|
369
|
+
end
|
370
|
+
|
371
|
+
it "can return the values of a single vary lazy tuple" do
|
372
|
+
res = @conn.exec( "VALUES(1),(2)" )
|
373
|
+
expect( res.tuple(0) ).to be_kind_of( PG::Tuple )
|
374
|
+
expect( res.tuple(1) ).to be_kind_of( PG::Tuple )
|
375
|
+
expect{ res.tuple(2) }.to raise_error(IndexError)
|
376
|
+
expect{ res.tuple(-1) }.to raise_error(IndexError)
|
377
|
+
expect{ res.tuple("x") }.to raise_error(TypeError)
|
378
|
+
end
|
379
|
+
|
346
380
|
it "raises a proper exception for a nonexistant table" do
|
347
381
|
expect {
|
348
382
|
@conn.exec( "SELECT * FROM nonexistant_table" )
|
@@ -386,7 +420,7 @@ describe PG::Result do
|
|
386
420
|
end
|
387
421
|
|
388
422
|
it "the raised result is nil in case of a connection error" do
|
389
|
-
c =
|
423
|
+
c = PG::Connection.connect_start( '127.0.0.1', 54320, "", "", "me", "xxxx", "somedb" )
|
390
424
|
expect {
|
391
425
|
c.exec "select 1"
|
392
426
|
}.to raise_error {|error|
|
@@ -403,6 +437,13 @@ describe PG::Result do
|
|
403
437
|
expect( r.cleared? ).to eq(true)
|
404
438
|
end
|
405
439
|
|
440
|
+
it "can be inspected before and after clear" do
|
441
|
+
r = @conn.exec "select 1"
|
442
|
+
expect( r.inspect ).to match(/status=PGRES_TUPLES_OK/)
|
443
|
+
r.clear
|
444
|
+
expect( r.inspect ).to match(/cleared/)
|
445
|
+
end
|
446
|
+
|
406
447
|
context 'result value conversions with TypeMapByColumn' do
|
407
448
|
let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
|
408
449
|
let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
|
@@ -429,6 +470,7 @@ describe PG::Result do
|
|
429
470
|
expect( res.enum_for(:each).to_a ).to eq( [{'f' => 123}] )
|
430
471
|
expect( res.column_values(0) ).to eq( [123] )
|
431
472
|
expect( res.field_values('f') ).to eq( [123] )
|
473
|
+
expect( res.tuple_values(0) ).to eq( [123] )
|
432
474
|
end
|
433
475
|
|
434
476
|
it "should be usable for several querys" do
|
@@ -0,0 +1,280 @@
|
|
1
|
+
# -*- rspec -*-
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../helpers'
|
5
|
+
require 'pg'
|
6
|
+
require 'objspace'
|
7
|
+
|
8
|
+
describe PG::Tuple do
|
9
|
+
let!(:typemap) { PG::BasicTypeMapForResults.new(@conn) }
|
10
|
+
let!(:result2x2) { @conn.exec( "VALUES(1, 'a'), (2, 'b')" ) }
|
11
|
+
let!(:result2x3cast) { @conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" ).map_types!(typemap) }
|
12
|
+
let!(:tuple0) { result2x2.tuple(0) }
|
13
|
+
let!(:tuple1) { result2x2.tuple(1) }
|
14
|
+
let!(:tuple2) { result2x3cast.tuple(0) }
|
15
|
+
let!(:tuple3) { str = Marshal.dump(result2x3cast.tuple(1)); Marshal.load(str) }
|
16
|
+
let!(:tuple_empty) { PG::Tuple.new }
|
17
|
+
|
18
|
+
describe "[]" do
|
19
|
+
it "returns nil for invalid keys" do
|
20
|
+
expect( tuple0["x"] ).to be_nil
|
21
|
+
expect( tuple0[0.5] ).to be_nil
|
22
|
+
expect( tuple0[2] ).to be_nil
|
23
|
+
expect( tuple0[-3] ).to be_nil
|
24
|
+
expect( tuple2[-4] ).to be_nil
|
25
|
+
expect{ tuple_empty[0] }.to raise_error(TypeError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "supports array like access" do
|
29
|
+
expect( tuple0[0] ).to eq( "1" )
|
30
|
+
expect( tuple0[1] ).to eq( "a" )
|
31
|
+
expect( tuple1[0] ).to eq( "2" )
|
32
|
+
expect( tuple1[1] ).to eq( "b" )
|
33
|
+
expect( tuple2[0] ).to eq( 1 )
|
34
|
+
expect( tuple2[1] ).to eq( true )
|
35
|
+
expect( tuple2[2] ).to eq( "3" )
|
36
|
+
expect( tuple3[0] ).to eq( 2 )
|
37
|
+
expect( tuple3[1] ).to eq( false )
|
38
|
+
expect( tuple3[2] ).to eq( "4" )
|
39
|
+
end
|
40
|
+
|
41
|
+
it "supports negative indices" do
|
42
|
+
expect( tuple0[-2] ).to eq( "1" )
|
43
|
+
expect( tuple0[-1] ).to eq( "a" )
|
44
|
+
expect( tuple2[-3] ).to eq( 1 )
|
45
|
+
expect( tuple2[-2] ).to eq( true )
|
46
|
+
expect( tuple2[-1] ).to eq( "3" )
|
47
|
+
end
|
48
|
+
|
49
|
+
it "supports hash like access" do
|
50
|
+
expect( tuple0["column1"] ).to eq( "1" )
|
51
|
+
expect( tuple0["column2"] ).to eq( "a" )
|
52
|
+
expect( tuple2["a"] ).to eq( 1 )
|
53
|
+
expect( tuple2["b"] ).to eq( "3" )
|
54
|
+
expect( tuple0["x"] ).to be_nil
|
55
|
+
end
|
56
|
+
|
57
|
+
it "casts lazy and caches result" do
|
58
|
+
a = []
|
59
|
+
deco = Class.new(PG::SimpleDecoder) do
|
60
|
+
define_method(:decode) do |*args|
|
61
|
+
a << args
|
62
|
+
args.last
|
63
|
+
end
|
64
|
+
end.new
|
65
|
+
|
66
|
+
result2x2.map_types!(PG::TypeMapByColumn.new([deco, deco]))
|
67
|
+
t = result2x2.tuple(1)
|
68
|
+
|
69
|
+
# cast and cache at first call to [0]
|
70
|
+
a.clear
|
71
|
+
expect( t[0] ).to eq( 0 )
|
72
|
+
expect( a ).to eq([["2", 1, 0]])
|
73
|
+
|
74
|
+
# use cache at second call to [0]
|
75
|
+
a.clear
|
76
|
+
expect( t[0] ).to eq( 0 )
|
77
|
+
expect( a ).to eq([])
|
78
|
+
|
79
|
+
# cast and cache at first call to [1]
|
80
|
+
a.clear
|
81
|
+
expect( t[1] ).to eq( 1 )
|
82
|
+
expect( a ).to eq([["b", 1, 1]])
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "fetch" do
|
87
|
+
it "raises proper errors for invalid keys" do
|
88
|
+
expect{ tuple0.fetch("x") }.to raise_error(KeyError)
|
89
|
+
expect{ tuple0.fetch(0.5) }.to raise_error(KeyError)
|
90
|
+
expect{ tuple0.fetch(2) }.to raise_error(IndexError)
|
91
|
+
expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
|
92
|
+
expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
|
93
|
+
expect{ tuple2.fetch(-4) }.to raise_error(IndexError)
|
94
|
+
expect{ tuple_empty[0] }.to raise_error(TypeError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "supports array like access" do
|
98
|
+
expect( tuple0.fetch(0) ).to eq( "1" )
|
99
|
+
expect( tuple0.fetch(1) ).to eq( "a" )
|
100
|
+
expect( tuple2.fetch(0) ).to eq( 1 )
|
101
|
+
expect( tuple2.fetch(1) ).to eq( true )
|
102
|
+
expect( tuple2.fetch(2) ).to eq( "3" )
|
103
|
+
end
|
104
|
+
|
105
|
+
it "supports default value for indices" do
|
106
|
+
expect( tuple0.fetch(2, 42) ).to eq( 42 )
|
107
|
+
expect( tuple0.fetch(2){43} ).to eq( 43 )
|
108
|
+
end
|
109
|
+
|
110
|
+
it "supports negative indices" do
|
111
|
+
expect( tuple0.fetch(-2) ).to eq( "1" )
|
112
|
+
expect( tuple0.fetch(-1) ).to eq( "a" )
|
113
|
+
expect( tuple2.fetch(-3) ).to eq( 1 )
|
114
|
+
expect( tuple2.fetch(-2) ).to eq( true )
|
115
|
+
expect( tuple2.fetch(-1) ).to eq( "3" )
|
116
|
+
end
|
117
|
+
|
118
|
+
it "supports hash like access" do
|
119
|
+
expect( tuple0.fetch("column1") ).to eq( "1" )
|
120
|
+
expect( tuple0.fetch("column2") ).to eq( "a" )
|
121
|
+
expect( tuple2.fetch("a") ).to eq( 1 )
|
122
|
+
expect( tuple2.fetch("b") ).to eq( "3" )
|
123
|
+
end
|
124
|
+
|
125
|
+
it "supports default value for name keys" do
|
126
|
+
expect( tuple0.fetch("x", "defa") ).to eq("defa")
|
127
|
+
expect( tuple0.fetch("x"){"defa"} ).to eq("defa")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "each" do
|
132
|
+
it "can be used as an enumerator" do
|
133
|
+
expect( tuple0.each ).to be_kind_of(Enumerator)
|
134
|
+
expect( tuple0.each.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
|
135
|
+
expect( tuple1.each.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
|
136
|
+
expect( tuple2.each.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
|
137
|
+
expect( tuple3.each.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
|
138
|
+
expect{ tuple_empty.each }.to raise_error(TypeError)
|
139
|
+
end
|
140
|
+
|
141
|
+
it "can be used with block" do
|
142
|
+
a = []
|
143
|
+
tuple0.each do |*v|
|
144
|
+
a << v
|
145
|
+
end
|
146
|
+
expect( a ).to eq( [["column1", "1"], ["column2", "a"]] )
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "each_value" do
|
151
|
+
it "can be used as an enumerator" do
|
152
|
+
expect( tuple0.each_value ).to be_kind_of(Enumerator)
|
153
|
+
expect( tuple0.each_value.to_a ).to eq( ["1", "a"] )
|
154
|
+
expect( tuple1.each_value.to_a ).to eq( ["2", "b"] )
|
155
|
+
expect( tuple2.each_value.to_a ).to eq( [1, true, "3"] )
|
156
|
+
expect( tuple3.each_value.to_a ).to eq( [2, false, "4"] )
|
157
|
+
expect{ tuple_empty.each_value }.to raise_error(TypeError)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "can be used with block" do
|
161
|
+
a = []
|
162
|
+
tuple0.each_value do |v|
|
163
|
+
a << v
|
164
|
+
end
|
165
|
+
expect( a ).to eq( ["1", "a"] )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "responds to values" do
|
170
|
+
expect( tuple0.values ).to eq( ["1", "a"] )
|
171
|
+
expect( tuple3.values ).to eq( [2, false, "4"] )
|
172
|
+
expect{ tuple_empty.values }.to raise_error(TypeError)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "responds to key?" do
|
176
|
+
expect( tuple1.key?("column1") ).to eq( true )
|
177
|
+
expect( tuple1.key?("other") ).to eq( false )
|
178
|
+
expect( tuple1.has_key?("column1") ).to eq( true )
|
179
|
+
expect( tuple1.has_key?("other") ).to eq( false )
|
180
|
+
end
|
181
|
+
|
182
|
+
it "responds to keys" do
|
183
|
+
expect( tuple0.keys ).to eq( ["column1", "column2"] )
|
184
|
+
expect( tuple2.keys ).to eq( ["a", "b", "b"] )
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "each_key" do
|
188
|
+
it "can be used as an enumerator" do
|
189
|
+
expect( tuple0.each_key ).to be_kind_of(Enumerator)
|
190
|
+
expect( tuple0.each_key.to_a ).to eq( ["column1", "column2"] )
|
191
|
+
expect( tuple2.each_key.to_a ).to eq( ["a", "b", "b"] )
|
192
|
+
end
|
193
|
+
|
194
|
+
it "can be used with block" do
|
195
|
+
a = []
|
196
|
+
tuple0.each_key do |v|
|
197
|
+
a << v
|
198
|
+
end
|
199
|
+
expect( a ).to eq( ["column1", "column2"] )
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it "responds to length" do
|
204
|
+
expect( tuple0.length ).to eq( 2 )
|
205
|
+
expect( tuple0.size ).to eq( 2 )
|
206
|
+
expect( tuple2.size ).to eq( 3 )
|
207
|
+
end
|
208
|
+
|
209
|
+
it "responds to index" do
|
210
|
+
expect( tuple0.index("column1") ).to eq( 0 )
|
211
|
+
expect( tuple0.index("column2") ).to eq( 1 )
|
212
|
+
expect( tuple0.index("x") ).to eq( nil )
|
213
|
+
expect( tuple2.index("a") ).to eq( 0 )
|
214
|
+
expect( tuple2.index("b") ).to eq( 2 )
|
215
|
+
end
|
216
|
+
|
217
|
+
it "can be used as Enumerable" do
|
218
|
+
expect( tuple0.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
|
219
|
+
expect( tuple1.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
|
220
|
+
expect( tuple2.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
|
221
|
+
expect( tuple3.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
|
222
|
+
end
|
223
|
+
|
224
|
+
it "can be marshaled" do
|
225
|
+
[tuple0, tuple1, tuple2, tuple3].each do |t1|
|
226
|
+
str = Marshal.dump(t1)
|
227
|
+
t2 = Marshal.load(str)
|
228
|
+
|
229
|
+
expect( t2 ).to be_kind_of(t1.class)
|
230
|
+
expect( t2 ).not_to equal(t1)
|
231
|
+
expect( t2.to_a ).to eq(t1.to_a)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
it "passes instance variables when marshaled" do
|
236
|
+
t1 = tuple0
|
237
|
+
t1.instance_variable_set("@a", 4711)
|
238
|
+
str = Marshal.dump(t1)
|
239
|
+
t2 = Marshal.load(str)
|
240
|
+
|
241
|
+
expect( t2.instance_variable_get("@a") ).to eq( 4711 )
|
242
|
+
end
|
243
|
+
|
244
|
+
it "can't be marshaled when empty" do
|
245
|
+
expect{ Marshal.dump(tuple_empty) }.to raise_error(TypeError)
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should give account about memory usage" do
|
249
|
+
expect( ObjectSpace.memsize_of(tuple0) ).to be > 40
|
250
|
+
expect( ObjectSpace.memsize_of(tuple_empty) ).to be > 0
|
251
|
+
end
|
252
|
+
|
253
|
+
it "should override #inspect" do
|
254
|
+
expect( tuple1.inspect ).to eq('#<PG::Tuple column1: "2", column2: "b">')
|
255
|
+
expect( tuple2.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
|
256
|
+
expect{ tuple_empty.inspect }.to raise_error(TypeError)
|
257
|
+
end
|
258
|
+
|
259
|
+
context "with cleared result" do
|
260
|
+
it "should raise an error when non-materialized fields are used" do
|
261
|
+
r = result2x2
|
262
|
+
t = r.tuple(0)
|
263
|
+
t[0] # materialize first field only
|
264
|
+
r.clear
|
265
|
+
|
266
|
+
# second column should fail
|
267
|
+
expect{ t[1] }.to raise_error(PG::Error)
|
268
|
+
expect{ t.fetch(1) }.to raise_error(PG::Error)
|
269
|
+
expect{ t.fetch("column2") }.to raise_error(PG::Error)
|
270
|
+
|
271
|
+
# first column should succeed
|
272
|
+
expect( t[0] ).to eq( "1" )
|
273
|
+
expect( t.fetch(0) ).to eq( "1" )
|
274
|
+
expect( t.fetch("column1") ).to eq( "1" )
|
275
|
+
|
276
|
+
# should fail due to the second column
|
277
|
+
expect{ t.values }.to raise_error(PG::Error)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -59,7 +59,7 @@ describe PG::TypeMapByClass do
|
|
59
59
|
it "should retrieve particular conversions" do
|
60
60
|
expect( tm[Integer] ).to eq(binaryenc_int)
|
61
61
|
expect( tm[Float] ).to eq(textenc_float)
|
62
|
-
expect( tm[
|
62
|
+
expect( tm[Range] ).to be_nil
|
63
63
|
expect( derived_tm[raise_class] ).to be_kind_of(Proc)
|
64
64
|
expect( derived_tm[Array] ).to eq(:array_type_map_for)
|
65
65
|
end
|
data/spec/pg/type_map_spec.rb
CHANGED