pg 1.2.3 → 1.3.5
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 +153 -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 +131 -25
- data/ext/gvl_wrappers.c +4 -0
- data/ext/gvl_wrappers.h +23 -0
- data/ext/pg.c +59 -4
- data/ext/pg.h +19 -1
- data/ext/pg_coder.c +82 -28
- data/ext/pg_connection.c +680 -508
- data/ext/pg_copy_coder.c +45 -16
- data/ext/pg_record_coder.c +45 -15
- data/ext/pg_result.c +77 -40
- 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 +589 -58
- 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,68 +21,153 @@ 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
|
-
hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
|
40
|
-
option_string = ''
|
49
|
+
# Decode a connection string to Hash options
|
50
|
+
#
|
51
|
+
# Value are properly unquoted and unescaped.
|
52
|
+
def self.connect_string_to_hash( str )
|
41
53
|
options = {}
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
54
|
+
key = nil
|
55
|
+
value = String.new
|
56
|
+
str.scan(/\G\s*(?>([^\s\\\']+)\s*=\s*|([^\s\\\']+)|'((?:[^\'\\]|\\.)*)'|(\\.?)|(\S))(\s|\z)?/m) do
|
57
|
+
|k, word, sq, esc, garbage, sep|
|
58
|
+
raise ArgumentError, "unterminated quoted string in connection info string: #{str.inspect}" if garbage
|
59
|
+
if k
|
60
|
+
key = k
|
61
|
+
else
|
62
|
+
value << (word || (sq || esc).gsub(/\\(.)/, '\\1'))
|
63
|
+
end
|
64
|
+
if sep
|
65
|
+
raise ArgumentError, "missing = after #{value.inspect}" unless key
|
66
|
+
options[key.to_sym] = value
|
67
|
+
key = nil
|
68
|
+
value = String.new
|
69
|
+
end
|
47
70
|
end
|
71
|
+
options
|
72
|
+
end
|
73
|
+
|
74
|
+
# URI defined in RFC3986
|
75
|
+
# This regexp is modified to allow host to specify multiple comma separated components captured as <hostports> and to disallow comma in hostnames.
|
76
|
+
# Taken from: https://github.com/ruby/ruby/blob/be04006c7d2f9aeb7e9d8d09d945b3a9c7850202/lib/uri/rfc3986_parser.rb#L6
|
77
|
+
HOST_AND_PORT = /(?<hostport>(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[-\.!$&-+0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)/
|
78
|
+
POSTGRESQL_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<hostports>#{HOST_AND_PORT}(?:,\g<hostport>)*))(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/
|
79
|
+
|
80
|
+
# Parse the connection +args+ into a connection-parameter string.
|
81
|
+
# See PG::Connection.new for valid arguments.
|
82
|
+
#
|
83
|
+
# It accepts:
|
84
|
+
# * an option String kind of "host=name port=5432"
|
85
|
+
# * an option Hash kind of {host: "name", port: 5432}
|
86
|
+
# * URI string
|
87
|
+
# * URI object
|
88
|
+
# * positional arguments
|
89
|
+
#
|
90
|
+
# The method adds the option "hostaddr" and "fallback_application_name" if they aren't already set.
|
91
|
+
# The URI and the options string is passed through and "hostaddr" as well as "fallback_application_name"
|
92
|
+
# are added to the end.
|
93
|
+
def self::parse_connect_args( *args )
|
94
|
+
hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
|
95
|
+
option_string = ""
|
96
|
+
iopts = {}
|
48
97
|
|
49
98
|
if args.length == 1
|
50
99
|
case args.first
|
51
|
-
when URI,
|
52
|
-
uri =
|
53
|
-
|
100
|
+
when URI, POSTGRESQL_URI
|
101
|
+
uri = args.first.to_s
|
102
|
+
uri_match = POSTGRESQL_URI.match(uri)
|
103
|
+
if uri_match['query']
|
104
|
+
iopts = URI.decode_www_form(uri_match['query']).to_h.transform_keys(&:to_sym)
|
105
|
+
end
|
106
|
+
# extract "host1,host2" from "host1:5432,host2:5432"
|
107
|
+
iopts[:host] = uri_match['hostports'].split(',', -1).map do |hostport|
|
108
|
+
hostmatch = /\A#{HOST_AND_PORT}\z/.match(hostport)
|
109
|
+
hostmatch['IPv6address'] || hostmatch['IPv4address'] || hostmatch['reg-name']&.gsub(/%(\h\h)/){ $1.hex.chr }
|
110
|
+
end.join(',')
|
111
|
+
oopts = {}
|
54
112
|
when /=/
|
55
113
|
# Option string style
|
56
114
|
option_string = args.first.to_s
|
115
|
+
iopts = connect_string_to_hash(option_string)
|
116
|
+
oopts = {}
|
57
117
|
else
|
58
|
-
# Positional parameters
|
59
|
-
|
118
|
+
# Positional parameters (only host given)
|
119
|
+
iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
|
120
|
+
oopts = iopts.dup
|
60
121
|
end
|
61
122
|
else
|
123
|
+
# Positional parameters
|
62
124
|
max = CONNECT_ARGUMENT_ORDER.length
|
63
125
|
raise ArgumentError,
|
64
126
|
"Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
|
65
127
|
|
66
128
|
CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
|
67
|
-
|
129
|
+
iopts[ k.to_sym ] = v if v
|
130
|
+
end
|
131
|
+
iopts.delete(:tty) # ignore obsolete tty parameter
|
132
|
+
oopts = iopts.dup
|
133
|
+
end
|
134
|
+
|
135
|
+
iopts.merge!( hash_arg )
|
136
|
+
oopts.merge!( hash_arg )
|
137
|
+
|
138
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
|
139
|
+
if (host=iopts[:host]) && !iopts[:hostaddr]
|
140
|
+
hostaddrs = host.split(",", -1).map do |mhost|
|
141
|
+
if !mhost.empty? && !mhost.start_with?("/") && # isn't UnixSocket
|
142
|
+
# isn't a path on Windows
|
143
|
+
(RUBY_PLATFORM !~ /mingw|mswin/ || mhost !~ /\A\w:[\/\\]/)
|
144
|
+
|
145
|
+
if Fiber.respond_to?(:scheduler) &&
|
146
|
+
Fiber.scheduler &&
|
147
|
+
RUBY_VERSION < '3.1.'
|
148
|
+
|
149
|
+
# Use a second thread to avoid blocking of the scheduler.
|
150
|
+
# `IPSocket.getaddress` isn't fiber aware before ruby-3.1.
|
151
|
+
Thread.new{ IPSocket.getaddress(mhost) rescue '' }.value
|
152
|
+
else
|
153
|
+
IPSocket.getaddress(mhost) rescue ''
|
154
|
+
end
|
155
|
+
end
|
68
156
|
end
|
157
|
+
oopts[:hostaddr] = hostaddrs.join(",") if hostaddrs.any?
|
69
158
|
end
|
70
159
|
|
71
|
-
|
160
|
+
if !iopts[:fallback_application_name]
|
161
|
+
oopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
|
162
|
+
end
|
72
163
|
|
73
164
|
if uri
|
74
|
-
uri
|
75
|
-
uri.
|
76
|
-
uri
|
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}://" )
|
165
|
+
uri += uri_match['query'] ? "&" : "?"
|
166
|
+
uri += URI.encode_www_form( oopts )
|
167
|
+
return uri
|
81
168
|
else
|
82
|
-
option_string += ' ' unless option_string.empty? &&
|
83
|
-
return option_string +
|
169
|
+
option_string += ' ' unless option_string.empty? && oopts.empty?
|
170
|
+
return option_string + connect_hash_to_string(oopts)
|
84
171
|
end
|
85
172
|
end
|
86
173
|
|
@@ -88,7 +175,7 @@ class PG::Connection
|
|
88
175
|
# call-seq:
|
89
176
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
90
177
|
#
|
91
|
-
# Execute a copy process for
|
178
|
+
# Execute a copy process for transferring data to or from the server.
|
92
179
|
#
|
93
180
|
# This issues the SQL COPY command via #exec. The response to this
|
94
181
|
# (if there is no error in the command) is a PG::Result object that
|
@@ -154,6 +241,7 @@ class PG::Connection
|
|
154
241
|
# ["more", "data", "to", "copy"]
|
155
242
|
|
156
243
|
def copy_data( sql, coder=nil )
|
244
|
+
raise PG::NotInBlockingMode, "copy_data can not be used in nonblocking mode" if nonblocking?
|
157
245
|
res = exec( sql )
|
158
246
|
|
159
247
|
case res.result_status
|
@@ -214,6 +302,25 @@ class PG::Connection
|
|
214
302
|
define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
|
215
303
|
end
|
216
304
|
|
305
|
+
#
|
306
|
+
# call-seq:
|
307
|
+
# conn.transaction { |conn| ... } -> result of the block
|
308
|
+
#
|
309
|
+
# Executes a +BEGIN+ at the start of the block,
|
310
|
+
# and a +COMMIT+ at the end of the block, or
|
311
|
+
# +ROLLBACK+ if any exception occurs.
|
312
|
+
def transaction
|
313
|
+
exec "BEGIN"
|
314
|
+
res = yield(self)
|
315
|
+
rescue Exception
|
316
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
317
|
+
block
|
318
|
+
exec "ROLLBACK"
|
319
|
+
raise
|
320
|
+
else
|
321
|
+
exec "COMMIT"
|
322
|
+
res
|
323
|
+
end
|
217
324
|
|
218
325
|
### Returns an array of Hashes with connection defaults. See ::conndefaults
|
219
326
|
### for details.
|
@@ -237,17 +344,13 @@ class PG::Connection
|
|
237
344
|
return self.class.conndefaults_hash
|
238
345
|
end
|
239
346
|
|
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
|
347
|
+
### Return the Postgres connection info structure as a Hash keyed by option
|
348
|
+
### keyword (as a Symbol).
|
349
|
+
###
|
350
|
+
### See also #conninfo
|
351
|
+
def conninfo_hash
|
352
|
+
return self.conninfo.each_with_object({}) do |info, hash|
|
353
|
+
hash[ info[:keyword].to_sym ] = info[:val]
|
251
354
|
end
|
252
355
|
end
|
253
356
|
|
@@ -269,23 +372,451 @@ class PG::Connection
|
|
269
372
|
end
|
270
373
|
end
|
271
374
|
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
375
|
+
# call-seq:
|
376
|
+
# conn.get_result() -> PG::Result
|
377
|
+
# conn.get_result() {|pg_result| block }
|
378
|
+
#
|
379
|
+
# Blocks waiting for the next result from a call to
|
380
|
+
# #send_query (or another asynchronous command), and returns
|
381
|
+
# it. Returns +nil+ if no more results are available.
|
382
|
+
#
|
383
|
+
# Note: call this function repeatedly until it returns +nil+, or else
|
384
|
+
# you will not be able to issue further commands.
|
385
|
+
#
|
386
|
+
# If the optional code block is given, it will be passed <i>result</i> as an argument,
|
387
|
+
# and the PG::Result object will automatically be cleared when the block terminates.
|
388
|
+
# In this instance, <code>conn.exec</code> returns the value of the block.
|
389
|
+
def get_result
|
390
|
+
block
|
391
|
+
sync_get_result
|
392
|
+
end
|
393
|
+
alias async_get_result get_result
|
394
|
+
|
395
|
+
# call-seq:
|
396
|
+
# conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
|
397
|
+
#
|
398
|
+
# Return one row of data, +nil+
|
399
|
+
# if the copy is done, or +false+ if the call would
|
400
|
+
# block (only possible if _nonblock_ is true).
|
401
|
+
#
|
402
|
+
# If _decoder_ is not set or +nil+, data is returned as binary string.
|
403
|
+
#
|
404
|
+
# If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
|
405
|
+
# PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
|
406
|
+
# COPY text format to an Array of Strings.
|
407
|
+
# Optionally the decoder can type cast the single fields to various Ruby types in one step,
|
408
|
+
# if PG::TextDecoder::CopyRow#type_map is set accordingly.
|
409
|
+
#
|
410
|
+
# See also #copy_data.
|
411
|
+
#
|
412
|
+
def get_copy_data(async=false, decoder=nil)
|
413
|
+
if async
|
414
|
+
return sync_get_copy_data(async, decoder)
|
415
|
+
else
|
416
|
+
while (res=sync_get_copy_data(true, decoder)) == false
|
417
|
+
socket_io.wait_readable
|
418
|
+
consume_input
|
419
|
+
end
|
420
|
+
return res
|
421
|
+
end
|
422
|
+
end
|
423
|
+
alias async_get_copy_data get_copy_data
|
424
|
+
|
425
|
+
|
426
|
+
# In async_api=true mode (default) all send calls run nonblocking.
|
427
|
+
# The difference is that setnonblocking(true) disables automatic handling of would-block cases.
|
428
|
+
# In async_api=false mode all send calls run directly on libpq.
|
429
|
+
# Blocking vs. nonblocking state can be changed in libpq.
|
430
|
+
|
431
|
+
# call-seq:
|
432
|
+
# conn.setnonblocking(Boolean) -> nil
|
433
|
+
#
|
434
|
+
# Sets the nonblocking status of the connection.
|
435
|
+
# In the blocking state, calls to #send_query
|
436
|
+
# will block until the message is sent to the server,
|
437
|
+
# but will not wait for the query results.
|
438
|
+
# In the nonblocking state, calls to #send_query
|
439
|
+
# will return an error if the socket is not ready for
|
440
|
+
# writing.
|
441
|
+
# Note: This function does not affect #exec, because
|
442
|
+
# that function doesn't return until the server has
|
443
|
+
# processed the query and returned the results.
|
444
|
+
#
|
445
|
+
# Returns +nil+.
|
446
|
+
def setnonblocking(enabled)
|
447
|
+
singleton_class.async_send_api = !enabled
|
448
|
+
self.flush_data = !enabled
|
449
|
+
sync_setnonblocking(true)
|
450
|
+
end
|
451
|
+
alias async_setnonblocking setnonblocking
|
452
|
+
|
453
|
+
# sync/async isnonblocking methods are switched by async_setnonblocking()
|
454
|
+
|
455
|
+
# call-seq:
|
456
|
+
# conn.isnonblocking() -> Boolean
|
457
|
+
#
|
458
|
+
# Returns the blocking status of the database connection.
|
459
|
+
# Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
|
460
|
+
def isnonblocking
|
461
|
+
false
|
462
|
+
end
|
463
|
+
alias async_isnonblocking isnonblocking
|
464
|
+
alias nonblocking? isnonblocking
|
465
|
+
|
466
|
+
# call-seq:
|
467
|
+
# conn.put_copy_data( buffer [, encoder] ) -> Boolean
|
468
|
+
#
|
469
|
+
# Transmits _buffer_ as copy data to the server.
|
470
|
+
# Returns true if the data was sent, false if it was
|
471
|
+
# not sent (false is only possible if the connection
|
472
|
+
# is in nonblocking mode, and this command would block).
|
473
|
+
#
|
474
|
+
# _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
|
475
|
+
# This encodes the data fields given as _buffer_ from an Array of Strings to
|
476
|
+
# PostgreSQL's COPY text format inclusive proper escaping. Optionally
|
477
|
+
# the encoder can type cast the fields from various Ruby types in one step,
|
478
|
+
# if PG::TextEncoder::CopyRow#type_map is set accordingly.
|
479
|
+
#
|
480
|
+
# Raises an exception if an error occurs.
|
481
|
+
#
|
482
|
+
# See also #copy_data.
|
483
|
+
#
|
484
|
+
def put_copy_data(buffer, encoder=nil)
|
485
|
+
until sync_put_copy_data(buffer, encoder)
|
486
|
+
flush
|
487
|
+
end
|
488
|
+
flush
|
489
|
+
end
|
490
|
+
alias async_put_copy_data put_copy_data
|
491
|
+
|
492
|
+
# call-seq:
|
493
|
+
# conn.put_copy_end( [ error_message ] ) -> Boolean
|
494
|
+
#
|
495
|
+
# Sends end-of-data indication to the server.
|
496
|
+
#
|
497
|
+
# _error_message_ is an optional parameter, and if set,
|
498
|
+
# forces the COPY command to fail with the string
|
499
|
+
# _error_message_.
|
500
|
+
#
|
501
|
+
# Returns true if the end-of-data was sent, #false* if it was
|
502
|
+
# not sent (*false* is only possible if the connection
|
503
|
+
# is in nonblocking mode, and this command would block).
|
504
|
+
def put_copy_end(*args)
|
505
|
+
until sync_put_copy_end(*args)
|
506
|
+
flush
|
507
|
+
end
|
508
|
+
flush
|
509
|
+
end
|
510
|
+
alias async_put_copy_end put_copy_end
|
511
|
+
|
512
|
+
if method_defined? :sync_encrypt_password
|
513
|
+
# call-seq:
|
514
|
+
# conn.encrypt_password( password, username, algorithm=nil ) -> String
|
515
|
+
#
|
516
|
+
# This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
|
517
|
+
# 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.
|
518
|
+
# Instead, use this function to convert the password to encrypted form before it is sent.
|
519
|
+
#
|
520
|
+
# The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
|
521
|
+
# +algorithm+ specifies the encryption algorithm to use to encrypt the password.
|
522
|
+
# 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).
|
523
|
+
# Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
|
524
|
+
# If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
|
525
|
+
# That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
|
526
|
+
# 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.
|
527
|
+
#
|
528
|
+
# Return value is the encrypted password.
|
529
|
+
# The caller can assume the string doesn't contain any special characters that would require escaping.
|
530
|
+
#
|
531
|
+
# Available since PostgreSQL-10.
|
532
|
+
# See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
|
533
|
+
def encrypt_password( password, username, algorithm=nil )
|
534
|
+
algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
|
535
|
+
sync_encrypt_password(password, username, algorithm)
|
536
|
+
end
|
537
|
+
alias async_encrypt_password encrypt_password
|
538
|
+
end
|
539
|
+
|
540
|
+
# call-seq:
|
541
|
+
# conn.reset()
|
542
|
+
#
|
543
|
+
# Resets the backend connection. This method closes the
|
544
|
+
# backend connection and tries to re-connect.
|
545
|
+
def reset
|
546
|
+
reset_start
|
547
|
+
async_connect_or_reset(:reset_poll)
|
548
|
+
end
|
549
|
+
alias async_reset reset
|
550
|
+
|
551
|
+
# call-seq:
|
552
|
+
# conn.cancel() -> String
|
553
|
+
#
|
554
|
+
# Requests cancellation of the command currently being
|
555
|
+
# processed.
|
556
|
+
#
|
557
|
+
# Returns +nil+ on success, or a string containing the
|
558
|
+
# error message if a failure occurs.
|
559
|
+
def cancel
|
560
|
+
be_pid = backend_pid
|
561
|
+
be_key = backend_key
|
562
|
+
cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
|
563
|
+
|
564
|
+
if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
|
565
|
+
# Ruby's nonblocking IO is not really supported on Windows.
|
566
|
+
# We work around by using threads and explicit calls to wait_readable/wait_writable.
|
567
|
+
cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
|
568
|
+
begin
|
569
|
+
cl.write_nonblock(cancel_request)
|
570
|
+
rescue IO::WaitReadable, Errno::EINTR
|
571
|
+
cl.wait_writable
|
572
|
+
retry
|
573
|
+
end
|
574
|
+
begin
|
575
|
+
cl.read_nonblock(1)
|
576
|
+
rescue IO::WaitReadable, Errno::EINTR
|
577
|
+
cl.wait_readable
|
578
|
+
retry
|
579
|
+
rescue EOFError
|
580
|
+
end
|
581
|
+
elsif RUBY_ENGINE == 'truffleruby'
|
582
|
+
begin
|
583
|
+
cl = socket_io.remote_address.connect
|
584
|
+
rescue NotImplementedError
|
585
|
+
# Workaround for truffleruby < 21.3.0
|
586
|
+
cl2 = Socket.for_fd(socket_io.fileno)
|
587
|
+
cl2.autoclose = false
|
588
|
+
adr = cl2.remote_address
|
589
|
+
if adr.ip?
|
590
|
+
cl = TCPSocket.new(adr.ip_address, adr.ip_port)
|
591
|
+
cl.autoclose = false
|
592
|
+
else
|
593
|
+
cl = UNIXSocket.new(adr.unix_path)
|
594
|
+
cl.autoclose = false
|
595
|
+
end
|
596
|
+
end
|
597
|
+
cl.write(cancel_request)
|
598
|
+
cl.read(1)
|
599
|
+
else
|
600
|
+
cl = socket_io.remote_address.connect
|
601
|
+
# Send CANCEL_REQUEST_CODE and parameters
|
602
|
+
cl.write(cancel_request)
|
603
|
+
# Wait for the postmaster to close the connection, which indicates that it's processed the request.
|
604
|
+
cl.read(1)
|
605
|
+
end
|
606
|
+
|
607
|
+
cl.close
|
608
|
+
nil
|
609
|
+
rescue SystemCallError => err
|
610
|
+
err.to_s
|
611
|
+
end
|
612
|
+
alias async_cancel cancel
|
613
|
+
|
614
|
+
private def async_connect_or_reset(poll_meth)
|
615
|
+
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
616
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
617
|
+
until poll_status == PG::PGRES_POLLING_OK ||
|
618
|
+
poll_status == PG::PGRES_POLLING_FAILED
|
619
|
+
|
620
|
+
# If the socket needs to read, wait 'til it becomes readable to poll again
|
621
|
+
case poll_status
|
622
|
+
when PG::PGRES_POLLING_READING
|
623
|
+
socket_io.wait_readable
|
624
|
+
|
625
|
+
# ...and the same for when the socket needs to write
|
626
|
+
when PG::PGRES_POLLING_WRITING
|
627
|
+
socket_io.wait_writable
|
628
|
+
end
|
629
|
+
|
630
|
+
# Check to see if it's finished or failed yet
|
631
|
+
poll_status = send( poll_meth )
|
632
|
+
end
|
633
|
+
|
634
|
+
unless status == PG::CONNECTION_OK
|
635
|
+
msg = error_message
|
636
|
+
finish
|
637
|
+
raise PG::ConnectionBad, msg
|
638
|
+
end
|
639
|
+
|
640
|
+
# Set connection to nonblocking to handle all blocking states in ruby.
|
641
|
+
# That way a fiber scheduler is able to handle IO requests.
|
642
|
+
sync_setnonblocking(true)
|
643
|
+
self.flush_data = true
|
644
|
+
set_default_encoding
|
645
|
+
|
646
|
+
self
|
647
|
+
end
|
648
|
+
|
649
|
+
class << self
|
650
|
+
# call-seq:
|
651
|
+
# PG::Connection.new -> conn
|
652
|
+
# PG::Connection.new(connection_hash) -> conn
|
653
|
+
# PG::Connection.new(connection_string) -> conn
|
654
|
+
# PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
|
655
|
+
#
|
656
|
+
# Create a connection to the specified server.
|
657
|
+
#
|
658
|
+
# +connection_hash+ must be a ruby Hash with connection parameters.
|
659
|
+
# See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
|
660
|
+
#
|
661
|
+
# There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
|
662
|
+
# See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
|
663
|
+
#
|
664
|
+
# The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
|
665
|
+
# [+host+]
|
666
|
+
# server hostname
|
667
|
+
# [+port+]
|
668
|
+
# server port number
|
669
|
+
# [+options+]
|
670
|
+
# backend options
|
671
|
+
# [+tty+]
|
672
|
+
# (ignored in all versions of PostgreSQL)
|
673
|
+
# [+dbname+]
|
674
|
+
# connecting database name
|
675
|
+
# [+user+]
|
676
|
+
# login user name
|
677
|
+
# [+password+]
|
678
|
+
# login password
|
679
|
+
#
|
680
|
+
# Examples:
|
681
|
+
#
|
682
|
+
# # Connect using all defaults
|
683
|
+
# PG::Connection.new
|
684
|
+
#
|
685
|
+
# # As a Hash
|
686
|
+
# PG::Connection.new( dbname: 'test', port: 5432 )
|
687
|
+
#
|
688
|
+
# # As a String
|
689
|
+
# PG::Connection.new( "dbname=test port=5432" )
|
690
|
+
#
|
691
|
+
# # As an Array
|
692
|
+
# PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
|
693
|
+
#
|
694
|
+
# # As an URI
|
695
|
+
# PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
|
696
|
+
#
|
697
|
+
# If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
|
698
|
+
# connection will have its +client_encoding+ set accordingly.
|
699
|
+
#
|
700
|
+
# Raises a PG::Error if the connection fails.
|
701
|
+
def new(*args, **kwargs)
|
702
|
+
conn = self.connect_start(*args, **kwargs ) or
|
703
|
+
raise(PG::Error, "Unable to create a new connection")
|
704
|
+
|
705
|
+
raise(PG::ConnectionBad, conn.error_message) if conn.status == PG::CONNECTION_BAD
|
706
|
+
|
707
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
708
|
+
end
|
709
|
+
alias async_connect new
|
710
|
+
alias connect new
|
711
|
+
alias open new
|
712
|
+
alias setdb new
|
713
|
+
alias setdblogin new
|
714
|
+
|
715
|
+
# call-seq:
|
716
|
+
# PG::Connection.ping(connection_hash) -> Integer
|
717
|
+
# PG::Connection.ping(connection_string) -> Integer
|
718
|
+
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
719
|
+
#
|
720
|
+
# Check server status.
|
721
|
+
#
|
722
|
+
# See PG::Connection.new for a description of the parameters.
|
723
|
+
#
|
724
|
+
# Returns one of:
|
725
|
+
# [+PQPING_OK+]
|
726
|
+
# server is accepting connections
|
727
|
+
# [+PQPING_REJECT+]
|
728
|
+
# server is alive but rejecting connections
|
729
|
+
# [+PQPING_NO_RESPONSE+]
|
730
|
+
# could not establish connection
|
731
|
+
# [+PQPING_NO_ATTEMPT+]
|
732
|
+
# connection not attempted (bad params)
|
733
|
+
def ping(*args)
|
734
|
+
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
735
|
+
# Run PQping in a second thread to avoid blocking of the scheduler.
|
736
|
+
# Unfortunately there's no nonblocking way to run ping.
|
737
|
+
Thread.new { sync_ping(*args) }.value
|
738
|
+
else
|
739
|
+
sync_ping(*args)
|
740
|
+
end
|
741
|
+
end
|
742
|
+
alias async_ping ping
|
743
|
+
|
744
|
+
REDIRECT_CLASS_METHODS = {
|
745
|
+
:new => [:async_connect, :sync_connect],
|
746
|
+
:connect => [:async_connect, :sync_connect],
|
747
|
+
:open => [:async_connect, :sync_connect],
|
748
|
+
:setdb => [:async_connect, :sync_connect],
|
749
|
+
:setdblogin => [:async_connect, :sync_connect],
|
750
|
+
:ping => [:async_ping, :sync_ping],
|
751
|
+
}
|
752
|
+
|
753
|
+
# These methods are affected by PQsetnonblocking
|
754
|
+
REDIRECT_SEND_METHODS = {
|
755
|
+
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
756
|
+
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
757
|
+
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
758
|
+
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
759
|
+
:flush => [:async_flush, :sync_flush],
|
760
|
+
}
|
761
|
+
REDIRECT_METHODS = {
|
762
|
+
:exec => [:async_exec, :sync_exec],
|
763
|
+
:query => [:async_exec, :sync_exec],
|
764
|
+
:exec_params => [:async_exec_params, :sync_exec_params],
|
765
|
+
:prepare => [:async_prepare, :sync_prepare],
|
766
|
+
:exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
|
767
|
+
:describe_portal => [:async_describe_portal, :sync_describe_portal],
|
768
|
+
:describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
|
769
|
+
:setnonblocking => [:async_setnonblocking, :sync_setnonblocking],
|
770
|
+
:get_result => [:async_get_result, :sync_get_result],
|
771
|
+
:get_last_result => [:async_get_last_result, :sync_get_last_result],
|
772
|
+
:get_copy_data => [:async_get_copy_data, :sync_get_copy_data],
|
773
|
+
:reset => [:async_reset, :sync_reset],
|
774
|
+
:set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
|
775
|
+
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
776
|
+
:cancel => [:async_cancel, :sync_cancel],
|
777
|
+
}
|
778
|
+
|
779
|
+
if PG::Connection.instance_methods.include? :async_encrypt_password
|
780
|
+
REDIRECT_METHODS.merge!({
|
781
|
+
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
782
|
+
})
|
783
|
+
end
|
784
|
+
|
785
|
+
def async_send_api=(enable)
|
786
|
+
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
787
|
+
undef_method(ali) if method_defined?(ali)
|
788
|
+
alias_method( ali, enable ? async : sync )
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
# Switch between sync and async libpq API.
|
793
|
+
#
|
794
|
+
# PG::Connection.async_api = true
|
795
|
+
# this is the default.
|
796
|
+
# It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
|
797
|
+
#
|
798
|
+
# PG::Connection.async_api = false
|
799
|
+
# sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
|
800
|
+
#
|
801
|
+
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
|
802
|
+
# pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
|
803
|
+
#
|
804
|
+
# _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
|
805
|
+
# Do not use this method in production code.
|
806
|
+
# Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
|
807
|
+
#
|
808
|
+
def async_api=(enable)
|
809
|
+
self.async_send_api = enable
|
810
|
+
REDIRECT_METHODS.each do |ali, (async, sync)|
|
811
|
+
remove_method(ali) if method_defined?(ali)
|
812
|
+
alias_method( ali, enable ? async : sync )
|
813
|
+
end
|
814
|
+
REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
|
815
|
+
singleton_class.remove_method(ali) if method_defined?(ali)
|
816
|
+
singleton_class.alias_method(ali, enable ? async : sync )
|
817
|
+
end
|
286
818
|
end
|
287
819
|
end
|
288
820
|
|
289
|
-
# pg-1.1.0+ defaults to libpq's async API for query related blocking methods
|
290
821
|
self.async_api = true
|
291
822
|
end # class PG::Connection
|