em-pg-client-12 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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