pg 0.18.1 → 1.5.6

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