pg 0.18.0.pre20140820094244 → 0.18.0.pre20141017155815
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +1573 -2
- data/History.rdoc +3 -11
- data/Manifest.txt +24 -0
- data/README.rdoc +51 -4
- data/Rakefile +20 -14
- data/Rakefile.cross +39 -32
- data/ext/extconf.rb +27 -26
- data/ext/pg.c +75 -21
- data/ext/pg.h +194 -6
- data/ext/pg_binary_decoder.c +160 -0
- data/ext/pg_binary_encoder.c +160 -0
- data/ext/pg_coder.c +454 -0
- data/ext/pg_connection.c +815 -518
- data/ext/pg_copy_coder.c +557 -0
- data/ext/pg_result.c +258 -103
- data/ext/pg_text_decoder.c +424 -0
- data/ext/pg_text_encoder.c +608 -0
- data/ext/pg_type_map.c +113 -0
- data/ext/pg_type_map_all_strings.c +113 -0
- data/ext/pg_type_map_by_column.c +254 -0
- data/ext/pg_type_map_by_mri_type.c +266 -0
- data/ext/pg_type_map_by_oid.c +341 -0
- data/ext/util.c +121 -0
- data/ext/util.h +63 -0
- data/lib/pg.rb +11 -1
- data/lib/pg/basic_type_mapping.rb +377 -0
- data/lib/pg/coder.rb +74 -0
- data/lib/pg/connection.rb +38 -5
- data/lib/pg/result.rb +13 -3
- data/lib/pg/text_decoder.rb +42 -0
- data/lib/pg/text_encoder.rb +27 -0
- data/lib/pg/type_map_by_column.rb +15 -0
- data/spec/helpers.rb +9 -1
- data/spec/pg/basic_type_mapping_spec.rb +251 -0
- data/spec/pg/connection_spec.rb +232 -13
- data/spec/pg/result_spec.rb +52 -0
- data/spec/pg/type_map_by_column_spec.rb +135 -0
- data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
- data/spec/pg/type_map_by_oid_spec.rb +133 -0
- data/spec/pg/type_map_spec.rb +39 -0
- data/spec/pg/type_spec.rb +620 -0
- metadata +40 -4
- metadata.gz.sig +0 -0
data/lib/pg/coder.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module PG
|
4
|
+
|
5
|
+
class Coder
|
6
|
+
# Create a new coder object based on the attribute Hash.
|
7
|
+
def initialize(params={})
|
8
|
+
params.each do |key, val|
|
9
|
+
send("#{key}=", val)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def dup
|
14
|
+
self.class.new(to_h)
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns coder attributes as Hash.
|
18
|
+
def to_h
|
19
|
+
{
|
20
|
+
oid: oid,
|
21
|
+
format: format,
|
22
|
+
name: name,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(v)
|
27
|
+
self.class == v.class && to_h == v.to_h
|
28
|
+
end
|
29
|
+
|
30
|
+
def marshal_dump
|
31
|
+
Marshal.dump(to_h)
|
32
|
+
end
|
33
|
+
|
34
|
+
def marshal_load(str)
|
35
|
+
initialize Marshal.load(str)
|
36
|
+
end
|
37
|
+
|
38
|
+
def inspect
|
39
|
+
str = self.to_s
|
40
|
+
oid_str = " oid=#{oid}" unless oid==0
|
41
|
+
format_str = " format=#{format}" unless format==0
|
42
|
+
name_str = " #{name.inspect}" if name
|
43
|
+
str[-1,0] = "#{name_str} #{oid_str}#{format_str}"
|
44
|
+
str
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class CompositeCoder < Coder
|
49
|
+
def to_h
|
50
|
+
super.merge!({
|
51
|
+
elements_type: elements_type,
|
52
|
+
needs_quotation: needs_quotation?,
|
53
|
+
delimiter: delimiter,
|
54
|
+
})
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
str = super
|
59
|
+
str[-1,0] = " elements_type=#{elements_type.inspect} #{needs_quotation? ? 'needs' : 'no'} quotation"
|
60
|
+
str
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class CopyCoder < Coder
|
65
|
+
def to_h
|
66
|
+
super.merge!({
|
67
|
+
type_map: type_map,
|
68
|
+
delimiter: delimiter,
|
69
|
+
null_string: null_string,
|
70
|
+
})
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end # module PG
|
74
|
+
|
data/lib/pg/connection.rb
CHANGED
@@ -116,12 +116,16 @@ class PG::Connection
|
|
116
116
|
# This prints all rows of +my_table+ to stdout:
|
117
117
|
# "some,csv,data,to,copy\n"
|
118
118
|
# "more,csv,data,to,copy\n"
|
119
|
-
def copy_data( sql )
|
119
|
+
def copy_data( sql, coder=nil )
|
120
120
|
res = exec( sql )
|
121
121
|
|
122
122
|
case res.result_status
|
123
123
|
when PGRES_COPY_IN
|
124
124
|
begin
|
125
|
+
if coder
|
126
|
+
old_coder = self.encoder_for_put_copy_data
|
127
|
+
self.encoder_for_put_copy_data = coder
|
128
|
+
end
|
125
129
|
yield res
|
126
130
|
rescue Exception => err
|
127
131
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
@@ -131,10 +135,16 @@ class PG::Connection
|
|
131
135
|
else
|
132
136
|
put_copy_end
|
133
137
|
get_last_result
|
138
|
+
ensure
|
139
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
134
140
|
end
|
135
141
|
|
136
142
|
when PGRES_COPY_OUT
|
137
143
|
begin
|
144
|
+
if coder
|
145
|
+
old_coder = self.decoder_for_get_copy_data
|
146
|
+
self.decoder_for_get_copy_data = coder
|
147
|
+
end
|
138
148
|
yield res
|
139
149
|
rescue Exception => err
|
140
150
|
cancel
|
@@ -153,6 +163,8 @@ class PG::Connection
|
|
153
163
|
raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
|
154
164
|
end
|
155
165
|
res
|
166
|
+
ensure
|
167
|
+
self.decoder_for_get_copy_data = old_coder if coder
|
156
168
|
end
|
157
169
|
|
158
170
|
else
|
@@ -172,15 +184,36 @@ class PG::Connection
|
|
172
184
|
return self.class.conndefaults
|
173
185
|
end
|
174
186
|
|
175
|
-
|
176
|
-
### Return the Postgres connection info structure as a Hash keyed by option
|
187
|
+
### Return the Postgres connection defaults structure as a Hash keyed by option
|
177
188
|
### keyword (as a Symbol).
|
178
|
-
|
179
|
-
|
189
|
+
###
|
190
|
+
### See also #conndefaults
|
191
|
+
def self.conndefaults_hash
|
192
|
+
return self.conndefaults.each_with_object({}) do |info, hash|
|
180
193
|
hash[ info[:keyword].to_sym ] = info[:val]
|
181
194
|
end
|
182
195
|
end
|
183
196
|
|
197
|
+
### Returns a Hash with connection defaults. See ::conndefaults_hash
|
198
|
+
### for details.
|
199
|
+
def conndefaults_hash
|
200
|
+
return self.class.conndefaults_hash
|
201
|
+
end
|
202
|
+
|
203
|
+
# Method 'conninfo' was introduced in PostgreSQL 9.3.
|
204
|
+
if self.instance_methods.find{|m| m.to_sym == :conninfo }
|
205
|
+
|
206
|
+
### Return the Postgres connection info structure as a Hash keyed by option
|
207
|
+
### keyword (as a Symbol).
|
208
|
+
###
|
209
|
+
### See also #conninfo
|
210
|
+
def conninfo_hash
|
211
|
+
return self.conninfo.each_with_object({}) do |info, hash|
|
212
|
+
hash[ info[:keyword].to_sym ] = info[:val]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
184
217
|
end # class PG::Connection
|
185
218
|
|
186
219
|
# Backward-compatible alias
|
data/lib/pg/result.rb
CHANGED
@@ -5,11 +5,21 @@ require 'pg' unless defined?( PG )
|
|
5
5
|
|
6
6
|
class PG::Result
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
# Apply a type map for all value retrieving methods.
|
9
|
+
#
|
10
|
+
# +type_map+: a PG::TypeMap instance.
|
11
|
+
#
|
12
|
+
# See PG::BasicTypeMapForResults
|
13
|
+
def map_types!(type_map)
|
14
|
+
self.type_map = type_map
|
15
|
+
self
|
11
16
|
end
|
12
17
|
|
18
|
+
def inspect
|
19
|
+
str = self.to_s
|
20
|
+
str[-1,0] = " status=#{res_status(result_status)} ntuples=#{ntuples} nfields=#{nfields} cmd_tuples=#{cmd_tuples}"
|
21
|
+
str
|
22
|
+
end
|
13
23
|
end # class PG::Result
|
14
24
|
|
15
25
|
# Backward-compatible alias
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module PG
|
4
|
+
module TextDecoder
|
5
|
+
class Date < SimpleDecoder
|
6
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
7
|
+
|
8
|
+
def decode(string, tuple=nil, field=nil)
|
9
|
+
if string =~ ISO_DATE
|
10
|
+
Time.new $1.to_i, $2.to_i, $3.to_i
|
11
|
+
else
|
12
|
+
string
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TimestampWithoutTimeZone < SimpleDecoder
|
18
|
+
ISO_DATETIME_WITHOUT_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
19
|
+
|
20
|
+
def decode(string, tuple=nil, field=nil)
|
21
|
+
if string =~ ISO_DATETIME_WITHOUT_TIMEZONE
|
22
|
+
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r
|
23
|
+
else
|
24
|
+
string
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class TimestampWithTimeZone < SimpleDecoder
|
30
|
+
ISO_DATETIME_WITH_TIMEZONE = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?([-\+]\d\d)\z/
|
31
|
+
|
32
|
+
def decode(string, tuple=nil, field=nil)
|
33
|
+
if string =~ ISO_DATETIME_WITH_TIMEZONE
|
34
|
+
Time.new $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, "#{$6}#{$7}".to_r, "#{$8}:00"
|
35
|
+
else
|
36
|
+
string
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end # module PG
|
42
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module PG
|
4
|
+
module TextEncoder
|
5
|
+
class Date < SimpleEncoder
|
6
|
+
STRFTIME_ISO_DATE = "%Y-%m-%d".freeze
|
7
|
+
def encode(value)
|
8
|
+
value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATE) : value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TimestampWithoutTimeZone < SimpleEncoder
|
13
|
+
STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N".freeze
|
14
|
+
def encode(value)
|
15
|
+
value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITHOUT_TIMEZONE) : value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class TimestampWithTimeZone < SimpleEncoder
|
20
|
+
STRFTIME_ISO_DATETIME_WITH_TIMEZONE = "%Y-%m-%d %H:%M:%S.%N %:z".freeze
|
21
|
+
def encode(value)
|
22
|
+
value.respond_to?(:strftime) ? value.strftime(STRFTIME_ISO_DATETIME_WITH_TIMEZONE) : value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end # module PG
|
27
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pg' unless defined?( PG )
|
4
|
+
|
5
|
+
class PG::TypeMapByColumn
|
6
|
+
# Returns the type oids of the assigned coders.
|
7
|
+
def oids
|
8
|
+
coders.map{|c| c.oid if c }
|
9
|
+
end
|
10
|
+
|
11
|
+
def inspect
|
12
|
+
type_strings = coders.map{|c| c ? "#{c.name}:#{c.format}" : 'nil' }
|
13
|
+
"#<#{self.class} #{type_strings.join(' ')}>"
|
14
|
+
end
|
15
|
+
end
|
data/spec/helpers.rb
CHANGED
@@ -16,7 +16,7 @@ module PG::TestingHelpers
|
|
16
16
|
|
17
17
|
if mod.respond_to?( :around )
|
18
18
|
|
19
|
-
mod.before( :all ) { @conn = setup_testing_db(described_class.name) }
|
19
|
+
mod.before( :all ) { @conn = setup_testing_db(described_class ? described_class.name : mod.description) }
|
20
20
|
|
21
21
|
mod.around( :each ) do |example|
|
22
22
|
begin
|
@@ -262,6 +262,14 @@ module PG::TestingHelpers
|
|
262
262
|
end
|
263
263
|
|
264
264
|
|
265
|
+
# Retrieve the names of the column types of a given result set.
|
266
|
+
def result_typenames(res)
|
267
|
+
@conn.exec( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","),
|
268
|
+
res.nfields.times.map{|i| [res.ftype(i), res.fmod(i)] }.flatten ).
|
269
|
+
values[0]
|
270
|
+
end
|
271
|
+
|
272
|
+
|
265
273
|
# A matcher for checking the status of a PG::Connection to ensure it's still
|
266
274
|
# usable.
|
267
275
|
class ConnStillUsableMatcher
|
@@ -0,0 +1,251 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../helpers'
|
5
|
+
|
6
|
+
require 'pg'
|
7
|
+
|
8
|
+
describe 'Basic type mapping' do
|
9
|
+
|
10
|
+
describe PG::BasicTypeMapForQueries do
|
11
|
+
let!(:basic_type_mapping) do
|
12
|
+
PG::BasicTypeMapForQueries.new @conn
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Encoding Examples
|
17
|
+
#
|
18
|
+
|
19
|
+
it "should do basic param encoding", :ruby_19 do
|
20
|
+
res = @conn.exec_params( "SELECT $1::int8,$2::float,$3,$4::TEXT",
|
21
|
+
[1, 2.1, true, "b"], nil, basic_type_mapping )
|
22
|
+
|
23
|
+
expect( res.values ).to eq( [
|
24
|
+
[ "1", "2.1", "t", "b" ],
|
25
|
+
] )
|
26
|
+
|
27
|
+
expect( result_typenames(res) ).to eq( ['bigint', 'double precision', 'boolean', 'text'] )
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should do array param encoding" do
|
31
|
+
res = @conn.exec_params( "SELECT $1,$2,$3,$4", [
|
32
|
+
[1, 2, 3], [[1, 2], [3, nil]],
|
33
|
+
[1.11, 2.21],
|
34
|
+
['/,"'.gsub("/", "\\"), nil, 'abcäöü'],
|
35
|
+
], nil, basic_type_mapping )
|
36
|
+
|
37
|
+
expect( res.values ).to eq( [[
|
38
|
+
'{1,2,3}', '{{1,2},{3,NULL}}',
|
39
|
+
'{1.11,2.21}',
|
40
|
+
'{"//,/"",NULL,abcäöü}'.gsub("/", "\\"),
|
41
|
+
]] )
|
42
|
+
|
43
|
+
expect( result_typenames(res) ).to eq( ['bigint[]', 'bigint[]', 'double precision[]', 'text[]'] )
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
describe PG::BasicTypeMapForResults do
|
50
|
+
let!(:basic_type_mapping) do
|
51
|
+
PG::BasicTypeMapForResults.new @conn
|
52
|
+
end
|
53
|
+
|
54
|
+
#
|
55
|
+
# Decoding Examples
|
56
|
+
#
|
57
|
+
|
58
|
+
it "should do OID based type conversions", :ruby_19 do
|
59
|
+
res = @conn.exec( "SELECT 1, 'a', 2.0::FLOAT, TRUE, '2013-06-30'::DATE, generate_series(4,5)" )
|
60
|
+
expect( res.map_types!(basic_type_mapping).values ).to eq( [
|
61
|
+
[ 1, 'a', 2.0, true, Time.new(2013,6,30), 4 ],
|
62
|
+
[ 1, 'a', 2.0, true, Time.new(2013,6,30), 5 ],
|
63
|
+
] )
|
64
|
+
end
|
65
|
+
|
66
|
+
#
|
67
|
+
# Decoding Examples text+binary format converters
|
68
|
+
#
|
69
|
+
|
70
|
+
describe "connection wide type mapping" do
|
71
|
+
before :each do
|
72
|
+
@conn.type_map_for_results = basic_type_mapping
|
73
|
+
end
|
74
|
+
|
75
|
+
after :each do
|
76
|
+
@conn.type_map_for_results = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should do boolean type conversions" do
|
80
|
+
[1, 0].each do |format|
|
81
|
+
res = @conn.exec( "SELECT true::BOOLEAN, false::BOOLEAN, NULL::BOOLEAN", [], format )
|
82
|
+
expect( res.values ).to eq( [[true, false, nil]] )
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should do binary type conversions" do
|
87
|
+
[1, 0].each do |format|
|
88
|
+
res = @conn.exec( "SELECT E'\\\\000\\\\377'::BYTEA", [], format )
|
89
|
+
expect( res.values ).to eq( [[["00ff"].pack("H*")]] )
|
90
|
+
expect( res.values[0][0].encoding ).to eq( Encoding::ASCII_8BIT ) if Object.const_defined? :Encoding
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should do integer type conversions" do
|
95
|
+
[1, 0].each do |format|
|
96
|
+
res = @conn.exec( "SELECT -8999::INT2, -899999999::INT4, -8999999999999999999::INT8", [], format )
|
97
|
+
expect( res.values ).to eq( [[-8999, -899999999, -8999999999999999999]] )
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should do string type conversions" do
|
102
|
+
@conn.internal_encoding = 'utf-8' if Object.const_defined? :Encoding
|
103
|
+
[1, 0].each do |format|
|
104
|
+
res = @conn.exec( "SELECT 'abcäöü'::TEXT", [], format )
|
105
|
+
expect( res.values ).to eq( [['abcäöü']] )
|
106
|
+
expect( res.values[0][0].encoding ).to eq( Encoding::UTF_8 ) if Object.const_defined? :Encoding
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should do float type conversions" do
|
111
|
+
[1, 0].each do |format|
|
112
|
+
res = @conn.exec( "SELECT -8.999e3::FLOAT4,
|
113
|
+
8.999e10::FLOAT4,
|
114
|
+
-8999999999e-99::FLOAT8,
|
115
|
+
NULL::FLOAT4,
|
116
|
+
'NaN'::FLOAT4,
|
117
|
+
'Infinity'::FLOAT4,
|
118
|
+
'-Infinity'::FLOAT4
|
119
|
+
", [], format )
|
120
|
+
expect( res.getvalue(0,0) ).to be_within(1e-2).of(-8.999e3)
|
121
|
+
expect( res.getvalue(0,1) ).to be_within(1e5).of(8.999e10)
|
122
|
+
expect( res.getvalue(0,2) ).to be_within(1e-109).of(-8999999999e-99)
|
123
|
+
expect( res.getvalue(0,3) ).to be_nil
|
124
|
+
expect( res.getvalue(0,4) ).to be_nan
|
125
|
+
expect( res.getvalue(0,5) ).to eq( Float::INFINITY )
|
126
|
+
expect( res.getvalue(0,6) ).to eq( -Float::INFINITY )
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should do datetime without time zone type conversions" do
|
131
|
+
[0].each do |format|
|
132
|
+
res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITHOUT TIME ZONE),
|
133
|
+
CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITHOUT TIME ZONE),
|
134
|
+
CAST('infinity' AS TIMESTAMP WITHOUT TIME ZONE),
|
135
|
+
CAST('-infinity' AS TIMESTAMP WITHOUT TIME ZONE)", [], format )
|
136
|
+
expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59) )
|
137
|
+
expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123))
|
138
|
+
expect( res.getvalue(0,2) ).to eq( 'infinity' )
|
139
|
+
expect( res.getvalue(0,3) ).to eq( '-infinity' )
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should do datetime with time zone type conversions" do
|
144
|
+
[0].each do |format|
|
145
|
+
res = @conn.exec( "SELECT CAST('2013-12-31 23:58:59+02' AS TIMESTAMP WITH TIME ZONE),
|
146
|
+
CAST('1913-12-31 23:58:59.123-03' AS TIMESTAMP WITH TIME ZONE),
|
147
|
+
CAST('infinity' AS TIMESTAMP WITH TIME ZONE),
|
148
|
+
CAST('-infinity' AS TIMESTAMP WITH TIME ZONE)", [], format )
|
149
|
+
expect( res.getvalue(0,0) ).to eq( Time.new(2013, 12, 31, 23, 58, 59, "+02:00") )
|
150
|
+
expect( res.getvalue(0,1) ).to be_within(1e-3).of(Time.new(1913, 12, 31, 23, 58, 59.123, "-03:00"))
|
151
|
+
expect( res.getvalue(0,2) ).to eq( 'infinity' )
|
152
|
+
expect( res.getvalue(0,3) ).to eq( '-infinity' )
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should do date type conversions" do
|
157
|
+
[0].each do |format|
|
158
|
+
res = @conn.exec( "SELECT CAST('2113-12-31' AS DATE),
|
159
|
+
CAST('1913-12-31' AS DATE),
|
160
|
+
CAST('infinity' AS DATE),
|
161
|
+
CAST('-infinity' AS DATE)", [], format )
|
162
|
+
expect( res.getvalue(0,0) ).to eq( Time.new(2113, 12, 31) )
|
163
|
+
expect( res.getvalue(0,1) ).to eq( Time.new(1913, 12, 31) )
|
164
|
+
expect( res.getvalue(0,2) ).to eq( 'infinity' )
|
165
|
+
expect( res.getvalue(0,3) ).to eq( '-infinity' )
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should do array type conversions" do
|
170
|
+
[0].each do |format|
|
171
|
+
res = @conn.exec( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
|
172
|
+
CAST('{1,2,3}' AS INT4[]),
|
173
|
+
CAST('{1,2,3}' AS INT8[]),
|
174
|
+
CAST('{1,2,3}' AS TEXT[]),
|
175
|
+
CAST('{1,2,3}' AS VARCHAR[]),
|
176
|
+
CAST('{1,2,3}' AS FLOAT4[]),
|
177
|
+
CAST('{1,2,3}' AS FLOAT8[])
|
178
|
+
", [], format )
|
179
|
+
expect( res.getvalue(0,0) ).to eq( [1,2,3] )
|
180
|
+
expect( res.getvalue(0,1) ).to eq( [[1,2],[3,4]] )
|
181
|
+
expect( res.getvalue(0,2) ).to eq( [1,2,3] )
|
182
|
+
expect( res.getvalue(0,3) ).to eq( [1,2,3] )
|
183
|
+
expect( res.getvalue(0,4) ).to eq( ['1','2','3'] )
|
184
|
+
expect( res.getvalue(0,5) ).to eq( ['1','2','3'] )
|
185
|
+
expect( res.getvalue(0,6) ).to eq( [1.0,2.0,3.0] )
|
186
|
+
expect( res.getvalue(0,7) ).to eq( [1.0,2.0,3.0] )
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "with usage of result oids for copy decoder selection" do
|
192
|
+
it "can type cast #copy_data output with explicit decoder" do
|
193
|
+
@conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
|
194
|
+
@conn.exec( "INSERT INTO copytable VALUES ('a', 123, '{5,4,3}'), ('b', 234, '{2,3}')" )
|
195
|
+
|
196
|
+
# Retrieve table OIDs per empty result.
|
197
|
+
res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
|
198
|
+
tm = basic_type_mapping.fit_to_result( res, false )
|
199
|
+
row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
|
200
|
+
|
201
|
+
rows = []
|
202
|
+
@conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
|
203
|
+
while row=@conn.get_copy_data
|
204
|
+
rows << row
|
205
|
+
end
|
206
|
+
end
|
207
|
+
expect( rows ).to eq( [['a', 123, [5,4,3]], ['b', 234, [2,3]]] )
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
describe PG::BasicTypeMapBasedOnResult do
|
214
|
+
let!(:basic_type_mapping) do
|
215
|
+
PG::BasicTypeMapBasedOnResult.new @conn
|
216
|
+
end
|
217
|
+
|
218
|
+
context "with usage of result oids for bind params encoder selection" do
|
219
|
+
it "can type cast query params" do
|
220
|
+
@conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
|
221
|
+
|
222
|
+
# Retrieve table OIDs per empty result.
|
223
|
+
res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
|
224
|
+
tm = basic_type_mapping.fit_to_result( res, false )
|
225
|
+
|
226
|
+
@conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['a', 123, [5,4,3]], 0, tm )
|
227
|
+
@conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['b', 234, [2,3]], 0, tm )
|
228
|
+
res = @conn.exec( "SELECT * FROM copytable" )
|
229
|
+
expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context "with usage of result oids for copy encoder selection" do
|
234
|
+
it "can type cast #copy_data input with explicit encoder" do
|
235
|
+
@conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
|
236
|
+
|
237
|
+
# Retrieve table OIDs per empty result set.
|
238
|
+
res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
|
239
|
+
tm = basic_type_mapping.fit_to_result( res, false )
|
240
|
+
row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
|
241
|
+
|
242
|
+
@conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
|
243
|
+
@conn.put_copy_data ['a', 123, [5,4,3]]
|
244
|
+
@conn.put_copy_data ['b', 234, [2,3]]
|
245
|
+
end
|
246
|
+
res = @conn.exec( "SELECT * FROM copytable" )
|
247
|
+
expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|