pg 1.3.4 → 1.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/History.rdoc +51 -0
- data/Rakefile.cross +2 -2
- data/ext/pg_connection.c +218 -187
- data/ext/pg_record_coder.c +6 -4
- data/ext/pg_result.c +3 -2
- data/lib/pg/basic_type_registry.rb +8 -3
- data/lib/pg/connection.rb +174 -118
- data/lib/pg/exceptions.rb +7 -1
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +4 -4
- data.tar.gz.sig +0 -0
- metadata +3 -3
- metadata.gz.sig +0 -0
data/ext/pg_record_coder.c
CHANGED
@@ -344,10 +344,12 @@ record_isspace(char ch)
|
|
344
344
|
* oids = conn.exec( "SELECT (NULL::complex).*" )
|
345
345
|
* # Build a type map (PG::TypeMapByColumn) for decoding the "complex" type
|
346
346
|
* dtm = PG::BasicTypeMapForResults.new(conn).build_column_map( oids )
|
347
|
-
* #
|
348
|
-
* PG::BasicTypeRegistry.
|
349
|
-
* #
|
350
|
-
*
|
347
|
+
* # Build a type map and populate with basic types
|
348
|
+
* btr = PG::BasicTypeRegistry.new.register_default_types
|
349
|
+
* # Register a new record decoder for decoding our type "complex"
|
350
|
+
* btr.register_coder(PG::TextDecoder::Record.new(type_map: dtm, name: "complex"))
|
351
|
+
* # Apply our basic type registry to all results retrieved from the server
|
352
|
+
* conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: btr)
|
351
353
|
* # Now queries decode the "complex" type (and many basic types) automatically
|
352
354
|
* conn.exec("SELECT * FROM my_table").to_a
|
353
355
|
* # => [{"v1"=>[2.0, 3.0], "v2"=>[4.0, 5.0]}, {"v1"=>[6.0, 7.0], "v2"=>[8.0, 9.0]}]
|
data/ext/pg_result.c
CHANGED
@@ -1457,6 +1457,7 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* d
|
|
1457
1457
|
|
1458
1458
|
switch( PQresultStatus(pgresult) ){
|
1459
1459
|
case PGRES_TUPLES_OK:
|
1460
|
+
case PGRES_COMMAND_OK:
|
1460
1461
|
if( ntuples == 0 )
|
1461
1462
|
return self;
|
1462
1463
|
rb_raise( rb_eInvalidResultStatus, "PG::Result is not in single row mode");
|
@@ -1475,10 +1476,10 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* d
|
|
1475
1476
|
|
1476
1477
|
pgresult = gvl_PQgetResult(pgconn);
|
1477
1478
|
if( pgresult == NULL )
|
1478
|
-
rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another
|
1479
|
+
rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another query");
|
1479
1480
|
|
1480
1481
|
if( nfields != PQnfields(pgresult) )
|
1481
|
-
rb_raise( rb_eInvalidChangeOfResultFields, "number of fields
|
1482
|
+
rb_raise( rb_eInvalidChangeOfResultFields, "number of fields changed in single row mode from %d to %d - this is a sign for intersection with another query", nfields, PQnfields(pgresult));
|
1482
1483
|
|
1483
1484
|
this->pgresult = pgresult;
|
1484
1485
|
}
|
@@ -22,7 +22,7 @@ require 'pg' unless defined?( PG )
|
|
22
22
|
# end
|
23
23
|
#
|
24
24
|
# conn = PG.connect
|
25
|
-
# regi = PG::BasicTypeRegistry.new.
|
25
|
+
# regi = PG::BasicTypeRegistry.new.register_default_types
|
26
26
|
# regi.register_type(0, 'inet', InetEncoder, InetDecoder)
|
27
27
|
# conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn, registry: regi)
|
28
28
|
class PG::BasicTypeRegistry
|
@@ -184,6 +184,7 @@ class PG::BasicTypeRegistry
|
|
184
184
|
name = coder.name || raise(ArgumentError, "name of #{coder.inspect} must be defined")
|
185
185
|
h[:encoder][name] = coder if coder.respond_to?(:encode)
|
186
186
|
h[:decoder][name] = coder if coder.respond_to?(:decode)
|
187
|
+
self
|
187
188
|
end
|
188
189
|
|
189
190
|
# Register the given +encoder_class+ and/or +decoder_class+ for casting a PostgreSQL type.
|
@@ -193,6 +194,7 @@ class PG::BasicTypeRegistry
|
|
193
194
|
def register_type(format, name, encoder_class, decoder_class)
|
194
195
|
register_coder(encoder_class.new(name: name, format: format)) if encoder_class
|
195
196
|
register_coder(decoder_class.new(name: name, format: format)) if decoder_class
|
197
|
+
self
|
196
198
|
end
|
197
199
|
|
198
200
|
# Alias the +old+ type to the +new+ type.
|
@@ -205,10 +207,11 @@ class PG::BasicTypeRegistry
|
|
205
207
|
@coders_by_name[format][ende].delete(new)
|
206
208
|
end
|
207
209
|
end
|
210
|
+
self
|
208
211
|
end
|
209
212
|
|
210
213
|
# Populate the registry with all builtin types of ruby-pg
|
211
|
-
def
|
214
|
+
def register_default_types
|
212
215
|
register_type 0, 'int2', PG::TextEncoder::Integer, PG::TextDecoder::Integer
|
213
216
|
alias_type 0, 'int4', 'int2'
|
214
217
|
alias_type 0, 'int8', 'int2'
|
@@ -281,8 +284,10 @@ class PG::BasicTypeRegistry
|
|
281
284
|
self
|
282
285
|
end
|
283
286
|
|
287
|
+
alias define_default_types register_default_types
|
288
|
+
|
284
289
|
# @private
|
285
|
-
DEFAULT_TYPE_REGISTRY = PG::BasicTypeRegistry.new.
|
290
|
+
DEFAULT_TYPE_REGISTRY = PG::BasicTypeRegistry.new.register_default_types
|
286
291
|
|
287
292
|
# Delegate class method calls to DEFAULT_TYPE_REGISTRY
|
288
293
|
class << self
|
data/lib/pg/connection.rb
CHANGED
@@ -46,37 +46,6 @@ class PG::Connection
|
|
46
46
|
hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
|
47
47
|
end
|
48
48
|
|
49
|
-
# Decode a connection string to Hash options
|
50
|
-
#
|
51
|
-
# Value are properly unquoted and unescaped.
|
52
|
-
def self.connect_string_to_hash( str )
|
53
|
-
options = {}
|
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
|
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
49
|
# Parse the connection +args+ into a connection-parameter string.
|
81
50
|
# See PG::Connection.new for valid arguments.
|
82
51
|
#
|
@@ -87,91 +56,43 @@ class PG::Connection
|
|
87
56
|
# * URI object
|
88
57
|
# * positional arguments
|
89
58
|
#
|
90
|
-
# The method adds the option "
|
91
|
-
#
|
92
|
-
|
93
|
-
def self::parse_connect_args( *args )
|
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 )
|
94
62
|
hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
|
95
|
-
option_string = ""
|
96
63
|
iopts = {}
|
97
64
|
|
98
65
|
if args.length == 1
|
99
66
|
case args.first
|
100
|
-
when URI,
|
101
|
-
|
102
|
-
|
103
|
-
if
|
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 = {}
|
112
|
-
when /=/
|
113
|
-
# Option string style
|
114
|
-
option_string = args.first.to_s
|
115
|
-
iopts = connect_string_to_hash(option_string)
|
116
|
-
oopts = {}
|
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] }
|
117
71
|
else
|
118
72
|
# Positional parameters (only host given)
|
119
73
|
iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
|
120
|
-
oopts = iopts.dup
|
121
74
|
end
|
122
75
|
else
|
123
|
-
# Positional parameters
|
76
|
+
# Positional parameters with host and more
|
124
77
|
max = CONNECT_ARGUMENT_ORDER.length
|
125
78
|
raise ArgumentError,
|
126
|
-
|
79
|
+
"Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
|
127
80
|
|
128
81
|
CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
|
129
82
|
iopts[ k.to_sym ] = v if v
|
130
83
|
end
|
131
84
|
iopts.delete(:tty) # ignore obsolete tty parameter
|
132
|
-
oopts = iopts.dup
|
133
85
|
end
|
134
86
|
|
135
87
|
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
|
156
|
-
end
|
157
|
-
oopts[:hostaddr] = hostaddrs.join(",") if hostaddrs.any?
|
158
|
-
end
|
159
88
|
|
160
89
|
if !iopts[:fallback_application_name]
|
161
|
-
|
90
|
+
iopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
|
162
91
|
end
|
163
92
|
|
164
|
-
|
165
|
-
uri += uri_match['query'] ? "&" : "?"
|
166
|
-
uri += URI.encode_www_form( oopts )
|
167
|
-
return uri
|
168
|
-
else
|
169
|
-
option_string += ' ' unless option_string.empty? && oopts.empty?
|
170
|
-
return option_string + connect_hash_to_string(oopts)
|
171
|
-
end
|
93
|
+
return connect_hash_to_string(iopts)
|
172
94
|
end
|
173
95
|
|
174
|
-
|
175
96
|
# call-seq:
|
176
97
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
177
98
|
#
|
@@ -241,7 +162,7 @@ class PG::Connection
|
|
241
162
|
# ["more", "data", "to", "copy"]
|
242
163
|
|
243
164
|
def copy_data( sql, coder=nil )
|
244
|
-
raise PG::NotInBlockingMode
|
165
|
+
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
245
166
|
res = exec( sql )
|
246
167
|
|
247
168
|
case res.result_status
|
@@ -273,11 +194,15 @@ class PG::Connection
|
|
273
194
|
yield res
|
274
195
|
rescue Exception => err
|
275
196
|
cancel
|
276
|
-
|
197
|
+
begin
|
198
|
+
while get_copy_data
|
199
|
+
end
|
200
|
+
rescue PG::Error
|
201
|
+
# Ignore error in cleanup to avoid losing original exception
|
277
202
|
end
|
278
203
|
while get_result
|
279
204
|
end
|
280
|
-
raise
|
205
|
+
raise err
|
281
206
|
else
|
282
207
|
res = get_last_result
|
283
208
|
if !res || res.result_status != PGRES_COMMAND_OK
|
@@ -285,7 +210,7 @@ class PG::Connection
|
|
285
210
|
end
|
286
211
|
while get_result
|
287
212
|
end
|
288
|
-
raise PG::NotAllCopyDataRetrieved
|
213
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
289
214
|
end
|
290
215
|
res
|
291
216
|
ensure
|
@@ -310,16 +235,17 @@ class PG::Connection
|
|
310
235
|
# and a +COMMIT+ at the end of the block, or
|
311
236
|
# +ROLLBACK+ if any exception occurs.
|
312
237
|
def transaction
|
238
|
+
rollback = false
|
313
239
|
exec "BEGIN"
|
314
|
-
|
240
|
+
yield(self)
|
315
241
|
rescue Exception
|
242
|
+
rollback = true
|
316
243
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
317
244
|
block
|
318
245
|
exec "ROLLBACK"
|
319
246
|
raise
|
320
|
-
|
321
|
-
exec "COMMIT"
|
322
|
-
res
|
247
|
+
ensure
|
248
|
+
exec "COMMIT" unless rollback
|
323
249
|
end
|
324
250
|
|
325
251
|
### Returns an array of Hashes with connection defaults. See ::conndefaults
|
@@ -482,10 +408,10 @@ class PG::Connection
|
|
482
408
|
# See also #copy_data.
|
483
409
|
#
|
484
410
|
def put_copy_data(buffer, encoder=nil)
|
485
|
-
until sync_put_copy_data(buffer, encoder)
|
486
|
-
flush
|
411
|
+
until res=sync_put_copy_data(buffer, encoder)
|
412
|
+
res = flush
|
487
413
|
end
|
488
|
-
|
414
|
+
res
|
489
415
|
end
|
490
416
|
alias async_put_copy_data put_copy_data
|
491
417
|
|
@@ -545,6 +471,7 @@ class PG::Connection
|
|
545
471
|
def reset
|
546
472
|
reset_start
|
547
473
|
async_connect_or_reset(:reset_poll)
|
474
|
+
self
|
548
475
|
end
|
549
476
|
alias async_reset reset
|
550
477
|
|
@@ -613,28 +540,62 @@ class PG::Connection
|
|
613
540
|
|
614
541
|
private def async_connect_or_reset(poll_meth)
|
615
542
|
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
543
|
+
|
544
|
+
if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
|
545
|
+
# Lowest timeout is 2 seconds - like in libpq
|
546
|
+
timeo = [timeo, 2].max
|
547
|
+
stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
548
|
+
end
|
549
|
+
|
616
550
|
poll_status = PG::PGRES_POLLING_WRITING
|
617
551
|
until poll_status == PG::PGRES_POLLING_OK ||
|
618
552
|
poll_status == PG::PGRES_POLLING_FAILED
|
619
553
|
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
554
|
+
timeout = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
555
|
+
event = if !timeout || timeout >= 0
|
556
|
+
# If the socket needs to read, wait 'til it becomes readable to poll again
|
557
|
+
case poll_status
|
558
|
+
when PG::PGRES_POLLING_READING
|
559
|
+
if defined?(IO::READABLE) # ruby-3.0+
|
560
|
+
socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
|
561
|
+
else
|
562
|
+
IO.select([socket_io], nil, [socket_io], timeout)
|
563
|
+
end
|
624
564
|
|
625
|
-
|
626
|
-
|
627
|
-
|
565
|
+
# ...and the same for when the socket needs to write
|
566
|
+
when PG::PGRES_POLLING_WRITING
|
567
|
+
if defined?(IO::WRITABLE) # ruby-3.0+
|
568
|
+
# Use wait instead of wait_readable, since connection errors are delivered as
|
569
|
+
# exceptional/priority events on Windows.
|
570
|
+
socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
|
571
|
+
else
|
572
|
+
# io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
|
573
|
+
IO.select(nil, [socket_io], [socket_io], timeout)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
# connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
|
578
|
+
# connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
|
579
|
+
unless event
|
580
|
+
if self.class.send(:host_is_named_pipe?, host)
|
581
|
+
connhost = "on socket \"#{host}\""
|
582
|
+
elsif respond_to?(:hostaddr)
|
583
|
+
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
584
|
+
else
|
585
|
+
connhost = "at \"#{host}\", port #{port}"
|
586
|
+
end
|
587
|
+
raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
|
628
588
|
end
|
629
589
|
|
630
590
|
# Check to see if it's finished or failed yet
|
631
591
|
poll_status = send( poll_meth )
|
592
|
+
@last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
|
632
593
|
end
|
633
594
|
|
634
595
|
unless status == PG::CONNECTION_OK
|
635
596
|
msg = error_message
|
636
597
|
finish
|
637
|
-
raise PG::ConnectionBad,
|
598
|
+
raise PG::ConnectionBad.new(msg, connection: self)
|
638
599
|
end
|
639
600
|
|
640
601
|
# Set connection to nonblocking to handle all blocking states in ruby.
|
@@ -642,8 +603,6 @@ class PG::Connection
|
|
642
603
|
sync_setnonblocking(true)
|
643
604
|
self.flush_data = true
|
644
605
|
set_default_encoding
|
645
|
-
|
646
|
-
self
|
647
606
|
end
|
648
607
|
|
649
608
|
class << self
|
@@ -698,13 +657,17 @@ class PG::Connection
|
|
698
657
|
# connection will have its +client_encoding+ set accordingly.
|
699
658
|
#
|
700
659
|
# Raises a PG::Error if the connection fails.
|
701
|
-
def new(*args
|
702
|
-
conn =
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
660
|
+
def new(*args)
|
661
|
+
conn = connect_to_hosts(*args)
|
662
|
+
|
663
|
+
if block_given?
|
664
|
+
begin
|
665
|
+
return yield conn
|
666
|
+
ensure
|
667
|
+
conn.finish
|
668
|
+
end
|
669
|
+
end
|
670
|
+
conn
|
708
671
|
end
|
709
672
|
alias async_connect new
|
710
673
|
alias connect new
|
@@ -712,6 +675,99 @@ class PG::Connection
|
|
712
675
|
alias setdb new
|
713
676
|
alias setdblogin new
|
714
677
|
|
678
|
+
private def connect_to_hosts(*args)
|
679
|
+
option_string = parse_connect_args(*args)
|
680
|
+
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
681
|
+
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
682
|
+
|
683
|
+
errors = []
|
684
|
+
if iopts[:hostaddr]
|
685
|
+
# hostaddr is provided -> no need to resolve hostnames
|
686
|
+
ihostaddrs = iopts[:hostaddr].split(",", -1)
|
687
|
+
|
688
|
+
ihosts = iopts[:host].split(",", -1) if iopts[:host]
|
689
|
+
raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size
|
690
|
+
|
691
|
+
iports = iopts[:port].split(",", -1)
|
692
|
+
iports = iports * ihostaddrs.size if iports.size == 1
|
693
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihostaddrs.size} hosts" if iports.size != ihostaddrs.size
|
694
|
+
|
695
|
+
# Try to connect to each hostaddr with separate timeout
|
696
|
+
ihostaddrs.each_with_index do |ihostaddr, idx|
|
697
|
+
oopts = iopts.merge(hostaddr: ihostaddr, port: iports[idx])
|
698
|
+
oopts[:host] = ihosts[idx] if ihosts
|
699
|
+
c = connect_internal(oopts, errors)
|
700
|
+
return c if c
|
701
|
+
end
|
702
|
+
elsif iopts[:host]
|
703
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
|
704
|
+
ihosts = iopts[:host].split(",", -1)
|
705
|
+
|
706
|
+
iports = iopts[:port].split(",", -1)
|
707
|
+
iports = iports * ihosts.size if iports.size == 1
|
708
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
709
|
+
|
710
|
+
ihosts.each_with_index do |mhost, idx|
|
711
|
+
unless host_is_named_pipe?(mhost)
|
712
|
+
addrs = if Fiber.respond_to?(:scheduler) &&
|
713
|
+
Fiber.scheduler &&
|
714
|
+
RUBY_VERSION < '3.1.'
|
715
|
+
|
716
|
+
# Use a second thread to avoid blocking of the scheduler.
|
717
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
718
|
+
Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
719
|
+
else
|
720
|
+
Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
721
|
+
end
|
722
|
+
|
723
|
+
# Try to connect to each host with separate timeout
|
724
|
+
addrs.each do |addr|
|
725
|
+
oopts = iopts.merge(hostaddr: addr, host: mhost, port: iports[idx])
|
726
|
+
c = connect_internal(oopts, errors)
|
727
|
+
return c if c
|
728
|
+
end
|
729
|
+
else
|
730
|
+
# No hostname to resolve (UnixSocket)
|
731
|
+
oopts = iopts.merge(host: mhost, port: iports[idx])
|
732
|
+
c = connect_internal(oopts, errors)
|
733
|
+
return c if c
|
734
|
+
end
|
735
|
+
end
|
736
|
+
else
|
737
|
+
# No host given
|
738
|
+
return connect_internal(iopts)
|
739
|
+
end
|
740
|
+
raise PG::ConnectionBad, errors.join("\n")
|
741
|
+
end
|
742
|
+
|
743
|
+
private def connect_internal(opts, errors=nil)
|
744
|
+
begin
|
745
|
+
conn = self.connect_start(opts) or
|
746
|
+
raise(PG::Error, "Unable to create a new connection")
|
747
|
+
|
748
|
+
raise PG::ConnectionBad.new(conn.error_message, connection: self) if conn.status == PG::CONNECTION_BAD
|
749
|
+
|
750
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
751
|
+
rescue PG::ConnectionBad => err
|
752
|
+
if errors && !(conn && [PG::CONNECTION_AWAITING_RESPONSE].include?(conn.instance_variable_get(:@last_status)))
|
753
|
+
# Seems to be no authentication error -> try next host
|
754
|
+
errors << err
|
755
|
+
return nil
|
756
|
+
else
|
757
|
+
# Probably an authentication error
|
758
|
+
raise
|
759
|
+
end
|
760
|
+
end
|
761
|
+
conn
|
762
|
+
end
|
763
|
+
|
764
|
+
private def host_is_named_pipe?(host_string)
|
765
|
+
host_string.empty? || host_string.start_with?("/") || # it's UnixSocket?
|
766
|
+
host_string.start_with?("@") || # it's UnixSocket in the abstract namespace?
|
767
|
+
# it's a path on Windows?
|
768
|
+
(RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/)
|
769
|
+
end
|
770
|
+
|
715
771
|
# call-seq:
|
716
772
|
# PG::Connection.ping(connection_hash) -> Integer
|
717
773
|
# PG::Connection.ping(connection_string) -> Integer
|
data/lib/pg/exceptions.rb
CHANGED
@@ -6,7 +6,13 @@ require 'pg' unless defined?( PG )
|
|
6
6
|
|
7
7
|
module PG
|
8
8
|
|
9
|
-
class Error < StandardError
|
9
|
+
class Error < StandardError
|
10
|
+
def initialize(msg=nil, connection: nil, result: nil)
|
11
|
+
@connection = connection
|
12
|
+
@result = result
|
13
|
+
super(msg)
|
14
|
+
end
|
15
|
+
end
|
10
16
|
|
11
17
|
end # module PG
|
12
18
|
|
data/lib/pg/version.rb
CHANGED
data/lib/pg.rb
CHANGED
@@ -59,14 +59,14 @@ module PG
|
|
59
59
|
# Get the PG library version.
|
60
60
|
#
|
61
61
|
# +include_buildnum+ is no longer used and any value passed will be ignored.
|
62
|
-
def self
|
63
|
-
|
62
|
+
def self.version_string( include_buildnum=nil )
|
63
|
+
"%s %s" % [ self.name, VERSION ]
|
64
64
|
end
|
65
65
|
|
66
66
|
|
67
67
|
### Convenience alias for PG::Connection.new.
|
68
|
-
def self
|
69
|
-
|
68
|
+
def self.connect( *args, &block )
|
69
|
+
Connection.new( *args, &block )
|
70
70
|
end
|
71
71
|
|
72
72
|
|
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pg
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Granger
|
@@ -36,7 +36,7 @@ cert_chain:
|
|
36
36
|
oL1mUdzB8KrZL4/WbG5YNX6UTtJbIOu9qEFbBAy4/jtIkJX+dlNoFwd4GXQW1YNO
|
37
37
|
nA==
|
38
38
|
-----END CERTIFICATE-----
|
39
|
-
date: 2022-
|
39
|
+
date: 2022-06-24 00:00:00.000000000 Z
|
40
40
|
dependencies: []
|
41
41
|
description: Pg is the Ruby interface to the PostgreSQL RDBMS. It works with PostgreSQL
|
42
42
|
9.3 and later.
|
@@ -179,7 +179,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
181
|
requirements: []
|
182
|
-
rubygems_version: 3.
|
182
|
+
rubygems_version: 3.3.7
|
183
183
|
signing_key:
|
184
184
|
specification_version: 4
|
185
185
|
summary: Pg is the Ruby interface to the PostgreSQL RDBMS
|
metadata.gz.sig
CHANGED
Binary file
|