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 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