pg 1.0.0 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +0 -6595
  5. data/History.rdoc +156 -0
  6. data/Manifest.txt +8 -2
  7. data/README-Windows.rdoc +4 -4
  8. data/README.ja.rdoc +1 -2
  9. data/README.rdoc +55 -9
  10. data/Rakefile +9 -7
  11. data/Rakefile.cross +58 -57
  12. data/ext/errorcodes.def +68 -0
  13. data/ext/errorcodes.rb +1 -1
  14. data/ext/errorcodes.txt +19 -2
  15. data/ext/extconf.rb +7 -5
  16. data/ext/pg.c +141 -98
  17. data/ext/pg.h +64 -21
  18. data/ext/pg_binary_decoder.c +82 -15
  19. data/ext/pg_binary_encoder.c +13 -12
  20. data/ext/pg_coder.c +73 -12
  21. data/ext/pg_connection.c +625 -346
  22. data/ext/pg_copy_coder.c +16 -8
  23. data/ext/pg_record_coder.c +491 -0
  24. data/ext/pg_result.c +571 -191
  25. data/ext/pg_text_decoder.c +606 -40
  26. data/ext/pg_text_encoder.c +185 -54
  27. data/ext/pg_tuple.c +549 -0
  28. data/ext/pg_type_map.c +1 -1
  29. data/ext/pg_type_map_all_strings.c +4 -4
  30. data/ext/pg_type_map_by_class.c +9 -4
  31. data/ext/pg_type_map_by_column.c +7 -6
  32. data/ext/pg_type_map_by_mri_type.c +1 -1
  33. data/ext/pg_type_map_by_oid.c +3 -2
  34. data/ext/pg_type_map_in_ruby.c +1 -1
  35. data/ext/{util.c → pg_util.c} +10 -10
  36. data/ext/{util.h → pg_util.h} +2 -2
  37. data/lib/pg.rb +8 -6
  38. data/lib/pg/basic_type_mapping.rb +121 -25
  39. data/lib/pg/binary_decoder.rb +23 -0
  40. data/lib/pg/coder.rb +23 -2
  41. data/lib/pg/connection.rb +22 -3
  42. data/lib/pg/constants.rb +2 -1
  43. data/lib/pg/exceptions.rb +2 -1
  44. data/lib/pg/result.rb +14 -2
  45. data/lib/pg/text_decoder.rb +21 -26
  46. data/lib/pg/text_encoder.rb +32 -8
  47. data/lib/pg/tuple.rb +30 -0
  48. data/lib/pg/type_map_by_column.rb +3 -2
  49. data/spec/helpers.rb +52 -20
  50. data/spec/pg/basic_type_mapping_spec.rb +362 -37
  51. data/spec/pg/connection_spec.rb +376 -146
  52. data/spec/pg/connection_sync_spec.rb +41 -0
  53. data/spec/pg/result_spec.rb +240 -15
  54. data/spec/pg/tuple_spec.rb +333 -0
  55. data/spec/pg/type_map_by_class_spec.rb +2 -2
  56. data/spec/pg/type_map_by_column_spec.rb +6 -2
  57. data/spec/pg/type_map_by_mri_type_spec.rb +1 -1
  58. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  59. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  60. data/spec/pg/type_map_spec.rb +1 -1
  61. data/spec/pg/type_spec.rb +363 -17
  62. data/spec/pg_spec.rb +1 -1
  63. metadata +47 -47
  64. metadata.gz.sig +0 -0
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
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
- res = @conn.exec_params( "SELECT $1", [true], 0, tm )
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
- #!/usr/bin/env rspec
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
- expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:0 TEXT:0 FLOAT4:0 pass_through:1 nil>" )
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
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
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 2, 123 }.to raise_error(ArgumentError)
58
- expect{ tm.rm_coder -1, 123 }.to raise_error(ArgumentError)
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,7 +1,8 @@
1
- #!/usr/bin/env rspec
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 be_within(0.000001).of( Time.new(2016,01,02, 23, 23, 59.123456) )
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('2015-01-26 17:26:42.691511-04') ).
105
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:00") )
106
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10') ).
107
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:00") )
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-01-26 17:26:42.691511-0430') ).
113
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:30") )
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.00221,[3.31,-441]],[nil,6.61],-7.71]) ).to match(Regexp.new(%[^{1.0001*E+*03,{-2.2*E-*03,{3.3*E+*00,-4.4*E+*02}},{NULL,6.6*E+*00},-7.7*E+*00}$].gsub(/([\.\+\{\}\,])/, "\\\\\\1").gsub(/\*/, "\\d*")))
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 encoders" do
544
- mt = Marshal.dump(textdec_int_array)
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( textdec_int_array.to_h )
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 different types of Ruby objects" do
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