em-pg-client 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,31 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - 2.0.0
6
+ env:
7
+ - PGVERSION=9.3 PGBUILD=9.3.2-1-linux-x64
8
+ - PGVERSION=9.0 PGBUILD=9.0.15-1-linux-x64
9
+ - PGVERSION=8.4 PGBUILD=8.4.19-1-linux-x64
10
+ before_install:
11
+ - sudo /etc/init.d/postgresql stop
12
+ - export PGPREFIX=/opt/PostgreSQL/$PGVERSION
13
+ - export PGDATA=$PGPREFIX/data
14
+ - export PATH="$PGPREFIX/bin:$PATH"
15
+ - wget http://get.enterprisedb.com/postgresql/postgresql-$PGBUILD.run
16
+ - chmod +x postgresql-$PGBUILD.run
17
+ - sudo ./postgresql-$PGBUILD.run --mode unattended --unattendedmodeui minimal --prefix
18
+ $PGPREFIX --datadir $PGDATA;
19
+ - sudo sed s/md5\$/trust/g $PGDATA/pg_hba.conf >/tmp/pg_hba.conf.$$
20
+ - sudo mv /tmp/pg_hba.conf.$$ $PGDATA/pg_hba.conf
21
+ - sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA reload
22
+ - PGHOST_UNIX=`netstat -l --protocol=unix|grep PGSQL|awk '{print $NF}'`
23
+ - export PGHOST_UNIX="`dirname $PGHOST_UNIX`"
24
+ - test -n "$PGHOST_UNIX"
25
+ - export PG_CTL_STOP_CMD="sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA stop
26
+ -s -m fast"
27
+ - export PG_CTL_START_CMD="sudo -i -u postgres $PGPREFIX/bin/pg_ctl -D $PGDATA start
28
+ -l $PGDATA/postgresql.log -s -w"
29
+ - export PGUSER=postgres
30
+ - psql -c 'create database test;' -h "$PGHOST_UNIX"
31
+ script: COVERAGE=1 bundle exec rake test_with_coveralls
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source "https://rubygems.org/"
2
+
3
+ gem 'rake' # Travis-CI complains about it not being here
4
+
5
+ gemspec
data/HISTORY.md CHANGED
@@ -1,3 +1,13 @@
1
+ 0.3.1
2
+
3
+ - support for asynchronous data streaming in single row mode -
4
+ asynchronous versions of get_result, get_last_result and
5
+ their deferrable variants
6
+ - watcher improvements allowing to reset pending commands
7
+ - minor DRY improvements in code and specs
8
+ - single_row_mode? helper
9
+ - spec: Travis CI and Coverage
10
+
1
11
  0.3.0
2
12
 
3
13
  - dedicated asynchronous connection pool
data/README.md CHANGED
@@ -1,16 +1,20 @@
1
- em-pg-client
2
- ============
1
+ #em-pg-client
3
2
 
4
- Author: Rafał Michalski (rafal at yeondir dot com)
3
+ The Ruby EventMachine driver interface to the PostgreSQL RDBMS. It is based on
4
+ [ruby-pg](https://bitbucket.org/ged/ruby-pg).
5
+
6
+ [![Gem Version][GV img]][Gem Version]
7
+ [![Dependency Status][DS img]][Dependency Status]
8
+ [![Coverage Status][CS img]][Coverage Status]
9
+ [![Build Status][BS img]][Build Status]
10
+
11
+ Author: Rafał Michalski (rafal at yeondir dot com)
5
12
 
6
13
  * http://github.com/royaltm/ruby-em-pg-client
7
14
 
8
15
  Description
9
16
  -----------
10
17
 
11
- __em-pg-client__ is the Ruby EventMachine driver interface to the
12
- PostgreSQL RDBMS. It is based on [ruby-pg](https://bitbucket.org/ged/ruby-pg).
13
-
14
18
  __em-pg-client__ provides {PG::EM::Client} class which inherits
15
19
  [PG::Connection](http://deveiate.org/code/pg/PG/Connection.html).
16
20
  You can work with {PG::EM::Client} almost the same way you would work
@@ -58,7 +62,7 @@ Features
58
62
  * Non-blocking / fully asynchronous processing with EventMachine.
59
63
  * Event reactor auto-detecting, asynchronous fiber-synchronized command methods
60
64
  (the same code can be used regardless of the EventMachine reactor state)
61
- * Asynchronous EM-style (deferrable returning) command methods.
65
+ * Asynchronous EM-style (Deferrable returning) command methods.
62
66
  * Fully asynchronous automatic re-connects on connection failures
63
67
  (e.g.: RDBMS restarts, network failures).
64
68
  * Minimal changes to [PG::Connection](http://deveiate.org/code/pg/PG/Connection.html) API.
@@ -66,7 +70,9 @@ Features
66
70
  * Dedicated connection pool with dynamic size, supporting asynchronous
67
71
  processing and transactions.
68
72
  * [Sequel Adapter](https://github.com/fl00r/em-pg-sequel) by Peter Yanovich.
69
- * Works on windows (requires ruby 2.0) (issue #7).
73
+ * Works on windows (requires ruby 2.0) ([issue #7][Issue 7]).
74
+ * __New__ - supports asynchronous query data processing in single row mode
75
+ ([issue #12][Issue 12]).
70
76
 
71
77
  Requirements
72
78
  ------------
@@ -82,14 +88,14 @@ Requirements
82
88
  Install
83
89
  -------
84
90
 
85
- ```
91
+ ```sh
86
92
  $ [sudo] gem install em-pg-client
87
93
  ```
88
94
 
89
95
  #### Gemfile
90
96
 
91
97
  ```ruby
92
- gem "em-pg-client", "~> 0.3.0"
98
+ gem "em-pg-client", "~> 0.3.1"
93
99
  ```
94
100
 
95
101
  #### Github
@@ -390,3 +396,18 @@ The greetz go to:
390
396
  [em-pg-sequel](https://github.com/fl00r/em-pg-sequel)
391
397
  * Andrew Rudenko [prepor](https://github.com/prepor) for the implicit idea
392
398
  of the re-usable watcher from his [em-pg](https://github.com/prepor/em-pg).
399
+
400
+ [![Bitdeli Badge][BB img]][Bitdeli Badge]
401
+
402
+ [Gem Version]: https://rubygems.org/gems/em-pg-client
403
+ [Dependency Status]: https://gemnasium.com/royaltm/ruby-em-pg-client
404
+ [Coverage Status]: https://coveralls.io/r/royaltm/ruby-em-pg-client
405
+ [Build Status]: https://travis-ci.org/royaltm/ruby-em-pg-client
406
+ [Bitdeli Badge]: https://bitdeli.com/free
407
+ [Issue 7]: https://github.com/royaltm/ruby-em-pg-client/issues/7
408
+ [Issue 12]: https://github.com/royaltm/ruby-em-pg-client/issues/12
409
+ [GV img]: https://badge.fury.io/rb/em-pg-client.png
410
+ [DS img]: https://gemnasium.com/royaltm/ruby-em-pg-client.png
411
+ [CS img]: https://coveralls.io/repos/royaltm/ruby-em-pg-client/badge.png
412
+ [BS img]: https://travis-ci.org/royaltm/ruby-em-pg-client.png
413
+ [BB img]: https://d2weczhvl823v0.cloudfront.net/royaltm/ruby-em-pg-client/trend.png
data/Rakefile CHANGED
@@ -1,3 +1,7 @@
1
+ require 'coveralls/rake/task'
2
+ Coveralls::RakeTask.new
3
+ task :test_with_coveralls => ['test:all', 'coveralls:push']
4
+
1
5
  $:.unshift "lib"
2
6
 
3
7
  task :default => [:test]
@@ -13,43 +17,76 @@ task :test => :'test:safe'
13
17
 
14
18
  namespace :test do
15
19
  env_common = {'PGDATABASE' => 'test'}
16
- env_unix_socket = env_common.merge('PGHOST' => '/tmp')
17
- env_tcpip = env_common.merge('PGHOST' => 'localhost')
20
+ env_unix = env_common.merge('PGHOST' => ENV['PGHOST_UNIX'] || '/tmp')
21
+ env_inet = env_common.merge('PGHOST' => ENV['PGHOST_INET'] || 'localhost')
18
22
 
19
23
  task :warn do
20
- puts "WARNING: The test needs to be run with an available local PostgreSQL server"
24
+ puts "WARNING: The tests needs to be run with an available local PostgreSQL server"
21
25
  end
22
26
 
23
27
  desc "Run specs only"
24
- task :spec do
25
- sh "rspec spec/pg_em_featured_deferrable.rb"
26
- sh "rspec spec/pg_em_client_options.rb"
27
- sh "rspec spec/pg_em_client_connect_finish.rb"
28
- sh "rspec spec/pg_em_client_connect_timeout.rb"
29
- sh "rspec spec/pg_em_connection_pool.rb"
28
+ task :spec => [:spec_defer, :spec_pool, :spec_client]
29
+
30
+ task :spec_client do
31
+ sh({'COVNAME'=>'spec:client'}, "rspec spec/pg_em_client_*.rb")
32
+ end
33
+
34
+ task :spec_pool do
35
+ sh({'COVNAME'=>'spec:pool'}, "rspec spec/pg_em_connection_pool.rb")
36
+ end
37
+
38
+ task :spec_defer do
39
+ sh({'COVNAME'=>'spec:deferrable'}, "rspec spec/pg_em_featured_deferrable.rb")
30
40
  end
31
41
 
32
42
  desc "Run safe tests only"
33
- task :safe => [:warn, :spec] do
34
- %w[
35
- spec/em_client.rb
36
- spec/em_synchrony_client.rb
37
- ].each do |spec|
38
- sh env_unix_socket, "rspec #{spec}" unless windows_os?
39
- sh env_tcpip, "rspec #{spec}"
43
+ task :safe => [:warn, :spec, :async, :fiber]
44
+ task :async => [:async_inet, :async_unix]
45
+ task :fiber => [:fiber_inet, :fiber_unix]
46
+
47
+ task :async_inet do
48
+ sh env_inet.merge('COVNAME'=>'async:inet'), "rspec spec/em_client.rb"
49
+ end
50
+
51
+ task :async_unix do
52
+ sh env_unix.merge('COVNAME'=>'async:unix'), "rspec spec/em_client.rb" unless windows_os?
53
+ end
54
+
55
+ task :fiber_inet do
56
+ sh env_inet.merge('COVNAME'=>'fiber:inet'), "rspec spec/em_synchrony_client.rb"
57
+ end
58
+
59
+ task :fiber_unix do
60
+ sh env_unix.merge('COVNAME'=>'fiber:unix'), "rspec spec/em_synchrony_client.rb" unless windows_os?
61
+ end
62
+
63
+ task :pgdata_check do
64
+ unless ENV['PGDATA'] || (ENV['PG_CTL_STOP_CMD'] && ENV['PG_CTL_START_CMD'])
65
+ raise "Set PGDATA environment variable before running the autoreconnect tests."
40
66
  end
41
67
  end
42
68
 
43
69
  desc "Run unsafe tests only"
44
- task :unsafe => :warn do
45
- raise "Set PGDATA environment variable before running the autoreconnect tests." unless ENV['PGDATA']
46
- %w[
47
- spec/em_client_autoreconnect.rb
48
- spec/em_synchrony_client_autoreconnect.rb
49
- ].each do |spec|
50
- sh env_unix_socket, "rspec #{spec}" unless windows_os?
51
- sh env_tcpip, "rspec #{spec}"
52
- end
70
+ task :unsafe => [:warn, :pgdata_check,
71
+ :async_autoreconnect_inet,
72
+ :async_autoreconnect_unix,
73
+ :fiber_autoreconnect_inet,
74
+ :fiber_autoreconnect_unix]
75
+
76
+ task :async_autoreconnect_inet do
77
+ sh env_inet.merge('COVNAME'=>'async:autoreconnect:inet'), "rspec spec/em_client_autoreconnect.rb"
78
+ end
79
+
80
+ task :async_autoreconnect_unix do
81
+ sh env_unix.merge('COVNAME'=>'async:autoreconnect:unix'), "rspec spec/em_client_autoreconnect.rb"
82
+ end
83
+
84
+ task :fiber_autoreconnect_inet do
85
+ sh env_inet.merge('COVNAME'=>'fiber:autoreconnect:inet'), "rspec spec/em_synchrony_client_autoreconnect.rb"
86
+ end
87
+
88
+ task :fiber_autoreconnect_unix do
89
+ sh env_unix.merge('COVNAME'=>'fiber:autoreconnect:unix'), "rspec spec/em_synchrony_client_autoreconnect.rb"
53
90
  end
54
91
 
55
92
  desc "Run safe and unsafe tests"
@@ -1,13 +1,11 @@
1
1
  $:.unshift('./lib')
2
2
  require 'eventmachine'
3
3
  require 'em-synchrony'
4
- require 'em-synchrony/pg'
4
+ require 'pg/em/connection_pool'
5
5
  require "em-synchrony/fiber_iterator"
6
6
  require 'pp'
7
7
  require 'benchmark'
8
8
 
9
- $dbname = 'alpha'
10
-
11
9
  def benchmark(repeat=100)
12
10
  Benchmark.bm(20) do |b|
13
11
  b.report('single:') { single(repeat) }
@@ -28,31 +26,36 @@ end
28
26
 
29
27
  def patch_remove_blocking
30
28
  PG::EM::Client::Watcher.module_eval <<-EOE
31
- alias_method :notify_readable, :original_notify_readable
32
- undef :original_notify_readable
29
+ alias_method :fetch_results, :original_fetch_results
30
+ alias_method :notify_readable, :fetch_results
31
+ undef :original_fetch_results
33
32
  EOE
34
33
  end
35
34
 
36
35
  def patch_blocking
36
+ PG::Connection.class_eval <<-EOE
37
+ alias_method :blocking_get_last_result, :get_last_result
38
+ EOE
37
39
  PG::EM::Client::Watcher.module_eval <<-EOE
38
- alias_method :original_notify_readable, :notify_readable
39
- def notify_readable
40
- detach
40
+ alias_method :original_fetch_results, :fetch_results
41
+ def fetch_results
42
+ self.notify_readable = false
41
43
  begin
42
- result = @client.get_last_result
44
+ result = @client.blocking_get_last_result
43
45
  rescue Exception => e
44
46
  @deferrable.fail(e)
45
47
  else
46
48
  @deferrable.succeed(result)
47
49
  end
48
50
  end
51
+ alias_method :notify_readable, :fetch_results
49
52
  EOE
50
53
  end
51
54
 
52
55
  # retrieve resources using single select query
53
56
  def single(repeat=1)
54
57
  rowcount = 0
55
- p = PGconn.new :dbname => $dbname, :host => 'localhost'
58
+ p = PGconn.new
56
59
  p.query('select count(*) from resources') do |result|
57
60
  rowcount = result.getvalue(0,0).to_i
58
61
  end
@@ -69,7 +72,7 @@ def parallel(repeat=1, chunk_size=2000, concurrency=10)
69
72
  resources = []
70
73
  rowcount = 0
71
74
  EM.synchrony do
72
- p = EM::Synchrony::ConnectionPool.new(size: concurrency) { PG::EM::Client.new :dbname => $dbname }
75
+ p = PG::EM::ConnectionPool.new size: concurrency
73
76
  p.query('select count(*) from resources') do |result|
74
77
  rowcount = result.getvalue(0,0).to_i
75
78
  end
@@ -78,7 +81,7 @@ def parallel(repeat=1, chunk_size=2000, concurrency=10)
78
81
  EM::Synchrony::FiberIterator.new(offsets, concurrency).each do |offset|
79
82
  p.query('select * from resources order by cdate limit $1 offset $2', [chunk_size, offset]) do |result|
80
83
  resources[offset, chunk_size] = result.values
81
- end
84
+ end
82
85
  end
83
86
  end
84
87
  EM.stop
@@ -22,6 +22,8 @@ Gem::Specification.new do |s|
22
22
  s.requirements << "PostgreSQL server"
23
23
  s.add_runtime_dependency "pg", ">= 0.17.0"
24
24
  s.add_runtime_dependency "eventmachine", "~> 1.0.0"
25
- s.add_development_dependency "rspec", "~> 2.8.0"
25
+ s.add_development_dependency "rspec", "~> 2.14"
26
26
  s.add_development_dependency "em-synchrony", "~> 1.0.0"
27
+ s.add_development_dependency "coveralls", ">= 0.7.0"
28
+ s.add_development_dependency "simplecov", ">= 0.8.2"
27
29
  end
@@ -0,0 +1,67 @@
1
+ # Demonstation of fully asynchronous query data streaming.
2
+ #
3
+ # This is a low-level method which has its:
4
+ #
5
+ # upside: it's the same way you would work with PG::Connection
6
+ # downside: it's a little verbose and doesn't support automatic re-connects
7
+ gem 'em-pg-client', '>= 0.3.1'
8
+ require 'pg/em/connection_pool'
9
+ require 'em-synchrony'
10
+ require 'em-synchrony/fiber_iterator'
11
+
12
+ TABLE_NAME = 'resources'
13
+
14
+ unless PG::EM::Client.single_row_mode?
15
+ raise 'compile pg against pqlib >= 9.2 to support single row mode'
16
+ end
17
+
18
+ def tick_sleep
19
+ f = Fiber.current
20
+ EM.next_tick { f.resume }
21
+ Fiber.yield
22
+ end
23
+
24
+ EM.synchrony do
25
+ EM.add_periodic_timer(0.01) { print ' ' }
26
+
27
+ db = PG::EM::ConnectionPool.new size: 3
28
+
29
+ 10.times do
30
+
31
+ EM::Synchrony::FiberIterator.new(%w[@ * #], 3).each do |mark|
32
+
33
+ db.hold do |pg|
34
+ pg.send_query("select * from #{TABLE_NAME}")
35
+ pg.set_single_row_mode
36
+ rows = 0
37
+ while result = pg.get_result
38
+ begin
39
+ result.check
40
+ result.each do |tuple|
41
+ rows += 1
42
+ # process tuple
43
+ print mark
44
+ if (rows % 10).zero?
45
+ # let reactor do some work if data is coming too fast
46
+ tick_sleep
47
+ end
48
+ # break stream cleanly
49
+ pg.reset if rows > 1000
50
+ end
51
+ rescue PG::Error => e
52
+ # cleanup connection
53
+ pg.get_last_result
54
+ raise e
55
+ ensure
56
+ result.clear
57
+ end
58
+ end
59
+ end
60
+
61
+ end
62
+
63
+ puts
64
+ puts '='*80
65
+ end
66
+ EM.stop
67
+ end
@@ -1,5 +1,5 @@
1
1
  module PG
2
2
  module EM
3
- VERSION = '0.3.0'
3
+ VERSION = '0.3.1'
4
4
  end
5
5
  end
@@ -175,6 +175,21 @@ module PG
175
175
  # Used internally for marking connection as aborted on query timeout.
176
176
  attr_accessor :async_command_aborted
177
177
 
178
+ # Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
179
+ # Single row mode is available since +libpq+ 9.2.
180
+ # @return [Boolean]
181
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-set_single_row_mode PG::Connection#set_single_row_mode
182
+ def self.single_row_mode?
183
+ method_defined? :set_single_row_mode
184
+ end
185
+
186
+ # Returns +true+ if +pg+ supports single row mode or +false+ otherwise.
187
+ # @return [Boolean]
188
+ # @see single_row_mode?
189
+ def single_row_mode?
190
+ self.class.single_row_mode?
191
+ end
192
+
178
193
  # environment variable name for connect_timeout fallback value
179
194
  @@connect_timeout_envvar = conndefaults.find{|d| d[:keyword] == "connect_timeout" }[:envvar]
180
195
 
@@ -267,9 +282,10 @@ module PG
267
282
  # after successfull reset.
268
283
  # If the block is provided it's bound to +callback+ and +errback+ hooks
269
284
  # of the returned deferrable.
285
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
270
286
  def reset_defer(&blk)
271
287
  @async_command_aborted = false
272
- df = PG::EM::FeaturedDeferrable.new(&blk)
288
+ df = FeaturedDeferrable.new(&blk)
273
289
  # there can be only one watch handler over the socket
274
290
  # apparently eventmachine has hard time dealing with more than one
275
291
  # for blocking reset this is not needed
@@ -302,10 +318,9 @@ module PG
302
318
  # Otherwise performs a thread-blocking call to the parent method.
303
319
  #
304
320
  # @raise [PG::Error]
305
- #
321
+ # @see #reset_defer
306
322
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-reset PG::Connection#reset
307
323
  def reset
308
- @async_command_aborted = false
309
324
  if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
310
325
  reset_defer {|r| f.resume(r) }
311
326
 
@@ -313,6 +328,11 @@ module PG
313
328
  raise conn if conn.is_a?(::Exception)
314
329
  conn
315
330
  else
331
+ @async_command_aborted = false
332
+ if @watcher
333
+ @watcher.detach if @watcher.watching?
334
+ @watcher = nil
335
+ end
316
336
  super
317
337
  end
318
338
  end
@@ -394,7 +414,7 @@ module PG
394
414
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-status PG::Connection#status
395
415
  def status
396
416
  if @async_command_aborted
397
- PG::CONNECTION_BAD
417
+ CONNECTION_BAD
398
418
  else
399
419
  super
400
420
  end
@@ -404,10 +424,10 @@ module PG
404
424
  # Perform auto re-connect. Used internally.
405
425
  def async_autoreconnect!(deferrable, error, &send_proc)
406
426
  # reconnect only if connection is bad and flag is set
407
- if self.status != PG::CONNECTION_OK && async_autoreconnect
427
+ if self.status == CONNECTION_BAD && async_autoreconnect
408
428
  # check if transaction was active
409
429
  was_in_transaction = case @last_transaction_status
410
- when PG::PQTRANS_IDLE, PG::PQTRANS_UNKNOWN
430
+ when PQTRANS_IDLE, PQTRANS_UNKNOWN
411
431
  false
412
432
  else
413
433
  true
@@ -431,7 +451,7 @@ module PG
431
451
  if returned_df.respond_to?(:callback) && returned_df.respond_to?(:errback)
432
452
  # the handler returned a deferrable
433
453
  returned_df.callback do
434
- if was_in_transaction
454
+ if was_in_transaction || !send_proc
435
455
  # there was a transaction in progress, fail anyway
436
456
  deferrable.fail error
437
457
  else
@@ -444,7 +464,7 @@ module PG
444
464
  elsif returned_df.is_a?(Exception)
445
465
  # tha handler returned an exception object, so fail with it
446
466
  deferrable.fail returned_df
447
- elsif returned_df == false || (was_in_transaction && returned_df != true)
467
+ elsif returned_df == false || !send_proc || (was_in_transaction && returned_df != true)
448
468
  # tha handler returned false or raised an exception
449
469
  # or there was an active transaction and handler didn't return true
450
470
  deferrable.fail error
@@ -453,7 +473,7 @@ module PG
453
473
  deferrable.protect(&send_proc)
454
474
  end
455
475
  end.resume
456
- elsif was_in_transaction
476
+ elsif was_in_transaction || !send_proc
457
477
  # there was a transaction in progress, fail anyway
458
478
  deferrable.fail error
459
479
  else
@@ -469,11 +489,14 @@ module PG
469
489
  end
470
490
 
471
491
  # @!macro deferrable_api
472
- # @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
473
492
  # @return [FeaturedDeferrable]
474
- # Use the returned deferrable's +callback+ and +errback+ method to get the result.
475
- # If the block is provided it's bound to both the +callback+ and +errback+ hooks
476
- # of the returned deferrable.
493
+ # Use the returned Deferrable's +callback+ and +errback+ methods to
494
+ # get the result. If the block is provided it's bound to both the
495
+ # +callback+ and +errback+ hooks of the returned deferrable.
496
+
497
+ # @!macro deferrable_query_api
498
+ # @yieldparam result [PG::Result|Error] command result on success or a PG::Error instance on error.
499
+ # @macro deferrable_api
477
500
 
478
501
  # @!group Deferrable command methods
479
502
 
@@ -481,41 +504,41 @@ module PG
481
504
  # Sends SQL query request specified by +sql+ to PostgreSQL for asynchronous processing,
482
505
  # and immediately returns with +deferrable+.
483
506
  #
484
- # @macro deferrable_api
507
+ # @macro deferrable_query_api
485
508
  #
486
509
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec PG::Connection#exec
487
510
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_params PG::Connection#exec_params
488
511
  #
489
512
  # @!method prepare_defer(stmt_name, sql, param_types=nil, &blk)
490
513
  # Prepares statement +sql+ with name +stmt_name+ to be executed later asynchronously,
491
- # and immediately returns with deferrable.
514
+ # and immediately returns with a Deferrable.
492
515
  #
493
- # @macro deferrable_api
516
+ # @macro deferrable_query_api
494
517
  #
495
518
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
496
519
  #
497
520
  # @!method exec_prepared_defer(statement_name, params=nil, result_format=nil, &blk)
498
521
  # Execute prepared named statement specified by +statement_name+ asynchronously,
499
- # and immediately returns with deferrable.
522
+ # and immediately returns with a Deferrable.
500
523
  #
501
- # @macro deferrable_api
524
+ # @macro deferrable_query_api
502
525
  #
503
526
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query_prepared PG::Connection#send_query_prepared
504
527
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#send_exec_prepared
505
528
  #
506
529
  # @!method describe_prepared_defer(statement_name, &blk)
507
530
  # Asynchronously sends command to retrieve information about the prepared statement +statement_name+,
508
- # and immediately returns with deferrable.
531
+ # and immediately returns with a Deferrable.
509
532
  #
510
- # @macro deferrable_api
533
+ # @macro deferrable_query_api
511
534
  #
512
535
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
513
536
  #
514
537
  # @!method describe_portal_defer(portal_name, &blk)
515
538
  # Asynchronously sends command to retrieve information about the portal +portal_name+,
516
- # and immediately returns with deferrable.
539
+ # and immediately returns with a Deferrable.
517
540
  #
518
- # @macro deferrable_api
541
+ # @macro deferrable_query_api
519
542
  #
520
543
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
521
544
  #
@@ -529,25 +552,16 @@ module PG
529
552
 
530
553
  class_eval <<-EOD, __FILE__, __LINE__
531
554
  def #{defer_name}(*args, &blk)
532
- df = PG::EM::FeaturedDeferrable.new(&blk)
555
+ df = FeaturedDeferrable.new(&blk)
533
556
  send_proc = proc do
534
557
  #{send_name}(*args)
535
- if @watcher && @watcher.watching?
536
- @watcher.watch_query(df, send_proc)
537
- else
538
- @watcher = ::EM.watch(self.socket_io, Watcher, self).
539
- watch_query(df, send_proc)
540
- end
558
+ setup_emio_watcher.watch_results(df, send_proc)
541
559
  end
542
560
  begin
543
- if @async_command_aborted
544
- error = ConnectionBad.new("previous query expired, need connection reset")
545
- error.instance_variable_set(:@connection, self)
546
- raise error
547
- end
561
+ check_async_command_aborted!
548
562
  @last_transaction_status = transaction_status
549
563
  send_proc.call
550
- rescue PG::Error => e
564
+ rescue Error => e
551
565
  ::EM.next_tick { async_autoreconnect!(df, e, &send_proc) }
552
566
  rescue Exception => e
553
567
  ::EM.next_tick { df.fail(e) }
@@ -565,18 +579,106 @@ module PG
565
579
 
566
580
  # @!endgroup
567
581
 
568
- # @!macro auto_synchrony_api
569
- # Performs command asynchronously yielding current fiber
570
- # if EventMachine reactor is running and the current fiber isn't the
571
- # root fiber. Other fibers can process while waiting for the server
582
+ # Asynchronously retrieves the next result from a call to
583
+ # #send_query (or another asynchronous command) and immediately
584
+ # returns with a Deferrable.
585
+ # It then receives the result object on :succeed, or +nil+
586
+ # if no results are available.
587
+ #
588
+ # @macro deferrable_api
589
+ # @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
590
+ # or +nil+ if no results are available.
591
+ #
592
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
593
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
594
+ #
595
+ def get_result_defer(&blk)
596
+ begin
597
+ df = FeaturedDeferrable.new(&blk)
598
+ check_async_command_aborted!
599
+ setup_emio_watcher.watch_results(df, nil, true)
600
+ rescue Error => e
601
+ ::EM.next_tick { async_autoreconnect!(df, e) }
602
+ rescue Exception => e
603
+ ::EM.next_tick { df.fail(e) }
604
+ end
605
+ df
606
+ end
607
+
608
+ alias_method :blocking_get_result, :get_result
609
+
610
+ # Asynchronously retrieves all available results on the current
611
+ # connection (from previously issued asynchronous commands like
612
+ # +send_query()+) and immediately returns with a Deferrable.
613
+ # It then receives the last non-NULL result on :succeed, or +nil+
614
+ # if no results are available.
615
+ #
616
+ # @macro deferrable_api
617
+ # @yieldparam result [PG::Result|Error|nil] command result on success or a PG::Error instance on error
618
+ # or +nil+ if no results are available.
619
+ #
620
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-send_query PG::Connection#send_query
621
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
622
+ #
623
+ def get_last_result_defer(&blk)
624
+ begin
625
+ df = FeaturedDeferrable.new(&blk)
626
+ check_async_command_aborted!
627
+ setup_emio_watcher.watch_results(df)
628
+ rescue Error => e
629
+ ::EM.next_tick { async_autoreconnect!(df, e) }
630
+ rescue Exception => e
631
+ ::EM.next_tick { df.fail(e) }
632
+ end
633
+ df
634
+ end
635
+
636
+ def raise_error(klass=Error, message=error_message)
637
+ error = klass.new(message)
638
+ error.instance_variable_set(:@connection, self)
639
+ raise error
640
+ end
641
+
642
+ private
643
+
644
+ def check_async_command_aborted!
645
+ if @async_command_aborted
646
+ raise_error ConnectionBad, "previous query expired, need connection reset"
647
+ end
648
+ end
649
+
650
+ def setup_emio_watcher
651
+ case status
652
+ when CONNECTION_BAD
653
+ raise_error ConnectionBad
654
+ when CONNECTION_OK
655
+ if @watcher && @watcher.watching?
656
+ @watcher
657
+ else
658
+ @watcher = ::EM.watch(self.socket_io, Watcher, self)
659
+ end
660
+ else
661
+ raise_error ConnectionBad, "connection reset pending"
662
+ end
663
+ end
664
+
665
+ public
666
+
667
+ # @!macro auto_synchrony_api_intro
668
+ # If EventMachine reactor is running and the current fiber isn't the
669
+ # root fiber this method performs command asynchronously yielding
670
+ # current fiber. Other fibers can process while waiting for the server
572
671
  # to complete the request.
573
672
  #
574
- # Otherwise performs a blocking call to parent method.
673
+ # Otherwise performs a blocking call to a parent method.
575
674
  #
576
675
  # @yieldparam result [PG::Result] command result on success
676
+ # @raise [PG::Error]
677
+
678
+ # @!macro auto_synchrony_api
679
+ # @macro auto_synchrony_api_intro
577
680
  # @return [PG::Result] if block wasn't given
578
681
  # @return [Object] result of the given block
579
- # @raise [PG::Error]
580
682
 
581
683
  # @!group Auto-sensing thread or fiber blocking command methods
582
684
 
@@ -605,7 +707,7 @@ module PG
605
707
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-prepare PG::Connection#prepare
606
708
  #
607
709
  # @!method exec_prepared(statement_name, params=nil, result_format=nil, &blk)
608
- # Execute prepared named statement specified by +statement_name+.
710
+ # Executes prepared named statement specified by +statement_name+.
609
711
  #
610
712
  # @macro auto_synchrony_api
611
713
  #
@@ -613,7 +715,7 @@ module PG
613
715
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-exec_prepared PG::Connection#exec_prepared
614
716
  #
615
717
  # @!method describe_prepared(statement_name, &blk)
616
- # Retrieve information about the prepared statement +statement_name+,
718
+ # Retrieves information about the prepared statement +statement_name+,
617
719
  #
618
720
  # @macro auto_synchrony_api
619
721
  #
@@ -621,13 +723,35 @@ module PG
621
723
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_prepared PG::Connection#describe_prepared
622
724
  #
623
725
  # @!method describe_portal(portal_name, &blk)
624
- # Retrieve information about the portal +portal_name+,
726
+ # Retrieves information about the portal +portal_name+,
625
727
  #
626
728
  # @macro auto_synchrony_api
627
729
  #
628
730
  # @see PG::EM::Client#describe_portal_defer
629
731
  # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-describe_portal PG::Connection#describe_portal
630
732
  #
733
+ # @!method get_result(&blk)
734
+ # Retrieves the next result from a call to #send_query (or another
735
+ # asynchronous command). If no more results are available returns
736
+ # +nil+ and the block (if given) is never called.
737
+ #
738
+ # @macro auto_synchrony_api
739
+ # @return [nil] if no more results
740
+ #
741
+ # @see #get_result_defer
742
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_result PG::Connection#get_result
743
+ #
744
+ # @!method get_last_result
745
+ # Retrieves all available results on the current connection
746
+ # (from previously issued asynchronous commands like +send_query()+)
747
+ # and returns the last non-NULL result, or +nil+ if no results are
748
+ # available.
749
+ #
750
+ # @macro auto_synchrony_api
751
+ # @return [nil] if no more results
752
+ #
753
+ # @see #get_last_result_defer
754
+ # @see http://deveiate.org/code/pg/PG/Connection.html#method-i-get_last_result PG::Connection#get_last_result
631
755
  %w(
632
756
  exec exec_defer
633
757
  exec_params exec_defer
@@ -635,18 +759,25 @@ module PG
635
759
  prepare prepare_defer
636
760
  describe_prepared describe_prepared_defer
637
761
  describe_portal describe_portal_defer
762
+ get_result get_result_defer
763
+ get_last_result get_last_result_defer
638
764
  ).each_slice(2) do |name, defer_name|
639
765
 
640
766
  class_eval <<-EOD, __FILE__, __LINE__
641
767
  def #{name}(*args, &blk)
642
768
  if ::EM.reactor_running? && !(f = Fiber.current).equal?(ROOT_FIBER)
769
+ result = fiber = nil
643
770
  #{defer_name}(*args) do |res|
644
- f.resume(res)
771
+ f = nil
772
+ if fiber
773
+ fiber.resume(res)
774
+ else
775
+ result = res
776
+ end
645
777
  end
646
-
647
- result = Fiber.yield
778
+ result = Fiber.yield if (fiber = f)
648
779
  raise result if result.is_a?(::Exception)
649
- if block_given?
780
+ if block_given? && result
650
781
  begin
651
782
  yield result
652
783
  ensure
@@ -668,9 +799,11 @@ module PG
668
799
 
669
800
  # @!endgroup
670
801
 
802
+
671
803
  TRAN_BEGIN_QUERY = 'BEGIN'
672
804
  TRAN_ROLLBACK_QUERY = 'ROLLBACK'
673
805
  TRAN_COMMIT_QUERY = 'COMMIT'
806
+
674
807
  # Executes a BEGIN at the start of the block and a COMMIT at the end
675
808
  # of the block or ROLLBACK if any exception occurs.
676
809
  #
@@ -727,12 +860,12 @@ module PG
727
860
  tcount = @client_tran_count.to_i
728
861
 
729
862
  case transaction_status
730
- when PG::PQTRANS_IDLE
863
+ when PQTRANS_IDLE
731
864
  # there is no transaction yet, so let's begin
732
865
  exec(TRAN_BEGIN_QUERY)
733
866
  # reset transaction count in case user code rolled it back before
734
867
  tcount = 0 if tcount != 0
735
- when PG::PQTRANS_INTRANS
868
+ when PQTRANS_INTRANS
736
869
  # transaction in progress, leave it be
737
870
  else
738
871
  # transaction failed, is in unknown state or command is active
@@ -748,7 +881,7 @@ module PG
748
881
  rescue
749
882
  # error was raised
750
883
  case transaction_status
751
- when PG::PQTRANS_INTRANS, PG::PQTRANS_INERROR
884
+ when PQTRANS_INTRANS, PQTRANS_INERROR
752
885
  # do not rollback if transaction was rolled back before
753
886
  # or is in unknown state, which means connection reset is needed
754
887
  # and rollback only from the outermost transaction block
@@ -759,15 +892,15 @@ module PG
759
892
  else
760
893
  # we are good (but not out of woods yet)
761
894
  case transaction_status
762
- when PG::PQTRANS_INTRANS
895
+ when PQTRANS_INTRANS
763
896
  # commit only from the outermost transaction block
764
897
  exec(TRAN_COMMIT_QUERY) if tcount.zero?
765
- when PG::PQTRANS_INERROR
898
+ when PQTRANS_INERROR
766
899
  # no ruby error was raised (or an error was rescued in code block)
767
900
  # but there was an sql error anyway
768
901
  # so rollback after the outermost block
769
902
  exec(TRAN_ROLLBACK_QUERY) if tcount.zero?
770
- when PG::PQTRANS_IDLE
903
+ when PQTRANS_IDLE
771
904
  # the code block has terminated the transaction on its own
772
905
  # so just reset the counter
773
906
  tcount = 0