pg 0.17.1-x64-mingw32 → 0.18.0.pre20141017160319-x64-mingw32

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 (50) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/ChangeLog +1885 -169
  5. data/History.rdoc +6 -0
  6. data/Manifest.txt +25 -1
  7. data/README.rdoc +47 -0
  8. data/Rakefile +21 -12
  9. data/Rakefile.cross +39 -33
  10. data/ext/extconf.rb +27 -26
  11. data/ext/pg.c +73 -19
  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 +473 -0
  16. data/ext/pg_connection.c +872 -534
  17. data/ext/pg_copy_coder.c +557 -0
  18. data/ext/pg_result.c +266 -111
  19. data/ext/pg_text_decoder.c +424 -0
  20. data/ext/pg_text_encoder.c +631 -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 +149 -0
  27. data/ext/util.h +65 -0
  28. data/lib/2.0/pg_ext.so +0 -0
  29. data/lib/2.1/pg_ext.so +0 -0
  30. data/lib/pg.rb +11 -1
  31. data/lib/pg/basic_type_mapping.rb +377 -0
  32. data/lib/pg/coder.rb +74 -0
  33. data/lib/pg/connection.rb +43 -1
  34. data/lib/pg/result.rb +13 -3
  35. data/lib/pg/text_decoder.rb +42 -0
  36. data/lib/pg/text_encoder.rb +27 -0
  37. data/lib/pg/type_map_by_column.rb +15 -0
  38. data/lib/x64-mingw32/libpq.dll +0 -0
  39. data/spec/{lib/helpers.rb → helpers.rb} +95 -35
  40. data/spec/pg/basic_type_mapping_spec.rb +251 -0
  41. data/spec/pg/connection_spec.rb +416 -214
  42. data/spec/pg/result_spec.rb +146 -116
  43. data/spec/pg/type_map_by_column_spec.rb +135 -0
  44. data/spec/pg/type_map_by_mri_type_spec.rb +122 -0
  45. data/spec/pg/type_map_by_oid_spec.rb +133 -0
  46. data/spec/pg/type_map_spec.rb +39 -0
  47. data/spec/pg/type_spec.rb +649 -0
  48. data/spec/pg_spec.rb +10 -18
  49. metadata +129 -50
  50. metadata.gz.sig +0 -0
@@ -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
+
@@ -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,6 +184,36 @@ class PG::Connection
172
184
  return self.class.conndefaults
173
185
  end
174
186
 
187
+ ### Return the Postgres connection defaults structure as a Hash keyed by option
188
+ ### keyword (as a Symbol).
189
+ ###
190
+ ### See also #conndefaults
191
+ def self.conndefaults_hash
192
+ return self.conndefaults.each_with_object({}) do |info, hash|
193
+ hash[ info[:keyword].to_sym ] = info[:val]
194
+ end
195
+ end
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
+
175
217
  end # class PG::Connection
176
218
 
177
219
  # Backward-compatible alias
@@ -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
Binary file
@@ -9,6 +9,38 @@ TEST_DIRECTORY = Pathname.getwd + "tmp_test_specs"
9
9
 
10
10
  module PG::TestingHelpers
11
11
 
12
+ ### Automatically set up the database when it's used, and wrap a transaction around
13
+ ### examples that don't disable it.
14
+ def self::included( mod )
15
+ super
16
+
17
+ if mod.respond_to?( :around )
18
+
19
+ mod.before( :all ) { @conn = setup_testing_db(described_class ? described_class.name : mod.description) }
20
+
21
+ mod.around( :each ) do |example|
22
+ begin
23
+ @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
24
+ if PG.respond_to?( :library_version )
25
+ desc = example.source_location.join(':')
26
+ @conn.exec_params %Q{SET application_name TO '%s'} %
27
+ [@conn.escape_string(desc.slice(-60))]
28
+ end
29
+ example.run
30
+ ensure
31
+ @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
32
+ end
33
+ end
34
+
35
+ mod.after( :all ) { teardown_testing_db(@conn) }
36
+ end
37
+
38
+ end
39
+
40
+
41
+ #
42
+ # Examples
43
+ #
12
44
 
13
45
  # Set some ANSI escape code constants (Shamelessly stolen from Perl's
14
46
  # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
@@ -121,28 +153,9 @@ module PG::TestingHelpers
121
153
  end
122
154
 
123
155
  # Eliminate the noise of creating/tearing down the database by
124
- # redirecting STDERR/STDOUT to a logfile if the Ruby interpreter
125
- # supports fork()
156
+ # redirecting STDERR/STDOUT to a logfile
126
157
  logfh = File.open( logpath, File::WRONLY|File::CREAT|File::APPEND )
127
- begin
128
- pid = fork
129
- rescue NotImplementedError
130
- logfh.close
131
- system( *cmd )
132
- else
133
- if pid
134
- logfh.close
135
- else
136
- $stdout.reopen( logfh )
137
- $stderr.reopen( $stdout )
138
- $stderr.puts( ">>> " + cmd.shelljoin )
139
- exec( *cmd )
140
- $stderr.puts "After the exec()?!??!"
141
- exit!
142
- end
143
-
144
- Process.wait( pid )
145
- end
158
+ system( *cmd, [STDOUT, STDERR] => logfh )
146
159
 
147
160
  raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success?
148
161
  end
@@ -248,29 +261,76 @@ module PG::TestingHelpers
248
261
  end
249
262
  end
250
263
 
251
- def connection_string_should_contain_application_name(conn_args, app_name)
252
- conn_name = conn_args.match(/application_name='(.*)'/)[1]
253
- conn_name.should include(app_name[0..10])
254
- conn_name.should include(app_name[-10..-1])
255
- conn_name.length.should <= 64
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
+
273
+ # A matcher for checking the status of a PG::Connection to ensure it's still
274
+ # usable.
275
+ class ConnStillUsableMatcher
276
+
277
+ def initialize
278
+ @conn = nil
279
+ @problem = nil
280
+ end
281
+
282
+ def matches?( conn )
283
+ @conn = conn
284
+ @problem = self.check_for_problems
285
+ return @problem.nil?
286
+ end
287
+
288
+ def check_for_problems
289
+ return "is finished" if @conn.finished?
290
+ return "has bad status" unless @conn.status == PG::CONNECTION_OK
291
+ return "has bad transaction status (%d)" % [ @conn.transaction_status ] unless
292
+ @conn.transaction_status.between?( PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS )
293
+ return "is not usable." unless self.can_exec_query?
294
+ return nil
295
+ end
296
+
297
+ def can_exec_query?
298
+ @conn.send_query( "VALUES (1)" )
299
+ @conn.get_last_result.values == [["1"]]
300
+ end
301
+
302
+ def failure_message
303
+ return "expected %p to be usable, but it %s" % [ @conn, @problem ]
304
+ end
305
+
306
+ def failure_message_when_negated
307
+ "expected %p not to be usable, but it still is" % [ @conn ]
308
+ end
309
+
256
310
  end
257
311
 
258
- # Ensure the connection is in a clean execution status.
259
- def verify_clean_exec_status
260
- @conn.send_query( "VALUES (1)" )
261
- @conn.get_last_result.values.should == [["1"]]
312
+
313
+ ### Return a ConnStillUsableMatcher to be used like:
314
+ ###
315
+ ### expect( pg_conn ).to still_be_usable
316
+ ###
317
+ def still_be_usable
318
+ return ConnStillUsableMatcher.new
262
319
  end
320
+
263
321
  end
264
322
 
265
323
 
266
324
  RSpec.configure do |config|
267
- ruby_version_vec = RUBY_VERSION.split('.').map {|c| c.to_i }.pack( "N*" )
268
-
269
325
  config.include( PG::TestingHelpers )
270
- config.treat_symbols_as_metadata_keys_with_true_values = true
271
326
 
272
- config.mock_with :rspec
273
- config.filter_run_excluding :ruby_19 if ruby_version_vec <= [1,9,1].pack( "N*" )
327
+ config.run_all_when_everything_filtered = true
328
+ config.filter_run :focus
329
+ config.order = 'random'
330
+ config.mock_with( :rspec ) do |mock|
331
+ mock.syntax = :expect
332
+ end
333
+
274
334
  if RUBY_PLATFORM =~ /mingw|mswin/
275
335
  config.filter_run_excluding :unix
276
336
  else
@@ -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