pg 1.0.0 → 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 (126) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/Gemfile +20 -0
  4. data/History.md +932 -0
  5. data/Manifest.txt +8 -3
  6. data/README-Windows.rdoc +4 -4
  7. data/README.ja.md +300 -0
  8. data/README.md +286 -0
  9. data/Rakefile +41 -138
  10. data/Rakefile.cross +71 -66
  11. data/certs/ged.pem +24 -0
  12. data/certs/kanis@comcard.de.pem +20 -0
  13. data/certs/larskanis-2022.pem +26 -0
  14. data/certs/larskanis-2023.pem +24 -0
  15. data/certs/larskanis-2024.pem +24 -0
  16. data/ext/errorcodes.def +84 -5
  17. data/ext/errorcodes.rb +1 -1
  18. data/ext/errorcodes.txt +23 -6
  19. data/ext/extconf.rb +109 -25
  20. data/ext/gvl_wrappers.c +4 -0
  21. data/ext/gvl_wrappers.h +23 -0
  22. data/ext/pg.c +213 -155
  23. data/ext/pg.h +89 -23
  24. data/ext/pg_binary_decoder.c +164 -16
  25. data/ext/pg_binary_encoder.c +238 -13
  26. data/ext/pg_coder.c +159 -35
  27. data/ext/pg_connection.c +1584 -967
  28. data/ext/pg_copy_coder.c +373 -43
  29. data/ext/pg_errors.c +1 -1
  30. data/ext/pg_record_coder.c +522 -0
  31. data/ext/pg_result.c +710 -217
  32. data/ext/pg_text_decoder.c +630 -43
  33. data/ext/pg_text_encoder.c +222 -72
  34. data/ext/pg_tuple.c +572 -0
  35. data/ext/pg_type_map.c +45 -11
  36. data/ext/pg_type_map_all_strings.c +21 -7
  37. data/ext/pg_type_map_by_class.c +59 -27
  38. data/ext/pg_type_map_by_column.c +80 -37
  39. data/ext/pg_type_map_by_mri_type.c +49 -20
  40. data/ext/pg_type_map_by_oid.c +62 -29
  41. data/ext/pg_type_map_in_ruby.c +56 -22
  42. data/ext/{util.c → pg_util.c} +12 -12
  43. data/ext/{util.h → pg_util.h} +2 -2
  44. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  45. data/lib/pg/basic_type_map_for_queries.rb +202 -0
  46. data/lib/pg/basic_type_map_for_results.rb +104 -0
  47. data/lib/pg/basic_type_registry.rb +311 -0
  48. data/lib/pg/binary_decoder/date.rb +9 -0
  49. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  50. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  51. data/lib/pg/coder.rb +36 -13
  52. data/lib/pg/connection.rb +769 -70
  53. data/lib/pg/exceptions.rb +22 -2
  54. data/lib/pg/result.rb +14 -2
  55. data/lib/pg/text_decoder/date.rb +21 -0
  56. data/lib/pg/text_decoder/inet.rb +9 -0
  57. data/lib/pg/text_decoder/json.rb +17 -0
  58. data/lib/pg/text_decoder/numeric.rb +9 -0
  59. data/lib/pg/text_decoder/timestamp.rb +30 -0
  60. data/lib/pg/text_encoder/date.rb +13 -0
  61. data/lib/pg/text_encoder/inet.rb +31 -0
  62. data/lib/pg/text_encoder/json.rb +17 -0
  63. data/lib/pg/text_encoder/numeric.rb +9 -0
  64. data/lib/pg/text_encoder/timestamp.rb +24 -0
  65. data/lib/pg/tuple.rb +30 -0
  66. data/lib/pg/type_map_by_column.rb +3 -2
  67. data/lib/pg/version.rb +4 -0
  68. data/lib/pg.rb +106 -39
  69. data/misc/openssl-pg-segfault.rb +31 -0
  70. data/misc/postgres/History.txt +9 -0
  71. data/misc/postgres/Manifest.txt +5 -0
  72. data/misc/postgres/README.txt +21 -0
  73. data/misc/postgres/Rakefile +21 -0
  74. data/misc/postgres/lib/postgres.rb +16 -0
  75. data/misc/ruby-pg/History.txt +9 -0
  76. data/misc/ruby-pg/Manifest.txt +5 -0
  77. data/misc/ruby-pg/README.txt +21 -0
  78. data/misc/ruby-pg/Rakefile +21 -0
  79. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  80. data/pg.gemspec +36 -0
  81. data/rakelib/task_extension.rb +46 -0
  82. data/sample/array_insert.rb +20 -0
  83. data/sample/async_api.rb +102 -0
  84. data/sample/async_copyto.rb +39 -0
  85. data/sample/async_mixed.rb +56 -0
  86. data/sample/check_conn.rb +21 -0
  87. data/sample/copydata.rb +71 -0
  88. data/sample/copyfrom.rb +81 -0
  89. data/sample/copyto.rb +19 -0
  90. data/sample/cursor.rb +21 -0
  91. data/sample/disk_usage_report.rb +177 -0
  92. data/sample/issue-119.rb +94 -0
  93. data/sample/losample.rb +69 -0
  94. data/sample/minimal-testcase.rb +17 -0
  95. data/sample/notify_wait.rb +72 -0
  96. data/sample/pg_statistics.rb +285 -0
  97. data/sample/replication_monitor.rb +222 -0
  98. data/sample/test_binary_values.rb +33 -0
  99. data/sample/wal_shipper.rb +434 -0
  100. data/sample/warehouse_partitions.rb +311 -0
  101. data.tar.gz.sig +0 -0
  102. metadata +138 -223
  103. metadata.gz.sig +0 -0
  104. data/.gemtest +0 -0
  105. data/ChangeLog +0 -6595
  106. data/History.rdoc +0 -422
  107. data/README.ja.rdoc +0 -14
  108. data/README.rdoc +0 -167
  109. data/lib/pg/basic_type_mapping.rb +0 -426
  110. data/lib/pg/constants.rb +0 -11
  111. data/lib/pg/text_decoder.rb +0 -51
  112. data/lib/pg/text_encoder.rb +0 -35
  113. data/spec/data/expected_trace.out +0 -26
  114. data/spec/data/random_binary_data +0 -0
  115. data/spec/helpers.rb +0 -348
  116. data/spec/pg/basic_type_mapping_spec.rb +0 -305
  117. data/spec/pg/connection_spec.rb +0 -1719
  118. data/spec/pg/result_spec.rb +0 -456
  119. data/spec/pg/type_map_by_class_spec.rb +0 -138
  120. data/spec/pg/type_map_by_column_spec.rb +0 -222
  121. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  122. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  123. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  124. data/spec/pg/type_map_spec.rb +0 -22
  125. data/spec/pg/type_spec.rb +0 -777
  126. data/spec/pg_spec.rb +0 -50
data/lib/pg/connection.rb CHANGED
@@ -1,10 +1,12 @@
1
- #!/usr/bin/env ruby
1
+ # -*- ruby -*-
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'pg' unless defined?( PG )
4
- require 'uri'
5
+ require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable) # for ruby < 3.0
6
+ require 'socket'
5
7
 
6
8
  # The PostgreSQL connection class. The interface for this class is based on
7
- # {libpq}[http://www.postgresql.org/docs/9.2/interactive/libpq.html], the C
9
+ # {libpq}[http://www.postgresql.org/docs/current/libpq.html], the C
8
10
  # application programmer's interface to PostgreSQL. Some familiarity with libpq
9
11
  # is recommended, but not necessary.
10
12
  #
@@ -18,76 +20,110 @@ require 'uri'
18
20
  #
19
21
  # See the PG::Result class for information on working with the results of a query.
20
22
  #
23
+ # Many methods of this class have three variants kind of:
24
+ # 1. #exec - the base method which is an alias to #async_exec .
25
+ # This is the method that should be used in general.
26
+ # 2. #async_exec - the async aware version of the method, implemented by libpq's async API.
27
+ # 3. #sync_exec - the method version that is implemented by blocking function(s) of libpq.
28
+ #
29
+ # Sync and async version of the method can be switched by Connection.async_api= , however it is not recommended to change the default.
21
30
  class PG::Connection
22
31
 
23
32
  # The order the options are passed to the ::connect method.
24
- CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password]
25
-
33
+ CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password].freeze
34
+ private_constant :CONNECT_ARGUMENT_ORDER
26
35
 
27
- ### Quote the given +value+ for use in a connection-parameter string.
28
- def self::quote_connstr( value )
36
+ ### Quote a single +value+ for use in a connection-parameter string.
37
+ def self.quote_connstr( value )
29
38
  return "'" + value.to_s.gsub( /[\\']/ ) {|m| '\\' + m } + "'"
30
39
  end
31
40
 
41
+ # Convert Hash options to connection String
42
+ #
43
+ # Values are properly quoted and escaped.
44
+ def self.connect_hash_to_string( hash )
45
+ hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
46
+ end
32
47
 
33
- ### Parse the connection +args+ into a connection-parameter string. See PG::Connection.new
34
- ### for valid arguments.
35
- def self::parse_connect_args( *args )
36
- return '' if args.empty?
37
-
38
- hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
39
- option_string = ''
40
- options = {}
48
+ # Shareable program name for Ractor
49
+ PROGRAM_NAME = $PROGRAM_NAME.dup.freeze
50
+ private_constant :PROGRAM_NAME
41
51
 
42
- # Parameter 'fallback_application_name' was introduced in PostgreSQL 9.0
43
- # together with PQescapeLiteral().
44
- if PG::Connection.instance_methods.find {|m| m.to_sym == :escape_literal }
45
- options[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
46
- end
52
+ # Parse the connection +args+ into a connection-parameter string.
53
+ # See PG::Connection.new for valid arguments.
54
+ #
55
+ # It accepts:
56
+ # * an option String kind of "host=name port=5432"
57
+ # * an option Hash kind of {host: "name", port: 5432}
58
+ # * URI string
59
+ # * URI object
60
+ # * positional arguments
61
+ #
62
+ # The method adds the option "fallback_application_name" if it isn't already set.
63
+ # It returns a connection string with "key=value" pairs.
64
+ def self.parse_connect_args( *args )
65
+ hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
66
+ iopts = {}
47
67
 
48
68
  if args.length == 1
49
- case args.first
50
- when URI, /\A#{URI.regexp}\z/
51
- uri = URI(args.first)
52
- options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
53
- when /=/
54
- # Option string style
55
- option_string = args.first.to_s
69
+ case args.first.to_s
70
+ when /=/, /:\/\//
71
+ # Option or URL string style
72
+ conn_string = args.first.to_s
73
+ iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
56
74
  else
57
- # Positional parameters
58
- options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
75
+ # Positional parameters (only host given)
76
+ iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
59
77
  end
60
78
  else
79
+ # Positional parameters with host and more
61
80
  max = CONNECT_ARGUMENT_ORDER.length
62
81
  raise ArgumentError,
63
- "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
82
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
64
83
 
65
84
  CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
66
- options[ k.to_sym ] = v if v
85
+ iopts[ k.to_sym ] = v if v
67
86
  end
87
+ iopts.delete(:tty) # ignore obsolete tty parameter
88
+ end
89
+
90
+ iopts.merge!( hash_arg )
91
+
92
+ if !iopts[:fallback_application_name]
93
+ iopts[:fallback_application_name] = PROGRAM_NAME.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
68
94
  end
69
95
 
70
- options.merge!( hash_arg )
96
+ return connect_hash_to_string(iopts)
97
+ end
71
98
 
72
- if uri
73
- uri.host = nil if options[:host]
74
- uri.port = nil if options[:port]
75
- uri.user = nil if options[:user]
76
- uri.password = nil if options[:password]
77
- uri.path = '' if options[:dbname]
78
- uri.query = URI.encode_www_form( options )
79
- return uri.to_s.sub( /^#{uri.scheme}:(?!\/\/)/, "#{uri.scheme}://" )
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"
80
104
  else
81
- option_string += ' ' unless option_string.empty? && options.empty?
82
- return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
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}"
83
116
  end
117
+ return str
84
118
  end
85
119
 
120
+ BinarySignature = "PGCOPY\n\377\r\n\0"
121
+ private_constant :BinarySignature
86
122
 
87
123
  # call-seq:
88
124
  # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
89
125
  #
90
- # Execute a copy process for transfering data to or from the server.
126
+ # Execute a copy process for transferring data to or from the server.
91
127
  #
92
128
  # This issues the SQL COPY command via #exec. The response to this
93
129
  # (if there is no error in the command) is a PG::Result object that
@@ -130,6 +166,17 @@ class PG::Connection
130
166
  # conn.put_copy_data ['more', 'data', 'to', 'copy']
131
167
  # end
132
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
+ #
133
180
  # Example with CSV output format:
134
181
  # conn.copy_data "COPY my_table TO STDOUT CSV" do
135
182
  # while row=conn.get_copy_data
@@ -151,25 +198,58 @@ class PG::Connection
151
198
  # This receives all rows of +my_table+ as ruby array:
152
199
  # ["some", "data", "to", "copy"]
153
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"]
154
213
 
155
214
  def copy_data( sql, coder=nil )
215
+ raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
156
216
  res = exec( sql )
157
217
 
158
218
  case res.result_status
159
219
  when PGRES_COPY_IN
160
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
+
161
226
  if coder
162
227
  old_coder = self.encoder_for_put_copy_data
163
228
  self.encoder_for_put_copy_data = coder
164
229
  end
230
+
165
231
  yield res
166
232
  rescue Exception => err
167
233
  errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
168
- put_copy_end( errmsg )
169
- get_result
170
- 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
171
241
  else
172
- 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
173
253
  get_last_result
174
254
  ensure
175
255
  self.encoder_for_put_copy_data = old_coder if coder
@@ -182,21 +262,26 @@ class PG::Connection
182
262
  self.decoder_for_get_copy_data = coder
183
263
  end
184
264
  yield res
185
- rescue Exception => err
265
+ rescue Exception
186
266
  cancel
187
- while get_copy_data
188
- end
189
- while get_result
190
- end
267
+ discard_results
191
268
  raise
192
269
  else
193
- res = get_last_result
194
- if !res || res.result_status != PGRES_COMMAND_OK
195
- while get_copy_data
196
- end
197
- while get_result
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)
198
276
  end
199
- raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
277
+ end
278
+ res = get_last_result
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
284
+ raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
200
285
  end
201
286
  res
202
287
  ensure
@@ -213,6 +298,31 @@ class PG::Connection
213
298
  define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
214
299
  end
215
300
 
301
+ #
302
+ # call-seq:
303
+ # conn.transaction { |conn| ... } -> result of the block
304
+ #
305
+ # Executes a +BEGIN+ at the start of the block,
306
+ # and a +COMMIT+ at the end of the block, or
307
+ # +ROLLBACK+ if any exception occurs.
308
+ def transaction
309
+ rollback = false
310
+ exec "BEGIN"
311
+ yield(self)
312
+ rescue PG::RollbackTransaction
313
+ rollback = true
314
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
315
+ block
316
+ exec "ROLLBACK"
317
+ rescue Exception
318
+ rollback = true
319
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
320
+ block
321
+ exec "ROLLBACK"
322
+ raise
323
+ ensure
324
+ exec "COMMIT" unless rollback
325
+ end
216
326
 
217
327
  ### Returns an array of Hashes with connection defaults. See ::conndefaults
218
328
  ### for details.
@@ -236,17 +346,13 @@ class PG::Connection
236
346
  return self.class.conndefaults_hash
237
347
  end
238
348
 
239
- # Method 'conninfo' was introduced in PostgreSQL 9.3.
240
- if self.instance_methods.find{|m| m.to_sym == :conninfo }
241
-
242
- ### Return the Postgres connection info structure as a Hash keyed by option
243
- ### keyword (as a Symbol).
244
- ###
245
- ### See also #conninfo
246
- def conninfo_hash
247
- return self.conninfo.each_with_object({}) do |info, hash|
248
- hash[ info[:keyword].to_sym ] = info[:val]
249
- end
349
+ ### Return the Postgres connection info structure as a Hash keyed by option
350
+ ### keyword (as a Symbol).
351
+ ###
352
+ ### See also #conninfo
353
+ def conninfo_hash
354
+ return self.conninfo.each_with_object({}) do |info, hash|
355
+ hash[ info[:keyword].to_sym ] = info[:val]
250
356
  end
251
357
  end
252
358
 
@@ -268,5 +374,598 @@ class PG::Connection
268
374
  end
269
375
  end
270
376
 
271
- end # class PG::Connection
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
+
394
+ # call-seq:
395
+ # conn.get_result() -> PG::Result
396
+ # conn.get_result() {|pg_result| block }
397
+ #
398
+ # Blocks waiting for the next result from a call to
399
+ # #send_query (or another asynchronous command), and returns
400
+ # it. Returns +nil+ if no more results are available.
401
+ #
402
+ # Note: call this function repeatedly until it returns +nil+, or else
403
+ # you will not be able to issue further commands.
404
+ #
405
+ # If the optional code block is given, it will be passed <i>result</i> as an argument,
406
+ # and the PG::Result object will automatically be cleared when the block terminates.
407
+ # In this instance, <code>conn.exec</code> returns the value of the block.
408
+ def get_result
409
+ block
410
+ sync_get_result
411
+ end
412
+ alias async_get_result get_result
413
+
414
+ # call-seq:
415
+ # conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
416
+ #
417
+ # Return one row of data, +nil+
418
+ # if the copy is done, or +false+ if the call would
419
+ # block (only possible if _nonblock_ is true).
420
+ #
421
+ # If _decoder_ is not set or +nil+, data is returned as binary string.
422
+ #
423
+ # If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
424
+ # PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
425
+ # COPY text format to an Array of Strings.
426
+ # Optionally the decoder can type cast the single fields to various Ruby types in one step,
427
+ # if PG::TextDecoder::CopyRow#type_map is set accordingly.
428
+ #
429
+ # See also #copy_data.
430
+ #
431
+ def get_copy_data(async=false, decoder=nil)
432
+ if async
433
+ return sync_get_copy_data(async, decoder)
434
+ else
435
+ while (res=sync_get_copy_data(true, decoder)) == false
436
+ socket_io.wait_readable
437
+ consume_input
438
+ end
439
+ return res
440
+ end
441
+ end
442
+ alias async_get_copy_data get_copy_data
443
+
444
+
445
+ # In async_api=true mode (default) all send calls run nonblocking.
446
+ # The difference is that setnonblocking(true) disables automatic handling of would-block cases.
447
+ # In async_api=false mode all send calls run directly on libpq.
448
+ # Blocking vs. nonblocking state can be changed in libpq.
272
449
 
450
+ # call-seq:
451
+ # conn.setnonblocking(Boolean) -> nil
452
+ #
453
+ # Sets the nonblocking status of the connection.
454
+ # In the blocking state, calls to #send_query
455
+ # will block until the message is sent to the server,
456
+ # but will not wait for the query results.
457
+ # In the nonblocking state, calls to #send_query
458
+ # will return an error if the socket is not ready for
459
+ # writing.
460
+ # Note: This function does not affect #exec, because
461
+ # that function doesn't return until the server has
462
+ # processed the query and returned the results.
463
+ #
464
+ # Returns +nil+.
465
+ def setnonblocking(enabled)
466
+ singleton_class.async_send_api = !enabled
467
+ self.flush_data = !enabled
468
+ sync_setnonblocking(true)
469
+ end
470
+ alias async_setnonblocking setnonblocking
471
+
472
+ # sync/async isnonblocking methods are switched by async_setnonblocking()
473
+
474
+ # call-seq:
475
+ # conn.isnonblocking() -> Boolean
476
+ #
477
+ # Returns the blocking status of the database connection.
478
+ # Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
479
+ def isnonblocking
480
+ false
481
+ end
482
+ alias async_isnonblocking isnonblocking
483
+ alias nonblocking? isnonblocking
484
+
485
+ # call-seq:
486
+ # conn.put_copy_data( buffer [, encoder] ) -> Boolean
487
+ #
488
+ # Transmits _buffer_ as copy data to the server.
489
+ # Returns true if the data was sent, false if it was
490
+ # not sent (false is only possible if the connection
491
+ # is in nonblocking mode, and this command would block).
492
+ #
493
+ # _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
494
+ # This encodes the data fields given as _buffer_ from an Array of Strings to
495
+ # PostgreSQL's COPY text format inclusive proper escaping. Optionally
496
+ # the encoder can type cast the fields from various Ruby types in one step,
497
+ # if PG::TextEncoder::CopyRow#type_map is set accordingly.
498
+ #
499
+ # Raises an exception if an error occurs.
500
+ #
501
+ # See also #copy_data.
502
+ #
503
+ def put_copy_data(buffer, encoder=nil)
504
+ # sync_put_copy_data does a non-blocking attempt to flush data.
505
+ until res=sync_put_copy_data(buffer, encoder)
506
+ # It didn't flush immediately and allocation of more buffering memory failed.
507
+ # Wait for all data sent by doing a blocking flush.
508
+ res = flush
509
+ end
510
+
511
+ # And do a blocking flush every 100 calls.
512
+ # This is to avoid memory bloat, when sending the data is slower than calls to put_copy_data happen.
513
+ if (@calls_to_put_copy_data += 1) > 100
514
+ @calls_to_put_copy_data = 0
515
+ res = flush
516
+ end
517
+ res
518
+ end
519
+ alias async_put_copy_data put_copy_data
520
+
521
+ # call-seq:
522
+ # conn.put_copy_end( [ error_message ] ) -> Boolean
523
+ #
524
+ # Sends end-of-data indication to the server.
525
+ #
526
+ # _error_message_ is an optional parameter, and if set,
527
+ # forces the COPY command to fail with the string
528
+ # _error_message_.
529
+ #
530
+ # Returns true if the end-of-data was sent, #false* if it was
531
+ # not sent (*false* is only possible if the connection
532
+ # is in nonblocking mode, and this command would block).
533
+ def put_copy_end(*args)
534
+ until sync_put_copy_end(*args)
535
+ flush
536
+ end
537
+ @calls_to_put_copy_data = 0
538
+ flush
539
+ end
540
+ alias async_put_copy_end put_copy_end
541
+
542
+ if method_defined? :sync_encrypt_password
543
+ # call-seq:
544
+ # conn.encrypt_password( password, username, algorithm=nil ) -> String
545
+ #
546
+ # This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
547
+ # It is good practice not to send the original cleartext password in such a command, because it might be exposed in command logs, activity displays, and so on.
548
+ # Instead, use this function to convert the password to encrypted form before it is sent.
549
+ #
550
+ # The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
551
+ # +algorithm+ specifies the encryption algorithm to use to encrypt the password.
552
+ # Currently supported algorithms are +md5+ and +scram-sha-256+ (+on+ and +off+ are also accepted as aliases for +md5+, for compatibility with older server versions).
553
+ # Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
554
+ # If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
555
+ # That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
556
+ # If you wish to use the default algorithm for the server but want to avoid blocking, query +password_encryption+ yourself before calling #encrypt_password, and pass that value as the algorithm.
557
+ #
558
+ # Return value is the encrypted password.
559
+ # The caller can assume the string doesn't contain any special characters that would require escaping.
560
+ #
561
+ # Available since PostgreSQL-10.
562
+ # See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
563
+ def encrypt_password( password, username, algorithm=nil )
564
+ algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
565
+ sync_encrypt_password(password, username, algorithm)
566
+ end
567
+ alias async_encrypt_password encrypt_password
568
+ end
569
+
570
+ # call-seq:
571
+ # conn.reset()
572
+ #
573
+ # Resets the backend connection. This method closes the
574
+ # backend connection and tries to re-connect.
575
+ def reset
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)
584
+ async_connect_or_reset(:reset_poll)
585
+ self
586
+ end
587
+ alias async_reset reset
588
+
589
+ # call-seq:
590
+ # conn.cancel() -> String
591
+ #
592
+ # Requests cancellation of the command currently being
593
+ # processed.
594
+ #
595
+ # Returns +nil+ on success, or a string containing the
596
+ # error message if a failure occurs.
597
+ def cancel
598
+ be_pid = backend_pid
599
+ be_key = backend_key
600
+ cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
601
+
602
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
603
+ # Ruby's nonblocking IO is not really supported on Windows.
604
+ # We work around by using threads and explicit calls to wait_readable/wait_writable.
605
+ cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
606
+ begin
607
+ cl.write_nonblock(cancel_request)
608
+ rescue IO::WaitReadable, Errno::EINTR
609
+ cl.wait_writable
610
+ retry
611
+ end
612
+ begin
613
+ cl.read_nonblock(1)
614
+ rescue IO::WaitReadable, Errno::EINTR
615
+ cl.wait_readable
616
+ retry
617
+ rescue EOFError
618
+ end
619
+ elsif RUBY_ENGINE == 'truffleruby'
620
+ begin
621
+ cl = socket_io.remote_address.connect
622
+ rescue NotImplementedError
623
+ # Workaround for truffleruby < 21.3.0
624
+ cl2 = Socket.for_fd(socket_io.fileno)
625
+ cl2.autoclose = false
626
+ adr = cl2.remote_address
627
+ if adr.ip?
628
+ cl = TCPSocket.new(adr.ip_address, adr.ip_port)
629
+ cl.autoclose = false
630
+ else
631
+ cl = UNIXSocket.new(adr.unix_path)
632
+ cl.autoclose = false
633
+ end
634
+ end
635
+ cl.write(cancel_request)
636
+ cl.read(1)
637
+ else
638
+ cl = socket_io.remote_address.connect
639
+ # Send CANCEL_REQUEST_CODE and parameters
640
+ cl.write(cancel_request)
641
+ # Wait for the postmaster to close the connection, which indicates that it's processed the request.
642
+ cl.read(1)
643
+ end
644
+
645
+ cl.close
646
+ nil
647
+ rescue SystemCallError => err
648
+ err.to_s
649
+ end
650
+ alias async_cancel cancel
651
+
652
+ private def async_connect_or_reset(poll_meth)
653
+ # Track the progress of the connection, waiting for the socket to become readable/writable before polling it
654
+
655
+ if (timeo = conninfo_hash[:connect_timeout].to_i) && timeo > 0
656
+ host_count = conninfo_hash[:host].to_s.count(",") + 1
657
+ stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
658
+ end
659
+
660
+ poll_status = PG::PGRES_POLLING_WRITING
661
+ until poll_status == PG::PGRES_POLLING_OK ||
662
+ poll_status == PG::PGRES_POLLING_FAILED
663
+
664
+ # Set single timeout to parameter "connect_timeout" but
665
+ # don't exceed total connection time of number-of-hosts * connect_timeout.
666
+ timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
667
+ event = if !timeout || timeout >= 0
668
+ # If the socket needs to read, wait 'til it becomes readable to poll again
669
+ case poll_status
670
+ when PG::PGRES_POLLING_READING
671
+ if defined?(IO::READABLE) # ruby-3.0+
672
+ socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
673
+ else
674
+ IO.select([socket_io], nil, [socket_io], timeout)
675
+ end
676
+
677
+ # ...and the same for when the socket needs to write
678
+ when PG::PGRES_POLLING_WRITING
679
+ if defined?(IO::WRITABLE) # ruby-3.0+
680
+ # Use wait instead of wait_readable, since connection errors are delivered as
681
+ # exceptional/priority events on Windows.
682
+ socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
683
+ else
684
+ # io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
685
+ IO.select(nil, [socket_io], [socket_io], timeout)
686
+ end
687
+ end
688
+ end
689
+ # connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
690
+ # connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
691
+ unless event
692
+ if self.class.send(:host_is_named_pipe?, host)
693
+ connhost = "on socket \"#{host}\""
694
+ elsif respond_to?(:hostaddr)
695
+ connhost = "at \"#{host}\" (#{hostaddr}), port #{port}"
696
+ else
697
+ connhost = "at \"#{host}\", port #{port}"
698
+ end
699
+ raise PG::ConnectionBad.new("connection to server #{connhost} failed: timeout expired", connection: self)
700
+ end
701
+
702
+ # Check to see if it's finished or failed yet
703
+ poll_status = send( poll_meth )
704
+ end
705
+
706
+ unless status == PG::CONNECTION_OK
707
+ msg = error_message
708
+ finish
709
+ raise PG::ConnectionBad.new(msg, connection: self)
710
+ end
711
+
712
+ # Set connection to nonblocking to handle all blocking states in ruby.
713
+ # That way a fiber scheduler is able to handle IO requests.
714
+ sync_setnonblocking(true)
715
+ self.flush_data = true
716
+ set_default_encoding
717
+ end
718
+
719
+ class << self
720
+ # call-seq:
721
+ # PG::Connection.new -> conn
722
+ # PG::Connection.new(connection_hash) -> conn
723
+ # PG::Connection.new(connection_string) -> conn
724
+ # PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
725
+ #
726
+ # Create a connection to the specified server.
727
+ #
728
+ # +connection_hash+ must be a ruby Hash with connection parameters.
729
+ # See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
730
+ #
731
+ # There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
732
+ # See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
733
+ #
734
+ # The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
735
+ # [+host+]
736
+ # server hostname
737
+ # [+port+]
738
+ # server port number
739
+ # [+options+]
740
+ # backend options
741
+ # [+tty+]
742
+ # (ignored in all versions of PostgreSQL)
743
+ # [+dbname+]
744
+ # connecting database name
745
+ # [+user+]
746
+ # login user name
747
+ # [+password+]
748
+ # login password
749
+ #
750
+ # Examples:
751
+ #
752
+ # # Connect using all defaults
753
+ # PG::Connection.new
754
+ #
755
+ # # As a Hash
756
+ # PG::Connection.new( dbname: 'test', port: 5432 )
757
+ #
758
+ # # As a String
759
+ # PG::Connection.new( "dbname=test port=5432" )
760
+ #
761
+ # # As an Array
762
+ # PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
763
+ #
764
+ # # As an URI
765
+ # PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
766
+ #
767
+ # If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
768
+ # connection will have its +client_encoding+ set accordingly.
769
+ #
770
+ # Raises a PG::Error if the connection fails.
771
+ def new(*args)
772
+ conn = connect_to_hosts(*args)
773
+
774
+ if block_given?
775
+ begin
776
+ return yield conn
777
+ ensure
778
+ conn.finish
779
+ end
780
+ end
781
+ conn
782
+ end
783
+ alias async_connect new
784
+ alias connect new
785
+ alias open new
786
+ alias setdb new
787
+ alias setdblogin new
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
+
823
+ private def connect_to_hosts(*args)
824
+ option_string = parse_connect_args(*args)
825
+ iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
826
+ iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
827
+
828
+ iopts_for_reset = iopts
829
+ if iopts[:hostaddr]
830
+ # hostaddr is provided -> no need to resolve hostnames
831
+
832
+ elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
833
+ iopts = resolve_hosts(iopts)
834
+ else
835
+ # No host given
836
+ end
837
+ conn = self.connect_start(iopts) or
838
+ raise(PG::Error, "Unable to create a new connection")
839
+
840
+ raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
841
+
842
+ # save the connection options for conn.reset
843
+ conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
844
+ conn.send(:async_connect_or_reset, :connect_poll)
845
+ conn
846
+ end
847
+
848
+ private def host_is_named_pipe?(host_string)
849
+ host_string.empty? || host_string.start_with?("/") || # it's UnixSocket?
850
+ host_string.start_with?("@") || # it's UnixSocket in the abstract namespace?
851
+ # it's a path on Windows?
852
+ (RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/)
853
+ end
854
+
855
+ # call-seq:
856
+ # PG::Connection.ping(connection_hash) -> Integer
857
+ # PG::Connection.ping(connection_string) -> Integer
858
+ # PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
859
+ #
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.
864
+ #
865
+ # See PG::Connection.new for a description of the parameters.
866
+ #
867
+ # Returns one of:
868
+ # [+PQPING_OK+]
869
+ # server is accepting connections
870
+ # [+PQPING_REJECT+]
871
+ # server is alive but rejecting connections
872
+ # [+PQPING_NO_RESPONSE+]
873
+ # could not establish connection
874
+ # [+PQPING_NO_ATTEMPT+]
875
+ # connection not attempted (bad params)
876
+ #
877
+ # See also check_socket for a way to check the connection without doing any server communication.
878
+ def ping(*args)
879
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler
880
+ # Run PQping in a second thread to avoid blocking of the scheduler.
881
+ # Unfortunately there's no nonblocking way to run ping.
882
+ Thread.new { sync_ping(*args) }.value
883
+ else
884
+ sync_ping(*args)
885
+ end
886
+ end
887
+ alias async_ping ping
888
+
889
+ REDIRECT_CLASS_METHODS = PG.make_shareable({
890
+ :new => [:async_connect, :sync_connect],
891
+ :connect => [:async_connect, :sync_connect],
892
+ :open => [:async_connect, :sync_connect],
893
+ :setdb => [:async_connect, :sync_connect],
894
+ :setdblogin => [:async_connect, :sync_connect],
895
+ :ping => [:async_ping, :sync_ping],
896
+ })
897
+ private_constant :REDIRECT_CLASS_METHODS
898
+
899
+ # These methods are affected by PQsetnonblocking
900
+ REDIRECT_SEND_METHODS = PG.make_shareable({
901
+ :isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
902
+ :nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
903
+ :put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
904
+ :put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
905
+ :flush => [:async_flush, :sync_flush],
906
+ })
907
+ private_constant :REDIRECT_SEND_METHODS
908
+ REDIRECT_METHODS = {
909
+ :exec => [:async_exec, :sync_exec],
910
+ :query => [:async_exec, :sync_exec],
911
+ :exec_params => [:async_exec_params, :sync_exec_params],
912
+ :prepare => [:async_prepare, :sync_prepare],
913
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
914
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
915
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
916
+ :setnonblocking => [:async_setnonblocking, :sync_setnonblocking],
917
+ :get_result => [:async_get_result, :sync_get_result],
918
+ :get_last_result => [:async_get_last_result, :sync_get_last_result],
919
+ :get_copy_data => [:async_get_copy_data, :sync_get_copy_data],
920
+ :reset => [:async_reset, :sync_reset],
921
+ :set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
922
+ :client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
923
+ :cancel => [:async_cancel, :sync_cancel],
924
+ }
925
+ private_constant :REDIRECT_METHODS
926
+
927
+ if PG::Connection.instance_methods.include? :async_encrypt_password
928
+ REDIRECT_METHODS.merge!({
929
+ :encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
930
+ })
931
+ end
932
+ PG.make_shareable(REDIRECT_METHODS)
933
+
934
+ def async_send_api=(enable)
935
+ REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
936
+ undef_method(ali) if method_defined?(ali)
937
+ alias_method( ali, enable ? async : sync )
938
+ end
939
+ end
940
+
941
+ # Switch between sync and async libpq API.
942
+ #
943
+ # PG::Connection.async_api = true
944
+ # this is the default.
945
+ # It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
946
+ #
947
+ # PG::Connection.async_api = false
948
+ # sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
949
+ #
950
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
951
+ # pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
952
+ #
953
+ # _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
954
+ # Do not use this method in production code.
955
+ # Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
956
+ #
957
+ def async_api=(enable)
958
+ self.async_send_api = enable
959
+ REDIRECT_METHODS.each do |ali, (async, sync)|
960
+ remove_method(ali) if method_defined?(ali)
961
+ alias_method( ali, enable ? async : sync )
962
+ end
963
+ REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
964
+ singleton_class.remove_method(ali) if method_defined?(ali)
965
+ singleton_class.alias_method(ali, enable ? async : sync )
966
+ end
967
+ end
968
+ end
969
+
970
+ self.async_api = true
971
+ end # class PG::Connection