cql-rb 1.0.6 → 1.1.0.pre0

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