em-pg-client-12 0.3.4
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.
- checksums.yaml +7 -0
- data/.rspec +1 -0
- data/.travis.yml +32 -0
- data/.yardopts +1 -0
- data/BENCHMARKS.md +91 -0
- data/Gemfile +5 -0
- data/HISTORY.md +107 -0
- data/LICENSE +21 -0
- data/README.md +456 -0
- data/Rakefile +157 -0
- data/benchmarks/em_pg.rb +96 -0
- data/benchmarks/single_row_mode.rb +88 -0
- data/em-pg-client.gemspec +34 -0
- data/examples/single_row_mode.rb +57 -0
- data/lib/em-pg-client.rb +1 -0
- data/lib/em-synchrony/pg.rb +3 -0
- data/lib/pg/em-version.rb +5 -0
- data/lib/pg/em.rb +1129 -0
- data/lib/pg/em/client/connect_watcher.rb +89 -0
- data/lib/pg/em/client/watcher.rb +204 -0
- data/lib/pg/em/connection_pool.rb +480 -0
- data/lib/pg/em/featured_deferrable.rb +43 -0
- data/spec/connection_pool_helpers.rb +89 -0
- data/spec/em_client.rb +33 -0
- data/spec/em_client_autoreconnect.rb +672 -0
- data/spec/em_client_common.rb +619 -0
- data/spec/em_client_on_connect.rb +171 -0
- data/spec/em_connection_pool.rb +200 -0
- data/spec/em_synchrony_client.rb +787 -0
- data/spec/em_synchrony_client_autoreconnect.rb +560 -0
- data/spec/pg_em_client_connect_finish.rb +54 -0
- data/spec/pg_em_client_connect_timeout.rb +91 -0
- data/spec/pg_em_client_options.rb +133 -0
- data/spec/pg_em_connection_pool.rb +679 -0
- data/spec/pg_em_featured_deferrable.rb +125 -0
- data/spec/spec_helper.rb +9 -0
- metadata +187 -0
@@ -0,0 +1,171 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
gem 'eventmachine', '~> 1.0.0'
|
3
|
+
gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'em-synchrony'
|
6
|
+
require 'pg/em'
|
7
|
+
module PGSpecMacros
|
8
|
+
def test_blocking
|
9
|
+
client = subject.new(options)
|
10
|
+
client.should be_an_instance_of subject
|
11
|
+
client.on_connect.should be on_connect
|
12
|
+
client.exec_prepared(query_name) do |result|
|
13
|
+
result.should be_an_instance_of PG::Result
|
14
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
15
|
+
end
|
16
|
+
client.reset
|
17
|
+
client.exec_prepared(query_name) do |result|
|
18
|
+
result.should be_an_instance_of PG::Result
|
19
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_blocking_error
|
24
|
+
expect do
|
25
|
+
subject.new(options)
|
26
|
+
end.to raise_error(on_connect_exception)
|
27
|
+
client = subject.new
|
28
|
+
client.should be_an_instance_of subject
|
29
|
+
client.on_connect = on_connect
|
30
|
+
expect do
|
31
|
+
client.reset
|
32
|
+
end.to raise_error(on_connect_exception)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
RSpec.configure do |config|
|
37
|
+
config.include(PGSpecMacros)
|
38
|
+
end
|
39
|
+
|
40
|
+
shared_context 'test on_connect' do
|
41
|
+
|
42
|
+
it "should invoke on_connect after deferrable connect and reset" do
|
43
|
+
EM.run do
|
44
|
+
subject.connect_defer(options) do |client|
|
45
|
+
client.should be_an_instance_of subject
|
46
|
+
client.on_connect.should be on_connect
|
47
|
+
client.exec_prepared_defer(query_name) do |result|
|
48
|
+
result.should be_an_instance_of PG::Result
|
49
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
50
|
+
client.reset_defer do |client|
|
51
|
+
client.should be_an_instance_of subject
|
52
|
+
client.exec_prepared_defer(query_name) do |result|
|
53
|
+
result.should be_an_instance_of PG::Result
|
54
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
55
|
+
EM.stop
|
56
|
+
end.should be_a_kind_of ::EM::Deferrable
|
57
|
+
end.should be_a_kind_of ::EM::Deferrable
|
58
|
+
end.should be_a_kind_of ::EM::Deferrable
|
59
|
+
end.should be_a_kind_of ::EM::Deferrable
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should invoke on_connect after synchrony connect and reset" do
|
64
|
+
EM.synchrony do
|
65
|
+
test_blocking
|
66
|
+
EM.stop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
shared_context 'test on_connect error' do
|
73
|
+
|
74
|
+
it "should fail on_connect with exception after deferrable connect and reset" do
|
75
|
+
EM.run do
|
76
|
+
subject.connect_defer(options) do |ex|
|
77
|
+
ex.should be_an_instance_of on_connect_exception
|
78
|
+
subject.connect_defer do |client|
|
79
|
+
client.should be_an_instance_of subject
|
80
|
+
client.on_connect = on_connect
|
81
|
+
client.reset_defer do |ex|
|
82
|
+
ex.should be_an_instance_of on_connect_exception
|
83
|
+
EM.stop
|
84
|
+
end.should be_a_kind_of ::EM::Deferrable
|
85
|
+
end.should be_a_kind_of ::EM::Deferrable
|
86
|
+
end.should be_a_kind_of ::EM::Deferrable
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should fail on_connect with exception after synchrony connect and reset" do
|
91
|
+
EM.synchrony do
|
92
|
+
test_blocking_error
|
93
|
+
EM.stop
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
shared_context 'test blocking' do
|
99
|
+
it "should invoke on_connect after blocking connect and reset" do
|
100
|
+
test_blocking
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
shared_context 'test blocking on_connect error' do
|
105
|
+
it "should fail on_connect with exception after blocking connect and reset" do
|
106
|
+
test_blocking_error
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
describe 'on_connect option' do
|
111
|
+
subject { PG::EM::Client }
|
112
|
+
let(:query_name) { 'get_db_size' }
|
113
|
+
let(:query) { 'SELECT pg_database_size(current_database());' }
|
114
|
+
let(:options) { {on_connect: on_connect} }
|
115
|
+
let(:sleep_query){ 'SELECT pg_sleep(0.1)'}
|
116
|
+
let(:on_connect_exception) { Class.new(StandardError) }
|
117
|
+
|
118
|
+
|
119
|
+
describe 'with deferrable on_connect' do
|
120
|
+
let(:on_connect) { proc {|client, is_async|
|
121
|
+
is_async.should be_true
|
122
|
+
PG::EM::FeaturedDeferrable.new.tap do |df|
|
123
|
+
client.exec_defer(sleep_query).callback do
|
124
|
+
df.bind_status client.prepare_defer(query_name, query)
|
125
|
+
end.errback { df.fail on_connect_exception }
|
126
|
+
end
|
127
|
+
} }
|
128
|
+
|
129
|
+
include_context 'test on_connect'
|
130
|
+
end
|
131
|
+
|
132
|
+
describe 'with synchrony on_connect' do
|
133
|
+
let(:on_connect) { proc {|client, is_async|
|
134
|
+
is_async.should be_true
|
135
|
+
was_async = false
|
136
|
+
EM.next_tick { was_async = true }
|
137
|
+
client.exec(sleep_query)
|
138
|
+
client.prepare(query_name, query)
|
139
|
+
was_async.should be_true
|
140
|
+
} }
|
141
|
+
|
142
|
+
include_context 'test on_connect'
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'with blocking on_connect' do
|
146
|
+
let(:on_connect) { proc {|client, is_async|
|
147
|
+
is_async.should be_false
|
148
|
+
client.prepare(query_name, query)
|
149
|
+
} }
|
150
|
+
|
151
|
+
include_context 'test blocking'
|
152
|
+
end
|
153
|
+
|
154
|
+
describe 'with error raised in on_connect' do
|
155
|
+
let(:on_connect) { proc {|client|
|
156
|
+
raise on_connect_exception
|
157
|
+
} }
|
158
|
+
|
159
|
+
include_context 'test on_connect error'
|
160
|
+
include_context 'test blocking on_connect error'
|
161
|
+
end
|
162
|
+
|
163
|
+
describe 'with on_connect deferrable failure' do
|
164
|
+
let(:on_connect) { proc {|client|
|
165
|
+
::EM::DefaultDeferrable.new.tap {|df| df.fail on_connect_exception.new }
|
166
|
+
} }
|
167
|
+
|
168
|
+
include_context 'test on_connect error'
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
gem 'eventmachine', '~> 1.0.0'
|
3
|
+
gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
|
4
|
+
require 'eventmachine'
|
5
|
+
require 'em-synchrony'
|
6
|
+
require 'em-synchrony/fiber_iterator'
|
7
|
+
require 'pg/em/connection_pool'
|
8
|
+
|
9
|
+
shared_context 'test on_connect' do
|
10
|
+
|
11
|
+
it 'should call prepared statement concurrently with synchrony' do
|
12
|
+
results = []
|
13
|
+
pool = subject.new(options)
|
14
|
+
pool.max_size.should eq concurrency
|
15
|
+
pool.size.should eq 1
|
16
|
+
start = Time.now
|
17
|
+
EM::Synchrony::FiberIterator.new((1..concurrency), concurrency).each do |index|
|
18
|
+
pool.exec_prepared(query_name) do |result|
|
19
|
+
result.should be_an_instance_of PG::Result
|
20
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
21
|
+
end
|
22
|
+
pool.query(sleep_query).should be_an_instance_of PG::Result
|
23
|
+
results << index
|
24
|
+
end
|
25
|
+
delta = Time.now - start
|
26
|
+
delta.should be_between(sleep_interval, sleep_interval * concurrency / 2)
|
27
|
+
results.sort.should eq (1..concurrency).to_a
|
28
|
+
pool.size.should eq concurrency
|
29
|
+
EM.stop
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should call prepared statement concurrently with deferrable' do
|
33
|
+
results = []
|
34
|
+
subject.connect_defer(options) do |pool|
|
35
|
+
pool.max_size.should eq concurrency
|
36
|
+
pool.size.should eq 1
|
37
|
+
start = Time.now
|
38
|
+
concurrency.times do |index|
|
39
|
+
pool.exec_prepared_defer(query_name) do |result|
|
40
|
+
result.should be_an_instance_of PG::Result
|
41
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
42
|
+
pool.query_defer(sleep_query) do |result|
|
43
|
+
result.should be_an_instance_of PG::Result
|
44
|
+
results << index
|
45
|
+
if results.length == concurrency
|
46
|
+
delta = Time.now - start
|
47
|
+
delta.should be_between(sleep_interval, sleep_interval * concurrency / 2)
|
48
|
+
results.sort.should eq (0...concurrency).to_a
|
49
|
+
pool.size.should eq concurrency
|
50
|
+
EM.stop
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
shared_context 'test on_connect error' do
|
60
|
+
|
61
|
+
it "should fail on_connect with exception after synchrony connect" do
|
62
|
+
expect do
|
63
|
+
subject.new(options)
|
64
|
+
end.to raise_error(on_connect_exception)
|
65
|
+
client = subject.new(options.merge(lazy: true))
|
66
|
+
client.should be_an_instance_of subject
|
67
|
+
expect do
|
68
|
+
client.query(sleep_query)
|
69
|
+
end.to raise_error(on_connect_exception)
|
70
|
+
EM.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should fail on_connect with exception after deferrable connect" do
|
74
|
+
subject.connect_defer(options) do |ex|
|
75
|
+
ex.should be_an_instance_of on_connect_exception
|
76
|
+
pool = subject.new(options.merge(lazy: true))
|
77
|
+
pool.should be_an_instance_of subject
|
78
|
+
pool.query_defer(sleep_query) do |ex|
|
79
|
+
ex.should be_an_instance_of on_connect_exception
|
80
|
+
EM.stop
|
81
|
+
end.should be_an_instance_of PG::EM::FeaturedDeferrable
|
82
|
+
end.should be_an_instance_of PG::EM::FeaturedDeferrable
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'connection pool' do
|
88
|
+
subject { PG::EM::ConnectionPool }
|
89
|
+
|
90
|
+
describe 'on_connect' do
|
91
|
+
let(:query_name) { 'get_db_size' }
|
92
|
+
let(:query) { 'SELECT pg_database_size(current_database());' }
|
93
|
+
let(:concurrency) { 10 }
|
94
|
+
let(:options) { {size: concurrency, on_connect: on_connect} }
|
95
|
+
let(:sleep_interval) { 0.1 }
|
96
|
+
let(:sleep_query) { "SELECT pg_sleep(#{sleep_interval})"}
|
97
|
+
let(:on_connect_exception) { Class.new(StandardError) }
|
98
|
+
let(:on_connect) { proc {} }
|
99
|
+
|
100
|
+
around(:each) do |testcase|
|
101
|
+
EM.synchrony do
|
102
|
+
begin
|
103
|
+
testcase.call
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'should setup block as on_connect client option' do
|
109
|
+
connect_hook = false
|
110
|
+
pool = subject.new { connect_hook = true }
|
111
|
+
connect_hook.should be_true
|
112
|
+
pool.should be_an_instance_of subject
|
113
|
+
pool.on_connect.should be_an_instance_of Proc
|
114
|
+
EM.stop
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should prefer on_connect from options' do
|
118
|
+
connect_hook = false
|
119
|
+
pool = subject.new(options) { connect_hook = true }
|
120
|
+
connect_hook.should be_false
|
121
|
+
pool.should be_an_instance_of subject
|
122
|
+
pool.on_connect.should be on_connect
|
123
|
+
EM.stop
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'with deferrable on_connect' do
|
127
|
+
let(:on_connect) { proc {|client, is_async|
|
128
|
+
is_async.should be_true
|
129
|
+
PG::EM::FeaturedDeferrable.new.tap do |df|
|
130
|
+
client.exec_defer(sleep_query).callback do
|
131
|
+
df.bind_status client.prepare_defer(query_name, query)
|
132
|
+
end.errback { df.fail on_connect_exception }
|
133
|
+
end
|
134
|
+
} }
|
135
|
+
|
136
|
+
include_context 'test on_connect'
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'with synchrony on_connect' do
|
140
|
+
let(:on_connect) { proc {|client, is_async|
|
141
|
+
is_async.should be_true
|
142
|
+
was_async = false
|
143
|
+
EM.next_tick { was_async = true }
|
144
|
+
client.exec(sleep_query)
|
145
|
+
client.prepare(query_name, query)
|
146
|
+
was_async.should be_true
|
147
|
+
} }
|
148
|
+
|
149
|
+
include_context 'test on_connect'
|
150
|
+
end
|
151
|
+
|
152
|
+
describe 'with error raised in on_connect' do
|
153
|
+
let(:on_connect) { proc {|client|
|
154
|
+
raise on_connect_exception
|
155
|
+
} }
|
156
|
+
|
157
|
+
include_context 'test on_connect error'
|
158
|
+
end
|
159
|
+
|
160
|
+
describe 'with on_connect deferrable failure' do
|
161
|
+
let(:on_connect) { proc {|client|
|
162
|
+
EM::DefaultDeferrable.new.tap {|df| df.fail on_connect_exception.new }
|
163
|
+
} }
|
164
|
+
|
165
|
+
include_context 'test on_connect error'
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe '#transaction' do
|
170
|
+
let(:concurrency) { 2 }
|
171
|
+
let(:options) { {size: concurrency} }
|
172
|
+
let(:query) { 'SELECT pg_database_size(current_database());' }
|
173
|
+
|
174
|
+
around(:each) do |testcase|
|
175
|
+
EM.synchrony do
|
176
|
+
begin
|
177
|
+
@pool = subject.new(options)
|
178
|
+
testcase.call
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'should lock transaction connection to fiber' do
|
184
|
+
over_count = 2
|
185
|
+
@pool.transaction do |pg|
|
186
|
+
@pool.hold {|c| c.should be pg }
|
187
|
+
Fiber.new do
|
188
|
+
@pool.size.should eq 1
|
189
|
+
@pool.hold {|c| c.should_not be pg }
|
190
|
+
@pool.size.should eq 2
|
191
|
+
EM.stop if (over_count-=1).zero?
|
192
|
+
end.resume
|
193
|
+
@pool.hold {|c| c.should be pg }
|
194
|
+
@pool.size.should eq 2
|
195
|
+
end
|
196
|
+
EM.stop if (over_count-=1).zero?
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,787 @@
|
|
1
|
+
$:.unshift "lib"
|
2
|
+
gem 'eventmachine', '~> 1.0.0'
|
3
|
+
gem 'pg', ENV['EM_PG_CLIENT_TEST_PG_VERSION']
|
4
|
+
require 'date'
|
5
|
+
require 'em-synchrony'
|
6
|
+
require 'pg/em'
|
7
|
+
|
8
|
+
NOTIFY_PAYLOAD = defined?(PG.library_version) && PG.library_version >= 90000
|
9
|
+
NOTIFY_PAYLOAD_QUERY = NOTIFY_PAYLOAD ? %q[NOTIFY "ruby-em-pg-client", 'foo'] : %q[NOTIFY "ruby-em-pg-client"]
|
10
|
+
|
11
|
+
describe PG::EM::Client do
|
12
|
+
|
13
|
+
it "should be client #{PG::VERSION}" do
|
14
|
+
@client.should be_an_instance_of described_class
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should have disabled async_autoreconnect" do
|
18
|
+
@client.async_autoreconnect.should be_false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should enable async_autoreconnect" do
|
22
|
+
@client.async_autoreconnect = true
|
23
|
+
@client.async_autoreconnect.should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have same internal and external encoding" do
|
27
|
+
@client.external_encoding.should be @client.internal_encoding
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should begin transaction" do
|
31
|
+
@client.query('BEGIN TRANSACTION').should be_an_instance_of PG::Result
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should drop table `foo` if exists" do
|
35
|
+
@client.query(
|
36
|
+
'DROP TABLE IF EXISTS foo'
|
37
|
+
).should be_an_instance_of PG::Result
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should create simple table `foo`" do
|
41
|
+
@client.query(
|
42
|
+
'CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)'
|
43
|
+
).should be_an_instance_of PG::Result
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should populate foo with some data " do
|
47
|
+
results = @values.map do |(data, id)|
|
48
|
+
@client.exec_params('INSERT INTO foo (id,cdate,data) VALUES($1,$2,$3) returning cdate', [id, DateTime.now, data]) do |result|
|
49
|
+
result.should be_an_instance_of PG::Result
|
50
|
+
DateTime.parse(result[0]['cdate'])
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@cdates.replace results
|
54
|
+
results.length.should == @values.length
|
55
|
+
results.each {|r| r.should be_an_instance_of DateTime }
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should create prepared statement" do
|
59
|
+
@client.prepare('get_foo',
|
60
|
+
'SELECT * FROM foo order by id'
|
61
|
+
).should be_an_instance_of PG::Result
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should describe prepared statement" do
|
65
|
+
@client.describe_prepared('get_foo') do |result|
|
66
|
+
result.should be_an_instance_of PG::Result
|
67
|
+
result.nfields.should eq 3
|
68
|
+
result.fname(0).should eq 'id'
|
69
|
+
result.values.should be_empty
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should read foo table with prepared statement" do
|
74
|
+
ret = @client.exec_prepared('get_foo') do |result|
|
75
|
+
result.should be_an_instance_of PG::Result
|
76
|
+
result.each_with_index do |row, i|
|
77
|
+
row['id'].to_i.should == i
|
78
|
+
DateTime.parse(row['cdate']).should == @cdates[i]
|
79
|
+
row['data'].should == @values[i][0]
|
80
|
+
end
|
81
|
+
result
|
82
|
+
end
|
83
|
+
ret.should be_an_instance_of PG::Result
|
84
|
+
expect { ret.fields }.to raise_error(PG::Error, /result has been cleared/)
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should declare cursor" do
|
88
|
+
@client.query(
|
89
|
+
'DECLARE foobar SCROLL CURSOR FOR SELECT * FROM foo'
|
90
|
+
).should be_an_instance_of PG::Result
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should fetch two rows from table" do
|
94
|
+
ret = @client.query('FETCH FORWARD 2 FROM foobar') do |result|
|
95
|
+
result.should be_an_instance_of PG::Result
|
96
|
+
result.nfields.should eq 3
|
97
|
+
result.fname(0).should eq 'id'
|
98
|
+
result.values.length.should eq 2
|
99
|
+
result
|
100
|
+
end
|
101
|
+
ret.should be_an_instance_of PG::Result
|
102
|
+
expect { ret.fields }.to raise_error(PG::Error, /result has been cleared/)
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should describe cursor with describe_portal" do
|
106
|
+
@client.describe_portal('foobar') do |result|
|
107
|
+
result.should be_an_instance_of PG::Result
|
108
|
+
result.nfields.should eq 3
|
109
|
+
result.fname(0).should eq 'id'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should close cursor" do
|
114
|
+
@client.query(
|
115
|
+
'CLOSE foobar'
|
116
|
+
).should be_an_instance_of PG::Result
|
117
|
+
end
|
118
|
+
|
119
|
+
if described_class.single_row_mode?
|
120
|
+
|
121
|
+
it "should get each result in single row mode" do
|
122
|
+
@client.single_row_mode?.should be_true
|
123
|
+
@client.send_query('SELECT data, id FROM foo order by id')
|
124
|
+
@client.set_single_row_mode
|
125
|
+
@values.each do |data, id|
|
126
|
+
result = @client.get_result
|
127
|
+
result.should be_an_instance_of PG::Result
|
128
|
+
result.result_status.should eq PG::PGRES_SINGLE_TUPLE
|
129
|
+
result.check
|
130
|
+
value = result.to_a
|
131
|
+
result.clear
|
132
|
+
value.should eq [{'data' => data, 'id' => id.to_s}]
|
133
|
+
end
|
134
|
+
result = @client.get_result
|
135
|
+
result.should be_an_instance_of PG::Result
|
136
|
+
result.check
|
137
|
+
result.result_status.should eq PG::PGRES_TUPLES_OK
|
138
|
+
result.to_a.should eq []
|
139
|
+
result.clear
|
140
|
+
@client.get_result.should be_nil
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should connect to database asynchronously" do
|
146
|
+
this = :first
|
147
|
+
Encoding.default_internal = Encoding::ISO_8859_1
|
148
|
+
f = Fiber.current
|
149
|
+
Fiber.new do
|
150
|
+
begin
|
151
|
+
result = described_class.new do |conn|
|
152
|
+
this = :second
|
153
|
+
Encoding.default_internal = nil
|
154
|
+
conn.should be_an_instance_of described_class
|
155
|
+
conn.external_encoding.should_not eq(conn.internal_encoding)
|
156
|
+
conn.internal_encoding.should be Encoding::ISO_8859_1
|
157
|
+
conn.get_client_encoding.should eq "LATIN1"
|
158
|
+
conn.query('SELECT pg_database_size(current_database());') do |result|
|
159
|
+
result.should be_an_instance_of PG::Result
|
160
|
+
result[0]['pg_database_size'].to_i.should be > 0
|
161
|
+
end
|
162
|
+
conn
|
163
|
+
end
|
164
|
+
result.should be_an_instance_of described_class
|
165
|
+
result.finished?.should be_true
|
166
|
+
ensure
|
167
|
+
f.resume
|
168
|
+
end
|
169
|
+
end.resume
|
170
|
+
this.should be :first
|
171
|
+
Fiber.yield
|
172
|
+
this.should be :second
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should connect without setting incompatible encoding" do
|
176
|
+
this = :first
|
177
|
+
Encoding.default_internal = Encoding::Emacs_Mule
|
178
|
+
f = Fiber.current
|
179
|
+
Fiber.new do
|
180
|
+
begin
|
181
|
+
conn = described_class.new
|
182
|
+
this = :second
|
183
|
+
Encoding.default_internal = nil
|
184
|
+
conn.should be_an_instance_of described_class
|
185
|
+
conn.external_encoding.should be conn.internal_encoding
|
186
|
+
conn.finish
|
187
|
+
ensure
|
188
|
+
f.resume
|
189
|
+
end
|
190
|
+
end.resume
|
191
|
+
this.should be :first
|
192
|
+
Fiber.yield
|
193
|
+
this.should be :second
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should raise syntax error in misspelled multiple statement" do
|
197
|
+
expect {
|
198
|
+
@client.query('SELECT * from pg_class; SRELECT CURRENT_TIMESTAMP; SELECT 42 number')
|
199
|
+
}.to raise_error(PG::SyntaxError, /syntax error/)
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should rollback transaction" do
|
203
|
+
@client.query('ROLLBACK') do |result|
|
204
|
+
result.should be_an_instance_of PG::Result
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
it "should return only last statement" do
|
209
|
+
@client.query('SELECT * from pg_class; SELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
|
210
|
+
result.should be_an_instance_of PG::Result
|
211
|
+
result[0]['number'].should eq "42"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should timeout expire while executing query" do
|
216
|
+
@client.query_timeout.should eq 0
|
217
|
+
@client.query_timeout = 1.5
|
218
|
+
@client.query_timeout.should eq 1.5
|
219
|
+
start_time = Time.now
|
220
|
+
expect {
|
221
|
+
@client.query('SELECT pg_sleep(2)')
|
222
|
+
}.to raise_error(PG::ConnectionBad, /query timeout expired/)
|
223
|
+
(Time.now - start_time).should be < 2
|
224
|
+
@client.query_timeout = 0
|
225
|
+
@client.query_timeout.should eq 0
|
226
|
+
@client.async_command_aborted.should be_true
|
227
|
+
@client.status.should be PG::CONNECTION_BAD
|
228
|
+
@client.async_autoreconnect = false
|
229
|
+
expect {
|
230
|
+
@client.query('SELECT 1')
|
231
|
+
}.to raise_error(PG::ConnectionBad, /previous query expired/)
|
232
|
+
@client.async_autoreconnect = true
|
233
|
+
end
|
234
|
+
|
235
|
+
it "should timeout not expire while executing query with partial results" do
|
236
|
+
@client.query_timeout.should eq 0
|
237
|
+
@client.query_timeout = 1.1
|
238
|
+
@client.query_timeout.should eq 1.1
|
239
|
+
start_time = Time.now
|
240
|
+
@client.query(
|
241
|
+
'SELECT * from pg_class;' +
|
242
|
+
'SELECT pg_sleep(1);' +
|
243
|
+
'SELECT * from pg_class;' +
|
244
|
+
'SELECT pg_sleep(1);' +
|
245
|
+
'SELECT 42 number') do |result|
|
246
|
+
(Time.now - start_time).should be > 2
|
247
|
+
result.should be_an_instance_of PG::Result
|
248
|
+
result[0]['number'].should eq "42"
|
249
|
+
@client.query_timeout = 0
|
250
|
+
@client.query_timeout.should eq 0
|
251
|
+
@client.async_command_aborted.should be_false
|
252
|
+
@client.status.should be PG::CONNECTION_OK
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
it "should timeout expire while executing query with partial results" do
|
257
|
+
@client.query_timeout.should eq 0
|
258
|
+
@client.query_timeout = 1.1
|
259
|
+
@client.query_timeout.should eq 1.1
|
260
|
+
start_time = Time.now
|
261
|
+
expect {
|
262
|
+
@client.query(
|
263
|
+
'SELECT * from pg_class;' +
|
264
|
+
'SELECT pg_sleep(1);' +
|
265
|
+
'SELECT * from pg_class;' +
|
266
|
+
'SELECT pg_sleep(2);' +
|
267
|
+
'SELECT 42 number')
|
268
|
+
}.to raise_error(PG::ConnectionBad, /query timeout expired/)
|
269
|
+
(Time.now - start_time).should be > 2
|
270
|
+
@client.async_command_aborted.should be_true
|
271
|
+
@client.status.should be PG::CONNECTION_BAD
|
272
|
+
@client.query_timeout = 0
|
273
|
+
@client.query_timeout.should eq 0
|
274
|
+
end
|
275
|
+
|
276
|
+
it "should clear connection with blocking reset" do
|
277
|
+
@after_em_stop = proc do
|
278
|
+
@client.async_command_aborted.should be_true
|
279
|
+
@client.status.should be PG::CONNECTION_BAD
|
280
|
+
@client.reset
|
281
|
+
@client.async_command_aborted.should be_false
|
282
|
+
@client.status.should be PG::CONNECTION_OK
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
it "should not expire after executing erraneous query" do
|
287
|
+
@client.query_timeout.should eq 0
|
288
|
+
@client.query_timeout = 1
|
289
|
+
@client.query_timeout.should eq 1
|
290
|
+
expect {
|
291
|
+
@client.query('SELLECT 1')
|
292
|
+
}.to raise_error(PG::SyntaxError, /syntax error/)
|
293
|
+
@client.async_command_aborted.should be_false
|
294
|
+
@client.status.should be PG::CONNECTION_OK
|
295
|
+
::EM::Synchrony.sleep 1.5
|
296
|
+
@client.async_command_aborted.should be_false
|
297
|
+
@client.status.should be PG::CONNECTION_OK
|
298
|
+
@client.query_timeout = 0
|
299
|
+
@client.query_timeout.should eq 0
|
300
|
+
end
|
301
|
+
|
302
|
+
it "should get last result asynchronously" do
|
303
|
+
result = @client.get_last_result
|
304
|
+
result.should be_nil
|
305
|
+
@client.get_last_result.should be_nil
|
306
|
+
@client.send_query('SELECT 1; SELECT 2; SELECT 3')
|
307
|
+
asynchronous = false
|
308
|
+
EM.next_tick { asynchronous = true }
|
309
|
+
result = @client.get_last_result
|
310
|
+
result.should be_an_instance_of PG::Result
|
311
|
+
result.getvalue(0,0).should eq '3'
|
312
|
+
result.clear
|
313
|
+
@client.get_last_result.should be_nil
|
314
|
+
asynchronous.should be true
|
315
|
+
end
|
316
|
+
|
317
|
+
it "should get each result asynchronously" do
|
318
|
+
result = @client.get_result
|
319
|
+
result.should be_nil
|
320
|
+
@client.get_result do |result|
|
321
|
+
result.should be_nil
|
322
|
+
end.should be_nil
|
323
|
+
@client.send_query('SELECT 4,pg_sleep(0.1); SELECT 5; SELECT 6')
|
324
|
+
asynchronous = false
|
325
|
+
EM.next_tick { asynchronous = true }
|
326
|
+
%w[4 5 6].map do |value|
|
327
|
+
result = @client.get_result do |result|
|
328
|
+
result.should be_an_instance_of PG::Result
|
329
|
+
result.check
|
330
|
+
result.result_status.should eq PG::PGRES_TUPLES_OK
|
331
|
+
result.getvalue(0,0).should eq value
|
332
|
+
result
|
333
|
+
end
|
334
|
+
expect do
|
335
|
+
result.clear
|
336
|
+
end.to raise_error PG::Error, /cleared/
|
337
|
+
value
|
338
|
+
end.should eq %w[4 5 6]
|
339
|
+
@client.get_result.should be_nil
|
340
|
+
asynchronous.should be true
|
341
|
+
end
|
342
|
+
|
343
|
+
it "should receive notification while waiting for it" do
|
344
|
+
sender = described_class.new
|
345
|
+
notify_flag = false
|
346
|
+
result = sender.query('SELECT pg_backend_pid()')
|
347
|
+
result.should be_an_instance_of PG::Result
|
348
|
+
sender_pid = result.getvalue(0,0).to_i
|
349
|
+
@client.query('LISTEN "ruby-em-pg-client"').should be_an_instance_of PG::Result
|
350
|
+
f = nil
|
351
|
+
EM::Synchrony.next_tick do
|
352
|
+
begin
|
353
|
+
sender.query(NOTIFY_PAYLOAD_QUERY).should be_an_instance_of PG::Result
|
354
|
+
sender.finish
|
355
|
+
ensure
|
356
|
+
sender = nil
|
357
|
+
f.resume if f
|
358
|
+
end
|
359
|
+
end
|
360
|
+
@client.wait_for_notify do |name, pid, payload|
|
361
|
+
name.should eq 'ruby-em-pg-client'
|
362
|
+
pid.should eq sender_pid
|
363
|
+
payload.should eq (NOTIFY_PAYLOAD ? 'foo' : '')
|
364
|
+
@client.query('UNLISTEN *').should be_an_instance_of PG::Result
|
365
|
+
notify_flag = true
|
366
|
+
end.should eq 'ruby-em-pg-client'
|
367
|
+
notify_flag.should be_true
|
368
|
+
f = Fiber.current
|
369
|
+
Fiber.yield if sender
|
370
|
+
end
|
371
|
+
|
372
|
+
it "should receive previously sent notification" do
|
373
|
+
notify_flag = false
|
374
|
+
@client.query('LISTEN "ruby-em-pg-client"').should be_an_instance_of PG::Result
|
375
|
+
result = @client.query('SELECT pg_backend_pid()')
|
376
|
+
result.should be_an_instance_of PG::Result
|
377
|
+
sender_pid = result.getvalue(0,0).to_i
|
378
|
+
@client.query(%q[NOTIFY "ruby-em-pg-client"]).should be_an_instance_of PG::Result
|
379
|
+
@client.wait_for_notify(1) do |name, pid, payload|
|
380
|
+
name.should eq 'ruby-em-pg-client'
|
381
|
+
pid.should eq sender_pid
|
382
|
+
payload.should eq ''
|
383
|
+
@client.query('UNLISTEN *').should be_an_instance_of PG::Result
|
384
|
+
notify_flag = true
|
385
|
+
end.should eq 'ruby-em-pg-client'
|
386
|
+
notify_flag.should be_true
|
387
|
+
end
|
388
|
+
|
389
|
+
it "should perform queries and receive own notification while waiting for it" do
|
390
|
+
f = nil
|
391
|
+
sender_pid = nil
|
392
|
+
notify_flag = false
|
393
|
+
Fiber.new do
|
394
|
+
begin
|
395
|
+
@client.wait_for_notify do |name, pid, payload|
|
396
|
+
name.should eq 'ruby-em-pg-client'
|
397
|
+
pid.should eq sender_pid
|
398
|
+
payload.should eq (NOTIFY_PAYLOAD ? 'foo' : '')
|
399
|
+
notify_flag = true
|
400
|
+
end.should eq 'ruby-em-pg-client'
|
401
|
+
notify_flag.should be_true
|
402
|
+
@client.query('UNLISTEN *').should be_an_instance_of PG::Result
|
403
|
+
ensure
|
404
|
+
sender_pid = nil
|
405
|
+
f.resume if f
|
406
|
+
end
|
407
|
+
end.resume
|
408
|
+
result = @client.query('SELECT pg_backend_pid()')
|
409
|
+
result.should be_an_instance_of PG::Result
|
410
|
+
sender_pid = result.getvalue(0,0).to_i
|
411
|
+
@client.query('LISTEN "ruby-em-pg-client"').should be_an_instance_of PG::Result
|
412
|
+
@client.query(NOTIFY_PAYLOAD_QUERY).should be_an_instance_of PG::Result
|
413
|
+
f = Fiber.current
|
414
|
+
Fiber.yield if sender_pid
|
415
|
+
end
|
416
|
+
|
417
|
+
it "should reach timeout while waiting for notification" do
|
418
|
+
start_time = Time.now
|
419
|
+
async_flag = false
|
420
|
+
EM.next_tick do
|
421
|
+
async_flag = true
|
422
|
+
end
|
423
|
+
@client.wait_for_notify(0.2) do
|
424
|
+
raise "This block should not be called"
|
425
|
+
end.should be_nil
|
426
|
+
(Time.now - start_time).should be >= 0.2
|
427
|
+
async_flag.should be_true
|
428
|
+
end
|
429
|
+
|
430
|
+
it "should reach timeout while waiting for notification and executing query" do
|
431
|
+
start_time = Time.now
|
432
|
+
async_flag = false
|
433
|
+
EM.next_tick do
|
434
|
+
async_flag = true
|
435
|
+
end
|
436
|
+
f = Fiber.current
|
437
|
+
Fiber.new do
|
438
|
+
begin
|
439
|
+
@client.query('SELECT pg_sleep(0.5)').should be_an_instance_of PG::Result
|
440
|
+
(Time.now - start_time).should be >= 0.5
|
441
|
+
async_flag.should be_true
|
442
|
+
ensure
|
443
|
+
f.resume
|
444
|
+
end
|
445
|
+
end.resume
|
446
|
+
@client.wait_for_notify(0.1) do
|
447
|
+
raise "This block should not be called"
|
448
|
+
end.should be_nil
|
449
|
+
delta = Time.now - start_time
|
450
|
+
delta.should be >= 0.1
|
451
|
+
delta.should be < 0.5
|
452
|
+
async_flag.should be_true
|
453
|
+
Fiber.yield
|
454
|
+
end
|
455
|
+
|
456
|
+
it "should timeout expire while executing query and waiting for notification" do
|
457
|
+
@client.query_timeout.should eq 0
|
458
|
+
@client.query_timeout = 0.2
|
459
|
+
@client.query_timeout.should eq 0.2
|
460
|
+
f = Fiber.current
|
461
|
+
visit_counter = 0
|
462
|
+
start_time = Time.now
|
463
|
+
Fiber.new do
|
464
|
+
expect {
|
465
|
+
@client.wait_for_notify do
|
466
|
+
raise "This block should not be called"
|
467
|
+
end
|
468
|
+
}.to raise_error(PG::ConnectionBad, /query timeout expired/)
|
469
|
+
(Time.now - start_time).should be >= 0.2
|
470
|
+
(visit_counter+=1).should eq 2
|
471
|
+
@client.query_timeout = 0
|
472
|
+
@client.query_timeout.should eq 0
|
473
|
+
@client.async_command_aborted.should be_true
|
474
|
+
@client.status.should be PG::CONNECTION_BAD
|
475
|
+
@client.async_autoreconnect = false
|
476
|
+
expect {
|
477
|
+
@client.wait_for_notify do
|
478
|
+
raise "This block should not be called"
|
479
|
+
end
|
480
|
+
}.to raise_error(PG::ConnectionBad, /previous query expired/)
|
481
|
+
@client.async_autoreconnect = true
|
482
|
+
f.resume
|
483
|
+
end.resume
|
484
|
+
expect {
|
485
|
+
@client.query('SELECT pg_sleep(1)')
|
486
|
+
}.to raise_error(PG::ConnectionBad, /query timeout expired/)
|
487
|
+
(Time.now - start_time).should be_between(0.2, 1)
|
488
|
+
(visit_counter+=1).should eq 1
|
489
|
+
Fiber.yield
|
490
|
+
@client.reset
|
491
|
+
end
|
492
|
+
|
493
|
+
it "should fail wait_for_notify on connection reset" do
|
494
|
+
@client.status.should be PG::CONNECTION_OK
|
495
|
+
visit_counter = 0
|
496
|
+
Fiber.new do
|
497
|
+
expect {
|
498
|
+
@client.wait_for_notify do
|
499
|
+
raise "This block should not be called"
|
500
|
+
end
|
501
|
+
}.to raise_error(PG::ConnectionBad, /connection reset/)
|
502
|
+
(visit_counter+=1).should eq 1
|
503
|
+
end.resume
|
504
|
+
@client.reset
|
505
|
+
(visit_counter+=1).should eq 2
|
506
|
+
end
|
507
|
+
|
508
|
+
it "should fail wait_for_notify and slow query on connection reset" do
|
509
|
+
@client.status.should be PG::CONNECTION_OK
|
510
|
+
visit_counter = 0
|
511
|
+
Fiber.new do
|
512
|
+
expect {
|
513
|
+
@client.query('SELECT pg_sleep(10)')
|
514
|
+
}.to raise_error(PG::ConnectionBad, /connection reset/)
|
515
|
+
(visit_counter+=1).should eq 1
|
516
|
+
end.resume
|
517
|
+
Fiber.new do
|
518
|
+
expect {
|
519
|
+
@client.wait_for_notify do
|
520
|
+
raise "This block should not be called"
|
521
|
+
end
|
522
|
+
}.to raise_error(PG::ConnectionBad, /connection reset/)
|
523
|
+
(visit_counter+=1).should eq 2
|
524
|
+
end.resume
|
525
|
+
@client.reset
|
526
|
+
(visit_counter+=1).should eq 3
|
527
|
+
end
|
528
|
+
|
529
|
+
describe 'PG::EM::Client#transaction' do
|
530
|
+
|
531
|
+
before(:all) do
|
532
|
+
@client.query_timeout = 0
|
533
|
+
@client.query_timeout.should eq 0
|
534
|
+
end
|
535
|
+
|
536
|
+
it "should raise ArgumentError when there is no block" do
|
537
|
+
expect do
|
538
|
+
@client.transaction
|
539
|
+
end.to raise_error(ArgumentError, /Must supply block for PG::EM::Client#transaction/)
|
540
|
+
end
|
541
|
+
|
542
|
+
it "should commit transaction and return whatever block yields" do
|
543
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
544
|
+
@client.transaction do |pg|
|
545
|
+
pg.should be @client
|
546
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
547
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
548
|
+
@client.query(
|
549
|
+
'DROP TABLE IF EXISTS bar'
|
550
|
+
).should be_an_instance_of PG::Result
|
551
|
+
@client.query(
|
552
|
+
'CREATE TABLE bar (key integer, value varchar)'
|
553
|
+
).should be_an_instance_of PG::Result
|
554
|
+
@client.query("INSERT INTO bar (key,value) VALUES(42,'xyz') returning value") do |result|
|
555
|
+
result.should be_an_instance_of PG::Result
|
556
|
+
result[0]['value']
|
557
|
+
end
|
558
|
+
end.should eq 'xyz'
|
559
|
+
@client.query('SELECT * FROM bar') do |result|
|
560
|
+
result.should be_an_instance_of PG::Result
|
561
|
+
result[0]['key'].should eq '42'
|
562
|
+
end
|
563
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
564
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
565
|
+
end
|
566
|
+
|
567
|
+
it "should rollback transaction on error and raise that error" do
|
568
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
569
|
+
expect do
|
570
|
+
@client.transaction do |pg|
|
571
|
+
pg.should be @client
|
572
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
573
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
574
|
+
@client.query(
|
575
|
+
"INSERT INTO bar (key,value) VALUES(11,'abc')"
|
576
|
+
).should be_an_instance_of PG::Result
|
577
|
+
@client.query('SELECT * FROM bar ORDER BY key') do |result|
|
578
|
+
result.should be_an_instance_of PG::Result
|
579
|
+
result[0]['key'].should eq '11'
|
580
|
+
end
|
581
|
+
@client.query('SELECT count(*) AS count FROM bar') do |result|
|
582
|
+
result.should be_an_instance_of PG::Result
|
583
|
+
result[0]['count'].should eq '2'
|
584
|
+
end
|
585
|
+
raise "rollback"
|
586
|
+
end
|
587
|
+
end.to raise_error(RuntimeError, /rollback/)
|
588
|
+
@client.query('SELECT count(*) AS count FROM bar') do |result|
|
589
|
+
result.should be_an_instance_of PG::Result
|
590
|
+
result[0]['count'].should eq '1'
|
591
|
+
end
|
592
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
593
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
594
|
+
end
|
595
|
+
|
596
|
+
it "should allow nesting transaction and return whatever innermost block yields" do
|
597
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
598
|
+
@client.transaction do |pg|
|
599
|
+
pg.should be @client
|
600
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
601
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
602
|
+
@client.query(
|
603
|
+
"INSERT INTO bar (key,value) VALUES(100,'hundred') returning value"
|
604
|
+
).should be_an_instance_of PG::Result
|
605
|
+
@client.transaction do |pg|
|
606
|
+
pg.should be @client
|
607
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
608
|
+
@client.instance_variable_get(:@client_tran_count).should eq 2
|
609
|
+
@client.query(
|
610
|
+
"INSERT INTO bar (key,value) VALUES(1000,'thousand') returning value"
|
611
|
+
).should be_an_instance_of PG::Result
|
612
|
+
@client.transaction do |pg|
|
613
|
+
pg.should be @client
|
614
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
615
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
616
|
+
@client.query("INSERT INTO bar (key,value) VALUES(1000000,'million') returning value")
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end.tap do |result|
|
620
|
+
result.should be_an_instance_of PG::Result
|
621
|
+
result[0]['value'].should eq 'million'
|
622
|
+
end
|
623
|
+
@client.query('SELECT key,value FROM bar ORDER BY key') do |result|
|
624
|
+
result.should be_an_instance_of PG::Result
|
625
|
+
result.column_values(0).should eq ['42','100','1000','1000000']
|
626
|
+
result.column_values(1).should eq ['xyz','hundred','thousand','million']
|
627
|
+
end
|
628
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
629
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
630
|
+
end
|
631
|
+
|
632
|
+
it "should allow nesting transaction and rollback on error" do
|
633
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
634
|
+
expect do
|
635
|
+
@client.transaction do |pg|
|
636
|
+
pg.should be @client
|
637
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
638
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
639
|
+
@client.query(
|
640
|
+
"INSERT INTO bar (key,value) VALUES(200,'two hundred') returning value"
|
641
|
+
).should be_an_instance_of PG::Result
|
642
|
+
@client.transaction do |pg|
|
643
|
+
pg.should be @client
|
644
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
645
|
+
@client.instance_variable_get(:@client_tran_count).should eq 2
|
646
|
+
@client.query(
|
647
|
+
"INSERT INTO bar (key,value) VALUES(2000,'two thousands') returning value"
|
648
|
+
).should be_an_instance_of PG::Result
|
649
|
+
@client.transaction do |pg|
|
650
|
+
pg.should be @client
|
651
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
652
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
653
|
+
@client.query(
|
654
|
+
"INSERT INTO bar (key,value) VALUES(2000000,'two millions') returning value"
|
655
|
+
).should be_an_instance_of PG::Result
|
656
|
+
raise "rollback from here"
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end.to raise_error(RuntimeError, /rollback from here/)
|
661
|
+
@client.query('SELECT key,value FROM bar ORDER BY key') do |result|
|
662
|
+
result.should be_an_instance_of PG::Result
|
663
|
+
result.column_values(0).should eq ['42','100','1000','1000000']
|
664
|
+
result.column_values(1).should eq ['xyz','hundred','thousand','million']
|
665
|
+
end
|
666
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
667
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
668
|
+
end
|
669
|
+
|
670
|
+
it "should allow rollback on rescued sql error from nested transaction" do
|
671
|
+
flag = false
|
672
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
673
|
+
@client.transaction do |pg|
|
674
|
+
pg.should be @client
|
675
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
676
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
677
|
+
@client.query(
|
678
|
+
"INSERT INTO bar (key,value) VALUES(300,'three hundred') returning value"
|
679
|
+
).should be_an_instance_of PG::Result
|
680
|
+
@client.transaction do |pg|
|
681
|
+
pg.should be @client
|
682
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
683
|
+
@client.instance_variable_get(:@client_tran_count).should eq 2
|
684
|
+
@client.query(
|
685
|
+
"INSERT INTO bar (key,value) VALUES(3000,'three thousands') returning value"
|
686
|
+
).should be_an_instance_of PG::Result
|
687
|
+
@client.transaction do |pg|
|
688
|
+
pg.should be @client
|
689
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
690
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
691
|
+
expect {
|
692
|
+
@client.query('SRELECT CURRENT_TIMESTAMP')
|
693
|
+
}.to raise_error(PG::SyntaxError, /syntax error/)
|
694
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
695
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
696
|
+
expect {
|
697
|
+
@client.query('SELECT CURRENT_TIMESTAMP')
|
698
|
+
}.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
699
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
700
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
701
|
+
expect do
|
702
|
+
@client.transaction { 'foo' }
|
703
|
+
end.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
704
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
705
|
+
@client.instance_variable_get(:@client_tran_count).should eq 3
|
706
|
+
flag = :was_here
|
707
|
+
end
|
708
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
709
|
+
@client.instance_variable_get(:@client_tran_count).should eq 2
|
710
|
+
expect {
|
711
|
+
@client.query('SELECT CURRENT_TIMESTAMP')
|
712
|
+
}.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
713
|
+
expect do
|
714
|
+
@client.transaction { 'foo' }
|
715
|
+
end.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
716
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
717
|
+
@client.instance_variable_get(:@client_tran_count).should eq 2
|
718
|
+
end
|
719
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
720
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
721
|
+
expect {
|
722
|
+
@client.query('SELECT CURRENT_TIMESTAMP')
|
723
|
+
}.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
724
|
+
expect do
|
725
|
+
@client.transaction { 'foo' }
|
726
|
+
end.to raise_error(PG::InFailedSqlTransaction, /transaction is aborted/)
|
727
|
+
@client.transaction_status.should be PG::PQTRANS_INERROR
|
728
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
729
|
+
end
|
730
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
731
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
732
|
+
@client.transaction { 'foo' }.should eq 'foo'
|
733
|
+
@client.query('SELECT key,value FROM bar ORDER BY key') do |result|
|
734
|
+
result.should be_an_instance_of PG::Result
|
735
|
+
result.column_values(0).should eq ['42','100','1000','1000000']
|
736
|
+
result.column_values(1).should eq ['xyz','hundred','thousand','million']
|
737
|
+
end
|
738
|
+
flag.should be :was_here
|
739
|
+
end
|
740
|
+
|
741
|
+
it "should detect premature transaction state change" do
|
742
|
+
flag = false
|
743
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
744
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
745
|
+
@client.transaction do |pg|
|
746
|
+
pg.should be @client
|
747
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
748
|
+
@client.query('ROLLBACK')
|
749
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
750
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
751
|
+
@client.transaction do
|
752
|
+
@client.transaction_status.should be PG::PQTRANS_INTRANS
|
753
|
+
@client.instance_variable_get(:@client_tran_count).should eq 1
|
754
|
+
'foo'
|
755
|
+
end.should eq 'foo'
|
756
|
+
@client.transaction_status.should be PG::PQTRANS_IDLE
|
757
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
758
|
+
end
|
759
|
+
@client.instance_variable_get(:@client_tran_count).should eq 0
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
around(:each) do |testcase|
|
764
|
+
@after_em_stop = nil
|
765
|
+
EM.synchrony do
|
766
|
+
begin
|
767
|
+
testcase.call
|
768
|
+
ensure
|
769
|
+
EM.stop
|
770
|
+
end
|
771
|
+
end
|
772
|
+
@after_em_stop.call if @after_em_stop
|
773
|
+
end
|
774
|
+
|
775
|
+
before(:all) do
|
776
|
+
@cdates = []
|
777
|
+
@values = Array(('AA'..'ZZ').each_with_index)
|
778
|
+
ENV['PGCLIENTENCODING'] = nil
|
779
|
+
Encoding.default_internal = nil
|
780
|
+
@client = described_class.new
|
781
|
+
end
|
782
|
+
|
783
|
+
after(:all) do
|
784
|
+
@client.close
|
785
|
+
end
|
786
|
+
|
787
|
+
end
|