pg 0.18.3 → 1.4.3

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