cql-rb 1.0.0.pre3 → 1.0.0.pre4
Sign up to get free protection for your applications and to get access to all the features.
data/lib/cql/io/io_reactor.rb
CHANGED
@@ -89,12 +89,12 @@ module Cql
|
|
89
89
|
end
|
90
90
|
end
|
91
91
|
future.on_complete do
|
92
|
-
command_queue_push
|
92
|
+
command_queue_push(:connection_established, connection)
|
93
93
|
end
|
94
94
|
@lock.synchronize do
|
95
95
|
@streams << connection
|
96
96
|
end
|
97
|
-
command_queue_push
|
97
|
+
command_queue_push(:connection_added, connection)
|
98
98
|
future
|
99
99
|
end
|
100
100
|
|
@@ -131,6 +131,7 @@ module Cql
|
|
131
131
|
readables && readables.each(&:handle_read)
|
132
132
|
writables && writables.each(&:handle_write)
|
133
133
|
@streams.each(&:ping)
|
134
|
+
@streams.reject!(&:closed?)
|
134
135
|
end
|
135
136
|
ensure
|
136
137
|
stop
|
@@ -163,7 +164,7 @@ module Cql
|
|
163
164
|
@read_buffer = ''
|
164
165
|
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
165
166
|
@response_tasks = [nil] * 128
|
166
|
-
@event_listeners = []
|
167
|
+
@event_listeners = Hash.new { |h, k| h[k] = [] }
|
167
168
|
end
|
168
169
|
|
169
170
|
def open
|
@@ -191,7 +192,11 @@ module Cql
|
|
191
192
|
end
|
192
193
|
|
193
194
|
def on_event(&listener)
|
194
|
-
@event_listeners << listener
|
195
|
+
@event_listeners[:event] << listener
|
196
|
+
end
|
197
|
+
|
198
|
+
def on_close(&listener)
|
199
|
+
@event_listeners[:close] << listener
|
195
200
|
end
|
196
201
|
|
197
202
|
def ping
|
@@ -204,6 +209,10 @@ module Cql
|
|
204
209
|
@io && !connecting?
|
205
210
|
end
|
206
211
|
|
212
|
+
def closed?
|
213
|
+
@io.nil? && !connecting?
|
214
|
+
end
|
215
|
+
|
207
216
|
def has_capacity?
|
208
217
|
!!next_stream_id && connected?
|
209
218
|
end
|
@@ -217,8 +226,15 @@ module Cql
|
|
217
226
|
Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
|
218
227
|
@response_tasks[stream_id] = future
|
219
228
|
rescue => e
|
229
|
+
case e
|
230
|
+
when CqlError
|
231
|
+
error = e
|
232
|
+
else
|
233
|
+
error = IoError.new(e.message)
|
234
|
+
error.set_backtrace(e.backtrace)
|
235
|
+
end
|
220
236
|
@response_tasks.delete(stream_id)
|
221
|
-
future.fail!(
|
237
|
+
future.fail!(error)
|
222
238
|
end
|
223
239
|
|
224
240
|
def handle_read
|
@@ -227,7 +243,7 @@ module Cql
|
|
227
243
|
while @current_frame.complete?
|
228
244
|
stream_id = @current_frame.stream_id
|
229
245
|
if stream_id == EVENT_STREAM_ID
|
230
|
-
@event_listeners.each { |listener| listener.call(@current_frame.body) }
|
246
|
+
@event_listeners[:event].each { |listener| listener.call(@current_frame.body) }
|
231
247
|
elsif @response_tasks[stream_id]
|
232
248
|
@response_tasks[stream_id].complete!([@current_frame.body, connection_id])
|
233
249
|
@response_tasks[stream_id] = nil
|
@@ -236,15 +252,33 @@ module Cql
|
|
236
252
|
end
|
237
253
|
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
238
254
|
end
|
255
|
+
rescue => e
|
256
|
+
force_close(e)
|
239
257
|
end
|
240
258
|
|
241
259
|
def handle_write
|
242
260
|
if connecting?
|
243
261
|
handle_connected
|
244
|
-
|
262
|
+
elsif connected?
|
245
263
|
bytes_written = @io.write_nonblock(@write_buffer)
|
246
264
|
@write_buffer.slice!(0, bytes_written)
|
247
265
|
end
|
266
|
+
rescue => e
|
267
|
+
force_close(e)
|
268
|
+
end
|
269
|
+
|
270
|
+
def force_close(e)
|
271
|
+
case e
|
272
|
+
when CqlError
|
273
|
+
error = e
|
274
|
+
else
|
275
|
+
error = IoError.new(e.message)
|
276
|
+
error.set_backtrace(e.backtrace)
|
277
|
+
end
|
278
|
+
@response_tasks.each do |listener|
|
279
|
+
listener.fail!(error) if listener
|
280
|
+
end
|
281
|
+
close
|
248
282
|
end
|
249
283
|
|
250
284
|
def close
|
@@ -258,6 +292,7 @@ module Cql
|
|
258
292
|
if connecting?
|
259
293
|
succeed_connection!
|
260
294
|
end
|
295
|
+
@event_listeners[:close].each { |listener| listener.call(self) }
|
261
296
|
end
|
262
297
|
end
|
263
298
|
|
@@ -328,6 +363,10 @@ module Cql
|
|
328
363
|
true
|
329
364
|
end
|
330
365
|
|
366
|
+
def closed?
|
367
|
+
false
|
368
|
+
end
|
369
|
+
|
331
370
|
def has_capacity?
|
332
371
|
false
|
333
372
|
end
|
@@ -338,9 +377,13 @@ module Cql
|
|
338
377
|
|
339
378
|
def on_event; end
|
340
379
|
|
380
|
+
def on_close; end
|
381
|
+
|
341
382
|
def ping
|
342
383
|
if can_deliver_command?
|
343
384
|
deliver_commands
|
385
|
+
else
|
386
|
+
prune_directed_requests!
|
344
387
|
end
|
345
388
|
end
|
346
389
|
|
@@ -379,16 +422,20 @@ module Cql
|
|
379
422
|
def deliver_commands
|
380
423
|
while (command = next_command)
|
381
424
|
case command.shift
|
425
|
+
when :connection_added
|
426
|
+
when :connection_established
|
427
|
+
connection = command.shift
|
428
|
+
connection.on_close(&method(:connection_closed))
|
382
429
|
when :event_listener
|
383
430
|
listener = command.shift
|
384
431
|
@node_connections.each { |c| c.on_event(&listener) }
|
385
|
-
|
432
|
+
when :request
|
386
433
|
request, future, connection_id = command
|
387
434
|
if connection_id
|
388
435
|
connection = @node_connections.find { |c| c.connection_id == connection_id }
|
389
|
-
if connection && connection.has_capacity?
|
436
|
+
if connection && connection.connected? && connection.has_capacity?
|
390
437
|
connection.perform_request(request, future)
|
391
|
-
elsif connection
|
438
|
+
elsif connection && connection.connected?
|
392
439
|
future.fail!(ConnectionBusyError.new("Connection ##{connection_id} is busy"))
|
393
440
|
else
|
394
441
|
future.fail!(ConnectionNotFoundError.new("Connection ##{connection_id} does not exist"))
|
@@ -399,6 +446,28 @@ module Cql
|
|
399
446
|
end
|
400
447
|
end
|
401
448
|
end
|
449
|
+
|
450
|
+
def prune_directed_requests!
|
451
|
+
failing_commands = []
|
452
|
+
@queue_lock.synchronize do
|
453
|
+
@command_queue.reject! do |command|
|
454
|
+
if command.first == :request && command.last && @node_connections.none? { |c| c.connection_id == command.last }
|
455
|
+
failing_commands << command
|
456
|
+
true
|
457
|
+
else
|
458
|
+
false
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
failing_commands.each do |command|
|
463
|
+
_, _, future, id = command
|
464
|
+
future.fail!(ConnectionNotFoundError.new("Connection ##{id} no longer exists"))
|
465
|
+
end
|
466
|
+
end
|
467
|
+
|
468
|
+
def connection_closed(connection)
|
469
|
+
prune_directed_requests!
|
470
|
+
end
|
402
471
|
end
|
403
472
|
end
|
404
473
|
end
|
data/lib/cql/version.rb
CHANGED
@@ -281,6 +281,49 @@ module Cql
|
|
281
281
|
event.should == Cql::Protocol::SchemaChangeEventResponse.new('DROPPED', 'keyspace01', 'users')
|
282
282
|
end
|
283
283
|
end
|
284
|
+
|
285
|
+
context 'with error conditions' do
|
286
|
+
context 'when receiving a bad frame' do
|
287
|
+
before do
|
288
|
+
io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
|
289
|
+
io_reactor.start
|
290
|
+
@connection_id = io_reactor.add_connection(host, port).get
|
291
|
+
@request_future = io_reactor.queue_request(Cql::Protocol::OptionsRequest.new)
|
292
|
+
await { server.received_bytes.bytesize > 0 }
|
293
|
+
server.broadcast!("\x01\x00\x00\x02\x00\x00\x00\x16")
|
294
|
+
expect { @request_future.get }.to raise_error(Protocol::UnsupportedFrameTypeError)
|
295
|
+
end
|
296
|
+
|
297
|
+
it 'does not kill the reactor' do
|
298
|
+
io_reactor.should be_running
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'cleans out failed connections' do
|
302
|
+
f = io_reactor.queue_request(Protocol::QueryRequest.new('USE system', :one), @connection_id)
|
303
|
+
expect { f.get }.to raise_error(ConnectionNotFoundError)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
context 'when there is an error while sending a frame' do
|
308
|
+
before do
|
309
|
+
io_reactor.queue_request(Cql::Protocol::StartupRequest.new)
|
310
|
+
io_reactor.start
|
311
|
+
@connection_id = io_reactor.add_connection(host, port).get
|
312
|
+
@bad_request_future = io_reactor.queue_request(BadRequest.new)
|
313
|
+
end
|
314
|
+
|
315
|
+
it 'does not kill the reactor' do
|
316
|
+
@bad_request_future.get rescue nil
|
317
|
+
io_reactor.should be_running
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class BadRequest < Protocol::OptionsRequest
|
324
|
+
def write(io)
|
325
|
+
raise 'Blurgh!'
|
326
|
+
end
|
284
327
|
end
|
285
328
|
end
|
286
329
|
end
|
@@ -109,5 +109,10 @@ describe 'A CQL client' do
|
|
109
109
|
it 'raises an error for bad consistency levels' do
|
110
110
|
expect { client.execute('SELECT * FROM system.peers', :helloworld) }.to raise_error(Cql::CqlError)
|
111
111
|
end
|
112
|
+
|
113
|
+
it 'fails gracefully when connecting to the Thrift port' do
|
114
|
+
client = Cql::Client.new(connection_options.merge(port: 9160))
|
115
|
+
expect { client.start! }.to raise_error(Cql::IoError)
|
116
|
+
end
|
112
117
|
end
|
113
118
|
end
|
@@ -80,7 +80,7 @@ describe 'Protocol parsing and communication' do
|
|
80
80
|
context 'when setting up' do
|
81
81
|
it 'sends OPTIONS and receives SUPPORTED' do
|
82
82
|
response = execute_request(Cql::Protocol::OptionsRequest.new)
|
83
|
-
response.options.should
|
83
|
+
response.options.should have_key('CQL_VERSION')
|
84
84
|
end
|
85
85
|
|
86
86
|
it 'sends STARTUP and receives READY' do
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
describe 'Regressions' do
|
7
|
+
let :connection_options do
|
8
|
+
{:host => ENV['CASSANDRA_HOST']}
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'queries' do
|
12
|
+
let :client do
|
13
|
+
Cql::Client.new(connection_options)
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
client.start!
|
18
|
+
client.execute(%(CREATE KEYSPACE cql_rb_client_spec WITH REPLICATION = {'CLASS': 'SimpleStrategy', 'replication_factor': 1}))
|
19
|
+
client.use('cql_rb_client_spec')
|
20
|
+
end
|
21
|
+
|
22
|
+
after do
|
23
|
+
client.execute('DROP KEYSPACE cql_rb_client_spec')
|
24
|
+
client.shutdown!
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'handles multibyte characters in prepared statements' do
|
28
|
+
client.execute(%(CREATE TABLE users (user_id VARCHAR PRIMARY KEY, first VARCHAR, last VARCHAR, age INT)))
|
29
|
+
client.execute("INSERT INTO users (user_id, first, last, age) VALUES ('test', 'ümlaut', 'test', 1)") # ok
|
30
|
+
statement = client.prepare('INSERT INTO users (user_id, first, last, age) VALUES (?, ?, ?, ?)')
|
31
|
+
statement.execute('test2', 'test2', 'test2', 2)
|
32
|
+
statement.execute('test3', 'ümlaut', 'test3', 3)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'prepares and executes a statement with an append to a set' do
|
36
|
+
client.execute(%(CREATE TABLE users (name VARCHAR PRIMARY KEY, emails SET<VARCHAR>)))
|
37
|
+
statement = client.prepare(%(UPDATE users SET emails = emails + ? WHERE name = 'eve'))
|
38
|
+
statement.execute(['eve@gmail.com'])
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'prepares and executes a statement with an append to a list' do
|
42
|
+
client.execute(%(CREATE TABLE users (name VARCHAR PRIMARY KEY, emails LIST<VARCHAR>)))
|
43
|
+
statement = client.prepare(%(UPDATE users SET emails = emails + ? WHERE name = 'eve'))
|
44
|
+
statement.execute(['eve@gmail.com', 'eve@yahoo.com'])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'prepares and executes a statement with an append to a map' do
|
48
|
+
client.execute(%(CREATE TABLE users (name VARCHAR PRIMARY KEY, emails MAP<VARCHAR, VARCHAR>)))
|
49
|
+
statement = client.prepare(%(UPDATE users SET emails = emails + ? WHERE name = 'eve'))
|
50
|
+
statement.execute({'home' => 'eve@yahoo.com'})
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'prepares and executes a statement with a map assignment' do
|
54
|
+
client.execute(%(CREATE TABLE users (name VARCHAR PRIMARY KEY, emails MAP<VARCHAR, VARCHAR>)))
|
55
|
+
statement = client.prepare(%(UPDATE users SET emails['home'] = ? WHERE name = 'eve'))
|
56
|
+
statement.execute('eve@gmail.com')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cql-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.pre4
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-03-
|
12
|
+
date: 2013-03-30 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: A pure Ruby CQL3 driver for Cassandra
|
15
15
|
email:
|
@@ -42,6 +42,7 @@ files:
|
|
42
42
|
- spec/cql/uuid_spec.rb
|
43
43
|
- spec/integration/client_spec.rb
|
44
44
|
- spec/integration/protocol_spec.rb
|
45
|
+
- spec/integration/regression_spec.rb
|
45
46
|
- spec/spec_helper.rb
|
46
47
|
- spec/support/await_helper.rb
|
47
48
|
- spec/support/bytes_helper.rb
|
@@ -83,6 +84,7 @@ test_files:
|
|
83
84
|
- spec/cql/uuid_spec.rb
|
84
85
|
- spec/integration/client_spec.rb
|
85
86
|
- spec/integration/protocol_spec.rb
|
87
|
+
- spec/integration/regression_spec.rb
|
86
88
|
- spec/spec_helper.rb
|
87
89
|
- spec/support/await_helper.rb
|
88
90
|
- spec/support/bytes_helper.rb
|