cql-rb 1.0.6 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.md +4 -9
  2. data/lib/cql.rb +1 -0
  3. data/lib/cql/byte_buffer.rb +23 -7
  4. data/lib/cql/client.rb +11 -6
  5. data/lib/cql/client/asynchronous_client.rb +37 -83
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
  7. data/lib/cql/client/column_metadata.rb +16 -0
  8. data/lib/cql/client/request_runner.rb +46 -0
  9. data/lib/cql/future.rb +4 -5
  10. data/lib/cql/io.rb +2 -5
  11. data/lib/cql/io/connection.rb +220 -0
  12. data/lib/cql/io/io_reactor.rb +213 -185
  13. data/lib/cql/protocol.rb +1 -0
  14. data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
  15. data/lib/cql/protocol/decoding.rb +6 -31
  16. data/lib/cql/protocol/encoding.rb +1 -5
  17. data/lib/cql/protocol/request.rb +4 -0
  18. data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
  19. data/lib/cql/protocol/type_converter.rb +56 -76
  20. data/lib/cql/time_uuid.rb +104 -0
  21. data/lib/cql/uuid.rb +4 -2
  22. data/lib/cql/version.rb +1 -1
  23. data/spec/cql/client/asynchronous_client_spec.rb +47 -71
  24. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
  25. data/spec/cql/client/client_shared.rb +3 -3
  26. data/spec/cql/client/column_metadata_spec.rb +80 -0
  27. data/spec/cql/client/request_runner_spec.rb +120 -0
  28. data/spec/cql/future_spec.rb +26 -11
  29. data/spec/cql/io/connection_spec.rb +460 -0
  30. data/spec/cql/io/io_reactor_spec.rb +212 -265
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
  32. data/spec/cql/protocol/decoding_spec.rb +9 -28
  33. data/spec/cql/protocol/encoding_spec.rb +0 -5
  34. data/spec/cql/protocol/request_spec.rb +16 -0
  35. data/spec/cql/protocol/response_frame_spec.rb +2 -2
  36. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
  37. data/spec/cql/time_uuid_spec.rb +136 -0
  38. data/spec/cql/uuid_spec.rb +1 -5
  39. data/spec/integration/client_spec.rb +34 -38
  40. data/spec/integration/io_spec.rb +283 -0
  41. data/spec/integration/protocol_spec.rb +53 -113
  42. data/spec/integration/regression_spec.rb +124 -0
  43. data/spec/integration/uuid_spec.rb +76 -0
  44. data/spec/spec_helper.rb +12 -9
  45. data/spec/support/fake_io_reactor.rb +52 -21
  46. data/spec/support/fake_server.rb +2 -2
  47. metadata +33 -10
  48. checksums.yaml +0 -15
  49. data/lib/cql/io/node_connection.rb +0 -209
  50. data/spec/cql/protocol/type_converter_spec.rb +0 -52
@@ -6,351 +6,298 @@ require 'spec_helper'
6
6
  module Cql
7
7
  module Io
8
8
  describe IoReactor do
9
- let :host do
10
- '127.0.0.1'
9
+ let :reactor do
10
+ described_class.new(protocol_handler_factory, selector: selector)
11
11
  end
12
12
 
13
- let :port do
14
- 2**15 + rand(2**15)
13
+ let :protocol_handler_factory do
14
+ stub(:protocol_handler_factory)
15
15
  end
16
16
 
17
- let :server do
18
- FakeServer.new(port)
17
+ let! :selector do
18
+ IoReactorSpec::FakeSelector.new
19
19
  end
20
20
 
21
- let :io_reactor do
22
- described_class.new(connection_timeout: 1)
23
- end
24
-
25
- before do
26
- server.start!
27
- end
28
-
29
- after do
30
- begin
31
- if io_reactor.running?
32
- io_reactor.stop.get
33
- end
34
- ensure
35
- server.stop!
21
+ describe '#start' do
22
+ after do
23
+ reactor.stop.get if reactor.running?
36
24
  end
37
- end
38
25
 
39
- describe '#initialize' do
40
- it 'does not connect' do
41
- described_class.new
42
- sleep(0.1)
43
- server.connects.should == 0
26
+ it 'returns a future that completes when the reactor has started' do
27
+ reactor.start.get
44
28
  end
45
- end
46
29
 
47
- describe '#running?' do
48
- it 'is initially false' do
49
- io_reactor.should_not be_running
30
+ it 'returns a future that resolves to the reactor' do
31
+ reactor.start.get.should equal(reactor)
50
32
  end
51
33
 
52
- it 'is true when started' do
53
- io_reactor.start.get
54
- io_reactor.should be_running
34
+ it 'is running after being started' do
35
+ reactor.start.get
36
+ reactor.should be_running
55
37
  end
56
38
 
57
- it 'is true when starting' do
58
- f = io_reactor.start
59
- io_reactor.should be_running
60
- f.get
39
+ it 'cannot be started again once stopped' do
40
+ reactor.start.get
41
+ reactor.stop.get
42
+ expect { reactor.start }.to raise_error(ReactorError)
61
43
  end
62
44
 
63
- it 'is false when stopped' do
64
- io_reactor.start.get
65
- io_reactor.stop.get
66
- io_reactor.should_not be_running
45
+ it 'calls the selector' do
46
+ called = false
47
+ selector.handler { called = true; [[], [], []] }
48
+ reactor.start.get
49
+ await { called }
50
+ reactor.stop.get
51
+ called.should be_true, 'expected the selector to have been called'
67
52
  end
68
53
  end
69
54
 
70
55
  describe '#stop' do
71
- it 'closes all connections' do
72
- io_reactor.start.get
73
- f1 = io_reactor.add_connection(host, port)
74
- f2 = io_reactor.add_connection(host, port)
75
- f3 = io_reactor.add_connection(host, port)
76
- f4 = io_reactor.add_connection(host, port)
77
- Future.combine(f1, f2, f3, f4).get
78
- io_reactor.stop.get
79
- server.await_disconnects!(4)
80
- server.disconnects.should == 4
81
- end
82
-
83
- it 'succeeds connection futures when stopping while connecting' do
84
- server.stop!
85
- server.start!(accept_delay: 2)
86
- f = io_reactor.add_connection(host, port)
87
- io_reactor.start
88
- io_reactor.stop
89
- f.get
56
+ after do
57
+ reactor.stop.get if reactor.running?
90
58
  end
91
- end
92
59
 
93
- describe '#add_connection' do
94
- before do
95
- io_reactor.start.get
60
+ it 'returns a future which completes when the reactor has stopped' do
61
+ reactor.start.get
62
+ reactor.stop.get
96
63
  end
97
64
 
98
- it 'connects to the specified host and port' do
99
- future = io_reactor.add_connection(host, port)
100
- future.get
101
- server.await_connects!(1)
102
- server.connects.should == 1
65
+ it 'returns a future which resolves to the reactor' do
66
+ reactor.start.get
67
+ reactor.stop.get.should equal(reactor)
103
68
  end
104
69
 
105
- it 'yields the connection ID when completed' do
106
- future = io_reactor.add_connection(host, port)
107
- future.get.should_not be_nil
70
+ it 'is not running after being stopped' do
71
+ reactor.start.get
72
+ reactor.stop.get
73
+ reactor.should_not be_running
108
74
  end
109
75
 
110
- it 'fails the returned future when it cannot connect to the host' do
111
- future = io_reactor.add_connection('example.com', port)
112
- expect { future.get }.to raise_error(ConnectionError)
76
+ it 'closes all sockets' do
77
+ connection = nil
78
+ protocol_handler_factory.stub(:new) do |sh|
79
+ connection = sh
80
+ stub(:protocol_handler)
81
+ end
82
+ reactor.start.get
83
+ reactor.connect('example.com', 9999, 5)
84
+ reactor.stop.get
85
+ connection.should be_closed
113
86
  end
87
+ end
114
88
 
115
- it 'fails the returned future when it cannot connect to the port' do
116
- future = io_reactor.add_connection(host, 9999)
117
- expect { future.get }.to raise_error(ConnectionError)
89
+ describe '#on_error' do
90
+ before do
91
+ selector.handler { raise 'Blurgh' }
118
92
  end
119
93
 
120
- it 'times out quickly when it cannot connect' do
121
- started_at = Time.now
122
- begin
123
- future = io_reactor.add_connection(host, 9999)
124
- future.get
125
- rescue ConnectionError
126
- end
127
- time_taken = (Time.now - started_at).to_f
128
- time_taken.should be < 1.5
94
+ it 'calls the listeners when the reactor crashes' do
95
+ error = nil
96
+ reactor.on_error { |e| error = e }
97
+ reactor.start
98
+ await { error }
99
+ error.message.should == 'Blurgh'
129
100
  end
130
101
 
131
- it 'can be called before the reactor is started' do
132
- r = described_class.new(connection_timeout: 1)
133
- f1 = r.add_connection(host, port)
134
- f2 = r.start
135
- f2.get
136
- f1.get
102
+ it 'calls the listener immediately when the reactor has already crashed' do
103
+ error = nil
104
+ reactor.start.get
105
+ await { !reactor.running? }
106
+ reactor.on_error { |e| error = e }
107
+ await { error }
137
108
  end
138
- end
139
109
 
140
- describe '#queue_request' do
141
- it 'eventually sends the request' do
142
- io_reactor.start
143
- io_reactor.add_connection(host, port).get
144
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
145
- await { server.received_bytes.bytesize > 0 }
146
- server.received_bytes.to_s[3, 1].should == "\x01"
110
+ it 'ignores errors raised by listeners' do
111
+ called = false
112
+ reactor.on_error { raise 'Blurgh' }
113
+ reactor.on_error { called = true }
114
+ reactor.start
115
+ await { called }
116
+ called.should be_true, 'expected all close listeners to have been called'
147
117
  end
118
+ end
148
119
 
149
- it 'can be called before the reactor is started' do
150
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
151
- io_reactor.start
152
- io_reactor.add_connection(host, port).get
153
- await { server.received_bytes.bytesize > 0 }
154
- server.received_bytes[3, 1].should == "\x01"
120
+ describe '#connect' do
121
+ let :protocol_handler do
122
+ stub(:protocol_handler)
155
123
  end
156
124
 
157
- it 'queues requests when all connections are busy' do
158
- request = Cql::Protocol::QueryRequest.new('UPDATE x SET y = 1 WHERE z = 2', :one)
159
-
160
- io_reactor.start
161
- io_reactor.add_connection(host, port).get
162
-
163
- futures = 200.times.map do
164
- io_reactor.queue_request(request)
165
- end
166
-
167
- 128.times do |i|
168
- server.broadcast!("\x81\x00#{[i].pack('c')}\b\x00\x00\x00\x04\x00\x00\x00\x01")
125
+ before do
126
+ protocol_handler_factory.stub(:new) do |connection|
127
+ connection.to_io.stub(:connect_nonblock)
128
+ protocol_handler.stub(:connection).and_return(connection)
129
+ protocol_handler
169
130
  end
131
+ end
170
132
 
171
- Future.combine(*futures.shift(128)).get
172
-
173
- 128.times do |i|
174
- server.broadcast!("\x81\x00#{[i].pack('c')}\b\x00\x00\x00\x04\x00\x00\x00\x01")
133
+ before do
134
+ selector.handler do |readables, writables, _, _|
135
+ writables.each do |writable|
136
+ fake_connected(writable)
137
+ end
138
+ [[], writables, []]
175
139
  end
176
-
177
- Future.combine(*futures).get
178
140
  end
179
141
 
180
- context 'with a connection ID' do
181
- it 'performs the request using the specified connection' do
182
- future = Future.new
183
- request = Cql::Protocol::StartupRequest.new
184
- response = "\x81\x00\x00\x02\x00\x00\x00\x00"
185
-
186
- io_reactor.start.get
187
- c1_id = io_reactor.add_connection(host, port).get
188
- c2_id = io_reactor.add_connection(host, port).get
189
- q1_future = io_reactor.queue_request(request, c2_id)
190
- q2_future = io_reactor.queue_request(request, c1_id)
142
+ def fake_connected(connection)
143
+ connection.to_io.stub(:connect_nonblock)
144
+ end
191
145
 
192
- Future.combine(q1_future, q2_future).on_complete do |(_, q1_id), (_, q2_id)|
193
- future.complete!([c1_id, c2_id, q1_id, q2_id])
194
- end
146
+ after do
147
+ reactor.stop if reactor.running?
148
+ end
195
149
 
196
- server.await_connects!(2)
197
- server.broadcast!(response.dup)
150
+ it 'returns a future that resolves to a new protocol handler' do
151
+ reactor.start.get
152
+ f = reactor.connect('example.com', 9999, 5)
153
+ f.get.should equal(protocol_handler)
154
+ end
198
155
 
199
- connection1_id, connection2_id, query1_id, query2_id = future.value
156
+ it 'returns a new protocol handler which wraps a socket handler' do
157
+ reactor.start.get
158
+ protocol_handler = reactor.connect('example.com', 9999, 5).get
159
+ protocol_handler.connection.should_not be_nil
160
+ protocol_handler.connection.host.should == 'example.com'
161
+ protocol_handler.connection.port.should == 9999
162
+ protocol_handler.connection.connection_timeout.should == 5
163
+ end
164
+ end
200
165
 
201
- connection1_id.should_not be_nil
202
- connection2_id.should_not be_nil
203
- query1_id.should == connection2_id
204
- query2_id.should == connection1_id
166
+ describe '#to_s' do
167
+ context 'returns a string that' do
168
+ it 'includes the class name' do
169
+ reactor.to_s.should include('Cql::Io::IoReactor')
205
170
  end
206
171
 
207
- it 'fails if the connection does not exist' do
208
- f = io_reactor.start.flat_map do
209
- io_reactor.add_connection(host, port).flat_map do
210
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new, 1234)
211
- end
212
- end
213
- expect { f.get }.to raise_error(ConnectionNotFoundError)
172
+ it 'includes a list of its connections' do
173
+ reactor.to_s.should include('@connections=[')
174
+ reactor.to_s.should include('#<Cql::Io::Unblocker>')
214
175
  end
176
+ end
177
+ end
178
+ end
215
179
 
216
- it 'queues requests when the connection is busy' do
217
- request = Cql::Protocol::QueryRequest.new('UPDATE x SET y = 1 WHERE z = 2', :one)
218
-
219
- io_reactor.start
220
- connection_id = io_reactor.add_connection(host, port).get
221
-
222
- futures = 200.times.map do
223
- io_reactor.queue_request(request, connection_id)
224
- end
225
-
226
- 128.times do |i|
227
- server.broadcast!("\x81\x00#{[i].pack('c')}\b\x00\x00\x00\x04\x00\x00\x00\x01")
228
- end
229
-
230
- Future.combine(*futures.shift(128)).get
180
+ describe IoLoopBody do
181
+ let :loop_body do
182
+ described_class.new(selector: selector)
183
+ end
231
184
 
232
- 128.times do |i|
233
- server.broadcast!("\x81\x00#{[i].pack('c')}\b\x00\x00\x00\x04\x00\x00\x00\x01")
234
- end
185
+ let :selector do
186
+ stub(:selector)
187
+ end
235
188
 
236
- Future.combine(*futures).get
237
- end
189
+ let :socket do
190
+ stub(:socket, connected?: false, connecting?: false, writable?: false, closed?: false)
191
+ end
238
192
 
239
- it 'fails queued requests when the connection closes' do
240
- request = Cql::Protocol::QueryRequest.new('UPDATE x SET y = 1 WHERE z = 2', :one)
193
+ describe '#tick' do
194
+ before do
195
+ loop_body.add_socket(socket)
196
+ end
241
197
 
242
- io_reactor.start
243
- connection_id = io_reactor.add_connection(host, port).get
198
+ it 'passes connected sockets as readables to the selector' do
199
+ socket.stub(:connected?).and_return(true)
200
+ selector.should_receive(:select).with([socket], anything, anything, anything).and_return([nil, nil, nil])
201
+ loop_body.tick
202
+ end
244
203
 
245
- failures = 0
204
+ it 'passes writable sockets as writable to the selector' do
205
+ socket.stub(:writable?).and_return(true)
206
+ selector.should_receive(:select).with(anything, [socket], anything, anything).and_return([nil, nil, nil])
207
+ loop_body.tick
208
+ end
246
209
 
247
- 200.times do
248
- f = io_reactor.queue_request(request, connection_id)
249
- f.on_failure do
250
- failures += 1
251
- end
252
- end
210
+ it 'passes connecting sockets as writable to the selector' do
211
+ socket.stub(:connecting?).and_return(true)
212
+ socket.stub(:connect)
213
+ selector.should_receive(:select).with(anything, [socket], anything, anything).and_return([nil, nil, nil])
214
+ loop_body.tick
215
+ end
253
216
 
254
- server.broadcast!("\x01\x00\x00\x02\x00\x00\x00\x16")
217
+ it 'filters out closed sockets' do
218
+ socket.stub(:closed?).and_return(true)
219
+ selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
220
+ loop_body.tick
221
+ socket.stub(:connected?).and_return(true)
222
+ selector.should_receive(:select).with([], [], anything, anything).and_return([nil, nil, nil])
223
+ loop_body.tick
224
+ end
255
225
 
256
- await { failures == 200 }
226
+ it 'calls #read on all readable sockets returned by the selector' do
227
+ socket.stub(:connected?).and_return(true)
228
+ socket.should_receive(:read)
229
+ selector.stub(:select) do |r, w, _, _|
230
+ [[socket], nil, nil]
257
231
  end
232
+ loop_body.tick
258
233
  end
259
234
 
260
- it 'fails if there is an error when encoding the request' do
261
- request = stub(:request)
262
- request.stub(:encode_frame).and_raise(Cql::ProtocolError.new('Boork!'))
263
- io_reactor.start
264
- io_reactor.add_connection(host, port).get
265
- f = io_reactor.queue_request(request)
266
- expect { f.get }.to raise_error(Cql::ProtocolError)
235
+ it 'calls #connect on all connecting sockets' do
236
+ socket.stub(:connecting?).and_return(true)
237
+ socket.should_receive(:connect)
238
+ selector.stub(:select).and_return([nil, nil, nil])
239
+ loop_body.tick
267
240
  end
268
241
 
269
- it 'yields the response when completed' do
270
- response = nil
271
- io_reactor.start
272
- io_reactor.add_connection(host, port).get
273
- f = io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
274
- f.on_complete do |r, _|
275
- response = r
242
+ it 'calls #flush on all writable sockets returned by the selector' do
243
+ socket.stub(:writable?).and_return(true)
244
+ socket.should_receive(:flush)
245
+ selector.stub(:select) do |r, w, _, _|
246
+ [nil, [socket], nil]
276
247
  end
277
- sleep(0.1)
278
- server.broadcast!("\x81\x00\x00\x02\x00\x00\x00\x00")
279
- await { response }
280
- response.should == Cql::Protocol::ReadyResponse.new
248
+ loop_body.tick
281
249
  end
282
250
 
283
- it 'yields the connection ID when completed' do
284
- connection = nil
285
- io_reactor.start
286
- io_reactor.add_connection(host, port).get
287
- f = io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
288
- f.on_complete do |_, c|
289
- connection = c
290
- end
291
- sleep(0.1)
292
- server.broadcast!("\x81\x00\x00\x02\x00\x00\x00\x00")
293
- await { connection }
294
- connection.should_not be_nil
251
+ it 'allows the caller to specify a custom timeout' do
252
+ selector.should_receive(:select).with(anything, anything, anything, 99).and_return([[], [], []])
253
+ loop_body.tick(99)
295
254
  end
296
255
  end
297
256
 
298
- describe '#add_event_listener' do
299
- it 'calls the listener when frames with stream ID -1 arrives' do
300
- event = nil
301
- io_reactor.start
302
- io_reactor.add_connection(host, port).get
303
- io_reactor.add_event_listener { |e| event = e }
304
- sleep(0.1)
305
- server.broadcast!("\x81\x00\xFF\f\x00\x00\x00+\x00\rSCHEMA_CHANGE\x00\aDROPPED\x00\nkeyspace01\x00\x05users")
306
- await { event }
307
- event.should == Cql::Protocol::SchemaChangeEventResponse.new('DROPPED', 'keyspace01', 'users')
257
+ describe '#close_sockets' do
258
+ it 'closes all sockets' do
259
+ socket1 = stub(:socket1, closed?: false)
260
+ socket2 = stub(:socket2, closed?: false)
261
+ socket1.should_receive(:close)
262
+ socket2.should_receive(:close)
263
+ loop_body.add_socket(socket1)
264
+ loop_body.add_socket(socket2)
265
+ loop_body.close_sockets
308
266
  end
309
- end
310
267
 
311
- context 'with error conditions' do
312
- context 'when receiving a bad frame' do
313
- before do
314
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
315
- io_reactor.start
316
- @connection_id = io_reactor.add_connection(host, port).get
317
- @request_future = io_reactor.queue_request(Cql::Protocol::OptionsRequest.new)
318
- await { server.received_bytes.bytesize > 0 }
319
- server.broadcast!("\x01\x00\x00\x02\x00\x00\x00\x16")
320
- expect { @request_future.get }.to raise_error(Protocol::UnsupportedFrameTypeError)
321
- end
322
-
323
- it 'does not kill the reactor' do
324
- io_reactor.should be_running
325
- end
326
-
327
- it 'cleans out failed connections' do
328
- pending 'There\'s some kind of race condition in the setup for this test'
329
- f = io_reactor.queue_request(Protocol::QueryRequest.new('USE system', :one), @connection_id)
330
- expect { f.get }.to raise_error(ConnectionNotFoundError)
331
- end
268
+ it 'closes all sockets, even when one of them raises an error' do
269
+ socket1 = stub(:socket1, closed?: false)
270
+ socket2 = stub(:socket2, closed?: false)
271
+ socket1.stub(:close).and_raise('Blurgh')
272
+ socket2.should_receive(:close)
273
+ loop_body.add_socket(socket1)
274
+ loop_body.add_socket(socket2)
275
+ loop_body.close_sockets
332
276
  end
333
277
 
334
- context 'when there is an error while sending a frame' do
335
- before do
336
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
337
- io_reactor.start
338
- @connection_id = io_reactor.add_connection(host, port).get
339
- @bad_request_future = io_reactor.queue_request(BadRequest.new)
340
- end
341
-
342
- it 'does not kill the reactor' do
343
- @bad_request_future.get rescue nil
344
- io_reactor.should be_running
345
- end
278
+ it 'does not close already closed sockets' do
279
+ socket.stub(:closed?).and_return(true)
280
+ socket.should_not_receive(:close)
281
+ loop_body.add_socket(socket)
282
+ loop_body.close_sockets
346
283
  end
347
284
  end
348
285
  end
286
+ end
287
+ end
349
288
 
350
- class BadRequest < Protocol::OptionsRequest
351
- def write(io)
352
- raise 'Blurgh!'
353
- end
289
+ module IoReactorSpec
290
+ class FakeSelector
291
+ def initialize
292
+ handler { [[], [], []] }
293
+ end
294
+
295
+ def handler(&body)
296
+ @body = body
297
+ end
298
+
299
+ def select(*args)
300
+ @body.call(*args)
354
301
  end
355
302
  end
356
303
  end