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