pg 1.1.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 (77) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gemtest +0 -0
  5. data/BSDL +22 -0
  6. data/ChangeLog +6595 -0
  7. data/Contributors.rdoc +46 -0
  8. data/History.rdoc +492 -0
  9. data/LICENSE +56 -0
  10. data/Manifest.txt +72 -0
  11. data/POSTGRES +23 -0
  12. data/README-OS_X.rdoc +68 -0
  13. data/README-Windows.rdoc +56 -0
  14. data/README.ja.rdoc +14 -0
  15. data/README.rdoc +178 -0
  16. data/Rakefile +215 -0
  17. data/Rakefile.cross +298 -0
  18. data/ext/errorcodes.def +968 -0
  19. data/ext/errorcodes.rb +45 -0
  20. data/ext/errorcodes.txt +478 -0
  21. data/ext/extconf.rb +94 -0
  22. data/ext/gvl_wrappers.c +17 -0
  23. data/ext/gvl_wrappers.h +241 -0
  24. data/ext/pg.c +640 -0
  25. data/ext/pg.h +365 -0
  26. data/ext/pg_binary_decoder.c +229 -0
  27. data/ext/pg_binary_encoder.c +162 -0
  28. data/ext/pg_coder.c +549 -0
  29. data/ext/pg_connection.c +4252 -0
  30. data/ext/pg_copy_coder.c +596 -0
  31. data/ext/pg_errors.c +95 -0
  32. data/ext/pg_result.c +1501 -0
  33. data/ext/pg_text_decoder.c +981 -0
  34. data/ext/pg_text_encoder.c +682 -0
  35. data/ext/pg_tuple.c +541 -0
  36. data/ext/pg_type_map.c +166 -0
  37. data/ext/pg_type_map_all_strings.c +116 -0
  38. data/ext/pg_type_map_by_class.c +239 -0
  39. data/ext/pg_type_map_by_column.c +312 -0
  40. data/ext/pg_type_map_by_mri_type.c +284 -0
  41. data/ext/pg_type_map_by_oid.c +355 -0
  42. data/ext/pg_type_map_in_ruby.c +299 -0
  43. data/ext/util.c +149 -0
  44. data/ext/util.h +65 -0
  45. data/ext/vc/pg.sln +26 -0
  46. data/ext/vc/pg_18/pg.vcproj +216 -0
  47. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  48. data/lib/pg.rb +74 -0
  49. data/lib/pg/basic_type_mapping.rb +459 -0
  50. data/lib/pg/binary_decoder.rb +22 -0
  51. data/lib/pg/coder.rb +83 -0
  52. data/lib/pg/connection.rb +291 -0
  53. data/lib/pg/constants.rb +11 -0
  54. data/lib/pg/exceptions.rb +11 -0
  55. data/lib/pg/result.rb +31 -0
  56. data/lib/pg/text_decoder.rb +47 -0
  57. data/lib/pg/text_encoder.rb +69 -0
  58. data/lib/pg/tuple.rb +30 -0
  59. data/lib/pg/type_map_by_column.rb +15 -0
  60. data/spec/data/expected_trace.out +26 -0
  61. data/spec/data/random_binary_data +0 -0
  62. data/spec/helpers.rb +380 -0
  63. data/spec/pg/basic_type_mapping_spec.rb +508 -0
  64. data/spec/pg/connection_spec.rb +1872 -0
  65. data/spec/pg/connection_sync_spec.rb +41 -0
  66. data/spec/pg/result_spec.rb +491 -0
  67. data/spec/pg/tuple_spec.rb +280 -0
  68. data/spec/pg/type_map_by_class_spec.rb +138 -0
  69. data/spec/pg/type_map_by_column_spec.rb +222 -0
  70. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  71. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  72. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  73. data/spec/pg/type_map_spec.rb +22 -0
  74. data/spec/pg/type_spec.rb +949 -0
  75. data/spec/pg_spec.rb +50 -0
  76. metadata +322 -0
  77. metadata.gz.sig +0 -0
@@ -0,0 +1,22 @@
1
+ # -*- rspec -*-
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMap do
10
+ let!(:tm){ PG::TypeMap.new }
11
+
12
+ it "should raise an error when used for param type casts" do
13
+ expect{
14
+ @conn.exec_params( "SELECT $1", [5], 0, tm )
15
+ }.to raise_error(NotImplementedError, /not suitable to map query params/)
16
+ end
17
+
18
+ it "should raise an error when used for result type casts" do
19
+ res = @conn.exec( "SELECT 1" )
20
+ expect{ res.map_types!(tm) }.to raise_error(NotImplementedError, /not suitable to map result values/)
21
+ end
22
+ end
@@ -0,0 +1,949 @@
1
+ # -*- rspec -*-
2
+ # encoding: utf-8
3
+
4
+ require 'pg'
5
+ require 'time'
6
+
7
+
8
+ describe "PG::Type derivations" do
9
+ let!(:textenc_int) { PG::TextEncoder::Integer.new name: 'Integer', oid: 23 }
10
+ let!(:textdec_int) { PG::TextDecoder::Integer.new name: 'Integer', oid: 23 }
11
+ let!(:textenc_boolean) { PG::TextEncoder::Boolean.new }
12
+ let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
13
+ let!(:textenc_float) { PG::TextEncoder::Float.new }
14
+ let!(:textdec_float) { PG::TextDecoder::Float.new }
15
+ let!(:textenc_string) { PG::TextEncoder::String.new }
16
+ let!(:textdec_string) { PG::TextDecoder::String.new }
17
+ let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
18
+ let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
19
+ let!(:textenc_timestamputc) { PG::TextEncoder::TimestampUtc.new }
20
+ let!(:textdec_timestamputc) { PG::TextDecoder::TimestampUtc.new }
21
+ let!(:textdec_timestampul) { PG::TextDecoder::TimestampUtcToLocal.new }
22
+ let!(:textenc_timestamptz) { PG::TextEncoder::TimestampWithTimeZone.new }
23
+ let!(:textdec_timestamptz) { PG::TextDecoder::TimestampWithTimeZone.new }
24
+ let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
25
+ let!(:textdec_bytea) { PG::TextDecoder::Bytea.new }
26
+ let!(:binaryenc_int2) { PG::BinaryEncoder::Int2.new }
27
+ let!(:binaryenc_int4) { PG::BinaryEncoder::Int4.new }
28
+ let!(:binaryenc_int8) { PG::BinaryEncoder::Int8.new }
29
+ let!(:binarydec_integer) { PG::BinaryDecoder::Integer.new }
30
+
31
+ let!(:intenc_incrementer) do
32
+ Class.new(PG::SimpleEncoder) do
33
+ def encode(value)
34
+ (value.to_i + 1).to_s + " "
35
+ end
36
+ end.new
37
+ end
38
+ let!(:intdec_incrementer) do
39
+ Class.new(PG::SimpleDecoder) do
40
+ def decode(string, tuple=nil, field=nil)
41
+ string.to_i+1
42
+ end
43
+ end.new
44
+ end
45
+
46
+ let!(:intenc_incrementer_with_encoding) do
47
+ Class.new(PG::SimpleEncoder) do
48
+ def encode(value, encoding)
49
+ r = (value.to_i + 1).to_s + " #{encoding}"
50
+ r.encode!(encoding)
51
+ end
52
+ end.new
53
+ end
54
+ let!(:intenc_incrementer_with_int_result) do
55
+ Class.new(PG::SimpleEncoder) do
56
+ def encode(value)
57
+ value.to_i+1
58
+ end
59
+ end.new
60
+ end
61
+
62
+ it "shouldn't be possible to build a PG::Type directly" do
63
+ expect{ PG::Coder.new }.to raise_error(TypeError, /cannot/)
64
+ end
65
+
66
+ describe PG::SimpleCoder do
67
+ describe '#decode' do
68
+ it "should offer decode method with tuple/field" do
69
+ res = textdec_int.decode("123", 1, 1)
70
+ expect( res ).to eq( 123 )
71
+ end
72
+
73
+ it "should offer decode method without tuple/field" do
74
+ res = textdec_int.decode("234")
75
+ expect( res ).to eq( 234 )
76
+ end
77
+
78
+ it "should decode with ruby decoder" do
79
+ expect( intdec_incrementer.decode("3") ).to eq( 4 )
80
+ end
81
+
82
+ it "should decode integers of different lengths from text format" do
83
+ 30.times do |zeros|
84
+ expect( textdec_int.decode("1" + "0"*zeros) ).to eq( 10 ** zeros )
85
+ expect( textdec_int.decode(zeros==0 ? "0" : "9"*zeros) ).to eq( 10 ** zeros - 1 )
86
+ expect( textdec_int.decode("-1" + "0"*zeros) ).to eq( -10 ** zeros )
87
+ expect( textdec_int.decode(zeros==0 ? "0" : "-" + "9"*zeros) ).to eq( -10 ** zeros + 1 )
88
+ end
89
+ 66.times do |bits|
90
+ expect( textdec_int.decode((2 ** bits).to_s) ).to eq( 2 ** bits )
91
+ expect( textdec_int.decode((2 ** bits - 1).to_s) ).to eq( 2 ** bits - 1 )
92
+ expect( textdec_int.decode((-2 ** bits).to_s) ).to eq( -2 ** bits )
93
+ expect( textdec_int.decode((-2 ** bits + 1).to_s) ).to eq( -2 ** bits + 1 )
94
+ end
95
+ end
96
+
97
+ it 'decodes bytea to a binary string' do
98
+ expect( textdec_bytea.decode("\\x00010203EF") ).to eq( "\x00\x01\x02\x03\xef".b )
99
+ expect( textdec_bytea.decode("\\377\\000") ).to eq( "\xff\0".b )
100
+ end
101
+
102
+ context 'timestamps' do
103
+ it 'decodes timestamps without timezone as local time' do
104
+ expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
105
+ to eq( Time.new(2016,1,2, 23,23,59.123456).iso8601(5) )
106
+ expect( textdec_timestamp.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
107
+ to eq( Time.new(2016,8,2, 23,23,59.123456).iso8601(5) )
108
+ end
109
+ it 'decodes timestamps with UTC time and returns UTC timezone' do
110
+ expect( textdec_timestamputc.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
111
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).iso8601(5) )
112
+ expect( textdec_timestamputc.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
113
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).iso8601(5) )
114
+ end
115
+ it 'decodes timestamps with UTC time and returns local timezone' do
116
+ expect( textdec_timestampul.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
117
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).getlocal.iso8601(5) )
118
+ expect( textdec_timestampul.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
119
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).getlocal.iso8601(5) )
120
+ end
121
+ it 'decodes timestamps with hour timezone' do
122
+ expect( textdec_timestamptz.decode('2016-01-02 23:23:59.123456-04').iso8601(5) ).
123
+ to eq( Time.new(2016,1,2, 23,23,59.123456, "-04:00").iso8601(5) )
124
+ expect( textdec_timestamptz.decode('2016-08-02 23:23:59.123456+10').iso8601(5) ).
125
+ to eq( Time.new(2016,8,2, 23,23,59.123456, "+10:00").iso8601(5) )
126
+ expect( textdec_timestamptz.decode('1913-12-31 23:58:59.1231-03').iso8601(5) ).
127
+ to eq( Time.new(1913, 12, 31, 23, 58, 59.1231, "-03:00").iso8601(5) )
128
+ expect( textdec_timestamptz.decode('4714-11-24 23:58:59.1231-03 BC').iso8601(5) ).
129
+ to eq( Time.new(-4713, 11, 24, 23, 58, 59.1231, "-03:00").iso8601(5) )
130
+ expect( textdec_timestamptz.decode('294276-12-31 23:58:59.1231+03').iso8601(5) ).
131
+ to eq( Time.new(294276, 12, 31, 23, 58, 59.1231, "+03:00").iso8601(5) )
132
+ end
133
+ it 'decodes timestamps with hour:minute timezone' do
134
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04:15') ).
135
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:15") )
136
+ expect( textdec_timestamptz.decode('2015-07-26 17:26:42.691511-04:30') ).
137
+ to be_within(0.000001).of( Time.new(2015,07,26, 17, 26, 42.691511, "-04:30") )
138
+ expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10:45') ).
139
+ to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:45") )
140
+ end
141
+ it 'decodes timestamps with hour:minute:sec timezone' do
142
+ # SET TIME ZONE 'Europe/Dublin'; -- Was UTC−00:25:21 until 1916
143
+ # SELECT '1900-01-01'::timestamptz;
144
+ # -- "1900-01-01 00:00:00-00:25:21"
145
+ expect( textdec_timestamptz.decode('1916-01-01 00:00:00-00:25:21') ).
146
+ to be_within(0.000001).of( Time.new(1916, 1, 1, 0, 0, 0, "-00:25:21") )
147
+ end
148
+ it 'decodes timestamps with date before 1823' do
149
+ expect( textdec_timestamp.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
150
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456).iso8601(5) )
151
+ expect( textdec_timestamputc.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
152
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).iso8601(5) )
153
+ expect( textdec_timestampul.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
154
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
155
+ expect( textdec_timestamptz.decode('1822-01-02 23:23:59.123456+04').iso8601(5) ).
156
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
157
+ end
158
+ it 'decodes timestamps with date after 2116' do
159
+ expect( textdec_timestamp.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
160
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456).iso8601(5) )
161
+ expect( textdec_timestamputc.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
162
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).iso8601(5) )
163
+ expect( textdec_timestampul.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
164
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
165
+ expect( textdec_timestamptz.decode('2117-01-02 23:23:59.123456+04').iso8601(5) ).
166
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
167
+ end
168
+ it 'decodes timestamps with variable number of digits for the useconds part' do
169
+ sec = "59.12345678912345"
170
+ (4..sec.length).each do |i|
171
+ expect( textdec_timestamp.decode("2016-01-02 23:23:#{sec[0,i]}") ).
172
+ to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, sec[0,i].to_f) )
173
+ end
174
+ end
175
+ it 'decodes timestamps with leap-second' do
176
+ expect( textdec_timestamp.decode('1998-12-31 23:59:60.1234') ).
177
+ to be_within(0.000001).of( Time.new(1998,12,31, 23, 59, 60.1234) )
178
+ end
179
+
180
+ def textdec_timestamptz_decode_should_fail(str)
181
+ expect(textdec_timestamptz.decode(str)).to eq(str)
182
+ end
183
+
184
+ it 'fails when the timestamp is an empty string' do
185
+ textdec_timestamptz_decode_should_fail('')
186
+ end
187
+ it 'fails when the timestamp contains values with less digits than expected' do
188
+ textdec_timestamptz_decode_should_fail('2016-0-02 23:23:59.123456+00:25:21')
189
+ textdec_timestamptz_decode_should_fail('2016-01-0 23:23:59.123456+00:25:21')
190
+ textdec_timestamptz_decode_should_fail('2016-01-02 2:23:59.123456+00:25:21')
191
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:2:59.123456+00:25:21')
192
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:5.123456+00:25:21')
193
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.+00:25:21')
194
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+0:25:21')
195
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:2:21')
196
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:2')
197
+ end
198
+ it 'fails when the timestamp contains values with more digits than expected' do
199
+ textdec_timestamptz_decode_should_fail('2016-011-02 23:23:59.123456+00:25:21')
200
+ textdec_timestamptz_decode_should_fail('2016-01-022 23:23:59.123456+00:25:21')
201
+ textdec_timestamptz_decode_should_fail('2016-01-02 233:23:59.123456+00:25:21')
202
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:233:59.123456+00:25:21')
203
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:599.123456+00:25:21')
204
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+000:25:21')
205
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:255:21')
206
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:211')
207
+ end
208
+ it 'fails when the timestamp contains values with invalid characters' do
209
+ str = '2013-01-02 23:23:59.123456+00:25:21'
210
+ str.length.times do |i|
211
+ textdec_timestamptz_decode_should_fail(str[0,i] + "x" + str[i+1..-1])
212
+ end
213
+ end
214
+ it 'fails when the timestamp contains leading characters' do
215
+ textdec_timestamptz_decode_should_fail(' 2016-01-02 23:23:59.123456')
216
+ end
217
+ it 'fails when the timestamp contains trailing characters' do
218
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456 ')
219
+ end
220
+ it 'fails when the timestamp contains non ASCII character' do
221
+ textdec_timestamptz_decode_should_fail('2016-01ª02 23:23:59.123456')
222
+ end
223
+ end
224
+
225
+ context 'identifier quotation' do
226
+ it 'should build an array out of an quoted identifier string' do
227
+ quoted_type = PG::TextDecoder::Identifier.new
228
+ expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] )
229
+ expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] )
230
+ end
231
+
232
+ it 'should split unquoted identifier string' do
233
+ quoted_type = PG::TextDecoder::Identifier.new
234
+ expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] )
235
+ expect( quoted_type.decode(%[a]) ).to eq( ['a'] )
236
+ end
237
+
238
+ it 'should split identifier string with correct character encoding' do
239
+ quoted_type = PG::TextDecoder::Identifier.new
240
+ v = quoted_type.decode(%[Héllo].encode("iso-8859-1")).first
241
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
242
+ expect( v ).to eq( %[Héllo].encode(Encoding::ISO_8859_1) )
243
+ end
244
+ end
245
+
246
+ it "should raise when decode method is called with wrong args" do
247
+ expect{ textdec_int.decode() }.to raise_error(ArgumentError)
248
+ expect{ textdec_int.decode("123", 2, 3, 4) }.to raise_error(ArgumentError)
249
+ expect{ textdec_int.decode(2, 3, 4) }.to raise_error(TypeError)
250
+ expect( intdec_incrementer.decode(2, 3, 4) ).to eq( 3 )
251
+ end
252
+
253
+ it "should pass through nil values" do
254
+ expect( textdec_string.decode( nil )).to be_nil
255
+ expect( textdec_int.decode( nil )).to be_nil
256
+ end
257
+ end
258
+
259
+ describe '#encode' do
260
+ it "should offer encode method for text type" do
261
+ res = textenc_int.encode(123)
262
+ expect( res ).to eq( "123" )
263
+ end
264
+
265
+ it "should offer encode method for binary type" do
266
+ res = binaryenc_int8.encode(123)
267
+ expect( res ).to eq( [123].pack("q>") )
268
+ end
269
+
270
+ it "should encode integers from string to binary format" do
271
+ expect( binaryenc_int2.encode(" -123 ") ).to eq( [-123].pack("s>") )
272
+ expect( binaryenc_int4.encode(" -123 ") ).to eq( [-123].pack("l>") )
273
+ expect( binaryenc_int8.encode(" -123 ") ).to eq( [-123].pack("q>") )
274
+ expect( binaryenc_int2.encode(" 123-xyz ") ).to eq( [123].pack("s>") )
275
+ expect( binaryenc_int4.encode(" 123-xyz ") ).to eq( [123].pack("l>") )
276
+ expect( binaryenc_int8.encode(" 123-xyz ") ).to eq( [123].pack("q>") )
277
+ end
278
+
279
+ it "should encode integers of different lengths to text format" do
280
+ 30.times do |zeros|
281
+ expect( textenc_int.encode(10 ** zeros) ).to eq( "1" + "0"*zeros )
282
+ expect( textenc_int.encode(10 ** zeros - 1) ).to eq( zeros==0 ? "0" : "9"*zeros )
283
+ expect( textenc_int.encode(-10 ** zeros) ).to eq( "-1" + "0"*zeros )
284
+ expect( textenc_int.encode(-10 ** zeros + 1) ).to eq( zeros==0 ? "0" : "-" + "9"*zeros )
285
+ end
286
+ 66.times do |bits|
287
+ expect( textenc_int.encode(2 ** bits) ).to eq( (2 ** bits).to_s )
288
+ expect( textenc_int.encode(2 ** bits - 1) ).to eq( (2 ** bits - 1).to_s )
289
+ expect( textenc_int.encode(-2 ** bits) ).to eq( (-2 ** bits).to_s )
290
+ expect( textenc_int.encode(-2 ** bits + 1) ).to eq( (-2 ** bits + 1).to_s )
291
+ end
292
+ end
293
+
294
+ it "should encode integers from string to text format" do
295
+ expect( textenc_int.encode(" -123 ") ).to eq( "-123" )
296
+ expect( textenc_int.encode(" 123-xyz ") ).to eq( "123" )
297
+ end
298
+
299
+ it "should encode boolean values" do
300
+ expect( textenc_boolean.encode(false) ).to eq( "f" )
301
+ expect( textenc_boolean.encode(true) ).to eq( "t" )
302
+ ["any", :other, "value", 0, 1, 2].each do |value|
303
+ expect( textenc_boolean.encode(value) ).to eq( value.to_s )
304
+ end
305
+ end
306
+
307
+ it "should encode special floats equally to Float#to_s" do
308
+ expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
309
+ expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
310
+ expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
311
+ end
312
+
313
+ it "encodes binary string to bytea" do
314
+ expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
315
+ end
316
+
317
+ context 'timestamps' do
318
+ it 'encodes timestamps without timezone' do
319
+ expect( textenc_timestamp.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
320
+ to match( /^2016-01-02 23:23:59.12345\d+$/ )
321
+ expect( textenc_timestamp.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
322
+ to match( /^2016-08-02 23:23:59.12345\d+$/ )
323
+ end
324
+ it 'encodes timestamps with UTC timezone' do
325
+ expect( textenc_timestamputc.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
326
+ to match( /^2016-01-02 20:23:59.12345\d+$/ )
327
+ expect( textenc_timestamputc.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
328
+ to match( /^2016-08-02 20:23:59.12345\d+$/ )
329
+ end
330
+ it 'encodes timestamps with hour timezone' do
331
+ expect( textenc_timestamptz.encode(Time.new(2016,1,02, 23, 23, 59.123456, -4*60*60)) ).
332
+ to match( /^2016-01-02 23:23:59.12345\d+ \-04:00$/ )
333
+ expect( textenc_timestamptz.encode(Time.new(2016,8,02, 23, 23, 59.123456, 10*60*60)) ).
334
+ to match( /^2016-08-02 23:23:59.12345\d+ \+10:00$/ )
335
+ end
336
+ end
337
+
338
+ context 'identifier quotation' do
339
+ it 'should quote and escape identifier' do
340
+ quoted_type = PG::TextEncoder::Identifier.new
341
+ expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] )
342
+ expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] )
343
+ expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] )
344
+ expect( quoted_type.encode( nil ) ).to be_nil
345
+ end
346
+
347
+ it 'should quote identifiers with correct character encoding' do
348
+ quoted_type = PG::TextEncoder::Identifier.new
349
+ v = quoted_type.encode(['Héllo'], "iso-8859-1")
350
+ expect( v ).to eq( %["Héllo"].encode(Encoding::ISO_8859_1) )
351
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
352
+ end
353
+
354
+ it "will raise a TypeError for invalid arguments to quote_ident" do
355
+ quoted_type = PG::TextEncoder::Identifier.new
356
+ expect{ quoted_type.encode( [nil] ) }.to raise_error(TypeError)
357
+ expect{ quoted_type.encode( [['a']] ) }.to raise_error(TypeError)
358
+ end
359
+ end
360
+
361
+ it "should encode with ruby encoder" do
362
+ expect( intenc_incrementer.encode(3) ).to eq( "4 " )
363
+ end
364
+
365
+ it "should encode with ruby encoder and given character encoding" do
366
+ r = intenc_incrementer_with_encoding.encode(3, Encoding::CP850)
367
+ expect( r ).to eq( "4 CP850" )
368
+ expect( r.encoding ).to eq( Encoding::CP850 )
369
+ end
370
+
371
+ it "should return when ruby encoder returns non string values" do
372
+ expect( intenc_incrementer_with_int_result.encode(3) ).to eq( 4 )
373
+ end
374
+
375
+ it "should pass through nil values" do
376
+ expect( textenc_string.encode( nil )).to be_nil
377
+ expect( textenc_int.encode( nil )).to be_nil
378
+ end
379
+ end
380
+
381
+ it "should be possible to marshal encoders" do
382
+ mt = Marshal.dump(textenc_int)
383
+ lt = Marshal.load(mt)
384
+ expect( lt.to_h ).to eq( textenc_int.to_h )
385
+ end
386
+
387
+ it "should be possible to marshal decoders" do
388
+ mt = Marshal.dump(textdec_int)
389
+ lt = Marshal.load(mt)
390
+ expect( lt.to_h ).to eq( textdec_int.to_h )
391
+ end
392
+
393
+ it "should respond to to_h" do
394
+ expect( textenc_int.to_h ).to eq( {
395
+ name: 'Integer', oid: 23, format: 0
396
+ } )
397
+ end
398
+
399
+ it "should have reasonable default values" do
400
+ t = PG::TextEncoder::String.new
401
+ expect( t.format ).to eq( 0 )
402
+ expect( t.oid ).to eq( 0 )
403
+ expect( t.name ).to be_nil
404
+
405
+ t = PG::BinaryEncoder::Int4.new
406
+ expect( t.format ).to eq( 1 )
407
+ expect( t.oid ).to eq( 0 )
408
+ expect( t.name ).to be_nil
409
+
410
+ t = PG::TextDecoder::String.new
411
+ expect( t.format ).to eq( 0 )
412
+ expect( t.oid ).to eq( 0 )
413
+ expect( t.name ).to be_nil
414
+
415
+ t = PG::BinaryDecoder::String.new
416
+ expect( t.format ).to eq( 1 )
417
+ expect( t.oid ).to eq( 0 )
418
+ expect( t.name ).to be_nil
419
+ end
420
+ end
421
+
422
+ describe PG::CompositeCoder do
423
+ describe "Array types" do
424
+ let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
425
+ let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
426
+ let!(:textdec_string_array_raise) { PG::TextDecoder::Array.new elements_type: textdec_string, flags: PG::Coder:: FORMAT_ERROR_TO_RAISE }
427
+ let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
428
+ let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
429
+ let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
430
+ let!(:textdec_float_array) { PG::TextDecoder::Array.new elements_type: textdec_float, needs_quotation: false }
431
+ let!(:textenc_timestamp_array) { PG::TextEncoder::Array.new elements_type: textenc_timestamp, needs_quotation: false }
432
+ let!(:textdec_timestamp_array) { PG::TextDecoder::Array.new elements_type: textdec_timestamp, needs_quotation: false }
433
+ let!(:textenc_string_array_with_delimiter) { PG::TextEncoder::Array.new elements_type: textenc_string, delimiter: ';' }
434
+ let!(:textdec_string_array_with_delimiter) { PG::TextDecoder::Array.new elements_type: textdec_string, delimiter: ';' }
435
+ let!(:textdec_bytea_array) { PG::TextDecoder::Array.new elements_type: textdec_bytea }
436
+
437
+ #
438
+ # Array parser specs are thankfully borrowed from here:
439
+ # https://github.com/dockyard/pg_array_parser
440
+ #
441
+ describe '#decode' do
442
+ context 'one dimensional arrays' do
443
+ context 'empty' do
444
+ it 'returns an empty array' do
445
+ expect( textdec_string_array.decode(%[{}]) ).to eq( [] )
446
+ end
447
+ end
448
+
449
+ context 'no strings' do
450
+ it 'returns an array of strings' do
451
+ expect( textdec_string_array.decode(%[{1,2,3}]) ).to eq( ['1','2','3'] )
452
+ end
453
+ end
454
+
455
+ context 'NULL values' do
456
+ it 'returns an array of strings, with nils replacing NULL characters' do
457
+ expect( textdec_string_array.decode(%[{1,NULL,NULL}]) ).to eq( ['1',nil,nil] )
458
+ end
459
+ end
460
+
461
+ context 'quoted NULL' do
462
+ it 'returns an array with the word NULL' do
463
+ expect( textdec_string_array.decode(%[{1,"NULL",3}]) ).to eq( ['1','NULL','3'] )
464
+ end
465
+ end
466
+
467
+ context 'strings' do
468
+ it 'returns an array of strings when containing commas in a quoted string' do
469
+ expect( textdec_string_array.decode(%[{1,"2,3",4}]) ).to eq( ['1','2,3','4'] )
470
+ end
471
+
472
+ it 'returns an array of strings when containing an escaped quote' do
473
+ expect( textdec_string_array.decode(%[{1,"2\\",3",4}]) ).to eq( ['1','2",3','4'] )
474
+ end
475
+
476
+ it 'returns an array of strings when containing an escaped backslash' do
477
+ expect( textdec_string_array.decode(%[{1,"2\\\\",3,4}]) ).to eq( ['1','2\\','3','4'] )
478
+ expect( textdec_string_array.decode(%[{1,"2\\\\\\",3",4}]) ).to eq( ['1','2\\",3','4'] )
479
+ end
480
+
481
+ it 'returns an array containing empty strings' do
482
+ expect( textdec_string_array.decode(%[{1,"",3,""}]) ).to eq( ['1', '', '3', ''] )
483
+ end
484
+
485
+ it 'returns an array containing unicode strings' do
486
+ expect( textdec_string_array.decode(%[{"Paragraph 399(b)(i) – “valid leave” – meaning"}]) ).to eq(['Paragraph 399(b)(i) – “valid leave” – meaning'])
487
+ end
488
+
489
+ it 'respects a different delimiter' do
490
+ expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
491
+ end
492
+
493
+ it 'ignores array dimensions' do
494
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3}]) ).to eq( ['1','2','3'] )
495
+ expect( textdec_string_array.decode(%[[]={1,2,3}]) ).to eq( ['1','2','3'] )
496
+ expect( textdec_string_array.decode(%[ [-1:+2]= {4,3,2,1}]) ).to eq( ['4','3','2','1'] )
497
+ end
498
+
499
+ it 'ignores spaces after array' do
500
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3} ]) ).to eq( ['1','2','3'] )
501
+ expect( textdec_string_array.decode(%[{1,2,3} ]) ).to eq( ['1','2','3'] )
502
+ end
503
+
504
+ describe "with malformed syntax are deprecated" do
505
+ it 'accepts broken array dimensions' do
506
+ expect( textdec_string_array.decode(%([2:4={1,2,3})) ).to eq([['1','2','3']])
507
+ expect( textdec_string_array.decode(%(2:4]={1,2,3})) ).to eq([['1','2','3']])
508
+ expect( textdec_string_array.decode(%(={1,2,3})) ).to eq([['1','2','3']])
509
+ expect( textdec_string_array.decode(%([x]={1,2,3})) ).to eq([['1','2','3']])
510
+ expect( textdec_string_array.decode(%([]{1,2,3})) ).to eq([['1','2','3']])
511
+ expect( textdec_string_array.decode(%(1,2,3)) ).to eq(['','2'])
512
+ end
513
+
514
+ it 'accepts malformed arrays' do
515
+ expect( textdec_string_array.decode(%({1,2,3)) ).to eq(['1','2'])
516
+ expect( textdec_string_array.decode(%({1,2,3}})) ).to eq(['1','2','3'])
517
+ expect( textdec_string_array.decode(%({1,2,3}x)) ).to eq(['1','2','3'])
518
+ expect( textdec_string_array.decode(%({{1,2},{2,3})) ).to eq([['1','2'],['2','3']])
519
+ expect( textdec_string_array.decode(%({{1,2},{2,3}}x)) ).to eq([['1','2'],['2','3']])
520
+ expect( textdec_string_array.decode(%({[1,2},{2,3}}})) ).to eq(['[1','2'])
521
+ end
522
+ end
523
+
524
+ describe "with malformed syntax are raised with pg-2.0+" do
525
+ it 'complains about broken array dimensions' do
526
+ expect{ textdec_string_array_raise.decode(%([2:4={1,2,3})) }.to raise_error(TypeError)
527
+ expect{ textdec_string_array_raise.decode(%(2:4]={1,2,3})) }.to raise_error(TypeError)
528
+ expect{ textdec_string_array_raise.decode(%(={1,2,3})) }.to raise_error(TypeError)
529
+ expect{ textdec_string_array_raise.decode(%([x]={1,2,3})) }.to raise_error(TypeError)
530
+ expect{ textdec_string_array_raise.decode(%([]{1,2,3})) }.to raise_error(TypeError)
531
+ expect{ textdec_string_array_raise.decode(%(1,2,3)) }.to raise_error(TypeError)
532
+ end
533
+
534
+ it 'complains about malformed array' do
535
+ expect{ textdec_string_array_raise.decode(%({1,2,3)) }.to raise_error(TypeError)
536
+ expect{ textdec_string_array_raise.decode(%({1,2,3}})) }.to raise_error(TypeError)
537
+ expect{ textdec_string_array_raise.decode(%({1,2,3}x)) }.to raise_error(TypeError)
538
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3})) }.to raise_error(TypeError)
539
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3}}x)) }.to raise_error(TypeError)
540
+ expect{ textdec_string_array_raise.decode(%({[1,2},{2,3}}})) }.to raise_error(TypeError)
541
+ end
542
+ end
543
+ end
544
+
545
+ context 'bytea' do
546
+ it 'returns an array of binary strings' do
547
+ expect( textdec_bytea_array.decode(%[{"\\\\x00010203EF","2,3",\\377}]) ).to eq( ["\x00\x01\x02\x03\xef".b,"2,3".b,"\xff".b] )
548
+ end
549
+ end
550
+
551
+ end
552
+
553
+ context 'two dimensional arrays' do
554
+ context 'empty' do
555
+ it 'returns an empty array' do
556
+ expect( textdec_string_array.decode(%[{{}}]) ).to eq( [[]] )
557
+ expect( textdec_string_array.decode(%[{{},{}}]) ).to eq( [[],[]] )
558
+ end
559
+ end
560
+ context 'no strings' do
561
+ it 'returns an array of strings with a sub array' do
562
+ expect( textdec_string_array.decode(%[{1,{2,3},4}]) ).to eq( ['1',['2','3'],'4'] )
563
+ end
564
+ end
565
+ context 'strings' do
566
+ it 'returns an array of strings with a sub array' do
567
+ expect( textdec_string_array.decode(%[{1,{"2,3"},4}]) ).to eq( ['1',['2,3'],'4'] )
568
+ end
569
+ it 'returns an array of strings with a sub array and a quoted }' do
570
+ expect( textdec_string_array.decode(%[{1,{"2,}3",NULL},4}]) ).to eq( ['1',['2,}3',nil],'4'] )
571
+ end
572
+ it 'returns an array of strings with a sub array and a quoted {' do
573
+ expect( textdec_string_array.decode(%[{1,{"2,{3"},4}]) ).to eq( ['1',['2,{3'],'4'] )
574
+ end
575
+ it 'returns an array of strings with a sub array and a quoted { and escaped quote' do
576
+ expect( textdec_string_array.decode(%[{1,{"2\\",{3"},4}]) ).to eq( ['1',['2",{3'],'4'] )
577
+ end
578
+ it 'returns an array of strings with a sub array with empty strings' do
579
+ expect( textdec_string_array.decode(%[{1,{""},4,{""}}]) ).to eq( ['1',[''],'4',['']] )
580
+ end
581
+ end
582
+ context 'timestamps' do
583
+ it 'decodes an array of timestamps with sub arrays' do
584
+ expect( textdec_timestamp_array.decode('{2014-12-31 00:00:00,{NULL,2016-01-02 23:23:59.0000000}}') ).
585
+ to eq( [Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59)]] )
586
+ end
587
+ end
588
+ end
589
+ context 'three dimensional arrays' do
590
+ context 'empty' do
591
+ it 'returns an empty array' do
592
+ expect( textdec_string_array.decode(%[{{{}}}]) ).to eq( [[[]]] )
593
+ expect( textdec_string_array.decode(%[{{{},{}},{{},{}}}]) ).to eq( [[[],[]],[[],[]]] )
594
+ end
595
+ end
596
+ it 'returns an array of strings with sub arrays' do
597
+ expect( textdec_string_array.decode(%[{1,{2,{3,4}},{NULL,6},7}]) ).to eq( ['1',['2',['3','4']],[nil,'6'],'7'] )
598
+ end
599
+ end
600
+
601
+ it 'should decode array of types with decoder in ruby space' do
602
+ array_type = PG::TextDecoder::Array.new elements_type: intdec_incrementer
603
+ expect( array_type.decode(%[{3,4}]) ).to eq( [4,5] )
604
+ end
605
+
606
+ it 'should decode array of nil types' do
607
+ array_type = PG::TextDecoder::Array.new elements_type: nil
608
+ expect( array_type.decode(%[{3,4}]) ).to eq( ['3','4'] )
609
+ end
610
+ end
611
+
612
+ describe '#encode' do
613
+ context 'three dimensional arrays' do
614
+ it 'encodes an array of strings and numbers with sub arrays' do
615
+ expect( textenc_string_array.encode(['1',['2',['3','4']],[nil,6],7.8]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7.8}] )
616
+ end
617
+ it 'encodes an array of strings with quotes' do
618
+ expect( textenc_string_array.encode(['',[' ',['{','}','\\',',','"','\t']]]) ).to eq( %[{"",{" ",{"{","}","\\\\",",","\\"","\\\\t"}}}] )
619
+ end
620
+ it 'encodes an array of int8 with sub arrays' do
621
+ expect( textenc_int_array.encode([1,[2,[3,4]],[nil,6],7]) ).to eq( %[{1,{2,{3,4}},{NULL,6},7}] )
622
+ end
623
+ it 'encodes an array of int8 with strings' do
624
+ expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
625
+ end
626
+ it 'encodes an array of float8 with sub arrays' do
627
+ 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*")))
628
+ end
629
+ end
630
+ context 'two dimensional arrays' do
631
+ it 'encodes an array of timestamps with sub arrays' do
632
+ expect( textenc_timestamp_array.encode([Time.new(2014,12,31),[nil, Time.new(2016,01,02, 23, 23, 59.99)]]) ).
633
+ to eq( %[{2014-12-31 00:00:00.000000000,{NULL,2016-01-02 23:23:59.990000000}}] )
634
+ end
635
+ end
636
+ context 'one dimensional array' do
637
+ it 'can encode empty arrays' do
638
+ expect( textenc_int_array.encode([]) ).to eq( '{}' )
639
+ expect( textenc_string_array.encode([]) ).to eq( '{}' )
640
+ end
641
+ it 'encodes an array of NULL strings w/wo quotes' do
642
+ expect( textenc_string_array.encode(['NUL', 'NULL', 'NULLL', 'nul', 'null', 'nulll']) ).to eq( %[{NUL,"NULL",NULLL,nul,"null",nulll}] )
643
+ end
644
+ it 'respects a different delimiter' do
645
+ expect( textenc_string_array_with_delimiter.encode(['a','b,','c']) ).to eq( '{a;b,;c}' )
646
+ end
647
+ end
648
+
649
+ context 'array of types with encoder in ruby space' do
650
+ it 'encodes with quotation and default character encoding' do
651
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
652
+ r = array_type.encode([3,4])
653
+ expect( r ).to eq( %[{"4 ","5 "}] )
654
+ expect( r.encoding ).to eq( Encoding::ASCII_8BIT )
655
+ end
656
+
657
+ it 'encodes with quotation and given character encoding' do
658
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
659
+ r = array_type.encode([3,4], Encoding::CP850)
660
+ expect( r ).to eq( %[{"4 ","5 "}] )
661
+ expect( r.encoding ).to eq( Encoding::CP850 )
662
+ end
663
+
664
+ it 'encodes without quotation' do
665
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: false
666
+ expect( array_type.encode([3,4]) ).to eq( %[{4 ,5 }] )
667
+ end
668
+
669
+ it 'encodes with default character encoding' do
670
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_encoding
671
+ r = array_type.encode([3,4])
672
+ expect( r ).to eq( %[{"4 ASCII-8BIT","5 ASCII-8BIT"}] )
673
+ expect( r.encoding ).to eq( Encoding::ASCII_8BIT )
674
+ end
675
+
676
+ it 'encodes with given character encoding' do
677
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_encoding
678
+ r = array_type.encode([3,4], Encoding::CP850)
679
+ expect( r ).to eq( %[{"4 CP850","5 CP850"}] )
680
+ expect( r.encoding ).to eq( Encoding::CP850 )
681
+ end
682
+
683
+ it "should raise when ruby encoder returns non string values" do
684
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_int_result, needs_quotation: false
685
+ expect{ array_type.encode([3,4]) }.to raise_error(TypeError)
686
+ end
687
+ end
688
+
689
+ it "should pass through non Array inputs" do
690
+ expect( textenc_float_array.encode("text") ).to eq( "text" )
691
+ expect( textenc_float_array.encode(1234) ).to eq( "1234" )
692
+ end
693
+
694
+ context 'literal quotation' do
695
+ it 'should quote and escape literals' do
696
+ quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
697
+ expect( quoted_type.encode(["'A\",","\\B'"]) ).to eq( %['{"''A\\",","\\\\B''"}'] )
698
+ end
699
+
700
+ it 'should quote literals with correct character encoding' do
701
+ quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
702
+ v = quoted_type.encode(["Héllo"], "iso-8859-1")
703
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
704
+ expect( v ).to eq( %['{Héllo}'].encode(Encoding::ISO_8859_1) )
705
+ end
706
+ end
707
+ end
708
+
709
+ it "should be possible to marshal encoders" do
710
+ mt = Marshal.dump(textenc_int_array)
711
+ lt = Marshal.load(mt)
712
+ expect( lt.to_h ).to eq( textenc_int_array.to_h )
713
+ end
714
+
715
+ it "should be possible to marshal encoders" do
716
+ mt = Marshal.dump(textdec_int_array)
717
+ lt = Marshal.load(mt)
718
+ expect( lt.to_h ).to eq( textdec_int_array.to_h )
719
+ end
720
+
721
+ it "should respond to to_h" do
722
+ expect( textenc_int_array.to_h ).to eq( {
723
+ name: nil, oid: 0, format: 0,
724
+ elements_type: textenc_int, needs_quotation: false, delimiter: ','
725
+ } )
726
+ end
727
+
728
+ it "shouldn't accept invalid elements_types" do
729
+ expect{ PG::TextEncoder::Array.new elements_type: false }.to raise_error(TypeError)
730
+ end
731
+
732
+ it "should have reasonable default values" do
733
+ t = PG::TextEncoder::Array.new
734
+ expect( t.format ).to eq( 0 )
735
+ expect( t.oid ).to eq( 0 )
736
+ expect( t.name ).to be_nil
737
+ expect( t.needs_quotation? ).to eq( true )
738
+ expect( t.delimiter ).to eq( ',' )
739
+ expect( t.elements_type ).to be_nil
740
+ end
741
+ end
742
+
743
+ it "should encode Strings as base64 in TextEncoder" do
744
+ e = PG::TextEncoder::ToBase64.new
745
+ expect( e.encode("") ).to eq("")
746
+ expect( e.encode("x") ).to eq("eA==")
747
+ expect( e.encode("xx") ).to eq("eHg=")
748
+ expect( e.encode("xxx") ).to eq("eHh4")
749
+ expect( e.encode("xxxx") ).to eq("eHh4eA==")
750
+ expect( e.encode("xxxxx") ).to eq("eHh4eHg=")
751
+ expect( e.encode("\0\n\t") ).to eq("AAoJ")
752
+ expect( e.encode("(\xFBm") ).to eq("KPtt")
753
+ end
754
+
755
+ it 'should encode Strings as base64 with correct character encoding' do
756
+ e = PG::TextEncoder::ToBase64.new
757
+ v = e.encode("Héllo".encode("utf-16le"), "iso-8859-1")
758
+ expect( v ).to eq("SOlsbG8=")
759
+ expect( v.encoding ).to eq(Encoding::ISO_8859_1)
760
+ end
761
+
762
+ it "should encode Strings as base64 in BinaryDecoder" do
763
+ e = PG::BinaryDecoder::ToBase64.new
764
+ expect( e.decode("x") ).to eq("eA==")
765
+ v = e.decode("Héllo".encode("utf-16le"))
766
+ expect( v ).to eq("SADpAGwAbABvAA==")
767
+ expect( v.encoding ).to eq(Encoding::ASCII_8BIT)
768
+ end
769
+
770
+ it "should encode Integers as base64" do
771
+ # Not really useful, but ensures that two-pass element and composite element encoders work.
772
+ e = PG::TextEncoder::ToBase64.new( elements_type: PG::TextEncoder::Array.new( elements_type: PG::TextEncoder::Integer.new, needs_quotation: false ))
773
+ expect( e.encode([1]) ).to eq(["{1}"].pack("m").chomp)
774
+ expect( e.encode([12]) ).to eq(["{12}"].pack("m").chomp)
775
+ expect( e.encode([123]) ).to eq(["{123}"].pack("m").chomp)
776
+ expect( e.encode([1234]) ).to eq(["{1234}"].pack("m").chomp)
777
+ expect( e.encode([12345]) ).to eq(["{12345}"].pack("m").chomp)
778
+ expect( e.encode([123456]) ).to eq(["{123456}"].pack("m").chomp)
779
+ expect( e.encode([1234567]) ).to eq(["{1234567}"].pack("m").chomp)
780
+ end
781
+
782
+ it "should decode base64 to Strings in TextDecoder" do
783
+ e = PG::TextDecoder::FromBase64.new
784
+ expect( e.decode("") ).to eq("")
785
+ expect( e.decode("eA==") ).to eq("x")
786
+ expect( e.decode("eHg=") ).to eq("xx")
787
+ expect( e.decode("eHh4") ).to eq("xxx")
788
+ expect( e.decode("eHh4eA==") ).to eq("xxxx")
789
+ expect( e.decode("eHh4eHg=") ).to eq("xxxxx")
790
+ expect( e.decode("AAoJ") ).to eq("\0\n\t")
791
+ expect( e.decode("KPtt") ).to eq("(\xFBm")
792
+ end
793
+
794
+ it "should decode base64 in BinaryEncoder" do
795
+ e = PG::BinaryEncoder::FromBase64.new
796
+ expect( e.encode("eA==") ).to eq("x")
797
+
798
+ e = PG::BinaryEncoder::FromBase64.new( elements_type: PG::TextEncoder::Integer.new )
799
+ expect( e.encode(124) ).to eq("124=".unpack("m")[0])
800
+ end
801
+
802
+ it "should decode base64 to Integers" do
803
+ # Not really useful, but ensures that composite element encoders work.
804
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::TextDecoder::Array.new( elements_type: PG::TextDecoder::Integer.new ))
805
+ expect( e.decode(["{1}"].pack("m")) ).to eq([1])
806
+ expect( e.decode(["{12}"].pack("m")) ).to eq([12])
807
+ expect( e.decode(["{123}"].pack("m")) ).to eq([123])
808
+ expect( e.decode(["{1234}"].pack("m")) ).to eq([1234])
809
+ expect( e.decode(["{12345}"].pack("m")) ).to eq([12345])
810
+ expect( e.decode(["{123456}"].pack("m")) ).to eq([123456])
811
+ expect( e.decode(["{1234567}"].pack("m")) ).to eq([1234567])
812
+ expect( e.decode(["{12345678}"].pack("m")) ).to eq([12345678])
813
+
814
+ e = PG::TextDecoder::FromBase64.new( elements_type: PG::BinaryDecoder::Integer.new )
815
+ expect( e.decode("ALxhTg==") ).to eq(12345678)
816
+ end
817
+
818
+ it "should decode base64 with garbage" do
819
+ e = PG::TextDecoder::FromBase64.new format: 1
820
+ expect( e.decode("=") ).to eq("=".unpack("m")[0])
821
+ expect( e.decode("==") ).to eq("==".unpack("m")[0])
822
+ expect( e.decode("===") ).to eq("===".unpack("m")[0])
823
+ expect( e.decode("====") ).to eq("====".unpack("m")[0])
824
+ expect( e.decode("a=") ).to eq("a=".unpack("m")[0])
825
+ expect( e.decode("a==") ).to eq("a==".unpack("m")[0])
826
+ expect( e.decode("a===") ).to eq("a===".unpack("m")[0])
827
+ expect( e.decode("a====") ).to eq("a====".unpack("m")[0])
828
+ expect( e.decode("aa=") ).to eq("aa=".unpack("m")[0])
829
+ expect( e.decode("aa==") ).to eq("aa==".unpack("m")[0])
830
+ expect( e.decode("aa===") ).to eq("aa===".unpack("m")[0])
831
+ expect( e.decode("aa====") ).to eq("aa====".unpack("m")[0])
832
+ expect( e.decode("aaa=") ).to eq("aaa=".unpack("m")[0])
833
+ expect( e.decode("aaa==") ).to eq("aaa==".unpack("m")[0])
834
+ expect( e.decode("aaa===") ).to eq("aaa===".unpack("m")[0])
835
+ expect( e.decode("aaa====") ).to eq("aaa====".unpack("m")[0])
836
+ expect( e.decode("=aa") ).to eq("=aa=".unpack("m")[0])
837
+ expect( e.decode("=aa=") ).to eq("=aa=".unpack("m")[0])
838
+ expect( e.decode("=aa==") ).to eq("=aa==".unpack("m")[0])
839
+ expect( e.decode("=aa===") ).to eq("=aa===".unpack("m")[0])
840
+ end
841
+ end
842
+
843
+ describe PG::CopyCoder do
844
+ describe PG::TextEncoder::CopyRow do
845
+ context "with default typemap" do
846
+ let!(:encoder) do
847
+ PG::TextEncoder::CopyRow.new
848
+ end
849
+
850
+ it "should encode different types of Ruby objects" do
851
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
852
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t[1, 2, 3]\t12.1\tabcdefg\t\\N\n")
853
+ end
854
+
855
+ it 'should output a string with correct character encoding' do
856
+ v = encoder.encode(["Héllo"], "iso-8859-1")
857
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
858
+ expect( v ).to eq( "Héllo\n".encode(Encoding::ISO_8859_1) )
859
+ end
860
+ end
861
+
862
+ context "with TypeMapByClass" do
863
+ let!(:tm) do
864
+ tm = PG::TypeMapByClass.new
865
+ tm[Integer] = textenc_int
866
+ tm[Float] = intenc_incrementer
867
+ tm[Array] = PG::TextEncoder::Array.new elements_type: textenc_string
868
+ tm
869
+ end
870
+ let!(:encoder) do
871
+ PG::TextEncoder::CopyRow.new type_map: tm
872
+ end
873
+
874
+ it "should have reasonable default values" do
875
+ expect( encoder.name ).to be_nil
876
+ expect( encoder.delimiter ).to eq( "\t" )
877
+ expect( encoder.null_string ).to eq( "\\N" )
878
+ end
879
+
880
+ it "copies all attributes with #dup" do
881
+ encoder.name = "test"
882
+ encoder.delimiter = "#"
883
+ encoder.null_string = "NULL"
884
+ encoder.type_map = PG::TypeMapByColumn.new []
885
+ encoder2 = encoder.dup
886
+ expect( encoder.object_id ).to_not eq( encoder2.object_id )
887
+ expect( encoder2.name ).to eq( "test" )
888
+ expect( encoder2.delimiter ).to eq( "#" )
889
+ expect( encoder2.null_string ).to eq( "NULL" )
890
+ expect( encoder2.type_map ).to be_a_kind_of( PG::TypeMapByColumn )
891
+ end
892
+
893
+ describe '#encode' do
894
+ it "should encode different types of Ruby objects" do
895
+ expect( encoder.encode([]) ).to eq("\n")
896
+ expect( encoder.encode(["a"]) ).to eq("a\n")
897
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
898
+ to eq("xyz\t123\t2456\t34567\t456789\t5678901\t{1,2,3}\t13 \tabcdefg\t\\N\n")
899
+ end
900
+
901
+ it "should escape special characters" do
902
+ expect( encoder.encode([" \0\t\n\r\\"]) ).to eq(" \0#\t#\n#\r#\\\n".gsub("#", "\\"))
903
+ end
904
+
905
+ it "should escape with different delimiter" do
906
+ encoder.delimiter = " "
907
+ encoder.null_string = "NULL"
908
+ expect( encoder.encode([nil, " ", "\0", "\t", "\n", "\r", "\\"]) ).to eq("NULL # \0 \t #\n #\r #\\\n".gsub("#", "\\"))
909
+ end
910
+ end
911
+ end
912
+ end
913
+
914
+ describe PG::TextDecoder::CopyRow do
915
+ context "with default typemap" do
916
+ let!(:decoder) do
917
+ PG::TextDecoder::CopyRow.new
918
+ end
919
+
920
+ describe '#decode' do
921
+ it "should decode COPY text format to array of strings" do
922
+ 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"] )
923
+ end
924
+
925
+ it 'should respect input character encoding' do
926
+ v = decoder.decode("Héllo\n".encode("iso-8859-1")).first
927
+ expect( v.encoding ).to eq(Encoding::ISO_8859_1)
928
+ expect( v ).to eq("Héllo".encode("iso-8859-1"))
929
+ end
930
+ end
931
+ end
932
+
933
+ context "with TypeMapByColumn" do
934
+ let!(:tm) do
935
+ PG::TypeMapByColumn.new [textdec_int, textdec_string, intdec_incrementer, nil]
936
+ end
937
+ let!(:decoder) do
938
+ PG::TextDecoder::CopyRow.new type_map: tm
939
+ end
940
+
941
+ describe '#decode' do
942
+ it "should decode different types of Ruby objects" do
943
+ 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"] )
944
+ end
945
+ end
946
+ end
947
+ end
948
+ end
949
+ end