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/lib/pg/em.rb CHANGED
@@ -1,4 +1,16 @@
1
- require 'pg'
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 which (re)defines methods:
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
- # which autodetects if EventMachine is running and uses appropriate
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 reseting it:
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 +Client.connect+ (which is also aliased
86
- # by PG::Connection as +new+, +open+, +setdb+, +setdblogin+) and +reset+.
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
- # Async methods might try to reset connection on connection error.
89
- # You won't even notice that (except for warning message from PG).
90
- # If you want to detect such event use +on_autoreconnect+ property.
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 disable such behavior set:
93
- # client.async_autoreconnect = false
113
+ # To enable auto-reconnecting set:
114
+ # client.async_autoreconnect = true
94
115
  #
95
116
  # or pass as new() hash argument:
96
- # PG::EM::Client.new database: 'bar', async_autoreconnect: false
117
+ # ::new database: 'bar', async_autoreconnect: true
97
118
  #
98
- # Otherwise nothing changes in PG::Connect API.
99
- # See PG::Connect docs for arguments to above methods.
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
- # +async_exec_prepared+ after +async_prepare+ should only be invoked on
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
- # +Client.async_connect+ and +async_reset+.
112
- # However if passed as initialization option also affects blocking
113
- # +Client.connect+ and +reset+.
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
- # +Client.async_connect+ and +async_reset+. For those two use
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
- # Default is +true+.
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 +connection+ as first argument and original
133
- # +exception+ that caused the reconnecting process as second argument.
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 +Defferable#fail+ and the send
139
- # query command is not invoked at all.
140
- # - If return value is an instance of exception it is passed to
141
- # +Defferable#fail+ and the send query command is not invoked at all.
142
- # - If return value responds to +callback+ and +errback+ methods
143
- # (like +Deferrable+), the send query command will be bound to this
144
- # deferrable's success callback. Otherwise the send query command is
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 PG::EM::Client.new.
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("birds_by_name", "select id, name from animals order by name where species=$1", ['birds'])
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 && !self.error?
194
- break if (single_result = @client.get_result).nil?
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
- detach
229
- @deferrable.protect do
230
- raise PG::Error, "timeout expired (async)"
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
- raise PG::Error, @client.error_message
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 => true,
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
- async_args[:@on_autoreconnect] = value if value.respond_to? :call
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
- # Attempt connection asynchronously.
297
- # Pass the same arguments as to +Client.new+.
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
- # Returns +deferrable+. Use it's +callback+ to obtain newly created and
300
- # already connected +Client+ object.
301
- # If block is provided it's bound to +callback+ and +errback+ of returned
302
- # +deferrable+.
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
- # Attempt connection reset asynchronously.
315
- # There are no arguments.
365
+ # Attempts to reset the connection asynchronously.
366
+ # There are no arguments, except block argument.
316
367
  #
317
- # Returns +deferrable+. Use it's +callback+ to handle success.
318
- # If block is provided it's bound to +callback+ and +errback+ of returned
319
- # +deferrable+.
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 send_query
372
- prepare send_prepare
373
- exec_prepared send_query_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(async_autoreconnect: false)
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
@@ -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 &testcase
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
- @client.should be_an_instance_of described_class
21
- EM.stop
72
+ ensure_em_stop do
73
+ @client.should be_an_instance_of described_class
74
+ end
22
75
  end
23
76
 
24
- it "should create simple table `foo`" do
25
- @client.query('DROP TABLE IF EXISTS foo') do |result|
26
- result.should be_an_instance_of PG::Result
27
- @client.query('CREATE TABLE foo (id integer,cdate timestamp with time zone,data varchar)') do |result|
28
- result.should be_an_instance_of PG::Result
29
- EM.stop
30
- end.should be_a_kind_of ::EM::DefaultDeferrable
31
- end.should be_a_kind_of ::EM::DefaultDeferrable
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.prepare('get_foo', 'SELECT * FROM foo order by id') do |result|
40
- result.should be_an_instance_of PG::Result
41
- @client.exec_prepared('get_foo') do |result|
42
- result.should be_an_instance_of PG::Result
43
- result.each_with_index do |row, i|
44
- row['id'].to_i.should == i
45
- DateTime.parse(row['cdate']).should == @cdates[i]
46
- row['data'].should == @values[i][0]
47
- end
48
- EM.stop
49
- end.should be_a_kind_of ::EM::DefaultDeferrable
50
- end.should be_a_kind_of ::EM::DefaultDeferrable
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.query('SELECT pg_database_size(current_database());') do |result|
59
- result.should be_an_instance_of PG::Result
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
- EM.stop
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 raise syntax error in misspelled multiple statement" do
68
- @client.query('SELECT * from pg_class; SRELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
69
- result.should be_an_instance_of PG::Error
70
- result.to_s.should include "syntax error"
71
- @client.query('ROLLBACK') do |result|
72
- result.should be_an_instance_of PG::Result
73
- @client.query('BEGIN TRANSACTION') do |result|
74
- result.should be_an_instance_of PG::Result
75
- EM.stop
76
- end.should be_a_kind_of ::EM::DefaultDeferrable
77
- end.should be_a_kind_of ::EM::DefaultDeferrable
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.query('SELECT * from pg_class; SELECT CURRENT_TIMESTAMP; SELECT 42 number') do |result|
83
- result.should be_an_instance_of PG::Result
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
- EM.stop
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.query('SELECT pg_sleep(2)') do |result|
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
- @client.query('BEGIN TRANSACTION') do |result|
102
- result.should be_an_instance_of PG::Result
103
- EM.stop
104
- end.should be_a_kind_of ::EM::DefaultDeferrable
105
- end.should be_a_kind_of ::EM::DefaultDeferrable
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.query(
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
- EM.stop
126
- end.should be_a_kind_of ::EM::DefaultDeferrable
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.query(
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 |result|
260
+ 'SELECT 42 number') do
140
261
  (Time.now - start_time).should be > 2
141
- result.should be_an_instance_of PG::Error
142
- result.to_s.should include "query timeout expired"
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
- @client.query('BEGIN TRANSACTION') do |result|
146
- result.should be_an_instance_of PG::Result
147
- EM.stop
148
- end.should be_a_kind_of ::EM::DefaultDeferrable
149
- end.should be_a_kind_of ::EM::DefaultDeferrable
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