pg 1.3.5 → 1.4.5
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/.github/workflows/binary-gems.yml +2 -2
- data/.github/workflows/source-gem.yml +10 -8
- data/History.rdoc +65 -0
- data/README.rdoc +24 -5
- data/Rakefile +3 -3
- data/Rakefile.cross +2 -2
- data/ext/errorcodes.def +4 -0
- data/ext/errorcodes.txt +2 -1
- data/ext/extconf.rb +0 -31
- data/ext/pg.c +4 -26
- data/ext/pg.h +1 -0
- data/ext/pg_binary_decoder.c +1 -1
- data/ext/pg_binary_encoder.c +1 -1
- data/ext/pg_coder.c +1 -1
- data/ext/pg_connection.c +212 -181
- data/ext/pg_copy_coder.c +1 -1
- data/ext/pg_errors.c +1 -1
- data/ext/pg_record_coder.c +1 -1
- data/ext/pg_result.c +13 -11
- data/ext/pg_text_decoder.c +1 -1
- data/ext/pg_text_encoder.c +1 -1
- data/ext/pg_tuple.c +1 -1
- data/ext/pg_type_map.c +1 -1
- data/ext/pg_type_map_all_strings.c +1 -1
- data/ext/pg_type_map_by_class.c +1 -1
- data/ext/pg_type_map_by_column.c +2 -2
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +1 -1
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/lib/pg/connection.rb +172 -116
- 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 +21 -27
- metadata.gz.sig +0 -0
data/ext/pg_copy_coder.c
CHANGED
data/ext/pg_errors.c
CHANGED
data/ext/pg_record_coder.c
CHANGED
data/ext/pg_result.c
CHANGED
@@ -1382,21 +1382,20 @@ pgresult_type_map_get(VALUE self)
|
|
1382
1382
|
}
|
1383
1383
|
|
1384
1384
|
|
1385
|
-
static
|
1385
|
+
static int
|
1386
1386
|
yield_hash(VALUE self, int ntuples, int nfields, void *data)
|
1387
1387
|
{
|
1388
1388
|
int tuple_num;
|
1389
|
-
t_pg_result *this = pgresult_get_this(self);
|
1390
1389
|
UNUSED(nfields);
|
1391
1390
|
|
1392
1391
|
for(tuple_num = 0; tuple_num < ntuples; tuple_num++) {
|
1393
1392
|
rb_yield(pgresult_aref(self, INT2NUM(tuple_num)));
|
1394
1393
|
}
|
1395
1394
|
|
1396
|
-
|
1395
|
+
return 1; /* clear the result */
|
1397
1396
|
}
|
1398
1397
|
|
1399
|
-
static
|
1398
|
+
static int
|
1400
1399
|
yield_array(VALUE self, int ntuples, int nfields, void *data)
|
1401
1400
|
{
|
1402
1401
|
int row;
|
@@ -1413,10 +1412,10 @@ yield_array(VALUE self, int ntuples, int nfields, void *data)
|
|
1413
1412
|
rb_yield( rb_ary_new4( nfields, row_values ));
|
1414
1413
|
}
|
1415
1414
|
|
1416
|
-
|
1415
|
+
return 1; /* clear the result */
|
1417
1416
|
}
|
1418
1417
|
|
1419
|
-
static
|
1418
|
+
static int
|
1420
1419
|
yield_tuple(VALUE self, int ntuples, int nfields, void *data)
|
1421
1420
|
{
|
1422
1421
|
int tuple_num;
|
@@ -1434,11 +1433,12 @@ yield_tuple(VALUE self, int ntuples, int nfields, void *data)
|
|
1434
1433
|
VALUE tuple = pgresult_tuple(copy, INT2FIX(tuple_num));
|
1435
1434
|
rb_yield( tuple );
|
1436
1435
|
}
|
1436
|
+
return 0; /* don't clear the result */
|
1437
1437
|
}
|
1438
1438
|
|
1439
1439
|
/* Non-static, and data pointer for use by sequel_pg */
|
1440
1440
|
VALUE
|
1441
|
-
pgresult_stream_any(VALUE self,
|
1441
|
+
pgresult_stream_any(VALUE self, int (*yielder)(VALUE, int, int, void*), void* data)
|
1442
1442
|
{
|
1443
1443
|
t_pg_result *this;
|
1444
1444
|
int nfields;
|
@@ -1467,7 +1467,9 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* d
|
|
1467
1467
|
pg_result_check( self );
|
1468
1468
|
}
|
1469
1469
|
|
1470
|
-
yielder( self, ntuples, nfields, data )
|
1470
|
+
if( yielder( self, ntuples, nfields, data ) ){
|
1471
|
+
pgresult_clear( this );
|
1472
|
+
}
|
1471
1473
|
|
1472
1474
|
if( gvl_PQisBusy(pgconn) ){
|
1473
1475
|
/* wait for input (without blocking) before reading each result */
|
@@ -1476,10 +1478,10 @@ pgresult_stream_any(VALUE self, void (*yielder)(VALUE, int, int, void*), void* d
|
|
1476
1478
|
|
1477
1479
|
pgresult = gvl_PQgetResult(pgconn);
|
1478
1480
|
if( pgresult == NULL )
|
1479
|
-
rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another
|
1481
|
+
rb_raise( rb_eNoResultError, "no result received - possibly an intersection with another query");
|
1480
1482
|
|
1481
1483
|
if( nfields != PQnfields(pgresult) )
|
1482
|
-
rb_raise( rb_eInvalidChangeOfResultFields, "number of fields
|
1484
|
+
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
1485
|
|
1484
1486
|
this->pgresult = pgresult;
|
1485
1487
|
}
|
@@ -1617,7 +1619,7 @@ pgresult_field_name_type_get(VALUE self)
|
|
1617
1619
|
}
|
1618
1620
|
|
1619
1621
|
void
|
1620
|
-
init_pg_result()
|
1622
|
+
init_pg_result(void)
|
1621
1623
|
{
|
1622
1624
|
sym_string = ID2SYM(rb_intern("string"));
|
1623
1625
|
sym_symbol = ID2SYM(rb_intern("symbol"));
|
data/ext/pg_text_decoder.c
CHANGED
@@ -923,7 +923,7 @@ pg_text_dec_inet(t_pg_coder *conv, const char *val, int len, int tuple, int fiel
|
|
923
923
|
}
|
924
924
|
|
925
925
|
void
|
926
|
-
init_pg_text_decoder()
|
926
|
+
init_pg_text_decoder(void)
|
927
927
|
{
|
928
928
|
rb_require("ipaddr");
|
929
929
|
s_IPAddr = rb_funcall(rb_cObject, rb_intern("const_get"), 1, rb_str_new2("IPAddr"));
|
data/ext/pg_text_encoder.c
CHANGED
data/ext/pg_tuple.c
CHANGED
data/ext/pg_type_map.c
CHANGED
data/ext/pg_type_map_by_class.c
CHANGED
data/ext/pg_type_map_by_column.c
CHANGED
@@ -243,7 +243,7 @@ pg_tmbc_s_allocate( VALUE klass )
|
|
243
243
|
}
|
244
244
|
|
245
245
|
VALUE
|
246
|
-
pg_tmbc_allocate()
|
246
|
+
pg_tmbc_allocate(void)
|
247
247
|
{
|
248
248
|
return pg_tmbc_s_allocate(rb_cTypeMapByColumn);
|
249
249
|
}
|
@@ -320,7 +320,7 @@ pg_tmbc_coders(VALUE self)
|
|
320
320
|
}
|
321
321
|
|
322
322
|
void
|
323
|
-
init_pg_type_map_by_column()
|
323
|
+
init_pg_type_map_by_column(void)
|
324
324
|
{
|
325
325
|
s_id_decode = rb_intern("decode");
|
326
326
|
s_id_encode = rb_intern("encode");
|
data/ext/pg_type_map_by_oid.c
CHANGED
data/ext/pg_type_map_in_ruby.c
CHANGED
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,64 @@ 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
|
-
|
166
|
-
|
167
|
-
|
93
|
+
return connect_hash_to_string(iopts)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Return a String representation of the object suitable for debugging.
|
97
|
+
def inspect
|
98
|
+
str = self.to_s
|
99
|
+
str[-1,0] = if finished?
|
100
|
+
" finished"
|
168
101
|
else
|
169
|
-
|
170
|
-
|
102
|
+
stats = []
|
103
|
+
stats << " status=#{ PG.constants.grep(/CONNECTION_/).find{|c| PG.const_get(c) == status} }" if status != CONNECTION_OK
|
104
|
+
stats << " transaction_status=#{ PG.constants.grep(/PQTRANS_/).find{|c| PG.const_get(c) == transaction_status} }" if transaction_status != PG::PQTRANS_IDLE
|
105
|
+
stats << " nonblocking=#{ isnonblocking }" if isnonblocking
|
106
|
+
stats << " pipeline_status=#{ PG.constants.grep(/PQ_PIPELINE_/).find{|c| PG.const_get(c) == pipeline_status} }" if respond_to?(:pipeline_status) && pipeline_status != PG::PQ_PIPELINE_OFF
|
107
|
+
stats << " client_encoding=#{ get_client_encoding }" if get_client_encoding != "UTF8"
|
108
|
+
stats << " type_map_for_results=#{ type_map_for_results.to_s }" unless type_map_for_results.is_a?(PG::TypeMapAllStrings)
|
109
|
+
stats << " type_map_for_queries=#{ type_map_for_queries.to_s }" unless type_map_for_queries.is_a?(PG::TypeMapAllStrings)
|
110
|
+
stats << " encoder_for_put_copy_data=#{ encoder_for_put_copy_data.to_s }" if encoder_for_put_copy_data
|
111
|
+
stats << " decoder_for_get_copy_data=#{ decoder_for_get_copy_data.to_s }" if decoder_for_get_copy_data
|
112
|
+
" host=#{host} port=#{port} user=#{user}#{stats.join}"
|
171
113
|
end
|
114
|
+
return str
|
172
115
|
end
|
173
116
|
|
174
|
-
|
175
117
|
# call-seq:
|
176
118
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
177
119
|
#
|
@@ -241,7 +183,7 @@ class PG::Connection
|
|
241
183
|
# ["more", "data", "to", "copy"]
|
242
184
|
|
243
185
|
def copy_data( sql, coder=nil )
|
244
|
-
raise PG::NotInBlockingMode
|
186
|
+
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
245
187
|
res = exec( sql )
|
246
188
|
|
247
189
|
case res.result_status
|
@@ -273,11 +215,15 @@ class PG::Connection
|
|
273
215
|
yield res
|
274
216
|
rescue Exception => err
|
275
217
|
cancel
|
276
|
-
|
218
|
+
begin
|
219
|
+
while get_copy_data
|
220
|
+
end
|
221
|
+
rescue PG::Error
|
222
|
+
# Ignore error in cleanup to avoid losing original exception
|
277
223
|
end
|
278
224
|
while get_result
|
279
225
|
end
|
280
|
-
raise
|
226
|
+
raise err
|
281
227
|
else
|
282
228
|
res = get_last_result
|
283
229
|
if !res || res.result_status != PGRES_COMMAND_OK
|
@@ -285,7 +231,7 @@ class PG::Connection
|
|
285
231
|
end
|
286
232
|
while get_result
|
287
233
|
end
|
288
|
-
raise PG::NotAllCopyDataRetrieved
|
234
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
289
235
|
end
|
290
236
|
res
|
291
237
|
ensure
|
@@ -310,16 +256,17 @@ class PG::Connection
|
|
310
256
|
# and a +COMMIT+ at the end of the block, or
|
311
257
|
# +ROLLBACK+ if any exception occurs.
|
312
258
|
def transaction
|
259
|
+
rollback = false
|
313
260
|
exec "BEGIN"
|
314
|
-
|
261
|
+
yield(self)
|
315
262
|
rescue Exception
|
263
|
+
rollback = true
|
316
264
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
317
265
|
block
|
318
266
|
exec "ROLLBACK"
|
319
267
|
raise
|
320
|
-
|
321
|
-
exec "COMMIT"
|
322
|
-
res
|
268
|
+
ensure
|
269
|
+
exec "COMMIT" unless rollback
|
323
270
|
end
|
324
271
|
|
325
272
|
### Returns an array of Hashes with connection defaults. See ::conndefaults
|
@@ -482,10 +429,20 @@ class PG::Connection
|
|
482
429
|
# See also #copy_data.
|
483
430
|
#
|
484
431
|
def put_copy_data(buffer, encoder=nil)
|
485
|
-
|
486
|
-
|
432
|
+
# sync_put_copy_data does a non-blocking attept to flush data.
|
433
|
+
until res=sync_put_copy_data(buffer, encoder)
|
434
|
+
# It didn't flush immediately and allocation of more buffering memory failed.
|
435
|
+
# Wait for all data sent by doing a blocking flush.
|
436
|
+
res = flush
|
487
437
|
end
|
488
|
-
|
438
|
+
|
439
|
+
# And do a blocking flush every 100 calls.
|
440
|
+
# This is to avoid memory bloat, when sending the data is slower than calls to put_copy_data happen.
|
441
|
+
if (@calls_to_put_copy_data += 1) > 100
|
442
|
+
@calls_to_put_copy_data = 0
|
443
|
+
res = flush
|
444
|
+
end
|
445
|
+
res
|
489
446
|
end
|
490
447
|
alias async_put_copy_data put_copy_data
|
491
448
|
|
@@ -505,6 +462,7 @@ class PG::Connection
|
|
505
462
|
until sync_put_copy_end(*args)
|
506
463
|
flush
|
507
464
|
end
|
465
|
+
@calls_to_put_copy_data = 0
|
508
466
|
flush
|
509
467
|
end
|
510
468
|
alias async_put_copy_end put_copy_end
|
@@ -545,6 +503,7 @@ class PG::Connection
|
|
545
503
|
def reset
|
546
504
|
reset_start
|
547
505
|
async_connect_or_reset(:reset_poll)
|
506
|
+
self
|
548
507
|
end
|
549
508
|
alias async_reset reset
|
550
509
|
|
@@ -613,18 +572,54 @@ class PG::Connection
|
|
613
572
|
|
614
573
|
private def async_connect_or_reset(poll_meth)
|
615
574
|
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
575
|
+
|
576
|
+
if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
|
577
|
+
# Lowest timeout is 2 seconds - like in libpq
|
578
|
+
timeo = [timeo, 2].max
|
579
|
+
host_count = conninfo_hash[:host].to_s.count(",") + 1
|
580
|
+
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
581
|
+
end
|
582
|
+
|
616
583
|
poll_status = PG::PGRES_POLLING_WRITING
|
617
584
|
until poll_status == PG::PGRES_POLLING_OK ||
|
618
585
|
poll_status == PG::PGRES_POLLING_FAILED
|
619
586
|
|
620
|
-
#
|
621
|
-
|
622
|
-
|
623
|
-
|
587
|
+
# Set single timeout to parameter "connect_timeout" but
|
588
|
+
# don't exceed total connection time of number-of-hosts * connect_timeout.
|
589
|
+
timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
|
590
|
+
event = if !timeout || timeout >= 0
|
591
|
+
# If the socket needs to read, wait 'til it becomes readable to poll again
|
592
|
+
case poll_status
|
593
|
+
when PG::PGRES_POLLING_READING
|
594
|
+
if defined?(IO::READABLE) # ruby-3.0+
|
595
|
+
socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
|
596
|
+
else
|
597
|
+
IO.select([socket_io], nil, [socket_io], timeout)
|
598
|
+
end
|
624
599
|
|
625
|
-
|
626
|
-
|
627
|
-
|
600
|
+
# ...and the same for when the socket needs to write
|
601
|
+
when PG::PGRES_POLLING_WRITING
|
602
|
+
if defined?(IO::WRITABLE) # ruby-3.0+
|
603
|
+
# Use wait instead of wait_readable, since connection errors are delivered as
|
604
|
+
# exceptional/priority events on Windows.
|
605
|
+
socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
|
606
|
+
else
|
607
|
+
# io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
|
608
|
+
IO.select(nil, [socket_io], [socket_io], timeout)
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
# connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
|
613
|
+
# connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
|
614
|
+
unless event
|
615
|
+
if self.class.send(:host_is_named_pipe?, host)
|
616
|
+
connhost = "on socket \"#{host}\""
|
617
|
+
elsif respond_to?(:hostaddr)
|
618
|
+
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
619
|
+
else
|
620
|
+
connhost = "at \"#{host}\", port #{port}"
|
621
|
+
end
|
622
|
+
raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
|
628
623
|
end
|
629
624
|
|
630
625
|
# Check to see if it's finished or failed yet
|
@@ -634,7 +629,7 @@ class PG::Connection
|
|
634
629
|
unless status == PG::CONNECTION_OK
|
635
630
|
msg = error_message
|
636
631
|
finish
|
637
|
-
raise PG::ConnectionBad,
|
632
|
+
raise PG::ConnectionBad.new(msg, connection: self)
|
638
633
|
end
|
639
634
|
|
640
635
|
# Set connection to nonblocking to handle all blocking states in ruby.
|
@@ -642,8 +637,6 @@ class PG::Connection
|
|
642
637
|
sync_setnonblocking(true)
|
643
638
|
self.flush_data = true
|
644
639
|
set_default_encoding
|
645
|
-
|
646
|
-
self
|
647
640
|
end
|
648
641
|
|
649
642
|
class << self
|
@@ -698,13 +691,17 @@ class PG::Connection
|
|
698
691
|
# connection will have its +client_encoding+ set accordingly.
|
699
692
|
#
|
700
693
|
# Raises a PG::Error if the connection fails.
|
701
|
-
def new(*args
|
702
|
-
conn =
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
694
|
+
def new(*args)
|
695
|
+
conn = connect_to_hosts(*args)
|
696
|
+
|
697
|
+
if block_given?
|
698
|
+
begin
|
699
|
+
return yield conn
|
700
|
+
ensure
|
701
|
+
conn.finish
|
702
|
+
end
|
703
|
+
end
|
704
|
+
conn
|
708
705
|
end
|
709
706
|
alias async_connect new
|
710
707
|
alias connect new
|
@@ -712,6 +709,65 @@ class PG::Connection
|
|
712
709
|
alias setdb new
|
713
710
|
alias setdblogin new
|
714
711
|
|
712
|
+
private def connect_to_hosts(*args)
|
713
|
+
option_string = parse_connect_args(*args)
|
714
|
+
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
715
|
+
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
716
|
+
|
717
|
+
if iopts[:hostaddr]
|
718
|
+
# hostaddr is provided -> no need to resolve hostnames
|
719
|
+
|
720
|
+
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
721
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting.
|
722
|
+
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
723
|
+
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
724
|
+
ihosts = iopts[:host].split(",", -1)
|
725
|
+
iports = iopts[:port].split(",", -1)
|
726
|
+
iports = [nil] if iports.size == 0
|
727
|
+
iports = iports * ihosts.size if iports.size == 1
|
728
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
729
|
+
|
730
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
731
|
+
unless host_is_named_pipe?(mhost)
|
732
|
+
if Fiber.respond_to?(:scheduler) &&
|
733
|
+
Fiber.scheduler &&
|
734
|
+
RUBY_VERSION < '3.1.'
|
735
|
+
|
736
|
+
# Use a second thread to avoid blocking of the scheduler.
|
737
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
738
|
+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
739
|
+
else
|
740
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
741
|
+
end
|
742
|
+
else
|
743
|
+
# No hostname to resolve (UnixSocket)
|
744
|
+
hostaddrs = [nil]
|
745
|
+
end
|
746
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
747
|
+
end
|
748
|
+
iopts.merge!(
|
749
|
+
hostaddr: dests.map{|d| d[0] }.join(","),
|
750
|
+
host: dests.map{|d| d[1] }.join(","),
|
751
|
+
port: dests.map{|d| d[2] }.join(","))
|
752
|
+
else
|
753
|
+
# No host given
|
754
|
+
end
|
755
|
+
conn = self.connect_start(iopts) or
|
756
|
+
raise(PG::Error, "Unable to create a new connection")
|
757
|
+
|
758
|
+
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
759
|
+
|
760
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
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