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 +1 -0
- data/.travis.yml +31 -0
- data/Gemfile +5 -0
- data/HISTORY.md +10 -0
- data/README.md +31 -10
- data/Rakefile +62 -25
- data/benchmarks/em_pg.rb +15 -12
- data/em-pg-client.gemspec +3 -1
- data/examples/single_row_mode.rb +67 -0
- data/lib/pg/em-version.rb +1 -1
- data/lib/pg/em.rb +187 -54
- data/lib/pg/em/client/connect_watcher.rb +2 -8
- data/lib/pg/em/client/watcher.rb +45 -21
- data/spec/em_client_autoreconnect.rb +140 -8
- data/spec/em_client_common.rb +77 -0
- data/spec/em_synchrony_client.rb +77 -9
- data/spec/em_synchrony_client_autoreconnect.rb +120 -6
- data/spec/pg_em_client_connect_finish.rb +1 -1
- data/spec/pg_em_client_options.rb +13 -3
- data/spec/spec_helper.rb +9 -0
- metadata +42 -4
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.travis.yml
ADDED
@@ -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
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
|
-
|
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 (
|
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.
|
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
|
-
|
17
|
-
|
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
|
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
|
25
|
-
|
26
|
-
|
27
|
-
sh
|
28
|
-
|
29
|
-
|
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
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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"
|
data/benchmarks/em_pg.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
1
|
$:.unshift('./lib')
|
2
2
|
require 'eventmachine'
|
3
3
|
require 'em-synchrony'
|
4
|
-
require 'em
|
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 :
|
32
|
-
|
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 :
|
39
|
-
def
|
40
|
-
|
40
|
+
alias_method :original_fetch_results, :fetch_results
|
41
|
+
def fetch_results
|
42
|
+
self.notify_readable = false
|
41
43
|
begin
|
42
|
-
result = @client.
|
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
|
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::
|
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
|
data/em-pg-client.gemspec
CHANGED
@@ -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.
|
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
|
data/lib/pg/em-version.rb
CHANGED
data/lib/pg/em.rb
CHANGED
@@ -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 =
|
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
|
-
|
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
|
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
|
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
|
475
|
-
# If the block is provided it's bound to both the
|
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
|
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
|
514
|
+
# and immediately returns with a Deferrable.
|
492
515
|
#
|
493
|
-
# @macro
|
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
|
522
|
+
# and immediately returns with a Deferrable.
|
500
523
|
#
|
501
|
-
# @macro
|
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
|
531
|
+
# and immediately returns with a Deferrable.
|
509
532
|
#
|
510
|
-
# @macro
|
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
|
539
|
+
# and immediately returns with a Deferrable.
|
517
540
|
#
|
518
|
-
# @macro
|
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 =
|
555
|
+
df = FeaturedDeferrable.new(&blk)
|
533
556
|
send_proc = proc do
|
534
557
|
#{send_name}(*args)
|
535
|
-
|
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
|
-
|
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
|
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
|
-
#
|
569
|
-
#
|
570
|
-
#
|
571
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
895
|
+
when PQTRANS_INTRANS
|
763
896
|
# commit only from the outermost transaction block
|
764
897
|
exec(TRAN_COMMIT_QUERY) if tcount.zero?
|
765
|
-
when
|
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
|
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
|