cql-rb 1.1.0.pre3 → 1.1.0.pre6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/README.md +2 -2
  2. data/lib/cql/client.rb +9 -5
  3. data/lib/cql/client/asynchronous_client.rb +105 -192
  4. data/lib/cql/client/asynchronous_prepared_statement.rb +51 -9
  5. data/lib/cql/client/connection_helper.rb +155 -0
  6. data/lib/cql/client/connection_manager.rb +56 -0
  7. data/lib/cql/client/keyspace_changer.rb +27 -0
  8. data/lib/cql/client/null_logger.rb +21 -0
  9. data/lib/cql/client/request_runner.rb +5 -3
  10. data/lib/cql/client/synchronous_client.rb +5 -5
  11. data/lib/cql/client/synchronous_prepared_statement.rb +4 -8
  12. data/lib/cql/future.rb +320 -210
  13. data/lib/cql/io/connection.rb +5 -5
  14. data/lib/cql/io/io_reactor.rb +21 -23
  15. data/lib/cql/protocol/cql_protocol_handler.rb +69 -38
  16. data/lib/cql/protocol/encoding.rb +5 -1
  17. data/lib/cql/protocol/requests/register_request.rb +2 -0
  18. data/lib/cql/protocol/type_converter.rb +1 -0
  19. data/lib/cql/version.rb +1 -1
  20. data/spec/cql/client/asynchronous_client_spec.rb +368 -175
  21. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +132 -22
  22. data/spec/cql/client/connection_helper_spec.rb +335 -0
  23. data/spec/cql/client/connection_manager_spec.rb +118 -0
  24. data/spec/cql/client/keyspace_changer_spec.rb +50 -0
  25. data/spec/cql/client/request_runner_spec.rb +12 -12
  26. data/spec/cql/client/synchronous_client_spec.rb +15 -15
  27. data/spec/cql/client/synchronous_prepared_statement_spec.rb +15 -11
  28. data/spec/cql/future_spec.rb +529 -301
  29. data/spec/cql/io/connection_spec.rb +12 -12
  30. data/spec/cql/io/io_reactor_spec.rb +61 -61
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +26 -12
  32. data/spec/cql/protocol/encoding_spec.rb +5 -0
  33. data/spec/cql/protocol/type_converter_spec.rb +1 -1
  34. data/spec/cql/time_uuid_spec.rb +7 -7
  35. data/spec/integration/client_spec.rb +2 -2
  36. data/spec/integration/io_spec.rb +20 -20
  37. data/spec/integration/protocol_spec.rb +17 -17
  38. data/spec/integration/regression_spec.rb +6 -0
  39. data/spec/integration/uuid_spec.rb +4 -0
  40. data/spec/support/fake_io_reactor.rb +38 -8
  41. data/spec/support/fake_server.rb +3 -3
  42. metadata +12 -2
@@ -121,6 +121,11 @@ module Cql
121
121
  buffer.should eql_bytes("\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11")
122
122
  end
123
123
 
124
+ it 'encodes a UUID as 16 bytes' do
125
+ Encoding.write_uuid(buffer, Uuid.new('00000000-24e1-11df-8924-001ff3591711'))
126
+ buffer.size.should eql(16)
127
+ end
128
+
124
129
  it 'appends to the buffer' do
125
130
  buffer << 'FOO'
126
131
  Encoding.write_uuid(buffer, uuid)
@@ -14,7 +14,7 @@ module Cql
14
14
  ''
15
15
  end
16
16
 
17
- TYPES = [:ascii, :bigint, :blob, :boolean, :decimal, :double, :float, :inet, :int, :text, :varchar, :timestamp, :timeuuid, :uuid, :varint].freeze
17
+ TYPES = [:ascii, :bigint, :blob, :boolean, :counter, :decimal, :double, :float, :inet, :int, :text, :varchar, :timestamp, :timeuuid, :uuid, :varint].freeze
18
18
 
19
19
  describe '#to_bytes' do
20
20
  context 'when encoding normal value' do
@@ -20,11 +20,11 @@ module Cql
20
20
 
21
21
  describe TimeUuid::Generator do
22
22
  let :generator do
23
- described_class.new(nil, nil, stub(now: clock))
23
+ described_class.new(nil, nil, double(now: clock))
24
24
  end
25
25
 
26
26
  let :clock do
27
- stub(:clock, to_i: 1370771820, usec: 329394)
27
+ double(:clock, to_i: 1370771820, usec: 329394)
28
28
  end
29
29
 
30
30
  describe '#next' do
@@ -103,17 +103,17 @@ module Cql
103
103
 
104
104
  context 'when specifying a custom clock ID' do
105
105
  it 'uses the lower 14 bits of the specified clock ID' do
106
- g = described_class.new(nil, 0x2bad, stub(now: clock))
106
+ g = described_class.new(nil, 0x2bad, double(now: clock))
107
107
  (g.next.value >> 48 & 0x3fff).should == 0x2bad
108
108
  end
109
109
 
110
110
  it 'ensures that the high bit of the clock ID is 1 (the variant)' do
111
- g = described_class.new(nil, 0x2bad, stub(now: clock))
111
+ g = described_class.new(nil, 0x2bad, double(now: clock))
112
112
  (g.next.value >> 60 & 0b1000).should == 0b1000
113
113
  end
114
114
 
115
115
  it 'generates a new random clock ID if time has moved backwards' do
116
- g = described_class.new(nil, 0x2bad, stub(now: clock))
116
+ g = described_class.new(nil, 0x2bad, double(now: clock))
117
117
  str1 = g.next.to_s.split('-')[3]
118
118
  clock.stub(:to_i).and_return(1370771820 - 2)
119
119
  str2 = g.next.to_s.split('-')[3]
@@ -123,12 +123,12 @@ module Cql
123
123
 
124
124
  context 'when specifying a custom node ID' do
125
125
  it 'uses the lower 48 bits of the specified node ID' do
126
- g = described_class.new(0xd00b1ed00b1ed00b, 0x0000, stub(now: clock))
126
+ g = described_class.new(0xd00b1ed00b1ed00b, 0x0000, double(now: clock))
127
127
  g.next.to_s.should end_with('00-1ed00b1ed00b')
128
128
  end
129
129
 
130
130
  it 'does not modify the multicast bit' do
131
- g = described_class.new(0x000000000000, 0x0000, stub(now: clock))
131
+ g = described_class.new(0x000000000000, 0x0000, double(now: clock))
132
132
  g.next.to_s.should end_with('00-000000000000')
133
133
  end
134
134
  end
@@ -109,7 +109,7 @@ describe 'A CQL client' do
109
109
 
110
110
  context 'with authentication' do
111
111
  let :client do
112
- stub(:client, connect: nil, close: nil)
112
+ double(:client, connect: nil, close: nil)
113
113
  end
114
114
 
115
115
  let :authentication_enabled do
@@ -146,7 +146,7 @@ describe 'A CQL client' do
146
146
  expect { client.execute('BAD cql') }.to raise_error(Cql::CqlError)
147
147
  end
148
148
 
149
- it 'raises an error for bad consistency levels' do
149
+ it 'raises an error for bad consistency' do
150
150
  expect { client.execute('SELECT * FROM system.peers', :helloworld) }.to raise_error(ArgumentError)
151
151
  end
152
152
 
@@ -29,7 +29,7 @@ describe 'An IO reactor' do
29
29
  end
30
30
 
31
31
  it 'receives data' do
32
- protocol_handler = io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1).get
32
+ protocol_handler = io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1).value
33
33
  fake_server.await_connects!(1)
34
34
  fake_server.broadcast!('hello world')
35
35
  await { protocol_handler.data.bytesize > 0 }
@@ -37,7 +37,7 @@ describe 'An IO reactor' do
37
37
  end
38
38
 
39
39
  it 'receives data on multiple connections' do
40
- protocol_handlers = Array.new(10) { io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1).get }
40
+ protocol_handlers = Array.new(10) { io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1).value }
41
41
  fake_server.await_connects!(10)
42
42
  fake_server.broadcast!('hello world')
43
43
  await { protocol_handlers.all? { |c| c.data.bytesize > 0 } }
@@ -52,23 +52,23 @@ describe 'An IO reactor' do
52
52
 
53
53
  let :protocol_handler do
54
54
  begin
55
- io_reactor.connect(ENV['CASSANDRA_HOST'], 6379, 1).get
55
+ io_reactor.connect(ENV['CASSANDRA_HOST'], 6379, 1).value
56
56
  rescue Cql::Io::ConnectionError
57
57
  nil
58
58
  end
59
59
  end
60
60
 
61
61
  before do
62
- io_reactor.start.get
62
+ io_reactor.start.value
63
63
  end
64
64
 
65
65
  after do
66
- io_reactor.stop.get
66
+ io_reactor.stop.value
67
67
  end
68
68
 
69
69
  it 'can set a value' do
70
70
  pending('Redis not running', unless: protocol_handler)
71
- response = protocol_handler.send_request('SET', 'foo', 'bar').get
71
+ response = protocol_handler.send_request('SET', 'foo', 'bar').value
72
72
  response.should == 'OK'
73
73
  end
74
74
 
@@ -77,7 +77,7 @@ describe 'An IO reactor' do
77
77
  f = protocol_handler.send_request('SET', 'foo', 'bar').flat_map do
78
78
  protocol_handler.send_request('GET', 'foo')
79
79
  end
80
- f.get.should == 'bar'
80
+ f.value.should == 'bar'
81
81
  end
82
82
 
83
83
  it 'can delete values' do
@@ -85,7 +85,7 @@ describe 'An IO reactor' do
85
85
  f = protocol_handler.send_request('SET', 'hello', 'world').flat_map do
86
86
  protocol_handler.send_request('DEL', 'hello')
87
87
  end
88
- f.get.should == 1
88
+ f.value.should == 1
89
89
  end
90
90
 
91
91
  it 'handles nil values' do
@@ -93,23 +93,23 @@ describe 'An IO reactor' do
93
93
  f = protocol_handler.send_request('DEL', 'hello').flat_map do
94
94
  protocol_handler.send_request('GET', 'hello')
95
95
  end
96
- f.get.should be_nil
96
+ f.value.should be_nil
97
97
  end
98
98
 
99
99
  it 'handles errors' do
100
100
  pending('Redis not running', unless: protocol_handler)
101
101
  f = protocol_handler.send_request('SET', 'foo')
102
- expect { f.get }.to raise_error("ERR wrong number of arguments for 'set' command")
102
+ expect { f.value }.to raise_error("ERR wrong number of arguments for 'set' command")
103
103
  end
104
104
 
105
105
  it 'handles replies with multiple elements' do
106
106
  pending('Redis not running', unless: protocol_handler)
107
107
  f = protocol_handler.send_request('DEL', 'stuff')
108
- f.get
108
+ f.value
109
109
  f = protocol_handler.send_request('RPUSH', 'stuff', 'hello', 'world')
110
- f.get.should == 2
110
+ f.value.should == 2
111
111
  f = protocol_handler.send_request('LRANGE', 'stuff', 0, 2)
112
- f.get.should == ['hello', 'world']
112
+ f.value.should == ['hello', 'world']
113
113
  end
114
114
 
115
115
  it 'handles nil values when reading multiple elements' do
@@ -117,7 +117,7 @@ describe 'An IO reactor' do
117
117
  protocol_handler.send_request('DEL', 'things')
118
118
  protocol_handler.send_request('HSET', 'things', 'hello', 'world')
119
119
  f = protocol_handler.send_request('HMGET', 'things', 'hello', 'foo')
120
- f.get.should == ['world', nil]
120
+ f.value.should == ['world', nil]
121
121
  end
122
122
  end
123
123
  end
@@ -185,9 +185,9 @@ module IoSpec
185
185
  end
186
186
 
187
187
  def send_request(*args)
188
- future = Cql::Future.new
188
+ promise = Cql::Promise.new
189
189
  @lock.synchronize do
190
- @responses << future
190
+ @responses << promise
191
191
  end
192
192
  request = "*#{args.size}\r\n"
193
193
  args.each do |arg|
@@ -195,17 +195,17 @@ module IoSpec
195
195
  request << "$#{arg_str.bytesize}\r\n#{arg_str}\r\n"
196
196
  end
197
197
  @line_protocol.write(request)
198
- future
198
+ promise.future
199
199
  end
200
200
 
201
201
  def handle_response(result, error=false)
202
- future = @lock.synchronize do
202
+ promise = @lock.synchronize do
203
203
  @responses.shift
204
204
  end
205
205
  if error
206
- future.fail!(StandardError.new(result))
206
+ promise.fail(StandardError.new(result))
207
207
  else
208
- future.complete!(result)
208
+ promise.fulfill(result)
209
209
  end
210
210
  end
211
211
 
@@ -7,7 +7,7 @@ describe 'Protocol parsing and communication' do
7
7
  let! :io_reactor do
8
8
  ir = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
9
9
  ir.start
10
- connections << ir.connect(ENV['CASSANDRA_HOST'], 9042, 5).get
10
+ connections << ir.connect(ENV['CASSANDRA_HOST'], 9042, 5).value
11
11
  ir
12
12
  end
13
13
 
@@ -22,13 +22,13 @@ describe 'Protocol parsing and communication' do
22
22
  after do
23
23
  if io_reactor.running?
24
24
  drop_keyspace! rescue nil
25
- io_reactor.stop.get rescue nil
25
+ io_reactor.stop.value rescue nil
26
26
  end
27
27
  end
28
28
 
29
29
  def raw_execute_request(request)
30
30
  connection = connections.first
31
- connection.send_request(request).get
31
+ connection.send_request(request).value
32
32
  end
33
33
 
34
34
  def execute_request(request)
@@ -124,9 +124,9 @@ describe 'Protocol parsing and communication' do
124
124
  started = connected.flat_map do |connection|
125
125
  connection.send_request(Cql::Protocol::StartupRequest.new)
126
126
  end
127
- response = started.get
127
+ response = started.value
128
128
  required = response.is_a?(Cql::Protocol::AuthenticateResponse)
129
- ir.stop.get
129
+ ir.stop.value
130
130
  required
131
131
  end
132
132
 
@@ -359,7 +359,7 @@ describe 'Protocol parsing and communication' do
359
359
 
360
360
  futures << connection.send_request(Cql::Protocol::QueryRequest.new(%<INSERT INTO users (user_name, email) VALUES ('sam', 'sam@ham.com')>, :one))
361
361
 
362
- Cql::Future.combine(*futures).get
362
+ Cql::Future.all(*futures).value
363
363
  end
364
364
  end
365
365
 
@@ -370,7 +370,7 @@ describe 'Protocol parsing and communication' do
370
370
  futures = 200.times.map do
371
371
  connection.send_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
372
372
  end
373
- Cql::Future.combine(*futures).get
373
+ Cql::Future.all(*futures).value
374
374
  end
375
375
  end
376
376
  threads.each(&:join)
@@ -383,22 +383,22 @@ describe 'Protocol parsing and communication' do
383
383
  context 'in special circumstances' do
384
384
  it 'raises an exception when it cannot connect to Cassandra' do
385
385
  io_reactor = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
386
- io_reactor.start.get
387
- expect { io_reactor.connect('example.com', 9042, 0.1).get }.to raise_error(Cql::Io::ConnectionError)
388
- expect { io_reactor.connect('blackhole', 9042, 0.1).get }.to raise_error(Cql::Io::ConnectionError)
389
- io_reactor.stop.get
386
+ io_reactor.start.value
387
+ expect { io_reactor.connect('example.com', 9042, 0.1).value }.to raise_error(Cql::Io::ConnectionError)
388
+ expect { io_reactor.connect('blackhole', 9042, 0.1).value }.to raise_error(Cql::Io::ConnectionError)
389
+ io_reactor.stop.value
390
390
  end
391
391
 
392
392
  it 'does nothing the second time #start is called' do
393
393
  io_reactor = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
394
- io_reactor.start.get
395
- connection = io_reactor.connect(ENV['CASSANDRA_HOST'], 9042, 0.1).get
396
- response = connection.send_request(Cql::Protocol::StartupRequest.new).get
394
+ io_reactor.start.value
395
+ connection = io_reactor.connect(ENV['CASSANDRA_HOST'], 9042, 0.1).value
396
+ response = connection.send_request(Cql::Protocol::StartupRequest.new).value
397
397
  if response.is_a?(Cql::Protocol::AuthenticateResponse)
398
- connection.send_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra')).get
398
+ connection.send_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra')).value
399
399
  end
400
- io_reactor.start.get
401
- response = connection.send_request(Cql::Protocol::QueryRequest.new('USE system', :any)).get
400
+ io_reactor.start.value
401
+ response = connection.send_request(Cql::Protocol::QueryRequest.new('USE system', :any)).value
402
402
  response.should_not be_a(Cql::Protocol::ErrorResponse)
403
403
  end
404
404
  end
@@ -205,4 +205,10 @@ describe 'Regressions' do
205
205
  client.keyspace.should == 'system'
206
206
  end
207
207
  end
208
+
209
+ context 'with prepared statements where the table does not exist' do
210
+ it 'raises an error when a statement is prepared on a table that does not exist' do
211
+ expect { client.prepare('SELECT * FROM table_that_does_not_exist') }.to raise_error(Cql::QueryError)
212
+ end
213
+ end
208
214
  end
@@ -44,6 +44,10 @@ describe 'Loading and storing UUIDs' do
44
44
  store_statement.execute(Cql::Uuid.new('39bc6ab8-d0f5-11e2-b041-adb2253022a3'), 'hello world')
45
45
  client.execute(%<SELECT * FROM ids>).first['id'].should == Cql::Uuid.new('39bc6ab8-d0f5-11e2-b041-adb2253022a3')
46
46
  end
47
+
48
+ it 'works even when the UUID could be represented as fewer than 16 bytes' do
49
+ store_statement.execute(Cql::Uuid.new('00853800-5400-11e2-90c5-3409d6a3565d'), 'hello world')
50
+ end
47
51
  end
48
52
 
49
53
  context Cql::TimeUuid::Generator do
@@ -9,7 +9,7 @@ class FakeIoReactor
9
9
  @queued_responses = Hash.new { |h, k| h[k] = [] }
10
10
  @default_host = nil
11
11
  @connection_listeners = []
12
- @started_future = Cql::Future.new
12
+ @started_promise = Cql::Promise.new
13
13
  @before_startup_handler = nil
14
14
  @down_nodes = []
15
15
  end
@@ -18,6 +18,10 @@ class FakeIoReactor
18
18
  @down_nodes << hostname
19
19
  end
20
20
 
21
+ def node_up(hostname)
22
+ @down_nodes.delete(hostname)
23
+ end
24
+
21
25
  def before_startup(&handler)
22
26
  @before_startup_handler = handler
23
27
  end
@@ -33,7 +37,7 @@ class FakeIoReactor
33
37
  @connection_listeners.each do |listener|
34
38
  listener.call(connection)
35
39
  end
36
- Cql::Future.completed(connection)
40
+ Cql::Future.resolved(connection)
37
41
  end
38
42
  end
39
43
 
@@ -47,18 +51,18 @@ class FakeIoReactor
47
51
  @before_startup_handler = nil
48
52
  Thread.start do
49
53
  @before_startup_handler.call
50
- @started_future.complete!(self)
54
+ @started_promise.fulfill(self)
51
55
  end
52
- elsif !(@started_future.complete? || @started_future.failed?)
53
- @started_future.complete!(self)
56
+ elsif !@started_promise.future.completed?
57
+ @started_promise.fulfill(self)
54
58
  end
55
- @started_future
59
+ @started_promise.future
56
60
  end
57
61
 
58
62
  def stop
59
63
  @running = false
60
64
  @connections.each(&:close)
61
- Cql::Future.completed
65
+ Cql::Future.resolved
62
66
  end
63
67
 
64
68
  def running?
@@ -78,6 +82,9 @@ class FakeConnection
78
82
  @closed = false
79
83
  @keyspace = nil
80
84
  @data = {}
85
+ @registered_event_types = []
86
+ @event_listeners = []
87
+ @closed_listeners = []
81
88
  @request_handler = method(:default_request_handler)
82
89
  end
83
90
 
@@ -95,22 +102,45 @@ class FakeConnection
95
102
 
96
103
  def close
97
104
  @closed = true
105
+ @closed_listeners.each(&:call)
98
106
  end
99
107
 
100
108
  def handle_request(&handler)
101
109
  @request_handler = handler
102
110
  end
103
111
 
112
+ def on_closed(&listener)
113
+ @closed_listeners << listener
114
+ end
115
+
116
+ def on_event(&listener)
117
+ @event_listeners << listener
118
+ end
119
+
120
+ def trigger_event(response)
121
+ if @event_listeners.any? && @registered_event_types.include?(response.type)
122
+ @event_listeners.each { |l| l.call(response) }
123
+ end
124
+ end
125
+
126
+ def has_event_listener?
127
+ @event_listeners.any? && @registered_event_types.any?
128
+ end
129
+
104
130
  def send_request(request)
105
131
  if @closed
106
132
  Cql::Future.failed(Cql::NotConnectedError.new)
107
133
  else
108
134
  @requests << request
135
+ case request
136
+ when Cql::Protocol::RegisterRequest
137
+ @registered_event_types.concat(request.events)
138
+ end
109
139
  response = @request_handler.call(request)
110
140
  if response.is_a?(Cql::Protocol::SetKeyspaceResultResponse)
111
141
  @keyspace = response.keyspace
112
142
  end
113
- Cql::Future.completed(response)
143
+ Cql::Future.resolved(response)
114
144
  end
115
145
  end
116
146
 
@@ -19,14 +19,14 @@ class FakeServer
19
19
  @running = true
20
20
  end
21
21
  @sockets = [TCPServer.new(@port)]
22
- @started = Cql::Future.new
22
+ @started = Cql::Promise.new
23
23
  @thread = Thread.start do
24
24
  Thread.current.abort_on_exception = true
25
25
  sleep(options[:accept_delay] || 0)
26
- @started.complete!
26
+ @started.fulfill
27
27
  io_loop
28
28
  end
29
- @started.get
29
+ @started.future.value
30
30
  self
31
31
  end
32
32