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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/Gemfile +20 -0
- data/History.md +932 -0
- data/Manifest.txt +8 -3
- data/README-Windows.rdoc +4 -4
- data/README.ja.md +300 -0
- data/README.md +286 -0
- data/Rakefile +41 -138
- data/Rakefile.cross +71 -66
- data/certs/ged.pem +24 -0
- data/certs/kanis@comcard.de.pem +20 -0
- data/certs/larskanis-2022.pem +26 -0
- data/certs/larskanis-2023.pem +24 -0
- data/certs/larskanis-2024.pem +24 -0
- data/ext/errorcodes.def +84 -5
- data/ext/errorcodes.rb +1 -1
- data/ext/errorcodes.txt +23 -6
- data/ext/extconf.rb +109 -25
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +213 -155
- data/ext/pg.h +89 -23
- data/ext/pg_binary_decoder.c +164 -16
- data/ext/pg_binary_encoder.c +238 -13
- data/ext/pg_coder.c +159 -35
- data/ext/pg_connection.c +1584 -967
- data/ext/pg_copy_coder.c +373 -43
- data/ext/pg_errors.c +1 -1
- data/ext/pg_record_coder.c +522 -0
- data/ext/pg_result.c +710 -217
- data/ext/pg_text_decoder.c +630 -43
- data/ext/pg_text_encoder.c +222 -72
- data/ext/pg_tuple.c +572 -0
- data/ext/pg_type_map.c +45 -11
- data/ext/pg_type_map_all_strings.c +21 -7
- data/ext/pg_type_map_by_class.c +59 -27
- data/ext/pg_type_map_by_column.c +80 -37
- data/ext/pg_type_map_by_mri_type.c +49 -20
- data/ext/pg_type_map_by_oid.c +62 -29
- data/ext/pg_type_map_in_ruby.c +56 -22
- data/ext/{util.c → pg_util.c} +12 -12
- data/ext/{util.h → pg_util.h} +2 -2
- data/lib/pg/basic_type_map_based_on_result.rb +67 -0
- data/lib/pg/basic_type_map_for_queries.rb +202 -0
- data/lib/pg/basic_type_map_for_results.rb +104 -0
- data/lib/pg/basic_type_registry.rb +311 -0
- data/lib/pg/binary_decoder/date.rb +9 -0
- data/lib/pg/binary_decoder/timestamp.rb +26 -0
- data/lib/pg/binary_encoder/timestamp.rb +20 -0
- data/lib/pg/coder.rb +36 -13
- data/lib/pg/connection.rb +769 -70
- data/lib/pg/exceptions.rb +22 -2
- data/lib/pg/result.rb +14 -2
- data/lib/pg/text_decoder/date.rb +21 -0
- data/lib/pg/text_decoder/inet.rb +9 -0
- data/lib/pg/text_decoder/json.rb +17 -0
- data/lib/pg/text_decoder/numeric.rb +9 -0
- data/lib/pg/text_decoder/timestamp.rb +30 -0
- data/lib/pg/text_encoder/date.rb +13 -0
- data/lib/pg/text_encoder/inet.rb +31 -0
- data/lib/pg/text_encoder/json.rb +17 -0
- data/lib/pg/text_encoder/numeric.rb +9 -0
- data/lib/pg/text_encoder/timestamp.rb +24 -0
- data/lib/pg/tuple.rb +30 -0
- data/lib/pg/type_map_by_column.rb +3 -2
- data/lib/pg/version.rb +4 -0
- data/lib/pg.rb +106 -39
- data/misc/openssl-pg-segfault.rb +31 -0
- data/misc/postgres/History.txt +9 -0
- data/misc/postgres/Manifest.txt +5 -0
- data/misc/postgres/README.txt +21 -0
- data/misc/postgres/Rakefile +21 -0
- data/misc/postgres/lib/postgres.rb +16 -0
- data/misc/ruby-pg/History.txt +9 -0
- data/misc/ruby-pg/Manifest.txt +5 -0
- data/misc/ruby-pg/README.txt +21 -0
- data/misc/ruby-pg/Rakefile +21 -0
- data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
- data/pg.gemspec +36 -0
- data/rakelib/task_extension.rb +46 -0
- data/sample/array_insert.rb +20 -0
- data/sample/async_api.rb +102 -0
- data/sample/async_copyto.rb +39 -0
- data/sample/async_mixed.rb +56 -0
- data/sample/check_conn.rb +21 -0
- data/sample/copydata.rb +71 -0
- data/sample/copyfrom.rb +81 -0
- data/sample/copyto.rb +19 -0
- data/sample/cursor.rb +21 -0
- data/sample/disk_usage_report.rb +177 -0
- data/sample/issue-119.rb +94 -0
- data/sample/losample.rb +69 -0
- data/sample/minimal-testcase.rb +17 -0
- data/sample/notify_wait.rb +72 -0
- data/sample/pg_statistics.rb +285 -0
- data/sample/replication_monitor.rb +222 -0
- data/sample/test_binary_values.rb +33 -0
- data/sample/wal_shipper.rb +434 -0
- data/sample/warehouse_partitions.rb +311 -0
- data.tar.gz.sig +0 -0
- metadata +138 -223
- metadata.gz.sig +0 -0
- data/.gemtest +0 -0
- data/ChangeLog +0 -6595
- data/History.rdoc +0 -422
- data/README.ja.rdoc +0 -14
- data/README.rdoc +0 -167
- data/lib/pg/basic_type_mapping.rb +0 -426
- data/lib/pg/constants.rb +0 -11
- data/lib/pg/text_decoder.rb +0 -51
- data/lib/pg/text_encoder.rb +0 -35
- data/spec/data/expected_trace.out +0 -26
- data/spec/data/random_binary_data +0 -0
- data/spec/helpers.rb +0 -348
- data/spec/pg/basic_type_mapping_spec.rb +0 -305
- data/spec/pg/connection_spec.rb +0 -1719
- data/spec/pg/result_spec.rb +0 -456
- data/spec/pg/type_map_by_class_spec.rb +0 -138
- data/spec/pg/type_map_by_column_spec.rb +0 -222
- data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
- data/spec/pg/type_map_by_oid_spec.rb +0 -149
- data/spec/pg/type_map_in_ruby_spec.rb +0 -164
- data/spec/pg/type_map_spec.rb +0 -22
- data/spec/pg/type_spec.rb +0 -777
- 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
|