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