cql-rb 1.0.0.pre3 → 1.0.0.pre4
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/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
|