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