pg 0.18.0 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/BSDL +2 -2
  4. data/ChangeLog +1221 -4
  5. data/History.rdoc +200 -0
  6. data/Manifest.txt +5 -18
  7. data/README-Windows.rdoc +15 -26
  8. data/README.rdoc +27 -10
  9. data/Rakefile +33 -24
  10. data/Rakefile.cross +57 -39
  11. data/ext/errorcodes.def +37 -0
  12. data/ext/errorcodes.rb +1 -1
  13. data/ext/errorcodes.txt +16 -1
  14. data/ext/extconf.rb +29 -35
  15. data/ext/gvl_wrappers.c +4 -0
  16. data/ext/gvl_wrappers.h +27 -39
  17. data/ext/pg.c +27 -53
  18. data/ext/pg.h +66 -83
  19. data/ext/pg_binary_decoder.c +75 -6
  20. data/ext/pg_binary_encoder.c +14 -12
  21. data/ext/pg_coder.c +83 -13
  22. data/ext/pg_connection.c +627 -351
  23. data/ext/pg_copy_coder.c +44 -9
  24. data/ext/pg_result.c +364 -134
  25. data/ext/pg_text_decoder.c +605 -46
  26. data/ext/pg_text_encoder.c +95 -76
  27. data/ext/pg_tuple.c +541 -0
  28. data/ext/pg_type_map.c +20 -13
  29. data/ext/pg_type_map_by_column.c +7 -7
  30. data/ext/pg_type_map_by_mri_type.c +2 -2
  31. data/ext/pg_type_map_in_ruby.c +4 -7
  32. data/ext/util.c +7 -7
  33. data/ext/util.h +3 -3
  34. data/lib/pg/basic_type_mapping.rb +105 -45
  35. data/lib/pg/binary_decoder.rb +22 -0
  36. data/lib/pg/coder.rb +1 -1
  37. data/lib/pg/connection.rb +109 -39
  38. data/lib/pg/constants.rb +1 -1
  39. data/lib/pg/exceptions.rb +1 -1
  40. data/lib/pg/result.rb +11 -6
  41. data/lib/pg/text_decoder.rb +25 -20
  42. data/lib/pg/text_encoder.rb +43 -1
  43. data/lib/pg/tuple.rb +30 -0
  44. data/lib/pg/type_map_by_column.rb +1 -1
  45. data/lib/pg.rb +21 -11
  46. data/spec/helpers.rb +50 -25
  47. data/spec/pg/basic_type_mapping_spec.rb +287 -30
  48. data/spec/pg/connection_spec.rb +695 -282
  49. data/spec/pg/connection_sync_spec.rb +41 -0
  50. data/spec/pg/result_spec.rb +59 -17
  51. data/spec/pg/tuple_spec.rb +280 -0
  52. data/spec/pg/type_map_by_class_spec.rb +3 -3
  53. data/spec/pg/type_map_by_column_spec.rb +1 -1
  54. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  55. data/spec/pg/type_map_by_oid_spec.rb +1 -1
  56. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  57. data/spec/pg/type_map_spec.rb +1 -1
  58. data/spec/pg/type_spec.rb +319 -35
  59. data/spec/pg_spec.rb +2 -2
  60. data.tar.gz.sig +0 -0
  61. metadata +68 -68
  62. metadata.gz.sig +0 -0
  63. data/sample/array_insert.rb +0 -20
  64. data/sample/async_api.rb +0 -106
  65. data/sample/async_copyto.rb +0 -39
  66. data/sample/async_mixed.rb +0 -56
  67. data/sample/check_conn.rb +0 -21
  68. data/sample/copyfrom.rb +0 -81
  69. data/sample/copyto.rb +0 -19
  70. data/sample/cursor.rb +0 -21
  71. data/sample/disk_usage_report.rb +0 -186
  72. data/sample/issue-119.rb +0 -94
  73. data/sample/losample.rb +0 -69
  74. data/sample/minimal-testcase.rb +0 -17
  75. data/sample/notify_wait.rb +0 -72
  76. data/sample/pg_statistics.rb +0 -294
  77. data/sample/replication_monitor.rb +0 -231
  78. data/sample/test_binary_values.rb +0 -33
  79. data/sample/wal_shipper.rb +0 -434
  80. data/sample/warehouse_partitions.rb +0 -320
data/spec/pg/type_spec.rb CHANGED
@@ -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
@@ -15,6 +16,11 @@ describe "PG::Type derivations" do
15
16
  let!(:textdec_string) { PG::TextDecoder::String.new }
16
17
  let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
17
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 }
18
24
  let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
19
25
  let!(:textdec_bytea) { PG::TextDecoder::Bytea.new }
20
26
  let!(:binaryenc_int2) { PG::BinaryEncoder::Int2.new }
@@ -37,6 +43,14 @@ describe "PG::Type derivations" do
37
43
  end.new
38
44
  end
39
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
40
54
  let!(:intenc_incrementer_with_int_result) do
41
55
  Class.new(PG::SimpleEncoder) do
42
56
  def encode(value)
@@ -65,7 +79,7 @@ describe "PG::Type derivations" do
65
79
  expect( intdec_incrementer.decode("3") ).to eq( 4 )
66
80
  end
67
81
 
68
- it "should decode integers of different lengths form text format" do
82
+ it "should decode integers of different lengths from text format" do
69
83
  30.times do |zeros|
70
84
  expect( textdec_int.decode("1" + "0"*zeros) ).to eq( 10 ** zeros )
71
85
  expect( textdec_int.decode(zeros==0 ? "0" : "9"*zeros) ).to eq( 10 ** zeros - 1 )
@@ -85,6 +99,150 @@ describe "PG::Type derivations" do
85
99
  expect( textdec_bytea.decode("\\377\\000") ).to eq( "\xff\0".b )
86
100
  end
87
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
+
88
246
  it "should raise when decode method is called with wrong args" do
89
247
  expect{ textdec_int.decode() }.to raise_error(ArgumentError)
90
248
  expect{ textdec_int.decode("123", 2, 3, 4) }.to raise_error(ArgumentError)
@@ -156,10 +314,60 @@ describe "PG::Type derivations" do
156
314
  expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
157
315
  end
158
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
+
159
361
  it "should encode with ruby encoder" do
160
362
  expect( intenc_incrementer.encode(3) ).to eq( "4 " )
161
363
  end
162
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
+
163
371
  it "should return when ruby encoder returns non string values" do
164
372
  expect( intenc_incrementer_with_int_result.encode(3) ).to eq( 4 )
165
373
  end
@@ -215,6 +423,7 @@ describe "PG::Type derivations" do
215
423
  describe "Array types" do
216
424
  let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
217
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 }
218
427
  let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
219
428
  let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
220
429
  let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
@@ -280,6 +489,57 @@ describe "PG::Type derivations" do
280
489
  it 'respects a different delimiter' do
281
490
  expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
282
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
283
543
  end
284
544
 
285
545
  context 'bytea' do
@@ -347,20 +607,6 @@ describe "PG::Type derivations" do
347
607
  array_type = PG::TextDecoder::Array.new elements_type: nil
348
608
  expect( array_type.decode(%[{3,4}]) ).to eq( ['3','4'] )
349
609
  end
350
-
351
- context 'identifier quotation' do
352
- it 'should build an array out of an quoted identifier string' do
353
- quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string
354
- expect( quoted_type.decode(%["A.".".B"]) ).to eq( ["A.", ".B"] )
355
- expect( quoted_type.decode(%["'A"".""B'"]) ).to eq( ['\'A"."B\''] )
356
- end
357
-
358
- it 'should split unquoted identifier string' do
359
- quoted_type = PG::TextDecoder::Identifier.new elements_type: textdec_string
360
- expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] )
361
- expect( quoted_type.decode(%[a]) ).to eq( ['a'] )
362
- end
363
- end
364
610
  end
365
611
 
366
612
  describe '#encode' do
@@ -401,9 +647,18 @@ describe "PG::Type derivations" do
401
647
  end
402
648
 
403
649
  context 'array of types with encoder in ruby space' do
404
- it 'encodes with quotation' 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
405
658
  array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
406
- expect( array_type.encode([3,4]) ).to eq( %[{"4 ","5 "}] )
659
+ r = array_type.encode([3,4], Encoding::CP850)
660
+ expect( r ).to eq( %[{"4 ","5 "}] )
661
+ expect( r.encoding ).to eq( Encoding::CP850 )
407
662
  end
408
663
 
409
664
  it 'encodes without quotation' do
@@ -411,6 +666,20 @@ describe "PG::Type derivations" do
411
666
  expect( array_type.encode([3,4]) ).to eq( %[{4 ,5 }] )
412
667
  end
413
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
+
414
683
  it "should raise when ruby encoder returns non string values" do
415
684
  array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_int_result, needs_quotation: false
416
685
  expect{ array_type.encode([3,4]) }.to raise_error(TypeError)
@@ -422,27 +691,18 @@ describe "PG::Type derivations" do
422
691
  expect( textenc_float_array.encode(1234) ).to eq( "1234" )
423
692
  end
424
693
 
425
- context 'identifier quotation' do
426
- it 'should quote and escape identifier' do
427
- quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string
428
- expect( quoted_type.encode(['schema','table','col']) ).to eq( %["schema"."table"."col"] )
429
- expect( quoted_type.encode(['A.','.B']) ).to eq( %["A.".".B"] )
430
- expect( quoted_type.encode(%['A"."B']) ).to eq( %["'A"".""B'"] )
431
- end
432
-
433
- it 'shouldn\'t quote or escape identifier if requested to not do' do
434
- quoted_type = PG::TextEncoder::Identifier.new elements_type: textenc_string,
435
- needs_quotation: false
436
- expect( quoted_type.encode(['a','b']) ).to eq( %[a.b] )
437
- expect( quoted_type.encode(%[a.b]) ).to eq( %[a.b] )
438
- end
439
- end
440
-
441
694
  context 'literal quotation' do
442
695
  it 'should quote and escape literals' do
443
696
  quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
444
697
  expect( quoted_type.encode(["'A\",","\\B'"]) ).to eq( %['{"''A\\",","\\\\B''"}'] )
445
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
446
706
  end
447
707
  end
448
708
 
@@ -489,11 +749,22 @@ describe "PG::Type derivations" do
489
749
  expect( e.encode("xxxx") ).to eq("eHh4eA==")
490
750
  expect( e.encode("xxxxx") ).to eq("eHh4eHg=")
491
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)
492
760
  end
493
761
 
494
762
  it "should encode Strings as base64 in BinaryDecoder" do
495
763
  e = PG::BinaryDecoder::ToBase64.new
496
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)
497
768
  end
498
769
 
499
770
  it "should encode Integers as base64" do
@@ -517,6 +788,7 @@ describe "PG::Type derivations" do
517
788
  expect( e.decode("eHh4eA==") ).to eq("xxxx")
518
789
  expect( e.decode("eHh4eHg=") ).to eq("xxxxx")
519
790
  expect( e.decode("AAoJ") ).to eq("\0\n\t")
791
+ expect( e.decode("KPtt") ).to eq("(\xFBm")
520
792
  end
521
793
 
522
794
  it "should decode base64 in BinaryEncoder" do
@@ -579,6 +851,12 @@ describe "PG::Type derivations" do
579
851
  expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
580
852
  to eq("xyz\t123\t2456\t34567\t456789\t5678901\t[1, 2, 3]\t12.1\tabcdefg\t\\N\n")
581
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
582
860
  end
583
861
 
584
862
  context "with TypeMapByClass" do
@@ -640,9 +918,15 @@ describe "PG::Type derivations" do
640
918
  end
641
919
 
642
920
  describe '#decode' do
643
- it "should decode different types of Ruby objects" do
921
+ it "should decode COPY text format to array of strings" do
644
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"] )
645
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
646
930
  end
647
931
  end
648
932
 
data/spec/pg_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative 'helpers'
@@ -7,7 +7,7 @@ require 'pg'
7
7
 
8
8
  describe PG do
9
9
 
10
- it "knows what version of the libpq library is loaded", :postgresql_91 do
10
+ it "knows what version of the libpq library is loaded" do
11
11
  expect( PG.library_version ).to be_an( Integer )
12
12
  expect( PG.library_version ).to be >= 90100
13
13
  end
data.tar.gz.sig CHANGED
Binary file