pg 0.18.4 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/BSDL +2 -2
  5. data/ChangeLog +0 -5911
  6. data/History.rdoc +240 -0
  7. data/Manifest.txt +8 -20
  8. data/README-Windows.rdoc +4 -4
  9. data/README.ja.rdoc +1 -2
  10. data/README.rdoc +64 -15
  11. data/Rakefile +20 -21
  12. data/Rakefile.cross +67 -69
  13. data/ext/errorcodes.def +101 -0
  14. data/ext/errorcodes.rb +1 -1
  15. data/ext/errorcodes.txt +33 -2
  16. data/ext/extconf.rb +26 -36
  17. data/ext/gvl_wrappers.c +4 -0
  18. data/ext/gvl_wrappers.h +27 -39
  19. data/ext/pg.c +156 -145
  20. data/ext/pg.h +74 -98
  21. data/ext/pg_binary_decoder.c +82 -15
  22. data/ext/pg_binary_encoder.c +20 -19
  23. data/ext/pg_coder.c +103 -21
  24. data/ext/pg_connection.c +917 -523
  25. data/ext/pg_copy_coder.c +50 -12
  26. data/ext/pg_record_coder.c +491 -0
  27. data/ext/pg_result.c +590 -208
  28. data/ext/pg_text_decoder.c +606 -40
  29. data/ext/pg_text_encoder.c +245 -94
  30. data/ext/pg_tuple.c +549 -0
  31. data/ext/pg_type_map.c +14 -7
  32. data/ext/pg_type_map_all_strings.c +4 -4
  33. data/ext/pg_type_map_by_class.c +9 -4
  34. data/ext/pg_type_map_by_column.c +7 -6
  35. data/ext/pg_type_map_by_mri_type.c +1 -1
  36. data/ext/pg_type_map_by_oid.c +3 -2
  37. data/ext/pg_type_map_in_ruby.c +1 -1
  38. data/ext/{util.c → pg_util.c} +10 -10
  39. data/ext/{util.h → pg_util.h} +2 -2
  40. data/lib/pg.rb +23 -13
  41. data/lib/pg/basic_type_mapping.rb +155 -32
  42. data/lib/pg/binary_decoder.rb +23 -0
  43. data/lib/pg/coder.rb +23 -2
  44. data/lib/pg/connection.rb +73 -13
  45. data/lib/pg/constants.rb +2 -1
  46. data/lib/pg/exceptions.rb +2 -1
  47. data/lib/pg/result.rb +24 -7
  48. data/lib/pg/text_decoder.rb +24 -22
  49. data/lib/pg/text_encoder.rb +40 -8
  50. data/lib/pg/tuple.rb +30 -0
  51. data/lib/pg/type_map_by_column.rb +3 -2
  52. data/spec/helpers.rb +61 -36
  53. data/spec/pg/basic_type_mapping_spec.rb +415 -36
  54. data/spec/pg/connection_spec.rb +732 -327
  55. data/spec/pg/connection_sync_spec.rb +41 -0
  56. data/spec/pg/result_spec.rb +253 -21
  57. data/spec/pg/tuple_spec.rb +333 -0
  58. data/spec/pg/type_map_by_class_spec.rb +4 -4
  59. data/spec/pg/type_map_by_column_spec.rb +6 -2
  60. data/spec/pg/type_map_by_mri_type_spec.rb +2 -2
  61. data/spec/pg/type_map_by_oid_spec.rb +3 -3
  62. data/spec/pg/type_map_in_ruby_spec.rb +1 -1
  63. data/spec/pg/type_map_spec.rb +1 -1
  64. data/spec/pg/type_spec.rb +446 -20
  65. data/spec/pg_spec.rb +2 -2
  66. metadata +63 -72
  67. metadata.gz.sig +0 -0
  68. data/sample/array_insert.rb +0 -20
  69. data/sample/async_api.rb +0 -106
  70. data/sample/async_copyto.rb +0 -39
  71. data/sample/async_mixed.rb +0 -56
  72. data/sample/check_conn.rb +0 -21
  73. data/sample/copyfrom.rb +0 -81
  74. data/sample/copyto.rb +0 -19
  75. data/sample/cursor.rb +0 -21
  76. data/sample/disk_usage_report.rb +0 -186
  77. data/sample/issue-119.rb +0 -94
  78. data/sample/losample.rb +0 -69
  79. data/sample/minimal-testcase.rb +0 -17
  80. data/sample/notify_wait.rb +0 -72
  81. data/sample/pg_statistics.rb +0 -294
  82. data/sample/replication_monitor.rb +0 -231
  83. data/sample/test_binary_values.rb +0 -33
  84. data/sample/wal_shipper.rb +0 -434
  85. data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,333 @@
1
+ # -*- rspec -*-
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+ require 'pg'
6
+ require 'objspace'
7
+
8
+ describe PG::Tuple do
9
+ let!(:typemap) { PG::BasicTypeMapForResults.new(@conn) }
10
+ let!(:result2x2) { @conn.exec( "VALUES(1, 'a'), (2, 'b')" ) }
11
+ let!(:result2x2sym) { @conn.exec( "VALUES(1, 'a'), (2, 'b')" ).field_names_as(:symbol) }
12
+ let!(:result2x3cast) do
13
+ @conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" )
14
+ .map_types!(typemap)
15
+ end
16
+ let!(:result2x3symcast) do
17
+ @conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" )
18
+ .map_types!(typemap)
19
+ .field_names_as(:symbol)
20
+ end
21
+ let!(:tuple0) { result2x2.tuple(0) }
22
+ let!(:tuple0sym) { result2x2sym.tuple(0) }
23
+ let!(:tuple1) { result2x2.tuple(1) }
24
+ let!(:tuple1sym) { result2x2sym.tuple(1) }
25
+ let!(:tuple2) { result2x3cast.tuple(0) }
26
+ let!(:tuple2sym) { result2x3symcast.tuple(0) }
27
+ let!(:tuple3) { str = Marshal.dump(result2x3cast.tuple(1)); Marshal.load(str) }
28
+ let!(:tuple_empty) { PG::Tuple.new }
29
+
30
+ describe "[]" do
31
+ it "returns nil for invalid keys" do
32
+ expect( tuple0["x"] ).to be_nil
33
+ expect( tuple0[0.5] ).to be_nil
34
+ expect( tuple0[2] ).to be_nil
35
+ expect( tuple0[-3] ).to be_nil
36
+ expect( tuple2[-4] ).to be_nil
37
+ expect{ tuple_empty[0] }.to raise_error(TypeError)
38
+ end
39
+
40
+ it "supports array like access" do
41
+ expect( tuple0[0] ).to eq( "1" )
42
+ expect( tuple0[1] ).to eq( "a" )
43
+ expect( tuple1[0] ).to eq( "2" )
44
+ expect( tuple1[1] ).to eq( "b" )
45
+ expect( tuple2[0] ).to eq( 1 )
46
+ expect( tuple2[1] ).to eq( true )
47
+ expect( tuple2[2] ).to eq( "3" )
48
+ expect( tuple3[0] ).to eq( 2 )
49
+ expect( tuple3[1] ).to eq( false )
50
+ expect( tuple3[2] ).to eq( "4" )
51
+ end
52
+
53
+ it "supports negative indices" do
54
+ expect( tuple0[-2] ).to eq( "1" )
55
+ expect( tuple0[-1] ).to eq( "a" )
56
+ expect( tuple2[-3] ).to eq( 1 )
57
+ expect( tuple2[-2] ).to eq( true )
58
+ expect( tuple2[-1] ).to eq( "3" )
59
+ end
60
+
61
+ it "supports hash like access" do
62
+ expect( tuple0["column1"] ).to eq( "1" )
63
+ expect( tuple0["column2"] ).to eq( "a" )
64
+ expect( tuple2["a"] ).to eq( 1 )
65
+ expect( tuple2["b"] ).to eq( "3" )
66
+ expect( tuple0[:b] ).to be_nil
67
+ expect( tuple0["x"] ).to be_nil
68
+ end
69
+
70
+ it "supports hash like access with symbols" do
71
+ expect( tuple0sym[:column1] ).to eq( "1" )
72
+ expect( tuple0sym[:column2] ).to eq( "a" )
73
+ expect( tuple2sym[:a] ).to eq( 1 )
74
+ expect( tuple2sym[:b] ).to eq( "3" )
75
+ expect( tuple2sym["b"] ).to be_nil
76
+ expect( tuple0sym[:x] ).to be_nil
77
+ end
78
+
79
+ it "casts lazy and caches result" do
80
+ a = []
81
+ deco = Class.new(PG::SimpleDecoder) do
82
+ define_method(:decode) do |*args|
83
+ a << args
84
+ args.last
85
+ end
86
+ end.new
87
+
88
+ result2x2.map_types!(PG::TypeMapByColumn.new([deco, deco]))
89
+ t = result2x2.tuple(1)
90
+
91
+ # cast and cache at first call to [0]
92
+ a.clear
93
+ expect( t[0] ).to eq( 0 )
94
+ expect( a ).to eq([["2", 1, 0]])
95
+
96
+ # use cache at second call to [0]
97
+ a.clear
98
+ expect( t[0] ).to eq( 0 )
99
+ expect( a ).to eq([])
100
+
101
+ # cast and cache at first call to [1]
102
+ a.clear
103
+ expect( t[1] ).to eq( 1 )
104
+ expect( a ).to eq([["b", 1, 1]])
105
+ end
106
+ end
107
+
108
+ describe "fetch" do
109
+ it "raises proper errors for invalid keys" do
110
+ expect{ tuple0.fetch("x") }.to raise_error(KeyError)
111
+ expect{ tuple0.fetch(0.5) }.to raise_error(KeyError)
112
+ expect{ tuple0.fetch(2) }.to raise_error(IndexError)
113
+ expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
114
+ expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
115
+ expect{ tuple2.fetch(-4) }.to raise_error(IndexError)
116
+ expect{ tuple_empty[0] }.to raise_error(TypeError)
117
+ end
118
+
119
+ it "supports array like access" do
120
+ expect( tuple0.fetch(0) ).to eq( "1" )
121
+ expect( tuple0.fetch(1) ).to eq( "a" )
122
+ expect( tuple2.fetch(0) ).to eq( 1 )
123
+ expect( tuple2.fetch(1) ).to eq( true )
124
+ expect( tuple2.fetch(2) ).to eq( "3" )
125
+ end
126
+
127
+ it "supports default value for indices" do
128
+ expect( tuple0.fetch(2, 42) ).to eq( 42 )
129
+ expect( tuple0.fetch(2){43} ).to eq( 43 )
130
+ end
131
+
132
+ it "supports negative indices" do
133
+ expect( tuple0.fetch(-2) ).to eq( "1" )
134
+ expect( tuple0.fetch(-1) ).to eq( "a" )
135
+ expect( tuple2.fetch(-3) ).to eq( 1 )
136
+ expect( tuple2.fetch(-2) ).to eq( true )
137
+ expect( tuple2.fetch(-1) ).to eq( "3" )
138
+ end
139
+
140
+ it "supports hash like access" do
141
+ expect( tuple0.fetch("column1") ).to eq( "1" )
142
+ expect( tuple0.fetch("column2") ).to eq( "a" )
143
+ expect( tuple2.fetch("a") ).to eq( 1 )
144
+ expect( tuple2.fetch("b") ).to eq( "3" )
145
+ end
146
+
147
+ it "supports default value for name keys" do
148
+ expect( tuple0.fetch("x", "defa") ).to eq("defa")
149
+ expect( tuple0.fetch("x"){"defa"} ).to eq("defa")
150
+ end
151
+ end
152
+
153
+ describe "each" do
154
+ it "can be used as an enumerator" do
155
+ expect( tuple0.each ).to be_kind_of(Enumerator)
156
+ expect( tuple0.each.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
157
+ expect( tuple1.each.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
158
+ expect( tuple2.each.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
159
+ expect( tuple3.each.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
160
+ expect{ tuple_empty.each }.to raise_error(TypeError)
161
+ end
162
+
163
+ it "can be used as an enumerator with symbols" do
164
+ expect( tuple0sym.each ).to be_kind_of(Enumerator)
165
+ expect( tuple0sym.each.to_a ).to eq( [[:column1, "1"], [:column2, "a"]] )
166
+ expect( tuple2sym.each.to_a ).to eq( [[:a, 1], [:b, true], [:b, "3"]] )
167
+ end
168
+
169
+ it "can be used with block" do
170
+ a = []
171
+ tuple0.each do |*v|
172
+ a << v
173
+ end
174
+ expect( a ).to eq( [["column1", "1"], ["column2", "a"]] )
175
+ end
176
+ end
177
+
178
+ describe "each_value" do
179
+ it "can be used as an enumerator" do
180
+ expect( tuple0.each_value ).to be_kind_of(Enumerator)
181
+ expect( tuple0.each_value.to_a ).to eq( ["1", "a"] )
182
+ expect( tuple1.each_value.to_a ).to eq( ["2", "b"] )
183
+ expect( tuple2.each_value.to_a ).to eq( [1, true, "3"] )
184
+ expect( tuple3.each_value.to_a ).to eq( [2, false, "4"] )
185
+ expect{ tuple_empty.each_value }.to raise_error(TypeError)
186
+ end
187
+
188
+ it "can be used with block" do
189
+ a = []
190
+ tuple0.each_value do |v|
191
+ a << v
192
+ end
193
+ expect( a ).to eq( ["1", "a"] )
194
+ end
195
+ end
196
+
197
+ it "responds to values" do
198
+ expect( tuple0.values ).to eq( ["1", "a"] )
199
+ expect( tuple3.values ).to eq( [2, false, "4"] )
200
+ expect{ tuple_empty.values }.to raise_error(TypeError)
201
+ end
202
+
203
+ it "responds to key?" do
204
+ expect( tuple1.key?("column1") ).to eq( true )
205
+ expect( tuple1.key?(:column1) ).to eq( false )
206
+ expect( tuple1.key?("other") ).to eq( false )
207
+ expect( tuple1.has_key?("column1") ).to eq( true )
208
+ expect( tuple1.has_key?("other") ).to eq( false )
209
+ end
210
+
211
+ it "responds to key? as symbol" do
212
+ expect( tuple1sym.key?(:column1) ).to eq( true )
213
+ expect( tuple1sym.key?("column1") ).to eq( false )
214
+ expect( tuple1sym.key?(:other) ).to eq( false )
215
+ expect( tuple1sym.has_key?(:column1) ).to eq( true )
216
+ expect( tuple1sym.has_key?(:other) ).to eq( false )
217
+ end
218
+
219
+ it "responds to keys" do
220
+ expect( tuple0.keys ).to eq( ["column1", "column2"] )
221
+ expect( tuple2.keys ).to eq( ["a", "b", "b"] )
222
+ end
223
+
224
+ it "responds to keys as symbol" do
225
+ expect( tuple0sym.keys ).to eq( [:column1, :column2] )
226
+ expect( tuple2sym.keys ).to eq( [:a, :b, :b] )
227
+ end
228
+
229
+ describe "each_key" do
230
+ it "can be used as an enumerator" do
231
+ expect( tuple0.each_key ).to be_kind_of(Enumerator)
232
+ expect( tuple0.each_key.to_a ).to eq( ["column1", "column2"] )
233
+ expect( tuple2.each_key.to_a ).to eq( ["a", "b", "b"] )
234
+ end
235
+
236
+ it "can be used with block" do
237
+ a = []
238
+ tuple0.each_key do |v|
239
+ a << v
240
+ end
241
+ expect( a ).to eq( ["column1", "column2"] )
242
+ end
243
+ end
244
+
245
+ it "responds to length" do
246
+ expect( tuple0.length ).to eq( 2 )
247
+ expect( tuple0.size ).to eq( 2 )
248
+ expect( tuple2.size ).to eq( 3 )
249
+ end
250
+
251
+ it "responds to index" do
252
+ expect( tuple0.index("column1") ).to eq( 0 )
253
+ expect( tuple0.index(:column1) ).to eq( nil )
254
+ expect( tuple0.index("column2") ).to eq( 1 )
255
+ expect( tuple0.index("x") ).to eq( nil )
256
+ expect( tuple2.index("a") ).to eq( 0 )
257
+ expect( tuple2.index("b") ).to eq( 2 )
258
+ end
259
+
260
+ it "responds to index with symbol" do
261
+ expect( tuple0sym.index(:column1) ).to eq( 0 )
262
+ expect( tuple0sym.index("column1") ).to eq( nil )
263
+ expect( tuple0sym.index(:column2) ).to eq( 1 )
264
+ expect( tuple0sym.index(:x) ).to eq( nil )
265
+ expect( tuple2sym.index(:a) ).to eq( 0 )
266
+ expect( tuple2sym.index(:b) ).to eq( 2 )
267
+ end
268
+
269
+ it "can be used as Enumerable" do
270
+ expect( tuple0.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
271
+ expect( tuple1.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
272
+ expect( tuple2.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
273
+ expect( tuple3.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
274
+ end
275
+
276
+ it "can be marshaled" do
277
+ [tuple0, tuple1, tuple2, tuple3, tuple0sym, tuple2sym].each do |t1|
278
+ str = Marshal.dump(t1)
279
+ t2 = Marshal.load(str)
280
+
281
+ expect( t2 ).to be_kind_of(t1.class)
282
+ expect( t2 ).not_to equal(t1)
283
+ expect( t2.to_a ).to eq(t1.to_a)
284
+ end
285
+ end
286
+
287
+ it "passes instance variables when marshaled" do
288
+ t1 = tuple0
289
+ t1.instance_variable_set("@a", 4711)
290
+ str = Marshal.dump(t1)
291
+ t2 = Marshal.load(str)
292
+
293
+ expect( t2.instance_variable_get("@a") ).to eq( 4711 )
294
+ end
295
+
296
+ it "can't be marshaled when empty" do
297
+ expect{ Marshal.dump(tuple_empty) }.to raise_error(TypeError)
298
+ end
299
+
300
+ it "should give account about memory usage" do
301
+ expect( ObjectSpace.memsize_of(tuple0) ).to be > 40
302
+ expect( ObjectSpace.memsize_of(tuple_empty) ).to be > 0
303
+ end
304
+
305
+ it "should override #inspect" do
306
+ expect( tuple1.inspect ).to eq('#<PG::Tuple column1: "2", column2: "b">')
307
+ expect( tuple2.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
308
+ expect( tuple2sym.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
309
+ expect{ tuple_empty.inspect }.to raise_error(TypeError)
310
+ end
311
+
312
+ context "with cleared result" do
313
+ it "should raise an error when non-materialized fields are used" do
314
+ r = result2x2
315
+ t = r.tuple(0)
316
+ t[0] # materialize first field only
317
+ r.clear
318
+
319
+ # second column should fail
320
+ expect{ t[1] }.to raise_error(PG::Error)
321
+ expect{ t.fetch(1) }.to raise_error(PG::Error)
322
+ expect{ t.fetch("column2") }.to raise_error(PG::Error)
323
+
324
+ # first column should succeed
325
+ expect( t[0] ).to eq( "1" )
326
+ expect( t.fetch(0) ).to eq( "1" )
327
+ expect( t.fetch("column1") ).to eq( "1" )
328
+
329
+ # should fail due to the second column
330
+ expect{ t.values }.to raise_error(PG::Error)
331
+ end
332
+ end
333
+ end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -59,7 +59,7 @@ describe PG::TypeMapByClass do
59
59
  it "should retrieve particular conversions" do
60
60
  expect( tm[Integer] ).to eq(binaryenc_int)
61
61
  expect( tm[Float] ).to eq(textenc_float)
62
- expect( tm[Bignum] ).to be_nil
62
+ expect( tm[Range] ).to be_nil
63
63
  expect( derived_tm[raise_class] ).to be_kind_of(Proc)
64
64
  expect( derived_tm[Array] ).to eq(:array_type_map_for)
65
65
  end
@@ -102,7 +102,7 @@ describe PG::TypeMapByClass do
102
102
 
103
103
  it "should allow mixed type conversions" do
104
104
  res = @conn.exec_params( "SELECT $1, $2, $3", [5, 1.23, :TestSymbol], 0, tm )
105
- expect( res.values ).to eq([['5', '1.23', '[:TestSymbol]']])
105
+ expect( res.values ).to eq([['5', '1.23', "[:TestSymbol, #{@conn.internal_encoding.inspect}]"]])
106
106
  expect( res.ftype(0) ).to eq(20)
107
107
  end
108
108
 
@@ -132,7 +132,7 @@ describe PG::TypeMapByClass do
132
132
  it "should raise error on invalid coder object" do
133
133
  tm[TrueClass] = "dummy"
134
134
  expect{
135
- res = @conn.exec_params( "SELECT $1", [true], 0, tm )
135
+ @conn.exec_params( "SELECT $1", [true], 0, tm )
136
136
  }.to raise_error(NoMethodError, /undefined method.*call/)
137
137
  end
138
138
  end
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -38,7 +38,11 @@ describe PG::TypeMapByColumn do
38
38
  pass_through_type,
39
39
  nil
40
40
  ] )
41
- expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:0 TEXT:0 FLOAT4:0 pass_through:1 nil>" )
41
+ end
42
+
43
+ it "should respond to inspect" do
44
+ cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, PG::TextEncoder::Float.new, nil] )
45
+ expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:TD TEXT:TE FLOAT4:TD pass_through:BD PG::TextEncoder::Float:TE nil>" )
42
46
  end
43
47
 
44
48
  it "should retrieve it's oids" do
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -116,7 +116,7 @@ describe PG::TypeMapByMriType do
116
116
 
117
117
  it "should allow mixed type conversions" do
118
118
  res = @conn.exec_params( "SELECT $1, $2, $3", [5, 1.23, :TestSymbol], 0, tm )
119
- expect( res.values ).to eq([['5', '1.23', '[:TestSymbol]']])
119
+ expect( res.values ).to eq([['5', '1.23', "[:TestSymbol, #{@conn.internal_encoding.inspect}]"]])
120
120
  expect( res.ftype(0) ).to eq(20)
121
121
  end
122
122
 
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -54,8 +54,8 @@ describe PG::TypeMapByOid do
54
54
  end
55
55
 
56
56
  it "should check format when deleting coders" do
57
- expect{ tm.rm_coder 2, 123 }.to raise_error(ArgumentError)
58
- expect{ tm.rm_coder -1, 123 }.to raise_error(ArgumentError)
57
+ expect{ tm.rm_coder(2, 123) }.to raise_error(ArgumentError)
58
+ expect{ tm.rm_coder(-1, 123) }.to raise_error(ArgumentError)
59
59
  end
60
60
 
61
61
  it "should check format when adding coders" do
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require_relative '../helpers'
@@ -1,7 +1,8 @@
1
- #!/usr/bin/env rspec
1
+ # -*- rspec -*-
2
2
  # encoding: utf-8
3
3
 
4
4
  require 'pg'
5
+ require 'time'
5
6
 
6
7
 
7
8
  describe "PG::Type derivations" do
@@ -11,10 +12,14 @@ describe "PG::Type derivations" do
11
12
  let!(:textdec_boolean) { PG::TextDecoder::Boolean.new }
12
13
  let!(:textenc_float) { PG::TextEncoder::Float.new }
13
14
  let!(:textdec_float) { PG::TextDecoder::Float.new }
15
+ let!(:textenc_numeric) { PG::TextEncoder::Numeric.new }
14
16
  let!(:textenc_string) { PG::TextEncoder::String.new }
15
17
  let!(:textdec_string) { PG::TextDecoder::String.new }
16
18
  let!(:textenc_timestamp) { PG::TextEncoder::TimestampWithoutTimeZone.new }
17
19
  let!(:textdec_timestamp) { PG::TextDecoder::TimestampWithoutTimeZone.new }
20
+ let!(:textenc_timestamputc) { PG::TextEncoder::TimestampUtc.new }
21
+ let!(:textdec_timestamputc) { PG::TextDecoder::TimestampUtc.new }
22
+ let!(:textdec_timestampul) { PG::TextDecoder::TimestampUtcToLocal.new }
18
23
  let!(:textenc_timestamptz) { PG::TextEncoder::TimestampWithTimeZone.new }
19
24
  let!(:textdec_timestamptz) { PG::TextDecoder::TimestampWithTimeZone.new }
20
25
  let!(:textenc_bytea) { PG::TextEncoder::Bytea.new }
@@ -39,6 +44,14 @@ describe "PG::Type derivations" do
39
44
  end.new
40
45
  end
41
46
 
47
+ let!(:intenc_incrementer_with_encoding) do
48
+ Class.new(PG::SimpleEncoder) do
49
+ def encode(value, encoding)
50
+ r = (value.to_i + 1).to_s + " #{encoding}"
51
+ r.encode!(encoding)
52
+ end
53
+ end.new
54
+ end
42
55
  let!(:intenc_incrementer_with_int_result) do
43
56
  Class.new(PG::SimpleEncoder) do
44
57
  def encode(value)
@@ -67,7 +80,7 @@ describe "PG::Type derivations" do
67
80
  expect( intdec_incrementer.decode("3") ).to eq( 4 )
68
81
  end
69
82
 
70
- it "should decode integers of different lengths form text format" do
83
+ it "should decode integers of different lengths from text format" do
71
84
  30.times do |zeros|
72
85
  expect( textdec_int.decode("1" + "0"*zeros) ).to eq( 10 ** zeros )
73
86
  expect( textdec_int.decode(zeros==0 ? "0" : "9"*zeros) ).to eq( 10 ** zeros - 1 )
@@ -88,21 +101,41 @@ describe "PG::Type derivations" do
88
101
  end
89
102
 
90
103
  context 'timestamps' do
91
- it 'decodes timestamps without timezone' do
92
- expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456') ).
93
- to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, 59.123456) )
104
+ it 'decodes timestamps without timezone as local time' do
105
+ expect( textdec_timestamp.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
106
+ to eq( Time.new(2016,1,2, 23,23,59.123456).iso8601(5) )
107
+ expect( textdec_timestamp.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
108
+ to eq( Time.new(2016,8,2, 23,23,59.123456).iso8601(5) )
109
+ end
110
+ it 'decodes timestamps with UTC time and returns UTC timezone' do
111
+ expect( textdec_timestamputc.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
112
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).iso8601(5) )
113
+ expect( textdec_timestamputc.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
114
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).iso8601(5) )
115
+ end
116
+ it 'decodes timestamps with UTC time and returns local timezone' do
117
+ expect( textdec_timestampul.decode('2016-01-02 23:23:59.123456').iso8601(5) ).
118
+ to eq( Time.utc(2016,1,2, 23,23,59.123456).getlocal.iso8601(5) )
119
+ expect( textdec_timestampul.decode('2016-08-02 23:23:59.123456').iso8601(5) ).
120
+ to eq( Time.utc(2016,8,2, 23,23,59.123456).getlocal.iso8601(5) )
94
121
  end
95
122
  it 'decodes timestamps with hour timezone' do
96
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04') ).
97
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:00") )
98
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10') ).
99
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:00") )
123
+ expect( textdec_timestamptz.decode('2016-01-02 23:23:59.123456-04').iso8601(5) ).
124
+ to eq( Time.new(2016,1,2, 23,23,59.123456, "-04:00").iso8601(5) )
125
+ expect( textdec_timestamptz.decode('2016-08-02 23:23:59.123456+10').iso8601(5) ).
126
+ to eq( Time.new(2016,8,2, 23,23,59.123456, "+10:00").iso8601(5) )
127
+ expect( textdec_timestamptz.decode('1913-12-31 23:58:59.1231-03').iso8601(5) ).
128
+ to eq( Time.new(1913, 12, 31, 23, 58, 59.1231, "-03:00").iso8601(5) )
129
+ expect( textdec_timestamptz.decode('4714-11-24 23:58:59.1231-03 BC').iso8601(5) ).
130
+ to eq( Time.new(-4713, 11, 24, 23, 58, 59.1231, "-03:00").iso8601(5) )
131
+ expect( textdec_timestamptz.decode('294276-12-31 23:58:59.1231+03').iso8601(5) ).
132
+ to eq( Time.new(294276, 12, 31, 23, 58, 59.1231, "+03:00").iso8601(5) )
100
133
  end
101
134
  it 'decodes timestamps with hour:minute timezone' do
102
135
  expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-04:15') ).
103
136
  to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:15") )
104
- expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511-0430') ).
105
- to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "-04:30") )
137
+ expect( textdec_timestamptz.decode('2015-07-26 17:26:42.691511-04:30') ).
138
+ to be_within(0.000001).of( Time.new(2015,07,26, 17, 26, 42.691511, "-04:30") )
106
139
  expect( textdec_timestamptz.decode('2015-01-26 17:26:42.691511+10:45') ).
107
140
  to be_within(0.000001).of( Time.new(2015,01,26, 17, 26, 42.691511, "+10:45") )
108
141
  end
@@ -113,6 +146,81 @@ describe "PG::Type derivations" do
113
146
  expect( textdec_timestamptz.decode('1916-01-01 00:00:00-00:25:21') ).
114
147
  to be_within(0.000001).of( Time.new(1916, 1, 1, 0, 0, 0, "-00:25:21") )
115
148
  end
149
+ it 'decodes timestamps with date before 1823' do
150
+ expect( textdec_timestamp.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
151
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456).iso8601(5) )
152
+ expect( textdec_timestamputc.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
153
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).iso8601(5) )
154
+ expect( textdec_timestampul.decode('1822-01-02 23:23:59.123456').iso8601(5) ).
155
+ to eq( Time.utc(1822,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
156
+ expect( textdec_timestamptz.decode('1822-01-02 23:23:59.123456+04').iso8601(5) ).
157
+ to eq( Time.new(1822,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
158
+ end
159
+ it 'decodes timestamps with date after 2116' do
160
+ expect( textdec_timestamp.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
161
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456).iso8601(5) )
162
+ expect( textdec_timestamputc.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
163
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).iso8601(5) )
164
+ expect( textdec_timestampul.decode('2117-01-02 23:23:59.123456').iso8601(5) ).
165
+ to eq( Time.utc(2117,01,02, 23, 23, 59.123456).getlocal.iso8601(5) )
166
+ expect( textdec_timestamptz.decode('2117-01-02 23:23:59.123456+04').iso8601(5) ).
167
+ to eq( Time.new(2117,01,02, 23, 23, 59.123456, "+04:00").iso8601(5) )
168
+ end
169
+ it 'decodes timestamps with variable number of digits for the useconds part' do
170
+ sec = "59.12345678912345"
171
+ (4..sec.length).each do |i|
172
+ expect( textdec_timestamp.decode("2016-01-02 23:23:#{sec[0,i]}") ).
173
+ to be_within(0.000001).of( Time.new(2016,01,02, 23, 23, sec[0,i].to_f) )
174
+ end
175
+ end
176
+ it 'decodes timestamps with leap-second' do
177
+ expect( textdec_timestamp.decode('1998-12-31 23:59:60.1234') ).
178
+ to be_within(0.000001).of( Time.new(1998,12,31, 23, 59, 60.1234) )
179
+ end
180
+
181
+ def textdec_timestamptz_decode_should_fail(str)
182
+ expect(textdec_timestamptz.decode(str)).to eq(str)
183
+ end
184
+
185
+ it 'fails when the timestamp is an empty string' do
186
+ textdec_timestamptz_decode_should_fail('')
187
+ end
188
+ it 'fails when the timestamp contains values with less digits than expected' do
189
+ textdec_timestamptz_decode_should_fail('2016-0-02 23:23:59.123456+00:25:21')
190
+ textdec_timestamptz_decode_should_fail('2016-01-0 23:23:59.123456+00:25:21')
191
+ textdec_timestamptz_decode_should_fail('2016-01-02 2:23:59.123456+00:25:21')
192
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:2:59.123456+00:25:21')
193
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:5.123456+00:25:21')
194
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.+00:25:21')
195
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+0:25:21')
196
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:2:21')
197
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:2')
198
+ end
199
+ it 'fails when the timestamp contains values with more digits than expected' do
200
+ textdec_timestamptz_decode_should_fail('2016-011-02 23:23:59.123456+00:25:21')
201
+ textdec_timestamptz_decode_should_fail('2016-01-022 23:23:59.123456+00:25:21')
202
+ textdec_timestamptz_decode_should_fail('2016-01-02 233:23:59.123456+00:25:21')
203
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:233:59.123456+00:25:21')
204
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:599.123456+00:25:21')
205
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+000:25:21')
206
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:255:21')
207
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456+00:25:211')
208
+ end
209
+ it 'fails when the timestamp contains values with invalid characters' do
210
+ str = '2013-01-02 23:23:59.123456+00:25:21'
211
+ str.length.times do |i|
212
+ textdec_timestamptz_decode_should_fail(str[0,i] + "x" + str[i+1..-1])
213
+ end
214
+ end
215
+ it 'fails when the timestamp contains leading characters' do
216
+ textdec_timestamptz_decode_should_fail(' 2016-01-02 23:23:59.123456')
217
+ end
218
+ it 'fails when the timestamp contains trailing characters' do
219
+ textdec_timestamptz_decode_should_fail('2016-01-02 23:23:59.123456 ')
220
+ end
221
+ it 'fails when the timestamp contains non ASCII character' do
222
+ textdec_timestamptz_decode_should_fail('2016-01ª02 23:23:59.123456')
223
+ end
116
224
  end
117
225
 
118
226
  context 'identifier quotation' do
@@ -127,6 +235,13 @@ describe "PG::Type derivations" do
127
235
  expect( quoted_type.decode(%[a.b]) ).to eq( ['a','b'] )
128
236
  expect( quoted_type.decode(%[a]) ).to eq( ['a'] )
129
237
  end
238
+
239
+ it 'should split identifier string with correct character encoding' do
240
+ quoted_type = PG::TextDecoder::Identifier.new
241
+ v = quoted_type.decode(%[Héllo].encode("iso-8859-1")).first
242
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
243
+ expect( v ).to eq( %[Héllo].encode(Encoding::ISO_8859_1) )
244
+ end
130
245
  end
131
246
 
132
247
  it "should raise when decode method is called with wrong args" do
@@ -140,6 +255,11 @@ describe "PG::Type derivations" do
140
255
  expect( textdec_string.decode( nil )).to be_nil
141
256
  expect( textdec_int.decode( nil )).to be_nil
142
257
  end
258
+
259
+ it "should be defined on an encoder but not on a decoder instance" do
260
+ expect( textdec_int.respond_to?(:decode) ).to be_truthy
261
+ expect( textenc_int.respond_to?(:decode) ).to be_falsey
262
+ end
143
263
  end
144
264
 
145
265
  describe '#encode' do
@@ -190,16 +310,98 @@ describe "PG::Type derivations" do
190
310
  end
191
311
  end
192
312
 
313
+ it "should encode floats" do
314
+ expect( textenc_float.encode(0) ).to eq( "0.0" )
315
+ expect( textenc_float.encode(-1) ).to eq( "-1.0" )
316
+ expect( textenc_float.encode(-1.234567890123456789) ).to eq( "-1.234567890123457" )
317
+ expect( textenc_float.encode(9) ).to eq( "9.0" )
318
+ expect( textenc_float.encode(10) ).to eq( "10.0" )
319
+ expect( textenc_float.encode(-99) ).to eq( "-99.0" )
320
+ expect( textenc_float.encode(-100) ).to eq( "-100.0" )
321
+ expect( textenc_float.encode(999) ).to eq( "999.0" )
322
+ expect( textenc_float.encode(-1000) ).to eq( "-1000.0" )
323
+ expect( textenc_float.encode(1234.567890123456789) ).to eq( "1234.567890123457" )
324
+ expect( textenc_float.encode(-9999) ).to eq( "-9999.0" )
325
+ expect( textenc_float.encode(10000) ).to eq( "10000.0" )
326
+ expect( textenc_float.encode(99999) ).to eq( "99999.0" )
327
+ expect( textenc_float.encode(-100000) ).to eq( "-100000.0" )
328
+ expect( textenc_float.encode(-999999) ).to eq( "-999999.0" )
329
+ expect( textenc_float.encode(1000000) ).to eq( "1000000.0" )
330
+ expect( textenc_float.encode(9999999) ).to eq( "9999999.0" )
331
+ expect( textenc_float.encode(-100000000000000) ).to eq( "-100000000000000.0" )
332
+ expect( textenc_float.encode(123456789012345) ).to eq( "123456789012345.0" )
333
+ expect( textenc_float.encode(-999999999999999) ).to eq( "-999999999999999.0" )
334
+ expect( textenc_float.encode(1000000000000000) ).to eq( "1e15" )
335
+ expect( textenc_float.encode(-1234567890123456) ).to eq( "-1.234567890123456e15" )
336
+ expect( textenc_float.encode(9999999999999999) ).to eq( "1e16" )
337
+
338
+ expect( textenc_float.encode(-0.0) ).to eq( "0.0" )
339
+ expect( textenc_float.encode(0.1) ).to eq( "0.1" )
340
+ expect( textenc_float.encode(0.1234567890123456789) ).to eq( "0.1234567890123457" )
341
+ expect( textenc_float.encode(-0.9) ).to eq( "-0.9" )
342
+ expect( textenc_float.encode(-0.01234567890123456789) ).to eq( "-0.01234567890123457" )
343
+ expect( textenc_float.encode(0.09) ).to eq( "0.09" )
344
+ expect( textenc_float.encode(0.001234567890123456789) ).to eq( "0.001234567890123457" )
345
+ expect( textenc_float.encode(-0.009) ).to eq( "-0.009" )
346
+ expect( textenc_float.encode(-0.0001234567890123456789) ).to eq( "-0.0001234567890123457" )
347
+ expect( textenc_float.encode(0.0009) ).to eq( "0.0009" )
348
+ expect( textenc_float.encode(0.00001) ).to eq( "1e-5" )
349
+ expect( textenc_float.encode(0.00001234567890123456789) ).to eq( "1.234567890123457e-5" )
350
+ expect( textenc_float.encode(-0.00009) ).to eq( "-9e-5" )
351
+ expect( textenc_float.encode(-0.11) ).to eq( "-0.11" )
352
+ expect( textenc_float.encode(10.11) ).to eq( "10.11" )
353
+ expect( textenc_float.encode(-1.234567890123456789E-280) ).to eq( "-1.234567890123457e-280" )
354
+ expect( textenc_float.encode(-1.234567890123456789E280) ).to eq( "-1.234567890123457e280" )
355
+ expect( textenc_float.encode(9876543210987654321E280) ).to eq( "9.87654321098765e298" )
356
+ expect( textenc_float.encode(9876543210987654321E-400) ).to eq( "0.0" )
357
+ expect( textenc_float.encode(9876543210987654321E400) ).to eq( "Infinity" )
358
+ end
359
+
193
360
  it "should encode special floats equally to Float#to_s" do
194
361
  expect( textenc_float.encode(Float::INFINITY) ).to eq( Float::INFINITY.to_s )
195
362
  expect( textenc_float.encode(-Float::INFINITY) ).to eq( (-Float::INFINITY).to_s )
196
363
  expect( textenc_float.encode(-Float::NAN) ).to eq( Float::NAN.to_s )
197
364
  end
198
365
 
366
+ it "should encode various inputs to numeric format" do
367
+ expect( textenc_numeric.encode(0) ).to eq( "0" )
368
+ expect( textenc_numeric.encode(1) ).to eq( "1" )
369
+ expect( textenc_numeric.encode(-12345678901234567890123) ).to eq( "-12345678901234567890123" )
370
+ expect( textenc_numeric.encode(0.0) ).to eq( "0.0" )
371
+ expect( textenc_numeric.encode(1.0) ).to eq( "1.0" )
372
+ expect( textenc_numeric.encode(-1.23456789012e45) ).to eq( "-1.23456789012e45" )
373
+ expect( textenc_numeric.encode(Float::NAN) ).to eq( Float::NAN.to_s )
374
+ expect( textenc_numeric.encode(BigDecimal(0)) ).to eq( "0.0" )
375
+ expect( textenc_numeric.encode(BigDecimal(1)) ).to eq( "1.0" )
376
+ expect( textenc_numeric.encode(BigDecimal("-12345678901234567890.1234567")) ).to eq( "-12345678901234567890.1234567" )
377
+ expect( textenc_numeric.encode(" 123 ") ).to eq( " 123 " )
378
+ end
379
+
199
380
  it "encodes binary string to bytea" do
200
381
  expect( textenc_bytea.encode("\x00\x01\x02\x03\xef".b) ).to eq( "\\x00010203ef" )
201
382
  end
202
383
 
384
+ context 'timestamps' do
385
+ it 'encodes timestamps without timezone' do
386
+ expect( textenc_timestamp.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
387
+ to match( /^2016-01-02 23:23:59.12345\d+$/ )
388
+ expect( textenc_timestamp.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
389
+ to match( /^2016-08-02 23:23:59.12345\d+$/ )
390
+ end
391
+ it 'encodes timestamps with UTC timezone' do
392
+ expect( textenc_timestamputc.encode(Time.new(2016,1,2, 23, 23, 59.123456, 3*60*60)) ).
393
+ to match( /^2016-01-02 20:23:59.12345\d+$/ )
394
+ expect( textenc_timestamputc.encode(Time.new(2016,8,2, 23, 23, 59.123456, 3*60*60)) ).
395
+ to match( /^2016-08-02 20:23:59.12345\d+$/ )
396
+ end
397
+ it 'encodes timestamps with hour timezone' do
398
+ expect( textenc_timestamptz.encode(Time.new(2016,1,02, 23, 23, 59.123456, -4*60*60)) ).
399
+ to match( /^2016-01-02 23:23:59.12345\d+ \-04:00$/ )
400
+ expect( textenc_timestamptz.encode(Time.new(2016,8,02, 23, 23, 59.123456, 10*60*60)) ).
401
+ to match( /^2016-08-02 23:23:59.12345\d+ \+10:00$/ )
402
+ end
403
+ end
404
+
203
405
  context 'identifier quotation' do
204
406
  it 'should quote and escape identifier' do
205
407
  quoted_type = PG::TextEncoder::Identifier.new
@@ -209,6 +411,13 @@ describe "PG::Type derivations" do
209
411
  expect( quoted_type.encode( nil ) ).to be_nil
210
412
  end
211
413
 
414
+ it 'should quote identifiers with correct character encoding' do
415
+ quoted_type = PG::TextEncoder::Identifier.new
416
+ v = quoted_type.encode(['Héllo'], "iso-8859-1")
417
+ expect( v ).to eq( %["Héllo"].encode(Encoding::ISO_8859_1) )
418
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
419
+ end
420
+
212
421
  it "will raise a TypeError for invalid arguments to quote_ident" do
213
422
  quoted_type = PG::TextEncoder::Identifier.new
214
423
  expect{ quoted_type.encode( [nil] ) }.to raise_error(TypeError)
@@ -220,6 +429,12 @@ describe "PG::Type derivations" do
220
429
  expect( intenc_incrementer.encode(3) ).to eq( "4 " )
221
430
  end
222
431
 
432
+ it "should encode with ruby encoder and given character encoding" do
433
+ r = intenc_incrementer_with_encoding.encode(3, Encoding::CP850)
434
+ expect( r ).to eq( "4 CP850" )
435
+ expect( r.encoding ).to eq( Encoding::CP850 )
436
+ end
437
+
223
438
  it "should return when ruby encoder returns non string values" do
224
439
  expect( intenc_incrementer_with_int_result.encode(3) ).to eq( 4 )
225
440
  end
@@ -228,6 +443,11 @@ describe "PG::Type derivations" do
228
443
  expect( textenc_string.encode( nil )).to be_nil
229
444
  expect( textenc_int.encode( nil )).to be_nil
230
445
  end
446
+
447
+ it "should be defined on a decoder but not on an encoder instance" do
448
+ expect( textenc_int.respond_to?(:encode) ).to be_truthy
449
+ expect( textdec_int.respond_to?(:encode) ).to be_falsey
450
+ end
231
451
  end
232
452
 
233
453
  it "should be possible to marshal encoders" do
@@ -244,7 +464,7 @@ describe "PG::Type derivations" do
244
464
 
245
465
  it "should respond to to_h" do
246
466
  expect( textenc_int.to_h ).to eq( {
247
- name: 'Integer', oid: 23, format: 0
467
+ name: 'Integer', oid: 23, format: 0, flags: 0
248
468
  } )
249
469
  end
250
470
 
@@ -275,6 +495,7 @@ describe "PG::Type derivations" do
275
495
  describe "Array types" do
276
496
  let!(:textenc_string_array) { PG::TextEncoder::Array.new elements_type: textenc_string }
277
497
  let!(:textdec_string_array) { PG::TextDecoder::Array.new elements_type: textdec_string }
498
+ let!(:textdec_string_array_raise) { PG::TextDecoder::Array.new elements_type: textdec_string, flags: PG::Coder:: FORMAT_ERROR_TO_RAISE }
278
499
  let!(:textenc_int_array) { PG::TextEncoder::Array.new elements_type: textenc_int, needs_quotation: false }
279
500
  let!(:textdec_int_array) { PG::TextDecoder::Array.new elements_type: textdec_int, needs_quotation: false }
280
501
  let!(:textenc_float_array) { PG::TextEncoder::Array.new elements_type: textenc_float, needs_quotation: false }
@@ -340,6 +561,57 @@ describe "PG::Type derivations" do
340
561
  it 'respects a different delimiter' do
341
562
  expect( textdec_string_array_with_delimiter.decode(%[{1;2;3}]) ).to eq( ['1','2','3'] )
342
563
  end
564
+
565
+ it 'ignores array dimensions' do
566
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3}]) ).to eq( ['1','2','3'] )
567
+ expect( textdec_string_array.decode(%[[]={1,2,3}]) ).to eq( ['1','2','3'] )
568
+ expect( textdec_string_array.decode(%[ [-1:+2]= {4,3,2,1}]) ).to eq( ['4','3','2','1'] )
569
+ end
570
+
571
+ it 'ignores spaces after array' do
572
+ expect( textdec_string_array.decode(%[[2:4]={1,2,3} ]) ).to eq( ['1','2','3'] )
573
+ expect( textdec_string_array.decode(%[{1,2,3} ]) ).to eq( ['1','2','3'] )
574
+ end
575
+
576
+ describe "with malformed syntax are deprecated" do
577
+ it 'accepts broken array dimensions' do
578
+ expect( textdec_string_array.decode(%([2:4={1,2,3})) ).to eq([['1','2','3']])
579
+ expect( textdec_string_array.decode(%(2:4]={1,2,3})) ).to eq([['1','2','3']])
580
+ expect( textdec_string_array.decode(%(={1,2,3})) ).to eq([['1','2','3']])
581
+ expect( textdec_string_array.decode(%([x]={1,2,3})) ).to eq([['1','2','3']])
582
+ expect( textdec_string_array.decode(%([]{1,2,3})) ).to eq([['1','2','3']])
583
+ expect( textdec_string_array.decode(%(1,2,3)) ).to eq(['','2'])
584
+ end
585
+
586
+ it 'accepts malformed arrays' do
587
+ expect( textdec_string_array.decode(%({1,2,3)) ).to eq(['1','2'])
588
+ expect( textdec_string_array.decode(%({1,2,3}})) ).to eq(['1','2','3'])
589
+ expect( textdec_string_array.decode(%({1,2,3}x)) ).to eq(['1','2','3'])
590
+ expect( textdec_string_array.decode(%({{1,2},{2,3})) ).to eq([['1','2'],['2','3']])
591
+ expect( textdec_string_array.decode(%({{1,2},{2,3}}x)) ).to eq([['1','2'],['2','3']])
592
+ expect( textdec_string_array.decode(%({[1,2},{2,3}}})) ).to eq(['[1','2'])
593
+ end
594
+ end
595
+
596
+ describe "with malformed syntax are raised with pg-2.0+" do
597
+ it 'complains about broken array dimensions' do
598
+ expect{ textdec_string_array_raise.decode(%([2:4={1,2,3})) }.to raise_error(TypeError)
599
+ expect{ textdec_string_array_raise.decode(%(2:4]={1,2,3})) }.to raise_error(TypeError)
600
+ expect{ textdec_string_array_raise.decode(%(={1,2,3})) }.to raise_error(TypeError)
601
+ expect{ textdec_string_array_raise.decode(%([x]={1,2,3})) }.to raise_error(TypeError)
602
+ expect{ textdec_string_array_raise.decode(%([]{1,2,3})) }.to raise_error(TypeError)
603
+ expect{ textdec_string_array_raise.decode(%(1,2,3)) }.to raise_error(TypeError)
604
+ end
605
+
606
+ it 'complains about malformed array' do
607
+ expect{ textdec_string_array_raise.decode(%({1,2,3)) }.to raise_error(TypeError)
608
+ expect{ textdec_string_array_raise.decode(%({1,2,3}})) }.to raise_error(TypeError)
609
+ expect{ textdec_string_array_raise.decode(%({1,2,3}x)) }.to raise_error(TypeError)
610
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3})) }.to raise_error(TypeError)
611
+ expect{ textdec_string_array_raise.decode(%({{1,2},{2,3}}x)) }.to raise_error(TypeError)
612
+ expect{ textdec_string_array_raise.decode(%({[1,2},{2,3}}})) }.to raise_error(TypeError)
613
+ end
614
+ end
343
615
  end
344
616
 
345
617
  context 'bytea' do
@@ -424,7 +696,7 @@ describe "PG::Type derivations" do
424
696
  expect( textenc_int_array.encode(['1',['2'],'3']) ).to eq( %[{1,{2},3}] )
425
697
  end
426
698
  it 'encodes an array of float8 with sub arrays' do
427
- expect( textenc_float_array.encode([1000.11,[-0.00221,[3.31,-441]],[nil,6.61],-7.71]) ).to match(Regexp.new(%[^{1.0001*E+*03,{-2.2*E-*03,{3.3*E+*00,-4.4*E+*02}},{NULL,6.6*E+*00},-7.7*E+*00}$].gsub(/([\.\+\{\}\,])/, "\\\\\\1").gsub(/\*/, "\\d*")))
699
+ expect( textenc_float_array.encode([1000.11,[-0.00000221,[3.31,-441]],[nil,6.61],-7.71]) ).to match(Regexp.new(%[^{1000.1*,{-2.2*e-*6,{3.3*,-441.0}},{NULL,6.6*},-7.7*}$].gsub(/([\.\+\{\}\,])/, "\\\\\\1").gsub(/\*/, "\\d*")))
428
700
  end
429
701
  end
430
702
  context 'two dimensional arrays' do
@@ -447,9 +719,18 @@ describe "PG::Type derivations" do
447
719
  end
448
720
 
449
721
  context 'array of types with encoder in ruby space' do
450
- it 'encodes with quotation' do
722
+ it 'encodes with quotation and default character encoding' do
723
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
724
+ r = array_type.encode([3,4])
725
+ expect( r ).to eq( %[{"4 ","5 "}] )
726
+ expect( r.encoding ).to eq( Encoding::ASCII_8BIT )
727
+ end
728
+
729
+ it 'encodes with quotation and given character encoding' do
451
730
  array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer, needs_quotation: true
452
- expect( array_type.encode([3,4]) ).to eq( %[{"4 ","5 "}] )
731
+ r = array_type.encode([3,4], Encoding::CP850)
732
+ expect( r ).to eq( %[{"4 ","5 "}] )
733
+ expect( r.encoding ).to eq( Encoding::CP850 )
453
734
  end
454
735
 
455
736
  it 'encodes without quotation' do
@@ -457,6 +738,20 @@ describe "PG::Type derivations" do
457
738
  expect( array_type.encode([3,4]) ).to eq( %[{4 ,5 }] )
458
739
  end
459
740
 
741
+ it 'encodes with default character encoding' do
742
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_encoding
743
+ r = array_type.encode([3,4])
744
+ expect( r ).to eq( %[{"4 ASCII-8BIT","5 ASCII-8BIT"}] )
745
+ expect( r.encoding ).to eq( Encoding::ASCII_8BIT )
746
+ end
747
+
748
+ it 'encodes with given character encoding' do
749
+ array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_encoding
750
+ r = array_type.encode([3,4], Encoding::CP850)
751
+ expect( r ).to eq( %[{"4 CP850","5 CP850"}] )
752
+ expect( r.encoding ).to eq( Encoding::CP850 )
753
+ end
754
+
460
755
  it "should raise when ruby encoder returns non string values" do
461
756
  array_type = PG::TextEncoder::Array.new elements_type: intenc_incrementer_with_int_result, needs_quotation: false
462
757
  expect{ array_type.encode([3,4]) }.to raise_error(TypeError)
@@ -473,6 +768,13 @@ describe "PG::Type derivations" do
473
768
  quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
474
769
  expect( quoted_type.encode(["'A\",","\\B'"]) ).to eq( %['{"''A\\",","\\\\B''"}'] )
475
770
  end
771
+
772
+ it 'should quote literals with correct character encoding' do
773
+ quoted_type = PG::TextEncoder::QuotedLiteral.new elements_type: textenc_string_array
774
+ v = quoted_type.encode(["Héllo"], "iso-8859-1")
775
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
776
+ expect( v ).to eq( %['{Héllo}'].encode(Encoding::ISO_8859_1) )
777
+ end
476
778
  end
477
779
  end
478
780
 
@@ -482,15 +784,15 @@ describe "PG::Type derivations" do
482
784
  expect( lt.to_h ).to eq( textenc_int_array.to_h )
483
785
  end
484
786
 
485
- it "should be possible to marshal encoders" do
486
- mt = Marshal.dump(textdec_int_array)
787
+ it "should be possible to marshal decoders" do
788
+ mt = Marshal.dump(textdec_string_array_raise)
487
789
  lt = Marshal.load(mt)
488
- expect( lt.to_h ).to eq( textdec_int_array.to_h )
790
+ expect( lt.to_h ).to eq( textdec_string_array_raise.to_h )
489
791
  end
490
792
 
491
793
  it "should respond to to_h" do
492
794
  expect( textenc_int_array.to_h ).to eq( {
493
- name: nil, oid: 0, format: 0,
795
+ name: nil, oid: 0, format: 0, flags: 0,
494
796
  elements_type: textenc_int, needs_quotation: false, delimiter: ','
495
797
  } )
496
798
  end
@@ -522,9 +824,19 @@ describe "PG::Type derivations" do
522
824
  expect( e.encode("(\xFBm") ).to eq("KPtt")
523
825
  end
524
826
 
827
+ it 'should encode Strings as base64 with correct character encoding' do
828
+ e = PG::TextEncoder::ToBase64.new
829
+ v = e.encode("Héllo".encode("utf-16le"), "iso-8859-1")
830
+ expect( v ).to eq("SOlsbG8=")
831
+ expect( v.encoding ).to eq(Encoding::ISO_8859_1)
832
+ end
833
+
525
834
  it "should encode Strings as base64 in BinaryDecoder" do
526
835
  e = PG::BinaryDecoder::ToBase64.new
527
836
  expect( e.decode("x") ).to eq("eA==")
837
+ v = e.decode("Héllo".encode("utf-16le"))
838
+ expect( v ).to eq("SADpAGwAbABvAA==")
839
+ expect( v.encoding ).to eq(Encoding::ASCII_8BIT)
528
840
  end
529
841
 
530
842
  it "should encode Integers as base64" do
@@ -611,6 +923,12 @@ describe "PG::Type derivations" do
611
923
  expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
612
924
  to eq("xyz\t123\t2456\t34567\t456789\t5678901\t[1, 2, 3]\t12.1\tabcdefg\t\\N\n")
613
925
  end
926
+
927
+ it 'should output a string with correct character encoding' do
928
+ v = encoder.encode(["Héllo"], "iso-8859-1")
929
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
930
+ expect( v ).to eq( "Héllo\n".encode(Encoding::ISO_8859_1) )
931
+ end
614
932
  end
615
933
 
616
934
  context "with TypeMapByClass" do
@@ -672,9 +990,15 @@ describe "PG::Type derivations" do
672
990
  end
673
991
 
674
992
  describe '#decode' do
675
- it "should decode different types of Ruby objects" do
993
+ it "should decode COPY text format to array of strings" do
676
994
  expect( decoder.decode("123\t \0#\t#\n#\r#\\ \t234\t#\x01#\002\n".gsub("#", "\\"))).to eq( ["123", " \0\t\n\r\\ ", "234", "\x01\x02"] )
677
995
  end
996
+
997
+ it 'should respect input character encoding' do
998
+ v = decoder.decode("Héllo\n".encode("iso-8859-1")).first
999
+ expect( v.encoding ).to eq(Encoding::ISO_8859_1)
1000
+ expect( v ).to eq("Héllo".encode("iso-8859-1"))
1001
+ end
678
1002
  end
679
1003
  end
680
1004
 
@@ -694,4 +1018,106 @@ describe "PG::Type derivations" do
694
1018
  end
695
1019
  end
696
1020
  end
1021
+
1022
+ describe PG::RecordCoder do
1023
+ describe PG::TextEncoder::Record do
1024
+ context "with default typemap" do
1025
+ let!(:encoder) do
1026
+ PG::TextEncoder::Record.new
1027
+ end
1028
+
1029
+ it "should encode different types of Ruby objects" do
1030
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
1031
+ to eq('("xyz","123","2456","34567","456789","5678901","[1, 2, 3]","12.1","abcdefg",)')
1032
+ end
1033
+
1034
+ it 'should output a string with correct character encoding' do
1035
+ v = encoder.encode(["Héllo"], "iso-8859-1")
1036
+ expect( v.encoding ).to eq( Encoding::ISO_8859_1 )
1037
+ expect( v ).to eq( '("Héllo")'.encode(Encoding::ISO_8859_1) )
1038
+ end
1039
+ end
1040
+
1041
+ context "with TypeMapByClass" do
1042
+ let!(:tm) do
1043
+ tm = PG::TypeMapByClass.new
1044
+ tm[Integer] = textenc_int
1045
+ tm[Float] = intenc_incrementer
1046
+ tm[Array] = PG::TextEncoder::Array.new elements_type: textenc_string
1047
+ tm
1048
+ end
1049
+ let!(:encoder) do
1050
+ PG::TextEncoder::Record.new type_map: tm
1051
+ end
1052
+
1053
+ it "should have reasonable default values" do
1054
+ expect( encoder.name ).to be_nil
1055
+ end
1056
+
1057
+ it "copies all attributes with #dup" do
1058
+ encoder.name = "test"
1059
+ encoder.type_map = PG::TypeMapByColumn.new []
1060
+ encoder2 = encoder.dup
1061
+ expect( encoder.object_id ).to_not eq( encoder2.object_id )
1062
+ expect( encoder2.name ).to eq( "test" )
1063
+ expect( encoder2.type_map ).to be_a_kind_of( PG::TypeMapByColumn )
1064
+ end
1065
+
1066
+ describe '#encode' do
1067
+ it "should encode different types of Ruby objects" do
1068
+ expect( encoder.encode([]) ).to eq("()")
1069
+ expect( encoder.encode(["a"]) ).to eq('("a")')
1070
+ expect( encoder.encode([:xyz, 123, 2456, 34567, 456789, 5678901, [1,2,3], 12.1, "abcdefg", nil]) ).
1071
+ to eq('("xyz","123","2456","34567","456789","5678901","{1,2,3}","13 ","abcdefg",)')
1072
+ end
1073
+
1074
+ it "should escape special characters" do
1075
+ expect( encoder.encode([" \"\t\n\\\r"]) ).to eq("(\" \"\"\t\n##\r\")".gsub("#", "\\"))
1076
+ end
1077
+ end
1078
+ end
1079
+ end
1080
+
1081
+ describe PG::TextDecoder::Record do
1082
+ context "with default typemap" do
1083
+ let!(:decoder) do
1084
+ PG::TextDecoder::Record.new
1085
+ end
1086
+
1087
+ describe '#decode' do
1088
+ it "should decode composite text format to array of strings" do
1089
+ expect( decoder.decode('("fuzzy dice",,"",42,)') ).to eq( ["fuzzy dice",nil, "", "42", nil] )
1090
+ end
1091
+
1092
+ it 'should respect input character encoding' do
1093
+ v = decoder.decode("(Héllo)".encode("iso-8859-1")).first
1094
+ expect( v.encoding ).to eq(Encoding::ISO_8859_1)
1095
+ expect( v ).to eq("Héllo".encode("iso-8859-1"))
1096
+ end
1097
+
1098
+ it 'should raise an error on malformed input' do
1099
+ expect{ decoder.decode('') }.to raise_error(ArgumentError, /"" - Missing left parenthesis/)
1100
+ expect{ decoder.decode('(') }.to raise_error(ArgumentError, /"\(" - Unexpected end of input/)
1101
+ expect{ decoder.decode('(\\') }.to raise_error(ArgumentError, /"\(\\" - Unexpected end of input/)
1102
+ expect{ decoder.decode('()x') }.to raise_error(ArgumentError, /"\(\)x" - Junk after right parenthesis/)
1103
+ end
1104
+ end
1105
+ end
1106
+
1107
+ context "with TypeMapByColumn" do
1108
+ let!(:tm) do
1109
+ PG::TypeMapByColumn.new [textdec_int, textdec_string, intdec_incrementer, nil]
1110
+ end
1111
+ let!(:decoder) do
1112
+ PG::TextDecoder::Record.new type_map: tm
1113
+ end
1114
+
1115
+ describe '#decode' do
1116
+ it "should decode different types of Ruby objects" do
1117
+ expect( decoder.decode("(123,\" #,#\n#\r#\\ \",234,#\x01#\002)".gsub("#", "\\"))).to eq( [123, " ,\n\r\\ ", 235, "\x01\x02"] )
1118
+ end
1119
+ end
1120
+ end
1121
+ end
1122
+ end
697
1123
  end