pg 1.4.6 → 1.6.1
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/{History.md → CHANGELOG.md} +185 -3
- data/Gemfile +12 -3
- data/README-Windows.rdoc +1 -1
- data/README.ja.md +75 -41
- data/README.md +86 -31
- data/Rakefile +95 -14
- data/certs/kanis@comcard.de.pem +20 -0
- data/certs/larskanis-2024.pem +24 -0
- data/ext/errorcodes.def +4 -5
- data/ext/errorcodes.txt +2 -5
- data/ext/extconf.rb +165 -14
- data/ext/gvl_wrappers.c +13 -2
- data/ext/gvl_wrappers.h +33 -0
- data/ext/pg.c +28 -35
- data/ext/pg.h +18 -14
- data/ext/pg_binary_decoder.c +231 -0
- data/ext/pg_binary_encoder.c +427 -0
- data/ext/pg_cancel_connection.c +360 -0
- data/ext/pg_coder.c +70 -12
- data/ext/pg_connection.c +473 -208
- data/ext/pg_copy_coder.c +316 -23
- data/ext/pg_record_coder.c +12 -11
- data/ext/pg_result.c +102 -30
- data/ext/pg_text_decoder.c +31 -10
- data/ext/pg_text_encoder.c +58 -26
- data/ext/pg_tuple.c +36 -33
- data/ext/pg_type_map.c +4 -3
- data/ext/pg_type_map_all_strings.c +3 -3
- data/ext/pg_type_map_by_class.c +6 -4
- data/ext/pg_type_map_by_column.c +9 -4
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +10 -5
- data/ext/pg_type_map_in_ruby.c +6 -3
- data/lib/pg/basic_type_map_based_on_result.rb +21 -1
- data/lib/pg/basic_type_map_for_queries.rb +23 -10
- data/lib/pg/basic_type_map_for_results.rb +26 -3
- data/lib/pg/basic_type_registry.rb +46 -36
- data/lib/pg/binary_decoder/date.rb +9 -0
- data/lib/pg/binary_decoder/timestamp.rb +26 -0
- data/lib/pg/binary_encoder/timestamp.rb +20 -0
- data/lib/pg/cancel_connection.rb +53 -0
- data/lib/pg/coder.rb +18 -14
- data/lib/pg/connection.rb +387 -172
- data/lib/pg/exceptions.rb +6 -0
- data/lib/pg/text_decoder/date.rb +21 -0
- data/lib/pg/text_decoder/inet.rb +9 -0
- data/lib/pg/text_decoder/json.rb +17 -0
- data/lib/pg/text_decoder/numeric.rb +9 -0
- data/lib/pg/text_decoder/timestamp.rb +30 -0
- data/lib/pg/text_encoder/date.rb +13 -0
- data/lib/pg/text_encoder/inet.rb +31 -0
- data/lib/pg/text_encoder/json.rb +17 -0
- data/lib/pg/text_encoder/numeric.rb +9 -0
- data/lib/pg/text_encoder/timestamp.rb +24 -0
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +78 -17
- data/misc/yugabyte/Dockerfile +9 -0
- data/misc/yugabyte/docker-compose.yml +28 -0
- data/misc/yugabyte/pg-test.rb +45 -0
- data/pg.gemspec +9 -5
- data/ports/patches/krb5/1.21.3/0001-Allow-static-linking-krb5-library.patch +30 -0
- data/ports/patches/openssl/3.5.1/0001-aarch64-mingw.patch +21 -0
- data/ports/patches/postgresql/17.5/0001-Use-workaround-of-__builtin_setjmp-only-on-MINGW-on-.patch +42 -0
- data/ports/patches/postgresql/17.5/0001-libpq-Process-buffered-SSL-read-bytes-to-support-rec.patch +52 -0
- data/rakelib/pg_gem_helper.rb +64 -0
- data.tar.gz.sig +0 -0
- metadata +61 -49
- metadata.gz.sig +0 -0
- data/.appveyor.yml +0 -42
- data/.gems +0 -6
- data/.gemtest +0 -0
- data/.github/workflows/binary-gems.yml +0 -117
- data/.github/workflows/source-gem.yml +0 -137
- data/.gitignore +0 -19
- data/.hgsigs +0 -34
- data/.hgtags +0 -41
- data/.irbrc +0 -23
- data/.pryrc +0 -23
- data/.tm_properties +0 -21
- data/.travis.yml +0 -49
- data/Manifest.txt +0 -72
- data/Rakefile.cross +0 -298
- data/lib/pg/binary_decoder.rb +0 -23
- data/lib/pg/constants.rb +0 -12
- data/lib/pg/text_decoder.rb +0 -46
- data/lib/pg/text_encoder.rb +0 -59
- data/translation/.po4a-version +0 -7
- data/translation/po/all.pot +0 -875
- data/translation/po/ja.po +0 -868
- data/translation/po4a.cfg +0 -9
data/lib/pg/connection.rb
CHANGED
@@ -2,8 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pg' unless defined?( PG )
|
5
|
-
require '
|
6
|
-
require 'io/wait'
|
5
|
+
require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable) # for ruby < 3.0
|
7
6
|
require 'socket'
|
8
7
|
|
9
8
|
# The PostgreSQL connection class. The interface for this class is based on
|
@@ -31,8 +30,8 @@ require 'socket'
|
|
31
30
|
class PG::Connection
|
32
31
|
|
33
32
|
# The order the options are passed to the ::connect method.
|
34
|
-
CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password]
|
35
|
-
|
33
|
+
CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password].freeze
|
34
|
+
private_constant :CONNECT_ARGUMENT_ORDER
|
36
35
|
|
37
36
|
### Quote a single +value+ for use in a connection-parameter string.
|
38
37
|
def self.quote_connstr( value )
|
@@ -46,6 +45,10 @@ class PG::Connection
|
|
46
45
|
hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
|
47
46
|
end
|
48
47
|
|
48
|
+
# Shareable program name for Ractor
|
49
|
+
PROGRAM_NAME = $PROGRAM_NAME.dup.freeze
|
50
|
+
private_constant :PROGRAM_NAME
|
51
|
+
|
49
52
|
# Parse the connection +args+ into a connection-parameter string.
|
50
53
|
# See PG::Connection.new for valid arguments.
|
51
54
|
#
|
@@ -63,8 +66,8 @@ class PG::Connection
|
|
63
66
|
iopts = {}
|
64
67
|
|
65
68
|
if args.length == 1
|
66
|
-
case args.first
|
67
|
-
when
|
69
|
+
case args.first.to_s
|
70
|
+
when /=/, /:\/\//
|
68
71
|
# Option or URL string style
|
69
72
|
conn_string = args.first.to_s
|
70
73
|
iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
@@ -87,7 +90,7 @@ class PG::Connection
|
|
87
90
|
iopts.merge!( hash_arg )
|
88
91
|
|
89
92
|
if !iopts[:fallback_application_name]
|
90
|
-
iopts[:fallback_application_name] =
|
93
|
+
iopts[:fallback_application_name] = PROGRAM_NAME.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
|
91
94
|
end
|
92
95
|
|
93
96
|
return connect_hash_to_string(iopts)
|
@@ -114,6 +117,9 @@ class PG::Connection
|
|
114
117
|
return str
|
115
118
|
end
|
116
119
|
|
120
|
+
BinarySignature = "PGCOPY\n\377\r\n\0"
|
121
|
+
private_constant :BinarySignature
|
122
|
+
|
117
123
|
# call-seq:
|
118
124
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
119
125
|
#
|
@@ -160,6 +166,17 @@ class PG::Connection
|
|
160
166
|
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
161
167
|
# end
|
162
168
|
#
|
169
|
+
# All 4 CopyRow classes can take a type map to specify how the columns are mapped to and from the database format.
|
170
|
+
# For details see the particular CopyRow class description.
|
171
|
+
#
|
172
|
+
# PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
|
173
|
+
# In this case copy_data generates the header and trailer data automatically:
|
174
|
+
# enco = PG::BinaryEncoder::CopyRow.new
|
175
|
+
# conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
|
176
|
+
# conn.put_copy_data ['some', 'data', 'to', 'copy']
|
177
|
+
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
178
|
+
# end
|
179
|
+
#
|
163
180
|
# Example with CSV output format:
|
164
181
|
# conn.copy_data "COPY my_table TO STDOUT CSV" do
|
165
182
|
# while row=conn.get_copy_data
|
@@ -181,6 +198,18 @@ class PG::Connection
|
|
181
198
|
# This receives all rows of +my_table+ as ruby array:
|
182
199
|
# ["some", "data", "to", "copy"]
|
183
200
|
# ["more", "data", "to", "copy"]
|
201
|
+
#
|
202
|
+
# Also PG::BinaryDecoder::CopyRow can be used to retrieve data in binary format from the server.
|
203
|
+
# In this case the header and trailer data is processed by the decoder and the remaining +nil+ from get_copy_data is processed by copy_data, so that binary data can be processed equally to text data:
|
204
|
+
# deco = PG::BinaryDecoder::CopyRow.new
|
205
|
+
# conn.copy_data "COPY my_table TO STDOUT (FORMAT binary)", deco do
|
206
|
+
# while row=conn.get_copy_data
|
207
|
+
# p row
|
208
|
+
# end
|
209
|
+
# end
|
210
|
+
# This receives all rows of +my_table+ as ruby array:
|
211
|
+
# ["some", "data", "to", "copy"]
|
212
|
+
# ["more", "data", "to", "copy"]
|
184
213
|
|
185
214
|
def copy_data( sql, coder=nil )
|
186
215
|
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
@@ -189,10 +218,16 @@ class PG::Connection
|
|
189
218
|
case res.result_status
|
190
219
|
when PGRES_COPY_IN
|
191
220
|
begin
|
221
|
+
if coder && res.binary_tuples == 1
|
222
|
+
# Binary file header (11 byte signature, 32 bit flags and 32 bit extension length)
|
223
|
+
put_copy_data(BinarySignature + ("\x00" * 8))
|
224
|
+
end
|
225
|
+
|
192
226
|
if coder
|
193
227
|
old_coder = self.encoder_for_put_copy_data
|
194
228
|
self.encoder_for_put_copy_data = coder
|
195
229
|
end
|
230
|
+
|
196
231
|
yield res
|
197
232
|
rescue Exception => err
|
198
233
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
@@ -205,6 +240,12 @@ class PG::Connection
|
|
205
240
|
raise err
|
206
241
|
else
|
207
242
|
begin
|
243
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
244
|
+
|
245
|
+
if coder && res.binary_tuples == 1
|
246
|
+
put_copy_data("\xFF\xFF") # Binary file trailer 16 bit "-1"
|
247
|
+
end
|
248
|
+
|
208
249
|
put_copy_end
|
209
250
|
rescue PG::Error => err
|
210
251
|
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
@@ -226,6 +267,14 @@ class PG::Connection
|
|
226
267
|
discard_results
|
227
268
|
raise
|
228
269
|
else
|
270
|
+
if coder && res.binary_tuples == 1
|
271
|
+
# There are two end markers in binary mode: file trailer and the final nil.
|
272
|
+
# The file trailer is expected to be processed by BinaryDecoder::CopyRow and already returns nil, so that the remaining NULL from PQgetCopyData is retrieved here:
|
273
|
+
if get_copy_data
|
274
|
+
discard_results
|
275
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all binary COPY data retrieved", connection: self)
|
276
|
+
end
|
277
|
+
end
|
229
278
|
res = get_last_result
|
230
279
|
if !res
|
231
280
|
discard_results
|
@@ -260,6 +309,11 @@ class PG::Connection
|
|
260
309
|
rollback = false
|
261
310
|
exec "BEGIN"
|
262
311
|
yield(self)
|
312
|
+
rescue PG::RollbackTransaction
|
313
|
+
rollback = true
|
314
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
315
|
+
block
|
316
|
+
exec "ROLLBACK"
|
263
317
|
rescue Exception
|
264
318
|
rollback = true
|
265
319
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
@@ -302,24 +356,38 @@ class PG::Connection
|
|
302
356
|
end
|
303
357
|
end
|
304
358
|
|
305
|
-
#
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
ssl_attribute_names.each.with_object({}) do |n,h|
|
318
|
-
h[n] = ssl_attribute(n)
|
319
|
-
end
|
359
|
+
# call-seq:
|
360
|
+
# conn.ssl_attributes -> Hash<String,String>
|
361
|
+
#
|
362
|
+
# Returns SSL-related information about the connection as key/value pairs
|
363
|
+
#
|
364
|
+
# The available attributes varies depending on the SSL library being used,
|
365
|
+
# and the type of connection.
|
366
|
+
#
|
367
|
+
# See also #ssl_attribute
|
368
|
+
def ssl_attributes
|
369
|
+
ssl_attribute_names.each.with_object({}) do |n,h|
|
370
|
+
h[n] = ssl_attribute(n)
|
320
371
|
end
|
321
372
|
end
|
322
373
|
|
374
|
+
# Read all pending socket input to internal memory and raise an exception in case of errors.
|
375
|
+
#
|
376
|
+
# This verifies that the connection socket is in a usable state and not aborted in any way.
|
377
|
+
# No communication is done with the server.
|
378
|
+
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
|
379
|
+
#
|
380
|
+
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
|
381
|
+
#
|
382
|
+
# The method doesn't verify that the server is still responding.
|
383
|
+
# To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
|
384
|
+
def check_socket
|
385
|
+
while socket_io.wait_readable(0)
|
386
|
+
consume_input
|
387
|
+
end
|
388
|
+
nil
|
389
|
+
end
|
390
|
+
|
323
391
|
# call-seq:
|
324
392
|
# conn.get_result() -> PG::Result
|
325
393
|
# conn.get_result() {|pg_result| block }
|
@@ -430,7 +498,7 @@ class PG::Connection
|
|
430
498
|
# See also #copy_data.
|
431
499
|
#
|
432
500
|
def put_copy_data(buffer, encoder=nil)
|
433
|
-
# sync_put_copy_data does a non-blocking
|
501
|
+
# sync_put_copy_data does a non-blocking attempt to flush data.
|
434
502
|
until res=sync_put_copy_data(buffer, encoder)
|
435
503
|
# It didn't flush immediately and allocation of more buffering memory failed.
|
436
504
|
# Wait for all data sent by doing a blocking flush.
|
@@ -468,6 +536,25 @@ class PG::Connection
|
|
468
536
|
end
|
469
537
|
alias async_put_copy_end put_copy_end
|
470
538
|
|
539
|
+
if method_defined? :send_pipeline_sync
|
540
|
+
# call-seq:
|
541
|
+
# conn.pipeline_sync
|
542
|
+
#
|
543
|
+
# Marks a synchronization point in a pipeline by sending a sync message and flushing the send buffer.
|
544
|
+
# This serves as the delimiter of an implicit transaction and an error recovery point.
|
545
|
+
#
|
546
|
+
# See enter_pipeline_mode
|
547
|
+
#
|
548
|
+
# Raises PG::Error if the connection is not in pipeline mode or sending a sync message failed.
|
549
|
+
#
|
550
|
+
# Available since PostgreSQL-14
|
551
|
+
def pipeline_sync(*args)
|
552
|
+
send_pipeline_sync(*args)
|
553
|
+
flush
|
554
|
+
end
|
555
|
+
alias async_pipeline_sync pipeline_sync
|
556
|
+
end
|
557
|
+
|
471
558
|
if method_defined? :sync_encrypt_password
|
472
559
|
# call-seq:
|
473
560
|
# conn.encrypt_password( password, username, algorithm=nil ) -> String
|
@@ -502,136 +589,210 @@ class PG::Connection
|
|
502
589
|
# Resets the backend connection. This method closes the
|
503
590
|
# backend connection and tries to re-connect.
|
504
591
|
def reset
|
505
|
-
|
592
|
+
# Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
|
593
|
+
# Use conninfo_hash as a fallback when connect_start was used to create the connection object.
|
594
|
+
iopts = @iopts_for_reset || conninfo_hash.compact
|
595
|
+
if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
596
|
+
iopts = self.class.send(:resolve_hosts, iopts)
|
597
|
+
end
|
598
|
+
conninfo = self.class.parse_connect_args( iopts );
|
599
|
+
reset_start2(conninfo)
|
506
600
|
async_connect_or_reset(:reset_poll)
|
507
601
|
self
|
508
602
|
end
|
509
603
|
alias async_reset reset
|
510
604
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
cl
|
554
|
-
|
605
|
+
if defined?(PG::CancelConnection)
|
606
|
+
# PostgreSQL-17+
|
607
|
+
|
608
|
+
def sync_cancel
|
609
|
+
cancon = PG::CancelConnection.new(self)
|
610
|
+
cancon.sync_cancel
|
611
|
+
rescue PG::Error => err
|
612
|
+
err.to_s
|
613
|
+
end
|
614
|
+
|
615
|
+
# call-seq:
|
616
|
+
# conn.cancel() -> String
|
617
|
+
#
|
618
|
+
# Requests cancellation of the command currently being
|
619
|
+
# processed.
|
620
|
+
#
|
621
|
+
# Returns +nil+ on success, or a string containing the
|
622
|
+
# error message if a failure occurs.
|
623
|
+
#
|
624
|
+
# On PostgreSQL-17+ client libaray the class PG::CancelConnection is used.
|
625
|
+
# On older client library a pure ruby implementation is used.
|
626
|
+
def cancel
|
627
|
+
cancon = PG::CancelConnection.new(self)
|
628
|
+
cancon.async_cancel
|
629
|
+
rescue PG::Error => err
|
630
|
+
err.to_s
|
631
|
+
end
|
632
|
+
|
633
|
+
else
|
634
|
+
|
635
|
+
# PostgreSQL < 17
|
636
|
+
|
637
|
+
def cancel
|
638
|
+
be_pid = backend_pid
|
639
|
+
be_key = backend_key
|
640
|
+
cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
|
641
|
+
|
642
|
+
if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
|
643
|
+
# Ruby's nonblocking IO is not really supported on Windows.
|
644
|
+
# We work around by using threads and explicit calls to wait_readable/wait_writable.
|
645
|
+
cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
|
646
|
+
begin
|
647
|
+
cl.write_nonblock(cancel_request)
|
648
|
+
rescue IO::WaitReadable, Errno::EINTR
|
649
|
+
cl.wait_writable
|
650
|
+
retry
|
555
651
|
end
|
652
|
+
begin
|
653
|
+
cl.read_nonblock(1)
|
654
|
+
rescue IO::WaitReadable, Errno::EINTR
|
655
|
+
cl.wait_readable
|
656
|
+
retry
|
657
|
+
rescue EOFError
|
658
|
+
end
|
659
|
+
else
|
660
|
+
cl = socket_io.remote_address.connect
|
661
|
+
# Send CANCEL_REQUEST_CODE and parameters
|
662
|
+
cl.write(cancel_request)
|
663
|
+
# Wait for the postmaster to close the connection, which indicates that it's processed the request.
|
664
|
+
cl.read(1)
|
556
665
|
end
|
557
|
-
cl.write(cancel_request)
|
558
|
-
cl.read(1)
|
559
|
-
else
|
560
|
-
cl = socket_io.remote_address.connect
|
561
|
-
# Send CANCEL_REQUEST_CODE and parameters
|
562
|
-
cl.write(cancel_request)
|
563
|
-
# Wait for the postmaster to close the connection, which indicates that it's processed the request.
|
564
|
-
cl.read(1)
|
565
|
-
end
|
566
666
|
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
667
|
+
cl.close
|
668
|
+
nil
|
669
|
+
rescue SystemCallError => err
|
670
|
+
err.to_s
|
671
|
+
end
|
571
672
|
end
|
572
673
|
alias async_cancel cancel
|
573
674
|
|
574
|
-
|
575
|
-
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
675
|
+
module Pollable
|
676
|
+
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it.
|
677
|
+
#
|
678
|
+
# Connecting to multiple hosts is done like so:
|
679
|
+
# - All hosts are passed to PG::Connection.connect_start
|
680
|
+
# - As soon as the host is tried to connect the related host is removed from the hosts list
|
681
|
+
# - When the polling status changes to `PG::PGRES_POLLING_OK` the connection is returned and ready to use.
|
682
|
+
# - When the polling status changes to `PG::PGRES_POLLING_FAILED` connecting is aborted and a PG::ConnectionBad is raised with details to all connection attepts.
|
683
|
+
# - When a timeout occurs, connecting is restarted with the remaining hosts.
|
684
|
+
#
|
685
|
+
# The downside is that this connects only once to hosts which are listed twice when they timeout.
|
686
|
+
private def polling_loop(poll_meth)
|
687
|
+
connect_timeout = conninfo_hash[:connect_timeout]
|
688
|
+
if (timeo = connect_timeout.to_i) && timeo > 0
|
689
|
+
host_count = (conninfo_hash[:hostaddr].to_s.empty? ? conninfo_hash[:host] : conninfo_hash[:hostaddr]).to_s.count(",") + 1
|
690
|
+
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
691
|
+
end
|
692
|
+
iopts = conninfo_hash.compact
|
693
|
+
connection_errors = []
|
694
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
576
695
|
|
577
|
-
|
578
|
-
|
579
|
-
timeo = [timeo, 2].max
|
580
|
-
host_count = conninfo_hash[:host].to_s.count(",") + 1
|
581
|
-
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
582
|
-
end
|
696
|
+
until poll_status == PG::PGRES_POLLING_OK ||
|
697
|
+
poll_status == PG::PGRES_POLLING_FAILED
|
583
698
|
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
699
|
+
# Set single timeout to parameter "connect_timeout" but
|
700
|
+
# don't exceed total connection time of number-of-hosts * connect_timeout.
|
701
|
+
timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
|
702
|
+
|
703
|
+
hostcnt = remove_current_host(iopts)
|
704
|
+
|
705
|
+
event = if !timeout || timeout >= 0
|
706
|
+
# If the socket needs to read, wait 'til it becomes readable to poll again
|
707
|
+
case poll_status
|
708
|
+
when PG::PGRES_POLLING_READING
|
709
|
+
if defined?(IO::READABLE) # ruby-3.0+
|
710
|
+
socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
|
711
|
+
else
|
712
|
+
IO.select([socket_io], nil, [socket_io], timeout)
|
713
|
+
end
|
714
|
+
|
715
|
+
# ...and the same for when the socket needs to write
|
716
|
+
when PG::PGRES_POLLING_WRITING
|
717
|
+
if defined?(IO::WRITABLE) # ruby-3.0+
|
718
|
+
# Use wait instead of wait_readable, since connection errors are delivered as
|
719
|
+
# exceptional/priority events on Windows.
|
720
|
+
socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
|
721
|
+
else
|
722
|
+
# io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
|
723
|
+
IO.select(nil, [socket_io], [socket_io], timeout)
|
724
|
+
end
|
599
725
|
end
|
726
|
+
end
|
600
727
|
|
601
|
-
#
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
728
|
+
# connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
|
729
|
+
# connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
|
730
|
+
unless event
|
731
|
+
connection_errors << (error_message + "timeout expired")
|
732
|
+
if hostcnt > 0
|
733
|
+
reset_start2(self.class.parse_connect_args(iopts))
|
734
|
+
# Restart polling with waiting for writable.
|
735
|
+
# Otherwise "not connected" error is raised on Windows.
|
736
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
737
|
+
next
|
607
738
|
else
|
608
|
-
|
609
|
-
|
739
|
+
finish
|
740
|
+
raise PG::ConnectionBad.new(connection_errors.join("\n").b, connection: self)
|
610
741
|
end
|
611
742
|
end
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
unless event
|
616
|
-
if self.class.send(:host_is_named_pipe?, host)
|
617
|
-
connhost = "on socket \"#{host}\""
|
618
|
-
elsif respond_to?(:hostaddr)
|
619
|
-
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
620
|
-
else
|
621
|
-
connhost = "at \"#{host}\", port #{port}"
|
622
|
-
end
|
623
|
-
raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
|
743
|
+
|
744
|
+
# Check to see if it's finished or failed yet
|
745
|
+
poll_status = send( poll_meth )
|
624
746
|
end
|
625
747
|
|
626
|
-
|
627
|
-
|
748
|
+
unless status == PG::CONNECTION_OK
|
749
|
+
msg = error_message
|
750
|
+
finish
|
751
|
+
raise PG::ConnectionBad.new(connection_errors.map{|e| e + "\n" }.join.b + msg, connection: self)
|
752
|
+
end
|
628
753
|
end
|
629
754
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
755
|
+
# Remove the host to which the connection is currently established from the option hash.
|
756
|
+
# Affected options are:
|
757
|
+
# - :host
|
758
|
+
# - :hostaddr
|
759
|
+
# - :port
|
760
|
+
#
|
761
|
+
# Return the number of remaining hosts.
|
762
|
+
private def remove_current_host(iopts)
|
763
|
+
ihosts = iopts[:host]&.split(",", -1)
|
764
|
+
ihostaddrs = iopts[:hostaddr]&.split(",", -1)
|
765
|
+
iports = iopts[:port]&.split(",", -1)
|
766
|
+
iports = iports * (ihosts || ihostaddrs || [1]).size if iports&.size == 1
|
767
|
+
|
768
|
+
idx = (ihosts || ihostaddrs || iports).index.with_index do |_, i|
|
769
|
+
(ihosts ? ihosts[i] == host : true) &&
|
770
|
+
(ihostaddrs && respond_to?(:hostaddr, true) ? ihostaddrs[i] == hostaddr : true) &&
|
771
|
+
(iports ? iports[i].to_i == port : true)
|
772
|
+
end
|
773
|
+
|
774
|
+
if idx
|
775
|
+
ihosts&.delete_at(idx)
|
776
|
+
ihostaddrs&.delete_at(idx)
|
777
|
+
iports&.delete_at(idx)
|
778
|
+
|
779
|
+
iopts.merge!(
|
780
|
+
host: ihosts.join(",")) if ihosts
|
781
|
+
iopts.merge!(
|
782
|
+
hostaddr: ihostaddrs.join(",")) if ihostaddrs
|
783
|
+
iopts.merge!(
|
784
|
+
port: iports.join(",")) if iports
|
785
|
+
end
|
786
|
+
|
787
|
+
(ihosts || ihostaddrs || iports).size
|
634
788
|
end
|
789
|
+
end
|
790
|
+
|
791
|
+
include Pollable
|
792
|
+
|
793
|
+
private def async_connect_or_reset(poll_meth)
|
794
|
+
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
795
|
+
polling_loop(poll_meth)
|
635
796
|
|
636
797
|
# Set connection to nonblocking to handle all blocking states in ruby.
|
637
798
|
# That way a fiber scheduler is able to handle IO requests.
|
@@ -647,7 +808,7 @@ class PG::Connection
|
|
647
808
|
# PG::Connection.new(connection_string) -> conn
|
648
809
|
# PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
|
649
810
|
#
|
650
|
-
# Create a connection to the specified server.
|
811
|
+
# === Create a connection to the specified server.
|
651
812
|
#
|
652
813
|
# +connection_hash+ must be a ruby Hash with connection parameters.
|
653
814
|
# See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
|
@@ -671,7 +832,13 @@ class PG::Connection
|
|
671
832
|
# [+password+]
|
672
833
|
# login password
|
673
834
|
#
|
674
|
-
#
|
835
|
+
#
|
836
|
+
# If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
|
837
|
+
# connection will have its +client_encoding+ set accordingly.
|
838
|
+
#
|
839
|
+
# Raises a PG::Error if the connection fails.
|
840
|
+
#
|
841
|
+
# === Examples:
|
675
842
|
#
|
676
843
|
# # Connect using all defaults
|
677
844
|
# PG::Connection.new
|
@@ -688,10 +855,18 @@ class PG::Connection
|
|
688
855
|
# # As an URI
|
689
856
|
# PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
|
690
857
|
#
|
691
|
-
#
|
692
|
-
#
|
858
|
+
# === Specifying Multiple Hosts
|
859
|
+
#
|
860
|
+
# It is possible to specify multiple hosts to connect to, so that they are tried in the given order or optionally in random order.
|
861
|
+
# In the Keyword/Value format, the host, hostaddr, and port options accept comma-separated lists of values.
|
862
|
+
# The {details to libpq}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS] describe how it works, but there are two small differences how ruby-pg handles multiple hosts:
|
863
|
+
# - All hosts are resolved before the first connection is tried.
|
864
|
+
# This means that when +load_balance_hosts+ is set to +random+, then all resolved addresses are tried randomly in one level.
|
865
|
+
# When a host resolves to more than one address, it is therefore tried more often than a host that has only one address.
|
866
|
+
# - When a timeout occurs due to the value of +connect_timeout+, then the given +host+, +hostaddr+ and +port+ combination is not tried a second time, even if it's specified several times.
|
867
|
+
# It's still possible to do load balancing with +load_balance_hosts+ set to +random+ and to increase the number of connections a node gets, when the hostname is provided multiple times in the host string.
|
868
|
+
# This is because in non-timeout cases the host is tried multiple times.
|
693
869
|
#
|
694
|
-
# Raises a PG::Error if the connection fails.
|
695
870
|
def new(*args)
|
696
871
|
conn = connect_to_hosts(*args)
|
697
872
|
|
@@ -710,46 +885,59 @@ class PG::Connection
|
|
710
885
|
alias setdb new
|
711
886
|
alias setdblogin new
|
712
887
|
|
888
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting.
|
889
|
+
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
890
|
+
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
891
|
+
private def resolve_hosts(iopts)
|
892
|
+
ihosts = iopts[:host].split(",", -1)
|
893
|
+
iports = iopts[:port].split(",", -1)
|
894
|
+
iports = [nil] if iports.size == 0
|
895
|
+
iports = iports * ihosts.size if iports.size == 1
|
896
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
897
|
+
|
898
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
899
|
+
unless host_is_named_pipe?(mhost)
|
900
|
+
if Fiber.respond_to?(:scheduler) &&
|
901
|
+
Fiber.scheduler &&
|
902
|
+
RUBY_VERSION < '3.1.'
|
903
|
+
|
904
|
+
# Use a second thread to avoid blocking of the scheduler.
|
905
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
906
|
+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
907
|
+
else
|
908
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
909
|
+
end
|
910
|
+
else
|
911
|
+
# No hostname to resolve (UnixSocket)
|
912
|
+
hostaddrs = [nil]
|
913
|
+
end
|
914
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
915
|
+
end
|
916
|
+
iopts.merge(
|
917
|
+
hostaddr: dests.map{|d| d[0] }.join(","),
|
918
|
+
host: dests.map{|d| d[1] }.join(","),
|
919
|
+
port: dests.map{|d| d[2] }.join(","))
|
920
|
+
end
|
921
|
+
|
713
922
|
private def connect_to_hosts(*args)
|
714
923
|
option_string = parse_connect_args(*args)
|
715
924
|
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
716
925
|
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
717
926
|
|
927
|
+
if PG::BUNDLED_LIBPQ_WITH_UNIXSOCKET && iopts[:host].to_s.empty? && iopts[:hostaddr].to_s.empty?
|
928
|
+
# Many distors patch the hardcoded default UnixSocket path in libpq to /var/run/postgresql instead of /tmp .
|
929
|
+
# We simply try them all.
|
930
|
+
iopts[:host] = "/var/run/postgresql" + # Ubuntu, Debian, Fedora, Opensuse
|
931
|
+
",/run/postgresql" + # Alpine, Archlinux, Gentoo
|
932
|
+
",/tmp" # Stock PostgreSQL
|
933
|
+
end
|
934
|
+
|
935
|
+
iopts_for_reset = iopts
|
718
936
|
if iopts[:hostaddr]
|
719
937
|
# hostaddr is provided -> no need to resolve hostnames
|
720
938
|
|
721
939
|
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
722
|
-
|
723
|
-
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
724
|
-
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
725
|
-
ihosts = iopts[:host].split(",", -1)
|
726
|
-
iports = iopts[:port].split(",", -1)
|
727
|
-
iports = [nil] if iports.size == 0
|
728
|
-
iports = iports * ihosts.size if iports.size == 1
|
729
|
-
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
730
|
-
|
731
|
-
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
732
|
-
unless host_is_named_pipe?(mhost)
|
733
|
-
if Fiber.respond_to?(:scheduler) &&
|
734
|
-
Fiber.scheduler &&
|
735
|
-
RUBY_VERSION < '3.1.'
|
736
|
-
|
737
|
-
# Use a second thread to avoid blocking of the scheduler.
|
738
|
-
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
739
|
-
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
740
|
-
else
|
741
|
-
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
742
|
-
end
|
743
|
-
else
|
744
|
-
# No hostname to resolve (UnixSocket)
|
745
|
-
hostaddrs = [nil]
|
746
|
-
end
|
747
|
-
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
748
|
-
end
|
749
|
-
iopts.merge!(
|
750
|
-
hostaddr: dests.map{|d| d[0] }.join(","),
|
751
|
-
host: dests.map{|d| d[1] }.join(","),
|
752
|
-
port: dests.map{|d| d[2] }.join(","))
|
940
|
+
iopts = resolve_hosts(iopts)
|
753
941
|
else
|
754
942
|
# No host given
|
755
943
|
end
|
@@ -758,6 +946,8 @@ class PG::Connection
|
|
758
946
|
|
759
947
|
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
760
948
|
|
949
|
+
# save the connection options for conn.reset
|
950
|
+
conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
|
761
951
|
conn.send(:async_connect_or_reset, :connect_poll)
|
762
952
|
conn
|
763
953
|
end
|
@@ -774,7 +964,10 @@ class PG::Connection
|
|
774
964
|
# PG::Connection.ping(connection_string) -> Integer
|
775
965
|
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
776
966
|
#
|
777
|
-
#
|
967
|
+
# PQpingParams reports the status of the server.
|
968
|
+
#
|
969
|
+
# It accepts connection parameters identical to those of PQ::Connection.new .
|
970
|
+
# It is not necessary to supply correct user name, password, or database name values to obtain the server status; however, if incorrect values are provided, the server will log a failed connection attempt.
|
778
971
|
#
|
779
972
|
# See PG::Connection.new for a description of the parameters.
|
780
973
|
#
|
@@ -787,6 +980,8 @@ class PG::Connection
|
|
787
980
|
# could not establish connection
|
788
981
|
# [+PQPING_NO_ATTEMPT+]
|
789
982
|
# connection not attempted (bad params)
|
983
|
+
#
|
984
|
+
# See also check_socket for a way to check the connection without doing any server communication.
|
790
985
|
def ping(*args)
|
791
986
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
792
987
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
@@ -798,14 +993,15 @@ class PG::Connection
|
|
798
993
|
end
|
799
994
|
alias async_ping ping
|
800
995
|
|
801
|
-
REDIRECT_CLASS_METHODS = {
|
996
|
+
REDIRECT_CLASS_METHODS = PG.make_shareable({
|
802
997
|
:new => [:async_connect, :sync_connect],
|
803
998
|
:connect => [:async_connect, :sync_connect],
|
804
999
|
:open => [:async_connect, :sync_connect],
|
805
1000
|
:setdb => [:async_connect, :sync_connect],
|
806
1001
|
:setdblogin => [:async_connect, :sync_connect],
|
807
1002
|
:ping => [:async_ping, :sync_ping],
|
808
|
-
}
|
1003
|
+
})
|
1004
|
+
private_constant :REDIRECT_CLASS_METHODS
|
809
1005
|
|
810
1006
|
# These methods are affected by PQsetnonblocking
|
811
1007
|
REDIRECT_SEND_METHODS = {
|
@@ -815,6 +1011,22 @@ class PG::Connection
|
|
815
1011
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
816
1012
|
:flush => [:async_flush, :sync_flush],
|
817
1013
|
}
|
1014
|
+
private_constant :REDIRECT_SEND_METHODS
|
1015
|
+
if PG::Connection.instance_methods.include? :sync_pipeline_sync
|
1016
|
+
if PG::Connection.instance_methods.include? :send_pipeline_sync
|
1017
|
+
# PostgreSQL-17+
|
1018
|
+
REDIRECT_SEND_METHODS.merge!({
|
1019
|
+
:pipeline_sync => [:async_pipeline_sync, :sync_pipeline_sync],
|
1020
|
+
})
|
1021
|
+
else
|
1022
|
+
# PostgreSQL-14+
|
1023
|
+
REDIRECT_SEND_METHODS.merge!({
|
1024
|
+
:pipeline_sync => [:sync_pipeline_sync, :sync_pipeline_sync],
|
1025
|
+
})
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
PG.make_shareable(REDIRECT_SEND_METHODS)
|
1029
|
+
|
818
1030
|
REDIRECT_METHODS = {
|
819
1031
|
:exec => [:async_exec, :sync_exec],
|
820
1032
|
:query => [:async_exec, :sync_exec],
|
@@ -831,13 +1043,16 @@ class PG::Connection
|
|
831
1043
|
:set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
|
832
1044
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
833
1045
|
:cancel => [:async_cancel, :sync_cancel],
|
1046
|
+
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
834
1047
|
}
|
835
|
-
|
836
|
-
if PG::Connection.instance_methods.include? :
|
1048
|
+
private_constant :REDIRECT_METHODS
|
1049
|
+
if PG::Connection.instance_methods.include? :async_close_prepared
|
837
1050
|
REDIRECT_METHODS.merge!({
|
838
|
-
:
|
1051
|
+
:close_prepared => [:async_close_prepared, :sync_close_prepared],
|
1052
|
+
:close_portal => [:async_close_portal, :sync_close_portal],
|
839
1053
|
})
|
840
1054
|
end
|
1055
|
+
PG.make_shareable(REDIRECT_METHODS)
|
841
1056
|
|
842
1057
|
def async_send_api=(enable)
|
843
1058
|
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|