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.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +6 -0
  4. data/{History.rdoc → History.md} +303 -151
  5. data/README.ja.md +300 -0
  6. data/README.md +286 -0
  7. data/Rakefile +16 -4
  8. data/Rakefile.cross +15 -14
  9. data/certs/kanis@comcard.de.pem +20 -0
  10. data/certs/larskanis-2023.pem +24 -0
  11. data/certs/larskanis-2024.pem +24 -0
  12. data/ext/errorcodes.def +8 -5
  13. data/ext/errorcodes.txt +3 -5
  14. data/ext/extconf.rb +7 -0
  15. data/ext/pg.c +15 -30
  16. data/ext/pg.h +10 -6
  17. data/ext/pg_binary_decoder.c +81 -0
  18. data/ext/pg_binary_encoder.c +224 -0
  19. data/ext/pg_coder.c +16 -7
  20. data/ext/pg_connection.c +220 -82
  21. data/ext/pg_copy_coder.c +315 -22
  22. data/ext/pg_record_coder.c +11 -10
  23. data/ext/pg_result.c +93 -19
  24. data/ext/pg_text_decoder.c +31 -10
  25. data/ext/pg_text_encoder.c +38 -19
  26. data/ext/pg_tuple.c +34 -31
  27. data/ext/pg_type_map.c +3 -2
  28. data/ext/pg_type_map_all_strings.c +2 -2
  29. data/ext/pg_type_map_by_class.c +5 -3
  30. data/ext/pg_type_map_by_column.c +7 -3
  31. data/ext/pg_type_map_by_oid.c +7 -4
  32. data/ext/pg_type_map_in_ruby.c +5 -2
  33. data/lib/pg/basic_type_map_based_on_result.rb +21 -1
  34. data/lib/pg/basic_type_map_for_queries.rb +19 -10
  35. data/lib/pg/basic_type_map_for_results.rb +26 -3
  36. data/lib/pg/basic_type_registry.rb +44 -34
  37. data/lib/pg/binary_decoder/date.rb +9 -0
  38. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  39. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  40. data/lib/pg/coder.rb +15 -13
  41. data/lib/pg/connection.rb +158 -64
  42. data/lib/pg/exceptions.rb +13 -0
  43. data/lib/pg/text_decoder/date.rb +21 -0
  44. data/lib/pg/text_decoder/inet.rb +9 -0
  45. data/lib/pg/text_decoder/json.rb +17 -0
  46. data/lib/pg/text_decoder/numeric.rb +9 -0
  47. data/lib/pg/text_decoder/timestamp.rb +30 -0
  48. data/lib/pg/text_encoder/date.rb +13 -0
  49. data/lib/pg/text_encoder/inet.rb +31 -0
  50. data/lib/pg/text_encoder/json.rb +17 -0
  51. data/lib/pg/text_encoder/numeric.rb +9 -0
  52. data/lib/pg/text_encoder/timestamp.rb +24 -0
  53. data/lib/pg/version.rb +1 -1
  54. data/lib/pg.rb +65 -15
  55. data/pg.gemspec +7 -3
  56. data/rakelib/task_extension.rb +1 -1
  57. data.tar.gz.sig +2 -4
  58. metadata +104 -46
  59. metadata.gz.sig +0 -0
  60. data/.appveyor.yml +0 -36
  61. data/.gems +0 -6
  62. data/.gemtest +0 -0
  63. data/.github/workflows/binary-gems.yml +0 -86
  64. data/.github/workflows/source-gem.yml +0 -131
  65. data/.gitignore +0 -13
  66. data/.hgsigs +0 -34
  67. data/.hgtags +0 -41
  68. data/.irbrc +0 -23
  69. data/.pryrc +0 -23
  70. data/.tm_properties +0 -21
  71. data/.travis.yml +0 -49
  72. data/README.ja.rdoc +0 -13
  73. data/README.rdoc +0 -233
  74. data/lib/pg/binary_decoder.rb +0 -23
  75. data/lib/pg/constants.rb +0 -12
  76. data/lib/pg/text_decoder.rb +0 -46
  77. 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) # 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 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,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] = $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)
@@ -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
- put_copy_end( errmsg )
200
- get_result
201
- raise
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
- put_copy_end
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 => err
265
+ rescue Exception
217
266
  cancel
218
- begin
219
- while get_copy_data
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 || res.result_status != PGRES_COMMAND_OK
230
- while get_copy_data
231
- end
232
- while get_result
233
- end
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 attept to flush data.
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
- reset_start
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
- # Resolve DNS in Ruby to avoid blocking state while connecting.
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
- # Check server status.
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,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,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,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,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,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.4'
3
+ VERSION = '1.5.9'
4
4
  end