pg 1.2.3 → 1.6.1

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 (135) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +986 -0
  4. data/Gemfile +23 -0
  5. data/README-Windows.rdoc +1 -1
  6. data/README.ja.md +300 -0
  7. data/README.md +327 -0
  8. data/Rakefile +123 -144
  9. data/certs/ged.pem +24 -0
  10. data/certs/kanis@comcard.de.pem +20 -0
  11. data/certs/larskanis-2022.pem +26 -0
  12. data/certs/larskanis-2023.pem +24 -0
  13. data/certs/larskanis-2024.pem +24 -0
  14. data/ext/errorcodes.def +16 -5
  15. data/ext/errorcodes.rb +0 -0
  16. data/ext/errorcodes.txt +5 -5
  17. data/ext/extconf.rb +259 -33
  18. data/ext/gvl_wrappers.c +17 -2
  19. data/ext/gvl_wrappers.h +56 -0
  20. data/ext/pg.c +89 -63
  21. data/ext/pg.h +31 -8
  22. data/ext/pg_binary_decoder.c +232 -1
  23. data/ext/pg_binary_encoder.c +428 -1
  24. data/ext/pg_cancel_connection.c +360 -0
  25. data/ext/pg_coder.c +148 -36
  26. data/ext/pg_connection.c +1365 -817
  27. data/ext/pg_copy_coder.c +360 -38
  28. data/ext/pg_errors.c +1 -1
  29. data/ext/pg_record_coder.c +56 -25
  30. data/ext/pg_result.c +187 -76
  31. data/ext/pg_text_decoder.c +32 -11
  32. data/ext/pg_text_encoder.c +65 -33
  33. data/ext/pg_tuple.c +84 -61
  34. data/ext/pg_type_map.c +44 -10
  35. data/ext/pg_type_map_all_strings.c +17 -3
  36. data/ext/pg_type_map_by_class.c +54 -27
  37. data/ext/pg_type_map_by_column.c +74 -31
  38. data/ext/pg_type_map_by_mri_type.c +48 -19
  39. data/ext/pg_type_map_by_oid.c +61 -27
  40. data/ext/pg_type_map_in_ruby.c +55 -21
  41. data/ext/pg_util.c +2 -2
  42. data/lib/pg/basic_type_map_based_on_result.rb +67 -0
  43. data/lib/pg/basic_type_map_for_queries.rb +206 -0
  44. data/lib/pg/basic_type_map_for_results.rb +104 -0
  45. data/lib/pg/basic_type_registry.rb +311 -0
  46. data/lib/pg/binary_decoder/date.rb +9 -0
  47. data/lib/pg/binary_decoder/timestamp.rb +26 -0
  48. data/lib/pg/binary_encoder/timestamp.rb +20 -0
  49. data/lib/pg/cancel_connection.rb +53 -0
  50. data/lib/pg/coder.rb +18 -14
  51. data/lib/pg/connection.rb +894 -91
  52. data/lib/pg/exceptions.rb +20 -1
  53. data/lib/pg/text_decoder/date.rb +21 -0
  54. data/lib/pg/text_decoder/inet.rb +9 -0
  55. data/lib/pg/text_decoder/json.rb +17 -0
  56. data/lib/pg/text_decoder/numeric.rb +9 -0
  57. data/lib/pg/text_decoder/timestamp.rb +30 -0
  58. data/lib/pg/text_encoder/date.rb +13 -0
  59. data/lib/pg/text_encoder/inet.rb +31 -0
  60. data/lib/pg/text_encoder/json.rb +17 -0
  61. data/lib/pg/text_encoder/numeric.rb +9 -0
  62. data/lib/pg/text_encoder/timestamp.rb +24 -0
  63. data/lib/pg/version.rb +4 -0
  64. data/lib/pg.rb +109 -39
  65. data/misc/openssl-pg-segfault.rb +31 -0
  66. data/misc/postgres/History.txt +9 -0
  67. data/misc/postgres/Manifest.txt +5 -0
  68. data/misc/postgres/README.txt +21 -0
  69. data/misc/postgres/Rakefile +21 -0
  70. data/misc/postgres/lib/postgres.rb +16 -0
  71. data/misc/ruby-pg/History.txt +9 -0
  72. data/misc/ruby-pg/Manifest.txt +5 -0
  73. data/misc/ruby-pg/README.txt +21 -0
  74. data/misc/ruby-pg/Rakefile +21 -0
  75. data/misc/ruby-pg/lib/ruby/pg.rb +16 -0
  76. data/misc/yugabyte/Dockerfile +9 -0
  77. data/misc/yugabyte/docker-compose.yml +28 -0
  78. data/misc/yugabyte/pg-test.rb +45 -0
  79. data/pg.gemspec +38 -0
  80. data/ports/patches/krb5/1.21.3/0001-Allow-static-linking-krb5-library.patch +30 -0
  81. data/ports/patches/openssl/3.5.1/0001-aarch64-mingw.patch +21 -0
  82. data/ports/patches/postgresql/17.5/0001-Use-workaround-of-__builtin_setjmp-only-on-MINGW-on-.patch +42 -0
  83. data/ports/patches/postgresql/17.5/0001-libpq-Process-buffered-SSL-read-bytes-to-support-rec.patch +52 -0
  84. data/rakelib/pg_gem_helper.rb +64 -0
  85. data/rakelib/task_extension.rb +46 -0
  86. data/sample/array_insert.rb +20 -0
  87. data/sample/async_api.rb +102 -0
  88. data/sample/async_copyto.rb +39 -0
  89. data/sample/async_mixed.rb +56 -0
  90. data/sample/check_conn.rb +21 -0
  91. data/sample/copydata.rb +71 -0
  92. data/sample/copyfrom.rb +81 -0
  93. data/sample/copyto.rb +19 -0
  94. data/sample/cursor.rb +21 -0
  95. data/sample/disk_usage_report.rb +177 -0
  96. data/sample/issue-119.rb +94 -0
  97. data/sample/losample.rb +69 -0
  98. data/sample/minimal-testcase.rb +17 -0
  99. data/sample/notify_wait.rb +72 -0
  100. data/sample/pg_statistics.rb +285 -0
  101. data/sample/replication_monitor.rb +222 -0
  102. data/sample/test_binary_values.rb +33 -0
  103. data/sample/wal_shipper.rb +434 -0
  104. data/sample/warehouse_partitions.rb +311 -0
  105. data.tar.gz.sig +0 -0
  106. metadata +139 -213
  107. metadata.gz.sig +0 -0
  108. data/.gemtest +0 -0
  109. data/ChangeLog +0 -0
  110. data/History.rdoc +0 -578
  111. data/Manifest.txt +0 -73
  112. data/README.ja.rdoc +0 -13
  113. data/README.rdoc +0 -213
  114. data/Rakefile.cross +0 -299
  115. data/lib/pg/basic_type_mapping.rb +0 -522
  116. data/lib/pg/binary_decoder.rb +0 -23
  117. data/lib/pg/constants.rb +0 -12
  118. data/lib/pg/text_decoder.rb +0 -46
  119. data/lib/pg/text_encoder.rb +0 -59
  120. data/spec/data/expected_trace.out +0 -26
  121. data/spec/data/random_binary_data +0 -0
  122. data/spec/helpers.rb +0 -380
  123. data/spec/pg/basic_type_mapping_spec.rb +0 -630
  124. data/spec/pg/connection_spec.rb +0 -1949
  125. data/spec/pg/connection_sync_spec.rb +0 -41
  126. data/spec/pg/result_spec.rb +0 -681
  127. data/spec/pg/tuple_spec.rb +0 -333
  128. data/spec/pg/type_map_by_class_spec.rb +0 -138
  129. data/spec/pg/type_map_by_column_spec.rb +0 -226
  130. data/spec/pg/type_map_by_mri_type_spec.rb +0 -136
  131. data/spec/pg/type_map_by_oid_spec.rb +0 -149
  132. data/spec/pg/type_map_in_ruby_spec.rb +0 -164
  133. data/spec/pg/type_map_spec.rb +0 -22
  134. data/spec/pg/type_spec.rb +0 -1123
  135. data/spec/pg_spec.rb +0 -50
data/lib/pg/connection.rb CHANGED
@@ -2,10 +2,11 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require 'pg' unless defined?( PG )
5
- require 'uri'
5
+ require 'io/wait' unless ::IO.public_instance_methods(false).include?(:wait_readable) # for ruby < 3.0
6
+ require 'socket'
6
7
 
7
8
  # The PostgreSQL connection class. The interface for this class is based on
8
- # {libpq}[http://www.postgresql.org/docs/9.2/interactive/libpq.html], the C
9
+ # {libpq}[http://www.postgresql.org/docs/current/libpq.html], the C
9
10
  # application programmer's interface to PostgreSQL. Some familiarity with libpq
10
11
  # is recommended, but not necessary.
11
12
  #
@@ -19,76 +20,110 @@ require 'uri'
19
20
  #
20
21
  # See the PG::Result class for information on working with the results of a query.
21
22
  #
23
+ # Many methods of this class have three variants kind of:
24
+ # 1. #exec - the base method which is an alias to #async_exec .
25
+ # This is the method that should be used in general.
26
+ # 2. #async_exec - the async aware version of the method, implemented by libpq's async API.
27
+ # 3. #sync_exec - the method version that is implemented by blocking function(s) of libpq.
28
+ #
29
+ # Sync and async version of the method can be switched by Connection.async_api= , however it is not recommended to change the default.
22
30
  class PG::Connection
23
31
 
24
32
  # The order the options are passed to the ::connect method.
25
- CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password]
26
-
33
+ CONNECT_ARGUMENT_ORDER = %w[host port options tty dbname user password].freeze
34
+ private_constant :CONNECT_ARGUMENT_ORDER
27
35
 
28
- ### Quote the given +value+ for use in a connection-parameter string.
29
- def self::quote_connstr( value )
36
+ ### Quote a single +value+ for use in a connection-parameter string.
37
+ def self.quote_connstr( value )
30
38
  return "'" + value.to_s.gsub( /[\\']/ ) {|m| '\\' + m } + "'"
31
39
  end
32
40
 
41
+ # Convert Hash options to connection String
42
+ #
43
+ # Values are properly quoted and escaped.
44
+ def self.connect_hash_to_string( hash )
45
+ hash.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
46
+ end
33
47
 
34
- ### Parse the connection +args+ into a connection-parameter string. See PG::Connection.new
35
- ### for valid arguments.
36
- def self::parse_connect_args( *args )
37
- return '' if args.empty?
38
-
39
- hash_arg = args.last.is_a?( Hash ) ? args.pop : {}
40
- option_string = ''
41
- options = {}
48
+ # Shareable program name for Ractor
49
+ PROGRAM_NAME = $PROGRAM_NAME.dup.freeze
50
+ private_constant :PROGRAM_NAME
42
51
 
43
- # Parameter 'fallback_application_name' was introduced in PostgreSQL 9.0
44
- # together with PQescapeLiteral().
45
- if PG::Connection.instance_methods.find {|m| m.to_sym == :escape_literal }
46
- options[:fallback_application_name] = $0.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
47
- end
52
+ # Parse the connection +args+ into a connection-parameter string.
53
+ # See PG::Connection.new for valid arguments.
54
+ #
55
+ # It accepts:
56
+ # * an option String kind of "host=name port=5432"
57
+ # * an option Hash kind of {host: "name", port: 5432}
58
+ # * URI string
59
+ # * URI object
60
+ # * positional arguments
61
+ #
62
+ # The method adds the option "fallback_application_name" if it isn't already set.
63
+ # It returns a connection string with "key=value" pairs.
64
+ def self.parse_connect_args( *args )
65
+ hash_arg = args.last.is_a?( Hash ) ? args.pop.transform_keys(&:to_sym) : {}
66
+ iopts = {}
48
67
 
49
68
  if args.length == 1
50
- case args.first
51
- when URI, /\A#{URI::ABS_URI_REF}\z/
52
- uri = URI(args.first)
53
- options.merge!( Hash[URI.decode_www_form( uri.query )] ) if uri.query
54
- when /=/
55
- # Option string style
56
- option_string = args.first.to_s
69
+ case args.first.to_s
70
+ when /=/, /:\/\//
71
+ # Option or URL string style
72
+ conn_string = args.first.to_s
73
+ iopts = PG::Connection.conninfo_parse(conn_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
57
74
  else
58
- # Positional parameters
59
- options[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
75
+ # Positional parameters (only host given)
76
+ iopts[CONNECT_ARGUMENT_ORDER.first.to_sym] = args.first
60
77
  end
61
78
  else
79
+ # Positional parameters with host and more
62
80
  max = CONNECT_ARGUMENT_ORDER.length
63
81
  raise ArgumentError,
64
- "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
82
+ "Extra positional parameter %d: %p" % [ max + 1, args[max] ] if args.length > max
65
83
 
66
84
  CONNECT_ARGUMENT_ORDER.zip( args ) do |(k,v)|
67
- options[ k.to_sym ] = v if v
85
+ iopts[ k.to_sym ] = v if v
68
86
  end
87
+ iopts.delete(:tty) # ignore obsolete tty parameter
69
88
  end
70
89
 
71
- options.merge!( hash_arg )
90
+ iopts.merge!( hash_arg )
72
91
 
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}://" )
92
+ if !iopts[:fallback_application_name]
93
+ iopts[:fallback_application_name] = PROGRAM_NAME.sub( /^(.{30}).{4,}(.{30})$/ ){ $1+"..."+$2 }
94
+ end
95
+
96
+ return connect_hash_to_string(iopts)
97
+ end
98
+
99
+ # Return a String representation of the object suitable for debugging.
100
+ def inspect
101
+ str = self.to_s
102
+ str[-1,0] = if finished?
103
+ " finished"
81
104
  else
82
- option_string += ' ' unless option_string.empty? && options.empty?
83
- return option_string + options.map { |k,v| "#{k}=#{quote_connstr(v)}" }.join( ' ' )
105
+ stats = []
106
+ stats << " status=#{ PG.constants.grep(/CONNECTION_/).find{|c| PG.const_get(c) == status} }" if status != CONNECTION_OK
107
+ stats << " transaction_status=#{ PG.constants.grep(/PQTRANS_/).find{|c| PG.const_get(c) == transaction_status} }" if transaction_status != PG::PQTRANS_IDLE
108
+ stats << " nonblocking=#{ isnonblocking }" if isnonblocking
109
+ stats << " pipeline_status=#{ PG.constants.grep(/PQ_PIPELINE_/).find{|c| PG.const_get(c) == pipeline_status} }" if respond_to?(:pipeline_status) && pipeline_status != PG::PQ_PIPELINE_OFF
110
+ stats << " client_encoding=#{ get_client_encoding }" if get_client_encoding != "UTF8"
111
+ stats << " type_map_for_results=#{ type_map_for_results.to_s }" unless type_map_for_results.is_a?(PG::TypeMapAllStrings)
112
+ stats << " type_map_for_queries=#{ type_map_for_queries.to_s }" unless type_map_for_queries.is_a?(PG::TypeMapAllStrings)
113
+ stats << " encoder_for_put_copy_data=#{ encoder_for_put_copy_data.to_s }" if encoder_for_put_copy_data
114
+ stats << " decoder_for_get_copy_data=#{ decoder_for_get_copy_data.to_s }" if decoder_for_get_copy_data
115
+ " host=#{host} port=#{port} user=#{user}#{stats.join}"
84
116
  end
117
+ return str
85
118
  end
86
119
 
120
+ BinarySignature = "PGCOPY\n\377\r\n\0"
121
+ private_constant :BinarySignature
87
122
 
88
123
  # call-seq:
89
124
  # conn.copy_data( sql [, coder] ) {|sql_result| ... } -> PG::Result
90
125
  #
91
- # Execute a copy process for transfering data to or from the server.
126
+ # Execute a copy process for transferring data to or from the server.
92
127
  #
93
128
  # This issues the SQL COPY command via #exec. The response to this
94
129
  # (if there is no error in the command) is a PG::Result object that
@@ -131,6 +166,17 @@ class PG::Connection
131
166
  # conn.put_copy_data ['more', 'data', 'to', 'copy']
132
167
  # end
133
168
  #
169
+ # All 4 CopyRow classes can take a type map to specify how the columns are mapped to and from the database format.
170
+ # For details see the particular CopyRow class description.
171
+ #
172
+ # PG::BinaryEncoder::CopyRow can be used to send data in binary format to the server.
173
+ # In this case copy_data generates the header and trailer data automatically:
174
+ # enco = PG::BinaryEncoder::CopyRow.new
175
+ # conn.copy_data "COPY my_table FROM STDIN (FORMAT binary)", enco do
176
+ # conn.put_copy_data ['some', 'data', 'to', 'copy']
177
+ # conn.put_copy_data ['more', 'data', 'to', 'copy']
178
+ # end
179
+ #
134
180
  # Example with CSV output format:
135
181
  # conn.copy_data "COPY my_table TO STDOUT CSV" do
136
182
  # while row=conn.get_copy_data
@@ -152,25 +198,58 @@ class PG::Connection
152
198
  # This receives all rows of +my_table+ as ruby array:
153
199
  # ["some", "data", "to", "copy"]
154
200
  # ["more", "data", "to", "copy"]
201
+ #
202
+ # Also PG::BinaryDecoder::CopyRow can be used to retrieve data in binary format from the server.
203
+ # In this case the header and trailer data is processed by the decoder and the remaining +nil+ from get_copy_data is processed by copy_data, so that binary data can be processed equally to text data:
204
+ # deco = PG::BinaryDecoder::CopyRow.new
205
+ # conn.copy_data "COPY my_table TO STDOUT (FORMAT binary)", deco do
206
+ # while row=conn.get_copy_data
207
+ # p row
208
+ # end
209
+ # end
210
+ # This receives all rows of +my_table+ as ruby array:
211
+ # ["some", "data", "to", "copy"]
212
+ # ["more", "data", "to", "copy"]
155
213
 
156
214
  def copy_data( sql, coder=nil )
215
+ raise PG::NotInBlockingMode.new("copy_data can not be used in nonblocking mode", connection: self) if nonblocking?
157
216
  res = exec( sql )
158
217
 
159
218
  case res.result_status
160
219
  when PGRES_COPY_IN
161
220
  begin
221
+ if coder && res.binary_tuples == 1
222
+ # Binary file header (11 byte signature, 32 bit flags and 32 bit extension length)
223
+ put_copy_data(BinarySignature + ("\x00" * 8))
224
+ end
225
+
162
226
  if coder
163
227
  old_coder = self.encoder_for_put_copy_data
164
228
  self.encoder_for_put_copy_data = coder
165
229
  end
230
+
166
231
  yield res
167
232
  rescue Exception => err
168
233
  errmsg = "%s while copy data: %s" % [ err.class.name, err.message ]
169
- put_copy_end( errmsg )
170
- get_result
171
- raise
234
+ begin
235
+ put_copy_end( errmsg )
236
+ rescue PG::Error
237
+ # Ignore error in cleanup to avoid losing original exception
238
+ end
239
+ discard_results
240
+ raise err
172
241
  else
173
- put_copy_end
242
+ begin
243
+ self.encoder_for_put_copy_data = old_coder if coder
244
+
245
+ if coder && res.binary_tuples == 1
246
+ put_copy_data("\xFF\xFF") # Binary file trailer 16 bit "-1"
247
+ end
248
+
249
+ put_copy_end
250
+ rescue PG::Error => err
251
+ raise PG::LostCopyState.new("#{err} (probably by executing another SQL query while running a COPY command)", connection: self)
252
+ end
174
253
  get_last_result
175
254
  ensure
176
255
  self.encoder_for_put_copy_data = old_coder if coder
@@ -183,21 +262,26 @@ class PG::Connection
183
262
  self.decoder_for_get_copy_data = coder
184
263
  end
185
264
  yield res
186
- rescue Exception => err
265
+ rescue Exception
187
266
  cancel
188
- while get_copy_data
189
- end
190
- while get_result
191
- end
267
+ discard_results
192
268
  raise
193
269
  else
194
- res = get_last_result
195
- if !res || res.result_status != PGRES_COMMAND_OK
196
- while get_copy_data
197
- end
198
- while get_result
270
+ if coder && res.binary_tuples == 1
271
+ # There are two end markers in binary mode: file trailer and the final nil.
272
+ # The file trailer is expected to be processed by BinaryDecoder::CopyRow and already returns nil, so that the remaining NULL from PQgetCopyData is retrieved here:
273
+ if get_copy_data
274
+ discard_results
275
+ raise PG::NotAllCopyDataRetrieved.new("Not all binary COPY data retrieved", connection: self)
199
276
  end
200
- raise PG::NotAllCopyDataRetrieved, "Not all COPY data retrieved"
277
+ end
278
+ res = get_last_result
279
+ if !res
280
+ discard_results
281
+ raise PG::LostCopyState.new("Lost COPY state (probably by executing another SQL query while running a COPY command)", connection: self)
282
+ elsif res.result_status != PGRES_COMMAND_OK
283
+ discard_results
284
+ raise PG::NotAllCopyDataRetrieved.new("Not all COPY data retrieved", connection: self)
201
285
  end
202
286
  res
203
287
  ensure
@@ -214,6 +298,31 @@ class PG::Connection
214
298
  define_method( :isthreadsafe, &PG.method(:isthreadsafe) )
215
299
  end
216
300
 
301
+ #
302
+ # call-seq:
303
+ # conn.transaction { |conn| ... } -> result of the block
304
+ #
305
+ # Executes a +BEGIN+ at the start of the block,
306
+ # and a +COMMIT+ at the end of the block, or
307
+ # +ROLLBACK+ if any exception occurs.
308
+ def transaction
309
+ rollback = false
310
+ exec "BEGIN"
311
+ yield(self)
312
+ rescue PG::RollbackTransaction
313
+ rollback = true
314
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
315
+ block
316
+ exec "ROLLBACK"
317
+ rescue Exception
318
+ rollback = true
319
+ cancel if transaction_status == PG::PQTRANS_ACTIVE
320
+ block
321
+ exec "ROLLBACK"
322
+ raise
323
+ ensure
324
+ exec "COMMIT" unless rollback
325
+ end
217
326
 
218
327
  ### Returns an array of Hashes with connection defaults. See ::conndefaults
219
328
  ### for details.
@@ -237,55 +346,749 @@ class PG::Connection
237
346
  return self.class.conndefaults_hash
238
347
  end
239
348
 
240
- # Method 'conninfo' was introduced in PostgreSQL 9.3.
241
- if self.instance_methods.find{|m| m.to_sym == :conninfo }
349
+ ### Return the Postgres connection info structure as a Hash keyed by option
350
+ ### keyword (as a Symbol).
351
+ ###
352
+ ### See also #conninfo
353
+ def conninfo_hash
354
+ return self.conninfo.each_with_object({}) do |info, hash|
355
+ hash[ info[:keyword].to_sym ] = info[:val]
356
+ end
357
+ end
358
+
359
+ # call-seq:
360
+ # conn.ssl_attributes -> Hash<String,String>
361
+ #
362
+ # Returns SSL-related information about the connection as key/value pairs
363
+ #
364
+ # The available attributes varies depending on the SSL library being used,
365
+ # and the type of connection.
366
+ #
367
+ # See also #ssl_attribute
368
+ def ssl_attributes
369
+ ssl_attribute_names.each.with_object({}) do |n,h|
370
+ h[n] = ssl_attribute(n)
371
+ end
372
+ end
373
+
374
+ # Read all pending socket input to internal memory and raise an exception in case of errors.
375
+ #
376
+ # This verifies that the connection socket is in a usable state and not aborted in any way.
377
+ # No communication is done with the server.
378
+ # Only pending data is read from the socket - the method doesn't wait for any outstanding server answers.
379
+ #
380
+ # Raises a kind of PG::Error if there was an error reading the data or if the socket is in a failure state.
381
+ #
382
+ # The method doesn't verify that the server is still responding.
383
+ # To verify that the communication to the server works, it is recommended to use something like <tt>conn.exec('')</tt> instead.
384
+ def check_socket
385
+ while socket_io.wait_readable(0)
386
+ consume_input
387
+ end
388
+ nil
389
+ end
390
+
391
+ # call-seq:
392
+ # conn.get_result() -> PG::Result
393
+ # conn.get_result() {|pg_result| block }
394
+ #
395
+ # Blocks waiting for the next result from a call to
396
+ # #send_query (or another asynchronous command), and returns
397
+ # it. Returns +nil+ if no more results are available.
398
+ #
399
+ # Note: call this function repeatedly until it returns +nil+, or else
400
+ # you will not be able to issue further commands.
401
+ #
402
+ # If the optional code block is given, it will be passed <i>result</i> as an argument,
403
+ # and the PG::Result object will automatically be cleared when the block terminates.
404
+ # In this instance, <code>conn.exec</code> returns the value of the block.
405
+ def get_result
406
+ block
407
+ sync_get_result
408
+ end
409
+ alias async_get_result get_result
242
410
 
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]
411
+ # call-seq:
412
+ # conn.get_copy_data( [ nonblock = false [, decoder = nil ]] ) -> Object
413
+ #
414
+ # Return one row of data, +nil+
415
+ # if the copy is done, or +false+ if the call would
416
+ # block (only possible if _nonblock_ is true).
417
+ #
418
+ # If _decoder_ is not set or +nil+, data is returned as binary string.
419
+ #
420
+ # If _decoder_ is set to a PG::Coder derivation, the return type depends on this decoder.
421
+ # PG::TextDecoder::CopyRow decodes the received data fields from one row of PostgreSQL's
422
+ # COPY text format to an Array of Strings.
423
+ # Optionally the decoder can type cast the single fields to various Ruby types in one step,
424
+ # if PG::TextDecoder::CopyRow#type_map is set accordingly.
425
+ #
426
+ # See also #copy_data.
427
+ #
428
+ def get_copy_data(async=false, decoder=nil)
429
+ if async
430
+ return sync_get_copy_data(async, decoder)
431
+ else
432
+ while (res=sync_get_copy_data(true, decoder)) == false
433
+ socket_io.wait_readable
434
+ consume_input
250
435
  end
436
+ return res
437
+ end
438
+ end
439
+ alias async_get_copy_data get_copy_data
440
+
441
+
442
+ # In async_api=true mode (default) all send calls run nonblocking.
443
+ # The difference is that setnonblocking(true) disables automatic handling of would-block cases.
444
+ # In async_api=false mode all send calls run directly on libpq.
445
+ # Blocking vs. nonblocking state can be changed in libpq.
446
+
447
+ # call-seq:
448
+ # conn.setnonblocking(Boolean) -> nil
449
+ #
450
+ # Sets the nonblocking status of the connection.
451
+ # In the blocking state, calls to #send_query
452
+ # will block until the message is sent to the server,
453
+ # but will not wait for the query results.
454
+ # In the nonblocking state, calls to #send_query
455
+ # will return an error if the socket is not ready for
456
+ # writing.
457
+ # Note: This function does not affect #exec, because
458
+ # that function doesn't return until the server has
459
+ # processed the query and returned the results.
460
+ #
461
+ # Returns +nil+.
462
+ def setnonblocking(enabled)
463
+ singleton_class.async_send_api = !enabled
464
+ self.flush_data = !enabled
465
+ sync_setnonblocking(true)
466
+ end
467
+ alias async_setnonblocking setnonblocking
468
+
469
+ # sync/async isnonblocking methods are switched by async_setnonblocking()
470
+
471
+ # call-seq:
472
+ # conn.isnonblocking() -> Boolean
473
+ #
474
+ # Returns the blocking status of the database connection.
475
+ # Returns +true+ if the connection is set to nonblocking mode and +false+ if blocking.
476
+ def isnonblocking
477
+ false
478
+ end
479
+ alias async_isnonblocking isnonblocking
480
+ alias nonblocking? isnonblocking
481
+
482
+ # call-seq:
483
+ # conn.put_copy_data( buffer [, encoder] ) -> Boolean
484
+ #
485
+ # Transmits _buffer_ as copy data to the server.
486
+ # Returns true if the data was sent, false if it was
487
+ # not sent (false is only possible if the connection
488
+ # is in nonblocking mode, and this command would block).
489
+ #
490
+ # _encoder_ can be a PG::Coder derivation (typically PG::TextEncoder::CopyRow).
491
+ # This encodes the data fields given as _buffer_ from an Array of Strings to
492
+ # PostgreSQL's COPY text format inclusive proper escaping. Optionally
493
+ # the encoder can type cast the fields from various Ruby types in one step,
494
+ # if PG::TextEncoder::CopyRow#type_map is set accordingly.
495
+ #
496
+ # Raises an exception if an error occurs.
497
+ #
498
+ # See also #copy_data.
499
+ #
500
+ def put_copy_data(buffer, encoder=nil)
501
+ # sync_put_copy_data does a non-blocking attempt to flush data.
502
+ until res=sync_put_copy_data(buffer, encoder)
503
+ # It didn't flush immediately and allocation of more buffering memory failed.
504
+ # Wait for all data sent by doing a blocking flush.
505
+ res = flush
506
+ end
507
+
508
+ # And do a blocking flush every 100 calls.
509
+ # This is to avoid memory bloat, when sending the data is slower than calls to put_copy_data happen.
510
+ if (@calls_to_put_copy_data += 1) > 100
511
+ @calls_to_put_copy_data = 0
512
+ res = flush
513
+ end
514
+ res
515
+ end
516
+ alias async_put_copy_data put_copy_data
517
+
518
+ # call-seq:
519
+ # conn.put_copy_end( [ error_message ] ) -> Boolean
520
+ #
521
+ # Sends end-of-data indication to the server.
522
+ #
523
+ # _error_message_ is an optional parameter, and if set,
524
+ # forces the COPY command to fail with the string
525
+ # _error_message_.
526
+ #
527
+ # Returns true if the end-of-data was sent, #false* if it was
528
+ # not sent (*false* is only possible if the connection
529
+ # is in nonblocking mode, and this command would block).
530
+ def put_copy_end(*args)
531
+ until sync_put_copy_end(*args)
532
+ flush
533
+ end
534
+ @calls_to_put_copy_data = 0
535
+ flush
536
+ end
537
+ alias async_put_copy_end put_copy_end
538
+
539
+ if method_defined? :send_pipeline_sync
540
+ # call-seq:
541
+ # conn.pipeline_sync
542
+ #
543
+ # Marks a synchronization point in a pipeline by sending a sync message and flushing the send buffer.
544
+ # This serves as the delimiter of an implicit transaction and an error recovery point.
545
+ #
546
+ # See enter_pipeline_mode
547
+ #
548
+ # Raises PG::Error if the connection is not in pipeline mode or sending a sync message failed.
549
+ #
550
+ # Available since PostgreSQL-14
551
+ def pipeline_sync(*args)
552
+ send_pipeline_sync(*args)
553
+ flush
554
+ end
555
+ alias async_pipeline_sync pipeline_sync
556
+ end
557
+
558
+ if method_defined? :sync_encrypt_password
559
+ # call-seq:
560
+ # conn.encrypt_password( password, username, algorithm=nil ) -> String
561
+ #
562
+ # This function is intended to be used by client applications that wish to send commands like <tt>ALTER USER joe PASSWORD 'pwd'</tt>.
563
+ # 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.
564
+ # Instead, use this function to convert the password to encrypted form before it is sent.
565
+ #
566
+ # The +password+ and +username+ arguments are the cleartext password, and the SQL name of the user it is for.
567
+ # +algorithm+ specifies the encryption algorithm to use to encrypt the password.
568
+ # 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).
569
+ # Note that support for +scram-sha-256+ was introduced in PostgreSQL version 10, and will not work correctly with older server versions.
570
+ # If algorithm is omitted or +nil+, this function will query the server for the current value of the +password_encryption+ setting.
571
+ # That can block, and will fail if the current transaction is aborted, or if the connection is busy executing another query.
572
+ # 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.
573
+ #
574
+ # Return value is the encrypted password.
575
+ # The caller can assume the string doesn't contain any special characters that would require escaping.
576
+ #
577
+ # Available since PostgreSQL-10.
578
+ # See also corresponding {libpq function}[https://www.postgresql.org/docs/current/libpq-misc.html#LIBPQ-PQENCRYPTPASSWORDCONN].
579
+ def encrypt_password( password, username, algorithm=nil )
580
+ algorithm ||= exec("SHOW password_encryption").getvalue(0,0)
581
+ sync_encrypt_password(password, username, algorithm)
582
+ end
583
+ alias async_encrypt_password encrypt_password
584
+ end
585
+
586
+ # call-seq:
587
+ # conn.reset()
588
+ #
589
+ # Resets the backend connection. This method closes the
590
+ # backend connection and tries to re-connect.
591
+ def reset
592
+ # Use connection options from PG::Connection.new to reconnect with the same options but with renewed DNS resolution.
593
+ # Use conninfo_hash as a fallback when connect_start was used to create the connection object.
594
+ iopts = @iopts_for_reset || conninfo_hash.compact
595
+ if iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
596
+ iopts = self.class.send(:resolve_hosts, iopts)
251
597
  end
598
+ conninfo = self.class.parse_connect_args( iopts );
599
+ reset_start2(conninfo)
600
+ async_connect_or_reset(:reset_poll)
601
+ self
252
602
  end
603
+ alias async_reset reset
604
+
605
+ if defined?(PG::CancelConnection)
606
+ # PostgreSQL-17+
607
+
608
+ def sync_cancel
609
+ cancon = PG::CancelConnection.new(self)
610
+ cancon.sync_cancel
611
+ rescue PG::Error => err
612
+ err.to_s
613
+ end
253
614
 
254
- # Method 'ssl_attribute' was introduced in PostgreSQL 9.5.
255
- if self.instance_methods.find{|m| m.to_sym == :ssl_attribute }
256
615
  # call-seq:
257
- # conn.ssl_attributes -> Hash<String,String>
616
+ # conn.cancel() -> String
617
+ #
618
+ # Requests cancellation of the command currently being
619
+ # processed.
258
620
  #
259
- # Returns SSL-related information about the connection as key/value pairs
621
+ # Returns +nil+ on success, or a string containing the
622
+ # error message if a failure occurs.
260
623
  #
261
- # The available attributes varies depending on the SSL library being used,
262
- # and the type of connection.
624
+ # On PostgreSQL-17+ client libaray the class PG::CancelConnection is used.
625
+ # On older client library a pure ruby implementation is used.
626
+ def cancel
627
+ cancon = PG::CancelConnection.new(self)
628
+ cancon.async_cancel
629
+ rescue PG::Error => err
630
+ err.to_s
631
+ end
632
+
633
+ else
634
+
635
+ # PostgreSQL < 17
636
+
637
+ def cancel
638
+ be_pid = backend_pid
639
+ be_key = backend_key
640
+ cancel_request = [0x10, 1234, 5678, be_pid, be_key].pack("NnnNN")
641
+
642
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler && RUBY_PLATFORM =~ /mingw|mswin/
643
+ # Ruby's nonblocking IO is not really supported on Windows.
644
+ # We work around by using threads and explicit calls to wait_readable/wait_writable.
645
+ cl = Thread.new(socket_io.remote_address) { |ra| ra.connect }.value
646
+ begin
647
+ cl.write_nonblock(cancel_request)
648
+ rescue IO::WaitReadable, Errno::EINTR
649
+ cl.wait_writable
650
+ retry
651
+ end
652
+ begin
653
+ cl.read_nonblock(1)
654
+ rescue IO::WaitReadable, Errno::EINTR
655
+ cl.wait_readable
656
+ retry
657
+ rescue EOFError
658
+ end
659
+ else
660
+ cl = socket_io.remote_address.connect
661
+ # Send CANCEL_REQUEST_CODE and parameters
662
+ cl.write(cancel_request)
663
+ # Wait for the postmaster to close the connection, which indicates that it's processed the request.
664
+ cl.read(1)
665
+ end
666
+
667
+ cl.close
668
+ nil
669
+ rescue SystemCallError => err
670
+ err.to_s
671
+ end
672
+ end
673
+ alias async_cancel cancel
674
+
675
+ module Pollable
676
+ # Track the progress of the connection, waiting for the socket to become readable/writable before polling it.
677
+ #
678
+ # Connecting to multiple hosts is done like so:
679
+ # - All hosts are passed to PG::Connection.connect_start
680
+ # - As soon as the host is tried to connect the related host is removed from the hosts list
681
+ # - When the polling status changes to `PG::PGRES_POLLING_OK` the connection is returned and ready to use.
682
+ # - When the polling status changes to `PG::PGRES_POLLING_FAILED` connecting is aborted and a PG::ConnectionBad is raised with details to all connection attepts.
683
+ # - When a timeout occurs, connecting is restarted with the remaining hosts.
684
+ #
685
+ # The downside is that this connects only once to hosts which are listed twice when they timeout.
686
+ private def polling_loop(poll_meth)
687
+ connect_timeout = conninfo_hash[:connect_timeout]
688
+ if (timeo = connect_timeout.to_i) && timeo > 0
689
+ host_count = (conninfo_hash[:hostaddr].to_s.empty? ? conninfo_hash[:host] : conninfo_hash[:hostaddr]).to_s.count(",") + 1
690
+ stop_time = timeo * host_count + Process.clock_gettime(Process::CLOCK_MONOTONIC)
691
+ end
692
+ iopts = conninfo_hash.compact
693
+ connection_errors = []
694
+ poll_status = PG::PGRES_POLLING_WRITING
695
+
696
+ until poll_status == PG::PGRES_POLLING_OK ||
697
+ poll_status == PG::PGRES_POLLING_FAILED
698
+
699
+ # Set single timeout to parameter "connect_timeout" but
700
+ # don't exceed total connection time of number-of-hosts * connect_timeout.
701
+ timeout = [timeo, stop_time - Process.clock_gettime(Process::CLOCK_MONOTONIC)].min if stop_time
702
+
703
+ hostcnt = remove_current_host(iopts)
704
+
705
+ event = if !timeout || timeout >= 0
706
+ # If the socket needs to read, wait 'til it becomes readable to poll again
707
+ case poll_status
708
+ when PG::PGRES_POLLING_READING
709
+ if defined?(IO::READABLE) # ruby-3.0+
710
+ socket_io.wait(IO::READABLE | IO::PRIORITY, timeout)
711
+ else
712
+ IO.select([socket_io], nil, [socket_io], timeout)
713
+ end
714
+
715
+ # ...and the same for when the socket needs to write
716
+ when PG::PGRES_POLLING_WRITING
717
+ if defined?(IO::WRITABLE) # ruby-3.0+
718
+ # Use wait instead of wait_readable, since connection errors are delivered as
719
+ # exceptional/priority events on Windows.
720
+ socket_io.wait(IO::WRITABLE | IO::PRIORITY, timeout)
721
+ else
722
+ # io#wait on ruby-2.x doesn't wait for priority, so fallback to IO.select
723
+ IO.select(nil, [socket_io], [socket_io], timeout)
724
+ end
725
+ end
726
+ end
727
+
728
+ # connection to server at "localhost" (127.0.0.1), port 5433 failed: timeout expired (PG::ConnectionBad)
729
+ # connection to server on socket "/var/run/postgresql/.s.PGSQL.5433" failed: No such file or directory
730
+ unless event
731
+ connection_errors << (error_message + "timeout expired")
732
+ if hostcnt > 0
733
+ reset_start2(self.class.parse_connect_args(iopts))
734
+ # Restart polling with waiting for writable.
735
+ # Otherwise "not connected" error is raised on Windows.
736
+ poll_status = PG::PGRES_POLLING_WRITING
737
+ next
738
+ else
739
+ finish
740
+ raise PG::ConnectionBad.new(connection_errors.join("\n").b, connection: self)
741
+ end
742
+ end
743
+
744
+ # Check to see if it's finished or failed yet
745
+ poll_status = send( poll_meth )
746
+ end
747
+
748
+ unless status == PG::CONNECTION_OK
749
+ msg = error_message
750
+ finish
751
+ raise PG::ConnectionBad.new(connection_errors.map{|e| e + "\n" }.join.b + msg, connection: self)
752
+ end
753
+ end
754
+
755
+ # Remove the host to which the connection is currently established from the option hash.
756
+ # Affected options are:
757
+ # - :host
758
+ # - :hostaddr
759
+ # - :port
263
760
  #
264
- # See also #ssl_attribute
265
- def ssl_attributes
266
- ssl_attribute_names.each.with_object({}) do |n,h|
267
- h[n] = ssl_attribute(n)
761
+ # Return the number of remaining hosts.
762
+ private def remove_current_host(iopts)
763
+ ihosts = iopts[:host]&.split(",", -1)
764
+ ihostaddrs = iopts[:hostaddr]&.split(",", -1)
765
+ iports = iopts[:port]&.split(",", -1)
766
+ iports = iports * (ihosts || ihostaddrs || [1]).size if iports&.size == 1
767
+
768
+ idx = (ihosts || ihostaddrs || iports).index.with_index do |_, i|
769
+ (ihosts ? ihosts[i] == host : true) &&
770
+ (ihostaddrs && respond_to?(:hostaddr, true) ? ihostaddrs[i] == hostaddr : true) &&
771
+ (iports ? iports[i].to_i == port : true)
268
772
  end
773
+
774
+ if idx
775
+ ihosts&.delete_at(idx)
776
+ ihostaddrs&.delete_at(idx)
777
+ iports&.delete_at(idx)
778
+
779
+ iopts.merge!(
780
+ host: ihosts.join(",")) if ihosts
781
+ iopts.merge!(
782
+ hostaddr: ihostaddrs.join(",")) if ihostaddrs
783
+ iopts.merge!(
784
+ port: iports.join(",")) if iports
785
+ end
786
+
787
+ (ihosts || ihostaddrs || iports).size
269
788
  end
270
789
  end
271
790
 
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 )
791
+ include Pollable
792
+
793
+ private def async_connect_or_reset(poll_meth)
794
+ # Track the progress of the connection, waiting for the socket to become readable/writable before polling it
795
+ polling_loop(poll_meth)
796
+
797
+ # Set connection to nonblocking to handle all blocking states in ruby.
798
+ # That way a fiber scheduler is able to handle IO requests.
799
+ sync_setnonblocking(true)
800
+ self.flush_data = true
801
+ set_default_encoding
802
+ end
803
+
804
+ class << self
805
+ # call-seq:
806
+ # PG::Connection.new -> conn
807
+ # PG::Connection.new(connection_hash) -> conn
808
+ # PG::Connection.new(connection_string) -> conn
809
+ # PG::Connection.new(host, port, options, tty, dbname, user, password) -> conn
810
+ #
811
+ # === Create a connection to the specified server.
812
+ #
813
+ # +connection_hash+ must be a ruby Hash with connection parameters.
814
+ # See the {list of valid parameters}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-PARAMKEYWORDS] in the PostgreSQL documentation.
815
+ #
816
+ # There are two accepted formats for +connection_string+: plain <code>keyword = value</code> strings and URIs.
817
+ # See the documentation of {connection strings}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING].
818
+ #
819
+ # The positional parameter form has the same functionality except that the missing parameters will always take on default values. The parameters are:
820
+ # [+host+]
821
+ # server hostname
822
+ # [+port+]
823
+ # server port number
824
+ # [+options+]
825
+ # backend options
826
+ # [+tty+]
827
+ # (ignored in all versions of PostgreSQL)
828
+ # [+dbname+]
829
+ # connecting database name
830
+ # [+user+]
831
+ # login user name
832
+ # [+password+]
833
+ # login password
834
+ #
835
+ #
836
+ # If the Ruby default internal encoding is set (i.e., <code>Encoding.default_internal != nil</code>), the
837
+ # connection will have its +client_encoding+ set accordingly.
838
+ #
839
+ # Raises a PG::Error if the connection fails.
840
+ #
841
+ # === Examples:
842
+ #
843
+ # # Connect using all defaults
844
+ # PG::Connection.new
845
+ #
846
+ # # As a Hash
847
+ # PG::Connection.new( dbname: 'test', port: 5432 )
848
+ #
849
+ # # As a String
850
+ # PG::Connection.new( "dbname=test port=5432" )
851
+ #
852
+ # # As an Array
853
+ # PG::Connection.new( nil, 5432, nil, nil, 'test', nil, nil )
854
+ #
855
+ # # As an URI
856
+ # PG::Connection.new( "postgresql://user:pass@pgsql.example.com:5432/testdb?sslmode=require" )
857
+ #
858
+ # === Specifying Multiple Hosts
859
+ #
860
+ # It is possible to specify multiple hosts to connect to, so that they are tried in the given order or optionally in random order.
861
+ # In the Keyword/Value format, the host, hostaddr, and port options accept comma-separated lists of values.
862
+ # The {details to libpq}[https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-MULTIPLE-HOSTS] describe how it works, but there are two small differences how ruby-pg handles multiple hosts:
863
+ # - All hosts are resolved before the first connection is tried.
864
+ # This means that when +load_balance_hosts+ is set to +random+, then all resolved addresses are tried randomly in one level.
865
+ # When a host resolves to more than one address, it is therefore tried more often than a host that has only one address.
866
+ # - When a timeout occurs due to the value of +connect_timeout+, then the given +host+, +hostaddr+ and +port+ combination is not tried a second time, even if it's specified several times.
867
+ # It's still possible to do load balancing with +load_balance_hosts+ set to +random+ and to increase the number of connections a node gets, when the hostname is provided multiple times in the host string.
868
+ # This is because in non-timeout cases the host is tried multiple times.
869
+ #
870
+ def new(*args)
871
+ conn = connect_to_hosts(*args)
872
+
873
+ if block_given?
874
+ begin
875
+ return yield conn
876
+ ensure
877
+ conn.finish
878
+ end
879
+ end
880
+ conn
881
+ end
882
+ alias async_connect new
883
+ alias connect new
884
+ alias open new
885
+ alias setdb new
886
+ alias setdblogin new
887
+
888
+ # Resolve DNS in Ruby to avoid blocking state while connecting.
889
+ # Multiple comma-separated values are generated, if the hostname resolves to both IPv4 and IPv6 addresses.
890
+ # This requires PostgreSQL-10+, so no DNS resolving is done on earlier versions.
891
+ private def resolve_hosts(iopts)
892
+ ihosts = iopts[:host].split(",", -1)
893
+ iports = iopts[:port].split(",", -1)
894
+ iports = [nil] if iports.size == 0
895
+ iports = iports * ihosts.size if iports.size == 1
896
+ raise PG::ConnectionBad, "could not match #{iports.size} port numbers to #{ihosts.size} hosts" if iports.size != ihosts.size
897
+
898
+ dests = ihosts.each_with_index.flat_map do |mhost, idx|
899
+ unless host_is_named_pipe?(mhost)
900
+ if Fiber.respond_to?(:scheduler) &&
901
+ Fiber.scheduler &&
902
+ RUBY_VERSION < '3.1.'
903
+
904
+ # Use a second thread to avoid blocking of the scheduler.
905
+ # `TCPSocket.gethostbyname` isn't fiber aware before ruby-3.1.
906
+ hostaddrs = Thread.new{ Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue [''] }.value
907
+ else
908
+ hostaddrs = Addrinfo.getaddrinfo(mhost, nil, nil, :STREAM).map(&:ip_address) rescue ['']
909
+ end
910
+ else
911
+ # No hostname to resolve (UnixSocket)
912
+ hostaddrs = [nil]
913
+ end
914
+ hostaddrs.map { |hostaddr| [hostaddr, mhost, iports[idx]] }
915
+ end
916
+ iopts.merge(
917
+ hostaddr: dests.map{|d| d[0] }.join(","),
918
+ host: dests.map{|d| d[1] }.join(","),
919
+ port: dests.map{|d| d[2] }.join(","))
920
+ end
921
+
922
+ private def connect_to_hosts(*args)
923
+ option_string = parse_connect_args(*args)
924
+ iopts = PG::Connection.conninfo_parse(option_string).each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }
925
+ iopts = PG::Connection.conndefaults.each_with_object({}){|h, o| o[h[:keyword].to_sym] = h[:val] if h[:val] }.merge(iopts)
926
+
927
+ if PG::BUNDLED_LIBPQ_WITH_UNIXSOCKET && iopts[:host].to_s.empty? && iopts[:hostaddr].to_s.empty?
928
+ # Many distors patch the hardcoded default UnixSocket path in libpq to /var/run/postgresql instead of /tmp .
929
+ # We simply try them all.
930
+ iopts[:host] = "/var/run/postgresql" + # Ubuntu, Debian, Fedora, Opensuse
931
+ ",/run/postgresql" + # Alpine, Archlinux, Gentoo
932
+ ",/tmp" # Stock PostgreSQL
933
+ end
934
+
935
+ iopts_for_reset = iopts
936
+ if iopts[:hostaddr]
937
+ # hostaddr is provided -> no need to resolve hostnames
938
+
939
+ elsif iopts[:host] && !iopts[:host].empty? && PG.library_version >= 100000
940
+ iopts = resolve_hosts(iopts)
941
+ else
942
+ # No host given
943
+ end
944
+ conn = self.connect_start(iopts) or
945
+ raise(PG::Error, "Unable to create a new connection")
946
+
947
+ raise PG::ConnectionBad, conn.error_message if conn.status == PG::CONNECTION_BAD
948
+
949
+ # save the connection options for conn.reset
950
+ conn.instance_variable_set(:@iopts_for_reset, iopts_for_reset)
951
+ conn.send(:async_connect_or_reset, :connect_poll)
952
+ conn
953
+ end
954
+
955
+ private def host_is_named_pipe?(host_string)
956
+ host_string.empty? || host_string.start_with?("/") || # it's UnixSocket?
957
+ host_string.start_with?("@") || # it's UnixSocket in the abstract namespace?
958
+ # it's a path on Windows?
959
+ (RUBY_PLATFORM =~ /mingw|mswin/ && host_string =~ /\A([\/\\]|\w:[\/\\])/)
960
+ end
961
+
962
+ # call-seq:
963
+ # PG::Connection.ping(connection_hash) -> Integer
964
+ # PG::Connection.ping(connection_string) -> Integer
965
+ # PG::Connection.ping(host, port, options, tty, dbname, login, password) -> Integer
966
+ #
967
+ # PQpingParams reports the status of the server.
968
+ #
969
+ # It accepts connection parameters identical to those of PQ::Connection.new .
970
+ # It is not necessary to supply correct user name, password, or database name values to obtain the server status; however, if incorrect values are provided, the server will log a failed connection attempt.
971
+ #
972
+ # See PG::Connection.new for a description of the parameters.
973
+ #
974
+ # Returns one of:
975
+ # [+PQPING_OK+]
976
+ # server is accepting connections
977
+ # [+PQPING_REJECT+]
978
+ # server is alive but rejecting connections
979
+ # [+PQPING_NO_RESPONSE+]
980
+ # could not establish connection
981
+ # [+PQPING_NO_ATTEMPT+]
982
+ # connection not attempted (bad params)
983
+ #
984
+ # See also check_socket for a way to check the connection without doing any server communication.
985
+ def ping(*args)
986
+ if Fiber.respond_to?(:scheduler) && Fiber.scheduler
987
+ # Run PQping in a second thread to avoid blocking of the scheduler.
988
+ # Unfortunately there's no nonblocking way to run ping.
989
+ Thread.new { sync_ping(*args) }.value
990
+ else
991
+ sync_ping(*args)
992
+ end
993
+ end
994
+ alias async_ping ping
995
+
996
+ REDIRECT_CLASS_METHODS = PG.make_shareable({
997
+ :new => [:async_connect, :sync_connect],
998
+ :connect => [:async_connect, :sync_connect],
999
+ :open => [:async_connect, :sync_connect],
1000
+ :setdb => [:async_connect, :sync_connect],
1001
+ :setdblogin => [:async_connect, :sync_connect],
1002
+ :ping => [:async_ping, :sync_ping],
1003
+ })
1004
+ private_constant :REDIRECT_CLASS_METHODS
1005
+
1006
+ # These methods are affected by PQsetnonblocking
1007
+ REDIRECT_SEND_METHODS = {
1008
+ :isnonblocking => [:async_isnonblocking, :sync_isnonblocking],
1009
+ :nonblocking? => [:async_isnonblocking, :sync_isnonblocking],
1010
+ :put_copy_data => [:async_put_copy_data, :sync_put_copy_data],
1011
+ :put_copy_end => [:async_put_copy_end, :sync_put_copy_end],
1012
+ :flush => [:async_flush, :sync_flush],
1013
+ }
1014
+ private_constant :REDIRECT_SEND_METHODS
1015
+ if PG::Connection.instance_methods.include? :sync_pipeline_sync
1016
+ if PG::Connection.instance_methods.include? :send_pipeline_sync
1017
+ # PostgreSQL-17+
1018
+ REDIRECT_SEND_METHODS.merge!({
1019
+ :pipeline_sync => [:async_pipeline_sync, :sync_pipeline_sync],
1020
+ })
1021
+ else
1022
+ # PostgreSQL-14+
1023
+ REDIRECT_SEND_METHODS.merge!({
1024
+ :pipeline_sync => [:sync_pipeline_sync, :sync_pipeline_sync],
1025
+ })
1026
+ end
1027
+ end
1028
+ PG.make_shareable(REDIRECT_SEND_METHODS)
1029
+
1030
+ REDIRECT_METHODS = {
1031
+ :exec => [:async_exec, :sync_exec],
1032
+ :query => [:async_exec, :sync_exec],
1033
+ :exec_params => [:async_exec_params, :sync_exec_params],
1034
+ :prepare => [:async_prepare, :sync_prepare],
1035
+ :exec_prepared => [:async_exec_prepared, :sync_exec_prepared],
1036
+ :describe_portal => [:async_describe_portal, :sync_describe_portal],
1037
+ :describe_prepared => [:async_describe_prepared, :sync_describe_prepared],
1038
+ :setnonblocking => [:async_setnonblocking, :sync_setnonblocking],
1039
+ :get_result => [:async_get_result, :sync_get_result],
1040
+ :get_last_result => [:async_get_last_result, :sync_get_last_result],
1041
+ :get_copy_data => [:async_get_copy_data, :sync_get_copy_data],
1042
+ :reset => [:async_reset, :sync_reset],
1043
+ :set_client_encoding => [:async_set_client_encoding, :sync_set_client_encoding],
1044
+ :client_encoding= => [:async_set_client_encoding, :sync_set_client_encoding],
1045
+ :cancel => [:async_cancel, :sync_cancel],
1046
+ :encrypt_password => [:async_encrypt_password, :sync_encrypt_password],
1047
+ }
1048
+ private_constant :REDIRECT_METHODS
1049
+ if PG::Connection.instance_methods.include? :async_close_prepared
1050
+ REDIRECT_METHODS.merge!({
1051
+ :close_prepared => [:async_close_prepared, :sync_close_prepared],
1052
+ :close_portal => [:async_close_portal, :sync_close_portal],
1053
+ })
1054
+ end
1055
+ PG.make_shareable(REDIRECT_METHODS)
1056
+
1057
+ def async_send_api=(enable)
1058
+ REDIRECT_SEND_METHODS.each do |ali, (async, sync)|
1059
+ undef_method(ali) if method_defined?(ali)
1060
+ alias_method( ali, enable ? async : sync )
1061
+ end
1062
+ end
1063
+
1064
+ # Switch between sync and async libpq API.
1065
+ #
1066
+ # PG::Connection.async_api = true
1067
+ # this is the default.
1068
+ # It sets an alias from #exec to #async_exec, #reset to #async_reset and so on.
1069
+ #
1070
+ # PG::Connection.async_api = false
1071
+ # sets an alias from #exec to #sync_exec, #reset to #sync_reset and so on.
1072
+ #
1073
+ # pg-1.1.0+ defaults to libpq's async API for query related blocking methods.
1074
+ # pg-1.3.0+ defaults to libpq's async API for all possibly blocking methods.
1075
+ #
1076
+ # _PLEASE_ _NOTE_: This method is not part of the public API and is for debug and development use only.
1077
+ # Do not use this method in production code.
1078
+ # Any issues with the default setting of <tt>async_api=true</tt> should be reported to the maintainers instead.
1079
+ #
1080
+ def async_api=(enable)
1081
+ self.async_send_api = enable
1082
+ REDIRECT_METHODS.each do |ali, (async, sync)|
1083
+ remove_method(ali) if method_defined?(ali)
1084
+ alias_method( ali, enable ? async : sync )
1085
+ end
1086
+ REDIRECT_CLASS_METHODS.each do |ali, (async, sync)|
1087
+ singleton_class.remove_method(ali) if method_defined?(ali)
1088
+ singleton_class.alias_method(ali, enable ? async : sync )
1089
+ end
286
1090
  end
287
1091
  end
288
1092
 
289
- # pg-1.1.0+ defaults to libpq's async API for query related blocking methods
290
1093
  self.async_api = true
291
1094
  end # class PG::Connection