pg 1.0.0 → 1.5.9

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 (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +20 -0
  4. data/History.md +932 -0
  5. data/Manifest.txt +8 -3
  6. data/README-Windows.rdoc +4 -4
  7. data/README.ja.md +300 -0
  8. data/README.md +286 -0
  9. data/Rakefile +41 -138
  10. data/Rakefile.cross +71 -66
  11. data/certs/ged.pem +24 -0
  12. data/certs/kanis@comcard.de.pem +20 -0
  13. data/certs/larskanis-2022.pem +26 -0
  14. data/certs/larskanis-2023.pem +24 -0
  15. data/certs/larskanis-2024.pem +24 -0
  16. data/ext/errorcodes.def +84 -5
  17. data/ext/errorcodes.rb +1 -1
  18. data/ext/errorcodes.txt +23 -6
  19. data/ext/extconf.rb +109 -25
  20. data/ext/gvl_wrappers.c +4 -0
  21. data/ext/gvl_wrappers.h +23 -0
  22. data/ext/pg.c +213 -155
  23. data/ext/pg.h +89 -23
  24. data/ext/pg_binary_decoder.c +164 -16
  25. data/ext/pg_binary_encoder.c +238 -13
  26. data/ext/pg_coder.c +159 -35
  27. data/ext/pg_connection.c +1584 -967
  28. data/ext/pg_copy_coder.c +373 -43
  29. data/ext/pg_errors.c +1 -1
  30. data/ext/pg_record_coder.c +522 -0
  31. data/ext/pg_result.c +710 -217
  32. data/ext/pg_text_decoder.c +630 -43
  33. data/ext/pg_text_encoder.c +222 -72
  34. data/ext/pg_tuple.c +572 -0
  35. data/ext/pg_type_map.c +45 -11
  36. data/ext/pg_type_map_all_strings.c +21 -7
  37. data/ext/pg_type_map_by_class.c +59 -27
  38. data/ext/pg_type_map_by_column.c +80 -37
  39. data/ext/pg_type_map_by_mri_type.c +49 -20
  40. data/ext/pg_type_map_by_oid.c +62 -29
  41. data/ext/pg_type_map_in_ruby.c +56 -22
  42. data/ext/{util.c → pg_util.c} +12 -12
  43. data/ext/{util.h → pg_util.h} +2 -2
  44. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  45. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  46. data/lib/pg/basic_type_map_for_results.rb +104 -0
  47. data/lib/pg/basic_type_registry.rb +311 -0
  48. data/lib/pg/binary_decoder/date.rb +9 -0
  49. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  50. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  51. data/lib/pg/coder.rb +36 -13
  52. data/lib/pg/connection.rb +769 -70
  53. data/lib/pg/exceptions.rb +22 -2
  54. data/lib/pg/result.rb +14 -2
  55. data/lib/pg/text_decoder/date.rb +21 -0
  56. data/lib/pg/text_decoder/inet.rb +9 -0
  57. data/lib/pg/text_decoder/json.rb +17 -0
  58. data/lib/pg/text_decoder/numeric.rb +9 -0
  59. data/lib/pg/text_decoder/timestamp.rb +30 -0
  60. data/lib/pg/text_encoder/date.rb +13 -0
  61. data/lib/pg/text_encoder/inet.rb +31 -0
  62. data/lib/pg/text_encoder/json.rb +17 -0
  63. data/lib/pg/text_encoder/numeric.rb +9 -0
  64. data/lib/pg/text_encoder/timestamp.rb +24 -0
  65. data/lib/pg/tuple.rb +30 -0
  66. data/lib/pg/type_map_by_column.rb +3 -2
  67. data/lib/pg/version.rb +4 -0
  68. data/lib/pg.rb +106 -39
  69. data/misc/openssl-pg-segfault.rb +31 -0
  70. data/misc/postgres/History.txt +9 -0
  71. data/misc/postgres/Manifest.txt +5 -0
  72. data/misc/postgres/README.txt +21 -0
  73. data/misc/postgres/Rakefile +21 -0
  74. data/misc/postgres/lib/postgres.rb +16 -0
  75. data/misc/ruby-pg/History.txt +9 -0
  76. data/misc/ruby-pg/Manifest.txt +5 -0
  77. data/misc/ruby-pg/README.txt +21 -0
  78. data/misc/ruby-pg/Rakefile +21 -0
  79. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  80. data/pg.gemspec +36 -0
  81. data/rakelib/task_extension.rb +46 -0
  82. data/sample/array_insert.rb +20 -0
  83. data/sample/async_api.rb +102 -0
  84. data/sample/async_copyto.rb +39 -0
  85. data/sample/async_mixed.rb +56 -0
  86. data/sample/check_conn.rb +21 -0
  87. data/sample/copydata.rb +71 -0
  88. data/sample/copyfrom.rb +81 -0
  89. data/sample/copyto.rb +19 -0
  90. data/sample/cursor.rb +21 -0
  91. data/sample/disk_usage_report.rb +177 -0
  92. data/sample/issue-119.rb +94 -0
  93. data/sample/losample.rb +69 -0
  94. data/sample/minimal-testcase.rb +17 -0
  95. data/sample/notify_wait.rb +72 -0
  96. data/sample/pg_statistics.rb +285 -0
  97. data/sample/replication_monitor.rb +222 -0
  98. data/sample/test_binary_values.rb +33 -0
  99. data/sample/wal_shipper.rb +434 -0
  100. data/sample/warehouse_partitions.rb +311 -0
  101. data.tar.gz.sig +0 -0
  102. metadata +138 -223
  103. metadata.gz.sig +0 -0
  104. data/.gemtest +0 -0
  105. data/ChangeLog +0 -6595
  106. data/History.rdoc +0 -422
  107. data/README.ja.rdoc +0 -14
  108. data/README.rdoc +0 -167
  109. data/lib/pg/basic_type_mapping.rb +0 -426
  110. data/lib/pg/constants.rb +0 -11
  111. data/lib/pg/text_decoder.rb +0 -51
  112. data/lib/pg/text_encoder.rb +0 -35
  113. data/spec/data/expected_trace.out +0 -26
  114. data/spec/data/random_binary_data +0 -0
  115. data/spec/helpers.rb +0 -348
  116. data/spec/pg/basic_type_mapping_spec.rb +0 -305
  117. data/spec/pg/connection_spec.rb +0 -1719
  118. data/spec/pg/result_spec.rb +0 -456
  119. data/spec/pg/type_map_by_class_spec.rb +0 -138
  120. data/spec/pg/type_map_by_column_spec.rb +0 -222
  121. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  122. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  123. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  124. data/spec/pg/type_map_spec.rb +0 -22
  125. data/spec/pg/type_spec.rb +0 -777
  126. data/spec/pg_spec.rb +0 -50
data/spec/helpers.rb DELETED
@@ -1,348 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'pathname'
4
- require 'rspec'
5
- require 'shellwords'
6
- require 'pg'
7
-
8
- DEFAULT_TEST_DIR_STR = File.join(Dir.pwd, "tmp_test_specs")
9
- TEST_DIR_STR = ENV['RUBY_PG_TEST_DIR'] || DEFAULT_TEST_DIR_STR
10
- TEST_DIRECTORY = Pathname.new(TEST_DIR_STR)
11
-
12
- module PG::TestingHelpers
13
-
14
- ### Automatically set up the database when it's used, and wrap a transaction around
15
- ### examples that don't disable it.
16
- def self::included( mod )
17
- super
18
-
19
- if mod.respond_to?( :around )
20
-
21
- mod.before( :all ) { @conn = setup_testing_db(described_class ? described_class.name : mod.description) }
22
-
23
- mod.around( :each ) do |example|
24
- begin
25
- @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction]
26
- if PG.respond_to?( :library_version )
27
- desc = example.source_location.join(':')
28
- @conn.exec_params %Q{SET application_name TO '%s'} %
29
- [@conn.escape_string(desc.slice(-60))]
30
- end
31
- example.run
32
- ensure
33
- @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction]
34
- end
35
- end
36
-
37
- mod.after( :all ) { teardown_testing_db(@conn) }
38
- end
39
-
40
- end
41
-
42
-
43
- #
44
- # Examples
45
- #
46
-
47
- # Set some ANSI escape code constants (Shamelessly stolen from Perl's
48
- # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
49
- ANSI_ATTRIBUTES = {
50
- 'clear' => 0,
51
- 'reset' => 0,
52
- 'bold' => 1,
53
- 'dark' => 2,
54
- 'underline' => 4,
55
- 'underscore' => 4,
56
- 'blink' => 5,
57
- 'reverse' => 7,
58
- 'concealed' => 8,
59
-
60
- 'black' => 30, 'on_black' => 40,
61
- 'red' => 31, 'on_red' => 41,
62
- 'green' => 32, 'on_green' => 42,
63
- 'yellow' => 33, 'on_yellow' => 43,
64
- 'blue' => 34, 'on_blue' => 44,
65
- 'magenta' => 35, 'on_magenta' => 45,
66
- 'cyan' => 36, 'on_cyan' => 46,
67
- 'white' => 37, 'on_white' => 47
68
- }
69
-
70
-
71
- ###############
72
- module_function
73
- ###############
74
-
75
- ### Create a string that contains the ANSI codes specified and return it
76
- def ansi_code( *attributes )
77
- attributes.flatten!
78
- attributes.collect! {|at| at.to_s }
79
-
80
- return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
81
- attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
82
-
83
- # $stderr.puts " attr is: %p" % [attributes]
84
- if attributes.empty?
85
- return ''
86
- else
87
- return "\e[%sm" % attributes
88
- end
89
- end
90
-
91
-
92
- ### Colorize the given +string+ with the specified +attributes+ and return it, handling
93
- ### line-endings, color reset, etc.
94
- def colorize( *args )
95
- string = ''
96
-
97
- if block_given?
98
- string = yield
99
- else
100
- string = args.shift
101
- end
102
-
103
- ending = string[/(\s)$/] || ''
104
- string = string.rstrip
105
-
106
- return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
107
- end
108
-
109
-
110
- ### Output a message with highlighting.
111
- def message( *msg )
112
- $stderr.puts( colorize(:bold) { msg.flatten.join(' ') } )
113
- end
114
-
115
-
116
- ### Output a logging message if $VERBOSE is true
117
- def trace( *msg )
118
- return unless $VERBOSE
119
- output = colorize( msg.flatten.join(' '), 'yellow' )
120
- $stderr.puts( output )
121
- end
122
-
123
-
124
- ### Return the specified args as a string, quoting any that have a space.
125
- def quotelist( *args )
126
- return args.flatten.collect {|part| part.to_s =~ /\s/ ? part.to_s.inspect : part.to_s }
127
- end
128
-
129
-
130
- ### Run the specified command +cmd+ with system(), failing if the execution
131
- ### fails.
132
- def run( *cmd )
133
- cmd.flatten!
134
-
135
- if cmd.length > 1
136
- trace( quotelist(*cmd) )
137
- else
138
- trace( cmd )
139
- end
140
-
141
- system( *cmd )
142
- raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success?
143
- end
144
-
145
-
146
- ### Run the specified command +cmd+ after redirecting stdout and stderr to the specified
147
- ### +logpath+, failing if the execution fails.
148
- def log_and_run( logpath, *cmd )
149
- cmd.flatten!
150
-
151
- if cmd.length > 1
152
- trace( quotelist(*cmd) )
153
- else
154
- trace( cmd )
155
- end
156
-
157
- # Eliminate the noise of creating/tearing down the database by
158
- # redirecting STDERR/STDOUT to a logfile
159
- logfh = File.open( logpath, File::WRONLY|File::CREAT|File::APPEND )
160
- system( *cmd, [STDOUT, STDERR] => logfh )
161
-
162
- raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success?
163
- end
164
-
165
-
166
- ### Check the current directory for directories that look like they're
167
- ### testing directories from previous tests, and tell any postgres instances
168
- ### running in them to shut down.
169
- def stop_existing_postmasters
170
- # tmp_test_0.22329534700318
171
- pat = Pathname.getwd + 'tmp_test_*'
172
- Pathname.glob( pat.to_s ).each do |testdir|
173
- datadir = testdir + 'data'
174
- pidfile = datadir + 'postmaster.pid'
175
- if pidfile.exist? && pid = pidfile.read.chomp.to_i
176
- $stderr.puts "pidfile (%p) exists: %d" % [ pidfile, pid ]
177
- begin
178
- Process.kill( 0, pid )
179
- rescue Errno::ESRCH
180
- $stderr.puts "No postmaster running for %s" % [ datadir ]
181
- # Process isn't alive, so don't try to stop it
182
- else
183
- $stderr.puts "Stopping lingering database at PID %d" % [ pid ]
184
- run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop'
185
- end
186
- else
187
- $stderr.puts "No pidfile (%p)" % [ pidfile ]
188
- end
189
- end
190
- end
191
-
192
-
193
- ### Set up a PostgreSQL database instance for testing.
194
- def setup_testing_db( description )
195
- require 'pg'
196
- stop_existing_postmasters()
197
-
198
- puts "Setting up test database for #{description}"
199
- @test_pgdata = TEST_DIRECTORY + 'data'
200
- @test_pgdata.mkpath
201
-
202
- @port = 54321
203
- ENV['PGPORT'] = @port.to_s
204
- ENV['PGHOST'] = 'localhost'
205
- @conninfo = "host=localhost port=#{@port} dbname=test"
206
-
207
- @logfile = TEST_DIRECTORY + 'setup.log'
208
- trace "Command output logged to #{@logfile}"
209
-
210
- begin
211
- unless (@test_pgdata+"postgresql.conf").exist?
212
- FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG )
213
- $stderr.puts "Running initdb"
214
- log_and_run @logfile, 'initdb', '-E', 'UTF8', '--no-locale', '-D', @test_pgdata.to_s
215
- end
216
-
217
- trace "Starting postgres"
218
- log_and_run @logfile, 'pg_ctl', '-w', '-o', "-k #{TEST_DIRECTORY.to_s.dump}",
219
- '-D', @test_pgdata.to_s, 'start'
220
- sleep 2
221
-
222
- $stderr.puts "Creating the test DB"
223
- log_and_run @logfile, 'psql', '-e', '-c', 'DROP DATABASE IF EXISTS test', 'postgres'
224
- log_and_run @logfile, 'createdb', '-e', 'test'
225
-
226
- rescue => err
227
- $stderr.puts "%p during test setup: %s" % [ err.class, err.message ]
228
- $stderr.puts "See #{@logfile} for details."
229
- $stderr.puts *err.backtrace if $DEBUG
230
- fail
231
- end
232
-
233
- conn = PG.connect( @conninfo )
234
- conn.set_notice_processor do |message|
235
- $stderr.puts( description + ':' + message ) if $DEBUG
236
- end
237
-
238
- return conn
239
- end
240
-
241
-
242
- def teardown_testing_db( conn )
243
- puts "Tearing down test database"
244
-
245
- if conn
246
- check_for_lingering_connections( conn )
247
- conn.finish
248
- end
249
-
250
- log_and_run @logfile, 'pg_ctl', '-D', @test_pgdata.to_s, 'stop'
251
- end
252
-
253
-
254
- def check_for_lingering_connections( conn )
255
- conn.exec( "SELECT * FROM pg_stat_activity" ) do |res|
256
- conns = res.find_all {|row| row['pid'].to_i != conn.backend_pid && ["client backend", nil].include?(row["backend_type"]) }
257
- unless conns.empty?
258
- puts "Lingering connections remain:"
259
- conns.each do |row|
260
- puts " [%s] {%s} %s -- %s" % row.values_at( 'pid', 'state', 'application_name', 'query' )
261
- end
262
- end
263
- end
264
- end
265
-
266
-
267
- # Retrieve the names of the column types of a given result set.
268
- def result_typenames(res)
269
- @conn.exec( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","),
270
- res.nfields.times.map{|i| [res.ftype(i), res.fmod(i)] }.flatten ).
271
- values[0]
272
- end
273
-
274
-
275
- # A matcher for checking the status of a PG::Connection to ensure it's still
276
- # usable.
277
- class ConnStillUsableMatcher
278
-
279
- def initialize
280
- @conn = nil
281
- @problem = nil
282
- end
283
-
284
- def matches?( conn )
285
- @conn = conn
286
- @problem = self.check_for_problems
287
- return @problem.nil?
288
- end
289
-
290
- def check_for_problems
291
- return "is finished" if @conn.finished?
292
- return "has bad status" unless @conn.status == PG::CONNECTION_OK
293
- return "has bad transaction status (%d)" % [ @conn.transaction_status ] unless
294
- @conn.transaction_status.between?( PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS )
295
- return "is not usable." unless self.can_exec_query?
296
- return nil
297
- end
298
-
299
- def can_exec_query?
300
- @conn.send_query( "VALUES (1)" )
301
- @conn.get_last_result.values == [["1"]]
302
- end
303
-
304
- def failure_message
305
- return "expected %p to be usable, but it %s" % [ @conn, @problem ]
306
- end
307
-
308
- def failure_message_when_negated
309
- "expected %p not to be usable, but it still is" % [ @conn ]
310
- end
311
-
312
- end
313
-
314
-
315
- ### Return a ConnStillUsableMatcher to be used like:
316
- ###
317
- ### expect( pg_conn ).to still_be_usable
318
- ###
319
- def still_be_usable
320
- return ConnStillUsableMatcher.new
321
- end
322
-
323
- end
324
-
325
-
326
- RSpec.configure do |config|
327
- config.include( PG::TestingHelpers )
328
-
329
- config.run_all_when_everything_filtered = true
330
- config.filter_run :focus
331
- config.order = 'random'
332
- config.mock_with( :rspec ) do |mock|
333
- mock.syntax = :expect
334
- end
335
-
336
- if RUBY_PLATFORM =~ /mingw|mswin/
337
- config.filter_run_excluding :unix
338
- else
339
- config.filter_run_excluding :windows
340
- end
341
- config.filter_run_excluding :socket_io unless
342
- PG::Connection.instance_methods.map( &:to_sym ).include?( :socket_io )
343
-
344
- config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300
345
- config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400
346
- config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500
347
- config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000
348
- end
@@ -1,305 +0,0 @@
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, Date.new(2013,6,30), 4 ],
62
- [ 1, 'a', 2.0, true, Date.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 = PG::TypeMapAllStrings.new
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( Date.new(2113, 12, 31) )
163
- expect( res.getvalue(0,1) ).to eq( Date.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 JSON conversions", :postgresql_94 do
170
- [0].each do |format|
171
- ['JSON', 'JSONB'].each do |type|
172
- res = @conn.exec( "SELECT CAST('123' AS #{type}),
173
- CAST('12.3' AS #{type}),
174
- CAST('true' AS #{type}),
175
- CAST('false' AS #{type}),
176
- CAST('null' AS #{type}),
177
- CAST('[1, \"a\", null]' AS #{type}),
178
- CAST('{\"b\" : [2,3]}' AS #{type})", [], format )
179
- expect( res.getvalue(0,0) ).to eq( 123 )
180
- expect( res.getvalue(0,1) ).to be_within(0.1).of( 12.3 )
181
- expect( res.getvalue(0,2) ).to eq( true )
182
- expect( res.getvalue(0,3) ).to eq( false )
183
- expect( res.getvalue(0,4) ).to eq( nil )
184
- expect( res.getvalue(0,5) ).to eq( [1, "a", nil] )
185
- expect( res.getvalue(0,6) ).to eq( {"b" => [2, 3]} )
186
- end
187
- end
188
- end
189
-
190
- it "should do array type conversions" do
191
- [0].each do |format|
192
- res = @conn.exec( "SELECT CAST('{1,2,3}' AS INT2[]), CAST('{{1,2},{3,4}}' AS INT2[][]),
193
- CAST('{1,2,3}' AS INT4[]),
194
- CAST('{1,2,3}' AS INT8[]),
195
- CAST('{1,2,3}' AS TEXT[]),
196
- CAST('{1,2,3}' AS VARCHAR[]),
197
- CAST('{1,2,3}' AS FLOAT4[]),
198
- CAST('{1,2,3}' AS FLOAT8[])
199
- ", [], format )
200
- expect( res.getvalue(0,0) ).to eq( [1,2,3] )
201
- expect( res.getvalue(0,1) ).to eq( [[1,2],[3,4]] )
202
- expect( res.getvalue(0,2) ).to eq( [1,2,3] )
203
- expect( res.getvalue(0,3) ).to eq( [1,2,3] )
204
- expect( res.getvalue(0,4) ).to eq( ['1','2','3'] )
205
- expect( res.getvalue(0,5) ).to eq( ['1','2','3'] )
206
- expect( res.getvalue(0,6) ).to eq( [1.0,2.0,3.0] )
207
- expect( res.getvalue(0,7) ).to eq( [1.0,2.0,3.0] )
208
- end
209
- end
210
- end
211
-
212
- context "with usage of result oids for copy decoder selection" do
213
- it "can type cast #copy_data output with explicit decoder" do
214
- @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
215
- @conn.exec( "INSERT INTO copytable VALUES ('a', 123, '{5,4,3}'), ('b', 234, '{2,3}')" )
216
-
217
- # Retrieve table OIDs per empty result.
218
- res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
219
- tm = basic_type_mapping.build_column_map( res )
220
- row_decoder = PG::TextDecoder::CopyRow.new type_map: tm
221
-
222
- rows = []
223
- @conn.copy_data( "COPY copytable TO STDOUT", row_decoder ) do |res|
224
- while row=@conn.get_copy_data
225
- rows << row
226
- end
227
- end
228
- expect( rows ).to eq( [['a', 123, [5,4,3]], ['b', 234, [2,3]]] )
229
- end
230
- end
231
- end
232
-
233
-
234
- describe PG::BasicTypeMapBasedOnResult do
235
- let!(:basic_type_mapping) do
236
- PG::BasicTypeMapBasedOnResult.new @conn
237
- end
238
-
239
- context "with usage of result oids for bind params encoder selection" do
240
- it "can type cast query params" do
241
- @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
242
-
243
- # Retrieve table OIDs per empty result.
244
- res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
245
- tm = basic_type_mapping.build_column_map( res )
246
-
247
- @conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['a', 123, [5,4,3]], 0, tm )
248
- @conn.exec_params( "INSERT INTO copytable VALUES ($1, $2, $3)", ['b', 234, [2,3]], 0, tm )
249
- res = @conn.exec( "SELECT * FROM copytable" )
250
- expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
251
- end
252
-
253
- it "can do JSON conversions", :postgresql_94 do
254
- ['JSON', 'JSONB'].each do |type|
255
- sql = "SELECT CAST('123' AS #{type}),
256
- CAST('12.3' AS #{type}),
257
- CAST('true' AS #{type}),
258
- CAST('false' AS #{type}),
259
- CAST('null' AS #{type}),
260
- CAST('[1, \"a\", null]' AS #{type}),
261
- CAST('{\"b\" : [2,3]}' AS #{type})"
262
-
263
- tm = basic_type_mapping.build_column_map( @conn.exec( sql ) )
264
- expect( tm.coders.map(&:name) ).to eq( [type.downcase] * 7 )
265
-
266
- res = @conn.exec_params( "SELECT $1, $2, $3, $4, $5, $6, $7",
267
- [ 123,
268
- 12.3,
269
- true,
270
- false,
271
- nil,
272
- [1, "a", nil],
273
- {"b" => [2, 3]},
274
- ], 0, tm )
275
-
276
- expect( res.getvalue(0,0) ).to eq( "123" )
277
- expect( res.getvalue(0,1) ).to eq( "12.3" )
278
- expect( res.getvalue(0,2) ).to eq( "true" )
279
- expect( res.getvalue(0,3) ).to eq( "false" )
280
- expect( res.getvalue(0,4) ).to eq( nil )
281
- expect( res.getvalue(0,5).gsub(" ","") ).to eq( "[1,\"a\",null]" )
282
- expect( res.getvalue(0,6).gsub(" ","") ).to eq( "{\"b\":[2,3]}" )
283
- end
284
- end
285
- end
286
-
287
- context "with usage of result oids for copy encoder selection" do
288
- it "can type cast #copy_data input with explicit encoder" do
289
- @conn.exec( "CREATE TEMP TABLE copytable (t TEXT, i INT, ai INT[])" )
290
-
291
- # Retrieve table OIDs per empty result set.
292
- res = @conn.exec( "SELECT * FROM copytable LIMIT 0" )
293
- tm = basic_type_mapping.build_column_map( res )
294
- row_encoder = PG::TextEncoder::CopyRow.new type_map: tm
295
-
296
- @conn.copy_data( "COPY copytable FROM STDIN", row_encoder ) do |res|
297
- @conn.put_copy_data ['a', 123, [5,4,3]]
298
- @conn.put_copy_data ['b', 234, [2,3]]
299
- end
300
- res = @conn.exec( "SELECT * FROM copytable" )
301
- expect( res.values ).to eq( [['a', '123', '{5,4,3}'], ['b', '234', '{2,3}']] )
302
- end
303
- end
304
- end
305
- end