em-pg-client 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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