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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/.appveyor.yml +15 -9
  4. data/.github/workflows/binary-gems.yml +43 -12
  5. data/.github/workflows/source-gem.yml +28 -20
  6. data/.gitignore +11 -2
  7. data/.travis.yml +2 -2
  8. data/{History.rdoc → History.md} +251 -145
  9. data/README.ja.md +276 -0
  10. data/README.md +286 -0
  11. data/Rakefile +15 -6
  12. data/Rakefile.cross +7 -11
  13. data/certs/larskanis-2023.pem +24 -0
  14. data/ext/errorcodes.def +4 -0
  15. data/ext/errorcodes.txt +2 -1
  16. data/ext/pg.c +14 -30
  17. data/ext/pg.h +11 -5
  18. data/ext/pg_binary_decoder.c +80 -1
  19. data/ext/pg_binary_encoder.c +225 -1
  20. data/ext/pg_coder.c +17 -8
  21. data/ext/pg_connection.c +162 -64
  22. data/ext/pg_copy_coder.c +307 -18
  23. data/ext/pg_errors.c +1 -1
  24. data/ext/pg_record_coder.c +6 -5
  25. data/ext/pg_result.c +102 -26
  26. data/ext/pg_text_decoder.c +28 -10
  27. data/ext/pg_text_encoder.c +23 -10
  28. data/ext/pg_tuple.c +35 -32
  29. data/ext/pg_type_map.c +4 -3
  30. data/ext/pg_type_map_all_strings.c +3 -3
  31. data/ext/pg_type_map_by_class.c +6 -4
  32. data/ext/pg_type_map_by_column.c +9 -5
  33. data/ext/pg_type_map_by_mri_type.c +1 -1
  34. data/ext/pg_type_map_by_oid.c +8 -5
  35. data/ext/pg_type_map_in_ruby.c +6 -3
  36. data/lib/pg/basic_type_map_based_on_result.rb +21 -1
  37. data/lib/pg/basic_type_map_for_queries.rb +13 -8
  38. data/lib/pg/basic_type_map_for_results.rb +26 -3
  39. data/lib/pg/basic_type_registry.rb +30 -32
  40. data/lib/pg/binary_decoder/date.rb +9 -0
  41. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  42. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  43. data/lib/pg/coder.rb +15 -13
  44. data/lib/pg/connection.rb +148 -86
  45. data/lib/pg/exceptions.rb +7 -0
  46. data/lib/pg/text_decoder/date.rb +18 -0
  47. data/lib/pg/text_decoder/inet.rb +9 -0
  48. data/lib/pg/text_decoder/json.rb +14 -0
  49. data/lib/pg/text_decoder/numeric.rb +9 -0
  50. data/lib/pg/text_decoder/timestamp.rb +30 -0
  51. data/lib/pg/text_encoder/date.rb +12 -0
  52. data/lib/pg/text_encoder/inet.rb +28 -0
  53. data/lib/pg/text_encoder/json.rb +14 -0
  54. data/lib/pg/text_encoder/numeric.rb +9 -0
  55. data/lib/pg/text_encoder/timestamp.rb +24 -0
  56. data/lib/pg/version.rb +1 -1
  57. data/lib/pg.rb +55 -15
  58. data/pg.gemspec +4 -2
  59. data/rakelib/task_extension.rb +1 -1
  60. data/translation/.po4a-version +7 -0
  61. data/translation/po/all.pot +910 -0
  62. data/translation/po/ja.po +1047 -0
  63. data/translation/po4a.cfg +12 -0
  64. data.tar.gz.sig +0 -0
  65. metadata +101 -32
  66. metadata.gz.sig +0 -0
  67. data/README.ja.rdoc +0 -13
  68. data/README.rdoc +0 -214
  69. data/lib/pg/binary_decoder.rb +0 -23
  70. data/lib/pg/constants.rb +0 -12
  71. data/lib/pg/text_decoder.rb +0 -46
  72. 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 'uri'
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 URI, /=/, /:\/\//
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] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
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
- put_copy_end( errmsg )
179
- get_result
180
- raise
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
- put_copy_end
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 => err
262
+ rescue Exception
196
263
  cancel
197
- begin
198
- while get_copy_data
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 || res.result_status != PGRES_COMMAND_OK
209
- while get_copy_data
210
- end
211
- while get_result
212
- end
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
- stop_time = timeo + Process.clock_gettime(Process::CLOCK_MONOTONIC)
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 = stop_time&.-(Process.clock_gettime(Process::CLOCK_MONOTONIC))
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
- ihosts = iopts[:host].split(",", -1) if iopts[:host]
700
- raise PG::ConnectionBad, "could not match #{ihosts.size} host names to #{ihostaddrs.size} hostaddr values" if ihosts && ihosts.size != ihostaddrs.size
701
-
702
- iports = iopts[:port].split(",", -1)
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
- addrs = if Fiber.respond_to?(:scheduler) &&
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
- oopts = iopts.merge(host: mhost, port: iports[idx])
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
- raise PG::ConnectionBad, errors.join("\n")
752
- end
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
- raise PG::ConnectionBad.new(conn.error_message, connection: self) if conn.status == PG::CONNECTION_BAD
822
+ raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
760
823
 
761
- conn.send(:async_connect_or_reset, :connect_poll)
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
- # Check server status.
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
@@ -14,5 +14,12 @@ 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
+
17
24
  end # module PG
18
25
 
@@ -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,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module TextDecoder
6
+ # Init C part of the decoder
7
+ init_inet
8
+ end
9
+ 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,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module TextDecoder
6
+ # Init C part of the decoder
7
+ init_numeric
8
+ end
9
+ 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,12 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module TextEncoder
6
+ class Date < SimpleEncoder
7
+ def encode(value)
8
+ value.respond_to?(:strftime) ? value.strftime("%Y-%m-%d") : value
9
+ end
10
+ end
11
+ end
12
+ 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,14 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ require 'json'
5
+
6
+ module PG
7
+ module TextEncoder
8
+ class JSON < SimpleEncoder
9
+ def encode(value)
10
+ ::JSON.generate(value, quirks_mode: true)
11
+ end
12
+ end
13
+ end
14
+ end # module PG
@@ -0,0 +1,9 @@
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
3
+
4
+ module PG
5
+ module TextEncoder
6
+ # Init C part of the decoder
7
+ init_numeric
8
+ end
9
+ 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
@@ -1,4 +1,4 @@
1
1
  module PG
2
2
  # Library version
3
- VERSION = '1.4.3'
3
+ VERSION = '1.5.3'
4
4
  end