pg 1.3.5 → 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 +3 -4
- data/History.rdoc +46 -0
- data/Rakefile.cross +2 -2
- data/ext/extconf.rb +0 -31
- data/ext/pg.c +0 -24
- data/ext/pg_connection.c +206 -177
- data/ext/pg_result.c +2 -2
- data/lib/pg/connection.rb +185 -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_result.c
CHANGED
|
@@ -1476,10 +1476,10 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* d
|
|
|
1476
1476
|
|
|
1477
1477
|
pgresult = gvl_PQgetResult(pgconn);
|
|
1478
1478
|
if( pgresult == NULL )
|
|
1479
|
-
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");
|
|
1480
1480
|
|
|
1481
1481
|
if( nfields != PQnfields(pgresult) )
|
|
1482
|
-
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));
|
|
1483
1483
|
|
|
1484
1484
|
this->pgresult = pgresult;
|
|
1485
1485
|
}
|
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,20 @@ class PG::Connection
|
|
|
482
408
|
# See also #copy_data.
|
|
483
409
|
#
|
|
484
410
|
def put_copy_data(buffer, encoder=nil)
|
|
485
|
-
|
|
486
|
-
|
|
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
|
|
487
416
|
end
|
|
488
|
-
|
|
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
|
|
489
425
|
end
|
|
490
426
|
alias async_put_copy_data put_copy_data
|
|
491
427
|
|
|
@@ -505,6 +441,7 @@ class PG::Connection
|
|
|
505
441
|
until sync_put_copy_end(*args)
|
|
506
442
|
flush
|
|
507
443
|
end
|
|
444
|
+
@calls_to_put_copy_data = 0
|
|
508
445
|
flush
|
|
509
446
|
end
|
|
510
447
|
alias async_put_copy_end put_copy_end
|
|
@@ -545,6 +482,7 @@ class PG::Connection
|
|
|
545
482
|
def reset
|
|
546
483
|
reset_start
|
|
547
484
|
async_connect_or_reset(:reset_poll)
|
|
485
|
+
self
|
|
548
486
|
end
|
|
549
487
|
alias async_reset reset
|
|
550
488
|
|
|
@@ -613,28 +551,62 @@ class PG::Connection
|
|
|
613
551
|
|
|
614
552
|
private def async_connect_or_reset(poll_meth)
|
|
615
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
|
+
|
|
616
561
|
poll_status = PG::PGRES_POLLING_WRITING
|
|
617
562
|
until poll_status == PG::PGRES_POLLING_OK ||
|
|
618
563
|
poll_status == PG::PGRES_POLLING_FAILED
|
|
619
564
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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
|
|
624
575
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
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)
|
|
628
599
|
end
|
|
629
600
|
|
|
630
601
|
# Check to see if it's finished or failed yet
|
|
631
602
|
poll_status = send( poll_meth )
|
|
603
|
+
@last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
|
|
632
604
|
end
|
|
633
605
|
|
|
634
606
|
unless status == PG::CONNECTION_OK
|
|
635
607
|
msg = error_message
|
|
636
608
|
finish
|
|
637
|
-
raise PG::ConnectionBad,
|
|
609
|
+
raise PG::ConnectionBad.new(msg, connection: self)
|
|
638
610
|
end
|
|
639
611
|
|
|
640
612
|
# Set connection to nonblocking to handle all blocking states in ruby.
|
|
@@ -642,8 +614,6 @@ class PG::Connection
|
|
|
642
614
|
sync_setnonblocking(true)
|
|
643
615
|
self.flush_data = true
|
|
644
616
|
set_default_encoding
|
|
645
|
-
|
|
646
|
-
self
|
|
647
617
|
end
|
|
648
618
|
|
|
649
619
|
class << self
|
|
@@ -698,13 +668,17 @@ class PG::Connection
|
|
|
698
668
|
# connection will have its +client_encoding+ set accordingly.
|
|
699
669
|
#
|
|
700
670
|
# Raises a PG::Error if the connection fails.
|
|
701
|
-
def new(*args
|
|
702
|
-
conn =
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
|
708
682
|
end
|
|
709
683
|
alias async_connect new
|
|
710
684
|
alias connect new
|
|
@@ -712,6 +686,99 @@ class PG::Connection
|
|
|
712
686
|
alias setdb new
|
|
713
687
|
alias setdblogin new
|
|
714
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
|
+
|
|
715
782
|
# call-seq:
|
|
716
783
|
# PG::Connection.ping(connection_hash) -> Integer
|
|
717
784
|
# 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.3
|
|
4
|
+
version: 1.4.3
|
|
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-08-09 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
|