cql-rb 1.1.0.pre3 → 1.1.0.pre6

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