em-pg-client 0.2.0.pre.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/BENCHMARKS.rdoc +1 -1
- data/HISTORY.rdoc +10 -0
- data/README.rdoc +189 -52
- data/em-pg-client.gemspec +2 -2
- data/lib/em-synchrony/pg.rb +32 -5
- data/lib/pg/em.rb +140 -70
- data/spec/em_client_autoreconnect.rb +4 -2
- data/spec/em_client_common.rb +191 -65
- data/spec/em_devel_client.rb +3 -0
- data/spec/em_release_client.rb +3 -0
- data/spec/em_synchrony_client.rb +130 -21
- data/spec/em_synchrony_client_autoreconnect.rb +4 -2
- metadata +82 -61
data/lib/pg/em.rb
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
require 'pg'
|
3
|
+
rescue LoadError => error
|
4
|
+
raise 'Missing pg driver: gem install pg'
|
5
|
+
end
|
6
|
+
unless defined? EventMachine
|
7
|
+
begin
|
8
|
+
require 'eventmachine'
|
9
|
+
rescue LoadError => error
|
10
|
+
raise 'Missing EventMachine: gem install eventmachine'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
2
14
|
module PG
|
3
15
|
|
4
16
|
class Result
|
@@ -61,96 +73,108 @@ module PG
|
|
61
73
|
# Licence:: MIT License
|
62
74
|
#
|
63
75
|
#
|
64
|
-
# PG::EM::Client is a wrapper for PG::Connection
|
76
|
+
# PG::EM::Client is a wrapper for PG::Connection[http://deveiate.org/code/pg/PG/Connection.html]
|
77
|
+
# which (re)defines methods:
|
65
78
|
#
|
66
79
|
# - +async_exec+ (alias: +async_query+)
|
67
80
|
# - +async_prepare+
|
68
81
|
# - +async_exec_prepared+
|
82
|
+
# - +async_describe_prepared+
|
83
|
+
# - +async_describe_portal+
|
84
|
+
#
|
85
|
+
# which are suitable to run in EM event loop (they return +Deferrable+)
|
69
86
|
#
|
70
87
|
# and following:
|
71
88
|
#
|
72
89
|
# - +exec+ (alias: +query+)
|
73
|
-
# - +exec_prepared+
|
74
90
|
# - +prepare+
|
91
|
+
# - +exec_prepared+
|
92
|
+
# - +describe_prepared+
|
93
|
+
# - +describe_portal+
|
75
94
|
#
|
76
|
-
#
|
95
|
+
# autodetecting if EventMachine is running and using the appropriate
|
77
96
|
# (async or sync) method version.
|
78
97
|
#
|
79
98
|
# Additionally to the above, there are asynchronous methods defined for
|
80
|
-
# establishing connection and
|
99
|
+
# establishing connection and re-connecting:
|
81
100
|
#
|
82
101
|
# - +Client.async_connect+
|
83
102
|
# - +async_reset+
|
84
103
|
#
|
85
|
-
# They are async equivalents of
|
86
|
-
# by PG::Connection as +new+, +open+, +setdb+, +setdblogin+) and
|
104
|
+
# They are async equivalents of PG::Connection.connect (which is also
|
105
|
+
# aliased by PG::Connection as +new+, +open+, +setdb+, +setdblogin+) and
|
106
|
+
# +reset+.
|
87
107
|
#
|
88
|
-
#
|
89
|
-
# You won't even notice that
|
90
|
-
#
|
108
|
+
# When #async_autoreconnect is +true+, async methods might try to
|
109
|
+
# re-connect after a connection error. You won't even notice that
|
110
|
+
# (except for warning message from PG).
|
111
|
+
# If you want to detect such event use #on_autoreconnect property.
|
91
112
|
#
|
92
|
-
# To
|
93
|
-
# client.async_autoreconnect =
|
113
|
+
# To enable auto-reconnecting set:
|
114
|
+
# client.async_autoreconnect = true
|
94
115
|
#
|
95
116
|
# or pass as new() hash argument:
|
96
|
-
#
|
117
|
+
# ::new database: 'bar', async_autoreconnect: true
|
97
118
|
#
|
98
|
-
# Otherwise nothing changes in PG::
|
99
|
-
# See PG::
|
119
|
+
# Otherwise nothing changes in PG::Connection API.
|
120
|
+
# See PG::Connection[http://deveiate.org/code/pg/PG/Connection.html] docs
|
121
|
+
# for arguments to above methods.
|
100
122
|
#
|
101
123
|
# *Warning:*
|
102
124
|
#
|
103
|
-
# +
|
104
|
-
# the *same* connection.
|
105
|
-
# If you are using connection pool, make sure to acquire single connection first.
|
125
|
+
# +describe_prepared+ and +exec_prepared+ after
|
126
|
+
# +prepare+ should only be invoked on the *same* connection.
|
127
|
+
# If you are using a connection pool, make sure to acquire single connection first.
|
106
128
|
#
|
107
129
|
class Client < PG::Connection
|
108
130
|
|
109
131
|
|
110
132
|
# Connection timeout. Changing this property only affects
|
111
|
-
#
|
112
|
-
# However if passed as initialization option also affects blocking
|
113
|
-
#
|
133
|
+
# ::async_connect and #async_reset.
|
134
|
+
# However if passed as initialization option, it also affects blocking
|
135
|
+
# ::new and #reset.
|
114
136
|
attr_accessor :connect_timeout
|
115
137
|
|
116
|
-
# (EXPERIMENTAL)
|
117
138
|
# Aborts async command processing if waiting for response from server
|
118
139
|
# exceedes +query_timeout+ seconds. This does not apply to
|
119
|
-
#
|
120
|
-
# +connect_timeout+ instead.
|
140
|
+
# ::async_connect and #async_reset. For them
|
141
|
+
# use +connect_timeout+ instead.
|
121
142
|
#
|
122
143
|
# To enable it set to seconds (> 0). To disable: set to 0.
|
123
144
|
# You can also specify this as initialization option.
|
124
145
|
attr_accessor :query_timeout
|
125
146
|
|
126
147
|
# Enable/disable auto-reconnect feature (+true+/+false+).
|
127
|
-
#
|
148
|
+
# Defaults to +false+. However it is implicitly set to +true+
|
149
|
+
# if #on_autoreconnect is specified as initialization option.
|
150
|
+
# Changing #on_autoreconnect with accessor method doesn't change
|
151
|
+
# #async_autoreconnect.
|
128
152
|
attr_accessor :async_autoreconnect
|
129
153
|
|
130
154
|
# +on_autoreconnect+ is a user defined Proc that is called after a connection
|
131
155
|
# with the server has been re-established.
|
132
|
-
# It's invoked with
|
133
|
-
# +exception+ that caused the reconnecting process
|
156
|
+
# It's invoked with two arguments. First one is the +connection+.
|
157
|
+
# The second is the original +exception+ that caused the reconnecting process.
|
134
158
|
#
|
135
|
-
# Certain rules should apply to on_autoreconnect proc:
|
159
|
+
# Certain rules should apply to #on_autoreconnect proc:
|
136
160
|
#
|
137
|
-
# - If proc returns +false+ (explicitly, +nil+ is ignored)
|
138
|
-
# the original +exception+ is passed to
|
139
|
-
# query command is not invoked at all.
|
140
|
-
# - If return value is an instance of exception it is passed to
|
141
|
-
#
|
142
|
-
# - If return value responds to +callback+ and +errback+ methods
|
143
|
-
#
|
144
|
-
#
|
145
|
-
# called immediately after on_autoreconnect proc is executed.
|
161
|
+
# - If proc returns +false+ (explicitly, +nil+ is ignored),
|
162
|
+
# the original +exception+ is passed to Defferable's +errback+ and
|
163
|
+
# the send query command is not invoked at all.
|
164
|
+
# - If return value is an instance of exception, it is passed to
|
165
|
+
# Defferable's +errback+ and the send query command is not invoked at all.
|
166
|
+
# - If return value responds to +callback+ and +errback+ methods,
|
167
|
+
# the send query command will be bound to value's success +callback+
|
168
|
+
# and the original Defferable's +errback+ or value's +errback+.
|
146
169
|
# - Other return values are ignored and the send query command is called
|
147
|
-
# immediately after on_autoreconnect proc is executed.
|
170
|
+
# immediately after #on_autoreconnect proc is executed.
|
148
171
|
#
|
149
|
-
# You may pass this proc as +:on_autoreconnect+ option to
|
172
|
+
# You may pass this proc as +:on_autoreconnect+ option to ::new.
|
150
173
|
#
|
151
174
|
# Example:
|
152
175
|
# pg.on_autoreconnect = proc do |conn, ex|
|
153
|
-
# conn.prepare("
|
176
|
+
# conn.prepare("species_by_name",
|
177
|
+
# "select id, name from animals where species=$1 order by name")
|
154
178
|
# end
|
155
179
|
#
|
156
180
|
attr_accessor :on_autoreconnect
|
@@ -171,13 +195,12 @@ module PG
|
|
171
195
|
@timer = nil
|
172
196
|
end
|
173
197
|
end
|
174
|
-
|
198
|
+
|
175
199
|
def setup_timer(timeout, adjustment = 0)
|
176
200
|
@timer = ::EM::Timer.new(timeout - adjustment) do
|
177
201
|
if (last_interval = Time.now - @notify_timestamp) >= timeout
|
178
202
|
detach
|
179
203
|
@client.async_command_aborted = true
|
180
|
-
IO.for_fd(@client.socket).close # break connection now (hack)
|
181
204
|
@deferrable.protect do
|
182
205
|
raise PG::Error, "query timeout expired (async)"
|
183
206
|
end
|
@@ -190,17 +213,17 @@ module PG
|
|
190
213
|
def notify_readable
|
191
214
|
result = false
|
192
215
|
@client.consume_input
|
193
|
-
until @client.is_busy
|
194
|
-
|
216
|
+
until @client.is_busy
|
217
|
+
if (single_result = @client.get_result).nil?
|
218
|
+
result = @last_result
|
219
|
+
Result.check_result(@client, result)
|
220
|
+
detach
|
221
|
+
@timer.cancel if @timer
|
222
|
+
break
|
223
|
+
end
|
195
224
|
@last_result.clear if @last_result
|
196
225
|
@last_result = single_result
|
197
226
|
end
|
198
|
-
unless @client.is_busy
|
199
|
-
result = @last_result
|
200
|
-
Result.check_result(@client, result)
|
201
|
-
detach
|
202
|
-
@timer.cancel if @timer
|
203
|
-
end
|
204
227
|
rescue Exception => e
|
205
228
|
detach
|
206
229
|
@timer.cancel if @timer
|
@@ -225,14 +248,22 @@ module PG
|
|
225
248
|
@poll_method = :"#{poll_method}_poll"
|
226
249
|
if (timeout = client.connect_timeout) > 0
|
227
250
|
@timer = ::EM::Timer.new(timeout) do
|
228
|
-
|
229
|
-
|
230
|
-
|
251
|
+
begin
|
252
|
+
detach
|
253
|
+
@deferrable.protect do
|
254
|
+
raise PG::Error, "timeout expired (async)"
|
255
|
+
end
|
256
|
+
ensure
|
257
|
+
@client.finish unless reconnecting?
|
231
258
|
end
|
232
259
|
end
|
233
260
|
end
|
234
261
|
end
|
235
262
|
|
263
|
+
def reconnecting?
|
264
|
+
@poll_method == :reset_poll
|
265
|
+
end
|
266
|
+
|
236
267
|
def notify_writable
|
237
268
|
poll_connection_and_check
|
238
269
|
end
|
@@ -254,7 +285,19 @@ module PG
|
|
254
285
|
detach
|
255
286
|
success = @deferrable.protect_and_succeed do
|
256
287
|
unless @client.status == PG::CONNECTION_OK
|
257
|
-
|
288
|
+
begin
|
289
|
+
raise PG::Error, @client.error_message
|
290
|
+
ensure
|
291
|
+
@client.finish unless reconnecting?
|
292
|
+
end
|
293
|
+
end
|
294
|
+
# mimic blocking connect behavior
|
295
|
+
unless Encoding.default_internal.nil? || reconnecting?
|
296
|
+
begin
|
297
|
+
@client.internal_encoding = Encoding.default_internal
|
298
|
+
rescue EncodingError
|
299
|
+
warn "warning: Failed to set the default_internal encoding to #{Encoding.default_internal}: '#{@client.error_message}'"
|
300
|
+
end
|
258
301
|
end
|
259
302
|
@client
|
260
303
|
end
|
@@ -264,7 +307,7 @@ module PG
|
|
264
307
|
|
265
308
|
def self.parse_async_args(*args)
|
266
309
|
async_args = {
|
267
|
-
:@async_autoreconnect =>
|
310
|
+
:@async_autoreconnect => nil,
|
268
311
|
:@connect_timeout => 0,
|
269
312
|
:@query_timeout => 0,
|
270
313
|
:@on_autoreconnect => nil,
|
@@ -279,7 +322,10 @@ module PG
|
|
279
322
|
when 'on_reconnect'
|
280
323
|
raise ArgumentError.new("on_reconnect is no longer supported, use on_autoreconnect")
|
281
324
|
when 'on_autoreconnect'
|
282
|
-
|
325
|
+
if value.respond_to? :call
|
326
|
+
async_args[:@on_autoreconnect] = value
|
327
|
+
async_args[:@async_autoreconnect] = true if async_args[:@async_autoreconnect].nil?
|
328
|
+
end
|
283
329
|
true
|
284
330
|
when 'connect_timeout'
|
285
331
|
async_args[:@connect_timeout] = value.to_f
|
@@ -290,16 +336,21 @@ module PG
|
|
290
336
|
end
|
291
337
|
end
|
292
338
|
end
|
339
|
+
async_args[:@async_autoreconnect] = false if async_args[:@async_autoreconnect].nil?
|
293
340
|
async_args
|
294
341
|
end
|
295
342
|
|
296
|
-
#
|
297
|
-
#
|
343
|
+
# Attempts to establish the connection asynchronously.
|
344
|
+
# For args see PG::Connection.new[http://deveiate.org/code/pg/PG/Connection.html#method-c-new].
|
345
|
+
# Returns +Deferrable+. Use its +callback+ to obtain newly created and
|
346
|
+
# already connected PG::EM::Client object.
|
347
|
+
# If block is provided, it's bound to +callback+ and +errback+ of returned
|
348
|
+
# +Deferrable+.
|
298
349
|
#
|
299
|
-
#
|
300
|
-
#
|
301
|
-
#
|
302
|
-
# +
|
350
|
+
# Special PG::EM::Client options (e.g.: +async_autoreconnect+) must be provided
|
351
|
+
# as +connection_hash+ argument variant. They will be ignored in +connection_string+.
|
352
|
+
#
|
353
|
+
# +client_encoding+ *will* be set for you according to Encoding.default_internal.
|
303
354
|
def self.async_connect(*args, &blk)
|
304
355
|
df = PG::EM::FeaturedDeferrable.new(&blk)
|
305
356
|
async_args = parse_async_args(*args)
|
@@ -311,12 +362,12 @@ module PG
|
|
311
362
|
df
|
312
363
|
end
|
313
364
|
|
314
|
-
#
|
315
|
-
# There are no arguments.
|
365
|
+
# Attempts to reset the connection asynchronously.
|
366
|
+
# There are no arguments, except block argument.
|
316
367
|
#
|
317
|
-
# Returns +
|
318
|
-
# If block is provided it's bound to +callback+ and +errback+ of returned
|
319
|
-
# +
|
368
|
+
# Returns +Deferrable+. Use it's +callback+ to handle success.
|
369
|
+
# If block is provided, it's bound to +callback+ and +errback+ of returned
|
370
|
+
# +Deferrable+.
|
320
371
|
def async_reset(&blk)
|
321
372
|
@async_command_aborted = false
|
322
373
|
df = PG::EM::FeaturedDeferrable.new(&blk)
|
@@ -327,11 +378,28 @@ module PG
|
|
327
378
|
df
|
328
379
|
end
|
329
380
|
|
381
|
+
# Uncheck #async_command_aborted on blocking reset.
|
382
|
+
def reset
|
383
|
+
@async_command_aborted = false
|
384
|
+
super
|
385
|
+
end
|
386
|
+
|
387
|
+
# Creates new instance of PG::EM::Client and attempts to establish connection.
|
388
|
+
# See PG::Connection.new[http://deveiate.org/code/pg/PG/Connection.html#method-c-new].
|
389
|
+
#
|
390
|
+
# Special PG::EM::Client options (e.g.: +async_autoreconnect+) must be provided
|
391
|
+
# as +connection_hash+ argument variant. They will be ignored in +connection_string+.
|
392
|
+
#
|
393
|
+
# +em-synchrony+ version *will* do set +client_encoding+ for you according to
|
394
|
+
# Encoding.default_internal.
|
330
395
|
def initialize(*args)
|
331
396
|
Client.parse_async_args(*args).each {|k, v| self.instance_variable_set(k, v) }
|
332
397
|
super(*args)
|
333
398
|
end
|
334
399
|
|
400
|
+
# Return +CONNECTION_BAD+ for connections with +async_command_aborted+
|
401
|
+
# flag set by expired query timeout. Otherwise return whatever
|
402
|
+
# PG::Connection#status[http://deveiate.org/code/pg/PG/Connection.html#method-i-status] provides.
|
335
403
|
def status
|
336
404
|
if @async_command_aborted
|
337
405
|
PG::CONNECTION_BAD
|
@@ -368,9 +436,11 @@ module PG
|
|
368
436
|
end
|
369
437
|
|
370
438
|
%w(
|
371
|
-
exec
|
372
|
-
prepare
|
373
|
-
exec_prepared
|
439
|
+
exec send_query
|
440
|
+
prepare send_prepare
|
441
|
+
exec_prepared send_query_prepared
|
442
|
+
describe_prepared send_describe_prepared
|
443
|
+
describe_portal send_describe_portal
|
374
444
|
).each_slice(2) do |name, send_name|
|
375
445
|
|
376
446
|
class_eval <<-EOD
|
@@ -50,7 +50,7 @@ describe 'em-pg default autoreconnect' do
|
|
50
50
|
EM.stop
|
51
51
|
end.should be_a_kind_of ::EM::DefaultDeferrable
|
52
52
|
end
|
53
|
-
@client = PG::EM::Client.new
|
53
|
+
@client = PG::EM::Client.new(async_autoreconnect: true)
|
54
54
|
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
55
55
|
end
|
56
56
|
end
|
@@ -104,8 +104,10 @@ describe 'em-pg with autoreconnect disabled' do
|
|
104
104
|
end
|
105
105
|
|
106
106
|
it "should get database size using query after async manual connection reset" do
|
107
|
+
@client.status.should be PG::CONNECTION_BAD
|
107
108
|
@client.async_reset do |conn|
|
108
109
|
conn.should be @client
|
110
|
+
@client.status.should be PG::CONNECTION_OK
|
109
111
|
@tested_proc.call
|
110
112
|
end.should be_a_kind_of ::EM::DefaultDeferrable
|
111
113
|
end
|
@@ -118,7 +120,7 @@ describe 'em-pg with autoreconnect disabled' do
|
|
118
120
|
EM.stop
|
119
121
|
end.should be_a_kind_of ::EM::DefaultDeferrable
|
120
122
|
end
|
121
|
-
@client = PG::EM::Client.new
|
123
|
+
@client = PG::EM::Client.new
|
122
124
|
@client.set_notice_processor {|msg| puts "warning from pgsql: #{msg.to_s.chomp.inspect}"}
|
123
125
|
end
|
124
126
|
end
|
data/spec/em_client_common.rb
CHANGED
@@ -1,89 +1,216 @@
|
|
1
|
+
module PGSpecMacros
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
def pg_exec_and_check(client, method, *args, &additional_checks)
|
7
|
+
client.__send__(method, *args) do |result|
|
8
|
+
result.should be_an_instance_of PG::Result
|
9
|
+
additional_checks.call(result) if additional_checks
|
10
|
+
EM.stop
|
11
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
12
|
+
end
|
13
|
+
|
14
|
+
def pg_exec_and_check_with_error(client, err_message, method, *args, &additional_checks)
|
15
|
+
client.__send__(method, *args) do |exception|
|
16
|
+
exception.should be_an_instance_of PG::Error
|
17
|
+
exception.to_s.should include err_message if err_message
|
18
|
+
additional_checks.call(exception) if additional_checks
|
19
|
+
EM.stop
|
20
|
+
end.should be_a_kind_of ::EM::DefaultDeferrable
|
21
|
+
end
|
22
|
+
|
23
|
+
def ensure_em_stop
|
24
|
+
yield
|
25
|
+
EM.stop
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
def it_should_execute(text, method, *args)
|
30
|
+
it "should #{text}" do
|
31
|
+
pg_exec_and_check(@client, method, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def it_should_execute_with_error(text, err_message, method, *args)
|
36
|
+
it "should #{text}" do
|
37
|
+
pg_exec_and_check_with_error(@client, err_message, method, *args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def it_should_rollback
|
42
|
+
it_should_execute("rollback transaction", :query, 'ROLLBACK')
|
43
|
+
end
|
44
|
+
|
45
|
+
def it_should_begin
|
46
|
+
it_should_execute("begin transaction", :query, 'BEGIN TRANSACTION')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
1
51
|
shared_context 'em-pg common before' do
|
2
52
|
|
3
53
|
around(:each) do |testcase|
|
4
|
-
EM.run
|
54
|
+
EM.run do
|
55
|
+
EM.stop if testcase.call.is_a? Exception
|
56
|
+
end
|
5
57
|
end
|
6
58
|
|
7
59
|
before(:all) do
|
8
60
|
@cdates = []
|
9
61
|
@values = Array(('AA'..'ZZ').each_with_index)
|
62
|
+
ENV['PGCLIENTENCODING'] = nil
|
63
|
+
Encoding.default_internal = nil
|
10
64
|
@client = described_class.new
|
11
|
-
@client.query 'BEGIN TRANSACTION'
|
12
65
|
end
|
13
66
|
|
14
67
|
after(:all) do
|
15
|
-
@client.query 'ROLLBACK TRANSACTION'
|
16
68
|
@client.close
|
17
69
|
end
|
18
70
|
|
19
71
|
it "should be a client" do
|
20
|
-
|
21
|
-
|
72
|
+
ensure_em_stop do
|
73
|
+
@client.should be_an_instance_of described_class
|
74
|
+
end
|
22
75
|
end
|
23
76
|
|
24
|
-
it "should
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
77
|
+
it "should have disabled async_autoreconnect" do
|
78
|
+
ensure_em_stop do
|
79
|
+
@client.async_autoreconnect.should be_false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should enable async_autoreconnect" do
|
84
|
+
ensure_em_stop do
|
85
|
+
@client.async_autoreconnect = true
|
86
|
+
@client.async_autoreconnect.should be_true
|
87
|
+
end
|
32
88
|
end
|
33
89
|
|
90
|
+
it "should set async_autoreconnect according to on_autoreconnect" do
|
91
|
+
ensure_em_stop do
|
92
|
+
on_autoreconnect = proc {|c, e| false }
|
93
|
+
async_args = described_class.parse_async_args
|
94
|
+
async_args.should be_an_instance_of Hash
|
95
|
+
async_args[:@on_autoreconnect].should be_nil
|
96
|
+
async_args[:@async_autoreconnect].should be_false
|
97
|
+
async_args = described_class.parse_async_args(on_autoreconnect: on_autoreconnect)
|
98
|
+
async_args.should be_an_instance_of Hash
|
99
|
+
async_args[:@on_autoreconnect].should be on_autoreconnect
|
100
|
+
async_args[:@async_autoreconnect].should be_true
|
101
|
+
async_args = described_class.parse_async_args(async_autoreconnect: false,
|
102
|
+
on_autoreconnect: on_autoreconnect)
|
103
|
+
async_args.should be_an_instance_of Hash
|
104
|
+
async_args[:@on_autoreconnect].should be on_autoreconnect
|
105
|
+
async_args[:@async_autoreconnect].should be_false
|
106
|
+
async_args = described_class.parse_async_args(on_autoreconnect: on_autoreconnect,
|
107
|
+
async_autoreconnect: false)
|
108
|
+
async_args.should be_an_instance_of Hash
|
109
|
+
async_args[:@on_autoreconnect].should be on_autoreconnect
|
110
|
+
async_args[:@async_autoreconnect].should be_false
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should have same internal and external encoding" do
|
115
|
+
ensure_em_stop do
|
116
|
+
@client.external_encoding.should be @client.internal_encoding
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it_should_begin
|
121
|
+
|
122
|
+
it_should_execute("drop table `foo` if exists",
|
123
|
+
:query, 'DROP TABLE IF EXISTS foo')
|
124
|
+
|
125
|
+
it_should_execute("create simple table `foo`",
|
126
|
+
:query, 'CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)')
|
127
|
+
|
34
128
|
end
|
35
129
|
|
36
130
|
shared_context 'em-pg common after' do
|
37
131
|
|
132
|
+
it_should_execute("create prepared statement",
|
133
|
+
:prepare, 'get_foo', 'SELECT * FROM foo order by id')
|
134
|
+
|
135
|
+
it "should describe prepared statement" do
|
136
|
+
pg_exec_and_check(@client, :describe_prepared, 'get_foo') do |result|
|
137
|
+
result.nfields.should eq 3
|
138
|
+
result.fname(0).should eq 'id'
|
139
|
+
result.values.should be_empty
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
38
143
|
it "should read foo table with prepared statement" do
|
39
|
-
@client
|
40
|
-
result.
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
144
|
+
pg_exec_and_check(@client, :exec_prepared, 'get_foo') do |result|
|
145
|
+
result.each_with_index do |row, i|
|
146
|
+
row['id'].to_i.should == i
|
147
|
+
DateTime.parse(row['cdate']).should == @cdates[i]
|
148
|
+
row['data'].should == @values[i][0]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it_should_execute("declare cursor",
|
154
|
+
:query, 'DECLARE foobar SCROLL CURSOR FOR SELECT * FROM foo')
|
155
|
+
|
156
|
+
it "should fetch two rows from table" do
|
157
|
+
pg_exec_and_check(@client, :query, 'FETCH FORWARD 2 FROM foobar') do |result|
|
158
|
+
result.nfields.should eq 3
|
159
|
+
result.fname(0).should eq 'id'
|
160
|
+
result.values.length.should eq 2
|
161
|
+
end
|
51
162
|
end
|
163
|
+
|
164
|
+
it "should describe cursor with describe_portal" do
|
165
|
+
pg_exec_and_check(@client, :describe_portal, 'foobar') do |result|
|
166
|
+
result.nfields.should eq 3
|
167
|
+
result.fname(0).should eq 'id'
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
it_should_execute("close cursor", :query, 'CLOSE foobar')
|
52
172
|
|
53
173
|
it "should connect to database asynchronously" do
|
54
174
|
this = :first
|
175
|
+
Encoding.default_internal = Encoding::ISO_8859_1
|
55
176
|
described_class.async_connect do |conn|
|
56
177
|
this = :second
|
178
|
+
Encoding.default_internal = nil
|
57
179
|
conn.should be_an_instance_of described_class
|
58
|
-
conn.
|
59
|
-
|
180
|
+
conn.external_encoding.should_not eq(conn.internal_encoding)
|
181
|
+
conn.internal_encoding.should be Encoding::ISO_8859_1
|
182
|
+
conn.get_client_encoding.should eq "LATIN1"
|
183
|
+
pg_exec_and_check(conn, :query, 'SELECT pg_database_size(current_database());') do |result|
|
60
184
|
result[0]['pg_database_size'].to_i.should be > 0
|
61
|
-
|
62
|
-
end.should be_a_kind_of ::EM::DefaultDeferrable
|
185
|
+
end
|
63
186
|
end.should be_a_kind_of ::EM::DefaultDeferrable
|
64
187
|
this.should be :first
|
65
188
|
end
|
66
189
|
|
67
|
-
it "should
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
end.should be_a_kind_of ::EM::DefaultDeferrable
|
77
|
-
end.should be_a_kind_of ::EM::DefaultDeferrable
|
190
|
+
it "should connect without setting incompatible encoding" do
|
191
|
+
this = :first
|
192
|
+
Encoding.default_internal = Encoding::Emacs_Mule
|
193
|
+
described_class.async_connect do |conn|
|
194
|
+
this = :second
|
195
|
+
Encoding.default_internal = nil
|
196
|
+
conn.should be_an_instance_of described_class
|
197
|
+
conn.external_encoding.should be conn.internal_encoding
|
198
|
+
EM.stop
|
78
199
|
end.should be_a_kind_of ::EM::DefaultDeferrable
|
200
|
+
this.should be :first
|
79
201
|
end
|
80
202
|
|
203
|
+
it_should_execute_with_error("raise syntax error in misspelled multiple statement",
|
204
|
+
"syntax error",
|
205
|
+
:query, 'SELECT * from pg_class; SRELECT CURRENT_TIMESTAMP; SELECT 42 number')
|
206
|
+
|
207
|
+
it_should_rollback
|
208
|
+
|
81
209
|
it "should return only last statement" do
|
82
|
-
@client
|
83
|
-
|
210
|
+
pg_exec_and_check(@client, :query,
|
211
|
+
'SELECT * from pg_class; SELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
|
84
212
|
result[0]['number'].should eq "42"
|
85
|
-
|
86
|
-
end.should be_a_kind_of ::EM::DefaultDeferrable
|
213
|
+
end
|
87
214
|
end
|
88
215
|
|
89
216
|
it "should timeout expire while executing query" do
|
@@ -91,18 +218,13 @@ shared_context 'em-pg common after' do
|
|
91
218
|
@client.query_timeout = 1.5
|
92
219
|
@client.query_timeout.should eq 1.5
|
93
220
|
start_time = Time.now
|
94
|
-
@client
|
221
|
+
pg_exec_and_check_with_error(@client, "query timeout expired", :query, 'SELECT pg_sleep(2)') do
|
95
222
|
(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
223
|
@client.async_command_aborted.should be_true
|
224
|
+
@client.status.should be PG::CONNECTION_BAD
|
99
225
|
@client.query_timeout = 0
|
100
226
|
@client.query_timeout.should eq 0
|
101
|
-
|
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
|
227
|
+
end
|
106
228
|
end
|
107
229
|
|
108
230
|
it "should timeout not expire while executing query with partial results" do
|
@@ -110,20 +232,19 @@ shared_context 'em-pg common after' do
|
|
110
232
|
@client.query_timeout = 1.1
|
111
233
|
@client.query_timeout.should eq 1.1
|
112
234
|
start_time = Time.now
|
113
|
-
@client
|
235
|
+
pg_exec_and_check(@client, :query,
|
114
236
|
'SELECT * from pg_class;' +
|
115
237
|
'SELECT pg_sleep(1);' +
|
116
238
|
'SELECT * from pg_class;' +
|
117
239
|
'SELECT pg_sleep(1);' +
|
118
240
|
'SELECT 42 number') do |result|
|
119
241
|
(Time.now - start_time).should be > 2
|
120
|
-
result.should be_an_instance_of PG::Result
|
121
242
|
result[0]['number'].should eq "42"
|
122
243
|
@client.query_timeout = 0
|
123
244
|
@client.query_timeout.should eq 0
|
124
245
|
@client.async_command_aborted.should be_false
|
125
|
-
|
126
|
-
end
|
246
|
+
@client.status.should be PG::CONNECTION_OK
|
247
|
+
end
|
127
248
|
end
|
128
249
|
|
129
250
|
it "should timeout expire while executing query with partial results" do
|
@@ -131,22 +252,27 @@ shared_context 'em-pg common after' do
|
|
131
252
|
@client.query_timeout = 1.1
|
132
253
|
@client.query_timeout.should eq 1.1
|
133
254
|
start_time = Time.now
|
134
|
-
@client
|
255
|
+
pg_exec_and_check_with_error(@client, "query timeout expired", :query,
|
135
256
|
'SELECT * from pg_class;' +
|
136
257
|
'SELECT pg_sleep(1);' +
|
137
258
|
'SELECT * from pg_class;' +
|
138
259
|
'SELECT pg_sleep(2);' +
|
139
|
-
'SELECT 42 number') do
|
260
|
+
'SELECT 42 number') do
|
140
261
|
(Time.now - start_time).should be > 2
|
141
|
-
|
142
|
-
|
262
|
+
@client.async_command_aborted.should be_true
|
263
|
+
@client.status.should be PG::CONNECTION_BAD
|
143
264
|
@client.query_timeout = 0
|
144
265
|
@client.query_timeout.should eq 0
|
145
|
-
|
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
|
266
|
+
end
|
150
267
|
end
|
151
268
|
|
269
|
+
it "should clear connection with blocking reset" do
|
270
|
+
ensure_em_stop do
|
271
|
+
@client.async_command_aborted.should be_true
|
272
|
+
@client.status.should be PG::CONNECTION_BAD
|
273
|
+
@client.reset
|
274
|
+
@client.async_command_aborted.should be_false
|
275
|
+
@client.status.should be PG::CONNECTION_OK
|
276
|
+
end
|
277
|
+
end
|
152
278
|
end
|