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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1573 -2
  5. data/History.rdoc +3 -11
  6. data/Manifest.txt +24 -0
  7. data/README.rdoc +51 -4
  8. data/Rakefile +20 -14
  9. data/Rakefile.cross +39 -32
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +75 -21
  12. data/ext/pg.h +194 -6
  13. data/ext/pg_binary_decoder.c +160 -0
  14. data/ext/pg_binary_encoder.c +160 -0
  15. data/ext/pg_coder.c +454 -0
  16. data/ext/pg_connection.c +815 -518
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +258 -103
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +608 -0
  21. data/ext/pg_type_map.c +113 -0
  22. data/ext/pg_type_map_all_strings.c +113 -0
  23. data/ext/pg_type_map_by_column.c +254 -0
  24. data/ext/pg_type_map_by_mri_type.c +266 -0
  25. data/ext/pg_type_map_by_oid.c +341 -0
  26. data/ext/util.c +121 -0
  27. data/ext/util.h +63 -0
  28. data/lib/pg.rb +11 -1
  29. data/lib/pg/basic_type_mapping.rb +377 -0
  30. data/lib/pg/coder.rb +74 -0
  31. data/lib/pg/connection.rb +38 -5
  32. data/lib/pg/result.rb +13 -3
  33. data/lib/pg/text_decoder.rb +42 -0
  34. data/lib/pg/text_encoder.rb +27 -0
  35. data/lib/pg/type_map_by_column.rb +15 -0
  36. data/spec/helpers.rb +9 -1
  37. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  38. data/spec/pg/connection_spec.rb +232 -13
  39. data/spec/pg/result_spec.rb +52 -0
  40. data/spec/pg/type_map_by_column_spec.rb +135 -0
  41. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  42. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  43. data/spec/pg/type_map_spec.rb +39 -0
  44. data/spec/pg/type_spec.rb +620 -0
  45. metadata +40 -4
  46. 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
- def conninfo_hash
179
- return self.conninfo.each_with_object({}) do |info, hash|
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
- ### Returns all tuples as an array of arrays
9
- def values
10
- return enum_for(:each_row).to_a
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