pg 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gemtest +0 -0
  5. data/BSDL +22 -0
  6. data/ChangeLog +6595 -0
  7. data/Contributors.rdoc +46 -0
  8. data/History.rdoc +492 -0
  9. data/LICENSE +56 -0
  10. data/Manifest.txt +72 -0
  11. data/POSTGRES +23 -0
  12. data/README-OS_X.rdoc +68 -0
  13. data/README-Windows.rdoc +56 -0
  14. data/README.ja.rdoc +14 -0
  15. data/README.rdoc +178 -0
  16. data/Rakefile +215 -0
  17. data/Rakefile.cross +298 -0
  18. data/ext/errorcodes.def +968 -0
  19. data/ext/errorcodes.rb +45 -0
  20. data/ext/errorcodes.txt +478 -0
  21. data/ext/extconf.rb +94 -0
  22. data/ext/gvl_wrappers.c +17 -0
  23. data/ext/gvl_wrappers.h +241 -0
  24. data/ext/pg.c +640 -0
  25. data/ext/pg.h +365 -0
  26. data/ext/pg_binary_decoder.c +229 -0
  27. data/ext/pg_binary_encoder.c +162 -0
  28. data/ext/pg_coder.c +549 -0
  29. data/ext/pg_connection.c +4252 -0
  30. data/ext/pg_copy_coder.c +596 -0
  31. data/ext/pg_errors.c +95 -0
  32. data/ext/pg_result.c +1501 -0
  33. data/ext/pg_text_decoder.c +981 -0
  34. data/ext/pg_text_encoder.c +682 -0
  35. data/ext/pg_tuple.c +541 -0
  36. data/ext/pg_type_map.c +166 -0
  37. data/ext/pg_type_map_all_strings.c +116 -0
  38. data/ext/pg_type_map_by_class.c +239 -0
  39. data/ext/pg_type_map_by_column.c +312 -0
  40. data/ext/pg_type_map_by_mri_type.c +284 -0
  41. data/ext/pg_type_map_by_oid.c +355 -0
  42. data/ext/pg_type_map_in_ruby.c +299 -0
  43. data/ext/util.c +149 -0
  44. data/ext/util.h +65 -0
  45. data/ext/vc/pg.sln +26 -0
  46. data/ext/vc/pg_18/pg.vcproj +216 -0
  47. data/ext/vc/pg_19/pg_19.vcproj +209 -0
  48. data/lib/pg.rb +74 -0
  49. data/lib/pg/basic_type_mapping.rb +459 -0
  50. data/lib/pg/binary_decoder.rb +22 -0
  51. data/lib/pg/coder.rb +83 -0
  52. data/lib/pg/connection.rb +291 -0
  53. data/lib/pg/constants.rb +11 -0
  54. data/lib/pg/exceptions.rb +11 -0
  55. data/lib/pg/result.rb +31 -0
  56. data/lib/pg/text_decoder.rb +47 -0
  57. data/lib/pg/text_encoder.rb +69 -0
  58. data/lib/pg/tuple.rb +30 -0
  59. data/lib/pg/type_map_by_column.rb +15 -0
  60. data/spec/data/expected_trace.out +26 -0
  61. data/spec/data/random_binary_data +0 -0
  62. data/spec/helpers.rb +380 -0
  63. data/spec/pg/basic_type_mapping_spec.rb +508 -0
  64. data/spec/pg/connection_spec.rb +1872 -0
  65. data/spec/pg/connection_sync_spec.rb +41 -0
  66. data/spec/pg/result_spec.rb +491 -0
  67. data/spec/pg/tuple_spec.rb +280 -0
  68. data/spec/pg/type_map_by_class_spec.rb +138 -0
  69. data/spec/pg/type_map_by_column_spec.rb +222 -0
  70. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  71. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  72. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  73. data/spec/pg/type_map_spec.rb +22 -0
  74. data/spec/pg/type_spec.rb +949 -0
  75. data/spec/pg_spec.rb +50 -0
  76. metadata +322 -0
  77. metadata.gz.sig +0 -0
@@ -0,0 +1,280 @@
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!(:result2x3cast) { @conn.exec( "SELECT * FROM (VALUES(1, TRUE, '3'), (2, FALSE, '4')) AS m (a, b, b)" ).map_types!(typemap) }
12
+ let!(:tuple0) { result2x2.tuple(0) }
13
+ let!(:tuple1) { result2x2.tuple(1) }
14
+ let!(:tuple2) { result2x3cast.tuple(0) }
15
+ let!(:tuple3) { str = Marshal.dump(result2x3cast.tuple(1)); Marshal.load(str) }
16
+ let!(:tuple_empty) { PG::Tuple.new }
17
+
18
+ describe "[]" do
19
+ it "returns nil for invalid keys" do
20
+ expect( tuple0["x"] ).to be_nil
21
+ expect( tuple0[0.5] ).to be_nil
22
+ expect( tuple0[2] ).to be_nil
23
+ expect( tuple0[-3] ).to be_nil
24
+ expect( tuple2[-4] ).to be_nil
25
+ expect{ tuple_empty[0] }.to raise_error(TypeError)
26
+ end
27
+
28
+ it "supports array like access" do
29
+ expect( tuple0[0] ).to eq( "1" )
30
+ expect( tuple0[1] ).to eq( "a" )
31
+ expect( tuple1[0] ).to eq( "2" )
32
+ expect( tuple1[1] ).to eq( "b" )
33
+ expect( tuple2[0] ).to eq( 1 )
34
+ expect( tuple2[1] ).to eq( true )
35
+ expect( tuple2[2] ).to eq( "3" )
36
+ expect( tuple3[0] ).to eq( 2 )
37
+ expect( tuple3[1] ).to eq( false )
38
+ expect( tuple3[2] ).to eq( "4" )
39
+ end
40
+
41
+ it "supports negative indices" do
42
+ expect( tuple0[-2] ).to eq( "1" )
43
+ expect( tuple0[-1] ).to eq( "a" )
44
+ expect( tuple2[-3] ).to eq( 1 )
45
+ expect( tuple2[-2] ).to eq( true )
46
+ expect( tuple2[-1] ).to eq( "3" )
47
+ end
48
+
49
+ it "supports hash like access" do
50
+ expect( tuple0["column1"] ).to eq( "1" )
51
+ expect( tuple0["column2"] ).to eq( "a" )
52
+ expect( tuple2["a"] ).to eq( 1 )
53
+ expect( tuple2["b"] ).to eq( "3" )
54
+ expect( tuple0["x"] ).to be_nil
55
+ end
56
+
57
+ it "casts lazy and caches result" do
58
+ a = []
59
+ deco = Class.new(PG::SimpleDecoder) do
60
+ define_method(:decode) do |*args|
61
+ a << args
62
+ args.last
63
+ end
64
+ end.new
65
+
66
+ result2x2.map_types!(PG::TypeMapByColumn.new([deco, deco]))
67
+ t = result2x2.tuple(1)
68
+
69
+ # cast and cache at first call to [0]
70
+ a.clear
71
+ expect( t[0] ).to eq( 0 )
72
+ expect( a ).to eq([["2", 1, 0]])
73
+
74
+ # use cache at second call to [0]
75
+ a.clear
76
+ expect( t[0] ).to eq( 0 )
77
+ expect( a ).to eq([])
78
+
79
+ # cast and cache at first call to [1]
80
+ a.clear
81
+ expect( t[1] ).to eq( 1 )
82
+ expect( a ).to eq([["b", 1, 1]])
83
+ end
84
+ end
85
+
86
+ describe "fetch" do
87
+ it "raises proper errors for invalid keys" do
88
+ expect{ tuple0.fetch("x") }.to raise_error(KeyError)
89
+ expect{ tuple0.fetch(0.5) }.to raise_error(KeyError)
90
+ expect{ tuple0.fetch(2) }.to raise_error(IndexError)
91
+ expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
92
+ expect{ tuple0.fetch(-3) }.to raise_error(IndexError)
93
+ expect{ tuple2.fetch(-4) }.to raise_error(IndexError)
94
+ expect{ tuple_empty[0] }.to raise_error(TypeError)
95
+ end
96
+
97
+ it "supports array like access" do
98
+ expect( tuple0.fetch(0) ).to eq( "1" )
99
+ expect( tuple0.fetch(1) ).to eq( "a" )
100
+ expect( tuple2.fetch(0) ).to eq( 1 )
101
+ expect( tuple2.fetch(1) ).to eq( true )
102
+ expect( tuple2.fetch(2) ).to eq( "3" )
103
+ end
104
+
105
+ it "supports default value for indices" do
106
+ expect( tuple0.fetch(2, 42) ).to eq( 42 )
107
+ expect( tuple0.fetch(2){43} ).to eq( 43 )
108
+ end
109
+
110
+ it "supports negative indices" do
111
+ expect( tuple0.fetch(-2) ).to eq( "1" )
112
+ expect( tuple0.fetch(-1) ).to eq( "a" )
113
+ expect( tuple2.fetch(-3) ).to eq( 1 )
114
+ expect( tuple2.fetch(-2) ).to eq( true )
115
+ expect( tuple2.fetch(-1) ).to eq( "3" )
116
+ end
117
+
118
+ it "supports hash like access" do
119
+ expect( tuple0.fetch("column1") ).to eq( "1" )
120
+ expect( tuple0.fetch("column2") ).to eq( "a" )
121
+ expect( tuple2.fetch("a") ).to eq( 1 )
122
+ expect( tuple2.fetch("b") ).to eq( "3" )
123
+ end
124
+
125
+ it "supports default value for name keys" do
126
+ expect( tuple0.fetch("x", "defa") ).to eq("defa")
127
+ expect( tuple0.fetch("x"){"defa"} ).to eq("defa")
128
+ end
129
+ end
130
+
131
+ describe "each" do
132
+ it "can be used as an enumerator" do
133
+ expect( tuple0.each ).to be_kind_of(Enumerator)
134
+ expect( tuple0.each.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
135
+ expect( tuple1.each.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
136
+ expect( tuple2.each.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
137
+ expect( tuple3.each.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
138
+ expect{ tuple_empty.each }.to raise_error(TypeError)
139
+ end
140
+
141
+ it "can be used with block" do
142
+ a = []
143
+ tuple0.each do |*v|
144
+ a << v
145
+ end
146
+ expect( a ).to eq( [["column1", "1"], ["column2", "a"]] )
147
+ end
148
+ end
149
+
150
+ describe "each_value" do
151
+ it "can be used as an enumerator" do
152
+ expect( tuple0.each_value ).to be_kind_of(Enumerator)
153
+ expect( tuple0.each_value.to_a ).to eq( ["1", "a"] )
154
+ expect( tuple1.each_value.to_a ).to eq( ["2", "b"] )
155
+ expect( tuple2.each_value.to_a ).to eq( [1, true, "3"] )
156
+ expect( tuple3.each_value.to_a ).to eq( [2, false, "4"] )
157
+ expect{ tuple_empty.each_value }.to raise_error(TypeError)
158
+ end
159
+
160
+ it "can be used with block" do
161
+ a = []
162
+ tuple0.each_value do |v|
163
+ a << v
164
+ end
165
+ expect( a ).to eq( ["1", "a"] )
166
+ end
167
+ end
168
+
169
+ it "responds to values" do
170
+ expect( tuple0.values ).to eq( ["1", "a"] )
171
+ expect( tuple3.values ).to eq( [2, false, "4"] )
172
+ expect{ tuple_empty.values }.to raise_error(TypeError)
173
+ end
174
+
175
+ it "responds to key?" do
176
+ expect( tuple1.key?("column1") ).to eq( true )
177
+ expect( tuple1.key?("other") ).to eq( false )
178
+ expect( tuple1.has_key?("column1") ).to eq( true )
179
+ expect( tuple1.has_key?("other") ).to eq( false )
180
+ end
181
+
182
+ it "responds to keys" do
183
+ expect( tuple0.keys ).to eq( ["column1", "column2"] )
184
+ expect( tuple2.keys ).to eq( ["a", "b", "b"] )
185
+ end
186
+
187
+ describe "each_key" do
188
+ it "can be used as an enumerator" do
189
+ expect( tuple0.each_key ).to be_kind_of(Enumerator)
190
+ expect( tuple0.each_key.to_a ).to eq( ["column1", "column2"] )
191
+ expect( tuple2.each_key.to_a ).to eq( ["a", "b", "b"] )
192
+ end
193
+
194
+ it "can be used with block" do
195
+ a = []
196
+ tuple0.each_key do |v|
197
+ a << v
198
+ end
199
+ expect( a ).to eq( ["column1", "column2"] )
200
+ end
201
+ end
202
+
203
+ it "responds to length" do
204
+ expect( tuple0.length ).to eq( 2 )
205
+ expect( tuple0.size ).to eq( 2 )
206
+ expect( tuple2.size ).to eq( 3 )
207
+ end
208
+
209
+ it "responds to index" do
210
+ expect( tuple0.index("column1") ).to eq( 0 )
211
+ expect( tuple0.index("column2") ).to eq( 1 )
212
+ expect( tuple0.index("x") ).to eq( nil )
213
+ expect( tuple2.index("a") ).to eq( 0 )
214
+ expect( tuple2.index("b") ).to eq( 2 )
215
+ end
216
+
217
+ it "can be used as Enumerable" do
218
+ expect( tuple0.to_a ).to eq( [["column1", "1"], ["column2", "a"]] )
219
+ expect( tuple1.to_a ).to eq( [["column1", "2"], ["column2", "b"]] )
220
+ expect( tuple2.to_a ).to eq( [["a", 1], ["b", true], ["b", "3"]] )
221
+ expect( tuple3.to_a ).to eq( [["a", 2], ["b", false], ["b", "4"]] )
222
+ end
223
+
224
+ it "can be marshaled" do
225
+ [tuple0, tuple1, tuple2, tuple3].each do |t1|
226
+ str = Marshal.dump(t1)
227
+ t2 = Marshal.load(str)
228
+
229
+ expect( t2 ).to be_kind_of(t1.class)
230
+ expect( t2 ).not_to equal(t1)
231
+ expect( t2.to_a ).to eq(t1.to_a)
232
+ end
233
+ end
234
+
235
+ it "passes instance variables when marshaled" do
236
+ t1 = tuple0
237
+ t1.instance_variable_set("@a", 4711)
238
+ str = Marshal.dump(t1)
239
+ t2 = Marshal.load(str)
240
+
241
+ expect( t2.instance_variable_get("@a") ).to eq( 4711 )
242
+ end
243
+
244
+ it "can't be marshaled when empty" do
245
+ expect{ Marshal.dump(tuple_empty) }.to raise_error(TypeError)
246
+ end
247
+
248
+ it "should give account about memory usage" do
249
+ expect( ObjectSpace.memsize_of(tuple0) ).to be > 40
250
+ expect( ObjectSpace.memsize_of(tuple_empty) ).to be > 0
251
+ end
252
+
253
+ it "should override #inspect" do
254
+ expect( tuple1.inspect ).to eq('#<PG::Tuple column1: "2", column2: "b">')
255
+ expect( tuple2.inspect ).to eq('#<PG::Tuple a: 1, b: true, b: "3">')
256
+ expect{ tuple_empty.inspect }.to raise_error(TypeError)
257
+ end
258
+
259
+ context "with cleared result" do
260
+ it "should raise an error when non-materialized fields are used" do
261
+ r = result2x2
262
+ t = r.tuple(0)
263
+ t[0] # materialize first field only
264
+ r.clear
265
+
266
+ # second column should fail
267
+ expect{ t[1] }.to raise_error(PG::Error)
268
+ expect{ t.fetch(1) }.to raise_error(PG::Error)
269
+ expect{ t.fetch("column2") }.to raise_error(PG::Error)
270
+
271
+ # first column should succeed
272
+ expect( t[0] ).to eq( "1" )
273
+ expect( t.fetch(0) ).to eq( "1" )
274
+ expect( t.fetch("column1") ).to eq( "1" )
275
+
276
+ # should fail due to the second column
277
+ expect{ t.values }.to raise_error(PG::Error)
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,138 @@
1
+ # -*- rspec -*-
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByClass do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT8', oid: 701 }
13
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
14
+ let!(:binaryenc_int){ PG::BinaryEncoder::Int8.new name: 'INT8', oid: 20, format: 1 }
15
+ let!(:pass_through_type) do
16
+ type = Class.new(PG::SimpleEncoder) do
17
+ def encode(*v)
18
+ v.inspect
19
+ end
20
+ end.new
21
+ type.oid = 25
22
+ type.format = 0
23
+ type.name = 'pass_through'
24
+ type
25
+ end
26
+
27
+ let!(:tm) do
28
+ tm = PG::TypeMapByClass.new
29
+ tm[Integer] = binaryenc_int
30
+ tm[Float] = textenc_float
31
+ tm[Symbol] = pass_through_type
32
+ tm
33
+ end
34
+
35
+ let!(:raise_class) do
36
+ Class.new
37
+ end
38
+
39
+ let!(:derived_tm) do
40
+ tm = Class.new(PG::TypeMapByClass) do
41
+ def array_type_map_for(value)
42
+ PG::TextEncoder::Array.new name: '_INT4', oid: 1007, elements_type: PG::TextEncoder::Integer.new
43
+ end
44
+ end.new
45
+ tm[Integer] = proc{|value| textenc_int }
46
+ tm[raise_class] = proc{|value| /invalid/ }
47
+ tm[Array] = :array_type_map_for
48
+ tm
49
+ end
50
+
51
+ it "should retrieve all conversions" do
52
+ expect( tm.coders ).to eq( {
53
+ Integer => binaryenc_int,
54
+ Float => textenc_float,
55
+ Symbol => pass_through_type,
56
+ } )
57
+ end
58
+
59
+ it "should retrieve particular conversions" do
60
+ expect( tm[Integer] ).to eq(binaryenc_int)
61
+ expect( tm[Float] ).to eq(textenc_float)
62
+ expect( tm[Range] ).to be_nil
63
+ expect( derived_tm[raise_class] ).to be_kind_of(Proc)
64
+ expect( derived_tm[Array] ).to eq(:array_type_map_for)
65
+ end
66
+
67
+ it "should allow deletion of coders" do
68
+ tm[Integer] = nil
69
+ expect( tm[Integer] ).to be_nil
70
+ expect( tm.coders ).to eq( {
71
+ Float => textenc_float,
72
+ Symbol => pass_through_type,
73
+ } )
74
+ end
75
+
76
+ it "forwards query param conversions to the #default_type_map" do
77
+ tm1 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] )
78
+
79
+ tm2 = PG::TypeMapByClass.new
80
+ tm2[Integer] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
81
+ tm2.default_type_map = tm1
82
+
83
+ res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", ['1', 2, 3], 0, tm2 )
84
+
85
+ expect( res.ftype(0) ).to eq( 23 ) # tm1
86
+ expect( res.ftype(1) ).to eq( 21 ) # tm2
87
+ expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
88
+ end
89
+
90
+ #
91
+ # Decoding Examples
92
+ #
93
+
94
+ it "should raise an error when used for results" do
95
+ res = @conn.exec_params( "SELECT 1", [], 1 )
96
+ expect{ res.type_map = tm }.to raise_error(NotImplementedError, /not suitable to map result values/)
97
+ end
98
+
99
+ #
100
+ # Encoding Examples
101
+ #
102
+
103
+ it "should allow mixed type conversions" do
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, #{@conn.internal_encoding.inspect}]"]])
106
+ expect( res.ftype(0) ).to eq(20)
107
+ end
108
+
109
+ it "should expire the cache after changes to the coders" do
110
+ res = @conn.exec_params( "SELECT $1", [5], 0, tm )
111
+ expect( res.ftype(0) ).to eq(20)
112
+
113
+ tm[Integer] = textenc_int
114
+
115
+ res = @conn.exec_params( "SELECT $1", [5], 0, tm )
116
+ expect( res.ftype(0) ).to eq(23)
117
+ end
118
+
119
+ it "should allow mixed type conversions with derived type map" do
120
+ res = @conn.exec_params( "SELECT $1, $2", [6, [7]], 0, derived_tm )
121
+ expect( res.values ).to eq([['6', '{7}']])
122
+ expect( res.ftype(0) ).to eq(23)
123
+ expect( res.ftype(1) ).to eq(1007)
124
+ end
125
+
126
+ it "should raise TypeError with derived type map" do
127
+ expect{
128
+ @conn.exec_params( "SELECT $1", [raise_class.new], 0, derived_tm )
129
+ }.to raise_error(TypeError, /invalid type Regexp/)
130
+ end
131
+
132
+ it "should raise error on invalid coder object" do
133
+ tm[TrueClass] = "dummy"
134
+ expect{
135
+ res = @conn.exec_params( "SELECT $1", [true], 0, tm )
136
+ }.to raise_error(NoMethodError, /undefined method.*call/)
137
+ end
138
+ end
@@ -0,0 +1,222 @@
1
+ # -*- rspec -*-
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByColumn do
10
+
11
+ let!(:textenc_int){ PG::TextEncoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
13
+ let!(:textenc_float){ PG::TextEncoder::Float.new name: 'FLOAT4', oid: 700 }
14
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT4', oid: 700 }
15
+ let!(:textenc_string){ PG::TextEncoder::String.new name: 'TEXT', oid: 25 }
16
+ let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
17
+ let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
18
+ let!(:binaryenc_bytea){ PG::BinaryEncoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
19
+ let!(:binarydec_bytea){ PG::BinaryDecoder::Bytea.new name: 'BYTEA', oid: 17, format: 1 }
20
+ let!(:pass_through_type) do
21
+ type = Class.new(PG::SimpleDecoder) do
22
+ def decode(*v)
23
+ v
24
+ end
25
+ end.new
26
+ type.oid = 123456
27
+ type.format = 1
28
+ type.name = 'pass_through'
29
+ type
30
+ end
31
+
32
+ it "should retrieve it's conversions" do
33
+ cm = PG::TypeMapByColumn.new( [textdec_int, textenc_string, textdec_float, pass_through_type, nil] )
34
+ expect( cm.coders ).to eq( [
35
+ textdec_int,
36
+ textenc_string,
37
+ textdec_float,
38
+ pass_through_type,
39
+ nil
40
+ ] )
41
+ expect( cm.inspect ).to eq( "#<PG::TypeMapByColumn INT4:0 TEXT:0 FLOAT4:0 pass_through:1 nil>" )
42
+ end
43
+
44
+ it "should retrieve it's oids" do
45
+ cm = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
46
+ expect( cm.oids ).to eq( [23, 25, 700, 123456, nil] )
47
+ end
48
+
49
+ it "should gracefully handle not initialized state" do
50
+ # PG::TypeMapByColumn is not initialized in allocate function, like other
51
+ # type maps, but in #initialize. So it might be not called by derived classes.
52
+
53
+ not_init = Class.new(PG::TypeMapByColumn) do
54
+ def initialize
55
+ # no super call
56
+ end
57
+ end.new
58
+
59
+ expect{ @conn.exec_params( "SELECT $1", [ 0 ], 0, not_init ) }.to raise_error(NotImplementedError)
60
+
61
+ res = @conn.exec( "SELECT 1" )
62
+ expect{ res.type_map = not_init }.to raise_error(NotImplementedError)
63
+
64
+ @conn.copy_data("COPY (SELECT 1) TO STDOUT") do
65
+ decoder = PG::TextDecoder::CopyRow.new(type_map: not_init)
66
+ expect{ @conn.get_copy_data(false, decoder) }.to raise_error(NotImplementedError)
67
+ @conn.get_copy_data
68
+ end
69
+ end
70
+
71
+
72
+ #
73
+ # Encoding Examples
74
+ #
75
+
76
+ it "should encode integer params" do
77
+ col_map = PG::TypeMapByColumn.new( [textenc_int]*3 )
78
+ res = @conn.exec_params( "SELECT $1, $2, $3", [ 0, nil, "-999" ], 0, col_map )
79
+ expect( res.values ).to eq( [
80
+ [ "0", nil, "-999" ],
81
+ ] )
82
+ end
83
+
84
+ it "should encode bytea params" do
85
+ data = "'\u001F\\"
86
+ col_map = PG::TypeMapByColumn.new( [binaryenc_bytea]*2 )
87
+ res = @conn.exec_params( "SELECT $1, $2", [ data, nil ], 0, col_map )
88
+ res.type_map = PG::TypeMapByColumn.new( [textdec_bytea]*2 )
89
+ expect( res.values ).to eq( [
90
+ [ data, nil ],
91
+ ] )
92
+ end
93
+
94
+
95
+ it "should allow hash form parameters for default encoder" do
96
+ col_map = PG::TypeMapByColumn.new( [nil, nil] )
97
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
98
+ hash_param_nil = { value: nil, type: 17, format: 1 }
99
+ res = @conn.exec_params( "SELECT $1, $2",
100
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
101
+ expect( res.values ).to eq( [["\\x00ff", nil]] )
102
+ expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
103
+ end
104
+
105
+ it "should convert hash form parameters to string when using string encoders" do
106
+ col_map = PG::TypeMapByColumn.new( [textenc_string, textenc_string] )
107
+ hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
108
+ hash_param_nil = { value: nil, type: 17, format: 1 }
109
+ res = @conn.exec_params( "SELECT $1::text, $2::text",
110
+ [ hash_param_bin, hash_param_nil ], 0, col_map )
111
+ expect( res.values ).to eq( [["{:value=>\"\\x00\\xFF\", :type=>17, :format=>1}", "{:value=>nil, :type=>17, :format=>1}"]] )
112
+ end
113
+
114
+ it "shouldn't allow param mappings with different number of fields" do
115
+ expect{
116
+ @conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([]) )
117
+ }.to raise_error(ArgumentError, /mapped columns/)
118
+ end
119
+
120
+ it "should verify the default type map for query params as well" do
121
+ tm1 = PG::TypeMapByColumn.new([])
122
+ expect{
123
+ @conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1) )
124
+ }.to raise_error(ArgumentError, /mapped columns/)
125
+ end
126
+
127
+ it "forwards query param conversions to the #default_type_map" do
128
+ tm1 = PG::TypeMapByClass.new
129
+ tm1[Integer] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
130
+
131
+ tm2 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] ).with_default_type_map( tm1 )
132
+ res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", [1, 2, :abc], 0, tm2 )
133
+
134
+ expect( res.ftype(0) ).to eq( 23 ) # tm2
135
+ expect( res.ftype(1) ).to eq( 21 ) # tm1
136
+ expect( res.getvalue(0,2) ).to eq( "abc" ) # TypeMapAllStrings
137
+ end
138
+
139
+ #
140
+ # Decoding Examples
141
+ #
142
+
143
+ class Exception_in_decode < PG::SimpleDecoder
144
+ def decode(res, tuple, field)
145
+ raise "no type decoder defined for tuple #{tuple} field #{field}"
146
+ end
147
+ end
148
+
149
+ it "should raise an error from decode method of type converter" do
150
+ res = @conn.exec( "SELECT now()" )
151
+ types = Array.new( res.nfields, Exception_in_decode.new )
152
+ res.type_map = PG::TypeMapByColumn.new( types )
153
+ expect{ res.values }.to raise_error(/no type decoder defined/)
154
+ end
155
+
156
+ it "should raise an error for invalid params" do
157
+ expect{ PG::TypeMapByColumn.new( :WrongType ) }.to raise_error(TypeError, /wrong argument type/)
158
+ expect{ PG::TypeMapByColumn.new( [123] ) }.to raise_error(ArgumentError, /invalid/)
159
+ end
160
+
161
+ it "shouldn't allow result mappings with different number of fields" do
162
+ res = @conn.exec( "SELECT 1" )
163
+ expect{ res.type_map = PG::TypeMapByColumn.new([]) }.to raise_error(ArgumentError, /mapped columns/)
164
+ end
165
+
166
+ it "should verify the default type map for result values as well" do
167
+ res = @conn.exec( "SELECT 1" )
168
+ tm1 = PG::TypeMapByColumn.new([])
169
+ expect{
170
+ res.type_map = PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1)
171
+ }.to raise_error(ArgumentError, /mapped columns/)
172
+ end
173
+
174
+ it "forwards result value conversions to a TypeMapByOid as #default_type_map" do
175
+ # One run with implicit built TypeMapByColumn and another with online lookup
176
+ [0, 10].each do |max_rows|
177
+ tm1 = PG::TypeMapByOid.new
178
+ tm1.add_coder PG::TextDecoder::Integer.new name: 'INT2', oid: 21
179
+ tm1.max_rows_for_online_lookup = max_rows
180
+
181
+ tm2 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] ).with_default_type_map( tm1 )
182
+ res = @conn.exec( "SELECT '1'::INT4, '2'::INT2, '3'::INT8" ).map_types!( tm2 )
183
+
184
+ expect( res.getvalue(0,0) ).to eq( 1 ) # tm2
185
+ expect( res.getvalue(0,1) ).to eq( 2 ) # tm1
186
+ expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
187
+ end
188
+ end
189
+
190
+ it "forwards get_copy_data conversions to another TypeMapByColumn as #default_type_map" do
191
+ tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] )
192
+ tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil] ).with_default_type_map( tm1 )
193
+ decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
194
+ @conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
195
+ expect( @conn.get_copy_data ).to eq( [1, 2, '3'] )
196
+ @conn.get_copy_data
197
+ end
198
+ end
199
+
200
+ it "will deny copy queries with different column count" do
201
+ [[2, 2], [2, 3], [3, 2]].each do |cols1, cols2|
202
+ tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil][0, cols1] )
203
+ tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil][0, cols2] ).with_default_type_map( tm1 )
204
+ decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
205
+ @conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
206
+ expect{ @conn.get_copy_data }.to raise_error(ArgumentError, /number of copy fields/)
207
+ @conn.get_copy_data
208
+ end
209
+ end
210
+ end
211
+
212
+ #
213
+ # Decoding Examples text format
214
+ #
215
+
216
+ it "should allow mixed type conversions" do
217
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE, 3" )
218
+ res.type_map = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
219
+ expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3], '3' ]] )
220
+ end
221
+
222
+ end