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