pg 1.4.3 → 1.5.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 +0 -0
- data/.appveyor.yml +15 -9
- data/.github/workflows/binary-gems.yml +43 -12
- data/.github/workflows/source-gem.yml +28 -20
- data/.gitignore +11 -2
- data/.travis.yml +2 -2
- data/{History.rdoc → History.md} +251 -145
- data/README.ja.md +276 -0
- data/README.md +286 -0
- data/Rakefile +15 -6
- data/Rakefile.cross +7 -11
- data/certs/larskanis-2023.pem +24 -0
- data/ext/errorcodes.def +4 -0
- data/ext/errorcodes.txt +2 -1
- data/ext/pg.c +14 -30
- data/ext/pg.h +11 -5
- data/ext/pg_binary_decoder.c +80 -1
- data/ext/pg_binary_encoder.c +225 -1
- data/ext/pg_coder.c +17 -8
- data/ext/pg_connection.c +162 -64
- data/ext/pg_copy_coder.c +307 -18
- data/ext/pg_errors.c +1 -1
- data/ext/pg_record_coder.c +6 -5
- data/ext/pg_result.c +102 -26
- data/ext/pg_text_decoder.c +28 -10
- data/ext/pg_text_encoder.c +23 -10
- data/ext/pg_tuple.c +35 -32
- 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 -5
- data/ext/pg_type_map_by_mri_type.c +1 -1
- data/ext/pg_type_map_by_oid.c +8 -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 +13 -8
- data/lib/pg/basic_type_map_for_results.rb +26 -3
- data/lib/pg/basic_type_registry.rb +30 -32
- 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/coder.rb +15 -13
- data/lib/pg/connection.rb +148 -86
- data/lib/pg/exceptions.rb +7 -0
- data/lib/pg/text_decoder/date.rb +18 -0
- data/lib/pg/text_decoder/inet.rb +9 -0
- data/lib/pg/text_decoder/json.rb +14 -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 +12 -0
- data/lib/pg/text_encoder/inet.rb +28 -0
- data/lib/pg/text_encoder/json.rb +14 -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 +55 -15
- data/pg.gemspec +4 -2
- data/rakelib/task_extension.rb +1 -1
- data/translation/.po4a-version +7 -0
- data/translation/po/all.pot +910 -0
- data/translation/po/ja.po +1047 -0
- data/translation/po4a.cfg +12 -0
- data.tar.gz.sig +0 -0
- metadata +101 -32
- metadata.gz.sig +0 -0
- data/README.ja.rdoc +0 -13
- data/README.rdoc +0 -214
- 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/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)
|
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,12 +90,36 @@ 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)
|
94
97
|
end
|
95
98
|
|
99
|
+
# Return a String representation of the object suitable for debugging.
|
100
|
+
def inspect
|
101
|
+
str = self.to_s
|
102
|
+
str[-1,0] = if finished?
|
103
|
+
" finished"
|
104
|
+
else
|
105
|
+
stats = []
|
106
|
+
stats << " status=#{ PG.constants.grep(/CONNECTION_/).find{|c| PG.const_get(c) == status} }" if status != CONNECTION_OK
|
107
|
+
stats << " transaction_status=#{ PG.constants.grep(/PQTRANS_/).find{|c| PG.const_get(c) == transaction_status} }" if transaction_status != PG::PQTRANS_IDLE
|
108
|
+
stats << " nonblocking=#{ isnonblocking }" if isnonblocking
|
109
|
+
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
|
110
|
+
stats << " client_encoding=#{ get_client_encoding }" if get_client_encoding != "UTF8"
|
111
|
+
stats << " type_map_for_results=#{ type_map_for_results.to_s }" unless type_map_for_results.is_a?(PG::TypeMapAllStrings)
|
112
|
+
stats << " type_map_for_queries=#{ type_map_for_queries.to_s }" unless type_map_for_queries.is_a?(PG::TypeMapAllStrings)
|
113
|
+
stats << " encoder_for_put_copy_data=#{ encoder_for_put_copy_data.to_s }" if encoder_for_put_copy_data
|
114
|
+
stats << " decoder_for_get_copy_data=#{ decoder_for_get_copy_data.to_s }" if decoder_for_get_copy_data
|
115
|
+
" host=#{host} port=#{port} user=#{user}#{stats.join}"
|
116
|
+
end
|
117
|
+
return str
|
118
|
+
end
|
119
|
+
|
120
|
+
BinarySignature = "PGCOPY\n\377\r\n\0".b
|
121
|
+
private_constant :BinarySignature
|
122
|
+
|
96
123
|
# call-seq:
|
97
124
|
# conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
|
98
125
|
#
|
@@ -139,6 +166,14 @@ class PG::Connection
|
|
139
166
|
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
140
167
|
# end
|
141
168
|
#
|
169
|
+
# Also PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
|
170
|
+
# In this case copy_data generates the header and trailer data automatically:
|
171
|
+
# enco = PG::BinaryEncoder::CopyRow.new
|
172
|
+
# conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
|
173
|
+
# conn.put_copy_data ['some', 'data', 'to', 'copy']
|
174
|
+
# conn.put_copy_data ['more', 'data', 'to', 'copy']
|
175
|
+
# end
|
176
|
+
#
|
142
177
|
# Example with CSV output format:
|
143
178
|
# conn.copy_data "COPY my_table TO STDOUT CSV" do
|
144
179
|
# while row=conn.get_copy_data
|
@@ -160,6 +195,18 @@ class PG::Connection
|
|
160
195
|
# This receives all rows of +my_table+ as ruby array:
|
161
196
|
# ["some", "data", "to", "copy"]
|
162
197
|
# ["more", "data", "to", "copy"]
|
198
|
+
#
|
199
|
+
# Also PG::BinaryDecoder::CopyRow can be used to retrieve data in binary format from the server.
|
200
|
+
# 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:
|
201
|
+
# deco = PG::BinaryDecoder::CopyRow.new
|
202
|
+
# conn.copy_data "COPY my_table TO STDOUT (FORMAT binary)", deco do
|
203
|
+
# while row=conn.get_copy_data
|
204
|
+
# p row
|
205
|
+
# end
|
206
|
+
# end
|
207
|
+
# This receives all rows of +my_table+ as ruby array:
|
208
|
+
# ["some", "data", "to", "copy"]
|
209
|
+
# ["more", "data", "to", "copy"]
|
163
210
|
|
164
211
|
def copy_data( sql, coder=nil )
|
165
212
|
raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
|
@@ -168,18 +215,38 @@ class PG::Connection
|
|
168
215
|
case res.result_status
|
169
216
|
when PGRES_COPY_IN
|
170
217
|
begin
|
218
|
+
if coder && res.binary_tuples == 1
|
219
|
+
# Binary file header (11 byte signature, 32 bit flags and 32 bit extension length)
|
220
|
+
put_copy_data(BinarySignature + ("\x00" * 8))
|
221
|
+
end
|
222
|
+
|
171
223
|
if coder
|
172
224
|
old_coder = self.encoder_for_put_copy_data
|
173
225
|
self.encoder_for_put_copy_data = coder
|
174
226
|
end
|
227
|
+
|
175
228
|
yield res
|
176
229
|
rescue Exception => err
|
177
230
|
errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
|
178
|
-
|
179
|
-
|
180
|
-
|
231
|
+
begin
|
232
|
+
put_copy_end( errmsg )
|
233
|
+
rescue PG::Error
|
234
|
+
# Ignore error in cleanup to avoid losing original exception
|
235
|
+
end
|
236
|
+
discard_results
|
237
|
+
raise err
|
181
238
|
else
|
182
|
-
|
239
|
+
begin
|
240
|
+
self.encoder_for_put_copy_data = old_coder if coder
|
241
|
+
|
242
|
+
if coder && res.binary_tuples == 1
|
243
|
+
put_copy_data("\xFF\xFF") # Binary file trailer 16 bit "-1"
|
244
|
+
end
|
245
|
+
|
246
|
+
put_copy_end
|
247
|
+
rescue PG::Error => err
|
248
|
+
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
249
|
+
end
|
183
250
|
get_last_result
|
184
251
|
ensure
|
185
252
|
self.encoder_for_put_copy_data = old_coder if coder
|
@@ -192,24 +259,25 @@ class PG::Connection
|
|
192
259
|
self.decoder_for_get_copy_data = coder
|
193
260
|
end
|
194
261
|
yield res
|
195
|
-
rescue Exception
|
262
|
+
rescue Exception
|
196
263
|
cancel
|
197
|
-
|
198
|
-
|
264
|
+
discard_results
|
265
|
+
raise
|
266
|
+
else
|
267
|
+
if coder && res.binary_tuples == 1
|
268
|
+
# There are two end markers in binary mode: file trailer and the final nil.
|
269
|
+
# 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:
|
270
|
+
if get_copy_data
|
271
|
+
discard_results
|
272
|
+
raise PG::NotAllCopyDataRetrieved.new("Not all binary COPY data retrieved", connection: self)
|
199
273
|
end
|
200
|
-
rescue PG::Error
|
201
|
-
# Ignore error in cleanup to avoid losing original exception
|
202
274
|
end
|
203
|
-
while get_result
|
204
|
-
end
|
205
|
-
raise err
|
206
|
-
else
|
207
275
|
res = get_last_result
|
208
|
-
if !res
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
276
|
+
if !res
|
277
|
+
discard_results
|
278
|
+
raise PG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", connection: self)
|
279
|
+
elsif res.result_status != PGRES_COMMAND_OK
|
280
|
+
discard_results
|
213
281
|
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
214
282
|
end
|
215
283
|
res
|
@@ -298,6 +366,23 @@ class PG::Connection
|
|
298
366
|
end
|
299
367
|
end
|
300
368
|
|
369
|
+
# Read all pending socket input to internal memory and raise an exception in case of errors.
|
370
|
+
#
|
371
|
+
# This verifies that the connection socket is in a usable state and not aborted in any way.
|
372
|
+
# No communication is done with the server.
|
373
|
+
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
|
374
|
+
#
|
375
|
+
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
|
376
|
+
#
|
377
|
+
# The method doesn't verify that the server is still responding.
|
378
|
+
# To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
|
379
|
+
def check_socket
|
380
|
+
while socket_io.wait_readable(0)
|
381
|
+
consume_input
|
382
|
+
end
|
383
|
+
nil
|
384
|
+
end
|
385
|
+
|
301
386
|
# call-seq:
|
302
387
|
# conn.get_result() -> PG::Result
|
303
388
|
# conn.get_result() {|pg_result| block }
|
@@ -555,14 +640,17 @@ class PG::Connection
|
|
555
640
|
if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
|
556
641
|
# Lowest timeout is 2 seconds - like in libpq
|
557
642
|
timeo = [timeo, 2].max
|
558
|
-
|
643
|
+
host_count = conninfo_hash[:host].to_s.count(",") + 1
|
644
|
+
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
559
645
|
end
|
560
646
|
|
561
647
|
poll_status = PG::PGRES_POLLING_WRITING
|
562
648
|
until poll_status == PG::PGRES_POLLING_OK ||
|
563
649
|
poll_status == PG::PGRES_POLLING_FAILED
|
564
650
|
|
565
|
-
timeout
|
651
|
+
# Set single timeout to parameter "connect_timeout" but
|
652
|
+
# don't exceed total connection time of number-of-hosts * connect_timeout.
|
653
|
+
timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
|
566
654
|
event = if !timeout || timeout >= 0
|
567
655
|
# If the socket needs to read, wait 'til it becomes readable to poll again
|
568
656
|
case poll_status
|
@@ -600,7 +688,6 @@ class PG::Connection
|
|
600
688
|
|
601
689
|
# Check to see if it's finished or failed yet
|
602
690
|
poll_status = send( poll_meth )
|
603
|
-
@last_status = status unless [PG::CONNECTION_BAD, PG::CONNECTION_OK].include?(status)
|
604
691
|
end
|
605
692
|
|
606
693
|
unless status == PG::CONNECTION_OK
|
@@ -691,84 +778,50 @@ class PG::Connection
|
|
691
778
|
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
692
779
|
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
693
780
|
|
694
|
-
errors = []
|
695
781
|
if iopts[:hostaddr]
|
696
782
|
# hostaddr is provided -> no need to resolve hostnames
|
697
|
-
ihostaddrs = iopts[:hostaddr].split(",", -1)
|
698
783
|
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
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 ...
|
784
|
+
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
785
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting.
|
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.
|
715
788
|
ihosts = iopts[:host].split(",", -1)
|
716
|
-
|
717
789
|
iports = iopts[:port].split(",", -1)
|
790
|
+
iports = [nil] if iports.size == 0
|
718
791
|
iports = iports * ihosts.size if iports.size == 1
|
719
792
|
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
720
793
|
|
721
|
-
ihosts.each_with_index do |mhost, idx|
|
794
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
722
795
|
unless host_is_named_pipe?(mhost)
|
723
|
-
|
796
|
+
if Fiber.respond_to?(:scheduler) &&
|
724
797
|
Fiber.scheduler &&
|
725
798
|
RUBY_VERSION < '3.1.'
|
726
799
|
|
727
800
|
# Use a second thread to avoid blocking of the scheduler.
|
728
801
|
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
729
|
-
Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
802
|
+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
730
803
|
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
|
804
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
739
805
|
end
|
740
806
|
else
|
741
807
|
# No hostname to resolve (UnixSocket)
|
742
|
-
|
743
|
-
c = connect_internal(oopts, errors)
|
744
|
-
return c if c
|
808
|
+
hostaddrs = [nil]
|
745
809
|
end
|
810
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
746
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(","))
|
747
816
|
else
|
748
817
|
# No host given
|
749
|
-
return connect_internal(iopts)
|
750
818
|
end
|
751
|
-
|
752
|
-
|
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")
|
819
|
+
conn = self.connect_start(iopts) or
|
820
|
+
raise(PG::Error, "Unable to create a new connection")
|
758
821
|
|
759
|
-
|
822
|
+
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
760
823
|
|
761
|
-
|
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
|
824
|
+
conn.send(:async_connect_or_reset, :connect_poll)
|
772
825
|
conn
|
773
826
|
end
|
774
827
|
|
@@ -784,7 +837,10 @@ class PG::Connection
|
|
784
837
|
# PG::Connection.ping(connection_string) -> Integer
|
785
838
|
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
786
839
|
#
|
787
|
-
#
|
840
|
+
# PQpingParams reports the status of the server.
|
841
|
+
#
|
842
|
+
# It accepts connection parameters identical to those of PQ::Connection.new .
|
843
|
+
# 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.
|
788
844
|
#
|
789
845
|
# See PG::Connection.new for a description of the parameters.
|
790
846
|
#
|
@@ -797,6 +853,8 @@ class PG::Connection
|
|
797
853
|
# could not establish connection
|
798
854
|
# [+PQPING_NO_ATTEMPT+]
|
799
855
|
# connection not attempted (bad params)
|
856
|
+
#
|
857
|
+
# See also check_socket for a way to check the connection without doing any server communication.
|
800
858
|
def ping(*args)
|
801
859
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
802
860
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
@@ -808,23 +866,25 @@ class PG::Connection
|
|
808
866
|
end
|
809
867
|
alias async_ping ping
|
810
868
|
|
811
|
-
REDIRECT_CLASS_METHODS = {
|
869
|
+
REDIRECT_CLASS_METHODS = PG.make_shareable({
|
812
870
|
:new => [:async_connect, :sync_connect],
|
813
871
|
:connect => [:async_connect, :sync_connect],
|
814
872
|
:open => [:async_connect, :sync_connect],
|
815
873
|
:setdb => [:async_connect, :sync_connect],
|
816
874
|
:setdblogin => [:async_connect, :sync_connect],
|
817
875
|
:ping => [:async_ping, :sync_ping],
|
818
|
-
}
|
876
|
+
})
|
877
|
+
private_constant :REDIRECT_CLASS_METHODS
|
819
878
|
|
820
879
|
# These methods are affected by PQsetnonblocking
|
821
|
-
REDIRECT_SEND_METHODS = {
|
880
|
+
REDIRECT_SEND_METHODS = PG.make_shareable({
|
822
881
|
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
823
882
|
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
824
883
|
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
825
884
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
826
885
|
:flush => [:async_flush, :sync_flush],
|
827
|
-
}
|
886
|
+
})
|
887
|
+
private_constant :REDIRECT_SEND_METHODS
|
828
888
|
REDIRECT_METHODS = {
|
829
889
|
:exec => [:async_exec, :sync_exec],
|
830
890
|
:query => [:async_exec, :sync_exec],
|
@@ -842,12 +902,14 @@ class PG::Connection
|
|
842
902
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
843
903
|
:cancel => [:async_cancel, :sync_cancel],
|
844
904
|
}
|
905
|
+
private_constant :REDIRECT_METHODS
|
845
906
|
|
846
907
|
if PG::Connection.instance_methods.include? :async_encrypt_password
|
847
908
|
REDIRECT_METHODS.merge!({
|
848
909
|
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
849
910
|
})
|
850
911
|
end
|
912
|
+
PG.make_shareable(REDIRECT_METHODS)
|
851
913
|
|
852
914
|
def async_send_api=(enable)
|
853
915
|
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
data/lib/pg/exceptions.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
module PG
|
7
|
+
module TextDecoder
|
8
|
+
class Date < SimpleDecoder
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
10
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
11
|
+
::Date.new $1.to_i, $2.to_i, $3.to_i
|
12
|
+
else
|
13
|
+
string
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end # module PG
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module PG
|
7
|
+
module TextDecoder
|
8
|
+
class JSON < SimpleDecoder
|
9
|
+
def decode(string, tuple=nil, field=nil)
|
10
|
+
::JSON.parse(string, quirks_mode: true)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end # module PG
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module PG
|
5
|
+
module TextDecoder
|
6
|
+
# Convenience classes for timezone options
|
7
|
+
class TimestampUtc < Timestamp
|
8
|
+
def initialize(hash={}, **kwargs)
|
9
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
10
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_UTC)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
class TimestampUtcToLocal < Timestamp
|
14
|
+
def initialize(hash={}, **kwargs)
|
15
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
16
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_UTC | PG::Coder::TIMESTAMP_APP_LOCAL)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
class TimestampLocal < Timestamp
|
20
|
+
def initialize(hash={}, **kwargs)
|
21
|
+
warn("PG::Coder.new(hash) is deprecated. Please use keyword arguments instead! Called from #{caller.first}", category: :deprecated) unless hash.empty?
|
22
|
+
super(**hash, **kwargs, flags: PG::Coder::TIMESTAMP_DB_LOCAL | PG::Coder::TIMESTAMP_APP_LOCAL)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# For backward compatibility:
|
27
|
+
TimestampWithoutTimeZone = TimestampLocal
|
28
|
+
TimestampWithTimeZone = Timestamp
|
29
|
+
end
|
30
|
+
end # module PG
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
module PG
|
7
|
+
module TextEncoder
|
8
|
+
class Inet < SimpleEncoder
|
9
|
+
def encode(value)
|
10
|
+
case value
|
11
|
+
when IPAddr
|
12
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
13
|
+
s = value.to_s
|
14
|
+
if value.respond_to?(:prefix)
|
15
|
+
prefix = value.prefix
|
16
|
+
else
|
17
|
+
range = value.to_range
|
18
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
19
|
+
end
|
20
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
21
|
+
s
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end # module PG
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module PG
|
5
|
+
module TextEncoder
|
6
|
+
class TimestampWithoutTimeZone < SimpleEncoder
|
7
|
+
def encode(value)
|
8
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TimestampUtc < SimpleEncoder
|
13
|
+
def encode(value)
|
14
|
+
value.respond_to?(:utc) ? value.utc.strftime("%Y-%m-%d %H:%M:%S.%N") : value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class TimestampWithTimeZone < SimpleEncoder
|
19
|
+
def encode(value)
|
20
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d %H:%M:%S.%N %:z") : value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end # module PG
|
data/lib/pg/version.rb
CHANGED