pg 1.1.4 → 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 +63 -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 +3 -3
- data/Rakefile.cross +6 -3
- data/ext/errorcodes.def +64 -0
- data/ext/errorcodes.txt +18 -2
- data/ext/extconf.rb +6 -6
- data/ext/pg.c +132 -95
- data/ext/pg.h +20 -18
- data/ext/pg_binary_decoder.c +9 -9
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +5 -5
- data/ext/pg_connection.c +388 -298
- data/ext/pg_copy_coder.c +5 -3
- data/ext/pg_record_coder.c +490 -0
- data/ext/pg_result.c +269 -123
- 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 +10 -8
- data/spec/pg/basic_type_mapping_spec.rb +150 -13
- data/spec/pg/connection_spec.rb +89 -50
- 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 +17 -15
- metadata.gz.sig +0 -0
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
|
|
data/spec/pg/tuple_spec.rb
CHANGED
@@ -8,10 +8,22 @@ require 'objspace'
|
|
8
8
|
describe PG::Tuple do
|
9
9
|
let!(:typemap) { PG::BasicTypeMapForResults.new(@conn) }
|
10
10
|
let!(:result2x2) { @conn.exec( "VALUES(1, 'a'), (2, 'b')" ) }
|
11
|
-
let!(:
|
11
|
+
let!(:result2x2sym) { @conn.exec( "VALUES(1, 'a'), (2, 'b')" ).field_names_as(:symbol) }
|
12
|
+
let!(:result2x3cast) do
|
13
|
+
@conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" )
|
14
|
+
.map_types!(typemap)
|
15
|
+
end
|
16
|
+
let!(:result2x3symcast) do
|
17
|
+
@conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" )
|
18
|
+
.map_types!(typemap)
|
19
|
+
.field_names_as(:symbol)
|
20
|
+
end
|
12
21
|
let!(:tuple0) { result2x2.tuple(0) }
|
22
|
+
let!(:tuple0sym) { result2x2sym.tuple(0) }
|
13
23
|
let!(:tuple1) { result2x2.tuple(1) }
|
24
|
+
let!(:tuple1sym) { result2x2sym.tuple(1) }
|
14
25
|
let!(:tuple2) { result2x3cast.tuple(0) }
|
26
|
+
let!(:tuple2sym) { result2x3symcast.tuple(0) }
|
15
27
|
let!(:tuple3) { str = Marshal.dump(result2x3cast.tuple(1)); Marshal.load(str) }
|
16
28
|
let!(:tuple_empty) { PG::Tuple.new }
|
17
29
|
|
@@ -51,9 +63,19 @@ describe PG::Tuple do
|
|
51
63
|
expect( tuple0["column2"] ).to eq( "a" )
|
52
64
|
expect( tuple2["a"] ).to eq( 1 )
|
53
65
|
expect( tuple2["b"] ).to eq( "3" )
|
66
|
+
expect( tuple0[:b] ).to be_nil
|
54
67
|
expect( tuple0["x"] ).to be_nil
|
55
68
|
end
|
56
69
|
|
70
|
+
it "supports hash like access with symbols" do
|
71
|
+
expect( tuple0sym[:column1] ).to eq( "1" )
|
72
|
+
expect( tuple0sym[:column2] ).to eq( "a" )
|
73
|
+
expect( tuple2sym[:a] ).to eq( 1 )
|
74
|
+
expect( tuple2sym[:b] ).to eq( "3" )
|
75
|
+
expect( tuple2sym["b"] ).to be_nil
|
76
|
+
expect( tuple0sym[:x] ).to be_nil
|
77
|
+
end
|
78
|
+
|
57
79
|
it "casts lazy and caches result" do
|
58
80
|
a = []
|
59
81
|
deco = Class.new(PG::SimpleDecoder) do
|
@@ -138,6 +160,12 @@ describe PG::Tuple do
|
|
138
160
|
expect{ tuple_empty.each }.to raise_error(TypeError)
|
139
161
|
end
|
140
162
|
|
163
|
+
it "can be used as an enumerator with symbols" do
|
164
|
+
expect( tuple0sym.each ).to be_kind_of(Enumerator)
|
165
|
+
expect( tuple0sym.each.to_a ).to eq( [[:column1, "1"], [:column2, "a"]] )
|
166
|
+
expect( tuple2sym.each.to_a ).to eq( [[:a, 1], [:b, true], [:b, "3"]] )
|
167
|
+
end
|
168
|
+
|
141
169
|
it "can be used with block" do
|
142
170
|
a = []
|
143
171
|
tuple0.each do |*v|
|
@@ -174,16 +202,30 @@ describe PG::Tuple do
|
|
174
202
|
|
175
203
|
it "responds to key?" do
|
176
204
|
expect( tuple1.key?("column1") ).to eq( true )
|
205
|
+
expect( tuple1.key?(:column1) ).to eq( false )
|
177
206
|
expect( tuple1.key?("other") ).to eq( false )
|
178
207
|
expect( tuple1.has_key?("column1") ).to eq( true )
|
179
208
|
expect( tuple1.has_key?("other") ).to eq( false )
|
180
209
|
end
|
181
210
|
|
211
|
+
it "responds to key? as symbol" do
|
212
|
+
expect( tuple1sym.key?(:column1) ).to eq( true )
|
213
|
+
expect( tuple1sym.key?("column1") ).to eq( false )
|
214
|
+
expect( tuple1sym.key?(:other) ).to eq( false )
|
215
|
+
expect( tuple1sym.has_key?(:column1) ).to eq( true )
|
216
|
+
expect( tuple1sym.has_key?(:other) ).to eq( false )
|
217
|
+
end
|
218
|
+
|
182
219
|
it "responds to keys" do
|
183
220
|
expect( tuple0.keys ).to eq( ["column1", "column2"] )
|
184
221
|
expect( tuple2.keys ).to eq( ["a", "b", "b"] )
|
185
222
|
end
|
186
223
|
|
224
|
+
it "responds to keys as symbol" do
|
225
|
+
expect( tuple0sym.keys ).to eq( [:column1, :column2] )
|
226
|
+
expect( tuple2sym.keys ).to eq( [:a, :b, :b] )
|
227
|
+
end
|
228
|
+
|
187
229
|
describe "each_key" do
|
188
230
|
it "can be used as an enumerator" do
|
189
231
|
expect( tuple0.each_key ).to be_kind_of(Enumerator)
|
@@ -208,12 +250,22 @@ describe PG::Tuple do
|
|
208
250
|
|
209
251
|
it "responds to index" do
|
210
252
|
expect( tuple0.index("column1") ).to eq( 0 )
|
253
|
+
expect( tuple0.index(:column1) ).to eq( nil )
|
211
254
|
expect( tuple0.index("column2") ).to eq( 1 )
|
212
255
|
expect( tuple0.index("x") ).to eq( nil )
|
213
256
|
expect( tuple2.index("a") ).to eq( 0 )
|
214
257
|
expect( tuple2.index("b") ).to eq( 2 )
|
215
258
|
end
|
216
259
|
|
260
|
+
it "responds to index with symbol" do
|
261
|
+
expect( tuple0sym.index(:column1) ).to eq( 0 )
|
262
|
+
expect( tuple0sym.index("column1") ).to eq( nil )
|
263
|
+
expect( tuple0sym.index(:column2) ).to eq( 1 )
|
264
|
+
expect( tuple0sym.index(:x) ).to eq( nil )
|
265
|
+
expect( tuple2sym.index(:a) ).to eq( 0 )
|
266
|
+
expect( tuple2sym.index(:b) ).to eq( 2 )
|
267
|
+
end
|
268
|
+
|
217
269
|
it "can be used as Enumerable" do
|
218
270
|
expect( tuple0.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
|
219
271
|
expect( tuple1.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
|
@@ -222,7 +274,7 @@ describe PG::Tuple do
|
|
222
274
|
end
|
223
275
|
|
224
276
|
it "can be marshaled" do
|
225
|
-
[tuple0, tuple1, tuple2, tuple3].each do |t1|
|
277
|
+
[tuple0, tuple1, tuple2, tuple3, tuple0sym, tuple2sym].each do |t1|
|
226
278
|
str = Marshal.dump(t1)
|
227
279
|
t2 = Marshal.load(str)
|
228
280
|
|
@@ -253,6 +305,7 @@ describe PG::Tuple do
|
|
253
305
|
it "should override #inspect" do
|
254
306
|
expect( tuple1.inspect ).to eq('#<PG::Tuple column1: "2", column2: "b">')
|
255
307
|
expect( tuple2.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
|
308
|
+
expect( tuple2sym.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
|
256
309
|
expect{ tuple_empty.inspect }.to raise_error(TypeError)
|
257
310
|
end
|
258
311
|
|
@@ -38,7 +38,11 @@ describe PG::TypeMapByColumn do
|
|
38
38
|
pass_through_type,
|
39
39
|
nil
|
40
40
|
] )
|
41
|
-
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should respond to inspect" do
|
44
|
+
cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, PG::TextEncoder::Float.new, nil] )
|
45
|
+
expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:TD TEXT:TE FLOAT4:TD pass_through:BD PG::TextEncoder::Float:TE nil>" )
|
42
46
|
end
|
43
47
|
|
44
48
|
it "should retrieve it's oids" do
|
data/spec/pg/type_spec.rb
CHANGED
@@ -12,6 +12,7 @@ describe "PG::Type derivations" do
|
|
12
12
|
let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
|
13
13
|
let!(:textenc_float) { PG::TextEncoder::Float.new }
|
14
14
|
let!(:textdec_float) { PG::TextDecoder::Float.new }
|
15
|
+
let!(:textenc_numeric) { PG::TextEncoder::Numeric.new }
|
15
16
|
let!(:textenc_string) { PG::TextEncoder::String.new }
|
16
17
|
let!(:textdec_string) { PG::TextDecoder::String.new }
|
17
18
|
let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
|
@@ -254,6 +255,11 @@ describe "PG::Type derivations" do
|
|
254
255
|
expect( textdec_string.decode( nil )).to be_nil
|
255
256
|
expect( textdec_int.decode( nil )).to be_nil
|
256
257
|
end
|
258
|
+
|
259
|
+
it "should be defined on an encoder but not on a decoder instance" do
|
260
|
+
expect( textdec_int.respond_to?(:decode) ).to be_truthy
|
261
|
+
expect( textenc_int.respond_to?(:decode) ).to be_falsey
|
262
|
+
end
|
257
263
|
end
|
258
264
|
|
259
265
|
describe '#encode' do
|
@@ -304,12 +310,73 @@ describe "PG::Type derivations" do
|
|
304
310
|
end
|
305
311
|
end
|
306
312
|
|
313
|
+
it "should encode floats" do
|
314
|
+
expect( textenc_float.encode(0) ).to eq( "0.0" )
|
315
|
+
expect( textenc_float.encode(-1) ).to eq( "-1.0" )
|
316
|
+
expect( textenc_float.encode(-1.234567890123456789) ).to eq( "-1.234567890123457" )
|
317
|
+
expect( textenc_float.encode(9) ).to eq( "9.0" )
|
318
|
+
expect( textenc_float.encode(10) ).to eq( "10.0" )
|
319
|
+
expect( textenc_float.encode(-99) ).to eq( "-99.0" )
|
320
|
+
expect( textenc_float.encode(-100) ).to eq( "-100.0" )
|
321
|
+
expect( textenc_float.encode(999) ).to eq( "999.0" )
|
322
|
+
expect( textenc_float.encode(-1000) ).to eq( "-1000.0" )
|
323
|
+
expect( textenc_float.encode(1234.567890123456789) ).to eq( "1234.567890123457" )
|
324
|
+
expect( textenc_float.encode(-9999) ).to eq( "-9999.0" )
|
325
|
+
expect( textenc_float.encode(10000) ).to eq( "10000.0" )
|
326
|
+
expect( textenc_float.encode(99999) ).to eq( "99999.0" )
|
327
|
+
expect( textenc_float.encode(-100000) ).to eq( "-100000.0" )
|
328
|
+
expect( textenc_float.encode(-999999) ).to eq( "-999999.0" )
|
329
|
+
expect( textenc_float.encode(1000000) ).to eq( "1000000.0" )
|
330
|
+
expect( textenc_float.encode(9999999) ).to eq( "9999999.0" )
|
331
|
+
expect( textenc_float.encode(-100000000000000) ).to eq( "-100000000000000.0" )
|
332
|
+
expect( textenc_float.encode(123456789012345) ).to eq( "123456789012345.0" )
|
333
|
+
expect( textenc_float.encode(-999999999999999) ).to eq( "-999999999999999.0" )
|
334
|
+
expect( textenc_float.encode(1000000000000000) ).to eq( "1e15" )
|
335
|
+
expect( textenc_float.encode(-1234567890123456) ).to eq( "-1.234567890123456e15" )
|
336
|
+
expect( textenc_float.encode(9999999999999999) ).to eq( "1e16" )
|
337
|
+
|
338
|
+
expect( textenc_float.encode(-0.0) ).to eq( "0.0" )
|
339
|
+
expect( textenc_float.encode(0.1) ).to eq( "0.1" )
|
340
|
+
expect( textenc_float.encode(0.1234567890123456789) ).to eq( "0.1234567890123457" )
|
341
|
+
expect( textenc_float.encode(-0.9) ).to eq( "-0.9" )
|
342
|
+
expect( textenc_float.encode(-0.01234567890123456789) ).to eq( "-0.01234567890123457" )
|
343
|
+
expect( textenc_float.encode(0.09) ).to eq( "0.09" )
|
344
|
+
expect( textenc_float.encode(0.001234567890123456789) ).to eq( "0.001234567890123457" )
|
345
|
+
expect( textenc_float.encode(-0.009) ).to eq( "-0.009" )
|
346
|
+
expect( textenc_float.encode(-0.0001234567890123456789) ).to eq( "-0.0001234567890123457" )
|
347
|
+
expect( textenc_float.encode(0.0009) ).to eq( "0.0009" )
|
348
|
+
expect( textenc_float.encode(0.00001) ).to eq( "1e-5" )
|
349
|
+
expect( textenc_float.encode(0.00001234567890123456789) ).to eq( "1.234567890123457e-5" )
|
350
|
+
expect( textenc_float.encode(-0.00009) ).to eq( "-9e-5" )
|
351
|
+
expect( textenc_float.encode(-0.11) ).to eq( "-0.11" )
|
352
|
+
expect( textenc_float.encode(10.11) ).to eq( "10.11" )
|
353
|
+
expect( textenc_float.encode(-1.234567890123456789E-280) ).to eq( "-1.234567890123457e-280" )
|
354
|
+
expect( textenc_float.encode(-1.234567890123456789E280) ).to eq( "-1.234567890123457e280" )
|
355
|
+
expect( textenc_float.encode(9876543210987654321E280) ).to eq( "9.87654321098765e298" )
|
356
|
+
expect( textenc_float.encode(9876543210987654321E-400) ).to eq( "0.0" )
|
357
|
+
expect( textenc_float.encode(9876543210987654321E400) ).to eq( "Infinity" )
|
358
|
+
end
|
359
|
+
|
307
360
|
it "should encode special floats equally to Float#to_s" do
|
308
361
|
expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
|
309
362
|
expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
|
310
363
|
expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
|
311
364
|
end
|
312
365
|
|
366
|
+
it "should encode various inputs to numeric format" do
|
367
|
+
expect( textenc_numeric.encode(0) ).to eq( "0" )
|
368
|
+
expect( textenc_numeric.encode(1) ).to eq( "1" )
|
369
|
+
expect( textenc_numeric.encode(-12345678901234567890123) ).to eq( "-12345678901234567890123" )
|
370
|
+
expect( textenc_numeric.encode(0.0) ).to eq( "0.0" )
|
371
|
+
expect( textenc_numeric.encode(1.0) ).to eq( "1.0" )
|
372
|
+
expect( textenc_numeric.encode(-1.23456789012e45) ).to eq( "-1.23456789012e45" )
|
373
|
+
expect( textenc_numeric.encode(Float::NAN) ).to eq( Float::NAN.to_s )
|
374
|
+
expect( textenc_numeric.encode(BigDecimal(0)) ).to eq( "0.0" )
|
375
|
+
expect( textenc_numeric.encode(BigDecimal(1)) ).to eq( "1.0" )
|
376
|
+
expect( textenc_numeric.encode(BigDecimal("-12345678901234567890.1234567")) ).to eq( "-12345678901234567890.1234567" )
|
377
|
+
expect( textenc_numeric.encode(" 123 ") ).to eq( " 123 " )
|
378
|
+
end
|
379
|
+
|
313
380
|
it "encodes binary string to bytea" do
|
314
381
|
expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
|
315
382
|
end
|
@@ -376,6 +443,11 @@ describe "PG::Type derivations" do
|
|
376
443
|
expect( textenc_string.encode( nil )).to be_nil
|
377
444
|
expect( textenc_int.encode( nil )).to be_nil
|
378
445
|
end
|
446
|
+
|
447
|
+
it "should be defined on a decoder but not on an encoder instance" do
|
448
|
+
expect( textenc_int.respond_to?(:encode) ).to be_truthy
|
449
|
+
expect( textdec_int.respond_to?(:encode) ).to be_falsey
|
450
|
+
end
|
379
451
|
end
|
380
452
|
|
381
453
|
it "should be possible to marshal encoders" do
|
@@ -392,7 +464,7 @@ describe "PG::Type derivations" do
|
|
392
464
|
|
393
465
|
it "should respond to to_h" do
|
394
466
|
expect( textenc_int.to_h ).to eq( {
|
395
|
-
name: 'Integer', oid: 23, format: 0
|
467
|
+
name: 'Integer', oid: 23, format: 0, flags: 0
|
396
468
|
} )
|
397
469
|
end
|
398
470
|
|
@@ -624,7 +696,7 @@ describe "PG::Type derivations" do
|
|
624
696
|
expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
|
625
697
|
end
|
626
698
|
it 'encodes an array of float8 with sub arrays' do
|
627
|
-
expect( textenc_float_array.encode([1000.11,[-0.
|
699
|
+
expect( textenc_float_array.encode([1000.11,[-0.00000221,[3.31,-441]],[nil,6.61],-7.71]) ).to match(Regexp.new(%[^{1000.1*,{-2.2*e-*6,{3.3*,-441.0}},{NULL,6.6*},-7.7*}$].gsub(/([\.\+\{\}\,])/, "\\\\\\1").gsub(/\*/, "\\d*")))
|
628
700
|
end
|
629
701
|
end
|
630
702
|
context 'two dimensional arrays' do
|
@@ -712,15 +784,15 @@ describe "PG::Type derivations" do
|
|
712
784
|
expect( lt.to_h ).to eq( textenc_int_array.to_h )
|
713
785
|
end
|
714
786
|
|
715
|
-
it "should be possible to marshal
|
716
|
-
mt = Marshal.dump(
|
787
|
+
it "should be possible to marshal decoders" do
|
788
|
+
mt = Marshal.dump(textdec_string_array_raise)
|
717
789
|
lt = Marshal.load(mt)
|
718
|
-
expect( lt.to_h ).to eq(
|
790
|
+
expect( lt.to_h ).to eq( textdec_string_array_raise.to_h )
|
719
791
|
end
|
720
792
|
|
721
793
|
it "should respond to to_h" do
|
722
794
|
expect( textenc_int_array.to_h ).to eq( {
|
723
|
-
name: nil, oid: 0, format: 0,
|
795
|
+
name: nil, oid: 0, format: 0, flags: 0,
|
724
796
|
elements_type: textenc_int, needs_quotation: false, delimiter: ','
|
725
797
|
} )
|
726
798
|
end
|
@@ -946,4 +1018,106 @@ describe "PG::Type derivations" do
|
|
946
1018
|
end
|
947
1019
|
end
|
948
1020
|
end
|
1021
|
+
|
1022
|
+
describe PG::RecordCoder do
|
1023
|
+
describe PG::TextEncoder::Record do
|
1024
|
+
context "with default typemap" do
|
1025
|
+
let!(:encoder) do
|
1026
|
+
PG::TextEncoder::Record.new
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
it "should encode different types of Ruby objects" do
|
1030
|
+
expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
|
1031
|
+
to eq('("xyz","123","2456","34567","456789","5678901","[1, 2, 3]","12.1","abcdefg",)')
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
it 'should output a string with correct character encoding' do
|
1035
|
+
v = encoder.encode(["Héllo"], "iso-8859-1")
|
1036
|
+
expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
|
1037
|
+
expect( v ).to eq( '("Héllo")'.encode(Encoding::ISO_8859_1) )
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
context "with TypeMapByClass" do
|
1042
|
+
let!(:tm) do
|
1043
|
+
tm = PG::TypeMapByClass.new
|
1044
|
+
tm[Integer] = textenc_int
|
1045
|
+
tm[Float] = intenc_incrementer
|
1046
|
+
tm[Array] = PG::TextEncoder::Array.new elements_type: textenc_string
|
1047
|
+
tm
|
1048
|
+
end
|
1049
|
+
let!(:encoder) do
|
1050
|
+
PG::TextEncoder::Record.new type_map: tm
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
it "should have reasonable default values" do
|
1054
|
+
expect( encoder.name ).to be_nil
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
it "copies all attributes with #dup" do
|
1058
|
+
encoder.name = "test"
|
1059
|
+
encoder.type_map = PG::TypeMapByColumn.new []
|
1060
|
+
encoder2 = encoder.dup
|
1061
|
+
expect( encoder.object_id ).to_not eq( encoder2.object_id )
|
1062
|
+
expect( encoder2.name ).to eq( "test" )
|
1063
|
+
expect( encoder2.type_map ).to be_a_kind_of( PG::TypeMapByColumn )
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
describe '#encode' do
|
1067
|
+
it "should encode different types of Ruby objects" do
|
1068
|
+
expect( encoder.encode([]) ).to eq("()")
|
1069
|
+
expect( encoder.encode(["a"]) ).to eq('("a")')
|
1070
|
+
expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
|
1071
|
+
to eq('("xyz","123","2456","34567","456789","5678901","{1,2,3}","13 ","abcdefg",)')
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
it "should escape special characters" do
|
1075
|
+
expect( encoder.encode([" \"\t\n\\\r"]) ).to eq("(\" \"\"\t\n##\r\")".gsub("#", "\\"))
|
1076
|
+
end
|
1077
|
+
end
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
describe PG::TextDecoder::Record do
|
1082
|
+
context "with default typemap" do
|
1083
|
+
let!(:decoder) do
|
1084
|
+
PG::TextDecoder::Record.new
|
1085
|
+
end
|
1086
|
+
|
1087
|
+
describe '#decode' do
|
1088
|
+
it "should decode composite text format to array of strings" do
|
1089
|
+
expect( decoder.decode('("fuzzy dice",,"",42,)') ).to eq( ["fuzzy dice",nil, "", "42", nil] )
|
1090
|
+
end
|
1091
|
+
|
1092
|
+
it 'should respect input character encoding' do
|
1093
|
+
v = decoder.decode("(Héllo)".encode("iso-8859-1")).first
|
1094
|
+
expect( v.encoding ).to eq(Encoding::ISO_8859_1)
|
1095
|
+
expect( v ).to eq("Héllo".encode("iso-8859-1"))
|
1096
|
+
end
|
1097
|
+
|
1098
|
+
it 'should raise an error on malformed input' do
|
1099
|
+
expect{ decoder.decode('') }.to raise_error(ArgumentError, /"" - Missing left parenthesis/)
|
1100
|
+
expect{ decoder.decode('(') }.to raise_error(ArgumentError, /"\(" - Unexpected end of input/)
|
1101
|
+
expect{ decoder.decode('(\\') }.to raise_error(ArgumentError, /"\(\\" - Unexpected end of input/)
|
1102
|
+
expect{ decoder.decode('()x') }.to raise_error(ArgumentError, /"\(\)x" - Junk after right parenthesis/)
|
1103
|
+
end
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
context "with TypeMapByColumn" do
|
1108
|
+
let!(:tm) do
|
1109
|
+
PG::TypeMapByColumn.new [textdec_int, textdec_string, intdec_incrementer, nil]
|
1110
|
+
end
|
1111
|
+
let!(:decoder) do
|
1112
|
+
PG::TextDecoder::Record.new type_map: tm
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
describe '#decode' do
|
1116
|
+
it "should decode different types of Ruby objects" do
|
1117
|
+
expect( decoder.decode("(123,\" #,#\n#\r#\\ \",234,#\x01#\002)".gsub("#", "\\"))).to eq( [123, " ,\n\r\\ ", 235, "\x01\x02"] )
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
end
|
949
1123
|
end
|