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