pg 1.2.3 → 1.4.3
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/.appveyor.yml +36 -0
- data/.gems +6 -0
- data/.github/workflows/binary-gems.yml +86 -0
- data/.github/workflows/source-gem.yml +129 -0
- data/.gitignore +13 -0
- data/.hgsigs +34 -0
- data/.hgtags +41 -0
- data/.irbrc +23 -0
- data/.pryrc +23 -0
- data/.tm_properties +21 -0
- data/.travis.yml +49 -0
- data/Gemfile +14 -0
- data/History.rdoc +199 -7
- data/Manifest.txt +0 -1
- data/README.rdoc +7 -6
- data/Rakefile +27 -138
- data/Rakefile.cross +8 -5
- data/certs/ged.pem +24 -0
- data/certs/larskanis-2022.pem +26 -0
- data/ext/errorcodes.def +8 -0
- data/ext/errorcodes.rb +0 -0
- data/ext/errorcodes.txt +3 -1
- data/ext/extconf.rb +100 -25
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +59 -28
- data/ext/pg.h +19 -1
- data/ext/pg_coder.c +82 -28
- data/ext/pg_connection.c +855 -654
- data/ext/pg_copy_coder.c +45 -16
- data/ext/pg_record_coder.c +45 -15
- data/ext/pg_result.c +79 -42
- data/ext/pg_text_decoder.c +1 -1
- data/ext/pg_text_encoder.c +6 -6
- data/ext/pg_tuple.c +49 -29
- data/ext/pg_type_map.c +41 -8
- data/ext/pg_type_map_all_strings.c +15 -1
- data/ext/pg_type_map_by_class.c +49 -24
- data/ext/pg_type_map_by_column.c +66 -28
- data/ext/pg_type_map_by_mri_type.c +47 -18
- data/ext/pg_type_map_by_oid.c +52 -23
- data/ext/pg_type_map_in_ruby.c +50 -19
- data/ext/pg_util.c +2 -2
- data/lib/pg/basic_type_map_based_on_result.rb +47 -0
- data/lib/pg/basic_type_map_for_queries.rb +193 -0
- data/lib/pg/basic_type_map_for_results.rb +81 -0
- data/lib/pg/basic_type_registry.rb +301 -0
- data/lib/pg/coder.rb +1 -1
- data/lib/pg/connection.rb +668 -70
- data/lib/pg/exceptions.rb +7 -1
- data/lib/pg/version.rb +4 -0
- data/lib/pg.rb +47 -32
- 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 +32 -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 +87 -224
- metadata.gz.sig +0 -0
- data/ChangeLog +0 -0
- data/lib/pg/basic_type_mapping.rb +0 -522
- data/spec/data/expected_trace.out +0 -26
- data/spec/data/random_binary_data +0 -0
- data/spec/helpers.rb +0 -380
- data/spec/pg/basic_type_mapping_spec.rb +0 -630
- data/spec/pg/connection_spec.rb +0 -1949
- data/spec/pg/connection_sync_spec.rb +0 -41
- data/spec/pg/result_spec.rb +0 -681
- data/spec/pg/tuple_spec.rb +0 -333
- data/spec/pg/type_map_by_class_spec.rb +0 -138
- data/spec/pg/type_map_by_column_spec.rb +0 -226
- 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 -1123
- data/spec/pg_spec.rb +0 -50
data/lib/pg/connection.rb
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
|
|
4
4
|
require 'pg' unless defined?( PG )
|
|
5
5
|
require 'uri'
|
|
6
|
+
require 'io/wait'
|
|
7
|
+
require 'socket'
|
|
6
8
|
|
|
7
9
|
# The PostgreSQL connection class. The interface for this class is based on
|
|
8
|
-
# {libpq}[http://www.postgresql.org/docs/
|
|
10
|
+
# {libpq}[http://www.postgresql.org/docs/current/libpq.html], the C
|
|
9
11
|
# application programmer's interface to PostgreSQL. Some familiarity with libpq
|
|
10
12
|
# is recommended, but not necessary.
|
|
11
13
|
#
|
|
@@ -19,76 +21,82 @@ require 'uri'
|
|
|
19
21
|
#
|
|
20
22
|
# See the PG::Result class for information on working with the results of a query.
|
|
21
23
|
#
|
|
24
|
+
# Many methods of this class have three variants kind of:
|
|
25
|
+
# 1. #exec - the base method which is an alias to #async_exec .
|
|
26
|
+
# This is the method that should be used in general.
|
|
27
|
+
# 2. #async_exec - the async aware version of the method, implemented by libpq's async API.
|
|
28
|
+
# 3. #sync_exec - the method version that is implemented by blocking function(s) of libpq.
|
|
29
|
+
#
|
|
30
|
+
# Sync and async version of the method can be switched by Connection.async_api= , however it is not recommended to change the default.
|
|
22
31
|
class PG::Connection
|
|
23
32
|
|
|
24
33
|
# The order the options are passed to the ::connect method.
|
|
25
34
|
CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password]
|
|
26
35
|
|
|
27
36
|
|
|
28
|
-
### Quote
|
|
29
|
-
def self
|
|
37
|
+
### Quote a single +value+ for use in a connection-parameter string.
|
|
38
|
+
def self.quote_connstr( value )
|
|
30
39
|
return "'" + value.to_s.gsub( /[\\']/ ) {|m| '\\' + m } + "'"
|
|
31
40
|
end
|
|
32
41
|
|
|
42
|
+
# Convert Hash options to connection String
|
|
43
|
+
#
|
|
44
|
+
# Values are properly quoted and escaped.
|
|
45
|
+
def self.connect_hash_to_string( hash )
|
|
46
|
+
hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
|
|
47
|
+
end
|
|
33
48
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
# Parse the connection +args+ into a connection-parameter string.
|
|
50
|
+
# See PG::Connection.new for valid arguments.
|
|
51
|
+
#
|
|
52
|
+
# It accepts:
|
|
53
|
+
# * an option String kind of "host=name port=5432"
|
|
54
|
+
# * an option Hash kind of {host: "name", port: 5432}
|
|
55
|
+
# * URI string
|
|
56
|
+
# * URI object
|
|
57
|
+
# * positional arguments
|
|
58
|
+
#
|
|
59
|
+
# The method adds the option "fallback_application_name" if it isn't already set.
|
|
60
|
+
# It returns a connection string with "key=value" pairs.
|
|
61
|
+
def self.parse_connect_args( *args )
|
|
62
|
+
hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
|
|
63
|
+
iopts = {}
|
|
48
64
|
|
|
49
65
|
if args.length == 1
|
|
50
66
|
case args.first
|
|
51
|
-
when URI,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Option string style
|
|
56
|
-
option_string = args.first.to_s
|
|
67
|
+
when URI, /=/, /:\/\//
|
|
68
|
+
# Option or URL string style
|
|
69
|
+
conn_string = args.first.to_s
|
|
70
|
+
iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
|
57
71
|
else
|
|
58
|
-
# Positional parameters
|
|
59
|
-
|
|
72
|
+
# Positional parameters (only host given)
|
|
73
|
+
iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
|
|
60
74
|
end
|
|
61
75
|
else
|
|
76
|
+
# Positional parameters with host and more
|
|
62
77
|
max = CONNECT_ARGUMENT_ORDER.length
|
|
63
78
|
raise ArgumentError,
|
|
64
|
-
|
|
79
|
+
"Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
|
|
65
80
|
|
|
66
81
|
CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
|
|
67
|
-
|
|
82
|
+
iopts[ k.to_sym ] = v if v
|
|
68
83
|
end
|
|
84
|
+
iopts.delete(:tty) # ignore obsolete tty parameter
|
|
69
85
|
end
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
iopts.merge!( hash_arg )
|
|
72
88
|
|
|
73
|
-
if
|
|
74
|
-
|
|
75
|
-
uri.port = nil if options[:port]
|
|
76
|
-
uri.user = nil if options[:user]
|
|
77
|
-
uri.password = nil if options[:password]
|
|
78
|
-
uri.path = '' if options[:dbname]
|
|
79
|
-
uri.query = URI.encode_www_form( options )
|
|
80
|
-
return uri.to_s.sub( /^#{uri.scheme}:(?!\/\/)/, "#{uri.scheme}://" )
|
|
81
|
-
else
|
|
82
|
-
option_string += ' ' unless option_string.empty? && options.empty?
|
|
83
|
-
return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
|
|
89
|
+
if !iopts[:fallback_application_name]
|
|
90
|
+
iopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
|
|
84
91
|
end
|
|
85
|
-
end
|
|
86
92
|
|
|
93
|
+
return connect_hash_to_string(iopts)
|
|
94
|
+
end
|
|
87
95
|
|
|
88
96
|
# call-seq:
|
|
89
97
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
|
90
98
|
#
|
|
91
|
-
# Execute a copy process for
|
|
99
|
+
# Execute a copy process for transferring data to or from the server.
|
|
92
100
|
#
|
|
93
101
|
# This issues the SQL COPY command via #exec. The response to this
|
|
94
102
|
# (if there is no error in the command) is a PG::Result object that
|
|
@@ -154,6 +162,7 @@ class PG::Connection
|
|
|
154
162
|
# ["more", "data", "to", "copy"]
|
|
155
163
|
|
|
156
164
|
def copy_data( sql, coder=nil )
|
|
165
|
+
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
|
157
166
|
res = exec( sql )
|
|
158
167
|
|
|
159
168
|
case res.result_status
|
|
@@ -185,11 +194,15 @@ class PG::Connection
|
|
|
185
194
|
yield res
|
|
186
195
|
rescue Exception => err
|
|
187
196
|
cancel
|
|
188
|
-
|
|
197
|
+
begin
|
|
198
|
+
while get_copy_data
|
|
199
|
+
end
|
|
200
|
+
rescue PG::Error
|
|
201
|
+
# Ignore error in cleanup to avoid losing original exception
|
|
189
202
|
end
|
|
190
203
|
while get_result
|
|
191
204
|
end
|
|
192
|
-
raise
|
|
205
|
+
raise err
|
|
193
206
|
else
|
|
194
207
|
res = get_last_result
|
|
195
208
|
if !res || res.result_status != PGRES_COMMAND_OK
|
|
@@ -197,7 +210,7 @@ class PG::Connection
|
|
|
197
210
|
end
|
|
198
211
|
while get_result
|
|
199
212
|
end
|
|
200
|
-
raise PG::NotAllCopyDataRetrieved
|
|
213
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
|
201
214
|
end
|
|
202
215
|
res
|
|
203
216
|
ensure
|
|
@@ -214,6 +227,26 @@ class PG::Connection
|
|
|
214
227
|
define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
|
|
215
228
|
end
|
|
216
229
|
|
|
230
|
+
#
|
|
231
|
+
# call-seq:
|
|
232
|
+
# conn.transaction { |conn| ... } -> result of the block
|
|
233
|
+
#
|
|
234
|
+
# Executes a +BEGIN+ at the start of the block,
|
|
235
|
+
# and a +COMMIT+ at the end of the block, or
|
|
236
|
+
# +ROLLBACK+ if any exception occurs.
|
|
237
|
+
def transaction
|
|
238
|
+
rollback = false
|
|
239
|
+
exec "BEGIN"
|
|
240
|
+
yield(self)
|
|
241
|
+
rescue Exception
|
|
242
|
+
rollback = true
|
|
243
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
|
244
|
+
block
|
|
245
|
+
exec "ROLLBACK"
|
|
246
|
+
raise
|
|
247
|
+
ensure
|
|
248
|
+
exec "COMMIT" unless rollback
|
|
249
|
+
end
|
|
217
250
|
|
|
218
251
|
### Returns an array of Hashes with connection defaults. See ::conndefaults
|
|
219
252
|
### for details.
|
|
@@ -237,17 +270,13 @@ class PG::Connection
|
|
|
237
270
|
return self.class.conndefaults_hash
|
|
238
271
|
end
|
|
239
272
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def conninfo_hash
|
|
248
|
-
return self.conninfo.each_with_object({}) do |info, hash|
|
|
249
|
-
hash[ info[:keyword].to_sym ] = info[:val]
|
|
250
|
-
end
|
|
273
|
+
### Return the Postgres connection info structure as a Hash keyed by option
|
|
274
|
+
### keyword (as a Symbol).
|
|
275
|
+
###
|
|
276
|
+
### See also #conninfo
|
|
277
|
+
def conninfo_hash
|
|
278
|
+
return self.conninfo.each_with_object({}) do |info, hash|
|
|
279
|
+
hash[ info[:keyword].to_sym ] = info[:val]
|
|
251
280
|
end
|
|
252
281
|
end
|
|
253
282
|
|
|
@@ -269,23 +298,592 @@ class PG::Connection
|
|
|
269
298
|
end
|
|
270
299
|
end
|
|
271
300
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
301
|
+
# call-seq:
|
|
302
|
+
# conn.get_result() -> PG::Result
|
|
303
|
+
# conn.get_result() {|pg_result| block }
|
|
304
|
+
#
|
|
305
|
+
# Blocks waiting for the next result from a call to
|
|
306
|
+
# #send_query (or another asynchronous command), and returns
|
|
307
|
+
# it. Returns +nil+ if no more results are available.
|
|
308
|
+
#
|
|
309
|
+
# Note: call this function repeatedly until it returns +nil+, or else
|
|
310
|
+
# you will not be able to issue further commands.
|
|
311
|
+
#
|
|
312
|
+
# If the optional code block is given, it will be passed <i>result</i> as an argument,
|
|
313
|
+
# and the PG::Result object will automatically be cleared when the block terminates.
|
|
314
|
+
# In this instance, <code>conn.exec</code> returns the value of the block.
|
|
315
|
+
def get_result
|
|
316
|
+
block
|
|
317
|
+
sync_get_result
|
|
318
|
+
end
|
|
319
|
+
alias async_get_result get_result
|
|
320
|
+
|
|
321
|
+
# call-seq:
|
|
322
|
+
# conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
|
|
323
|
+
#
|
|
324
|
+
# Return one row of data, +nil+
|
|
325
|
+
# if the copy is done, or +false+ if the call would
|
|
326
|
+
# block (only possible if _nonblock_ is true).
|
|
327
|
+
#
|
|
328
|
+
# If _decoder_ is not set or +nil+, data is returned as binary string.
|
|
329
|
+
#
|
|
330
|
+
# If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
|
|
331
|
+
# PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
|
|
332
|
+
# COPY text format to an Array of Strings.
|
|
333
|
+
# Optionally the decoder can type cast the single fields to various Ruby types in one step,
|
|
334
|
+
# if PG::TextDecoder::CopyRow#type_map is set accordingly.
|
|
335
|
+
#
|
|
336
|
+
# See also #copy_data.
|
|
337
|
+
#
|
|
338
|
+
def get_copy_data(async=false, decoder=nil)
|
|
339
|
+
if async
|
|
340
|
+
return sync_get_copy_data(async, decoder)
|
|
341
|
+
else
|
|
342
|
+
while (res=sync_get_copy_data(true, decoder)) == false
|
|
343
|
+
socket_io.wait_readable
|
|
344
|
+
consume_input
|
|
345
|
+
end
|
|
346
|
+
return res
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
alias async_get_copy_data get_copy_data
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
# In async_api=true mode (default) all send calls run nonblocking.
|
|
353
|
+
# The difference is that setnonblocking(true) disables automatic handling of would-block cases.
|
|
354
|
+
# In async_api=false mode all send calls run directly on libpq.
|
|
355
|
+
# Blocking vs. nonblocking state can be changed in libpq.
|
|
356
|
+
|
|
357
|
+
# call-seq:
|
|
358
|
+
# conn.setnonblocking(Boolean) -> nil
|
|
359
|
+
#
|
|
360
|
+
# Sets the nonblocking status of the connection.
|
|
361
|
+
# In the blocking state, calls to #send_query
|
|
362
|
+
# will block until the message is sent to the server,
|
|
363
|
+
# but will not wait for the query results.
|
|
364
|
+
# In the nonblocking state, calls to #send_query
|
|
365
|
+
# will return an error if the socket is not ready for
|
|
366
|
+
# writing.
|
|
367
|
+
# Note: This function does not affect #exec, because
|
|
368
|
+
# that function doesn't return until the server has
|
|
369
|
+
# processed the query and returned the results.
|
|
370
|
+
#
|
|
371
|
+
# Returns +nil+.
|
|
372
|
+
def setnonblocking(enabled)
|
|
373
|
+
singleton_class.async_send_api = !enabled
|
|
374
|
+
self.flush_data = !enabled
|
|
375
|
+
sync_setnonblocking(true)
|
|
376
|
+
end
|
|
377
|
+
alias async_setnonblocking setnonblocking
|
|
378
|
+
|
|
379
|
+
# sync/async isnonblocking methods are switched by async_setnonblocking()
|
|
380
|
+
|
|
381
|
+
# call-seq:
|
|
382
|
+
# conn.isnonblocking() -> Boolean
|
|
383
|
+
#
|
|
384
|
+
# Returns the blocking status of the database connection.
|
|
385
|
+
# Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
|
|
386
|
+
def isnonblocking
|
|
387
|
+
false
|
|
388
|
+
end
|
|
389
|
+
alias async_isnonblocking isnonblocking
|
|
390
|
+
alias nonblocking? isnonblocking
|
|
391
|
+
|
|
392
|
+
# call-seq:
|
|
393
|
+
# conn.put_copy_data( buffer [, encoder] ) -> Boolean
|
|
394
|
+
#
|
|
395
|
+
# Transmits _buffer_ as copy data to the server.
|
|
396
|
+
# Returns true if the data was sent, false if it was
|
|
397
|
+
# not sent (false is only possible if the connection
|
|
398
|
+
# is in nonblocking mode, and this command would block).
|
|
399
|
+
#
|
|
400
|
+
# _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
|
|
401
|
+
# This encodes the data fields given as _buffer_ from an Array of Strings to
|
|
402
|
+
# PostgreSQL's COPY text format inclusive proper escaping. Optionally
|
|
403
|
+
# the encoder can type cast the fields from various Ruby types in one step,
|
|
404
|
+
# if PG::TextEncoder::CopyRow#type_map is set accordingly.
|
|
405
|
+
#
|
|
406
|
+
# Raises an exception if an error occurs.
|
|
407
|
+
#
|
|
408
|
+
# See also #copy_data.
|
|
409
|
+
#
|
|
410
|
+
def put_copy_data(buffer, encoder=nil)
|
|
411
|
+
# sync_put_copy_data does a non-blocking attept to flush data.
|
|
412
|
+
until res=sync_put_copy_data(buffer, encoder)
|
|
413
|
+
# It didn't flush immediately and allocation of more buffering memory failed.
|
|
414
|
+
# Wait for all data sent by doing a blocking flush.
|
|
415
|
+
res = flush
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
# And do a blocking flush every 100 calls.
|
|
419
|
+
# This is to avoid memory bloat, when sending the data is slower than calls to put_copy_data happen.
|
|
420
|
+
if (@calls_to_put_copy_data += 1) > 100
|
|
421
|
+
@calls_to_put_copy_data = 0
|
|
422
|
+
res = flush
|
|
423
|
+
end
|
|
424
|
+
res
|
|
425
|
+
end
|
|
426
|
+
alias async_put_copy_data put_copy_data
|
|
427
|
+
|
|
428
|
+
# call-seq:
|
|
429
|
+
# conn.put_copy_end( [ error_message ] ) -> Boolean
|
|
430
|
+
#
|
|
431
|
+
# Sends end-of-data indication to the server.
|
|
432
|
+
#
|
|
433
|
+
# _error_message_ is an optional parameter, and if set,
|
|
434
|
+
# forces the COPY command to fail with the string
|
|
435
|
+
# _error_message_.
|
|
436
|
+
#
|
|
437
|
+
# Returns true if the end-of-data was sent, #false* if it was
|
|
438
|
+
# not sent (*false* is only possible if the connection
|
|
439
|
+
# is in nonblocking mode, and this command would block).
|
|
440
|
+
def put_copy_end(*args)
|
|
441
|
+
until sync_put_copy_end(*args)
|
|
442
|
+
flush
|
|
443
|
+
end
|
|
444
|
+
@calls_to_put_copy_data = 0
|
|
445
|
+
flush
|
|
446
|
+
end
|
|
447
|
+
alias async_put_copy_end put_copy_end
|
|
448
|
+
|
|
449
|
+
if method_defined? :sync_encrypt_password
|
|
450
|
+
# call-seq:
|
|
451
|
+
# conn.encrypt_password( password, username, algorithm=nil ) -> String
|
|
452
|
+
#
|
|
453
|
+
# This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
|
|
454
|
+
# It is good practice not to send the original cleartext password in such a command, because it might be exposed in command logs, activity displays, and so on.
|
|
455
|
+
# Instead, use this function to convert the password to encrypted form before it is sent.
|
|
456
|
+
#
|
|
457
|
+
# The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
|
|
458
|
+
# +algorithm+ specifies the encryption algorithm to use to encrypt the password.
|
|
459
|
+
# Currently supported algorithms are +md5+ and +scram-sha-256+ (+on+ and +off+ are also accepted as aliases for +md5+, for compatibility with older server versions).
|
|
460
|
+
# Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
|
|
461
|
+
# If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
|
|
462
|
+
# That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
|
|
463
|
+
# If you wish to use the default algorithm for the server but want to avoid blocking, query +password_encryption+ yourself before calling #encrypt_password, and pass that value as the algorithm.
|
|
464
|
+
#
|
|
465
|
+
# Return value is the encrypted password.
|
|
466
|
+
# The caller can assume the string doesn't contain any special characters that would require escaping.
|
|
467
|
+
#
|
|
468
|
+
# Available since PostgreSQL-10.
|
|
469
|
+
# See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
|
|
470
|
+
def encrypt_password( password, username, algorithm=nil )
|
|
471
|
+
algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
|
|
472
|
+
sync_encrypt_password(password, username, algorithm)
|
|
473
|
+
end
|
|
474
|
+
alias async_encrypt_password encrypt_password
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
# call-seq:
|
|
478
|
+
# conn.reset()
|
|
479
|
+
#
|
|
480
|
+
# Resets the backend connection. This method closes the
|
|
481
|
+
# backend connection and tries to re-connect.
|
|
482
|
+
def reset
|
|
483
|
+
reset_start
|
|
484
|
+
async_connect_or_reset(:reset_poll)
|
|
485
|
+
self
|
|
486
|
+
end
|
|
487
|
+
alias async_reset reset
|
|
488
|
+
|
|
489
|
+
# call-seq:
|
|
490
|
+
# conn.cancel() -> String
|
|
491
|
+
#
|
|
492
|
+
# Requests cancellation of the command currently being
|
|
493
|
+
# processed.
|
|
494
|
+
#
|
|
495
|
+
# Returns +nil+ on success, or a string containing the
|
|
496
|
+
# error message if a failure occurs.
|
|
497
|
+
def cancel
|
|
498
|
+
be_pid = backend_pid
|
|
499
|
+
be_key = backend_key
|
|
500
|
+
cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
|
|
501
|
+
|
|
502
|
+
if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
|
|
503
|
+
# Ruby's nonblocking IO is not really supported on Windows.
|
|
504
|
+
# We work around by using threads and explicit calls to wait_readable/wait_writable.
|
|
505
|
+
cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
|
|
506
|
+
begin
|
|
507
|
+
cl.write_nonblock(cancel_request)
|
|
508
|
+
rescue IO::WaitReadable, Errno::EINTR
|
|
509
|
+
cl.wait_writable
|
|
510
|
+
retry
|
|
511
|
+
end
|
|
512
|
+
begin
|
|
513
|
+
cl.read_nonblock(1)
|
|
514
|
+
rescue IO::WaitReadable, Errno::EINTR
|
|
515
|
+
cl.wait_readable
|
|
516
|
+
retry
|
|
517
|
+
rescue EOFError
|
|
518
|
+
end
|
|
519
|
+
elsif RUBY_ENGINE == 'truffleruby'
|
|
520
|
+
begin
|
|
521
|
+
cl = socket_io.remote_address.connect
|
|
522
|
+
rescue NotImplementedError
|
|
523
|
+
# Workaround for truffleruby < 21.3.0
|
|
524
|
+
cl2 = Socket.for_fd(socket_io.fileno)
|
|
525
|
+
cl2.autoclose = false
|
|
526
|
+
adr = cl2.remote_address
|
|
527
|
+
if adr.ip?
|
|
528
|
+
cl = TCPSocket.new(adr.ip_address, adr.ip_port)
|
|
529
|
+
cl.autoclose = false
|
|
530
|
+
else
|
|
531
|
+
cl = UNIXSocket.new(adr.unix_path)
|
|
532
|
+
cl.autoclose = false
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
cl.write(cancel_request)
|
|
536
|
+
cl.read(1)
|
|
537
|
+
else
|
|
538
|
+
cl = socket_io.remote_address.connect
|
|
539
|
+
# Send CANCEL_REQUEST_CODE and parameters
|
|
540
|
+
cl.write(cancel_request)
|
|
541
|
+
# Wait for the postmaster to close the connection, which indicates that it's processed the request.
|
|
542
|
+
cl.read(1)
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
cl.close
|
|
546
|
+
nil
|
|
547
|
+
rescue SystemCallError => err
|
|
548
|
+
err.to_s
|
|
549
|
+
end
|
|
550
|
+
alias async_cancel cancel
|
|
551
|
+
|
|
552
|
+
private def async_connect_or_reset(poll_meth)
|
|
553
|
+
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
|
554
|
+
|
|
555
|
+
if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
|
|
556
|
+
# Lowest timeout is 2 seconds - like in libpq
|
|
557
|
+
timeo = [timeo, 2].max
|
|
558
|
+
stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
|
562
|
+
until poll_status == PG::PGRES_POLLING_OK ||
|
|
563
|
+
poll_status == PG::PGRES_POLLING_FAILED
|
|
564
|
+
|
|
565
|
+
timeout = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
|
566
|
+
event = if !timeout || timeout >= 0
|
|
567
|
+
# If the socket needs to read, wait 'til it becomes readable to poll again
|
|
568
|
+
case poll_status
|
|
569
|
+
when PG::PGRES_POLLING_READING
|
|
570
|
+
if defined?(IO::READABLE) # ruby-3.0+
|
|
571
|
+
socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
|
|
572
|
+
else
|
|
573
|
+
IO.select([socket_io], nil, [socket_io], timeout)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
# ...and the same for when the socket needs to write
|
|
577
|
+
when PG::PGRES_POLLING_WRITING
|
|
578
|
+
if defined?(IO::WRITABLE) # ruby-3.0+
|
|
579
|
+
# Use wait instead of wait_readable, since connection errors are delivered as
|
|
580
|
+
# exceptional/priority events on Windows.
|
|
581
|
+
socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
|
|
582
|
+
else
|
|
583
|
+
# io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
|
|
584
|
+
IO.select(nil, [socket_io], [socket_io], timeout)
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
end
|
|
588
|
+
# connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
|
|
589
|
+
# connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
|
|
590
|
+
unless event
|
|
591
|
+
if self.class.send(:host_is_named_pipe?, host)
|
|
592
|
+
connhost = "on socket \"#{host}\""
|
|
593
|
+
elsif respond_to?(:hostaddr)
|
|
594
|
+
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
|
595
|
+
else
|
|
596
|
+
connhost = "at \"#{host}\", port #{port}"
|
|
597
|
+
end
|
|
598
|
+
raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
# Check to see if it's finished or failed yet
|
|
602
|
+
poll_status = send( poll_meth )
|
|
603
|
+
@last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
unless status == PG::CONNECTION_OK
|
|
607
|
+
msg = error_message
|
|
608
|
+
finish
|
|
609
|
+
raise PG::ConnectionBad.new(msg, connection: self)
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
# Set connection to nonblocking to handle all blocking states in ruby.
|
|
613
|
+
# That way a fiber scheduler is able to handle IO requests.
|
|
614
|
+
sync_setnonblocking(true)
|
|
615
|
+
self.flush_data = true
|
|
616
|
+
set_default_encoding
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
class << self
|
|
620
|
+
# call-seq:
|
|
621
|
+
# PG::Connection.new -> conn
|
|
622
|
+
# PG::Connection.new(connection_hash) -> conn
|
|
623
|
+
# PG::Connection.new(connection_string) -> conn
|
|
624
|
+
# PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
|
|
625
|
+
#
|
|
626
|
+
# Create a connection to the specified server.
|
|
627
|
+
#
|
|
628
|
+
# +connection_hash+ must be a ruby Hash with connection parameters.
|
|
629
|
+
# See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
|
|
630
|
+
#
|
|
631
|
+
# There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
|
|
632
|
+
# See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
|
|
633
|
+
#
|
|
634
|
+
# The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
|
|
635
|
+
# [+host+]
|
|
636
|
+
# server hostname
|
|
637
|
+
# [+port+]
|
|
638
|
+
# server port number
|
|
639
|
+
# [+options+]
|
|
640
|
+
# backend options
|
|
641
|
+
# [+tty+]
|
|
642
|
+
# (ignored in all versions of PostgreSQL)
|
|
643
|
+
# [+dbname+]
|
|
644
|
+
# connecting database name
|
|
645
|
+
# [+user+]
|
|
646
|
+
# login user name
|
|
647
|
+
# [+password+]
|
|
648
|
+
# login password
|
|
649
|
+
#
|
|
650
|
+
# Examples:
|
|
651
|
+
#
|
|
652
|
+
# # Connect using all defaults
|
|
653
|
+
# PG::Connection.new
|
|
654
|
+
#
|
|
655
|
+
# # As a Hash
|
|
656
|
+
# PG::Connection.new( dbname: 'test', port: 5432 )
|
|
657
|
+
#
|
|
658
|
+
# # As a String
|
|
659
|
+
# PG::Connection.new( "dbname=test port=5432" )
|
|
660
|
+
#
|
|
661
|
+
# # As an Array
|
|
662
|
+
# PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
|
|
663
|
+
#
|
|
664
|
+
# # As an URI
|
|
665
|
+
# PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
|
|
666
|
+
#
|
|
667
|
+
# If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
|
|
668
|
+
# connection will have its +client_encoding+ set accordingly.
|
|
669
|
+
#
|
|
670
|
+
# Raises a PG::Error if the connection fails.
|
|
671
|
+
def new(*args)
|
|
672
|
+
conn = connect_to_hosts(*args)
|
|
673
|
+
|
|
674
|
+
if block_given?
|
|
675
|
+
begin
|
|
676
|
+
return yield conn
|
|
677
|
+
ensure
|
|
678
|
+
conn.finish
|
|
679
|
+
end
|
|
680
|
+
end
|
|
681
|
+
conn
|
|
682
|
+
end
|
|
683
|
+
alias async_connect new
|
|
684
|
+
alias connect new
|
|
685
|
+
alias open new
|
|
686
|
+
alias setdb new
|
|
687
|
+
alias setdblogin new
|
|
688
|
+
|
|
689
|
+
private def connect_to_hosts(*args)
|
|
690
|
+
option_string = parse_connect_args(*args)
|
|
691
|
+
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
|
692
|
+
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
|
693
|
+
|
|
694
|
+
errors = []
|
|
695
|
+
if iopts[:hostaddr]
|
|
696
|
+
# hostaddr is provided -> no need to resolve hostnames
|
|
697
|
+
ihostaddrs = iopts[:hostaddr].split(",", -1)
|
|
698
|
+
|
|
699
|
+
ihosts = iopts[:host].split(",", -1) if iopts[:host]
|
|
700
|
+
raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size
|
|
701
|
+
|
|
702
|
+
iports = iopts[:port].split(",", -1)
|
|
703
|
+
iports = iports * ihostaddrs.size if iports.size == 1
|
|
704
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihostaddrs.size} hosts" if iports.size != ihostaddrs.size
|
|
705
|
+
|
|
706
|
+
# Try to connect to each hostaddr with separate timeout
|
|
707
|
+
ihostaddrs.each_with_index do |ihostaddr, idx|
|
|
708
|
+
oopts = iopts.merge(hostaddr: ihostaddr, port: iports[idx])
|
|
709
|
+
oopts[:host] = ihosts[idx] if ihosts
|
|
710
|
+
c = connect_internal(oopts, errors)
|
|
711
|
+
return c if c
|
|
712
|
+
end
|
|
713
|
+
elsif iopts[:host] && !iopts[:host].empty?
|
|
714
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
|
|
715
|
+
ihosts = iopts[:host].split(",", -1)
|
|
716
|
+
|
|
717
|
+
iports = iopts[:port].split(",", -1)
|
|
718
|
+
iports = iports * ihosts.size if iports.size == 1
|
|
719
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
|
720
|
+
|
|
721
|
+
ihosts.each_with_index do |mhost, idx|
|
|
722
|
+
unless host_is_named_pipe?(mhost)
|
|
723
|
+
addrs = if Fiber.respond_to?(:scheduler) &&
|
|
724
|
+
Fiber.scheduler &&
|
|
725
|
+
RUBY_VERSION < '3.1.'
|
|
726
|
+
|
|
727
|
+
# Use a second thread to avoid blocking of the scheduler.
|
|
728
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
|
729
|
+
Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
|
730
|
+
else
|
|
731
|
+
Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
|
732
|
+
end
|
|
733
|
+
|
|
734
|
+
# Try to connect to each host with separate timeout
|
|
735
|
+
addrs.each do |addr|
|
|
736
|
+
oopts = iopts.merge(hostaddr: addr, host: mhost, port: iports[idx])
|
|
737
|
+
c = connect_internal(oopts, errors)
|
|
738
|
+
return c if c
|
|
739
|
+
end
|
|
740
|
+
else
|
|
741
|
+
# No hostname to resolve (UnixSocket)
|
|
742
|
+
oopts = iopts.merge(host: mhost, port: iports[idx])
|
|
743
|
+
c = connect_internal(oopts, errors)
|
|
744
|
+
return c if c
|
|
745
|
+
end
|
|
746
|
+
end
|
|
747
|
+
else
|
|
748
|
+
# No host given
|
|
749
|
+
return connect_internal(iopts)
|
|
750
|
+
end
|
|
751
|
+
raise PG::ConnectionBad, errors.join("\n")
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
private def connect_internal(opts, errors=nil)
|
|
755
|
+
begin
|
|
756
|
+
conn = self.connect_start(opts) or
|
|
757
|
+
raise(PG::Error, "Unable to create a new connection")
|
|
758
|
+
|
|
759
|
+
raise PG::ConnectionBad.new(conn.error_message, connection: self) if conn.status == PG::CONNECTION_BAD
|
|
760
|
+
|
|
761
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
|
762
|
+
rescue PG::ConnectionBad => err
|
|
763
|
+
if errors && !(conn && [PG::CONNECTION_AWAITING_RESPONSE].include?(conn.instance_variable_get(:@last_status)))
|
|
764
|
+
# Seems to be no authentication error -> try next host
|
|
765
|
+
errors << err
|
|
766
|
+
return nil
|
|
767
|
+
else
|
|
768
|
+
# Probably an authentication error
|
|
769
|
+
raise
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
conn
|
|
773
|
+
end
|
|
774
|
+
|
|
775
|
+
private def host_is_named_pipe?(host_string)
|
|
776
|
+
host_string.empty? || host_string.start_with?("/") || # it's UnixSocket?
|
|
777
|
+
host_string.start_with?("@") || # it's UnixSocket in the abstract namespace?
|
|
778
|
+
# it's a path on Windows?
|
|
779
|
+
(RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/)
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
# call-seq:
|
|
783
|
+
# PG::Connection.ping(connection_hash) -> Integer
|
|
784
|
+
# PG::Connection.ping(connection_string) -> Integer
|
|
785
|
+
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
|
786
|
+
#
|
|
787
|
+
# Check server status.
|
|
788
|
+
#
|
|
789
|
+
# See PG::Connection.new for a description of the parameters.
|
|
790
|
+
#
|
|
791
|
+
# Returns one of:
|
|
792
|
+
# [+PQPING_OK+]
|
|
793
|
+
# server is accepting connections
|
|
794
|
+
# [+PQPING_REJECT+]
|
|
795
|
+
# server is alive but rejecting connections
|
|
796
|
+
# [+PQPING_NO_RESPONSE+]
|
|
797
|
+
# could not establish connection
|
|
798
|
+
# [+PQPING_NO_ATTEMPT+]
|
|
799
|
+
# connection not attempted (bad params)
|
|
800
|
+
def ping(*args)
|
|
801
|
+
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
|
802
|
+
# Run PQping in a second thread to avoid blocking of the scheduler.
|
|
803
|
+
# Unfortunately there's no nonblocking way to run ping.
|
|
804
|
+
Thread.new { sync_ping(*args) }.value
|
|
805
|
+
else
|
|
806
|
+
sync_ping(*args)
|
|
807
|
+
end
|
|
808
|
+
end
|
|
809
|
+
alias async_ping ping
|
|
810
|
+
|
|
811
|
+
REDIRECT_CLASS_METHODS = {
|
|
812
|
+
:new => [:async_connect, :sync_connect],
|
|
813
|
+
:connect => [:async_connect, :sync_connect],
|
|
814
|
+
:open => [:async_connect, :sync_connect],
|
|
815
|
+
:setdb => [:async_connect, :sync_connect],
|
|
816
|
+
:setdblogin => [:async_connect, :sync_connect],
|
|
817
|
+
:ping => [:async_ping, :sync_ping],
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
# These methods are affected by PQsetnonblocking
|
|
821
|
+
REDIRECT_SEND_METHODS = {
|
|
822
|
+
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
|
823
|
+
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
|
824
|
+
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
|
825
|
+
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
|
826
|
+
:flush => [:async_flush, :sync_flush],
|
|
827
|
+
}
|
|
828
|
+
REDIRECT_METHODS = {
|
|
829
|
+
:exec => [:async_exec, :sync_exec],
|
|
830
|
+
:query => [:async_exec, :sync_exec],
|
|
831
|
+
:exec_params => [:async_exec_params, :sync_exec_params],
|
|
832
|
+
:prepare => [:async_prepare, :sync_prepare],
|
|
833
|
+
:exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
|
|
834
|
+
:describe_portal => [:async_describe_portal, :sync_describe_portal],
|
|
835
|
+
:describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
|
|
836
|
+
:setnonblocking => [:async_setnonblocking, :sync_setnonblocking],
|
|
837
|
+
:get_result => [:async_get_result, :sync_get_result],
|
|
838
|
+
:get_last_result => [:async_get_last_result, :sync_get_last_result],
|
|
839
|
+
:get_copy_data => [:async_get_copy_data, :sync_get_copy_data],
|
|
840
|
+
:reset => [:async_reset, :sync_reset],
|
|
841
|
+
:set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
|
|
842
|
+
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
|
843
|
+
:cancel => [:async_cancel, :sync_cancel],
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
if PG::Connection.instance_methods.include? :async_encrypt_password
|
|
847
|
+
REDIRECT_METHODS.merge!({
|
|
848
|
+
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
|
849
|
+
})
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
def async_send_api=(enable)
|
|
853
|
+
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
|
854
|
+
undef_method(ali) if method_defined?(ali)
|
|
855
|
+
alias_method( ali, enable ? async : sync )
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
# Switch between sync and async libpq API.
|
|
860
|
+
#
|
|
861
|
+
# PG::Connection.async_api = true
|
|
862
|
+
# this is the default.
|
|
863
|
+
# It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
|
|
864
|
+
#
|
|
865
|
+
# PG::Connection.async_api = false
|
|
866
|
+
# sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
|
|
867
|
+
#
|
|
868
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
|
|
869
|
+
# pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
|
|
870
|
+
#
|
|
871
|
+
# _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
|
|
872
|
+
# Do not use this method in production code.
|
|
873
|
+
# Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
|
|
874
|
+
#
|
|
875
|
+
def async_api=(enable)
|
|
876
|
+
self.async_send_api = enable
|
|
877
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
|
878
|
+
remove_method(ali) if method_defined?(ali)
|
|
879
|
+
alias_method( ali, enable ? async : sync )
|
|
880
|
+
end
|
|
881
|
+
REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
|
|
882
|
+
singleton_class.remove_method(ali) if method_defined?(ali)
|
|
883
|
+
singleton_class.alias_method(ali, enable ? async : sync )
|
|
884
|
+
end
|
|
286
885
|
end
|
|
287
886
|
end
|
|
288
887
|
|
|
289
|
-
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
|
290
888
|
self.async_api = true
|
|
291
889
|
end # class PG::Connection
|