cipherstash-pg 1.0.0.beta.1-x86_64-linux → 1.0.0.beta.4-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +4 -4
  2. data/.appveyor.yml +42 -0
  3. data/.gems +6 -0
  4. data/.gemtest +0 -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 +3 -3
  15. data/Gemfile.lock +45 -0
  16. data/{History.rdoc → History.md} +168 -153
  17. data/README.ja.md +266 -0
  18. data/README.md +272 -0
  19. data/Rakefile +65 -104
  20. data/Rakefile.cross +298 -0
  21. data/certs/larskanis-2023.pem +24 -0
  22. data/cipherstash-pg.gemspec +0 -0
  23. data/lib/2.7/pg_ext.so +0 -0
  24. data/lib/3.0/pg_ext.so +0 -0
  25. data/lib/3.1/pg_ext.so +0 -0
  26. data/lib/3.2/pg_ext.so +0 -0
  27. data/lib/cipherstash-pg/basic_type_map_based_on_result.rb +11 -0
  28. data/lib/cipherstash-pg/basic_type_map_for_queries.rb +113 -0
  29. data/lib/cipherstash-pg/basic_type_map_for_results.rb +30 -0
  30. data/lib/cipherstash-pg/basic_type_registry.rb +206 -0
  31. data/lib/cipherstash-pg/binary_decoder.rb +21 -0
  32. data/lib/cipherstash-pg/coder.rb +82 -0
  33. data/lib/cipherstash-pg/connection.rb +467 -0
  34. data/lib/cipherstash-pg/constants.rb +3 -0
  35. data/lib/cipherstash-pg/exceptions.rb +19 -0
  36. data/lib/cipherstash-pg/result.rb +22 -0
  37. data/lib/cipherstash-pg/text_decoder.rb +43 -0
  38. data/lib/cipherstash-pg/text_encoder.rb +67 -0
  39. data/lib/cipherstash-pg/tuple.rb +24 -0
  40. data/lib/cipherstash-pg/type_map_by_column.rb +11 -0
  41. data/lib/cipherstash-pg/version.rb +3 -0
  42. data/lib/cipherstash-pg.rb +56 -11
  43. data/lib/libpq.so.5 +0 -0
  44. data/misc/openssl-pg-segfault.rb +15 -25
  45. data/misc/postgres/Rakefile +13 -20
  46. data/misc/postgres/lib/postgres.rb +10 -14
  47. data/misc/ruby-pg/Rakefile +13 -20
  48. data/misc/ruby-pg/lib/ruby/pg.rb +10 -14
  49. data/rakelib/task_extension.rb +17 -31
  50. data/sample/array_insert.rb +7 -20
  51. data/sample/async_api.rb +54 -96
  52. data/sample/async_copyto.rb +20 -35
  53. data/sample/async_mixed.rb +22 -50
  54. data/sample/check_conn.rb +8 -20
  55. data/sample/copydata.rb +18 -68
  56. data/sample/copyfrom.rb +26 -78
  57. data/sample/copyto.rb +10 -16
  58. data/sample/cursor.rb +9 -19
  59. data/sample/disk_usage_report.rb +89 -174
  60. data/sample/issue-119.rb +45 -93
  61. data/sample/losample.rb +48 -66
  62. data/sample/minimal-testcase.rb +6 -17
  63. data/sample/notify_wait.rb +21 -67
  64. data/sample/pg_statistics.rb +100 -281
  65. data/sample/replication_monitor.rb +119 -218
  66. data/sample/test_binary_values.rb +14 -30
  67. data/sample/wal_shipper.rb +199 -431
  68. data/sample/warehouse_partitions.rb +157 -307
  69. data/translation/.po4a-version +7 -0
  70. data/translation/po/all.pot +875 -0
  71. data/translation/po/ja.po +868 -0
  72. data/translation/po4a.cfg +9 -0
  73. metadata +50 -28
  74. data/README.ja.rdoc +0 -13
  75. data/README.rdoc +0 -233
  76. data/lib/pg/basic_type_map_based_on_result.rb +0 -47
  77. data/lib/pg/basic_type_map_for_queries.rb +0 -193
  78. data/lib/pg/basic_type_map_for_results.rb +0 -81
  79. data/lib/pg/basic_type_registry.rb +0 -301
  80. data/lib/pg/binary_decoder.rb +0 -23
  81. data/lib/pg/coder.rb +0 -104
  82. data/lib/pg/connection.rb +0 -878
  83. data/lib/pg/constants.rb +0 -12
  84. data/lib/pg/exceptions.rb +0 -18
  85. data/lib/pg/result.rb +0 -43
  86. data/lib/pg/text_decoder.rb +0 -46
  87. data/lib/pg/text_encoder.rb +0 -59
  88. data/lib/pg/tuple.rb +0 -30
  89. data/lib/pg/type_map_by_column.rb +0 -16
  90. data/lib/pg/version.rb +0 -4
  91. data/lib/pg.rb +0 -55
data/lib/pg/connection.rb DELETED
@@ -1,878 +0,0 @@
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