em-pg-client-12 0.3.4

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.
@@ -0,0 +1,1129 @@
1
+ require 'fiber'
2
+ begin
3
+ require 'pg'
4
+ rescue LoadError => error
5
+ raise 'Missing pg driver: gem install pg'
6
+ end
7
+ unless defined? EventMachine
8
+ begin
9
+ require 'eventmachine'
10
+ rescue LoadError => error
11
+ raise 'Missing EventMachine: gem install eventmachine'
12
+ end
13
+ end
14
+ require 'pg/em-version'
15
+ require 'pg/em/featured_deferrable'
16
+ require 'pg/em/client/watcher'
17
+ require 'pg/em/client/connect_watcher'
18
+
19
+ module PG
20
+ module EM
21
+ ROOT_FIBER = Fiber.current
22
+
23
+ # == PostgreSQL EventMachine client
24
+ #
25
+ # Author:: Rafal Michalski
26
+ #
27
+ # {PG::EM::Client} is a PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
28
+ # wrapper designed for EventMachine[http://rubyeventmachine.com/].
29
+ #
30
+ # The following new methods:
31
+ #
32
+ # - {#exec_defer} (alias: +query_defer+)
33
+ # - {#exec_params_defer}
34
+ # - {#prepare_defer}
35
+ # - {#exec_prepared_defer}
36
+ # - {#describe_prepared_defer}
37
+ # - {#describe_portal_defer}
38
+ # - {#get_result_defer}
39
+ # - {#get_last_result_defer}
40
+ #
41
+ # are added to execute queries asynchronously,
42
+ # returning +Deferrable+ object.
43
+ #
44
+ # The following methods of PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
45
+ # are overloaded:
46
+ #
47
+ # - {#exec} (alias: +query+, +async_exec+, +async_query+)
48
+ # - {#exec_params}
49
+ # - {#prepare}
50
+ # - {#exec_prepared}
51
+ # - {#describe_prepared}
52
+ # - {#describe_portal}
53
+ # - {#get_result}
54
+ # - {#get_last_result}
55
+ #
56
+ # and are now auto-detecting if EventMachine is running and
57
+ # performing commands asynchronously (blocking only current fiber) or
58
+ # calling parent thread-blocking methods.
59
+ #
60
+ # If {#async_autoreconnect} option is set to +true+, all of the above
61
+ # methods (in asynchronous mode) try to re-connect after a connection
62
+ # error occurs. It's performed behind the scenes, so no error is raised,
63
+ # except if there was a transaction in progress. In such instance the error
64
+ # is raised after establishing connection to signal that
65
+ # the transaction was aborted.
66
+ #
67
+ # If you want to detect auto re-connect event use {#on_autoreconnect}
68
+ # property/option.
69
+ #
70
+ # To enable auto-reconnecting set:
71
+ # client.async_autoreconnect = true
72
+ #
73
+ # or pass as {new} hash argument:
74
+ # PG::EM::Client.new dbname: 'bar', async_autoreconnect: true
75
+ #
76
+ # There are also new methods:
77
+ #
78
+ # - {Client.connect_defer}
79
+ # - {#reset_defer}
80
+ #
81
+ # which are asynchronous versions of PG::Connection.new and
82
+ # PG:Connection#reset.
83
+ #
84
+ # Additionally the following methods are overloaded:
85
+ #
86
+ # - {new} (alias: +connect+, +open+, +setdb+, +setdblogin+ )
87
+ # - {#reset}
88
+ #
89
+ # providing auto-detecting asynchronous (fiber-synchronized) or
90
+ # thread-blocking methods for (re)connecting.
91
+ #
92
+ # Otherwise nothing changes in PG::Connection API.
93
+ # See PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] docs
94
+ # for explanation of arguments to the above methods.
95
+ #
96
+ # *Warning:*
97
+ #
98
+ # {#describe_prepared} and {#exec_prepared} after
99
+ # {#prepare} should only be invoked on the *same* connection.
100
+ # If you are using a {ConnectionPool}, make sure to acquire a single
101
+ # connection first or execute +prepare+ command on every connection
102
+ # using +#on_connect+ hook.
103
+ #
104
+ class Client < PG::Connection
105
+
106
+ # @!attribute connect_timeout
107
+ # @return [Float] connection timeout in seconds
108
+ # Connection timeout. Affects {#reset} and {#reset_defer}.
109
+ #
110
+ # Changing this property does not affect thread-blocking {#reset} unless
111
+ # passed as a +connection_hash+.
112
+ #
113
+ # To enable it set to some positive value. To disable it: set to 0.
114
+ #
115
+ # You may set +:connect_timeout+ in a +connection_hash+ argument
116
+ # passed to {new} or {connect_defer}.
117
+ attr_accessor :connect_timeout
118
+
119
+ # @!attribute query_timeout
120
+ # @return [Float] query timeout in seconds
121
+ # Aborts async command processing if server response time
122
+ # exceedes +query_timeout+ seconds. This does not apply to
123
+ # {#reset} and {#reset_defer}.
124
+ #
125
+ # To enable it set to some positive value. To disable it: set to 0.
126
+ #
127
+ # You may set +:query_timeout+ in a +connection_hash+ argument
128
+ # passed to {new} or {connect_defer}.
129
+ attr_accessor :query_timeout
130
+
131
+ # @!attribute async_autoreconnect
132
+ # @return [Boolean] asynchronous auto re-connect status
133
+ # Enable/disable auto re-connect feature (+true+/+false+).
134
+ # Defaults to +false+ unless {#on_autoreconnect} is specified
135
+ # in a +connection_hash+.
136
+ #
137
+ # Changing {#on_autoreconnect} with accessor method doesn't change
138
+ # the state of {#async_autoreconnect}.
139
+ #
140
+ # You may set +:async_autoreconnect+ in a +connection_hash+ argument
141
+ # passed to {new} or {connect_defer}.
142
+ attr_accessor :async_autoreconnect
143
+
144
+ # @!attribute on_autoreconnect
145
+ # @return [Proc<Client, Error>] auto re-connect hook
146
+ # A proc like object that is being called after a connection with the
147
+ # server has been automatically re-established. It's being invoked
148
+ # just before the pending command is sent to the server.
149
+ #
150
+ # @yieldparam pg [Client] re-connected client instance
151
+ # @yieldparam error [Exception] an error after which the auto re-connect
152
+ # process began.
153
+ # @yieldreturn [false|true|Exception|EM::Deferrable|*]
154
+ #
155
+ # The first argument it receives is the connected {Client} instance.
156
+ # The second is the original +error+ that caused the reconnecting
157
+ # process.
158
+ #
159
+ # It's possible to execute queries from the +on_autoreconnect+ hook.
160
+ # Code is being executed in a fiber context, so both deferrable and
161
+ # fiber-synchronized query commands may be used.
162
+ #
163
+ # If exception is raised during execution of the +on_autoreconnect+
164
+ # hook the reset operation will fail with that exception.
165
+ #
166
+ # The hook can control later actions with its return value:
167
+ #
168
+ # - +false+ (explicitly, +nil+ is ignored) - the original +exception+
169
+ # is raised/passed back and the pending query command is not sent
170
+ # again to the server.
171
+ # - +true+ (explicitly, truish values are ignored), the pending command
172
+ # is called regardless of the connection's last transaction status.
173
+ # - +Exception+ object - is raised/passed back and the pending command
174
+ # is not sent.
175
+ # - +Deferrable+ object - the chosen action will depend on the returned
176
+ # deferrable status.
177
+ # - Other values are ignored and the pending query command is
178
+ # immediately sent to the server unless there was a transaction in
179
+ # progress before the connection was reset.
180
+ #
181
+ # If both +on_connect+ and +on_autoreconnect+ hooks are set,
182
+ # the +on_connect+ is being called first and +on_autoreconnect+ is
183
+ # called only when +on_connect+ succeeds.
184
+ #
185
+ # You may set +:on_autoreconnect+ hook in a +connection_hash+ argument
186
+ # passed to {new} or {connect_defer}.
187
+ #
188
+ # @example How to use deferrable in on_autoreconnect hook
189
+ # pg.on_autoreconnect do |pg, e|
190
+ # logger.warn "PG connection was reset: #{e.inspect}, delaying 1 sec."
191
+ # EM::DefaultDeferrable.new.tap do |df|
192
+ # EM.add_timer(1) { df.succeed }
193
+ # end
194
+ # end
195
+ #
196
+ attr_writer :on_autoreconnect
197
+
198
+ def on_autoreconnect(&hook)
199
+ if block_given?
200
+ @on_autoreconnect = hook
201
+ else
202
+ @on_autoreconnect
203
+ end
204
+ end
205
+
206
+ # @!attribute on_connect
207
+ # @return [Proc<Client,is_async,is_reset>] connect hook
208
+ # A proc like object that is being called after a connection with
209
+ # the server has been established.
210
+ #
211
+ # @yieldparam pg [Client] connected client instance
212
+ # @yieldparam is_async [Boolean] flag indicating if the connection
213
+ # was established asynchronously
214
+ # @yieldparam is_reset [Boolean] flag indicating if the connection
215
+ # client was reset
216
+ # @yieldreturn [EM::Deferrable|*]
217
+ #
218
+ # The first argument it receives is the connected {Client} instance.
219
+ # The second argument is +true+ if the connection was established in
220
+ # asynchronous manner, +false+ otherwise.
221
+ # The third argument is +true+ when the connection has been reset or
222
+ # +false+ on new connection.
223
+ #
224
+ # It's possible to execute queries from the +on_connect+ hook.
225
+ # Code is being executed in a fiber context, so both deferrable and
226
+ # fiber-synchronized query commands may be used.
227
+ # However deferrable commands will work only if eventmachine reactor
228
+ # is running, so check if +is_async+ is +true+.
229
+ #
230
+ # If exception is raised during execution of the +on_connect+ hook
231
+ # the connecting/reset operation will fail with that exception.
232
+ #
233
+ # The hook can control later actions with its return value:
234
+ #
235
+ # - +Deferrable+ object - the connection establishing status will depend
236
+ # on the returned deferrable status (only in asynchronous mode).
237
+ # - Other values are ignored.
238
+ #
239
+ # If both +on_connect+ and +on_autoreconnect+ hooks are set,
240
+ # the +on_connect+ is being called first and +on_autoreconnect+ is
241
+ # called only when +on_connect+ succeeds.
242
+ #
243
+ # You may set +:on_connect+ hook in a +connection_hash+ argument
244
+ # passed to {new} or {connect_defer}.
245
+ #
246
+ # @example How to use prepare in on_connect hook
247
+ # PG::EM::Client.new(on_connect: proc {|pg|
248
+ # pg.prepare("species_by_name",
249
+ # "select id, name from animals where species=$1 order by name")
250
+ # })
251
+ #
252
+ attr_writer :on_connect
253
+
254
+ def on_connect(&hook)
255
+ if block_given?
256
+ @on_connect = hook
257
+ else
258
+ @on_connect
259
+ end
260
+ end
261
+
262
+ # @!visibility private
263
+ # Used internally for marking connection as aborted on query timeout.
264
+ attr_accessor :async_command_aborted
265
+
266
+ # Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
267
+ # Single row mode is available since +libpq+ 9.2.
268
+ # @return [Boolean]
269
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-set_single_row_mode PG::Connection#set_single_row_mode
270
+ def self.single_row_mode?
271
+ method_defined? :set_single_row_mode
272
+ end
273
+
274
+ # Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
275
+ # @return [Boolean]
276
+ # @see single_row_mode?
277
+ def single_row_mode?
278
+ self.class.single_row_mode?
279
+ end
280
+
281
+ # environment variable name for connect_timeout fallback value
282
+ @@connect_timeout_envvar = conndefaults.find{|d| d[:keyword] == "connect_timeout" }[:envvar]
283
+
284
+ DEFAULT_ASYNC_VARS = {
285
+ :@async_autoreconnect => nil,
286
+ :@connect_timeout => nil,
287
+ :@query_timeout => 0,
288
+ :@on_connect => nil,
289
+ :@on_autoreconnect => nil,
290
+ :@async_command_aborted => false,
291
+ }.freeze
292
+
293
+ # @!visibility private
294
+ def self.parse_async_options(args)
295
+ options = DEFAULT_ASYNC_VARS.dup
296
+ if args.last.is_a? Hash
297
+ args[-1] = args.last.reject do |key, value|
298
+ case key.to_sym
299
+ when :async_autoreconnect
300
+ options[:@async_autoreconnect] = value
301
+ true
302
+ when :on_connect
303
+ if value.respond_to? :call
304
+ options[:@on_connect] = value
305
+ else
306
+ raise ArgumentError, "on_connect must respond to `call'"
307
+ end
308
+ true
309
+ when :on_reconnect
310
+ raise ArgumentError, "on_reconnect is no longer supported, use on_autoreconnect"
311
+ when :on_autoreconnect
312
+ if value.respond_to? :call
313
+ options[:@on_autoreconnect] = value
314
+ options[:@async_autoreconnect] = true if options[:@async_autoreconnect].nil?
315
+ else
316
+ raise ArgumentError, "on_autoreconnect must respond to `call'"
317
+ end
318
+ true
319
+ when :connect_timeout
320
+ options[:@connect_timeout] = value.to_f
321
+ false
322
+ when :query_timeout
323
+ options[:@query_timeout] = value.to_f
324
+ true
325
+ end
326
+ end
327
+ end
328
+ options[:@async_autoreconnect] = !!options[:@async_autoreconnect]
329
+ options[:@connect_timeout] ||= ENV[@@connect_timeout_envvar].to_f
330
+ options
331
+ end
332
+
333
+ # @!group Deferrable connection methods
334
+
335
+ # Attempts to establish the connection asynchronously.
336
+ #
337
+ # @return [FeaturedDeferrable]
338
+ # @yieldparam pg [Client|PG::Error] new and connected client instance on
339
+ # success or an instance of raised PG::Error
340
+ #
341
+ # Pass the block to the returned deferrable's +callback+ to obtain newly
342
+ # created and already connected {Client} object. In case of connection
343
+ # error +errback+ hook receives an error object as an argument.
344
+ # If the block is provided it's bound to both +callback+ and +errback+
345
+ # hooks of the returned deferrable.
346
+ #
347
+ # Special {Client} options (e.g.: {#async_autoreconnect}) must be
348
+ # provided in a +connection_hash+ argument variant. They will be ignored
349
+ # if passed in a +connection_string+.
350
+ #
351
+ # +client_encoding+ *will* be set according to +Encoding.default_internal+.
352
+ #
353
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
354
+ def self.connect_defer(*args, &blk)
355
+ df = FeaturedDeferrable.new(&blk)
356
+ async_args = parse_async_options(args)
357
+ conn = df.protect { connect_start(*args) }
358
+ if conn
359
+ async_args.each {|k, v| conn.instance_variable_set(k, v) }
360
+ ::EM.watch(conn.socket_io, ConnectWatcher, conn, df, false).
361
+ poll_connection_and_check
362
+ end
363
+ df
364
+ end
365
+
366
+ class << self
367
+ # @deprecated Use {connect_defer} instead.
368
+ alias_method :async_connect, :connect_defer
369
+ end
370
+
371
+ # Attempts to reset the connection asynchronously.
372
+ #
373
+ # @return [FeaturedDeferrable]
374
+ # @yieldparam pg [Client|PG::Error] reconnected client instance on
375
+ # success or an instance of raised PG::Error
376
+ #
377
+ # Pass the block to the returned deferrable's +callback+ to execute
378
+ # after successfull reset.
379
+ # If the block is provided it's bound to +callback+ and +errback+ hooks
380
+ # of the returned deferrable.
381
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
382
+ def reset_defer(&blk)
383
+ @async_command_aborted = false
384
+ df = FeaturedDeferrable.new(&blk)
385
+ # there can be only one watch handler over the socket
386
+ # apparently eventmachine has hard time dealing with more than one
387
+ if @watcher
388
+ @watcher.detach if @watcher.watching?
389
+ @watcher = nil
390
+ end
391
+ ret = df.protect(:fail) { reset_start }
392
+ unless ret == :fail
393
+ ::EM.watch(self.socket_io, ConnectWatcher, self, df, true).
394
+ poll_connection_and_check
395
+ end
396
+ df
397
+ end
398
+
399
+ # @deprecated Use {reset_defer} instead.
400
+ alias_method :async_reset, :reset_defer
401
+
402
+ # @!endgroup
403
+
404
+ # @!group Auto-sensing fiber-synchronized connection methods
405
+
406
+ # Attempts to reset the connection.
407
+ #
408
+ # Performs command asynchronously yielding from current fiber
409
+ # if EventMachine reactor is running and current fiber isn't the root
410
+ # fiber. Other fibers can process while waiting for the server to
411
+ # complete the request.
412
+ #
413
+ # Otherwise performs a thread-blocking call to the parent method.
414
+ #
415
+ # @raise [PG::Error]
416
+ # @see #reset_defer
417
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
418
+ def reset
419
+ if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
420
+ reset_defer {|r| f.resume(r) }
421
+
422
+ conn = Fiber.yield
423
+ raise conn if conn.is_a?(::Exception)
424
+ conn
425
+ else
426
+ @async_command_aborted = false
427
+ if @watcher
428
+ @watcher.detach if @watcher.watching?
429
+ @watcher = nil
430
+ end
431
+ super
432
+ @on_connect.call(self, false, true) if @on_connect
433
+ self
434
+ end
435
+ end
436
+
437
+ # Creates new instance of PG::EM::Client and attempts to establish
438
+ # connection.
439
+ #
440
+ # Performs command asynchronously yielding from current fiber
441
+ # if EventMachine reactor is running and current fiber isn't the root
442
+ # fiber. Other fibers can process while waiting for the server to
443
+ # complete the request.
444
+ #
445
+ # Otherwise performs a thread-blocking call to the parent method.
446
+ #
447
+ # @raise [PG::Error]
448
+ #
449
+ # Special {Client} options (e.g.: {#async_autoreconnect}) must be
450
+ # provided in a +connection_hash+ argument variant. They will be ignored
451
+ # if passed in a +connection_string+.
452
+ #
453
+ # +client_encoding+ *will* be set according to +Encoding.default_internal+.
454
+ #
455
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-c-new PG::Connection.new
456
+ def self.new(*args, &blk)
457
+ if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
458
+ connect_defer(*args) {|r| f.resume(r) }
459
+
460
+ conn = Fiber.yield
461
+ raise conn if conn.is_a?(::Exception)
462
+ if block_given?
463
+ begin
464
+ yield conn
465
+ ensure
466
+ conn.finish
467
+ end
468
+ else
469
+ conn
470
+ end
471
+ else
472
+ conn = super(*args)
473
+ if on_connect = conn.on_connect
474
+ on_connect.call(conn, false, false)
475
+ end
476
+ conn
477
+ end
478
+ end
479
+
480
+ # @!visibility private
481
+ def initialize(*args)
482
+ Client.parse_async_options(args).each {|k, v| instance_variable_set(k, v) }
483
+ super(*args)
484
+ end
485
+
486
+ class << self
487
+ alias_method :connect, :new
488
+ alias_method :open, :new
489
+ alias_method :setdb, :new
490
+ alias_method :setdblogin, :new
491
+ end
492
+
493
+ # @!endgroup
494
+
495
+ # Closes the backend connection.
496
+ #
497
+ # Detaches watch handler to prevent memory leak after
498
+ # calling parent PG::Connection#finish[http://deveiate.org/code/pg/PG/Connection.html#method-i-finish].
499
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-finish PG::Connection#finish
500
+ def finish
501
+ super
502
+ if @watcher
503
+ @watcher.detach if @watcher.watching?
504
+ @watcher = nil
505
+ end
506
+ end
507
+
508
+ alias_method :close, :finish
509
+
510
+ # Returns status of connection: PG::CONNECTION_OK or PG::CONNECTION_BAD.
511
+ #
512
+ # @return [Number]
513
+ # Returns +PG::CONNECTION_BAD+ for connections with +async_command_aborted+
514
+ # flag set by expired query timeout. Otherwise return whatever PG::Connection#status returns.
515
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-status PG::Connection#status
516
+ def status
517
+ if @async_command_aborted
518
+ CONNECTION_BAD
519
+ else
520
+ super
521
+ end
522
+ end
523
+
524
+ # @!visibility private
525
+ # Perform auto re-connect. Used internally.
526
+ def async_autoreconnect!(deferrable, error, send_proc = nil, &on_connection_bad)
527
+ # reconnect only if connection is bad and flag is set
528
+ if self.status == CONNECTION_BAD
529
+
530
+ yield if block_given?
531
+
532
+ if async_autoreconnect
533
+ # check if transaction was active
534
+ was_in_transaction = case @last_transaction_status
535
+ when PQTRANS_IDLE, PQTRANS_UNKNOWN
536
+ false
537
+ else
538
+ true
539
+ end
540
+ # reset asynchronously
541
+ reset_df = reset_defer
542
+ # just fail on reset failure
543
+ reset_df.errback { |ex| deferrable.fail ex }
544
+ # reset succeeds
545
+ reset_df.callback do
546
+ # handle on_autoreconnect
547
+ if on_autoreconnect
548
+ # wrap in a fiber, so on_autoreconnect code may yield from it
549
+ Fiber.new do
550
+ # call on_autoreconnect handler and fail if it raises an error
551
+ returned_df = begin
552
+ on_autoreconnect.call(self, error)
553
+ rescue => ex
554
+ ex
555
+ end
556
+ if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
557
+ # the handler returned a deferrable
558
+ returned_df.callback do
559
+ if was_in_transaction || !send_proc
560
+ # fail anyway, there was a transaction in progress or in single result mode
561
+ deferrable.fail error
562
+ else
563
+ # try to call failed query command again
564
+ deferrable.protect(&send_proc)
565
+ end
566
+ end
567
+ # fail when handler's deferrable fails
568
+ returned_df.errback { |ex| deferrable.fail ex }
569
+ elsif returned_df.is_a?(Exception)
570
+ # tha handler returned an exception object, so fail with it
571
+ deferrable.fail returned_df
572
+ elsif returned_df == false || !send_proc || (was_in_transaction && returned_df != true)
573
+ # tha handler returned false or in single result mode
574
+ # or there was an active transaction and handler didn't return true
575
+ deferrable.fail error
576
+ else
577
+ # try to call failed query command again
578
+ deferrable.protect(&send_proc)
579
+ end
580
+ end.resume
581
+ elsif was_in_transaction || !send_proc
582
+ # there was a transaction in progress or in single result mode;
583
+ # fail anyway
584
+ deferrable.fail error
585
+ else
586
+ # no on_autoreconnect handler, no transaction
587
+ # try to call failed query command again
588
+ deferrable.protect(&send_proc)
589
+ end
590
+ end
591
+ # connection is bad, reset in progress, all done
592
+ return
593
+ end
594
+ end
595
+ # connection is either good or bad, the async_autoreconnect is not set
596
+ deferrable.fail error
597
+ end
598
+
599
+ # @!macro deferrable_api
600
+ # @return [FeaturedDeferrable]
601
+ # Use the returned Deferrable's +callback+ and +errback+ methods to
602
+ # get the result. If the block is provided it's bound to both the
603
+ # +callback+ and +errback+ hooks of the returned deferrable.
604
+
605
+ # @!macro deferrable_query_api
606
+ # @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
607
+ # @macro deferrable_api
608
+
609
+ # @!group Deferrable command methods
610
+
611
+ # @!method exec_defer(sql, params=nil, result_format=nil, &blk)
612
+ # Sends SQL query request specified by +sql+ to PostgreSQL for asynchronous processing,
613
+ # and immediately returns with +deferrable+.
614
+ #
615
+ # @macro deferrable_query_api
616
+ #
617
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
618
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
619
+ #
620
+ # @!method prepare_defer(stmt_name, sql, param_types=nil, &blk)
621
+ # Prepares statement +sql+ with name +stmt_name+ to be executed later asynchronously,
622
+ # and immediately returns with a Deferrable.
623
+ #
624
+ # @macro deferrable_query_api
625
+ #
626
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
627
+ #
628
+ # @!method exec_prepared_defer(statement_name, params=nil, result_format=nil, &blk)
629
+ # Execute prepared named statement specified by +statement_name+ asynchronously,
630
+ # and immediately returns with a Deferrable.
631
+ #
632
+ # @macro deferrable_query_api
633
+ #
634
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query_prepared PG::Connection#send_query_prepared
635
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#send_exec_prepared
636
+ #
637
+ # @!method describe_prepared_defer(statement_name, &blk)
638
+ # Asynchronously sends command to retrieve information about the prepared statement +statement_name+,
639
+ # and immediately returns with a Deferrable.
640
+ #
641
+ # @macro deferrable_query_api
642
+ #
643
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
644
+ #
645
+ # @!method describe_portal_defer(portal_name, &blk)
646
+ # Asynchronously sends command to retrieve information about the portal +portal_name+,
647
+ # and immediately returns with a Deferrable.
648
+ #
649
+ # @macro deferrable_query_api
650
+ #
651
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
652
+ #
653
+ %w(
654
+ exec_defer send_query
655
+ prepare_defer send_prepare
656
+ exec_prepared_defer send_query_prepared
657
+ describe_prepared_defer send_describe_prepared
658
+ describe_portal_defer send_describe_portal
659
+ ).each_slice(2) do |defer_name, send_name|
660
+
661
+ class_eval <<-EOD, __FILE__, __LINE__
662
+ def #{defer_name}(*args, &blk)
663
+ df = FeaturedDeferrable.new
664
+ send_proc = proc do
665
+ #{send_name}(*args)
666
+ setup_emio_watcher.watch_results(df, send_proc)
667
+ end
668
+ begin
669
+ check_async_command_aborted!
670
+ @last_transaction_status = transaction_status
671
+ send_proc.call
672
+ rescue Error => e
673
+ ::EM.next_tick { async_autoreconnect!(df, e, send_proc) }
674
+ rescue Exception => e
675
+ ::EM.next_tick { df.fail(e) }
676
+ end
677
+ df.completion(&blk) if block_given?
678
+ df
679
+ end
680
+ EOD
681
+
682
+ end
683
+
684
+ alias_method :query_defer, :exec_defer
685
+ alias_method :async_query_defer, :exec_defer
686
+ alias_method :async_exec_defer, :exec_defer
687
+ alias_method :exec_params_defer, :exec_defer
688
+
689
+ # Asynchronously waits for notification or until the optional
690
+ # +timeout+ is reached, whichever comes first. +timeout+ is
691
+ # measured in seconds and can be fractional.
692
+ # Returns immediately with a Deferrable.
693
+ #
694
+ # Pass the block to the returned deferrable's +callback+ to obtain notification
695
+ # hash. In case of connection error +errback+ hook is called with an error object.
696
+ # If the +timeout+ is reached +nil+ is passed to deferrable's +callback+.
697
+ # If the block is provided it's bound to both the +callback+ and +errback+ hooks
698
+ # of the returned deferrable.
699
+ # If another call is made to this method before the notification is received
700
+ # (or before reaching timeout) the previous deferrable's +errback+ will be called
701
+ # with +nil+ argument.
702
+ #
703
+ # @return [FeaturedDeferrable]
704
+ # @yieldparam notification [Hash|nil|Error] notification hash or a PG::Error instance on error
705
+ # or nil when timeout is reached or canceled.
706
+ #
707
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-notifies PG::Connection#notifies
708
+ def wait_for_notify_defer(timeout = nil, &blk)
709
+ df = FeaturedDeferrable.new
710
+ begin
711
+ check_async_command_aborted!
712
+ if status == CONNECTION_OK
713
+ setup_emio_watcher.watch_notify(df, timeout)
714
+ else
715
+ raise_error ConnectionBad
716
+ end
717
+ rescue Error => e
718
+ ::EM.next_tick { async_autoreconnect!(df, e) }
719
+ rescue Exception => e
720
+ ::EM.next_tick { df.fail(e) }
721
+ end
722
+ df.completion(&blk) if block_given?
723
+ df
724
+ end
725
+
726
+ alias_method :notifies_wait_defer, :wait_for_notify_defer
727
+
728
+ # Asynchronously retrieves the next result from a call to
729
+ # #send_query (or another asynchronous command) and immediately
730
+ # returns with a Deferrable.
731
+ # It then receives the result object on :succeed, or +nil+
732
+ # if no results are available.
733
+ #
734
+ # @macro deferrable_api
735
+ # @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
736
+ # or +nil+ if no results are available.
737
+ #
738
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
739
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
740
+ #
741
+ def get_result_defer(&blk)
742
+ df = FeaturedDeferrable.new
743
+ begin
744
+ if status == CONNECTION_OK
745
+ if is_busy
746
+ setup_emio_watcher.watch_results(df, nil, true)
747
+ else
748
+ df.succeed blocking_get_result
749
+ end
750
+ else
751
+ df.succeed
752
+ end
753
+ rescue Error => e
754
+ ::EM.next_tick { async_autoreconnect!(df, e) }
755
+ rescue Exception => e
756
+ ::EM.next_tick { df.fail(e) }
757
+ end
758
+ df.completion(&blk) if block_given?
759
+ df
760
+ end
761
+
762
+ # Asynchronously retrieves all available results on the current
763
+ # connection (from previously issued asynchronous commands like
764
+ # +send_query()+) and immediately returns with a Deferrable.
765
+ # It then receives the last non-NULL result on :succeed, or +nil+
766
+ # if no results are available.
767
+ #
768
+ # @macro deferrable_api
769
+ # @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
770
+ # or +nil+ if no results are available.
771
+ #
772
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
773
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
774
+ #
775
+ def get_last_result_defer(&blk)
776
+ df = FeaturedDeferrable.new
777
+ begin
778
+ if status == CONNECTION_OK
779
+ setup_emio_watcher.watch_results(df)
780
+ else
781
+ df.succeed
782
+ end
783
+ rescue Error => e
784
+ ::EM.next_tick { async_autoreconnect!(df, e) }
785
+ rescue Exception => e
786
+ ::EM.next_tick { df.fail(e) }
787
+ end
788
+ df.completion(&blk) if block_given?
789
+ df
790
+ end
791
+
792
+ # @!endgroup
793
+
794
+ alias_method :blocking_wait_for_notify, :wait_for_notify
795
+ alias_method :blocking_get_result, :get_result
796
+
797
+ def raise_error(klass=Error, message=error_message)
798
+ error = klass.new(message)
799
+ error.instance_variable_set(:@connection, self)
800
+ raise error
801
+ end
802
+
803
+ private
804
+
805
+ def fiber_sync(df, fiber)
806
+ f = nil
807
+ df.completion do |res|
808
+ if f then f.resume res else return res end
809
+ end
810
+ f = fiber
811
+ Fiber.yield
812
+ end
813
+
814
+ def check_async_command_aborted!
815
+ if @async_command_aborted
816
+ raise_error ConnectionBad, "previous query expired, need connection reset"
817
+ end
818
+ end
819
+
820
+ def setup_emio_watcher
821
+ if @watcher && @watcher.watching?
822
+ @watcher
823
+ else
824
+ @watcher = ::EM.watch(self.socket_io, Watcher, self)
825
+ end
826
+ end
827
+
828
+ public
829
+
830
+ # @!macro auto_synchrony_api_intro
831
+ # If EventMachine reactor is running and the current fiber isn't the
832
+ # root fiber this method performs command asynchronously yielding
833
+ # current fiber. Other fibers can process while waiting for the server
834
+ # to complete the request.
835
+ #
836
+ # Otherwise performs a blocking call to a parent method.
837
+ #
838
+ # @yieldparam result [PG::Result] command result on success
839
+ # @raise [PG::Error]
840
+
841
+ # @!macro auto_synchrony_api
842
+ # @macro auto_synchrony_api_intro
843
+ # @return [PG::Result] if block wasn't given
844
+ # @return [Object] result of the given block
845
+
846
+ # @!group Auto-sensing fiber-synchronized command methods
847
+
848
+ # @!method exec(sql, &blk)
849
+ # Sends SQL query request specified by +sql+ to PostgreSQL.
850
+ #
851
+ # @macro auto_synchrony_api
852
+ #
853
+ # @see PG::EM::Client#exec_defer
854
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
855
+ #
856
+ # @!method exec_params(sql, params=nil, result_format=nil, &blk)
857
+ # Sends SQL query request specified by +sql+ with optional +params+ and +result_format+ to PostgreSQL.
858
+ #
859
+ # @macro auto_synchrony_api
860
+ #
861
+ # @see PG::EM::Client#exec_params_defer
862
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
863
+ #
864
+ # @!method prepare(stmt_name, sql, param_types=nil, &blk)
865
+ # Prepares statement +sql+ with name +stmt_name+ to be executed later.
866
+ #
867
+ # @macro auto_synchrony_api
868
+ #
869
+ # @see PG::EM::Client#prepare_defer
870
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
871
+ #
872
+ # @!method exec_prepared(statement_name, params=nil, result_format=nil, &blk)
873
+ # Executes prepared named statement specified by +statement_name+.
874
+ #
875
+ # @macro auto_synchrony_api
876
+ #
877
+ # @see PG::EM::Client#exec_prepared_defer
878
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#exec_prepared
879
+ #
880
+ # @!method describe_prepared(statement_name, &blk)
881
+ # Retrieves information about the prepared statement +statement_name+,
882
+ #
883
+ # @macro auto_synchrony_api
884
+ #
885
+ # @see PG::EM::Client#describe_prepared_defer
886
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
887
+ #
888
+ # @!method describe_portal(portal_name, &blk)
889
+ # Retrieves information about the portal +portal_name+,
890
+ #
891
+ # @macro auto_synchrony_api
892
+ #
893
+ # @see PG::EM::Client#describe_portal_defer
894
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
895
+ #
896
+ # @!method get_last_result
897
+ # Retrieves all available results on the current connection
898
+ # (from previously issued asynchronous commands like +send_query()+)
899
+ # and returns the last non-NULL result, or +nil+ if no results are
900
+ # available.
901
+ #
902
+ # @macro auto_synchrony_api
903
+ # @return [nil] if no more results
904
+ #
905
+ # @see #get_last_result_defer
906
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
907
+ %w(
908
+ exec exec_defer
909
+ exec_params exec_defer
910
+ exec_prepared exec_prepared_defer
911
+ prepare prepare_defer
912
+ describe_prepared describe_prepared_defer
913
+ describe_portal describe_portal_defer
914
+ get_last_result get_last_result_defer
915
+ ).each_slice(2) do |name, defer_name|
916
+
917
+ class_eval <<-EOD, __FILE__, __LINE__
918
+ def #{name}(*args, &blk)
919
+ if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
920
+ if (result = fiber_sync #{defer_name}(*args), f).is_a?(::Exception)
921
+ raise result
922
+ end
923
+ if block_given? && result
924
+ begin
925
+ yield result
926
+ ensure
927
+ result.clear
928
+ end
929
+ else
930
+ result
931
+ end
932
+ else
933
+ super
934
+ end
935
+ end
936
+ EOD
937
+ end
938
+
939
+ alias_method :query, :exec
940
+ alias_method :async_query, :exec
941
+ alias_method :async_exec, :exec
942
+
943
+ # Blocks while waiting for notification(s), or until the optional
944
+ # +timeout+ is reached, whichever comes first.
945
+ # Returns +nil+ if +timeout+ is reached, the name of the +NOTIFY+
946
+ # event otherwise.
947
+ #
948
+ # If EventMachine reactor is running and the current fiber isn't the
949
+ # root fiber this method performs command asynchronously yielding
950
+ # current fiber. Other fibers can process while the current one is
951
+ # waiting for notifications.
952
+ #
953
+ # Otherwise performs a blocking call to a parent method.
954
+ # @return [String|nil]
955
+ # @yieldparam name [String] the name of the +NOTIFY+ event
956
+ # @yieldparam pid [Number] the generating pid
957
+ # @yieldparam payload [String] the optional payload
958
+ # @raise [PG::Error]
959
+ #
960
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-wait_for_notify PG::Connection#wait_for_notify
961
+ def wait_for_notify(timeout = nil)
962
+ if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
963
+ unless notify_hash = notifies
964
+ if (notify_hash = fiber_sync wait_for_notify_defer(timeout), f).is_a?(::Exception)
965
+ raise notify_hash
966
+ end
967
+ end
968
+ if notify_hash
969
+ if block_given?
970
+ yield notify_hash.values_at(:relname, :be_pid, :extra)
971
+ end
972
+ notify_hash[:relname]
973
+ end
974
+ else
975
+ super
976
+ end
977
+ end
978
+
979
+ alias_method :notifies_wait, :wait_for_notify
980
+
981
+ # Retrieves the next result from a call to #send_query (or another
982
+ # asynchronous command). If no more results are available returns
983
+ # +nil+ and the block (if given) is never called.
984
+ #
985
+ # @macro auto_synchrony_api
986
+ # @return [nil] if no more results
987
+ #
988
+ # @see #get_result_defer
989
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
990
+ def get_result
991
+ if is_busy && ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
992
+ if (result = fiber_sync get_result_defer, f).is_a?(::Exception)
993
+ raise result
994
+ end
995
+ if block_given? && result
996
+ begin
997
+ yield result
998
+ ensure
999
+ result.clear
1000
+ end
1001
+ else
1002
+ result
1003
+ end
1004
+ else
1005
+ super
1006
+ end
1007
+ end
1008
+
1009
+ # @!endgroup
1010
+
1011
+
1012
+ TRAN_BEGIN_QUERY = 'BEGIN'
1013
+ TRAN_ROLLBACK_QUERY = 'ROLLBACK'
1014
+ TRAN_COMMIT_QUERY = 'COMMIT'
1015
+
1016
+ # Executes a BEGIN at the start of the block and a COMMIT at the end
1017
+ # of the block or ROLLBACK if any exception occurs.
1018
+ #
1019
+ # @note Avoid using PG::EM::Client#*_defer calls inside the block or make sure
1020
+ # all queries are completed before the provided block terminates.
1021
+ # @return [Object] result of the block
1022
+ # @yieldparam client [self]
1023
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-transaction PG::Connection#transaction
1024
+ #
1025
+ # Calls to {#transaction} may be nested, however without sub-transactions
1026
+ # (save points). If the innermost transaction block raises an error
1027
+ # the transaction is rolled back to the state before the outermost
1028
+ # transaction began.
1029
+ #
1030
+ # This is an extension to the +PG::Connection#transaction+ method
1031
+ # as it does not support nesting in this way.
1032
+ #
1033
+ # The method is sensitive to the transaction status and will safely
1034
+ # rollback on any sql error even when it was catched by some rescue block.
1035
+ # But consider that rescuing any sql error within an utility method
1036
+ # is a bad idea.
1037
+ #
1038
+ # This method works in both blocking/async modes (regardles of the reactor state)
1039
+ # and is considered as a generic extension to the +PG::Connection#transaction+
1040
+ # method.
1041
+ #
1042
+ # @example Nested transaction example
1043
+ # def add_comment(user_id, text)
1044
+ # db.transaction do
1045
+ # cmt_id = db.query(
1046
+ # 'insert into comments (text) where user_id=$1 values ($2) returning id',
1047
+ # [user_id, text]).getvalue(0,0)
1048
+ # db.query(
1049
+ # 'update users set last_comment_id=$2 where id=$1', [user_id, cmt_id])
1050
+ # cmt_id
1051
+ # end
1052
+ # end
1053
+ #
1054
+ # def update_comment_count(page_id)
1055
+ # db.transaction do
1056
+ # count = db.query('select count(*) from comments where page_id=$1', [page_id]).getvalue(0,0)
1057
+ # db.query('update pages set comment_count=$2 where id=$1', [page_id, count])
1058
+ # end
1059
+ # end
1060
+ #
1061
+ # # to run add_comment and update_comment_count within the same transaction
1062
+ # db.transaction do
1063
+ # add_comment(user_id, some_text)
1064
+ # update_comment_count(page_id)
1065
+ # end
1066
+ #
1067
+ def transaction
1068
+ raise ArgumentError, 'Must supply block for PG::EM::Client#transaction' unless block_given?
1069
+ tcount = @client_tran_count.to_i
1070
+
1071
+ case transaction_status
1072
+ when PQTRANS_IDLE
1073
+ # there is no transaction yet, so let's begin
1074
+ exec(TRAN_BEGIN_QUERY)
1075
+ # reset transaction count in case user code rolled it back before
1076
+ tcount = 0 if tcount != 0
1077
+ when PQTRANS_INTRANS
1078
+ # transaction in progress, leave it be
1079
+ else
1080
+ # transaction failed, is in unknown state or command is active
1081
+ # in any case calling begin will raise server transaction error
1082
+ exec(TRAN_BEGIN_QUERY) # raises PG::InFailedSqlTransaction
1083
+ end
1084
+ # memoize nested count
1085
+ @client_tran_count = tcount + 1
1086
+ begin
1087
+
1088
+ result = yield self
1089
+
1090
+ rescue
1091
+ # error was raised
1092
+ case transaction_status
1093
+ when PQTRANS_INTRANS, PQTRANS_INERROR
1094
+ # do not rollback if transaction was rolled back before
1095
+ # or is in unknown state, which means connection reset is needed
1096
+ # and rollback only from the outermost transaction block
1097
+ exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
1098
+ end
1099
+ # raise again
1100
+ raise
1101
+ else
1102
+ # we are good (but not out of woods yet)
1103
+ case transaction_status
1104
+ when PQTRANS_INTRANS
1105
+ # commit only from the outermost transaction block
1106
+ exec(TRAN_COMMIT_QUERY) if tcount.zero?
1107
+ when PQTRANS_INERROR
1108
+ # no ruby error was raised (or an error was rescued in code block)
1109
+ # but there was an sql error anyway
1110
+ # so rollback after the outermost block
1111
+ exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
1112
+ when PQTRANS_IDLE
1113
+ # the code block has terminated the transaction on its own
1114
+ # so just reset the counter
1115
+ tcount = 0
1116
+ else
1117
+ # something isn't right, so provoke an error just in case
1118
+ exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
1119
+ end
1120
+ result
1121
+ ensure
1122
+ @client_tran_count = tcount
1123
+ end
1124
+ end
1125
+
1126
+ end
1127
+ end
1128
+
1129
+ end