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