pg 1.2.3 → 1.3.5

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 (106) 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 +153 -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 +131 -25
  26. data/ext/gvl_wrappers.c +4 -0
  27. data/ext/gvl_wrappers.h +23 -0
  28. data/ext/pg.c +59 -4
  29. data/ext/pg.h +19 -1
  30. data/ext/pg_coder.c +82 -28
  31. data/ext/pg_connection.c +680 -508
  32. data/ext/pg_copy_coder.c +45 -16
  33. data/ext/pg_record_coder.c +45 -15
  34. data/ext/pg_result.c +77 -40
  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 +589 -58
  52. data/lib/pg/version.rb +4 -0
  53. data/lib/pg.rb +47 -32
  54. data/misc/openssl-pg-segfault.rb +31 -0
  55. data/misc/postgres/History.txt +9 -0
  56. data/misc/postgres/Manifest.txt +5 -0
  57. data/misc/postgres/README.txt +21 -0
  58. data/misc/postgres/Rakefile +21 -0
  59. data/misc/postgres/lib/postgres.rb +16 -0
  60. data/misc/ruby-pg/History.txt +9 -0
  61. data/misc/ruby-pg/Manifest.txt +5 -0
  62. data/misc/ruby-pg/README.txt +21 -0
  63. data/misc/ruby-pg/Rakefile +21 -0
  64. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  65. data/pg.gemspec +32 -0
  66. data/rakelib/task_extension.rb +46 -0
  67. data/sample/array_insert.rb +20 -0
  68. data/sample/async_api.rb +102 -0
  69. data/sample/async_copyto.rb +39 -0
  70. data/sample/async_mixed.rb +56 -0
  71. data/sample/check_conn.rb +21 -0
  72. data/sample/copydata.rb +71 -0
  73. data/sample/copyfrom.rb +81 -0
  74. data/sample/copyto.rb +19 -0
  75. data/sample/cursor.rb +21 -0
  76. data/sample/disk_usage_report.rb +177 -0
  77. data/sample/issue-119.rb +94 -0
  78. data/sample/losample.rb +69 -0
  79. data/sample/minimal-testcase.rb +17 -0
  80. data/sample/notify_wait.rb +72 -0
  81. data/sample/pg_statistics.rb +285 -0
  82. data/sample/replication_monitor.rb +222 -0
  83. data/sample/test_binary_values.rb +33 -0
  84. data/sample/wal_shipper.rb +434 -0
  85. data/sample/warehouse_partitions.rb +311 -0
  86. data.tar.gz.sig +0 -0
  87. metadata +87 -224
  88. metadata.gz.sig +0 -0
  89. data/ChangeLog +0 -0
  90. data/lib/pg/basic_type_mapping.rb +0 -522
  91. data/spec/data/expected_trace.out +0 -26
  92. data/spec/data/random_binary_data +0 -0
  93. data/spec/helpers.rb +0 -380
  94. data/spec/pg/basic_type_mapping_spec.rb +0 -630
  95. data/spec/pg/connection_spec.rb +0 -1949
  96. data/spec/pg/connection_sync_spec.rb +0 -41
  97. data/spec/pg/result_spec.rb +0 -681
  98. data/spec/pg/tuple_spec.rb +0 -333
  99. data/spec/pg/type_map_by_class_spec.rb +0 -138
  100. data/spec/pg/type_map_by_column_spec.rb +0 -226
  101. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  102. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  103. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  104. data/spec/pg/type_map_spec.rb +0 -22
  105. data/spec/pg/type_spec.rb +0 -1123
  106. 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,68 +21,153 @@ 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 = ''
49
+ # Decode a connection string to Hash options
50
+ #
51
+ # Value are properly unquoted and unescaped.
52
+ def self.connect_string_to_hash( str )
41
53
  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 }
54
+ key = nil
55
+ value = String.new
56
+ str.scan(/\G\s*(?>([^\s\\\']+)\s*=\s*|([^\s\\\']+)|'((?:[^\'\\]|\\.)*)'|(\\.?)|(\S))(\s|\z)?/m) do
57
+ |k, word, sq, esc, garbage, sep|
58
+ raise ArgumentError, "unterminated quoted string in connection info string: #{str.inspect}" if garbage
59
+ if k
60
+ key = k
61
+ else
62
+ value << (word || (sq || esc).gsub(/\\(.)/, '\\1'))
63
+ end
64
+ if sep
65
+ raise ArgumentError, "missing = after #{value.inspect}" unless key
66
+ options[key.to_sym] = value
67
+ key = nil
68
+ value = String.new
69
+ end
47
70
  end
71
+ options
72
+ end
73
+
74
+ # URI defined in RFC3986
75
+ # This regexp is modified to allow host to specify multiple comma separated components captured as <hostports> and to disallow comma in hostnames.
76
+ # Taken from: https://github.com/ruby/ruby/blob/be04006c7d2f9aeb7e9d8d09d945b3a9c7850202/lib/uri/rfc3986_parser.rb#L6
77
+ HOST_AND_PORT = /(?<hostport>(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[-\.!$&-+0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)/
78
+ POSTGRESQL_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<hostports>#{HOST_AND_PORT}(?:,\g<hostport>)*))(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/
79
+
80
+ # Parse the connection +args+ into a connection-parameter string.
81
+ # See PG::Connection.new for valid arguments.
82
+ #
83
+ # It accepts:
84
+ # * an option String kind of "host=name port=5432"
85
+ # * an option Hash kind of {host: "name", port: 5432}
86
+ # * URI string
87
+ # * URI object
88
+ # * positional arguments
89
+ #
90
+ # The method adds the option "hostaddr" and "fallback_application_name" if they aren't already set.
91
+ # The URI and the options string is passed through and "hostaddr" as well as "fallback_application_name"
92
+ # are added to the end.
93
+ def self::parse_connect_args( *args )
94
+ hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
95
+ option_string = ""
96
+ iopts = {}
48
97
 
49
98
  if args.length == 1
50
99
  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
100
+ when URI, POSTGRESQL_URI
101
+ uri = args.first.to_s
102
+ uri_match = POSTGRESQL_URI.match(uri)
103
+ if uri_match['query']
104
+ iopts = URI.decode_www_form(uri_match['query']).to_h.transform_keys(&:to_sym)
105
+ end
106
+ # extract "host1,host2" from "host1:5432,host2:5432"
107
+ iopts[:host] = uri_match['hostports'].split(',', -1).map do |hostport|
108
+ hostmatch = /\A#{HOST_AND_PORT}\z/.match(hostport)
109
+ hostmatch['IPv6address'] || hostmatch['IPv4address'] || hostmatch['reg-name']&.gsub(/%(\h\h)/){ $1.hex.chr }
110
+ end.join(',')
111
+ oopts = {}
54
112
  when /=/
55
113
  # Option string style
56
114
  option_string = args.first.to_s
115
+ iopts = connect_string_to_hash(option_string)
116
+ oopts = {}
57
117
  else
58
- # Positional parameters
59
- options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
118
+ # Positional parameters (only host given)
119
+ iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
120
+ oopts = iopts.dup
60
121
  end
61
122
  else
123
+ # Positional parameters
62
124
  max = CONNECT_ARGUMENT_ORDER.length
63
125
  raise ArgumentError,
64
126
  "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
65
127
 
66
128
  CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
67
- options[ k.to_sym ] = v if v
129
+ iopts[ k.to_sym ] = v if v
130
+ end
131
+ iopts.delete(:tty) # ignore obsolete tty parameter
132
+ oopts = iopts.dup
133
+ end
134
+
135
+ iopts.merge!( hash_arg )
136
+ oopts.merge!( hash_arg )
137
+
138
+ # Resolve DNS in Ruby to avoid blocking state while connecting, when it ...
139
+ if (host=iopts[:host]) && !iopts[:hostaddr]
140
+ hostaddrs = host.split(",", -1).map do |mhost|
141
+ if !mhost.empty? && !mhost.start_with?("/") && # isn't UnixSocket
142
+ # isn't a path on Windows
143
+ (RUBY_PLATFORM !~ /mingw|mswin/ || mhost !~ /\A\w:[\/\\]/)
144
+
145
+ if Fiber.respond_to?(:scheduler) &&
146
+ Fiber.scheduler &&
147
+ RUBY_VERSION < '3.1.'
148
+
149
+ # Use a second thread to avoid blocking of the scheduler.
150
+ # `IPSocket.getaddress` isn't fiber aware before ruby-3.1.
151
+ Thread.new{ IPSocket.getaddress(mhost) rescue '' }.value
152
+ else
153
+ IPSocket.getaddress(mhost) rescue ''
154
+ end
155
+ end
68
156
  end
157
+ oopts[:hostaddr] = hostaddrs.join(",") if hostaddrs.any?
69
158
  end
70
159
 
71
- options.merge!( hash_arg )
160
+ if !iopts[:fallback_application_name]
161
+ oopts[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
162
+ end
72
163
 
73
164
  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}://" )
165
+ uri += uri_match['query'] ? "&" : "?"
166
+ uri += URI.encode_www_form( oopts )
167
+ return uri
81
168
  else
82
- option_string += ' ' unless option_string.empty? && options.empty?
83
- return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
169
+ option_string += ' ' unless option_string.empty? && oopts.empty?
170
+ return option_string + connect_hash_to_string(oopts)
84
171
  end
85
172
  end
86
173
 
@@ -88,7 +175,7 @@ class PG::Connection
88
175
  # call-seq:
89
176
  # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
90
177
  #
91
- # Execute a copy process for transfering data to or from the server.
178
+ # Execute a copy process for transferring data to or from the server.
92
179
  #
93
180
  # This issues the SQL COPY command via #exec. The response to this
94
181
  # (if there is no error in the command) is a PG::Result object that
@@ -154,6 +241,7 @@ class PG::Connection
154
241
  # ["more", "data", "to", "copy"]
155
242
 
156
243
  def copy_data( sql, coder=nil )
244
+ raise PG::NotInBlockingMode, "copy_data can not be used in nonblocking mode" if nonblocking?
157
245
  res = exec( sql )
158
246
 
159
247
  case res.result_status
@@ -214,6 +302,25 @@ class PG::Connection
214
302
  define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
215
303
  end
216
304
 
305
+ #
306
+ # call-seq:
307
+ # conn.transaction { |conn| ... } -> result of the block
308
+ #
309
+ # Executes a +BEGIN+ at the start of the block,
310
+ # and a +COMMIT+ at the end of the block, or
311
+ # +ROLLBACK+ if any exception occurs.
312
+ def transaction
313
+ exec "BEGIN"
314
+ res = yield(self)
315
+ rescue Exception
316
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
317
+ block
318
+ exec "ROLLBACK"
319
+ raise
320
+ else
321
+ exec "COMMIT"
322
+ res
323
+ end
217
324
 
218
325
  ### Returns an array of Hashes with connection defaults. See ::conndefaults
219
326
  ### for details.
@@ -237,17 +344,13 @@ class PG::Connection
237
344
  return self.class.conndefaults_hash
238
345
  end
239
346
 
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
347
+ ### Return the Postgres connection info structure as a Hash keyed by option
348
+ ### keyword (as a Symbol).
349
+ ###
350
+ ### See also #conninfo
351
+ def conninfo_hash
352
+ return self.conninfo.each_with_object({}) do |info, hash|
353
+ hash[ info[:keyword].to_sym ] = info[:val]
251
354
  end
252
355
  end
253
356
 
@@ -269,23 +372,451 @@ class PG::Connection
269
372
  end
270
373
  end
271
374
 
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 )
375
+ # call-seq:
376
+ # conn.get_result() -> PG::Result
377
+ # conn.get_result() {|pg_result| block }
378
+ #
379
+ # Blocks waiting for the next result from a call to
380
+ # #send_query (or another asynchronous command), and returns
381
+ # it. Returns +nil+ if no more results are available.
382
+ #
383
+ # Note: call this function repeatedly until it returns +nil+, or else
384
+ # you will not be able to issue further commands.
385
+ #
386
+ # If the optional code block is given, it will be passed <i>result</i> as an argument,
387
+ # and the PG::Result object will automatically be cleared when the block terminates.
388
+ # In this instance, <code>conn.exec</code> returns the value of the block.
389
+ def get_result
390
+ block
391
+ sync_get_result
392
+ end
393
+ alias async_get_result get_result
394
+
395
+ # call-seq:
396
+ # conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
397
+ #
398
+ # Return one row of data, +nil+
399
+ # if the copy is done, or +false+ if the call would
400
+ # block (only possible if _nonblock_ is true).
401
+ #
402
+ # If _decoder_ is not set or +nil+, data is returned as binary string.
403
+ #
404
+ # If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
405
+ # PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
406
+ # COPY text format to an Array of Strings.
407
+ # Optionally the decoder can type cast the single fields to various Ruby types in one step,
408
+ # if PG::TextDecoder::CopyRow#type_map is set accordingly.
409
+ #
410
+ # See also #copy_data.
411
+ #
412
+ def get_copy_data(async=false, decoder=nil)
413
+ if async
414
+ return sync_get_copy_data(async, decoder)
415
+ else
416
+ while (res=sync_get_copy_data(true, decoder)) == false
417
+ socket_io.wait_readable
418
+ consume_input
419
+ end
420
+ return res
421
+ end
422
+ end
423
+ alias async_get_copy_data get_copy_data
424
+
425
+
426
+ # In async_api=true mode (default) all send calls run nonblocking.
427
+ # The difference is that setnonblocking(true) disables automatic handling of would-block cases.
428
+ # In async_api=false mode all send calls run directly on libpq.
429
+ # Blocking vs. nonblocking state can be changed in libpq.
430
+
431
+ # call-seq:
432
+ # conn.setnonblocking(Boolean) -> nil
433
+ #
434
+ # Sets the nonblocking status of the connection.
435
+ # In the blocking state, calls to #send_query
436
+ # will block until the message is sent to the server,
437
+ # but will not wait for the query results.
438
+ # In the nonblocking state, calls to #send_query
439
+ # will return an error if the socket is not ready for
440
+ # writing.
441
+ # Note: This function does not affect #exec, because
442
+ # that function doesn't return until the server has
443
+ # processed the query and returned the results.
444
+ #
445
+ # Returns +nil+.
446
+ def setnonblocking(enabled)
447
+ singleton_class.async_send_api = !enabled
448
+ self.flush_data = !enabled
449
+ sync_setnonblocking(true)
450
+ end
451
+ alias async_setnonblocking setnonblocking
452
+
453
+ # sync/async isnonblocking methods are switched by async_setnonblocking()
454
+
455
+ # call-seq:
456
+ # conn.isnonblocking() -> Boolean
457
+ #
458
+ # Returns the blocking status of the database connection.
459
+ # Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
460
+ def isnonblocking
461
+ false
462
+ end
463
+ alias async_isnonblocking isnonblocking
464
+ alias nonblocking? isnonblocking
465
+
466
+ # call-seq:
467
+ # conn.put_copy_data( buffer [, encoder] ) -> Boolean
468
+ #
469
+ # Transmits _buffer_ as copy data to the server.
470
+ # Returns true if the data was sent, false if it was
471
+ # not sent (false is only possible if the connection
472
+ # is in nonblocking mode, and this command would block).
473
+ #
474
+ # _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
475
+ # This encodes the data fields given as _buffer_ from an Array of Strings to
476
+ # PostgreSQL's COPY text format inclusive proper escaping. Optionally
477
+ # the encoder can type cast the fields from various Ruby types in one step,
478
+ # if PG::TextEncoder::CopyRow#type_map is set accordingly.
479
+ #
480
+ # Raises an exception if an error occurs.
481
+ #
482
+ # See also #copy_data.
483
+ #
484
+ def put_copy_data(buffer, encoder=nil)
485
+ until sync_put_copy_data(buffer, encoder)
486
+ flush
487
+ end
488
+ flush
489
+ end
490
+ alias async_put_copy_data put_copy_data
491
+
492
+ # call-seq:
493
+ # conn.put_copy_end( [ error_message ] ) -> Boolean
494
+ #
495
+ # Sends end-of-data indication to the server.
496
+ #
497
+ # _error_message_ is an optional parameter, and if set,
498
+ # forces the COPY command to fail with the string
499
+ # _error_message_.
500
+ #
501
+ # Returns true if the end-of-data was sent, #false* if it was
502
+ # not sent (*false* is only possible if the connection
503
+ # is in nonblocking mode, and this command would block).
504
+ def put_copy_end(*args)
505
+ until sync_put_copy_end(*args)
506
+ flush
507
+ end
508
+ flush
509
+ end
510
+ alias async_put_copy_end put_copy_end
511
+
512
+ if method_defined? :sync_encrypt_password
513
+ # call-seq:
514
+ # conn.encrypt_password( password, username, algorithm=nil ) -> String
515
+ #
516
+ # This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
517
+ # 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.
518
+ # Instead, use this function to convert the password to encrypted form before it is sent.
519
+ #
520
+ # The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
521
+ # +algorithm+ specifies the encryption algorithm to use to encrypt the password.
522
+ # 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).
523
+ # Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
524
+ # If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
525
+ # That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
526
+ # 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.
527
+ #
528
+ # Return value is the encrypted password.
529
+ # The caller can assume the string doesn't contain any special characters that would require escaping.
530
+ #
531
+ # Available since PostgreSQL-10.
532
+ # See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
533
+ def encrypt_password( password, username, algorithm=nil )
534
+ algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
535
+ sync_encrypt_password(password, username, algorithm)
536
+ end
537
+ alias async_encrypt_password encrypt_password
538
+ end
539
+
540
+ # call-seq:
541
+ # conn.reset()
542
+ #
543
+ # Resets the backend connection. This method closes the
544
+ # backend connection and tries to re-connect.
545
+ def reset
546
+ reset_start
547
+ async_connect_or_reset(:reset_poll)
548
+ end
549
+ alias async_reset reset
550
+
551
+ # call-seq:
552
+ # conn.cancel() -> String
553
+ #
554
+ # Requests cancellation of the command currently being
555
+ # processed.
556
+ #
557
+ # Returns +nil+ on success, or a string containing the
558
+ # error message if a failure occurs.
559
+ def cancel
560
+ be_pid = backend_pid
561
+ be_key = backend_key
562
+ cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
563
+
564
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
565
+ # Ruby's nonblocking IO is not really supported on Windows.
566
+ # We work around by using threads and explicit calls to wait_readable/wait_writable.
567
+ cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
568
+ begin
569
+ cl.write_nonblock(cancel_request)
570
+ rescue IO::WaitReadable, Errno::EINTR
571
+ cl.wait_writable
572
+ retry
573
+ end
574
+ begin
575
+ cl.read_nonblock(1)
576
+ rescue IO::WaitReadable, Errno::EINTR
577
+ cl.wait_readable
578
+ retry
579
+ rescue EOFError
580
+ end
581
+ elsif RUBY_ENGINE == 'truffleruby'
582
+ begin
583
+ cl = socket_io.remote_address.connect
584
+ rescue NotImplementedError
585
+ # Workaround for truffleruby < 21.3.0
586
+ cl2 = Socket.for_fd(socket_io.fileno)
587
+ cl2.autoclose = false
588
+ adr = cl2.remote_address
589
+ if adr.ip?
590
+ cl = TCPSocket.new(adr.ip_address, adr.ip_port)
591
+ cl.autoclose = false
592
+ else
593
+ cl = UNIXSocket.new(adr.unix_path)
594
+ cl.autoclose = false
595
+ end
596
+ end
597
+ cl.write(cancel_request)
598
+ cl.read(1)
599
+ else
600
+ cl = socket_io.remote_address.connect
601
+ # Send CANCEL_REQUEST_CODE and parameters
602
+ cl.write(cancel_request)
603
+ # Wait for the postmaster to close the connection, which indicates that it's processed the request.
604
+ cl.read(1)
605
+ end
606
+
607
+ cl.close
608
+ nil
609
+ rescue SystemCallError => err
610
+ err.to_s
611
+ end
612
+ alias async_cancel cancel
613
+
614
+ private def async_connect_or_reset(poll_meth)
615
+ # Track the progress of the connection, waiting for the socket to become readable/writable before polling it
616
+ poll_status = PG::PGRES_POLLING_WRITING
617
+ until poll_status == PG::PGRES_POLLING_OK ||
618
+ poll_status == PG::PGRES_POLLING_FAILED
619
+
620
+ # If the socket needs to read, wait 'til it becomes readable to poll again
621
+ case poll_status
622
+ when PG::PGRES_POLLING_READING
623
+ socket_io.wait_readable
624
+
625
+ # ...and the same for when the socket needs to write
626
+ when PG::PGRES_POLLING_WRITING
627
+ socket_io.wait_writable
628
+ end
629
+
630
+ # Check to see if it's finished or failed yet
631
+ poll_status = send( poll_meth )
632
+ end
633
+
634
+ unless status == PG::CONNECTION_OK
635
+ msg = error_message
636
+ finish
637
+ raise PG::ConnectionBad, msg
638
+ end
639
+
640
+ # Set connection to nonblocking to handle all blocking states in ruby.
641
+ # That way a fiber scheduler is able to handle IO requests.
642
+ sync_setnonblocking(true)
643
+ self.flush_data = true
644
+ set_default_encoding
645
+
646
+ self
647
+ end
648
+
649
+ class << self
650
+ # call-seq:
651
+ # PG::Connection.new -> conn
652
+ # PG::Connection.new(connection_hash) -> conn
653
+ # PG::Connection.new(connection_string) -> conn
654
+ # PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
655
+ #
656
+ # Create a connection to the specified server.
657
+ #
658
+ # +connection_hash+ must be a ruby Hash with connection parameters.
659
+ # See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
660
+ #
661
+ # There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
662
+ # See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
663
+ #
664
+ # The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
665
+ # [+host+]
666
+ # server hostname
667
+ # [+port+]
668
+ # server port number
669
+ # [+options+]
670
+ # backend options
671
+ # [+tty+]
672
+ # (ignored in all versions of PostgreSQL)
673
+ # [+dbname+]
674
+ # connecting database name
675
+ # [+user+]
676
+ # login user name
677
+ # [+password+]
678
+ # login password
679
+ #
680
+ # Examples:
681
+ #
682
+ # # Connect using all defaults
683
+ # PG::Connection.new
684
+ #
685
+ # # As a Hash
686
+ # PG::Connection.new( dbname: 'test', port: 5432 )
687
+ #
688
+ # # As a String
689
+ # PG::Connection.new( "dbname=test port=5432" )
690
+ #
691
+ # # As an Array
692
+ # PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
693
+ #
694
+ # # As an URI
695
+ # PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
696
+ #
697
+ # If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
698
+ # connection will have its +client_encoding+ set accordingly.
699
+ #
700
+ # Raises a PG::Error if the connection fails.
701
+ def new(*args, **kwargs)
702
+ conn = self.connect_start(*args, **kwargs ) or
703
+ raise(PG::Error, "Unable to create a new connection")
704
+
705
+ raise(PG::ConnectionBad, conn.error_message) if conn.status == PG::CONNECTION_BAD
706
+
707
+ conn.send(:async_connect_or_reset, :connect_poll)
708
+ end
709
+ alias async_connect new
710
+ alias connect new
711
+ alias open new
712
+ alias setdb new
713
+ alias setdblogin new
714
+
715
+ # call-seq:
716
+ # PG::Connection.ping(connection_hash) -> Integer
717
+ # PG::Connection.ping(connection_string) -> Integer
718
+ # PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
719
+ #
720
+ # Check server status.
721
+ #
722
+ # See PG::Connection.new for a description of the parameters.
723
+ #
724
+ # Returns one of:
725
+ # [+PQPING_OK+]
726
+ # server is accepting connections
727
+ # [+PQPING_REJECT+]
728
+ # server is alive but rejecting connections
729
+ # [+PQPING_NO_RESPONSE+]
730
+ # could not establish connection
731
+ # [+PQPING_NO_ATTEMPT+]
732
+ # connection not attempted (bad params)
733
+ def ping(*args)
734
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler
735
+ # Run PQping in a second thread to avoid blocking of the scheduler.
736
+ # Unfortunately there's no nonblocking way to run ping.
737
+ Thread.new { sync_ping(*args) }.value
738
+ else
739
+ sync_ping(*args)
740
+ end
741
+ end
742
+ alias async_ping ping
743
+
744
+ REDIRECT_CLASS_METHODS = {
745
+ :new => [:async_connect, :sync_connect],
746
+ :connect => [:async_connect, :sync_connect],
747
+ :open => [:async_connect, :sync_connect],
748
+ :setdb => [:async_connect, :sync_connect],
749
+ :setdblogin => [:async_connect, :sync_connect],
750
+ :ping => [:async_ping, :sync_ping],
751
+ }
752
+
753
+ # These methods are affected by PQsetnonblocking
754
+ REDIRECT_SEND_METHODS = {
755
+ :isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
756
+ :nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
757
+ :put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
758
+ :put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
759
+ :flush => [:async_flush, :sync_flush],
760
+ }
761
+ REDIRECT_METHODS = {
762
+ :exec => [:async_exec, :sync_exec],
763
+ :query => [:async_exec, :sync_exec],
764
+ :exec_params => [:async_exec_params, :sync_exec_params],
765
+ :prepare => [:async_prepare, :sync_prepare],
766
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
767
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
768
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
769
+ :setnonblocking => [:async_setnonblocking, :sync_setnonblocking],
770
+ :get_result => [:async_get_result, :sync_get_result],
771
+ :get_last_result => [:async_get_last_result, :sync_get_last_result],
772
+ :get_copy_data => [:async_get_copy_data, :sync_get_copy_data],
773
+ :reset => [:async_reset, :sync_reset],
774
+ :set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
775
+ :client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
776
+ :cancel => [:async_cancel, :sync_cancel],
777
+ }
778
+
779
+ if PG::Connection.instance_methods.include? :async_encrypt_password
780
+ REDIRECT_METHODS.merge!({
781
+ :encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
782
+ })
783
+ end
784
+
785
+ def async_send_api=(enable)
786
+ REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
787
+ undef_method(ali) if method_defined?(ali)
788
+ alias_method( ali, enable ? async : sync )
789
+ end
790
+ end
791
+
792
+ # Switch between sync and async libpq API.
793
+ #
794
+ # PG::Connection.async_api = true
795
+ # this is the default.
796
+ # It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
797
+ #
798
+ # PG::Connection.async_api = false
799
+ # sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
800
+ #
801
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
802
+ # pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
803
+ #
804
+ # _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
805
+ # Do not use this method in production code.
806
+ # Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
807
+ #
808
+ def async_api=(enable)
809
+ self.async_send_api = enable
810
+ REDIRECT_METHODS.each do |ali, (async, sync)|
811
+ remove_method(ali) if method_defined?(ali)
812
+ alias_method( ali, enable ? async : sync )
813
+ end
814
+ REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
815
+ singleton_class.remove_method(ali) if method_defined?(ali)
816
+ singleton_class.alias_method(ali, enable ? async : sync )
817
+ end
286
818
  end
287
819
  end
288
820
 
289
- # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
290
821
  self.async_api = true
291
822
  end # class PG::Connection