pg 0.18.4 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
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