cql-rb 1.0.0.pre4 → 1.0.0.pre5
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/README.md +2 -3
- data/lib/cql/client.rb +65 -15
- data/lib/cql/io.rb +2 -0
- data/lib/cql/io/io_reactor.rb +90 -304
- data/lib/cql/io/node_connection.rb +206 -0
- data/lib/cql/protocol/encoding.rb +1 -0
- data/lib/cql/protocol/request_frame.rb +26 -0
- data/lib/cql/protocol/response_frame.rb +17 -0
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client_spec.rb +95 -56
- data/spec/cql/io/io_reactor_spec.rb +47 -37
- data/spec/cql/protocol/encoding_spec.rb +5 -0
- data/spec/cql/protocol/request_frame_spec.rb +74 -0
- data/spec/cql/protocol/response_frame_spec.rb +18 -0
- data/spec/integration/client_spec.rb +46 -9
- data/spec/integration/protocol_spec.rb +83 -14
- data/spec/integration/regression_spec.rb +2 -2
- data/spec/spec_helper.rb +6 -0
- metadata +3 -2
@@ -0,0 +1,206 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'resolv-replace'
|
5
|
+
|
6
|
+
|
7
|
+
module Cql
|
8
|
+
module Io
|
9
|
+
# @private
|
10
|
+
class NodeConnection
|
11
|
+
def initialize(*args)
|
12
|
+
@host, @port, @connection_timeout = args
|
13
|
+
@connected_future = Future.new
|
14
|
+
@io = nil
|
15
|
+
@addrinfo = nil
|
16
|
+
@write_buffer = ''
|
17
|
+
@read_buffer = ''
|
18
|
+
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
19
|
+
@response_tasks = [nil] * 128
|
20
|
+
@event_listeners = Hash.new { |h, k| h[k] = [] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def open
|
24
|
+
@connection_started_at = Time.now
|
25
|
+
begin
|
26
|
+
addrinfo = Socket.getaddrinfo(@host, @port, Socket::AF_INET, Socket::SOCK_STREAM)
|
27
|
+
_, port, _, ip, address_family, socket_type = addrinfo.first
|
28
|
+
@sockaddr = Socket.sockaddr_in(port, ip)
|
29
|
+
@io = Socket.new(address_family, socket_type, 0)
|
30
|
+
@io.connect_nonblock(@sockaddr)
|
31
|
+
rescue Errno::EINPROGRESS
|
32
|
+
# ok
|
33
|
+
rescue SystemCallError, SocketError => e
|
34
|
+
fail_connection!(e)
|
35
|
+
end
|
36
|
+
@connected_future
|
37
|
+
end
|
38
|
+
|
39
|
+
def connection_id
|
40
|
+
self.object_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_io
|
44
|
+
@io
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_event(&listener)
|
48
|
+
@event_listeners[:event] << listener
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_close(&listener)
|
52
|
+
@event_listeners[:close] << listener
|
53
|
+
end
|
54
|
+
|
55
|
+
def connected?
|
56
|
+
@io && !connecting?
|
57
|
+
end
|
58
|
+
|
59
|
+
def connecting?
|
60
|
+
@io && !(@connected_future.complete? || @connected_future.failed?)
|
61
|
+
end
|
62
|
+
|
63
|
+
def closed?
|
64
|
+
@io.nil? && !connecting?
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_capacity?
|
68
|
+
!!next_stream_id && connected?
|
69
|
+
end
|
70
|
+
|
71
|
+
def can_write?
|
72
|
+
@io && (!@write_buffer.empty? || connecting?)
|
73
|
+
end
|
74
|
+
|
75
|
+
def perform_request(request, future)
|
76
|
+
stream_id = next_stream_id
|
77
|
+
Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
|
78
|
+
@response_tasks[stream_id] = future
|
79
|
+
rescue => e
|
80
|
+
case e
|
81
|
+
when CqlError
|
82
|
+
error = e
|
83
|
+
else
|
84
|
+
error = IoError.new(e.message)
|
85
|
+
error.set_backtrace(e.backtrace)
|
86
|
+
end
|
87
|
+
@response_tasks.delete(stream_id)
|
88
|
+
future.fail!(error)
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_read
|
92
|
+
new_bytes = @io.read_nonblock(2**16)
|
93
|
+
@current_frame << new_bytes
|
94
|
+
while @current_frame.complete?
|
95
|
+
stream_id = @current_frame.stream_id
|
96
|
+
if stream_id == EVENT_STREAM_ID
|
97
|
+
@event_listeners[:event].each { |listener| listener.call(@current_frame.body) }
|
98
|
+
elsif @response_tasks[stream_id]
|
99
|
+
@response_tasks[stream_id].complete!([@current_frame.body, connection_id])
|
100
|
+
@response_tasks[stream_id] = nil
|
101
|
+
else
|
102
|
+
# TODO dropping the request on the floor here, but we didn't send it
|
103
|
+
end
|
104
|
+
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
105
|
+
end
|
106
|
+
rescue => e
|
107
|
+
force_close(e)
|
108
|
+
end
|
109
|
+
|
110
|
+
def handle_write
|
111
|
+
succeed_connection! if connecting?
|
112
|
+
if !@write_buffer.empty?
|
113
|
+
bytes_written = @io.write_nonblock(@write_buffer)
|
114
|
+
@write_buffer.slice!(0, bytes_written)
|
115
|
+
end
|
116
|
+
rescue => e
|
117
|
+
force_close(e)
|
118
|
+
end
|
119
|
+
|
120
|
+
def handle_connecting
|
121
|
+
if connecting_timed_out?
|
122
|
+
fail_connection!(ConnectionTimeoutError.new("Could not connect to #{@host}:#{@port} within #{@connection_timeout}s"))
|
123
|
+
else
|
124
|
+
@io.connect_nonblock(@sockaddr)
|
125
|
+
succeed_connection!
|
126
|
+
end
|
127
|
+
rescue Errno::EISCONN
|
128
|
+
# ok
|
129
|
+
succeed_connection!
|
130
|
+
rescue SystemCallError, SocketError => e
|
131
|
+
fail_connection!(e)
|
132
|
+
end
|
133
|
+
|
134
|
+
def close
|
135
|
+
if @io
|
136
|
+
begin
|
137
|
+
@io.close
|
138
|
+
rescue SystemCallError
|
139
|
+
# nothing to do, it wasn't open
|
140
|
+
end
|
141
|
+
if connecting?
|
142
|
+
succeed_connection!
|
143
|
+
end
|
144
|
+
@io = nil
|
145
|
+
@event_listeners[:close].each { |listener| listener.call(self) }
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def to_s
|
150
|
+
state = begin
|
151
|
+
if connected? then 'connected'
|
152
|
+
elsif connecting? then 'connecting'
|
153
|
+
else 'not connected'
|
154
|
+
end
|
155
|
+
end
|
156
|
+
%<NodeConnection(#{@host}:#{@port}, #{state})>
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
EVENT_STREAM_ID = -1
|
162
|
+
|
163
|
+
def connecting_timed_out?
|
164
|
+
(Time.now - @connection_started_at) > @connection_timeout
|
165
|
+
end
|
166
|
+
|
167
|
+
def succeed_connection!
|
168
|
+
@connected_future.complete!(connection_id)
|
169
|
+
end
|
170
|
+
|
171
|
+
def fail_connection!(e)
|
172
|
+
case e
|
173
|
+
when ConnectionError
|
174
|
+
error = e
|
175
|
+
else
|
176
|
+
message = "Could not connect to #{@host}:#{@port}: #{e.message} (#{e.class.name})"
|
177
|
+
error = ConnectionError.new(message)
|
178
|
+
error.set_backtrace(e.backtrace)
|
179
|
+
end
|
180
|
+
@connected_future.fail!(error)
|
181
|
+
force_close(error)
|
182
|
+
end
|
183
|
+
|
184
|
+
def force_close(e)
|
185
|
+
case e
|
186
|
+
when CqlError
|
187
|
+
error = e
|
188
|
+
else
|
189
|
+
error = IoError.new(e.message)
|
190
|
+
error.set_backtrace(e.backtrace)
|
191
|
+
end
|
192
|
+
@response_tasks.each do |listener|
|
193
|
+
listener.fail!(error) if listener
|
194
|
+
end
|
195
|
+
close
|
196
|
+
end
|
197
|
+
|
198
|
+
def next_stream_id
|
199
|
+
@response_tasks.each_with_index do |task, index|
|
200
|
+
return index if task.nil?
|
201
|
+
end
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -49,6 +49,32 @@ module Cql
|
|
49
49
|
COMPRESSION = 'COMPRESSION'.freeze
|
50
50
|
end
|
51
51
|
|
52
|
+
class CredentialsRequest < RequestBody
|
53
|
+
attr_reader :credentials
|
54
|
+
|
55
|
+
def initialize(credentials)
|
56
|
+
super(4)
|
57
|
+
@credentials = credentials.dup.freeze
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(io)
|
61
|
+
write_string_map(io, @credentials)
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_s
|
65
|
+
%(CREDENTIALS #{@credentials})
|
66
|
+
end
|
67
|
+
|
68
|
+
def eql?(rq)
|
69
|
+
self.class === rq && rq.credentials.eql?(@credentials)
|
70
|
+
end
|
71
|
+
alias_method :==, :eql?
|
72
|
+
|
73
|
+
def hash
|
74
|
+
@h ||= @credentials.hash
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
52
78
|
class OptionsRequest < RequestBody
|
53
79
|
def initialize
|
54
80
|
super(5)
|
@@ -55,6 +55,7 @@ module Cql
|
|
55
55
|
case @headers.opcode
|
56
56
|
when 0x00 then ErrorResponse
|
57
57
|
when 0x02 then ReadyResponse
|
58
|
+
when 0x03 then AuthenticateResponse
|
58
59
|
when 0x06 then SupportedResponse
|
59
60
|
when 0x08 then ResultResponse
|
60
61
|
when 0x0c then EventResponse
|
@@ -214,6 +215,22 @@ module Cql
|
|
214
215
|
end
|
215
216
|
end
|
216
217
|
|
218
|
+
class AuthenticateResponse < ResponseBody
|
219
|
+
attr_reader :authentication_class
|
220
|
+
|
221
|
+
def self.decode!(buffer)
|
222
|
+
new(read_string!(buffer))
|
223
|
+
end
|
224
|
+
|
225
|
+
def initialize(authentication_class)
|
226
|
+
@authentication_class = authentication_class
|
227
|
+
end
|
228
|
+
|
229
|
+
def to_s
|
230
|
+
%(AUTHENTICATE #{authentication_class})
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
217
234
|
class SupportedResponse < ResponseBody
|
218
235
|
attr_reader :options
|
219
236
|
|
data/lib/cql/version.rb
CHANGED
data/spec/cql/client_spec.rb
CHANGED
@@ -33,114 +33,153 @@ module Cql
|
|
33
33
|
requests.last
|
34
34
|
end
|
35
35
|
|
36
|
-
describe '
|
36
|
+
describe '.connect' do
|
37
|
+
it 'connects and returns the client' do
|
38
|
+
client = described_class.connect(connection_options)
|
39
|
+
client.should be_connected
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#connect' do
|
37
44
|
it 'connects' do
|
38
|
-
client.
|
45
|
+
client.connect
|
39
46
|
connections.should have(1).item
|
40
47
|
end
|
41
48
|
|
42
49
|
it 'connects only once' do
|
43
|
-
client.
|
44
|
-
client.
|
50
|
+
client.connect
|
51
|
+
client.connect
|
45
52
|
connections.should have(1).item
|
46
53
|
end
|
47
54
|
|
48
55
|
it 'connects to all hosts' do
|
49
|
-
client.
|
56
|
+
client.close
|
50
57
|
io_reactor.stop.get
|
51
58
|
io_reactor.start.get
|
52
59
|
|
53
60
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
54
|
-
c.
|
61
|
+
c.connect
|
55
62
|
connections.should have(3).items
|
56
63
|
end
|
57
64
|
|
58
65
|
it 'returns itself' do
|
59
|
-
client.
|
66
|
+
client.connect.should equal(client)
|
60
67
|
end
|
61
68
|
|
62
69
|
it 'forwards the host and port' do
|
63
|
-
client.
|
70
|
+
client.connect
|
64
71
|
connection[:host].should == 'example.com'
|
65
72
|
connection[:port].should == 12321
|
66
73
|
end
|
67
74
|
|
68
75
|
it 'sends a startup request' do
|
69
|
-
client.
|
76
|
+
client.connect
|
70
77
|
last_request.should be_a(Protocol::StartupRequest)
|
71
78
|
end
|
72
79
|
|
73
80
|
it 'sends a startup request to each connection' do
|
74
|
-
client.
|
81
|
+
client.close
|
75
82
|
io_reactor.stop.get
|
76
83
|
io_reactor.start.get
|
77
84
|
|
78
85
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
79
|
-
c.
|
86
|
+
c.connect
|
80
87
|
connections.each do |cc|
|
81
88
|
cc[:requests].last.should be_a(Protocol::StartupRequest)
|
82
89
|
end
|
83
90
|
end
|
84
91
|
|
85
92
|
it 'is not in a keyspace' do
|
86
|
-
client.
|
93
|
+
client.connect
|
87
94
|
client.keyspace.should be_nil
|
88
95
|
end
|
89
96
|
|
90
97
|
it 'changes to the keyspace given as an option' do
|
91
98
|
c = described_class.new(connection_options.merge(:keyspace => 'hello_world'))
|
92
|
-
c.
|
99
|
+
c.connect
|
93
100
|
last_request.should == Protocol::QueryRequest.new('USE hello_world', :one)
|
94
101
|
end
|
95
102
|
|
96
103
|
it 'validates the keyspace name before sending the USE command' do
|
97
104
|
c = described_class.new(connection_options.merge(:keyspace => 'system; DROP KEYSPACE system'))
|
98
|
-
expect { c.
|
105
|
+
expect { c.connect }.to raise_error(Client::InvalidKeyspaceNameError)
|
99
106
|
requests.should_not include(Protocol::QueryRequest.new('USE system; DROP KEYSPACE system', :one))
|
100
107
|
end
|
101
108
|
|
102
109
|
it 're-raises any errors raised' do
|
103
110
|
io_reactor.stub(:add_connection).and_raise(ArgumentError)
|
104
|
-
expect { client.
|
111
|
+
expect { client.connect }.to raise_error(ArgumentError)
|
105
112
|
end
|
106
113
|
|
107
114
|
it 'is not connected if an error is raised' do
|
108
115
|
io_reactor.stub(:add_connection).and_raise(ArgumentError)
|
109
|
-
client.
|
116
|
+
client.connect rescue nil
|
110
117
|
client.should_not be_connected
|
118
|
+
io_reactor.should_not be_running
|
111
119
|
end
|
112
120
|
|
113
|
-
it 'is connected after #
|
114
|
-
client.
|
121
|
+
it 'is connected after #connect returns' do
|
122
|
+
client.connect
|
115
123
|
client.should be_connected
|
116
124
|
end
|
125
|
+
|
126
|
+
context 'when the server requests authentication' do
|
127
|
+
before do
|
128
|
+
io_reactor.queue_response(Protocol::AuthenticateResponse.new('com.example.Auth'))
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'sends credentials' do
|
132
|
+
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
133
|
+
client.connect
|
134
|
+
last_request.should == Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar')
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'raises an error when no credentials have been given' do
|
138
|
+
client = described_class.new(connection_options)
|
139
|
+
expect { client.connect }.to raise_error(AuthenticationError)
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'raises an error when the server responds with an error to the credentials request' do
|
143
|
+
io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
|
144
|
+
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
145
|
+
expect { client.connect }.to raise_error(AuthenticationError)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'shuts down the client when there is an authentication error' do
|
149
|
+
io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
|
150
|
+
client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
|
151
|
+
client.connect rescue nil
|
152
|
+
client.should_not be_connected
|
153
|
+
io_reactor.should_not be_running
|
154
|
+
end
|
155
|
+
end
|
117
156
|
end
|
118
157
|
|
119
|
-
describe '#
|
158
|
+
describe '#close' do
|
120
159
|
it 'closes the connection' do
|
121
|
-
client.
|
122
|
-
client.
|
160
|
+
client.connect
|
161
|
+
client.close
|
123
162
|
io_reactor.should_not be_running
|
124
163
|
end
|
125
164
|
|
126
|
-
it 'does nothing when called before #
|
127
|
-
client.
|
165
|
+
it 'does nothing when called before #connect' do
|
166
|
+
client.close
|
128
167
|
end
|
129
168
|
|
130
|
-
it 'accepts multiple calls to #
|
131
|
-
client.
|
132
|
-
client.
|
133
|
-
client.
|
169
|
+
it 'accepts multiple calls to #close' do
|
170
|
+
client.connect
|
171
|
+
client.close
|
172
|
+
client.close
|
134
173
|
end
|
135
174
|
|
136
175
|
it 'returns itself' do
|
137
|
-
client.
|
176
|
+
client.connect.close.should equal(client)
|
138
177
|
end
|
139
178
|
end
|
140
179
|
|
141
180
|
describe '#use' do
|
142
181
|
before do
|
143
|
-
client.
|
182
|
+
client.connect
|
144
183
|
end
|
145
184
|
|
146
185
|
it 'executes a USE query' do
|
@@ -150,12 +189,12 @@ module Cql
|
|
150
189
|
end
|
151
190
|
|
152
191
|
it 'executes a USE query for each connection' do
|
153
|
-
client.
|
192
|
+
client.close
|
154
193
|
io_reactor.stop.get
|
155
194
|
io_reactor.start.get
|
156
195
|
|
157
196
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
158
|
-
c.
|
197
|
+
c.connect
|
159
198
|
|
160
199
|
c.use('system')
|
161
200
|
last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
|
@@ -179,7 +218,7 @@ module Cql
|
|
179
218
|
|
180
219
|
describe '#execute' do
|
181
220
|
before do
|
182
|
-
client.
|
221
|
+
client.connect
|
183
222
|
end
|
184
223
|
|
185
224
|
it 'asks the connection to execute the query' do
|
@@ -214,12 +253,12 @@ module Cql
|
|
214
253
|
end
|
215
254
|
|
216
255
|
it 'detects that one connection changed to a keyspace and changes the others too' do
|
217
|
-
client.
|
256
|
+
client.close
|
218
257
|
io_reactor.stop.get
|
219
258
|
io_reactor.start.get
|
220
259
|
|
221
260
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
222
|
-
c.
|
261
|
+
c.connect
|
223
262
|
|
224
263
|
io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h1.example.com' }[:host])
|
225
264
|
io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h2.example.com' }[:host])
|
@@ -299,7 +338,7 @@ module Cql
|
|
299
338
|
end
|
300
339
|
|
301
340
|
before do
|
302
|
-
client.
|
341
|
+
client.connect
|
303
342
|
end
|
304
343
|
|
305
344
|
it 'sends a prepare request' do
|
@@ -334,12 +373,12 @@ module Cql
|
|
334
373
|
end
|
335
374
|
|
336
375
|
it 'executes a prepared statement using the right connection' do
|
337
|
-
client.
|
376
|
+
client.close
|
338
377
|
io_reactor.stop.get
|
339
378
|
io_reactor.start.get
|
340
379
|
|
341
380
|
c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
|
342
|
-
c.
|
381
|
+
c.connect
|
343
382
|
|
344
383
|
io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, metadata))
|
345
384
|
io_reactor.queue_response(Protocol::PreparedResultResponse.new('B' * 32, metadata))
|
@@ -363,51 +402,51 @@ module Cql
|
|
363
402
|
end
|
364
403
|
|
365
404
|
context 'when not connected' do
|
366
|
-
it 'is not connected before #
|
405
|
+
it 'is not connected before #connect has been called' do
|
367
406
|
client.should_not be_connected
|
368
407
|
end
|
369
408
|
|
370
|
-
it 'is not connected after #
|
371
|
-
client.
|
372
|
-
client.
|
409
|
+
it 'is not connected after #close has been called' do
|
410
|
+
client.connect
|
411
|
+
client.close
|
373
412
|
client.should_not be_connected
|
374
413
|
end
|
375
414
|
|
376
|
-
it 'complains when #use is called before #
|
415
|
+
it 'complains when #use is called before #connect' do
|
377
416
|
expect { client.use('system') }.to raise_error(Client::NotConnectedError)
|
378
417
|
end
|
379
418
|
|
380
|
-
it 'complains when #use is called after #
|
381
|
-
client.
|
382
|
-
client.
|
419
|
+
it 'complains when #use is called after #close' do
|
420
|
+
client.connect
|
421
|
+
client.close
|
383
422
|
expect { client.use('system') }.to raise_error(Client::NotConnectedError)
|
384
423
|
end
|
385
424
|
|
386
|
-
it 'complains when #execute is called before #
|
425
|
+
it 'complains when #execute is called before #connect' do
|
387
426
|
expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
|
388
427
|
end
|
389
428
|
|
390
|
-
it 'complains when #execute is called after #
|
391
|
-
client.
|
392
|
-
client.
|
429
|
+
it 'complains when #execute is called after #close' do
|
430
|
+
client.connect
|
431
|
+
client.close
|
393
432
|
expect { client.execute('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
|
394
433
|
end
|
395
434
|
|
396
|
-
it 'complains when #prepare is called before #
|
435
|
+
it 'complains when #prepare is called before #connect' do
|
397
436
|
expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
|
398
437
|
end
|
399
438
|
|
400
|
-
it 'complains when #prepare is called after #
|
401
|
-
client.
|
402
|
-
client.
|
439
|
+
it 'complains when #prepare is called after #close' do
|
440
|
+
client.connect
|
441
|
+
client.close
|
403
442
|
expect { client.prepare('DELETE FROM stuff WHERE id = 3') }.to raise_error(Client::NotConnectedError)
|
404
443
|
end
|
405
444
|
|
406
|
-
it 'complains when #execute of a prepared statement is called after #
|
407
|
-
client.
|
445
|
+
it 'complains when #execute of a prepared statement is called after #close' do
|
446
|
+
client.connect
|
408
447
|
io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
|
409
448
|
statement = client.prepare('DELETE FROM stuff WHERE id = 3')
|
410
|
-
client.
|
449
|
+
client.close
|
411
450
|
expect { statement.execute }.to raise_error(Client::NotConnectedError)
|
412
451
|
end
|
413
452
|
end
|