em-pg-client 0.2.0.pre.2 → 0.2.0.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- data/HISTORY.rdoc +10 -1
- data/README.rdoc +1 -1
- data/Rakefile +28 -5
- data/em-pg-client.gemspec +1 -1
- data/lib/em-synchrony/pg.rb +4 -4
- data/lib/pg/em.rb +61 -11
- data/spec/em_client_autoreconnect.rb +124 -0
- data/spec/em_client_common.rb +152 -0
- data/spec/em_devel_client.rb +6 -41
- data/spec/em_release_client.rb +6 -40
- data/spec/em_synchrony_client.rb +120 -9
- data/spec/em_synchrony_client_autoreconnect.rb +118 -0
- metadata +8 -2
data/HISTORY.rdoc
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
0.2.0.pre.3
|
2
|
+
- #status() returns CONNECTION_BAD for connections with expired query
|
3
|
+
- spec: query timeout expiration
|
4
|
+
- non-blocking result processing for multiple data query statements sent at once
|
5
|
+
- refine code in em-synchrony/pg
|
6
|
+
- spec: more detailed tests
|
7
|
+
- spec: async_connect
|
8
|
+
- spec: autoreconnect
|
9
|
+
|
1
10
|
0.2.0.pre.2
|
2
11
|
- errors from consume_input fails deferrable
|
3
12
|
- query_timeout now measures only network response timeout,
|
@@ -5,7 +14,7 @@
|
|
5
14
|
|
6
15
|
0.2.0.pre.1
|
7
16
|
- added query_timeout feature for async query commands
|
8
|
-
- added connect_timeout property
|
17
|
+
- added connect_timeout property for async connect/reset
|
9
18
|
- fix: async_autoreconnect for tcp/ip connections
|
10
19
|
- fix: async_* does not raise errors; errors handled by deferrable
|
11
20
|
- rework async_autoreconnect in fully async manner
|
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -4,12 +4,35 @@ task :default => [:test]
|
|
4
4
|
|
5
5
|
$gem_name = "em-pg-client"
|
6
6
|
|
7
|
-
desc "Run tests"
|
8
|
-
task :test do
|
7
|
+
desc "Run spec tests"
|
8
|
+
task :test, [:which] do |t, args|
|
9
|
+
args.with_defaults(:which => 'safe')
|
10
|
+
|
11
|
+
env_unix_socket = {'PGDATABASE' => 'test', 'PGHOST' => '/tmp'}
|
12
|
+
env_tcpip = {'PGDATABASE' => 'test', 'PGHOST' => 'localhost'}
|
13
|
+
|
9
14
|
puts "WARNING: The test needs to be run with an available local PostgreSQL server"
|
10
|
-
|
11
|
-
|
12
|
-
|
15
|
+
|
16
|
+
if %w[all safe].include? args[:which]
|
17
|
+
%w[
|
18
|
+
spec/em_release_client.rb
|
19
|
+
spec/em_devel_client.rb
|
20
|
+
spec/em_synchrony_client.rb
|
21
|
+
].each do |spec|
|
22
|
+
sh env_unix_socket, "rspec #{spec}"
|
23
|
+
sh env_tcpip, "rspec #{spec}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if %w[all unsafe dangerous autoreconnect].include? args[:which]
|
28
|
+
%w[
|
29
|
+
spec/em_client_autoreconnect.rb
|
30
|
+
spec/em_synchrony_client_autoreconnect.rb
|
31
|
+
].each do |spec|
|
32
|
+
sh env_unix_socket, "rspec #{spec}"
|
33
|
+
sh env_tcpip, "rspec #{spec}"
|
34
|
+
end
|
35
|
+
end
|
13
36
|
end
|
14
37
|
|
15
38
|
desc "Build the gem"
|
data/em-pg-client.gemspec
CHANGED
data/lib/em-synchrony/pg.rb
CHANGED
@@ -20,10 +20,10 @@ module PG
|
|
20
20
|
class_eval <<-EOD
|
21
21
|
def #{name}(*args, &blk)
|
22
22
|
if ::EM.reactor_running?
|
23
|
-
df = #{async_name}(*args)
|
24
23
|
f = Fiber.current
|
25
|
-
|
26
|
-
|
24
|
+
#{async_name}(*args) do |res|
|
25
|
+
f.resume(res)
|
26
|
+
end
|
27
27
|
|
28
28
|
result = Fiber.yield
|
29
29
|
raise result if result.is_a?(::Exception)
|
@@ -49,7 +49,7 @@ module PG
|
|
49
49
|
alias_method :query, :exec
|
50
50
|
|
51
51
|
def async_autoreconnect!(deferrable, error, &send_proc)
|
52
|
-
if async_autoreconnect &&
|
52
|
+
if async_autoreconnect && self.status != PG::CONNECTION_OK
|
53
53
|
reset_df = async_reset
|
54
54
|
reset_df.errback { |ex| deferrable.fail(ex) }
|
55
55
|
reset_df.callback do
|
data/lib/pg/em.rb
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
require 'pg'
|
2
2
|
module PG
|
3
|
+
|
4
|
+
class Result
|
5
|
+
class << self
|
6
|
+
unless method_defined? :check_result
|
7
|
+
# pg_check_result is internal ext function. I wish it was rubified.
|
8
|
+
# https://bitbucket.org/ged/ruby-pg/issue/123/rubify-pg_check_result
|
9
|
+
def check_result(connection, result)
|
10
|
+
if result.nil?
|
11
|
+
if (error_message = connection.error_message)
|
12
|
+
error = PG::Error.new(error_message)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
case result.result_status
|
16
|
+
when PG::PGRES_BAD_RESPONSE,
|
17
|
+
PG::PGRES_FATAL_ERROR,
|
18
|
+
PG::PGRES_NONFATAL_ERROR
|
19
|
+
error = PG::Error.new(result.error_message)
|
20
|
+
error.instance_variable_set('@result', result)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
if error
|
24
|
+
error.instance_variable_set('@connection', connection)
|
25
|
+
raise error
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
3
32
|
module EM
|
4
33
|
class FeaturedDeferrable < ::EM::DefaultDeferrable
|
5
34
|
def initialize(&blk)
|
@@ -131,6 +160,7 @@ module PG
|
|
131
160
|
|
132
161
|
module Watcher
|
133
162
|
def initialize(client, deferrable, send_proc)
|
163
|
+
@last_result = nil
|
134
164
|
@client = client
|
135
165
|
@deferrable = deferrable
|
136
166
|
@send_proc = send_proc
|
@@ -158,21 +188,33 @@ module PG
|
|
158
188
|
end
|
159
189
|
|
160
190
|
def notify_readable
|
161
|
-
|
191
|
+
result = false
|
162
192
|
@client.consume_input
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
@
|
193
|
+
until @client.is_busy && !self.error?
|
194
|
+
break if (single_result = @client.get_result).nil?
|
195
|
+
@last_result.clear if @last_result
|
196
|
+
@last_result = single_result
|
197
|
+
end
|
198
|
+
unless @client.is_busy
|
199
|
+
result = @last_result
|
200
|
+
Result.check_result(@client, result)
|
167
201
|
detach
|
168
|
-
@
|
202
|
+
@timer.cancel if @timer
|
169
203
|
end
|
170
|
-
rescue PG::Error => e
|
171
|
-
@client.async_autoreconnect!(@deferrable, e, &@send_proc)
|
172
204
|
rescue Exception => e
|
173
|
-
|
205
|
+
detach
|
206
|
+
@timer.cancel if @timer
|
207
|
+
if e.is_a?(PG::Error)
|
208
|
+
@client.async_autoreconnect!(@deferrable, e, &@send_proc)
|
209
|
+
else
|
210
|
+
@deferrable.fail(e)
|
211
|
+
end
|
174
212
|
else
|
175
|
-
|
213
|
+
if result == false
|
214
|
+
@notify_timestamp = Time.now if @timer
|
215
|
+
else
|
216
|
+
@deferrable.succeed(result)
|
217
|
+
end
|
176
218
|
end
|
177
219
|
end
|
178
220
|
|
@@ -290,9 +332,17 @@ module PG
|
|
290
332
|
super(*args)
|
291
333
|
end
|
292
334
|
|
335
|
+
def status
|
336
|
+
if @async_command_aborted
|
337
|
+
PG::CONNECTION_BAD
|
338
|
+
else
|
339
|
+
super
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
293
343
|
# Perform autoreconnect. Used internally.
|
294
344
|
def async_autoreconnect!(deferrable, error, &send_proc)
|
295
|
-
if async_autoreconnect &&
|
345
|
+
if async_autoreconnect && self.status != PG::CONNECTION_OK
|
296
346
|
reset_df = async_reset
|
297
347
|
reset_df.errback { |ex| deferrable.fail(ex) }
|
298
348
|
reset_df.callback do
|
@@ -0,0 +1,124 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'date'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'pg/em'
|
5
|
+
|
6
|
+
$pgserver_cmd_stop = %Q[sudo su - postgres -c 'pg_ctl stop -m fast']
|
7
|
+
$pgserver_cmd_start = %Q[sudo su - postgres -c 'pg_ctl -l $PGDATA/postgres.log start -w']
|
8
|
+
|
9
|
+
shared_context 'em-pg common' do
|
10
|
+
around(:each) do |testcase|
|
11
|
+
EM.run(&testcase)
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:all) do
|
15
|
+
@client.close
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'em-pg default autoreconnect' do
|
20
|
+
include_context 'em-pg common'
|
21
|
+
|
22
|
+
it "should get database size using query" do
|
23
|
+
@tested_proc.call
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should get database size using query after server restart" do
|
27
|
+
system($pgserver_cmd_stop).should be_true
|
28
|
+
system($pgserver_cmd_start).should be_true
|
29
|
+
@tested_proc.call
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not get database size using query after server shutdown" do
|
33
|
+
system($pgserver_cmd_stop).should be_true
|
34
|
+
@client.query('SELECT pg_database_size(current_database());') do |ex|
|
35
|
+
ex.should be_an_instance_of PG::Error
|
36
|
+
EM.stop
|
37
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should get database size using query after server startup" do
|
41
|
+
system($pgserver_cmd_start).should be_true
|
42
|
+
@tested_proc.call
|
43
|
+
end
|
44
|
+
|
45
|
+
before(:all) do
|
46
|
+
@tested_proc = proc do
|
47
|
+
@client.query('SELECT pg_database_size(current_database());') do |result|
|
48
|
+
result.should be_an_instance_of PG::Result
|
49
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
50
|
+
EM.stop
|
51
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
52
|
+
end
|
53
|
+
@client = PG::EM::Client.new
|
54
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe 'em-pg autoreconnect with on_autoreconnect' do
|
59
|
+
include_context 'em-pg common'
|
60
|
+
|
61
|
+
it "should get database size using prepared statement"do
|
62
|
+
@tested_proc.call
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should get database size using prepared statement after server restart" do
|
66
|
+
system($pgserver_cmd_stop).should be_true
|
67
|
+
system($pgserver_cmd_start).should be_true
|
68
|
+
@tested_proc.call
|
69
|
+
end
|
70
|
+
|
71
|
+
before(:all) do
|
72
|
+
@tested_proc = proc do
|
73
|
+
@client.exec_prepared('get_db_size') do |result|
|
74
|
+
result.should be_an_instance_of PG::Result
|
75
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
76
|
+
EM.stop
|
77
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
78
|
+
end
|
79
|
+
on_autoreconnect = proc do |client, ex|
|
80
|
+
df = client.prepare('get_db_size', 'SELECT pg_database_size(current_database());')
|
81
|
+
df.should be_a_kind_of ::EM::DefaultDeferrable
|
82
|
+
df
|
83
|
+
end
|
84
|
+
@client = PG::EM::Client.new(on_autoreconnect: on_autoreconnect)
|
85
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
86
|
+
@client.prepare('get_db_size', 'SELECT pg_database_size(current_database());')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe 'em-pg with autoreconnect disabled' do
|
91
|
+
include_context 'em-pg common'
|
92
|
+
|
93
|
+
it "should get database size using query" do
|
94
|
+
@tested_proc.call
|
95
|
+
end
|
96
|
+
|
97
|
+
it "should not get database size using query after server restart" do
|
98
|
+
system($pgserver_cmd_stop).should be_true
|
99
|
+
system($pgserver_cmd_start).should be_true
|
100
|
+
@client.query('SELECT pg_database_size(current_database());') do |ex|
|
101
|
+
ex.should be_an_instance_of PG::Error
|
102
|
+
EM.stop
|
103
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should get database size using query after async manual connection reset" do
|
107
|
+
@client.async_reset do |conn|
|
108
|
+
conn.should be @client
|
109
|
+
@tested_proc.call
|
110
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
111
|
+
end
|
112
|
+
|
113
|
+
before(:all) do
|
114
|
+
@tested_proc = proc do
|
115
|
+
@client.query('SELECT pg_database_size(current_database());') do |result|
|
116
|
+
result.should be_an_instance_of PG::Result
|
117
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
118
|
+
EM.stop
|
119
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
120
|
+
end
|
121
|
+
@client = PG::EM::Client.new(async_autoreconnect: false)
|
122
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
shared_context 'em-pg common before' do
|
2
|
+
|
3
|
+
around(:each) do |testcase|
|
4
|
+
EM.run &testcase
|
5
|
+
end
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
@cdates = []
|
9
|
+
@values = Array(('AA'..'ZZ').each_with_index)
|
10
|
+
@client = described_class.new
|
11
|
+
@client.query 'BEGIN TRANSACTION'
|
12
|
+
end
|
13
|
+
|
14
|
+
after(:all) do
|
15
|
+
@client.query 'ROLLBACK TRANSACTION'
|
16
|
+
@client.close
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be a client" do
|
20
|
+
@client.should be_an_instance_of described_class
|
21
|
+
EM.stop
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should create simple table `foo`" do
|
25
|
+
@client.query('DROP TABLE IF EXISTS foo') do |result|
|
26
|
+
result.should be_an_instance_of PG::Result
|
27
|
+
@client.query('CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)') do |result|
|
28
|
+
result.should be_an_instance_of PG::Result
|
29
|
+
EM.stop
|
30
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
31
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
shared_context 'em-pg common after' do
|
37
|
+
|
38
|
+
it "should read foo table with prepared statement" do
|
39
|
+
@client.prepare('get_foo', 'SELECT * FROM foo order by id') do |result|
|
40
|
+
result.should be_an_instance_of PG::Result
|
41
|
+
@client.exec_prepared('get_foo') do |result|
|
42
|
+
result.should be_an_instance_of PG::Result
|
43
|
+
result.each_with_index do |row, i|
|
44
|
+
row['id'].to_i.should == i
|
45
|
+
DateTime.parse(row['cdate']).should == @cdates[i]
|
46
|
+
row['data'].should == @values[i][0]
|
47
|
+
end
|
48
|
+
EM.stop
|
49
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
50
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should connect to database asynchronously" do
|
54
|
+
this = :first
|
55
|
+
described_class.async_connect do |conn|
|
56
|
+
this = :second
|
57
|
+
conn.should be_an_instance_of described_class
|
58
|
+
conn.query('SELECT pg_database_size(current_database());') do |result|
|
59
|
+
result.should be_an_instance_of PG::Result
|
60
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
61
|
+
EM.stop
|
62
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
63
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
64
|
+
this.should be :first
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should raise syntax error in misspelled multiple statement" do
|
68
|
+
@client.query('SELECT * from pg_class; SRELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
|
69
|
+
result.should be_an_instance_of PG::Error
|
70
|
+
result.to_s.should include "syntax error"
|
71
|
+
@client.query('ROLLBACK') do |result|
|
72
|
+
result.should be_an_instance_of PG::Result
|
73
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
74
|
+
result.should be_an_instance_of PG::Result
|
75
|
+
EM.stop
|
76
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
77
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
78
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return only last statement" do
|
82
|
+
@client.query('SELECT * from pg_class; SELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
|
83
|
+
result.should be_an_instance_of PG::Result
|
84
|
+
result[0]['number'].should eq "42"
|
85
|
+
EM.stop
|
86
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should timeout expire while executing query" do
|
90
|
+
@client.query_timeout.should eq 0
|
91
|
+
@client.query_timeout = 1.5
|
92
|
+
@client.query_timeout.should eq 1.5
|
93
|
+
start_time = Time.now
|
94
|
+
@client.query('SELECT pg_sleep(2)') do |result|
|
95
|
+
(Time.now - start_time).should be < 2
|
96
|
+
result.should be_an_instance_of PG::Error
|
97
|
+
result.to_s.should include "query timeout expired"
|
98
|
+
@client.async_command_aborted.should be_true
|
99
|
+
@client.query_timeout = 0
|
100
|
+
@client.query_timeout.should eq 0
|
101
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
102
|
+
result.should be_an_instance_of PG::Result
|
103
|
+
EM.stop
|
104
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
105
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should timeout not expire while executing query with partial results" do
|
109
|
+
@client.query_timeout.should eq 0
|
110
|
+
@client.query_timeout = 1.1
|
111
|
+
@client.query_timeout.should eq 1.1
|
112
|
+
start_time = Time.now
|
113
|
+
@client.query(
|
114
|
+
'SELECT * from pg_class;' +
|
115
|
+
'SELECT pg_sleep(1);' +
|
116
|
+
'SELECT * from pg_class;' +
|
117
|
+
'SELECT pg_sleep(1);' +
|
118
|
+
'SELECT 42 number') do |result|
|
119
|
+
(Time.now - start_time).should be > 2
|
120
|
+
result.should be_an_instance_of PG::Result
|
121
|
+
result[0]['number'].should eq "42"
|
122
|
+
@client.query_timeout = 0
|
123
|
+
@client.query_timeout.should eq 0
|
124
|
+
@client.async_command_aborted.should be_false
|
125
|
+
EM.stop
|
126
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should timeout expire while executing query with partial results" do
|
130
|
+
@client.query_timeout.should eq 0
|
131
|
+
@client.query_timeout = 1.1
|
132
|
+
@client.query_timeout.should eq 1.1
|
133
|
+
start_time = Time.now
|
134
|
+
@client.query(
|
135
|
+
'SELECT * from pg_class;' +
|
136
|
+
'SELECT pg_sleep(1);' +
|
137
|
+
'SELECT * from pg_class;' +
|
138
|
+
'SELECT pg_sleep(2);' +
|
139
|
+
'SELECT 42 number') do |result|
|
140
|
+
(Time.now - start_time).should be > 2
|
141
|
+
result.should be_an_instance_of PG::Error
|
142
|
+
result.to_s.should include "query timeout expired"
|
143
|
+
@client.query_timeout = 0
|
144
|
+
@client.query_timeout.should eq 0
|
145
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
146
|
+
result.should be_an_instance_of PG::Result
|
147
|
+
EM.stop
|
148
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
149
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
data/spec/em_devel_client.rb
CHANGED
@@ -3,61 +3,26 @@ gem 'eventmachine', '>= 1.0.0.beta.1'
|
|
3
3
|
require 'date'
|
4
4
|
require 'eventmachine'
|
5
5
|
require 'pg/em'
|
6
|
+
require 'em_client_common'
|
6
7
|
|
7
8
|
describe PG::EM::Client do
|
8
9
|
|
9
|
-
|
10
|
-
@client.query('DROP TABLE IF EXISTS foo') do |result|
|
11
|
-
raise result if result.is_a? ::Exception
|
12
|
-
@client.query('CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)') do |result|
|
13
|
-
raise result if result.is_a? ::Exception
|
14
|
-
EM.stop
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
10
|
+
include_context 'em-pg common before'
|
18
11
|
|
19
12
|
it "should populate foo with some data " do
|
20
13
|
EM::Iterator.new(@values).map(proc{ |(data, id), iter|
|
21
14
|
@client.query('INSERT INTO foo (id,cdate,data) VALUES($1,$2,$3) returning cdate', [id, DateTime.now, data]) do |result|
|
22
|
-
|
15
|
+
result.should be_an_instance_of PG::Result
|
23
16
|
iter.return(DateTime.parse(result[0]['cdate']))
|
24
|
-
end
|
17
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
25
18
|
}, proc{ |results|
|
26
19
|
@cdates.replace results
|
27
20
|
results.length.should == @values.length
|
28
|
-
results.each {|r| r.
|
21
|
+
results.each {|r| r.should be_an_instance_of DateTime }
|
29
22
|
EM.stop
|
30
23
|
})
|
31
24
|
end
|
32
25
|
|
33
|
-
|
34
|
-
@client.prepare('get_foo', 'SELECT * FROM foo order by id') do |result|
|
35
|
-
raise result if result.is_a? ::Exception
|
36
|
-
@client.exec_prepared('get_foo') do |result|
|
37
|
-
raise result if result.is_a? ::Exception
|
38
|
-
result.each_with_index do |row, i|
|
39
|
-
row['id'].to_i.should == i
|
40
|
-
DateTime.parse(row['cdate']).should == @cdates[i]
|
41
|
-
row['data'].should == @values[i][0]
|
42
|
-
end
|
43
|
-
EM.stop
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
around(:each) do |testcase|
|
49
|
-
EM.run &testcase
|
50
|
-
end
|
51
|
-
|
52
|
-
before(:all) do
|
53
|
-
@cdates = []
|
54
|
-
@values = Array(('AA'..'ZZ').each_with_index)
|
55
|
-
@client = PG::EM::Client.new(dbname: 'test')
|
56
|
-
@client.query 'BEGIN TRANSACTION'
|
57
|
-
end
|
26
|
+
include_context 'em-pg common after'
|
58
27
|
|
59
|
-
after(:all) do
|
60
|
-
@client.query 'ROLLBACK TRANSACTION'
|
61
|
-
@client.close
|
62
|
-
end
|
63
28
|
end
|
data/spec/em_release_client.rb
CHANGED
@@ -3,18 +3,11 @@ gem 'eventmachine', '= 0.12.10'
|
|
3
3
|
require 'date'
|
4
4
|
require 'eventmachine'
|
5
5
|
require 'pg/em'
|
6
|
+
require 'em_client_common'
|
6
7
|
|
7
8
|
describe PG::EM::Client do
|
8
9
|
|
9
|
-
|
10
|
-
@client.query('DROP TABLE IF EXISTS foo') do |result|
|
11
|
-
raise result if result.is_a? ::Exception
|
12
|
-
@client.query('CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)') do |result|
|
13
|
-
raise result if result.is_a? ::Exception
|
14
|
-
EM.stop
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
10
|
+
include_context 'em-pg common before'
|
18
11
|
|
19
12
|
it "should populate foo with some data " do
|
20
13
|
values = @values.dup
|
@@ -22,48 +15,21 @@ describe PG::EM::Client do
|
|
22
15
|
do_query = proc do
|
23
16
|
data, id = values.shift
|
24
17
|
@client.query('INSERT INTO foo (id,cdate,data) VALUES($1,$2,$3) returning cdate', [id, DateTime.now, data]) do |result|
|
25
|
-
|
18
|
+
result.should be_an_instance_of PG::Result
|
26
19
|
results << DateTime.parse(result[0]['cdate'])
|
27
20
|
if values.empty?
|
28
21
|
@cdates.replace results
|
29
22
|
results.length.should == @values.length
|
30
|
-
results.each {|r| r.
|
23
|
+
results.each {|r| r.should be_an_instance_of DateTime }
|
31
24
|
EM.stop
|
32
25
|
else
|
33
26
|
do_query.call
|
34
27
|
end
|
35
|
-
end
|
28
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
36
29
|
end
|
37
30
|
do_query.call
|
38
31
|
end
|
39
32
|
|
40
|
-
|
41
|
-
@client.prepare('get_foo', 'SELECT * FROM foo order by id') do
|
42
|
-
@client.exec_prepared('get_foo') do |result|
|
43
|
-
raise result if result.is_a? ::Exception
|
44
|
-
result.each_with_index do |row, i|
|
45
|
-
row['id'].to_i.should == i
|
46
|
-
DateTime.parse(row['cdate']).should == @cdates[i]
|
47
|
-
row['data'].should == @values[i][0]
|
48
|
-
end
|
49
|
-
EM.stop
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
around(:each) do |testcase|
|
55
|
-
EM.run &testcase
|
56
|
-
end
|
57
|
-
|
58
|
-
before(:all) do
|
59
|
-
@cdates = []
|
60
|
-
@values = Array(('AA'..'ZZ').each_with_index)
|
61
|
-
@client = PG::EM::Client.new(dbname: 'test')
|
62
|
-
@client.query 'BEGIN TRANSACTION'
|
63
|
-
end
|
33
|
+
include_context 'em-pg common after'
|
64
34
|
|
65
|
-
after(:all) do
|
66
|
-
@client.query 'ROLLBACK TRANSACTION'
|
67
|
-
@client.close
|
68
|
-
end
|
69
35
|
end
|
data/spec/em_synchrony_client.rb
CHANGED
@@ -5,44 +5,155 @@ require 'em-synchrony/pg'
|
|
5
5
|
|
6
6
|
describe PG::EM::Client do
|
7
7
|
|
8
|
+
it "should be client" do
|
9
|
+
@client.should be_an_instance_of described_class
|
10
|
+
end
|
11
|
+
|
8
12
|
it "should create simple table `foo`" do
|
9
|
-
@client.query(
|
10
|
-
|
11
|
-
|
13
|
+
@client.query(
|
14
|
+
'DROP TABLE IF EXISTS foo'
|
15
|
+
).should be_an_instance_of PG::Result
|
16
|
+
@client.query(
|
17
|
+
'CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)'
|
18
|
+
).should be_an_instance_of PG::Result
|
12
19
|
end
|
13
20
|
|
14
21
|
it "should populate foo with some data " do
|
15
22
|
results = @values.map do |(data, id)|
|
16
23
|
@client.query('INSERT INTO foo (id,cdate,data) VALUES($1,$2,$3) returning cdate', [id, DateTime.now, data]) do |result|
|
24
|
+
result.should be_an_instance_of PG::Result
|
17
25
|
DateTime.parse(result[0]['cdate'])
|
18
26
|
end
|
19
27
|
end
|
20
28
|
@cdates.replace results
|
21
29
|
results.length.should == @values.length
|
22
|
-
results.each {|r| r.
|
23
|
-
EM.stop
|
30
|
+
results.each {|r| r.should be_an_instance_of DateTime }
|
24
31
|
end
|
25
32
|
|
26
33
|
it "should read foo table with prepared statement" do
|
27
|
-
@client.prepare('get_foo',
|
34
|
+
@client.prepare('get_foo',
|
35
|
+
'SELECT * FROM foo order by id'
|
36
|
+
).should be_an_instance_of PG::Result
|
28
37
|
@client.exec_prepared('get_foo') do |result|
|
38
|
+
result.should be_an_instance_of PG::Result
|
29
39
|
result.each_with_index do |row, i|
|
30
40
|
row['id'].to_i.should == i
|
31
41
|
DateTime.parse(row['cdate']).should == @cdates[i]
|
32
42
|
row['data'].should == @values[i][0]
|
33
43
|
end
|
34
44
|
end
|
35
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should connect to database asynchronously" do
|
48
|
+
this = :first
|
49
|
+
f = Fiber.current
|
50
|
+
Fiber.new do
|
51
|
+
conn = described_class.new
|
52
|
+
this = :second
|
53
|
+
conn.should be_an_instance_of described_class
|
54
|
+
conn.query('SELECT pg_database_size(current_database());') do |result|
|
55
|
+
result.should be_an_instance_of PG::Result
|
56
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
57
|
+
end
|
58
|
+
conn.close
|
59
|
+
f.resume
|
60
|
+
end.resume
|
61
|
+
this.should be :first
|
62
|
+
Fiber.yield
|
63
|
+
this.should be :second
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise syntax error in misspelled multiple statement" do
|
67
|
+
expect {
|
68
|
+
@client.query('SELECT * from pg_class; SRELECT CURRENT_TIMESTAMP; SELECT 42 number')
|
69
|
+
}.to raise_error(PG::Error, /syntax error/)
|
70
|
+
@client.query('ROLLBACK') do |result|
|
71
|
+
result.should be_an_instance_of PG::Result
|
72
|
+
end
|
73
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
74
|
+
result.should be_an_instance_of PG::Result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should return only last statement" do
|
79
|
+
@client.query('SELECT * from pg_class; SELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
|
80
|
+
result.should be_an_instance_of PG::Result
|
81
|
+
result[0]['number'].should eq "42"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should timeout expire while executing query" do
|
86
|
+
@client.query_timeout.should eq 0
|
87
|
+
@client.query_timeout = 1.5
|
88
|
+
@client.query_timeout.should eq 1.5
|
89
|
+
start_time = Time.now
|
90
|
+
expect {
|
91
|
+
@client.query('SELECT pg_sleep(2)')
|
92
|
+
}.to raise_error(PG::Error, /query timeout expired/)
|
93
|
+
(Time.now - start_time).should be < 2
|
94
|
+
@client.query_timeout = 0
|
95
|
+
@client.query_timeout.should eq 0
|
96
|
+
@client.async_command_aborted.should be_true
|
97
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
98
|
+
result.should be_an_instance_of PG::Result
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should timeout not expire while executing query with partial results" do
|
103
|
+
@client.query_timeout.should eq 0
|
104
|
+
@client.query_timeout = 1.1
|
105
|
+
@client.query_timeout.should eq 1.1
|
106
|
+
start_time = Time.now
|
107
|
+
@client.query(
|
108
|
+
'SELECT * from pg_class;' +
|
109
|
+
'SELECT pg_sleep(1);' +
|
110
|
+
'SELECT * from pg_class;' +
|
111
|
+
'SELECT pg_sleep(1);' +
|
112
|
+
'SELECT 42 number') do |result|
|
113
|
+
(Time.now - start_time).should be > 2
|
114
|
+
result.should be_an_instance_of PG::Result
|
115
|
+
result[0]['number'].should eq "42"
|
116
|
+
@client.query_timeout = 0
|
117
|
+
@client.query_timeout.should eq 0
|
118
|
+
@client.async_command_aborted.should be_false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should timeout expire while executing query with partial results" do
|
123
|
+
@client.query_timeout.should eq 0
|
124
|
+
@client.query_timeout = 1.1
|
125
|
+
@client.query_timeout.should eq 1.1
|
126
|
+
start_time = Time.now
|
127
|
+
expect {
|
128
|
+
@client.query(
|
129
|
+
'SELECT * from pg_class;' +
|
130
|
+
'SELECT pg_sleep(1);' +
|
131
|
+
'SELECT * from pg_class;' +
|
132
|
+
'SELECT pg_sleep(2);' +
|
133
|
+
'SELECT 42 number')
|
134
|
+
}.to raise_error(PG::Error, /query timeout expired/)
|
135
|
+
(Time.now - start_time).should be > 2
|
136
|
+
@client.query_timeout = 0
|
137
|
+
@client.query_timeout.should eq 0
|
138
|
+
@client.query('BEGIN TRANSACTION') do |result|
|
139
|
+
result.should be_an_instance_of PG::Result
|
140
|
+
end
|
36
141
|
end
|
37
142
|
|
38
143
|
around(:each) do |testcase|
|
39
|
-
EM.synchrony
|
144
|
+
EM.synchrony do
|
145
|
+
begin
|
146
|
+
testcase.call
|
147
|
+
ensure
|
148
|
+
EM.stop
|
149
|
+
end
|
150
|
+
end
|
40
151
|
end
|
41
152
|
|
42
153
|
before(:all) do
|
43
154
|
@cdates = []
|
44
155
|
@values = Array(('AA'..'ZZ').each_with_index)
|
45
|
-
@client =
|
156
|
+
@client = described_class.new
|
46
157
|
@client.query 'BEGIN TRANSACTION'
|
47
158
|
end
|
48
159
|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
require 'date'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/pg'
|
5
|
+
|
6
|
+
$pgserver_cmd_stop = %Q[sudo su - postgres -c 'pg_ctl stop -m fast']
|
7
|
+
$pgserver_cmd_start = %Q[sudo su - postgres -c 'pg_ctl -l $PGDATA/postgres.log start -w']
|
8
|
+
|
9
|
+
shared_context 'em-synchrony-pg common' do
|
10
|
+
around(:each) do |testcase|
|
11
|
+
EM.synchrony do
|
12
|
+
testcase.call
|
13
|
+
EM.stop
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
after(:all) do
|
18
|
+
@client.close
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'em-synchrony-pg default autoreconnect' do
|
23
|
+
include_context 'em-synchrony-pg common'
|
24
|
+
|
25
|
+
it "should get database size using query" do
|
26
|
+
@tested_proc.call
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should get database size using query after server restart" do
|
30
|
+
system($pgserver_cmd_stop).should be_true
|
31
|
+
system($pgserver_cmd_start).should be_true
|
32
|
+
@tested_proc.call
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should not get database size using query after server shutdown" do
|
36
|
+
system($pgserver_cmd_stop).should be_true
|
37
|
+
expect {
|
38
|
+
@tested_proc.call
|
39
|
+
}.to raise_error(PG::Error)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should get database size using query after server startup" do
|
43
|
+
system($pgserver_cmd_start).should be_true
|
44
|
+
@tested_proc.call
|
45
|
+
end
|
46
|
+
|
47
|
+
before(:all) do
|
48
|
+
@tested_proc = proc do
|
49
|
+
@client.query('SELECT pg_database_size(current_database());') do |result|
|
50
|
+
result.should be_an_instance_of PG::Result
|
51
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
52
|
+
end
|
53
|
+
end
|
54
|
+
@client = PG::EM::Client.new
|
55
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe 'em-synchrony-pg autoreconnect with on_autoreconnect' do
|
60
|
+
include_context 'em-synchrony-pg common'
|
61
|
+
|
62
|
+
it "should get database size using prepared statement" do
|
63
|
+
@tested_proc.call
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should get database size using prepared statement after server restart" do
|
67
|
+
system($pgserver_cmd_stop).should be_true
|
68
|
+
system($pgserver_cmd_start).should be_true
|
69
|
+
@tested_proc.call
|
70
|
+
end
|
71
|
+
|
72
|
+
before(:all) do
|
73
|
+
@tested_proc = proc do
|
74
|
+
@client.exec_prepared('get_db_size') do |result|
|
75
|
+
result.should be_an_instance_of PG::Result
|
76
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
77
|
+
end
|
78
|
+
end
|
79
|
+
on_autoreconnect = proc do |client, ex|
|
80
|
+
client.prepare('get_db_size', 'SELECT pg_database_size(current_database());')
|
81
|
+
end
|
82
|
+
@client = PG::EM::Client.new(on_autoreconnect: on_autoreconnect)
|
83
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
84
|
+
on_autoreconnect.call @client
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'em-synchrony-pg with autoreconnect disabled' do
|
89
|
+
include_context 'em-synchrony-pg common'
|
90
|
+
|
91
|
+
it "should get database size using query" do
|
92
|
+
@tested_proc.call
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should not get database size using query after server restart" do
|
96
|
+
system($pgserver_cmd_stop).should be_true
|
97
|
+
system($pgserver_cmd_start).should be_true
|
98
|
+
expect {
|
99
|
+
@tested_proc.call
|
100
|
+
}.to raise_error(PG::Error)
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should get database size using query after manual connection reset" do
|
104
|
+
@client.reset
|
105
|
+
@tested_proc.call
|
106
|
+
end
|
107
|
+
|
108
|
+
before(:all) do
|
109
|
+
@tested_proc = proc do
|
110
|
+
@client.query('SELECT pg_database_size(current_database());') do |result|
|
111
|
+
result.should be_an_instance_of PG::Result
|
112
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
113
|
+
end
|
114
|
+
end
|
115
|
+
@client = PG::EM::Client.new(async_autoreconnect: false)
|
116
|
+
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
117
|
+
end
|
118
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: em-pg-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease: 6
|
5
|
-
version: 0.2.0.pre.
|
5
|
+
version: 0.2.0.pre.3
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Rafal Michalski
|
@@ -10,7 +10,7 @@ autorequire:
|
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
12
|
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-05-02 00:00:00 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: pg
|
@@ -85,9 +85,12 @@ files:
|
|
85
85
|
- em-pg-client.gemspec
|
86
86
|
- lib/em-synchrony/pg.rb
|
87
87
|
- lib/pg/em.rb
|
88
|
+
- spec/em_client_autoreconnect.rb
|
89
|
+
- spec/em_client_common.rb
|
88
90
|
- spec/em_devel_client.rb
|
89
91
|
- spec/em_release_client.rb
|
90
92
|
- spec/em_synchrony_client.rb
|
93
|
+
- spec/em_synchrony_client_autoreconnect.rb
|
91
94
|
homepage: http://github.com/royaltm/ruby-em-pg-client
|
92
95
|
licenses: []
|
93
96
|
|
@@ -121,4 +124,7 @@ summary: EventMachine PostgreSQL client
|
|
121
124
|
test_files:
|
122
125
|
- spec/em_synchrony_client.rb
|
123
126
|
- spec/em_devel_client.rb
|
127
|
+
- spec/em_client_common.rb
|
124
128
|
- spec/em_release_client.rb
|
129
|
+
- spec/em_synchrony_client_autoreconnect.rb
|
130
|
+
- spec/em_client_autoreconnect.rb
|