pg 1.2.3 → 1.4.6

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