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.
@@ -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!(e)
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
- else
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
- else
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.0.0.pre3'.freeze
4
+ VERSION = '1.0.0.pre4'.freeze
5
5
  end
@@ -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 include('CQL_VERSION' => ['3.0.0'])
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.pre3
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-18 00:00:00.000000000 Z
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