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