pg 1.2.1-x64-mingw32 → 1.3.0.rc2-x64-mingw32

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