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.
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