pg 0.18.0 → 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 (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