pg 0.17.1 → 0.18.4

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 (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/ChangeLog +2407 -2
  4. data/History.rdoc +68 -0
  5. data/Manifest.txt +29 -1
  6. data/README-Windows.rdoc +15 -26
  7. data/README.rdoc +52 -2
  8. data/Rakefile +56 -18
  9. data/Rakefile.cross +77 -49
  10. data/ext/extconf.rb +33 -26
  11. data/ext/pg.c +142 -21
  12. data/ext/pg.h +242 -6
  13. data/ext/pg_binary_decoder.c +162 -0
  14. data/ext/pg_binary_encoder.c +162 -0
  15. data/ext/pg_coder.c +479 -0
  16. data/ext/pg_connection.c +858 -553
  17. data/ext/pg_copy_coder.c +561 -0
  18. data/ext/pg_errors.c +6 -0
  19. data/ext/pg_result.c +479 -128
  20. data/ext/pg_text_decoder.c +421 -0
  21. data/ext/pg_text_encoder.c +663 -0
  22. data/ext/pg_type_map.c +159 -0
  23. data/ext/pg_type_map_all_strings.c +116 -0
  24. data/ext/pg_type_map_by_class.c +239 -0
  25. data/ext/pg_type_map_by_column.c +312 -0
  26. data/ext/pg_type_map_by_mri_type.c +284 -0
  27. data/ext/pg_type_map_by_oid.c +355 -0
  28. data/ext/pg_type_map_in_ruby.c +299 -0
  29. data/ext/util.c +149 -0
  30. data/ext/util.h +65 -0
  31. data/lib/pg/basic_type_mapping.rb +399 -0
  32. data/lib/pg/coder.rb +83 -0
  33. data/lib/pg/connection.rb +81 -29
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +44 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/pg.rb +12 -2
  39. data/spec/{lib/helpers.rb → helpers.rb} +101 -39
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +516 -218
  42. data/spec/pg/result_spec.rb +216 -112
  43. data/spec/pg/type_map_by_class_spec.rb +138 -0
  44. data/spec/pg/type_map_by_column_spec.rb +222 -0
  45. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  46. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  47. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  48. data/spec/pg/type_map_spec.rb +22 -0
  49. data/spec/pg/type_spec.rb +697 -0
  50. data/spec/pg_spec.rb +24 -18
  51. data.tar.gz.sig +0 -0
  52. metadata +111 -45
  53. metadata.gz.sig +0 -0
@@ -0,0 +1,697 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require 'pg'
5
+
6
+
7
+ describe "PG::Type derivations" do
8
+ let!(:textenc_int) { PG::TextEncoder::Integer.new name: 'Integer', oid: 23 }
9
+ let!(:textdec_int) { PG::TextDecoder::Integer.new name: 'Integer', oid: 23 }
10
+ let!(:textenc_boolean) { PG::TextEncoder::Boolean.new }
11
+ let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
12
+ let!(:textenc_float) { PG::TextEncoder::Float.new }
13
+ let!(:textdec_float) { PG::TextDecoder::Float.new }
14
+ let!(:textenc_string) { PG::TextEncoder::String.new }
15
+ let!(:textdec_string) { PG::TextDecoder::String.new }
16
+ let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
17
+ let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
18
+ let!(:textenc_timestamptz) { PG::TextEncoder::TimestampWithTimeZone.new }
19
+ let!(:textdec_timestamptz) { PG::TextDecoder::TimestampWithTimeZone.new }
20
+ let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
21
+ let!(:textdec_bytea) { PG::TextDecoder::Bytea.new }
22
+ let!(:binaryenc_int2) { PG::BinaryEncoder::Int2.new }
23
+ let!(:binaryenc_int4) { PG::BinaryEncoder::Int4.new }
24
+ let!(:binaryenc_int8) { PG::BinaryEncoder::Int8.new }
25
+ let!(:binarydec_integer) { PG::BinaryDecoder::Integer.new }
26
+
27
+ let!(:intenc_incrementer) do
28
+ Class.new(PG::SimpleEncoder) do
29
+ def encode(value)
30
+ (value.to_i + 1).to_s + " "
31
+ end
32
+ end.new
33
+ end
34
+ let!(:intdec_incrementer) do
35
+ Class.new(PG::SimpleDecoder) do
36
+ def decode(string, tuple=nil, field=nil)
37
+ string.to_i+1
38
+ end
39
+ end.new
40
+ end
41
+
42
+ let!(:intenc_incrementer_with_int_result) do
43
+ Class.new(PG::SimpleEncoder) do
44
+ def encode(value)
45
+ value.to_i+1
46
+ end
47
+ end.new
48
+ end
49
+
50
+ it "shouldn't be possible to build a PG::Type directly" do
51
+ expect{ PG::Coder.new }.to raise_error(TypeError, /cannot/)
52
+ end
53
+
54
+ describe PG::SimpleCoder do
55
+ describe '#decode' do
56
+ it "should offer decode method with tuple/field" do
57
+ res = textdec_int.decode("123", 1, 1)
58
+ expect( res ).to eq( 123 )
59
+ end
60
+
61
+ it "should offer decode method without tuple/field" do
62
+ res = textdec_int.decode("234")
63
+ expect( res ).to eq( 234 )
64
+ end
65
+
66
+ it "should decode with ruby decoder" do
67
+ expect( intdec_incrementer.decode("3") ).to eq( 4 )
68
+ end
69
+
70
+ it "should decode integers of different lengths form text format" do
71
+ 30.times do |zeros|
72
+ expect( textdec_int.decode("1" + "0"*zeros) ).to eq( 10 ** zeros )
73
+ expect( textdec_int.decode(zeros==0 ? "0" : "9"*zeros) ).to eq( 10 ** zeros - 1 )
74
+ expect( textdec_int.decode("-1" + "0"*zeros) ).to eq( -10 ** zeros )
75
+ expect( textdec_int.decode(zeros==0 ? "0" : "-" + "9"*zeros) ).to eq( -10 ** zeros + 1 )
76
+ end
77
+ 66.times do |bits|
78
+ expect( textdec_int.decode((2 ** bits).to_s) ).to eq( 2 ** bits )
79
+ expect( textdec_int.decode((2 ** bits - 1).to_s) ).to eq( 2 ** bits - 1 )
80
+ expect( textdec_int.decode((-2 ** bits).to_s) ).to eq( -2 ** bits )
81
+ expect( textdec_int.decode((-2 ** bits + 1).to_s) ).to eq( -2 ** bits + 1 )
82
+ end
83
+ end
84
+
85
+ it 'decodes bytea to a binary string' do
86
+ expect( textdec_bytea.decode("\\x00010203EF") ).to eq( "\x00\x01\x02\x03\xef".b )
87
+ expect( textdec_bytea.decode("\\377\\000") ).to eq( "\xff\0".b )
88
+ end
89
+
90
+ context 'timestamps' do
91
+ it 'decodes timestamps without timezone' do
92
+ expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456') ).
93
+ to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, 59.123456) )
94
+ end
95
+ it 'decodes timestamps with hour timezone' do
96
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04') ).
97
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:00") )
98
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10') ).
99
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:00") )
100
+ end
101
+ it 'decodes timestamps with hour:minute timezone' do
102
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04:15') ).
103
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:15") )
104
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-0430') ).
105
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:30") )
106
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10:45') ).
107
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:45") )
108
+ end
109
+ it 'decodes timestamps with hour:minute:sec timezone' do
110
+ # SET TIME ZONE 'Europe/Dublin'; -- Was UTC−00:25:21 until 1916
111
+ # SELECT '1900-01-01'::timestamptz;
112
+ # -- "1900-01-01 00:00:00-00:25:21"
113
+ expect( textdec_timestamptz.decode('1916-01-01 00:00:00-00:25:21') ).
114
+ to be_within(0.000001).of( Time.new(1916, 1, 1, 0, 0, 0, "-00:25:21") )
115
+ end
116
+ end
117
+
118
+ context 'identifier quotation' do
119
+ it 'should build an array out of an quoted identifier string' do
120
+ quoted_type = PG::TextDecoder::Identifier.new
121
+ expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] )
122
+ expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] )
123
+ end
124
+
125
+ it 'should split unquoted identifier string' do
126
+ quoted_type = PG::TextDecoder::Identifier.new
127
+ expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] )
128
+ expect( quoted_type.decode(%[a]) ).to eq( ['a'] )
129
+ end
130
+ end
131
+
132
+ it "should raise when decode method is called with wrong args" do
133
+ expect{ textdec_int.decode() }.to raise_error(ArgumentError)
134
+ expect{ textdec_int.decode("123", 2, 3, 4) }.to raise_error(ArgumentError)
135
+ expect{ textdec_int.decode(2, 3, 4) }.to raise_error(TypeError)
136
+ expect( intdec_incrementer.decode(2, 3, 4) ).to eq( 3 )
137
+ end
138
+
139
+ it "should pass through nil values" do
140
+ expect( textdec_string.decode( nil )).to be_nil
141
+ expect( textdec_int.decode( nil )).to be_nil
142
+ end
143
+ end
144
+
145
+ describe '#encode' do
146
+ it "should offer encode method for text type" do
147
+ res = textenc_int.encode(123)
148
+ expect( res ).to eq( "123" )
149
+ end
150
+
151
+ it "should offer encode method for binary type" do
152
+ res = binaryenc_int8.encode(123)
153
+ expect( res ).to eq( [123].pack("q>") )
154
+ end
155
+
156
+ it "should encode integers from string to binary format" do
157
+ expect( binaryenc_int2.encode(" -123 ") ).to eq( [-123].pack("s>") )
158
+ expect( binaryenc_int4.encode(" -123 ") ).to eq( [-123].pack("l>") )
159
+ expect( binaryenc_int8.encode(" -123 ") ).to eq( [-123].pack("q>") )
160
+ expect( binaryenc_int2.encode(" 123-xyz ") ).to eq( [123].pack("s>") )
161
+ expect( binaryenc_int4.encode(" 123-xyz ") ).to eq( [123].pack("l>") )
162
+ expect( binaryenc_int8.encode(" 123-xyz ") ).to eq( [123].pack("q>") )
163
+ end
164
+
165
+ it "should encode integers of different lengths to text format" do
166
+ 30.times do |zeros|
167
+ expect( textenc_int.encode(10 ** zeros) ).to eq( "1" + "0"*zeros )
168
+ expect( textenc_int.encode(10 ** zeros - 1) ).to eq( zeros==0 ? "0" : "9"*zeros )
169
+ expect( textenc_int.encode(-10 ** zeros) ).to eq( "-1" + "0"*zeros )
170
+ expect( textenc_int.encode(-10 ** zeros + 1) ).to eq( zeros==0 ? "0" : "-" + "9"*zeros )
171
+ end
172
+ 66.times do |bits|
173
+ expect( textenc_int.encode(2 ** bits) ).to eq( (2 ** bits).to_s )
174
+ expect( textenc_int.encode(2 ** bits - 1) ).to eq( (2 ** bits - 1).to_s )
175
+ expect( textenc_int.encode(-2 ** bits) ).to eq( (-2 ** bits).to_s )
176
+ expect( textenc_int.encode(-2 ** bits + 1) ).to eq( (-2 ** bits + 1).to_s )
177
+ end
178
+ end
179
+
180
+ it "should encode integers from string to text format" do
181
+ expect( textenc_int.encode(" -123 ") ).to eq( "-123" )
182
+ expect( textenc_int.encode(" 123-xyz ") ).to eq( "123" )
183
+ end
184
+
185
+ it "should encode boolean values" do
186
+ expect( textenc_boolean.encode(false) ).to eq( "f" )
187
+ expect( textenc_boolean.encode(true) ).to eq( "t" )
188
+ ["any", :other, "value", 0, 1, 2].each do |value|
189
+ expect( textenc_boolean.encode(value) ).to eq( value.to_s )
190
+ end
191
+ end
192
+
193
+ it "should encode special floats equally to Float#to_s" do
194
+ expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
195
+ expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
196
+ expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
197
+ end
198
+
199
+ it "encodes binary string to bytea" do
200
+ expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
201
+ end
202
+
203
+ context 'identifier quotation' do
204
+ it 'should quote and escape identifier' do
205
+ quoted_type = PG::TextEncoder::Identifier.new
206
+ expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] )
207
+ expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] )
208
+ expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] )
209
+ expect( quoted_type.encode( nil ) ).to be_nil
210
+ end
211
+
212
+ it "will raise a TypeError for invalid arguments to quote_ident" do
213
+ quoted_type = PG::TextEncoder::Identifier.new
214
+ expect{ quoted_type.encode( [nil] ) }.to raise_error(TypeError)
215
+ expect{ quoted_type.encode( [['a']] ) }.to raise_error(TypeError)
216
+ end
217
+ end
218
+
219
+ it "should encode with ruby encoder" do
220
+ expect( intenc_incrementer.encode(3) ).to eq( "4 " )
221
+ end
222
+
223
+ it "should return when ruby encoder returns non string values" do
224
+ expect( intenc_incrementer_with_int_result.encode(3) ).to eq( 4 )
225
+ end
226
+
227
+ it "should pass through nil values" do
228
+ expect( textenc_string.encode( nil )).to be_nil
229
+ expect( textenc_int.encode( nil )).to be_nil
230
+ end
231
+ end
232
+
233
+ it "should be possible to marshal encoders" do
234
+ mt = Marshal.dump(textenc_int)
235
+ lt = Marshal.load(mt)
236
+ expect( lt.to_h ).to eq( textenc_int.to_h )
237
+ end
238
+
239
+ it "should be possible to marshal decoders" do
240
+ mt = Marshal.dump(textdec_int)
241
+ lt = Marshal.load(mt)
242
+ expect( lt.to_h ).to eq( textdec_int.to_h )
243
+ end
244
+
245
+ it "should respond to to_h" do
246
+ expect( textenc_int.to_h ).to eq( {
247
+ name: 'Integer', oid: 23, format: 0
248
+ } )
249
+ end
250
+
251
+ it "should have reasonable default values" do
252
+ t = PG::TextEncoder::String.new
253
+ expect( t.format ).to eq( 0 )
254
+ expect( t.oid ).to eq( 0 )
255
+ expect( t.name ).to be_nil
256
+
257
+ t = PG::BinaryEncoder::Int4.new
258
+ expect( t.format ).to eq( 1 )
259
+ expect( t.oid ).to eq( 0 )
260
+ expect( t.name ).to be_nil
261
+
262
+ t = PG::TextDecoder::String.new
263
+ expect( t.format ).to eq( 0 )
264
+ expect( t.oid ).to eq( 0 )
265
+ expect( t.name ).to be_nil
266
+
267
+ t = PG::BinaryDecoder::String.new
268
+ expect( t.format ).to eq( 1 )
269
+ expect( t.oid ).to eq( 0 )
270
+ expect( t.name ).to be_nil
271
+ end
272
+ end
273
+
274
+ describe PG::CompositeCoder do
275
+ describe "Array types" do
276
+ let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
277
+ let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
278
+ let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
279
+ let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
280
+ let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
281
+ let!(:textdec_float_array) { PG::TextDecoder::Array.new elements_type: textdec_float, needs_quotation: false }
282
+ let!(:textenc_timestamp_array) { PG::TextEncoder::Array.new elements_type: textenc_timestamp, needs_quotation: false }
283
+ let!(:textdec_timestamp_array) { PG::TextDecoder::Array.new elements_type: textdec_timestamp, needs_quotation: false }
284
+ let!(:textenc_string_array_with_delimiter) { PG::TextEncoder::Array.new elements_type: textenc_string, delimiter: ';' }
285
+ let!(:textdec_string_array_with_delimiter) { PG::TextDecoder::Array.new elements_type: textdec_string, delimiter: ';' }
286
+ let!(:textdec_bytea_array) { PG::TextDecoder::Array.new elements_type: textdec_bytea }
287
+
288
+ #
289
+ # Array parser specs are thankfully borrowed from here:
290
+ # https://github.com/dockyard/pg_array_parser
291
+ #
292
+ describe '#decode' do
293
+ context 'one dimensional arrays' do
294
+ context 'empty' do
295
+ it 'returns an empty array' do
296
+ expect( textdec_string_array.decode(%[{}]) ).to eq( [] )
297
+ end
298
+ end
299
+
300
+ context 'no strings' do
301
+ it 'returns an array of strings' do
302
+ expect( textdec_string_array.decode(%[{1,2,3}]) ).to eq( ['1','2','3'] )
303
+ end
304
+ end
305
+
306
+ context 'NULL values' do
307
+ it 'returns an array of strings, with nils replacing NULL characters' do
308
+ expect( textdec_string_array.decode(%[{1,NULL,NULL}]) ).to eq( ['1',nil,nil] )
309
+ end
310
+ end
311
+
312
+ context 'quoted NULL' do
313
+ it 'returns an array with the word NULL' do
314
+ expect( textdec_string_array.decode(%[{1,"NULL",3}]) ).to eq( ['1','NULL','3'] )
315
+ end
316
+ end
317
+
318
+ context 'strings' do
319
+ it 'returns an array of strings when containing commas in a quoted string' do
320
+ expect( textdec_string_array.decode(%[{1,"2,3",4}]) ).to eq( ['1','2,3','4'] )
321
+ end
322
+
323
+ it 'returns an array of strings when containing an escaped quote' do
324
+ expect( textdec_string_array.decode(%[{1,"2\\",3",4}]) ).to eq( ['1','2",3','4'] )
325
+ end
326
+
327
+ it 'returns an array of strings when containing an escaped backslash' do
328
+ expect( textdec_string_array.decode(%[{1,"2\\\\",3,4}]) ).to eq( ['1','2\\','3','4'] )
329
+ expect( textdec_string_array.decode(%[{1,"2\\\\\\",3",4}]) ).to eq( ['1','2\\",3','4'] )
330
+ end
331
+
332
+ it 'returns an array containing empty strings' do
333
+ expect( textdec_string_array.decode(%[{1,"",3,""}]) ).to eq( ['1', '', '3', ''] )
334
+ end
335
+
336
+ it 'returns an array containing unicode strings' do
337
+ expect( textdec_string_array.decode(%[{"Paragraph 399(b)(i) – “valid leave” – meaning"}]) ).to eq(['Paragraph 399(b)(i) – “valid leave” – meaning'])
338
+ end
339
+
340
+ it 'respects a different delimiter' do
341
+ expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
342
+ end
343
+ end
344
+
345
+ context 'bytea' do
346
+ it 'returns an array of binary strings' do
347
+ expect( textdec_bytea_array.decode(%[{"\\\\x00010203EF","2,3",\\377}]) ).to eq( ["\x00\x01\x02\x03\xef".b,"2,3".b,"\xff".b] )
348
+ end
349
+ end
350
+
351
+ end
352
+
353
+ context 'two dimensional arrays' do
354
+ context 'empty' do
355
+ it 'returns an empty array' do
356
+ expect( textdec_string_array.decode(%[{{}}]) ).to eq( [[]] )
357
+ expect( textdec_string_array.decode(%[{{},{}}]) ).to eq( [[],[]] )
358
+ end
359
+ end
360
+ context 'no strings' do
361
+ it 'returns an array of strings with a sub array' do
362
+ expect( textdec_string_array.decode(%[{1,{2,3},4}]) ).to eq( ['1',['2','3'],'4'] )
363
+ end
364
+ end
365
+ context 'strings' do
366
+ it 'returns an array of strings with a sub array' do
367
+ expect( textdec_string_array.decode(%[{1,{"2,3"},4}]) ).to eq( ['1',['2,3'],'4'] )
368
+ end
369
+ it 'returns an array of strings with a sub array and a quoted }' do
370
+ expect( textdec_string_array.decode(%[{1,{"2,}3",NULL},4}]) ).to eq( ['1',['2,}3',nil],'4'] )
371
+ end
372
+ it 'returns an array of strings with a sub array and a quoted {' do
373
+ expect( textdec_string_array.decode(%[{1,{"2,{3"},4}]) ).to eq( ['1',['2,{3'],'4'] )
374
+ end
375
+ it 'returns an array of strings with a sub array and a quoted { and escaped quote' do
376
+ expect( textdec_string_array.decode(%[{1,{"2\\",{3"},4}]) ).to eq( ['1',['2",{3'],'4'] )
377
+ end
378
+ it 'returns an array of strings with a sub array with empty strings' do
379
+ expect( textdec_string_array.decode(%[{1,{""},4,{""}}]) ).to eq( ['1',[''],'4',['']] )
380
+ end
381
+ end
382
+ context 'timestamps' do
383
+ it 'decodes an array of timestamps with sub arrays' do
384
+ expect( textdec_timestamp_array.decode('{2014-12-31 00:00:00,{NULL,2016-01-02 23:23:59.0000000}}') ).
385
+ to eq( [Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59)]] )
386
+ end
387
+ end
388
+ end
389
+ context 'three dimensional arrays' do
390
+ context 'empty' do
391
+ it 'returns an empty array' do
392
+ expect( textdec_string_array.decode(%[{{{}}}]) ).to eq( [[[]]] )
393
+ expect( textdec_string_array.decode(%[{{{},{}},{{},{}}}]) ).to eq( [[[],[]],[[],[]]] )
394
+ end
395
+ end
396
+ it 'returns an array of strings with sub arrays' do
397
+ expect( textdec_string_array.decode(%[{1,{2,{3,4}},{NULL,6},7}]) ).to eq( ['1',['2',['3','4']],[nil,'6'],'7'] )
398
+ end
399
+ end
400
+
401
+ it 'should decode array of types with decoder in ruby space' do
402
+ array_type = PG::TextDecoder::Array.new elements_type: intdec_incrementer
403
+ expect( array_type.decode(%[{3,4}]) ).to eq( [4,5] )
404
+ end
405
+
406
+ it 'should decode array of nil types' do
407
+ array_type = PG::TextDecoder::Array.new elements_type: nil
408
+ expect( array_type.decode(%[{3,4}]) ).to eq( ['3','4'] )
409
+ end
410
+ end
411
+
412
+ describe '#encode' do
413
+ context 'three dimensional arrays' do
414
+ it 'encodes an array of strings and numbers with sub arrays' do
415
+ expect( textenc_string_array.encode(['1',['2',['3','4']],[nil,6],7.8]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7.8}] )
416
+ end
417
+ it 'encodes an array of strings with quotes' do
418
+ expect( textenc_string_array.encode(['',[' ',['{','}','\\',',','"','\t']]]) ).to eq( %[{"",{" ",{"{","}","\\\\",",","\\"","\\\\t"}}}] )
419
+ end
420
+ it 'encodes an array of int8 with sub arrays' do
421
+ expect( textenc_int_array.encode([1,[2,[3,4]],[nil,6],7]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7}] )
422
+ end
423
+ it 'encodes an array of int8 with strings' do
424
+ expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
425
+ end
426
+ it 'encodes an array of float8 with sub arrays' do
427
+ 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*")))
428
+ end
429
+ end
430
+ context 'two dimensional arrays' do
431
+ it 'encodes an array of timestamps with sub arrays' do
432
+ expect( textenc_timestamp_array.encode([Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59.99)]]) ).
433
+ to eq( %[{2014-12-31 00:00:00.000000000,{NULL,2016-01-02 23:23:59.990000000}}] )
434
+ end
435
+ end
436
+ context 'one dimensional array' do
437
+ it 'can encode empty arrays' do
438
+ expect( textenc_int_array.encode([]) ).to eq( '{}' )
439
+ expect( textenc_string_array.encode([]) ).to eq( '{}' )
440
+ end
441
+ it 'encodes an array of NULL strings w/wo quotes' do
442
+ expect( textenc_string_array.encode(['NUL', 'NULL', 'NULLL', 'nul', 'null', 'nulll']) ).to eq( %[{NUL,"NULL",NULLL,nul,"null",nulll}] )
443
+ end
444
+ it 'respects a different delimiter' do
445
+ expect( textenc_string_array_with_delimiter.encode(['a','b,','c']) ).to eq( '{a;b,;c}' )
446
+ end
447
+ end
448
+
449
+ context 'array of types with encoder in ruby space' do
450
+ it 'encodes with quotation' do
451
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
452
+ expect( array_type.encode([3,4]) ).to eq( %[{"4 ","5 "}] )
453
+ end
454
+
455
+ it 'encodes without quotation' do
456
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: false
457
+ expect( array_type.encode([3,4]) ).to eq( %[{4 ,5 }] )
458
+ end
459
+
460
+ it "should raise when ruby encoder returns non string values" do
461
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_int_result, needs_quotation: false
462
+ expect{ array_type.encode([3,4]) }.to raise_error(TypeError)
463
+ end
464
+ end
465
+
466
+ it "should pass through non Array inputs" do
467
+ expect( textenc_float_array.encode("text") ).to eq( "text" )
468
+ expect( textenc_float_array.encode(1234) ).to eq( "1234" )
469
+ end
470
+
471
+ context 'literal quotation' do
472
+ it 'should quote and escape literals' do
473
+ quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
474
+ expect( quoted_type.encode(["'A\",","\\B'"]) ).to eq( %['{"''A\\",","\\\\B''"}'] )
475
+ end
476
+ end
477
+ end
478
+
479
+ it "should be possible to marshal encoders" do
480
+ mt = Marshal.dump(textenc_int_array)
481
+ lt = Marshal.load(mt)
482
+ expect( lt.to_h ).to eq( textenc_int_array.to_h )
483
+ end
484
+
485
+ it "should be possible to marshal encoders" do
486
+ mt = Marshal.dump(textdec_int_array)
487
+ lt = Marshal.load(mt)
488
+ expect( lt.to_h ).to eq( textdec_int_array.to_h )
489
+ end
490
+
491
+ it "should respond to to_h" do
492
+ expect( textenc_int_array.to_h ).to eq( {
493
+ name: nil, oid: 0, format: 0,
494
+ elements_type: textenc_int, needs_quotation: false, delimiter: ','
495
+ } )
496
+ end
497
+
498
+ it "shouldn't accept invalid elements_types" do
499
+ expect{ PG::TextEncoder::Array.new elements_type: false }.to raise_error(TypeError)
500
+ end
501
+
502
+ it "should have reasonable default values" do
503
+ t = PG::TextEncoder::Array.new
504
+ expect( t.format ).to eq( 0 )
505
+ expect( t.oid ).to eq( 0 )
506
+ expect( t.name ).to be_nil
507
+ expect( t.needs_quotation? ).to eq( true )
508
+ expect( t.delimiter ).to eq( ',' )
509
+ expect( t.elements_type ).to be_nil
510
+ end
511
+ end
512
+
513
+ it "should encode Strings as base64 in TextEncoder" do
514
+ e = PG::TextEncoder::ToBase64.new
515
+ expect( e.encode("") ).to eq("")
516
+ expect( e.encode("x") ).to eq("eA==")
517
+ expect( e.encode("xx") ).to eq("eHg=")
518
+ expect( e.encode("xxx") ).to eq("eHh4")
519
+ expect( e.encode("xxxx") ).to eq("eHh4eA==")
520
+ expect( e.encode("xxxxx") ).to eq("eHh4eHg=")
521
+ expect( e.encode("\0\n\t") ).to eq("AAoJ")
522
+ expect( e.encode("(\xFBm") ).to eq("KPtt")
523
+ end
524
+
525
+ it "should encode Strings as base64 in BinaryDecoder" do
526
+ e = PG::BinaryDecoder::ToBase64.new
527
+ expect( e.decode("x") ).to eq("eA==")
528
+ end
529
+
530
+ it "should encode Integers as base64" do
531
+ # Not really useful, but ensures that two-pass element and composite element encoders work.
532
+ e = PG::TextEncoder::ToBase64.new( elements_type: PG::TextEncoder::Array.new( elements_type: PG::TextEncoder::Integer.new, needs_quotation: false ))
533
+ expect( e.encode([1]) ).to eq(["{1}"].pack("m").chomp)
534
+ expect( e.encode([12]) ).to eq(["{12}"].pack("m").chomp)
535
+ expect( e.encode([123]) ).to eq(["{123}"].pack("m").chomp)
536
+ expect( e.encode([1234]) ).to eq(["{1234}"].pack("m").chomp)
537
+ expect( e.encode([12345]) ).to eq(["{12345}"].pack("m").chomp)
538
+ expect( e.encode([123456]) ).to eq(["{123456}"].pack("m").chomp)
539
+ expect( e.encode([1234567]) ).to eq(["{1234567}"].pack("m").chomp)
540
+ end
541
+
542
+ it "should decode base64 to Strings in TextDecoder" do
543
+ e = PG::TextDecoder::FromBase64.new
544
+ expect( e.decode("") ).to eq("")
545
+ expect( e.decode("eA==") ).to eq("x")
546
+ expect( e.decode("eHg=") ).to eq("xx")
547
+ expect( e.decode("eHh4") ).to eq("xxx")
548
+ expect( e.decode("eHh4eA==") ).to eq("xxxx")
549
+ expect( e.decode("eHh4eHg=") ).to eq("xxxxx")
550
+ expect( e.decode("AAoJ") ).to eq("\0\n\t")
551
+ expect( e.decode("KPtt") ).to eq("(\xFBm")
552
+ end
553
+
554
+ it "should decode base64 in BinaryEncoder" do
555
+ e = PG::BinaryEncoder::FromBase64.new
556
+ expect( e.encode("eA==") ).to eq("x")
557
+
558
+ e = PG::BinaryEncoder::FromBase64.new( elements_type: PG::TextEncoder::Integer.new )
559
+ expect( e.encode(124) ).to eq("124=".unpack("m")[0])
560
+ end
561
+
562
+ it "should decode base64 to Integers" do
563
+ # Not really useful, but ensures that composite element encoders work.
564
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::TextDecoder::Array.new( elements_type: PG::TextDecoder::Integer.new ))
565
+ expect( e.decode(["{1}"].pack("m")) ).to eq([1])
566
+ expect( e.decode(["{12}"].pack("m")) ).to eq([12])
567
+ expect( e.decode(["{123}"].pack("m")) ).to eq([123])
568
+ expect( e.decode(["{1234}"].pack("m")) ).to eq([1234])
569
+ expect( e.decode(["{12345}"].pack("m")) ).to eq([12345])
570
+ expect( e.decode(["{123456}"].pack("m")) ).to eq([123456])
571
+ expect( e.decode(["{1234567}"].pack("m")) ).to eq([1234567])
572
+ expect( e.decode(["{12345678}"].pack("m")) ).to eq([12345678])
573
+
574
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::BinaryDecoder::Integer.new )
575
+ expect( e.decode("ALxhTg==") ).to eq(12345678)
576
+ end
577
+
578
+ it "should decode base64 with garbage" do
579
+ e = PG::TextDecoder::FromBase64.new format: 1
580
+ expect( e.decode("=") ).to eq("=".unpack("m")[0])
581
+ expect( e.decode("==") ).to eq("==".unpack("m")[0])
582
+ expect( e.decode("===") ).to eq("===".unpack("m")[0])
583
+ expect( e.decode("====") ).to eq("====".unpack("m")[0])
584
+ expect( e.decode("a=") ).to eq("a=".unpack("m")[0])
585
+ expect( e.decode("a==") ).to eq("a==".unpack("m")[0])
586
+ expect( e.decode("a===") ).to eq("a===".unpack("m")[0])
587
+ expect( e.decode("a====") ).to eq("a====".unpack("m")[0])
588
+ expect( e.decode("aa=") ).to eq("aa=".unpack("m")[0])
589
+ expect( e.decode("aa==") ).to eq("aa==".unpack("m")[0])
590
+ expect( e.decode("aa===") ).to eq("aa===".unpack("m")[0])
591
+ expect( e.decode("aa====") ).to eq("aa====".unpack("m")[0])
592
+ expect( e.decode("aaa=") ).to eq("aaa=".unpack("m")[0])
593
+ expect( e.decode("aaa==") ).to eq("aaa==".unpack("m")[0])
594
+ expect( e.decode("aaa===") ).to eq("aaa===".unpack("m")[0])
595
+ expect( e.decode("aaa====") ).to eq("aaa====".unpack("m")[0])
596
+ expect( e.decode("=aa") ).to eq("=aa=".unpack("m")[0])
597
+ expect( e.decode("=aa=") ).to eq("=aa=".unpack("m")[0])
598
+ expect( e.decode("=aa==") ).to eq("=aa==".unpack("m")[0])
599
+ expect( e.decode("=aa===") ).to eq("=aa===".unpack("m")[0])
600
+ end
601
+ end
602
+
603
+ describe PG::CopyCoder do
604
+ describe PG::TextEncoder::CopyRow do
605
+ context "with default typemap" do
606
+ let!(:encoder) do
607
+ PG::TextEncoder::CopyRow.new
608
+ end
609
+
610
+ it "should encode different types of Ruby objects" do
611
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
612
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t[1, 2, 3]\t12.1\tabcdefg\t\\N\n")
613
+ end
614
+ end
615
+
616
+ context "with TypeMapByClass" do
617
+ let!(:tm) do
618
+ tm = PG::TypeMapByClass.new
619
+ tm[Integer] = textenc_int
620
+ tm[Float] = intenc_incrementer
621
+ tm[Array] = PG::TextEncoder::Array.new elements_type: textenc_string
622
+ tm
623
+ end
624
+ let!(:encoder) do
625
+ PG::TextEncoder::CopyRow.new type_map: tm
626
+ end
627
+
628
+ it "should have reasonable default values" do
629
+ expect( encoder.name ).to be_nil
630
+ expect( encoder.delimiter ).to eq( "\t" )
631
+ expect( encoder.null_string ).to eq( "\\N" )
632
+ end
633
+
634
+ it "copies all attributes with #dup" do
635
+ encoder.name = "test"
636
+ encoder.delimiter = "#"
637
+ encoder.null_string = "NULL"
638
+ encoder.type_map = PG::TypeMapByColumn.new []
639
+ encoder2 = encoder.dup
640
+ expect( encoder.object_id ).to_not eq( encoder2.object_id )
641
+ expect( encoder2.name ).to eq( "test" )
642
+ expect( encoder2.delimiter ).to eq( "#" )
643
+ expect( encoder2.null_string ).to eq( "NULL" )
644
+ expect( encoder2.type_map ).to be_a_kind_of( PG::TypeMapByColumn )
645
+ end
646
+
647
+ describe '#encode' do
648
+ it "should encode different types of Ruby objects" do
649
+ expect( encoder.encode([]) ).to eq("\n")
650
+ expect( encoder.encode(["a"]) ).to eq("a\n")
651
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
652
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t{1,2,3}\t13 \tabcdefg\t\\N\n")
653
+ end
654
+
655
+ it "should escape special characters" do
656
+ expect( encoder.encode([" \0\t\n\r\\"]) ).to eq(" \0#\t#\n#\r#\\\n".gsub("#", "\\"))
657
+ end
658
+
659
+ it "should escape with different delimiter" do
660
+ encoder.delimiter = " "
661
+ encoder.null_string = "NULL"
662
+ expect( encoder.encode([nil, " ", "\0", "\t", "\n", "\r", "\\"]) ).to eq("NULL # \0 \t #\n #\r #\\\n".gsub("#", "\\"))
663
+ end
664
+ end
665
+ end
666
+ end
667
+
668
+ describe PG::TextDecoder::CopyRow do
669
+ context "with default typemap" do
670
+ let!(:decoder) do
671
+ PG::TextDecoder::CopyRow.new
672
+ end
673
+
674
+ describe '#decode' do
675
+ it "should decode different types of Ruby objects" do
676
+ 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"] )
677
+ end
678
+ end
679
+ end
680
+
681
+ context "with TypeMapByColumn" do
682
+ let!(:tm) do
683
+ PG::TypeMapByColumn.new [textdec_int, textdec_string, intdec_incrementer, nil]
684
+ end
685
+ let!(:decoder) do
686
+ PG::TextDecoder::CopyRow.new type_map: tm
687
+ end
688
+
689
+ describe '#decode' do
690
+ it "should decode different types of Ruby objects" do
691
+ expect( decoder.decode("123\t \0#\t#\n#\r#\\ \t234\t#\x01#\002\n".gsub("#", "\\"))).to eq( [123, " \0\t\n\r\\ ", 235, "\x01\x02"] )
692
+ end
693
+ end
694
+ end
695
+ end
696
+ end
697
+ end