pg 1.0.0 → 1.2.3
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 +156 -0
- data/Manifest.txt +8 -2
- data/README-Windows.rdoc +4 -4
- data/README.ja.rdoc +1 -2
- data/README.rdoc +55 -9
- data/Rakefile +9 -7
- data/Rakefile.cross +58 -57
- data/ext/errorcodes.def +68 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +19 -2
- data/ext/extconf.rb +7 -5
- data/ext/pg.c +141 -98
- data/ext/pg.h +64 -21
- data/ext/pg_binary_decoder.c +82 -15
- data/ext/pg_binary_encoder.c +13 -12
- data/ext/pg_coder.c +73 -12
- data/ext/pg_connection.c +625 -346
- data/ext/pg_copy_coder.c +16 -8
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +571 -191
- data/ext/pg_text_decoder.c +606 -40
- data/ext/pg_text_encoder.c +185 -54
- data/ext/pg_tuple.c +549 -0
- 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 +9 -4
- 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} +10 -10
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg.rb +8 -6
- data/lib/pg/basic_type_mapping.rb +121 -25
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +23 -2
- data/lib/pg/connection.rb +22 -3
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +14 -2
- data/lib/pg/text_decoder.rb +21 -26
- data/lib/pg/text_encoder.rb +32 -8
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/spec/helpers.rb +52 -20
- data/spec/pg/basic_type_mapping_spec.rb +362 -37
- data/spec/pg/connection_spec.rb +376 -146
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +240 -15
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +2 -2
- data/spec/pg/type_map_by_column_spec.rb +6 -2
- data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
- data/spec/pg/type_map_by_oid_spec.rb +3 -3
- 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 +363 -17
- data/spec/pg_spec.rb +1 -1
- metadata +47 -47
- metadata.gz.sig +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -132,7 +132,7 @@ describe PG::TypeMapByClass do
|
|
132
132
|
it "should raise error on invalid coder object" do
|
133
133
|
tm[TrueClass] = "dummy"
|
134
134
|
expect{
|
135
|
-
|
135
|
+
@conn.exec_params( "SELECT $1", [true], 0, tm )
|
136
136
|
}.to raise_error(NoMethodError, /undefined method.*call/)
|
137
137
|
end
|
138
138
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -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
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require_relative '../helpers'
|
@@ -54,8 +54,8 @@ describe PG::TypeMapByOid do
|
|
54
54
|
end
|
55
55
|
|
56
56
|
it "should check format when deleting coders" do
|
57
|
-
expect{ tm.rm_coder
|
58
|
-
expect{ tm.rm_coder
|
57
|
+
expect{ tm.rm_coder(2, 123) }.to raise_error(ArgumentError)
|
58
|
+
expect{ tm.rm_coder(-1, 123) }.to raise_error(ArgumentError)
|
59
59
|
end
|
60
60
|
|
61
61
|
it "should check format when adding coders" do
|
data/spec/pg/type_map_spec.rb
CHANGED
data/spec/pg/type_spec.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
|
1
|
+
# -*- rspec -*-
|
2
2
|
# encoding: utf-8
|
3
3
|
|
4
4
|
require 'pg'
|
5
|
+
require 'time'
|
5
6
|
|
6
7
|
|
7
8
|
describe "PG::Type derivations" do
|
@@ -11,10 +12,14 @@ describe "PG::Type derivations" do
|
|
11
12
|
let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
|
12
13
|
let!(:textenc_float) { PG::TextEncoder::Float.new }
|
13
14
|
let!(:textdec_float) { PG::TextDecoder::Float.new }
|
15
|
+
let!(:textenc_numeric) { PG::TextEncoder::Numeric.new }
|
14
16
|
let!(:textenc_string) { PG::TextEncoder::String.new }
|
15
17
|
let!(:textdec_string) { PG::TextDecoder::String.new }
|
16
18
|
let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
|
17
19
|
let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
|
20
|
+
let!(:textenc_timestamputc) { PG::TextEncoder::TimestampUtc.new }
|
21
|
+
let!(:textdec_timestamputc) { PG::TextDecoder::TimestampUtc.new }
|
22
|
+
let!(:textdec_timestampul) { PG::TextDecoder::TimestampUtcToLocal.new }
|
18
23
|
let!(:textenc_timestamptz) { PG::TextEncoder::TimestampWithTimeZone.new }
|
19
24
|
let!(:textdec_timestamptz) { PG::TextDecoder::TimestampWithTimeZone.new }
|
20
25
|
let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
|
@@ -96,21 +101,41 @@ describe "PG::Type derivations" do
|
|
96
101
|
end
|
97
102
|
|
98
103
|
context 'timestamps' do
|
99
|
-
it 'decodes timestamps without timezone' do
|
100
|
-
expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456') ).
|
101
|
-
to
|
104
|
+
it 'decodes timestamps without timezone as local time' do
|
105
|
+
expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
|
106
|
+
to eq( Time.new(2016,1,2, 23,23,59.123456).iso8601(5) )
|
107
|
+
expect( textdec_timestamp.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
|
108
|
+
to eq( Time.new(2016,8,2, 23,23,59.123456).iso8601(5) )
|
109
|
+
end
|
110
|
+
it 'decodes timestamps with UTC time and returns UTC timezone' do
|
111
|
+
expect( textdec_timestamputc.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
|
112
|
+
to eq( Time.utc(2016,1,2, 23,23,59.123456).iso8601(5) )
|
113
|
+
expect( textdec_timestamputc.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
|
114
|
+
to eq( Time.utc(2016,8,2, 23,23,59.123456).iso8601(5) )
|
115
|
+
end
|
116
|
+
it 'decodes timestamps with UTC time and returns local timezone' do
|
117
|
+
expect( textdec_timestampul.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
|
118
|
+
to eq( Time.utc(2016,1,2, 23,23,59.123456).getlocal.iso8601(5) )
|
119
|
+
expect( textdec_timestampul.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
|
120
|
+
to eq( Time.utc(2016,8,2, 23,23,59.123456).getlocal.iso8601(5) )
|
102
121
|
end
|
103
122
|
it 'decodes timestamps with hour timezone' do
|
104
|
-
expect( textdec_timestamptz.decode('
|
105
|
-
to
|
106
|
-
expect( textdec_timestamptz.decode('
|
107
|
-
to
|
123
|
+
expect( textdec_timestamptz.decode('2016-01-02 23:23:59.123456-04').iso8601(5) ).
|
124
|
+
to eq( Time.new(2016,1,2, 23,23,59.123456, "-04:00").iso8601(5) )
|
125
|
+
expect( textdec_timestamptz.decode('2016-08-02 23:23:59.123456+10').iso8601(5) ).
|
126
|
+
to eq( Time.new(2016,8,2, 23,23,59.123456, "+10:00").iso8601(5) )
|
127
|
+
expect( textdec_timestamptz.decode('1913-12-31 23:58:59.1231-03').iso8601(5) ).
|
128
|
+
to eq( Time.new(1913, 12, 31, 23, 58, 59.1231, "-03:00").iso8601(5) )
|
129
|
+
expect( textdec_timestamptz.decode('4714-11-24 23:58:59.1231-03 BC').iso8601(5) ).
|
130
|
+
to eq( Time.new(-4713, 11, 24, 23, 58, 59.1231, "-03:00").iso8601(5) )
|
131
|
+
expect( textdec_timestamptz.decode('294276-12-31 23:58:59.1231+03').iso8601(5) ).
|
132
|
+
to eq( Time.new(294276, 12, 31, 23, 58, 59.1231, "+03:00").iso8601(5) )
|
108
133
|
end
|
109
134
|
it 'decodes timestamps with hour:minute timezone' do
|
110
135
|
expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04:15') ).
|
111
136
|
to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:15") )
|
112
|
-
expect( textdec_timestamptz.decode('2015-
|
113
|
-
to be_within(0.000001).of( Time.new(2015,
|
137
|
+
expect( textdec_timestamptz.decode('2015-07-26 17:26:42.691511-04:30') ).
|
138
|
+
to be_within(0.000001).of( Time.new(2015,07,26, 17, 26, 42.691511, "-04:30") )
|
114
139
|
expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10:45') ).
|
115
140
|
to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:45") )
|
116
141
|
end
|
@@ -121,6 +146,81 @@ describe "PG::Type derivations" do
|
|
121
146
|
expect( textdec_timestamptz.decode('1916-01-01 00:00:00-00:25:21') ).
|
122
147
|
to be_within(0.000001).of( Time.new(1916, 1, 1, 0, 0, 0, "-00:25:21") )
|
123
148
|
end
|
149
|
+
it 'decodes timestamps with date before 1823' do
|
150
|
+
expect( textdec_timestamp.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
|
151
|
+
to eq( Time.new(1822,01,02, 23, 23, 59.123456).iso8601(5) )
|
152
|
+
expect( textdec_timestamputc.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
|
153
|
+
to eq( Time.utc(1822,01,02, 23, 23, 59.123456).iso8601(5) )
|
154
|
+
expect( textdec_timestampul.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
|
155
|
+
to eq( Time.utc(1822,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
|
156
|
+
expect( textdec_timestamptz.decode('1822-01-02 23:23:59.123456+04').iso8601(5) ).
|
157
|
+
to eq( Time.new(1822,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
|
158
|
+
end
|
159
|
+
it 'decodes timestamps with date after 2116' do
|
160
|
+
expect( textdec_timestamp.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
|
161
|
+
to eq( Time.new(2117,01,02, 23, 23, 59.123456).iso8601(5) )
|
162
|
+
expect( textdec_timestamputc.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
|
163
|
+
to eq( Time.utc(2117,01,02, 23, 23, 59.123456).iso8601(5) )
|
164
|
+
expect( textdec_timestampul.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
|
165
|
+
to eq( Time.utc(2117,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
|
166
|
+
expect( textdec_timestamptz.decode('2117-01-02 23:23:59.123456+04').iso8601(5) ).
|
167
|
+
to eq( Time.new(2117,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
|
168
|
+
end
|
169
|
+
it 'decodes timestamps with variable number of digits for the useconds part' do
|
170
|
+
sec = "59.12345678912345"
|
171
|
+
(4..sec.length).each do |i|
|
172
|
+
expect( textdec_timestamp.decode("2016-01-02 23:23:#{sec[0,i]}") ).
|
173
|
+
to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, sec[0,i].to_f) )
|
174
|
+
end
|
175
|
+
end
|
176
|
+
it 'decodes timestamps with leap-second' do
|
177
|
+
expect( textdec_timestamp.decode('1998-12-31 23:59:60.1234') ).
|
178
|
+
to be_within(0.000001).of( Time.new(1998,12,31, 23, 59, 60.1234) )
|
179
|
+
end
|
180
|
+
|
181
|
+
def textdec_timestamptz_decode_should_fail(str)
|
182
|
+
expect(textdec_timestamptz.decode(str)).to eq(str)
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'fails when the timestamp is an empty string' do
|
186
|
+
textdec_timestamptz_decode_should_fail('')
|
187
|
+
end
|
188
|
+
it 'fails when the timestamp contains values with less digits than expected' do
|
189
|
+
textdec_timestamptz_decode_should_fail('2016-0-02 23:23:59.123456+00:25:21')
|
190
|
+
textdec_timestamptz_decode_should_fail('2016-01-0 23:23:59.123456+00:25:21')
|
191
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 2:23:59.123456+00:25:21')
|
192
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:2:59.123456+00:25:21')
|
193
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:5.123456+00:25:21')
|
194
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.+00:25:21')
|
195
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+0:25:21')
|
196
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:2:21')
|
197
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:2')
|
198
|
+
end
|
199
|
+
it 'fails when the timestamp contains values with more digits than expected' do
|
200
|
+
textdec_timestamptz_decode_should_fail('2016-011-02 23:23:59.123456+00:25:21')
|
201
|
+
textdec_timestamptz_decode_should_fail('2016-01-022 23:23:59.123456+00:25:21')
|
202
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 233:23:59.123456+00:25:21')
|
203
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:233:59.123456+00:25:21')
|
204
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:599.123456+00:25:21')
|
205
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+000:25:21')
|
206
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:255:21')
|
207
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:211')
|
208
|
+
end
|
209
|
+
it 'fails when the timestamp contains values with invalid characters' do
|
210
|
+
str = '2013-01-02 23:23:59.123456+00:25:21'
|
211
|
+
str.length.times do |i|
|
212
|
+
textdec_timestamptz_decode_should_fail(str[0,i] + "x" + str[i+1..-1])
|
213
|
+
end
|
214
|
+
end
|
215
|
+
it 'fails when the timestamp contains leading characters' do
|
216
|
+
textdec_timestamptz_decode_should_fail(' 2016-01-02 23:23:59.123456')
|
217
|
+
end
|
218
|
+
it 'fails when the timestamp contains trailing characters' do
|
219
|
+
textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456 ')
|
220
|
+
end
|
221
|
+
it 'fails when the timestamp contains non ASCII character' do
|
222
|
+
textdec_timestamptz_decode_should_fail('2016-01ª02 23:23:59.123456')
|
223
|
+
end
|
124
224
|
end
|
125
225
|
|
126
226
|
context 'identifier quotation' do
|
@@ -155,6 +255,11 @@ describe "PG::Type derivations" do
|
|
155
255
|
expect( textdec_string.decode( nil )).to be_nil
|
156
256
|
expect( textdec_int.decode( nil )).to be_nil
|
157
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
|
158
263
|
end
|
159
264
|
|
160
265
|
describe '#encode' do
|
@@ -205,16 +310,98 @@ describe "PG::Type derivations" do
|
|
205
310
|
end
|
206
311
|
end
|
207
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
|
+
|
208
360
|
it "should encode special floats equally to Float#to_s" do
|
209
361
|
expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
|
210
362
|
expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
|
211
363
|
expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
|
212
364
|
end
|
213
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
|
+
|
214
380
|
it "encodes binary string to bytea" do
|
215
381
|
expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
|
216
382
|
end
|
217
383
|
|
384
|
+
context 'timestamps' do
|
385
|
+
it 'encodes timestamps without timezone' do
|
386
|
+
expect( textenc_timestamp.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
|
387
|
+
to match( /^2016-01-02 23:23:59.12345\d+$/ )
|
388
|
+
expect( textenc_timestamp.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
|
389
|
+
to match( /^2016-08-02 23:23:59.12345\d+$/ )
|
390
|
+
end
|
391
|
+
it 'encodes timestamps with UTC timezone' do
|
392
|
+
expect( textenc_timestamputc.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
|
393
|
+
to match( /^2016-01-02 20:23:59.12345\d+$/ )
|
394
|
+
expect( textenc_timestamputc.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
|
395
|
+
to match( /^2016-08-02 20:23:59.12345\d+$/ )
|
396
|
+
end
|
397
|
+
it 'encodes timestamps with hour timezone' do
|
398
|
+
expect( textenc_timestamptz.encode(Time.new(2016,1,02, 23, 23, 59.123456, -4*60*60)) ).
|
399
|
+
to match( /^2016-01-02 23:23:59.12345\d+ \-04:00$/ )
|
400
|
+
expect( textenc_timestamptz.encode(Time.new(2016,8,02, 23, 23, 59.123456, 10*60*60)) ).
|
401
|
+
to match( /^2016-08-02 23:23:59.12345\d+ \+10:00$/ )
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
218
405
|
context 'identifier quotation' do
|
219
406
|
it 'should quote and escape identifier' do
|
220
407
|
quoted_type = PG::TextEncoder::Identifier.new
|
@@ -256,6 +443,11 @@ describe "PG::Type derivations" do
|
|
256
443
|
expect( textenc_string.encode( nil )).to be_nil
|
257
444
|
expect( textenc_int.encode( nil )).to be_nil
|
258
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
|
259
451
|
end
|
260
452
|
|
261
453
|
it "should be possible to marshal encoders" do
|
@@ -272,7 +464,7 @@ describe "PG::Type derivations" do
|
|
272
464
|
|
273
465
|
it "should respond to to_h" do
|
274
466
|
expect( textenc_int.to_h ).to eq( {
|
275
|
-
name: 'Integer', oid: 23, format: 0
|
467
|
+
name: 'Integer', oid: 23, format: 0, flags: 0
|
276
468
|
} )
|
277
469
|
end
|
278
470
|
|
@@ -303,6 +495,7 @@ describe "PG::Type derivations" do
|
|
303
495
|
describe "Array types" do
|
304
496
|
let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
|
305
497
|
let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
|
498
|
+
let!(:textdec_string_array_raise) { PG::TextDecoder::Array.new elements_type: textdec_string, flags: PG::Coder:: FORMAT_ERROR_TO_RAISE }
|
306
499
|
let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
|
307
500
|
let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
|
308
501
|
let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
|
@@ -368,6 +561,57 @@ describe "PG::Type derivations" do
|
|
368
561
|
it 'respects a different delimiter' do
|
369
562
|
expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
|
370
563
|
end
|
564
|
+
|
565
|
+
it 'ignores array dimensions' do
|
566
|
+
expect( textdec_string_array.decode(%[[2:4]={1,2,3}]) ).to eq( ['1','2','3'] )
|
567
|
+
expect( textdec_string_array.decode(%[[]={1,2,3}]) ).to eq( ['1','2','3'] )
|
568
|
+
expect( textdec_string_array.decode(%[ [-1:+2]= {4,3,2,1}]) ).to eq( ['4','3','2','1'] )
|
569
|
+
end
|
570
|
+
|
571
|
+
it 'ignores spaces after array' do
|
572
|
+
expect( textdec_string_array.decode(%[[2:4]={1,2,3} ]) ).to eq( ['1','2','3'] )
|
573
|
+
expect( textdec_string_array.decode(%[{1,2,3} ]) ).to eq( ['1','2','3'] )
|
574
|
+
end
|
575
|
+
|
576
|
+
describe "with malformed syntax are deprecated" do
|
577
|
+
it 'accepts broken array dimensions' do
|
578
|
+
expect( textdec_string_array.decode(%([2:4={1,2,3})) ).to eq([['1','2','3']])
|
579
|
+
expect( textdec_string_array.decode(%(2:4]={1,2,3})) ).to eq([['1','2','3']])
|
580
|
+
expect( textdec_string_array.decode(%(={1,2,3})) ).to eq([['1','2','3']])
|
581
|
+
expect( textdec_string_array.decode(%([x]={1,2,3})) ).to eq([['1','2','3']])
|
582
|
+
expect( textdec_string_array.decode(%([]{1,2,3})) ).to eq([['1','2','3']])
|
583
|
+
expect( textdec_string_array.decode(%(1,2,3)) ).to eq(['','2'])
|
584
|
+
end
|
585
|
+
|
586
|
+
it 'accepts malformed arrays' do
|
587
|
+
expect( textdec_string_array.decode(%({1,2,3)) ).to eq(['1','2'])
|
588
|
+
expect( textdec_string_array.decode(%({1,2,3}})) ).to eq(['1','2','3'])
|
589
|
+
expect( textdec_string_array.decode(%({1,2,3}x)) ).to eq(['1','2','3'])
|
590
|
+
expect( textdec_string_array.decode(%({{1,2},{2,3})) ).to eq([['1','2'],['2','3']])
|
591
|
+
expect( textdec_string_array.decode(%({{1,2},{2,3}}x)) ).to eq([['1','2'],['2','3']])
|
592
|
+
expect( textdec_string_array.decode(%({[1,2},{2,3}}})) ).to eq(['[1','2'])
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
describe "with malformed syntax are raised with pg-2.0+" do
|
597
|
+
it 'complains about broken array dimensions' do
|
598
|
+
expect{ textdec_string_array_raise.decode(%([2:4={1,2,3})) }.to raise_error(TypeError)
|
599
|
+
expect{ textdec_string_array_raise.decode(%(2:4]={1,2,3})) }.to raise_error(TypeError)
|
600
|
+
expect{ textdec_string_array_raise.decode(%(={1,2,3})) }.to raise_error(TypeError)
|
601
|
+
expect{ textdec_string_array_raise.decode(%([x]={1,2,3})) }.to raise_error(TypeError)
|
602
|
+
expect{ textdec_string_array_raise.decode(%([]{1,2,3})) }.to raise_error(TypeError)
|
603
|
+
expect{ textdec_string_array_raise.decode(%(1,2,3)) }.to raise_error(TypeError)
|
604
|
+
end
|
605
|
+
|
606
|
+
it 'complains about malformed array' do
|
607
|
+
expect{ textdec_string_array_raise.decode(%({1,2,3)) }.to raise_error(TypeError)
|
608
|
+
expect{ textdec_string_array_raise.decode(%({1,2,3}})) }.to raise_error(TypeError)
|
609
|
+
expect{ textdec_string_array_raise.decode(%({1,2,3}x)) }.to raise_error(TypeError)
|
610
|
+
expect{ textdec_string_array_raise.decode(%({{1,2},{2,3})) }.to raise_error(TypeError)
|
611
|
+
expect{ textdec_string_array_raise.decode(%({{1,2},{2,3}}x)) }.to raise_error(TypeError)
|
612
|
+
expect{ textdec_string_array_raise.decode(%({[1,2},{2,3}}})) }.to raise_error(TypeError)
|
613
|
+
end
|
614
|
+
end
|
371
615
|
end
|
372
616
|
|
373
617
|
context 'bytea' do
|
@@ -452,7 +696,7 @@ describe "PG::Type derivations" do
|
|
452
696
|
expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
|
453
697
|
end
|
454
698
|
it 'encodes an array of float8 with sub arrays' do
|
455
|
-
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*")))
|
456
700
|
end
|
457
701
|
end
|
458
702
|
context 'two dimensional arrays' do
|
@@ -540,15 +784,15 @@ describe "PG::Type derivations" do
|
|
540
784
|
expect( lt.to_h ).to eq( textenc_int_array.to_h )
|
541
785
|
end
|
542
786
|
|
543
|
-
it "should be possible to marshal
|
544
|
-
mt = Marshal.dump(
|
787
|
+
it "should be possible to marshal decoders" do
|
788
|
+
mt = Marshal.dump(textdec_string_array_raise)
|
545
789
|
lt = Marshal.load(mt)
|
546
|
-
expect( lt.to_h ).to eq(
|
790
|
+
expect( lt.to_h ).to eq( textdec_string_array_raise.to_h )
|
547
791
|
end
|
548
792
|
|
549
793
|
it "should respond to to_h" do
|
550
794
|
expect( textenc_int_array.to_h ).to eq( {
|
551
|
-
name: nil, oid: 0, format: 0,
|
795
|
+
name: nil, oid: 0, format: 0, flags: 0,
|
552
796
|
elements_type: textenc_int, needs_quotation: false, delimiter: ','
|
553
797
|
} )
|
554
798
|
end
|
@@ -746,7 +990,7 @@ describe "PG::Type derivations" do
|
|
746
990
|
end
|
747
991
|
|
748
992
|
describe '#decode' do
|
749
|
-
it "should decode
|
993
|
+
it "should decode COPY text format to array of strings" do
|
750
994
|
expect( decoder.decode("123\t \0#\t#\n#\r#\\ \t234\t#\x01#\002\n".gsub("#", "\\"))).to eq( ["123", " \0\t\n\r\\ ", "234", "\x01\x02"] )
|
751
995
|
end
|
752
996
|
|
@@ -774,4 +1018,106 @@ describe "PG::Type derivations" do
|
|
774
1018
|
end
|
775
1019
|
end
|
776
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
|
777
1123
|
end
|