em-pg-client 0.2.0.pre.2 → 0.2.0.pre.3
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/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
|