pg 1.4.4 → 1.5.9
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/Gemfile +6 -0
- data/{History.rdoc → History.md} +303 -151
- data/README.ja.md +300 -0
- data/README.md +286 -0
- data/Rakefile +16 -4
- data/Rakefile.cross +15 -14
- data/certs/kanis@comcard.de.pem +20 -0
- data/certs/larskanis-2023.pem +24 -0
- data/certs/larskanis-2024.pem +24 -0
- data/ext/errorcodes.def +8 -5
- data/ext/errorcodes.txt +3 -5
- data/ext/extconf.rb +7 -0
- data/ext/pg.c +15 -30
- data/ext/pg.h +10 -6
- data/ext/pg_binary_decoder.c +81 -0
- data/ext/pg_binary_encoder.c +224 -0
- data/ext/pg_coder.c +16 -7
- data/ext/pg_connection.c +220 -82
- data/ext/pg_copy_coder.c +315 -22
- data/ext/pg_record_coder.c +11 -10
- data/ext/pg_result.c +93 -19
- data/ext/pg_text_decoder.c +31 -10
- data/ext/pg_text_encoder.c +38 -19
- data/ext/pg_tuple.c +34 -31
- data/ext/pg_type_map.c +3 -2
- data/ext/pg_type_map_all_strings.c +2 -2
- data/ext/pg_type_map_by_class.c +5 -3
- data/ext/pg_type_map_by_column.c +7 -3
- data/ext/pg_type_map_by_oid.c +7 -4
- data/ext/pg_type_map_in_ruby.c +5 -2
- data/lib/pg/basic_type_map_based_on_result.rb +21 -1
- data/lib/pg/basic_type_map_for_queries.rb +19 -10
- data/lib/pg/basic_type_map_for_results.rb +26 -3
- data/lib/pg/basic_type_registry.rb +44 -34
- 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 +158 -64
- data/lib/pg/exceptions.rb +13 -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 +65 -15
- data/pg.gemspec +7 -3
- data/rakelib/task_extension.rb +1 -1
- data.tar.gz.sig +2 -4
- metadata +104 -46
- metadata.gz.sig +0 -0
- data/.appveyor.yml +0 -36
- data/.gems +0 -6
- data/.gemtest +0 -0
- data/.github/workflows/binary-gems.yml +0 -86
- data/.github/workflows/source-gem.yml +0 -131
- data/.gitignore +0 -13
- 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/README.ja.rdoc +0 -13
- data/README.rdoc +0 -233
- 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) # 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,18 +218,38 @@ 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 ]
|
199
|
-
|
200
|
-
|
201
|
-
|
234
|
+
begin
|
235
|
+
put_copy_end( errmsg )
|
236
|
+
rescue PG::Error
|
237
|
+
# Ignore error in cleanup to avoid losing original exception
|
238
|
+
end
|
239
|
+
discard_results
|
240
|
+
raise err
|
202
241
|
else
|
203
|
-
|
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
|
+
|
249
|
+
put_copy_end
|
250
|
+
rescue PG::Error => err
|
251
|
+
raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
|
252
|
+
end
|
204
253
|
get_last_result
|
205
254
|
ensure
|
206
255
|
self.encoder_for_put_copy_data = old_coder if coder
|
@@ -213,24 +262,25 @@ class PG::Connection
|
|
213
262
|
self.decoder_for_get_copy_data = coder
|
214
263
|
end
|
215
264
|
yield res
|
216
|
-
rescue Exception
|
265
|
+
rescue Exception
|
217
266
|
cancel
|
218
|
-
|
219
|
-
|
267
|
+
discard_results
|
268
|
+
raise
|
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)
|
220
276
|
end
|
221
|
-
rescue PG::Error
|
222
|
-
# Ignore error in cleanup to avoid losing original exception
|
223
|
-
end
|
224
|
-
while get_result
|
225
277
|
end
|
226
|
-
raise err
|
227
|
-
else
|
228
278
|
res = get_last_result
|
229
|
-
if !res
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
279
|
+
if !res
|
280
|
+
discard_results
|
281
|
+
raise PG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", connection: self)
|
282
|
+
elsif res.result_status != PGRES_COMMAND_OK
|
283
|
+
discard_results
|
234
284
|
raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
|
235
285
|
end
|
236
286
|
res
|
@@ -259,6 +309,11 @@ class PG::Connection
|
|
259
309
|
rollback = false
|
260
310
|
exec "BEGIN"
|
261
311
|
yield(self)
|
312
|
+
rescue PG::RollbackTransaction
|
313
|
+
rollback = true
|
314
|
+
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
315
|
+
block
|
316
|
+
exec "ROLLBACK"
|
262
317
|
rescue Exception
|
263
318
|
rollback = true
|
264
319
|
cancel if transaction_status == PG::PQTRANS_ACTIVE
|
@@ -319,6 +374,23 @@ class PG::Connection
|
|
319
374
|
end
|
320
375
|
end
|
321
376
|
|
377
|
+
# Read all pending socket input to internal memory and raise an exception in case of errors.
|
378
|
+
#
|
379
|
+
# This verifies that the connection socket is in a usable state and not aborted in any way.
|
380
|
+
# No communication is done with the server.
|
381
|
+
# Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
|
382
|
+
#
|
383
|
+
# Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
|
384
|
+
#
|
385
|
+
# The method doesn't verify that the server is still responding.
|
386
|
+
# To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
|
387
|
+
def check_socket
|
388
|
+
while socket_io.wait_readable(0)
|
389
|
+
consume_input
|
390
|
+
end
|
391
|
+
nil
|
392
|
+
end
|
393
|
+
|
322
394
|
# call-seq:
|
323
395
|
# conn.get_result() -> PG::Result
|
324
396
|
# conn.get_result() {|pg_result| block }
|
@@ -429,7 +501,7 @@ class PG::Connection
|
|
429
501
|
# See also #copy_data.
|
430
502
|
#
|
431
503
|
def put_copy_data(buffer, encoder=nil)
|
432
|
-
# sync_put_copy_data does a non-blocking
|
504
|
+
# sync_put_copy_data does a non-blocking attempt to flush data.
|
433
505
|
until res=sync_put_copy_data(buffer, encoder)
|
434
506
|
# It didn't flush immediately and allocation of more buffering memory failed.
|
435
507
|
# Wait for all data sent by doing a blocking flush.
|
@@ -501,7 +573,14 @@ class PG::Connection
|
|
501
573
|
# Resets the backend connection. This method closes the
|
502
574
|
# backend connection and tries to re-connect.
|
503
575
|
def reset
|
504
|
-
|
576
|
+
# Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
|
577
|
+
# Use conninfo_hash as a fallback when connect_start was used to create the connection object.
|
578
|
+
iopts = @iopts_for_reset || conninfo_hash.compact
|
579
|
+
if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
580
|
+
iopts = self.class.send(:resolve_hosts, iopts)
|
581
|
+
end
|
582
|
+
conninfo = self.class.parse_connect_args( iopts );
|
583
|
+
reset_start2(conninfo)
|
505
584
|
async_connect_or_reset(:reset_poll)
|
506
585
|
self
|
507
586
|
end
|
@@ -574,8 +653,6 @@ class PG::Connection
|
|
574
653
|
# Track the progress of the connection, waiting for the socket to become readable/writable before polling it
|
575
654
|
|
576
655
|
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
656
|
host_count = conninfo_hash[:host].to_s.count(",") + 1
|
580
657
|
stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
581
658
|
end
|
@@ -709,45 +786,51 @@ class PG::Connection
|
|
709
786
|
alias setdb new
|
710
787
|
alias setdblogin new
|
711
788
|
|
789
|
+
# Resolve DNS in Ruby to avoid blocking state while connecting.
|
790
|
+
# Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
|
791
|
+
# This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
|
792
|
+
private def resolve_hosts(iopts)
|
793
|
+
ihosts = iopts[:host].split(",", -1)
|
794
|
+
iports = iopts[:port].split(",", -1)
|
795
|
+
iports = [nil] if iports.size == 0
|
796
|
+
iports = iports * ihosts.size if iports.size == 1
|
797
|
+
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
798
|
+
|
799
|
+
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
800
|
+
unless host_is_named_pipe?(mhost)
|
801
|
+
if Fiber.respond_to?(:scheduler) &&
|
802
|
+
Fiber.scheduler &&
|
803
|
+
RUBY_VERSION < '3.1.'
|
804
|
+
|
805
|
+
# Use a second thread to avoid blocking of the scheduler.
|
806
|
+
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
807
|
+
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
808
|
+
else
|
809
|
+
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
810
|
+
end
|
811
|
+
else
|
812
|
+
# No hostname to resolve (UnixSocket)
|
813
|
+
hostaddrs = [nil]
|
814
|
+
end
|
815
|
+
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
816
|
+
end
|
817
|
+
iopts.merge(
|
818
|
+
hostaddr: dests.map{|d| d[0] }.join(","),
|
819
|
+
host: dests.map{|d| d[1] }.join(","),
|
820
|
+
port: dests.map{|d| d[2] }.join(","))
|
821
|
+
end
|
822
|
+
|
712
823
|
private def connect_to_hosts(*args)
|
713
824
|
option_string = parse_connect_args(*args)
|
714
825
|
iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
|
715
826
|
iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
|
716
827
|
|
828
|
+
iopts_for_reset = iopts
|
717
829
|
if iopts[:hostaddr]
|
718
830
|
# hostaddr is provided -> no need to resolve hostnames
|
719
831
|
|
720
832
|
elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
|
721
|
-
|
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 = iports * ihosts.size if iports.size == 1
|
727
|
-
raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
|
728
|
-
|
729
|
-
dests = ihosts.each_with_index.flat_map do |mhost, idx|
|
730
|
-
unless host_is_named_pipe?(mhost)
|
731
|
-
if Fiber.respond_to?(:scheduler) &&
|
732
|
-
Fiber.scheduler &&
|
733
|
-
RUBY_VERSION < '3.1.'
|
734
|
-
|
735
|
-
# Use a second thread to avoid blocking of the scheduler.
|
736
|
-
# `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
|
737
|
-
hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
|
738
|
-
else
|
739
|
-
hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
|
740
|
-
end
|
741
|
-
else
|
742
|
-
# No hostname to resolve (UnixSocket)
|
743
|
-
hostaddrs = [nil]
|
744
|
-
end
|
745
|
-
hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
|
746
|
-
end
|
747
|
-
iopts.merge!(
|
748
|
-
hostaddr: dests.map{|d| d[0] }.join(","),
|
749
|
-
host: dests.map{|d| d[1] }.join(","),
|
750
|
-
port: dests.map{|d| d[2] }.join(","))
|
833
|
+
iopts = resolve_hosts(iopts)
|
751
834
|
else
|
752
835
|
# No host given
|
753
836
|
end
|
@@ -756,6 +839,8 @@ class PG::Connection
|
|
756
839
|
|
757
840
|
raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
|
758
841
|
|
842
|
+
# save the connection options for conn.reset
|
843
|
+
conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
|
759
844
|
conn.send(:async_connect_or_reset, :connect_poll)
|
760
845
|
conn
|
761
846
|
end
|
@@ -772,7 +857,10 @@ class PG::Connection
|
|
772
857
|
# PG::Connection.ping(connection_string) -> Integer
|
773
858
|
# PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
|
774
859
|
#
|
775
|
-
#
|
860
|
+
# PQpingParams reports the status of the server.
|
861
|
+
#
|
862
|
+
# It accepts connection parameters identical to those of PQ::Connection.new .
|
863
|
+
# 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.
|
776
864
|
#
|
777
865
|
# See PG::Connection.new for a description of the parameters.
|
778
866
|
#
|
@@ -785,6 +873,8 @@ class PG::Connection
|
|
785
873
|
# could not establish connection
|
786
874
|
# [+PQPING_NO_ATTEMPT+]
|
787
875
|
# connection not attempted (bad params)
|
876
|
+
#
|
877
|
+
# See also check_socket for a way to check the connection without doing any server communication.
|
788
878
|
def ping(*args)
|
789
879
|
if Fiber.respond_to?(:scheduler) && Fiber.scheduler
|
790
880
|
# Run PQping in a second thread to avoid blocking of the scheduler.
|
@@ -796,23 +886,25 @@ class PG::Connection
|
|
796
886
|
end
|
797
887
|
alias async_ping ping
|
798
888
|
|
799
|
-
REDIRECT_CLASS_METHODS = {
|
889
|
+
REDIRECT_CLASS_METHODS = PG.make_shareable({
|
800
890
|
:new => [:async_connect, :sync_connect],
|
801
891
|
:connect => [:async_connect, :sync_connect],
|
802
892
|
:open => [:async_connect, :sync_connect],
|
803
893
|
:setdb => [:async_connect, :sync_connect],
|
804
894
|
:setdblogin => [:async_connect, :sync_connect],
|
805
895
|
:ping => [:async_ping, :sync_ping],
|
806
|
-
}
|
896
|
+
})
|
897
|
+
private_constant :REDIRECT_CLASS_METHODS
|
807
898
|
|
808
899
|
# These methods are affected by PQsetnonblocking
|
809
|
-
REDIRECT_SEND_METHODS = {
|
900
|
+
REDIRECT_SEND_METHODS = PG.make_shareable({
|
810
901
|
:isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
|
811
902
|
:nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
|
812
903
|
:put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
|
813
904
|
:put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
|
814
905
|
:flush => [:async_flush, :sync_flush],
|
815
|
-
}
|
906
|
+
})
|
907
|
+
private_constant :REDIRECT_SEND_METHODS
|
816
908
|
REDIRECT_METHODS = {
|
817
909
|
:exec => [:async_exec, :sync_exec],
|
818
910
|
:query => [:async_exec, :sync_exec],
|
@@ -830,12 +922,14 @@ class PG::Connection
|
|
830
922
|
:client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
|
831
923
|
:cancel => [:async_cancel, :sync_cancel],
|
832
924
|
}
|
925
|
+
private_constant :REDIRECT_METHODS
|
833
926
|
|
834
927
|
if PG::Connection.instance_methods.include? :async_encrypt_password
|
835
928
|
REDIRECT_METHODS.merge!({
|
836
929
|
:encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
|
837
930
|
})
|
838
931
|
end
|
932
|
+
PG.make_shareable(REDIRECT_METHODS)
|
839
933
|
|
840
934
|
def async_send_api=(enable)
|
841
935
|
REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
|
data/lib/pg/exceptions.rb
CHANGED
@@ -14,5 +14,18 @@ module PG
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class NotAllCopyDataRetrieved < PG::Error
|
18
|
+
end
|
19
|
+
class LostCopyState < PG::Error
|
20
|
+
end
|
21
|
+
class NotInBlockingMode < PG::Error
|
22
|
+
end
|
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
|
+
|
17
30
|
end # module PG
|
18
31
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
module PG
|
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'.
|
11
|
+
class Date < SimpleDecoder
|
12
|
+
def decode(string, tuple=nil, field=nil)
|
13
|
+
if string =~ /\A(\d{4})-(\d\d)-(\d\d)\z/
|
14
|
+
::Date.new $1.to_i, $2.to_i, $3.to_i
|
15
|
+
else
|
16
|
+
string
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end # module PG
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module PG
|
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'.
|
11
|
+
class JSON < SimpleDecoder
|
12
|
+
def decode(string, tuple=nil, field=nil)
|
13
|
+
::JSON.parse(string, quirks_mode: true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
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,13 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module PG
|
5
|
+
module TextEncoder
|
6
|
+
# This is a encoder class for conversion of Ruby Date values to PostgreSQL date type.
|
7
|
+
class Date < SimpleEncoder
|
8
|
+
def encode(value)
|
9
|
+
value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end # module PG
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'ipaddr'
|
5
|
+
|
6
|
+
module PG
|
7
|
+
module TextEncoder
|
8
|
+
# This is a encoder class for conversion of Ruby IPAddr values to PostgreSQL inet type.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'ipaddr'.
|
11
|
+
class Inet < SimpleEncoder
|
12
|
+
def encode(value)
|
13
|
+
case value
|
14
|
+
when IPAddr
|
15
|
+
default_prefix = (value.family == Socket::AF_INET ? 32 : 128)
|
16
|
+
s = value.to_s
|
17
|
+
if value.respond_to?(:prefix)
|
18
|
+
prefix = value.prefix
|
19
|
+
else
|
20
|
+
range = value.to_range
|
21
|
+
prefix = default_prefix - Math.log(((range.end.to_i - range.begin.to_i) + 1), 2).to_i
|
22
|
+
end
|
23
|
+
s << "/" << prefix.to_s if prefix != default_prefix
|
24
|
+
s
|
25
|
+
else
|
26
|
+
value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end # module PG
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module PG
|
7
|
+
module TextEncoder
|
8
|
+
# This is a encoder class for conversion of Ruby Hash, Array, String, Numeric, nil values to PostgreSQL JSON/JSONB type.
|
9
|
+
#
|
10
|
+
# As soon as this class is used, it requires the ruby standard library 'json'.
|
11
|
+
class JSON < SimpleEncoder
|
12
|
+
def encode(value)
|
13
|
+
::JSON.generate(value, quirks_mode: true)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
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