pg 1.5.4 → 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} +106 -4
- data/Gemfile +12 -3
- data/README-Windows.rdoc +1 -1
- data/README.ja.md +4 -4
- data/README.md +58 -17
- 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 +161 -14
- data/ext/gvl_wrappers.c +13 -2
- data/ext/gvl_wrappers.h +33 -0
- data/ext/pg.c +17 -6
- data/ext/pg.h +9 -9
- data/ext/pg_binary_decoder.c +152 -0
- data/ext/pg_binary_encoder.c +211 -8
- data/ext/pg_cancel_connection.c +360 -0
- data/ext/pg_coder.c +54 -5
- data/ext/pg_connection.c +409 -167
- data/ext/pg_copy_coder.c +19 -15
- data/ext/pg_record_coder.c +7 -7
- data/ext/pg_result.c +11 -13
- data/ext/pg_text_decoder.c +4 -1
- data/ext/pg_text_encoder.c +37 -18
- data/ext/pg_tuple.c +2 -2
- 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 -1
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +3 -1
- data/ext/pg_type_map_in_ruby.c +1 -1
- data/lib/pg/basic_type_map_for_queries.rb +15 -7
- data/lib/pg/basic_type_registry.rb +16 -4
- data/lib/pg/cancel_connection.rb +53 -0
- data/lib/pg/coder.rb +4 -2
- data/lib/pg/connection.rb +310 -167
- data/lib/pg/exceptions.rb +6 -0
- data/lib/pg/text_decoder/date.rb +3 -0
- data/lib/pg/text_decoder/json.rb +3 -0
- data/lib/pg/text_encoder/date.rb +1 -0
- data/lib/pg/text_encoder/inet.rb +3 -0
- data/lib/pg/text_encoder/json.rb +3 -0
- data/lib/pg/version.rb +1 -1
- data/lib/pg.rb +23 -8
- 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 +8 -4
- 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 +45 -47
- 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 -141
- data/.gitignore +0 -22
- 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/translation/.po4a-version +0 -7
- data/translation/po/all.pot +0 -936
- data/translation/po/ja.po +0 -1036
- data/translation/po4a.cfg +0 -12
data/lib/pg/connection.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'pg' unless defined?( PG )
|
5
|
-
require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable)
|
5
|
+
require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable) # for ruby < 3.0
|
6
6
|
require 'socket'
|
7
7
|
|
8
8
|
# The PostgreSQL connection class. The interface for this class is based on
|
@@ -117,7 +117,7 @@ class PG::Connection
|
|
117
117
|
return str
|
118
118
|
end
|
119
119
|
|
120
|
-
BinarySignature = "PGCOPY\n\377\r\n\0"
|
120
|
+
BinarySignature = "PGCOPY\n\377\r\n\0"
|
121
121
|
private_constant :BinarySignature
|
122
122
|
|
123
123
|
# call-seq:
|
@@ -166,7 +166,10 @@ class PG::Connection
|
|
166
166
|
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
167
167
|
# end
|
168
168
|
#
|
169
|
-
#
|
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.
|
170
173
|
# In this case copy_data generates the header and trailer data automatically:
|
171
174
|
# enco = PG::BinaryEncoder::CopyRow.new
|
172
175
|
# conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
|
@@ -306,6 +309,11 @@ class PG::Connection
|
|
306
309
|
rollback = false
|
307
310
|
exec "BEGIN"
|
308
311
|
yield(self)
|
312
|
+
rescue PG::RollbackTransaction
|
313
|
+
rollback = true
|
314
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
315
|
+
block
|
316
|
+
exec "ROLLBACK"
|
309
317
|
rescue Exception
|
310
318
|
rollback = true
|
311
319
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
@@ -348,21 +356,18 @@ class PG::Connection
|
|
348
356
|
end
|
349
357
|
end
|
350
358
|
|
351
|
-
#
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
ssl_attribute_names.each.with_object({}) do |n,h|
|
364
|
-
h[n] = ssl_attribute(n)
|
365
|
-
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)
|
366
371
|
end
|
367
372
|
end
|
368
373
|
|
@@ -493,7 +498,7 @@ class PG::Connection
|
|
493
498
|
# See also #copy_data.
|
494
499
|
#
|
495
500
|
def put_copy_data(buffer, encoder=nil)
|
496
|
-
# sync_put_copy_data does a non-blocking
|
501
|
+
# sync_put_copy_data does a non-blocking attempt to flush data.
|
497
502
|
until res=sync_put_copy_data(buffer, encoder)
|
498
503
|
# It didn't flush immediately and allocation of more buffering memory failed.
|
499
504
|
# Wait for all data sent by doing a blocking flush.
|
@@ -531,6 +536,25 @@ class PG::Connection
|
|
531
536
|
end
|
532
537
|
alias async_put_copy_end put_copy_end
|
533
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
|
+
|
534
558
|
if method_defined? :sync_encrypt_password
|
535
559
|
# call-seq:
|
536
560
|
# conn.encrypt_password( password, username, algorithm=nil ) -> String
|
@@ -565,136 +589,210 @@ class PG::Connection
|
|
565
589
|
# Resets the backend connection. This method closes the
|
566
590
|
# backend connection and tries to re-connect.
|
567
591
|
def reset
|
568
|
-
|
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)
|
569
600
|
async_connect_or_reset(:reset_poll)
|
570
601
|
self
|
571
602
|
end
|
572
603
|
alias async_reset reset
|
573
604
|
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
cl
|
617
|
-
|
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
|
618
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)
|
619
665
|
end
|
620
|
-
cl.write(cancel_request)
|
621
|
-
cl.read(1)
|
622
|
-
else
|
623
|
-
cl = socket_io.remote_address.connect
|
624
|
-
# Send CANCEL_REQUEST_CODE and parameters
|
625
|
-
cl.write(cancel_request)
|
626
|
-
# Wait for the postmaster to close the connection, which indicates that it's processed the request.
|
627
|
-
cl.read(1)
|
628
|
-
end
|
629
666
|
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
667
|
+
cl.close
|
668
|
+
nil
|
669
|
+
rescue SystemCallError => err
|
670
|
+
err.to_s
|
671
|
+
end
|
634
672
|
end
|
635
673
|
alias async_cancel cancel
|
636
674
|
|
637
|
-
|
638
|
-
# 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
|
639
695
|
|
640
|
-
|
641
|
-
|
642
|
-
timeo = [timeo, 2].max
|
643
|
-
host_count = conninfo_hash[:host].to_s.count(",") + 1
|
644
|
-
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
645
|
-
end
|
696
|
+
until poll_status == PG::PGRES_POLLING_OK ||
|
697
|
+
poll_status == PG::PGRES_POLLING_FAILED
|
646
698
|
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
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
|
662
725
|
end
|
726
|
+
end
|
663
727
|
|
664
|
-
#
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
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
|
670
738
|
else
|
671
|
-
|
672
|
-
|
739
|
+
finish
|
740
|
+
raise PG::ConnectionBad.new(connection_errors.join("\n").b, connection: self)
|
673
741
|
end
|
674
742
|
end
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
unless event
|
679
|
-
if self.class.send(:host_is_named_pipe?, host)
|
680
|
-
connhost = "on socket \"#{host}\""
|
681
|
-
elsif respond_to?(:hostaddr)
|
682
|
-
connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
|
683
|
-
else
|
684
|
-
connhost = "at \"#{host}\", port #{port}"
|
685
|
-
end
|
686
|
-
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 )
|
687
746
|
end
|
688
747
|
|
689
|
-
|
690
|
-
|
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
|
691
753
|
end
|
692
754
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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
|
697
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)
|
698
796
|
|
699
797
|
# Set connection to nonblocking to handle all blocking states in ruby.
|
700
798
|
# That way a fiber scheduler is able to handle IO requests.
|
@@ -710,7 +808,7 @@ class PG::Connection
|
|
710
808
|
# PG::Connection.new(connection_string) -> conn
|
711
809
|
# PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
|
712
810
|
#
|
713
|
-
# Create a connection to the specified server.
|
811
|
+
# === Create a connection to the specified server.
|
714
812
|
#
|
715
813
|
# +connection_hash+ must be a ruby Hash with connection parameters.
|
716
814
|
# See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
|
@@ -734,7 +832,13 @@ class PG::Connection
|
|
734
832
|
# [+password+]
|
735
833
|
# login password
|
736
834
|
#
|
737
|
-
#
|
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:
|
738
842
|
#
|
739
843
|
# # Connect using all defaults
|
740
844
|
# PG::Connection.new
|
@@ -751,10 +855,18 @@ class PG::Connection
|
|
751
855
|
# # As an URI
|
752
856
|
# PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
|
753
857
|
#
|
754
|
-
#
|
755
|
-
#
|
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.
|
756
869
|
#
|
757
|
-
# Raises a PG::Error if the connection fails.
|
758
870
|
def new(*args)
|
759
871
|
conn = connect_to_hosts(*args)
|
760
872
|
|
@@ -773,46 +885,59 @@ class PG::Connection
|
|
773
885
|
alias setdb new
|
774
886
|
alias setdblogin new
|
775
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
|
+
|
776
922
|
private def connect_to_hosts(*args)
|
777
923
|
option_string = parse_connect_args(*args)
|
778
924
|
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
779
925
|
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
780
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
|
781
936
|
if iopts[:hostaddr]
|
782
937
|
# hostaddr is provided -> no need to resolve hostnames
|
783
938
|
|
784
939
|
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
785
|
-
|
786
|
-
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
787
|
-
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
788
|
-
ihosts = iopts[:host].split(",", -1)
|
789
|
-
iports = iopts[:port].split(",", -1)
|
790
|
-
iports = [nil] if iports.size == 0
|
791
|
-
iports = iports * ihosts.size if iports.size == 1
|
792
|
-
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
793
|
-
|
794
|
-
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
795
|
-
unless host_is_named_pipe?(mhost)
|
796
|
-
if Fiber.respond_to?(:scheduler) &&
|
797
|
-
Fiber.scheduler &&
|
798
|
-
RUBY_VERSION < '3.1.'
|
799
|
-
|
800
|
-
# Use a second thread to avoid blocking of the scheduler.
|
801
|
-
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
802
|
-
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
803
|
-
else
|
804
|
-
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
805
|
-
end
|
806
|
-
else
|
807
|
-
# No hostname to resolve (UnixSocket)
|
808
|
-
hostaddrs = [nil]
|
809
|
-
end
|
810
|
-
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
811
|
-
end
|
812
|
-
iopts.merge!(
|
813
|
-
hostaddr: dests.map{|d| d[0] }.join(","),
|
814
|
-
host: dests.map{|d| d[1] }.join(","),
|
815
|
-
port: dests.map{|d| d[2] }.join(","))
|
940
|
+
iopts = resolve_hosts(iopts)
|
816
941
|
else
|
817
942
|
# No host given
|
818
943
|
end
|
@@ -821,6 +946,8 @@ class PG::Connection
|
|
821
946
|
|
822
947
|
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
823
948
|
|
949
|
+
# save the connection options for conn.reset
|
950
|
+
conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
|
824
951
|
conn.send(:async_connect_or_reset, :connect_poll)
|
825
952
|
conn
|
826
953
|
end
|
@@ -877,14 +1004,29 @@ class PG::Connection
|
|
877
1004
|
private_constant :REDIRECT_CLASS_METHODS
|
878
1005
|
|
879
1006
|
# These methods are affected by PQsetnonblocking
|
880
|
-
REDIRECT_SEND_METHODS =
|
1007
|
+
REDIRECT_SEND_METHODS = {
|
881
1008
|
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
882
1009
|
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
883
1010
|
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
884
1011
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
885
1012
|
:flush => [:async_flush, :sync_flush],
|
886
|
-
}
|
1013
|
+
}
|
887
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
|
+
|
888
1030
|
REDIRECT_METHODS = {
|
889
1031
|
:exec => [:async_exec, :sync_exec],
|
890
1032
|
:query => [:async_exec, :sync_exec],
|
@@ -901,12 +1043,13 @@ class PG::Connection
|
|
901
1043
|
:set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
|
902
1044
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
903
1045
|
:cancel => [:async_cancel, :sync_cancel],
|
1046
|
+
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
904
1047
|
}
|
905
1048
|
private_constant :REDIRECT_METHODS
|
906
|
-
|
907
|
-
if PG::Connection.instance_methods.include? :async_encrypt_password
|
1049
|
+
if PG::Connection.instance_methods.include? :async_close_prepared
|
908
1050
|
REDIRECT_METHODS.merge!({
|
909
|
-
:
|
1051
|
+
:close_prepared => [:async_close_prepared, :sync_close_prepared],
|
1052
|
+
:close_portal => [:async_close_portal, :sync_close_portal],
|
910
1053
|
})
|
911
1054
|
end
|
912
1055
|
PG.make_shareable(REDIRECT_METHODS)
|
data/lib/pg/exceptions.rb
CHANGED
@@ -21,5 +21,11 @@ module PG
|
|
21
21
|
class NotInBlockingMode < PG::Error
|
22
22
|
end
|
23
23
|
|
24
|
+
# PG::Connection#transaction uses this exception to distinguish a deliberate rollback from other exceptional situations.
|
25
|
+
# Normally, raising an exception will cause the .transaction method to rollback the database transaction and pass on the exception.
|
26
|
+
# But if you raise an PG::RollbackTransaction exception, then the database transaction will be rolled back, without passing on the exception.
|
27
|
+
class RollbackTransaction < StandardError
|
28
|
+
end
|
29
|
+
|
24
30
|
end # module PG
|
25
31
|
|
data/lib/pg/text_decoder/date.rb
CHANGED
@@ -5,6 +5,9 @@ require 'date'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextDecoder
|
8
|
+
# This is a decoder class for conversion of PostgreSQL date type to Ruby Date values.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'date'.
|
8
11
|
class Date < SimpleDecoder
|
9
12
|
def decode(string, tuple=nil, field=nil)
|
10
13
|
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
data/lib/pg/text_decoder/json.rb
CHANGED
@@ -5,6 +5,9 @@ require 'json'
|
|
5
5
|
|
6
6
|
module PG
|
7
7
|
module TextDecoder
|
8
|
+
# This is a decoder class for conversion of PostgreSQL JSON/JSONB type to Ruby Hash, Array, String, Numeric, nil values.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'json'.
|
8
11
|
class JSON < SimpleDecoder
|
9
12
|
def decode(string, tuple=nil, field=nil)
|
10
13
|
::JSON.parse(string, quirks_mode: true)
|
data/lib/pg/text_encoder/date.rb
CHANGED