cipherstash-pg 1.0.0.beta.1-arm64-darwin-21

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