pg 0.17.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/BSDL +2 -2
- data/ChangeLog +0 -3506
- data/History.rdoc +308 -0
- data/Manifest.txt +35 -19
- data/README-Windows.rdoc +17 -28
- data/README.ja.rdoc +1 -2
- data/README.rdoc +113 -14
- data/Rakefile +67 -30
- data/Rakefile.cross +109 -83
- data/ext/errorcodes.def +101 -0
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +33 -2
- data/ext/extconf.rb +55 -58
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +27 -39
- data/ext/pg.c +262 -130
- data/ext/pg.h +266 -54
- data/ext/pg_binary_decoder.c +229 -0
- data/ext/pg_binary_encoder.c +163 -0
- data/ext/pg_coder.c +561 -0
- data/ext/pg_connection.c +1689 -990
- data/ext/pg_copy_coder.c +599 -0
- data/ext/pg_errors.c +6 -0
- data/ext/pg_record_coder.c +491 -0
- data/ext/pg_result.c +897 -164
- data/ext/pg_text_decoder.c +987 -0
- data/ext/pg_text_encoder.c +814 -0
- data/ext/pg_tuple.c +549 -0
- data/ext/pg_type_map.c +166 -0
- data/ext/pg_type_map_all_strings.c +116 -0
- data/ext/pg_type_map_by_class.c +244 -0
- data/ext/pg_type_map_by_column.c +313 -0
- data/ext/pg_type_map_by_mri_type.c +284 -0
- data/ext/pg_type_map_by_oid.c +356 -0
- data/ext/pg_type_map_in_ruby.c +299 -0
- data/ext/pg_util.c +149 -0
- data/ext/pg_util.h +65 -0
- data/lib/pg/basic_type_mapping.rb +522 -0
- data/lib/pg/binary_decoder.rb +23 -0
- data/lib/pg/coder.rb +104 -0
- data/lib/pg/connection.rb +153 -41
- data/lib/pg/constants.rb +2 -1
- data/lib/pg/exceptions.rb +2 -1
- data/lib/pg/result.rb +33 -6
- data/lib/pg/text_decoder.rb +46 -0
- data/lib/pg/text_encoder.rb +59 -0
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +16 -0
- data/lib/pg.rb +29 -9
- data/spec/{lib/helpers.rb → helpers.rb} +151 -64
- data/spec/pg/basic_type_mapping_spec.rb +630 -0
- data/spec/pg/connection_spec.rb +1180 -477
- data/spec/pg/connection_sync_spec.rb +41 -0
- data/spec/pg/result_spec.rb +456 -120
- data/spec/pg/tuple_spec.rb +333 -0
- data/spec/pg/type_map_by_class_spec.rb +138 -0
- data/spec/pg/type_map_by_column_spec.rb +226 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +136 -0
- data/spec/pg/type_map_by_oid_spec.rb +149 -0
- data/spec/pg/type_map_in_ruby_spec.rb +164 -0
- data/spec/pg/type_map_spec.rb +22 -0
- data/spec/pg/type_spec.rb +1123 -0
- data/spec/pg_spec.rb +26 -20
- data.tar.gz.sig +0 -0
- metadata +148 -91
- metadata.gz.sig +0 -0
- data/sample/array_insert.rb +0 -20
- data/sample/async_api.rb +0 -106
- data/sample/async_copyto.rb +0 -39
- data/sample/async_mixed.rb +0 -56
- data/sample/check_conn.rb +0 -21
- data/sample/copyfrom.rb +0 -81
- data/sample/copyto.rb +0 -19
- data/sample/cursor.rb +0 -21
- data/sample/disk_usage_report.rb +0 -186
- data/sample/issue-119.rb +0 -94
- data/sample/losample.rb +0 -69
- data/sample/minimal-testcase.rb +0 -17
- data/sample/notify_wait.rb +0 -72
- data/sample/pg_statistics.rb +0 -294
- data/sample/replication_monitor.rb +0 -231
- data/sample/test_binary_values.rb +0 -33
- data/sample/wal_shipper.rb +0 -434
- data/sample/warehouse_partitions.rb +0 -320
@@ -0,0 +1,226 @@
|
|
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
|
+
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>" )
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should retrieve it's oids" do
|
49
|
+
cm = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
|
50
|
+
expect( cm.oids ).to eq( [23, 25, 700, 123456, nil] )
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should gracefully handle not initialized state" do
|
54
|
+
# PG::TypeMapByColumn is not initialized in allocate function, like other
|
55
|
+
# type maps, but in #initialize. So it might be not called by derived classes.
|
56
|
+
|
57
|
+
not_init = Class.new(PG::TypeMapByColumn) do
|
58
|
+
def initialize
|
59
|
+
# no super call
|
60
|
+
end
|
61
|
+
end.new
|
62
|
+
|
63
|
+
expect{ @conn.exec_params( "SELECT $1", [ 0 ], 0, not_init ) }.to raise_error(NotImplementedError)
|
64
|
+
|
65
|
+
res = @conn.exec( "SELECT 1" )
|
66
|
+
expect{ res.type_map = not_init }.to raise_error(NotImplementedError)
|
67
|
+
|
68
|
+
@conn.copy_data("COPY (SELECT 1) TO STDOUT") do
|
69
|
+
decoder = PG::TextDecoder::CopyRow.new(type_map: not_init)
|
70
|
+
expect{ @conn.get_copy_data(false, decoder) }.to raise_error(NotImplementedError)
|
71
|
+
@conn.get_copy_data
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
#
|
77
|
+
# Encoding Examples
|
78
|
+
#
|
79
|
+
|
80
|
+
it "should encode integer params" do
|
81
|
+
col_map = PG::TypeMapByColumn.new( [textenc_int]*3 )
|
82
|
+
res = @conn.exec_params( "SELECT $1, $2, $3", [ 0, nil, "-999" ], 0, col_map )
|
83
|
+
expect( res.values ).to eq( [
|
84
|
+
[ "0", nil, "-999" ],
|
85
|
+
] )
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should encode bytea params" do
|
89
|
+
data = "'\u001F\\"
|
90
|
+
col_map = PG::TypeMapByColumn.new( [binaryenc_bytea]*2 )
|
91
|
+
res = @conn.exec_params( "SELECT $1, $2", [ data, nil ], 0, col_map )
|
92
|
+
res.type_map = PG::TypeMapByColumn.new( [textdec_bytea]*2 )
|
93
|
+
expect( res.values ).to eq( [
|
94
|
+
[ data, nil ],
|
95
|
+
] )
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
it "should allow hash form parameters for default encoder" do
|
100
|
+
col_map = PG::TypeMapByColumn.new( [nil, nil] )
|
101
|
+
hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
|
102
|
+
hash_param_nil = { value: nil, type: 17, format: 1 }
|
103
|
+
res = @conn.exec_params( "SELECT $1, $2",
|
104
|
+
[ hash_param_bin, hash_param_nil ], 0, col_map )
|
105
|
+
expect( res.values ).to eq( [["\\x00ff", nil]] )
|
106
|
+
expect( result_typenames(res) ).to eq( ['bytea', 'bytea'] )
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should convert hash form parameters to string when using string encoders" do
|
110
|
+
col_map = PG::TypeMapByColumn.new( [textenc_string, textenc_string] )
|
111
|
+
hash_param_bin = { value: ["00ff"].pack("H*"), type: 17, format: 1 }
|
112
|
+
hash_param_nil = { value: nil, type: 17, format: 1 }
|
113
|
+
res = @conn.exec_params( "SELECT $1::text, $2::text",
|
114
|
+
[ hash_param_bin, hash_param_nil ], 0, col_map )
|
115
|
+
expect( res.values ).to eq( [["{:value=>\"\\x00\\xFF\", :type=>17, :format=>1}", "{:value=>nil, :type=>17, :format=>1}"]] )
|
116
|
+
end
|
117
|
+
|
118
|
+
it "shouldn't allow param mappings with different number of fields" do
|
119
|
+
expect{
|
120
|
+
@conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([]) )
|
121
|
+
}.to raise_error(ArgumentError, /mapped columns/)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should verify the default type map for query params as well" do
|
125
|
+
tm1 = PG::TypeMapByColumn.new([])
|
126
|
+
expect{
|
127
|
+
@conn.exec_params( "SELECT $1", [ 123 ], 0, PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1) )
|
128
|
+
}.to raise_error(ArgumentError, /mapped columns/)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "forwards query param conversions to the #default_type_map" do
|
132
|
+
tm1 = PG::TypeMapByClass.new
|
133
|
+
tm1[Integer] = PG::TextEncoder::Integer.new name: 'INT2', oid: 21
|
134
|
+
|
135
|
+
tm2 = PG::TypeMapByColumn.new( [textenc_int, nil, nil] ).with_default_type_map( tm1 )
|
136
|
+
res = @conn.exec_params( "SELECT $1, $2, $3::TEXT", [1, 2, :abc], 0, tm2 )
|
137
|
+
|
138
|
+
expect( res.ftype(0) ).to eq( 23 ) # tm2
|
139
|
+
expect( res.ftype(1) ).to eq( 21 ) # tm1
|
140
|
+
expect( res.getvalue(0,2) ).to eq( "abc" ) # TypeMapAllStrings
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
# Decoding Examples
|
145
|
+
#
|
146
|
+
|
147
|
+
class Exception_in_decode < PG::SimpleDecoder
|
148
|
+
def decode(res, tuple, field)
|
149
|
+
raise "no type decoder defined for tuple #{tuple} field #{field}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should raise an error from decode method of type converter" do
|
154
|
+
res = @conn.exec( "SELECT now()" )
|
155
|
+
types = Array.new( res.nfields, Exception_in_decode.new )
|
156
|
+
res.type_map = PG::TypeMapByColumn.new( types )
|
157
|
+
expect{ res.values }.to raise_error(/no type decoder defined/)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should raise an error for invalid params" do
|
161
|
+
expect{ PG::TypeMapByColumn.new( :WrongType ) }.to raise_error(TypeError, /wrong argument type/)
|
162
|
+
expect{ PG::TypeMapByColumn.new( [123] ) }.to raise_error(ArgumentError, /invalid/)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "shouldn't allow result mappings with different number of fields" do
|
166
|
+
res = @conn.exec( "SELECT 1" )
|
167
|
+
expect{ res.type_map = PG::TypeMapByColumn.new([]) }.to raise_error(ArgumentError, /mapped columns/)
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should verify the default type map for result values as well" do
|
171
|
+
res = @conn.exec( "SELECT 1" )
|
172
|
+
tm1 = PG::TypeMapByColumn.new([])
|
173
|
+
expect{
|
174
|
+
res.type_map = PG::TypeMapByColumn.new([nil]).with_default_type_map(tm1)
|
175
|
+
}.to raise_error(ArgumentError, /mapped columns/)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "forwards result value conversions to a TypeMapByOid as #default_type_map" do
|
179
|
+
# One run with implicit built TypeMapByColumn and another with online lookup
|
180
|
+
[0, 10].each do |max_rows|
|
181
|
+
tm1 = PG::TypeMapByOid.new
|
182
|
+
tm1.add_coder PG::TextDecoder::Integer.new name: 'INT2', oid: 21
|
183
|
+
tm1.max_rows_for_online_lookup = max_rows
|
184
|
+
|
185
|
+
tm2 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] ).with_default_type_map( tm1 )
|
186
|
+
res = @conn.exec( "SELECT '1'::INT4, '2'::INT2, '3'::INT8" ).map_types!( tm2 )
|
187
|
+
|
188
|
+
expect( res.getvalue(0,0) ).to eq( 1 ) # tm2
|
189
|
+
expect( res.getvalue(0,1) ).to eq( 2 ) # tm1
|
190
|
+
expect( res.getvalue(0,2) ).to eq( "3" ) # TypeMapAllStrings
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
it "forwards get_copy_data conversions to another TypeMapByColumn as #default_type_map" do
|
195
|
+
tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil] )
|
196
|
+
tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil] ).with_default_type_map( tm1 )
|
197
|
+
decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
|
198
|
+
@conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
|
199
|
+
expect( @conn.get_copy_data ).to eq( [1, 2, '3'] )
|
200
|
+
@conn.get_copy_data
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it "will deny copy queries with different column count" do
|
205
|
+
[[2, 2], [2, 3], [3, 2]].each do |cols1, cols2|
|
206
|
+
tm1 = PG::TypeMapByColumn.new( [textdec_int, nil, nil][0, cols1] )
|
207
|
+
tm2 = PG::TypeMapByColumn.new( [nil, textdec_int, nil][0, cols2] ).with_default_type_map( tm1 )
|
208
|
+
decoder = PG::TextDecoder::CopyRow.new(type_map: tm2)
|
209
|
+
@conn.copy_data("COPY (SELECT 1, 2, 3) TO STDOUT", decoder) do
|
210
|
+
expect{ @conn.get_copy_data }.to raise_error(ArgumentError, /number of copy fields/)
|
211
|
+
@conn.get_copy_data
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
#
|
217
|
+
# Decoding Examples text format
|
218
|
+
#
|
219
|
+
|
220
|
+
it "should allow mixed type conversions" do
|
221
|
+
res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, '2013-06-30'::DATE, 3" )
|
222
|
+
res.type_map = PG::TypeMapByColumn.new( [textdec_int, textdec_string, textdec_float, pass_through_type, nil] )
|
223
|
+
expect( res.values ).to eq( [[1, 'a', 2.0, ['2013-06-30', 0, 3], '3' ]] )
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# -*- 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, #{@conn.internal_encoding.inspect}]"]])
|
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
|
+
# -*- 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
|