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 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 with for async connect/reset
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
@@ -52,7 +52,7 @@ based on ruby-pg[https://bitbucket.org/ged/ruby-pg]
52
52
 
53
53
  === Latest branch (fully-async)
54
54
 
55
- $ [sudo] gem install em-pg-client --prerelease
55
+ $ [sudo] gem install em-pg-client --pre
56
56
 
57
57
  ==== Gemfile
58
58
 
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
- sh "rspec spec/em_release_client.rb"
11
- sh "rspec spec/em_devel_client.rb"
12
- sh "rspec spec/em_synchrony_client.rb"
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
@@ -2,7 +2,7 @@ $:.unshift "lib"
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "em-pg-client"
5
- s.version = "0.2.0.pre.2"
5
+ s.version = "0.2.0.pre.3"
6
6
  s.required_ruby_version = ">= 1.9.1"
7
7
  s.date = "#{Time.now.strftime("%Y-%m-%d")}"
8
8
  s.summary = "EventMachine PostgreSQL client"
@@ -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
- df.callback { |res| f.resume(res) }
26
- df.errback { |err| f.resume(err) }
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 && (@async_command_aborted || self.status != PG::CONNECTION_OK)
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
- @notify_timestamp = Time.now if @timer
191
+ result = false
162
192
  @client.consume_input
163
- result = if @client.is_busy
164
- false
165
- else
166
- @timer.cancel if @timer
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
- @client.get_last_result
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
- @deferrable.fail(e)
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
- @deferrable.succeed(result) unless result == false
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 && (@async_command_aborted || self.status != PG::CONNECTION_OK)
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
@@ -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
- it "should create simple table `foo`" do
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
- raise result if result.is_a? ::Exception
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.class.should == DateTime }
21
+ results.each {|r| r.should be_an_instance_of DateTime }
29
22
  EM.stop
30
23
  })
31
24
  end
32
25
 
33
- it "should read foo table with prepared statement" do
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
@@ -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
- it "should create simple table `foo`" do
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
- raise result if result.is_a? ::Exception
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.class.should == DateTime }
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
- it "should read foo table with prepared statement" do
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
@@ -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('DROP TABLE IF EXISTS foo')
10
- @client.query('CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)')
11
- EM.stop
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.class.should == DateTime }
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', 'SELECT * FROM foo order by id')
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
- EM.stop
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 &testcase
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 = PG::EM::Client.new(dbname: 'test')
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.2
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-04-30 00:00:00 Z
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