pg 0.17.1 → 0.18.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 (53) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/ChangeLog +2407 -2
  4. data/History.rdoc +68 -0
  5. data/Manifest.txt +29 -1
  6. data/README-Windows.rdoc +15 -26
  7. data/README.rdoc +52 -2
  8. data/Rakefile +56 -18
  9. data/Rakefile.cross +77 -49
  10. data/ext/extconf.rb +33 -26
  11. data/ext/pg.c +142 -21
  12. data/ext/pg.h +242 -6
  13. data/ext/pg_binary_decoder.c +162 -0
  14. data/ext/pg_binary_encoder.c +162 -0
  15. data/ext/pg_coder.c +479 -0
  16. data/ext/pg_connection.c +858 -553
  17. data/ext/pg_copy_coder.c +561 -0
  18. data/ext/pg_errors.c +6 -0
  19. data/ext/pg_result.c +479 -128
  20. data/ext/pg_text_decoder.c +421 -0
  21. data/ext/pg_text_encoder.c +663 -0
  22. data/ext/pg_type_map.c +159 -0
  23. data/ext/pg_type_map_all_strings.c +116 -0
  24. data/ext/pg_type_map_by_class.c +239 -0
  25. data/ext/pg_type_map_by_column.c +312 -0
  26. data/ext/pg_type_map_by_mri_type.c +284 -0
  27. data/ext/pg_type_map_by_oid.c +355 -0
  28. data/ext/pg_type_map_in_ruby.c +299 -0
  29. data/ext/util.c +149 -0
  30. data/ext/util.h +65 -0
  31. data/lib/pg/basic_type_mapping.rb +399 -0
  32. data/lib/pg/coder.rb +83 -0
  33. data/lib/pg/connection.rb +81 -29
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +44 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/pg.rb +12 -2
  39. data/spec/{lib/helpers.rb → helpers.rb} +101 -39
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +516 -218
  42. data/spec/pg/result_spec.rb +216 -112
  43. data/spec/pg/type_map_by_class_spec.rb +138 -0
  44. data/spec/pg/type_map_by_column_spec.rb +222 -0
  45. data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
  46. data/spec/pg/type_map_by_oid_spec.rb +149 -0
  47. data/spec/pg/type_map_in_ruby_spec.rb +164 -0
  48. data/spec/pg/type_map_spec.rb +22 -0
  49. data/spec/pg/type_spec.rb +697 -0
  50. data/spec/pg_spec.rb +24 -18
  51. data.tar.gz.sig +0 -0
  52. metadata +111 -45
  53. metadata.gz.sig +0 -0
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env 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[Bignum] ).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]']])
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
+ #!/usr/bin/env 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
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByMriType 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::TypeMapByMriType.new
29
+ tm['T_FIXNUM'] = binaryenc_int
30
+ tm['T_FLOAT'] = textenc_float
31
+ tm['T_SYMBOL'] = pass_through_type
32
+ tm
33
+ end
34
+
35
+ let!(:derived_tm) do
36
+ tm = Class.new(PG::TypeMapByMriType) do
37
+ def array_type_map_for(value)
38
+ PG::TextEncoder::Array.new name: '_INT4', oid: 1007, elements_type: PG::TextEncoder::Integer.new
39
+ end
40
+ end.new
41
+ tm['T_FIXNUM'] = proc{|value| textenc_int }
42
+ tm['T_REGEXP'] = proc{|value| :invalid }
43
+ tm['T_ARRAY'] = :array_type_map_for
44
+ tm
45
+ end
46
+
47
+ it "should retrieve all conversions" do
48
+ expect( tm.coders ).to eq( {
49
+ "T_FIXNUM" => binaryenc_int,
50
+ "T_FLOAT" => textenc_float,
51
+ "T_SYMBOL" => pass_through_type,
52
+ "T_HASH" => nil,
53
+ "T_ARRAY" => nil,
54
+ "T_BIGNUM" => nil,
55
+ "T_CLASS" => nil,
56
+ "T_COMPLEX" => nil,
57
+ "T_DATA" => nil,
58
+ "T_FALSE" => nil,
59
+ "T_FILE" => nil,
60
+ "T_MODULE" => nil,
61
+ "T_OBJECT" => nil,
62
+ "T_RATIONAL" => nil,
63
+ "T_REGEXP" => nil,
64
+ "T_STRING" => nil,
65
+ "T_STRUCT" => nil,
66
+ "T_TRUE" => nil,
67
+ } )
68
+ end
69
+
70
+ it "should retrieve particular conversions" do
71
+ expect( tm['T_FIXNUM'] ).to eq(binaryenc_int)
72
+ expect( tm['T_FLOAT'] ).to eq(textenc_float)
73
+ expect( tm['T_BIGNUM'] ).to be_nil
74
+ expect( derived_tm['T_REGEXP'] ).to be_kind_of(Proc)
75
+ expect( derived_tm['T_ARRAY'] ).to eq(:array_type_map_for)
76
+ end
77
+
78
+ it "should allow deletion of coders" do
79
+ tm['T_FIXNUM'] = nil
80
+ expect( tm['T_FIXNUM'] ).to be_nil
81
+ end
82
+
83
+ it "should check MRI type key" do
84
+ expect{ tm['NO_TYPE'] }.to raise_error(ArgumentError)
85
+ expect{ tm[123] }.to raise_error(TypeError)
86
+ expect{ tm['NO_TYPE'] = textenc_float }.to raise_error(ArgumentError)
87
+ expect{ tm[123] = textenc_float }.to raise_error(TypeError)
88
+ end
89
+
90
+ it "forwards query param conversions to the #default_type_map" do
91
+ tm1 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] )
92
+
93
+ tm2 = PG::TypeMapByMriType.new
94
+ tm2['T_FIXNUM'] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
95
+ tm2.default_type_map = tm1
96
+
97
+ res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", ['1', 2, 3], 0, tm2 )
98
+
99
+ expect( res.ftype(0) ).to eq( 23 ) # tm1
100
+ expect( res.ftype(1) ).to eq( 21 ) # tm2
101
+ expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
102
+ end
103
+
104
+ #
105
+ # Decoding Examples
106
+ #
107
+
108
+ it "should raise an error when used for results" do
109
+ res = @conn.exec_params( "SELECT 1", [], 1 )
110
+ expect{ res.type_map = tm }.to raise_error(NotImplementedError, /not suitable to map result values/)
111
+ end
112
+
113
+ #
114
+ # Encoding Examples
115
+ #
116
+
117
+ it "should allow mixed type conversions" do
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]']])
120
+ expect( res.ftype(0) ).to eq(20)
121
+ end
122
+
123
+ it "should allow mixed type conversions with derived type map" do
124
+ res = @conn.exec_params( "SELECT $1, $2", [6, [7]], 0, derived_tm )
125
+ expect( res.values ).to eq([['6', '{7}']])
126
+ expect( res.ftype(0) ).to eq(23)
127
+ expect( res.ftype(1) ).to eq(1007)
128
+ end
129
+
130
+ it "should raise TypeError with derived type map" do
131
+ expect{
132
+ @conn.exec_params( "SELECT $1", [//], 0, derived_tm )
133
+ }.to raise_error(TypeError, /argument 1/)
134
+ end
135
+
136
+ end
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env rspec
2
+ # encoding: utf-8
3
+
4
+ require_relative '../helpers'
5
+
6
+ require 'pg'
7
+
8
+
9
+ describe PG::TypeMapByOid do
10
+
11
+ let!(:textdec_int){ PG::TextDecoder::Integer.new name: 'INT4', oid: 23 }
12
+ let!(:textdec_float){ PG::TextDecoder::Float.new name: 'FLOAT8', oid: 701 }
13
+ let!(:textdec_string){ PG::TextDecoder::String.new name: 'TEXT', oid: 25 }
14
+ let!(:textdec_bytea){ PG::TextDecoder::Bytea.new name: 'BYTEA', oid: 17 }
15
+ let!(:binarydec_float){ PG::BinaryDecoder::Float.new name: 'FLOAT8', oid: 701, format: 1 }
16
+ let!(:pass_through_type) do
17
+ type = Class.new(PG::SimpleDecoder) do
18
+ def decode(*v)
19
+ v
20
+ end
21
+ end.new
22
+ type.oid = 1082
23
+ type.format = 0
24
+ type.name = 'pass_through'
25
+ type
26
+ end
27
+
28
+ let!(:tm) do
29
+ tm = PG::TypeMapByOid.new
30
+ tm.add_coder textdec_int
31
+ tm.add_coder textdec_float
32
+ tm.add_coder binarydec_float
33
+ tm.add_coder pass_through_type
34
+ tm
35
+ end
36
+
37
+ it "should retrieve it's conversions" do
38
+ expect( tm.coders ).to eq( [
39
+ textdec_int,
40
+ textdec_float,
41
+ pass_through_type,
42
+ binarydec_float,
43
+ ] )
44
+ end
45
+
46
+ it "should allow deletion of coders" do
47
+ expect( tm.rm_coder 0, 701 ).to eq(textdec_float)
48
+ expect( tm.rm_coder 0, 701 ).to eq(nil)
49
+ expect( tm.rm_coder 1, 701 ).to eq(binarydec_float)
50
+ expect( tm.coders ).to eq( [
51
+ textdec_int,
52
+ pass_through_type,
53
+ ] )
54
+ end
55
+
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)
59
+ end
60
+
61
+ it "should check format when adding coders" do
62
+ textdec_int.format = 2
63
+ expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
64
+ textdec_int.format = -1
65
+ expect{ tm.add_coder textdec_int }.to raise_error(ArgumentError)
66
+ end
67
+
68
+ it "should check coder type when adding coders" do
69
+ expect{ tm.add_coder :dummy }.to raise_error(ArgumentError)
70
+ end
71
+
72
+ it "should allow reading and writing max_rows_for_online_lookup" do
73
+ expect( tm.max_rows_for_online_lookup ).to eq(10)
74
+ tm.max_rows_for_online_lookup = 5
75
+ expect( tm.max_rows_for_online_lookup ).to eq(5)
76
+ end
77
+
78
+ it "should allow building new TypeMapByColumn for a given result" do
79
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
80
+ tm2 = tm.build_column_map(res)
81
+ expect( tm2 ).to be_a_kind_of(PG::TypeMapByColumn)
82
+ expect( tm2.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
83
+ end
84
+
85
+ it "forwards result value conversions to another TypeMapByOid as #default_type_map" do
86
+ # One run with implicit built TypeMapByColumn and another with online lookup
87
+ # for each type map.
88
+ [[0, 0], [0, 10], [10, 0], [10, 10]].each do |max_rows1, max_rows2|
89
+ tm1 = PG::TypeMapByOid.new
90
+ tm1.add_coder PG::TextDecoder::Integer.new name: 'INT2', oid: 21
91
+ tm1.max_rows_for_online_lookup = max_rows1
92
+
93
+ tm2 = PG::TypeMapByOid.new
94
+ tm2.add_coder PG::TextDecoder::Integer.new name: 'INT4', oid: 23
95
+ tm2.max_rows_for_online_lookup = max_rows2
96
+ tm2.default_type_map = tm1
97
+
98
+ res = @conn.exec( "SELECT '1'::INT4, '2'::INT2, '3'::INT8" ).map_types!( tm2 )
99
+
100
+ expect( res.getvalue(0,0) ).to eq( 1 ) # tm2
101
+ expect( res.getvalue(0,1) ).to eq( 2 ) # tm1
102
+ expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
103
+ end
104
+ end
105
+
106
+ #
107
+ # Decoding Examples text format
108
+ #
109
+
110
+ it "should allow mixed type conversions in text format" do
111
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
112
+ res.type_map = tm
113
+ expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3] ]] )
114
+ end
115
+
116
+ it "should build a TypeMapByColumn when assigned and the number of rows is high enough" do
117
+ res = @conn.exec( "SELECT generate_series(1,20), 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
118
+ res.type_map = tm
119
+ expect( res.type_map ).to be_kind_of( PG::TypeMapByColumn )
120
+ expect( res.type_map.coders ).to eq( [textdec_int, nil, textdec_float, pass_through_type] )
121
+ end
122
+
123
+ it "should use TypeMapByOid for online lookup and the number of rows is low enough" do
124
+ res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE" )
125
+ res.type_map = tm
126
+ expect( res.type_map ).to be_kind_of( PG::TypeMapByOid )
127
+ end
128
+
129
+ #
130
+ # Decoding Examples binary format
131
+ #
132
+
133
+ it "should allow mixed type conversions in binary format" do
134
+ res = @conn.exec_params( "SELECT 1, 2.0::FLOAT", [], 1 )
135
+ res.type_map = tm
136
+ expect( res.values ).to eq( [["\x00\x00\x00\x01", 2.0 ]] )
137
+ end
138
+
139
+ #
140
+ # Encoding Examples
141
+ #
142
+
143
+ it "should raise an error used for query params" do
144
+ expect{
145
+ @conn.exec_params( "SELECT $1", [5], 0, tm )
146
+ }.to raise_error(NotImplementedError, /not suitable to map query params/)
147
+ end
148
+
149
+ end