cql-rb 1.0.6 → 1.1.0.pre0

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 (50) hide show
  1. data/README.md +4 -9
  2. data/lib/cql.rb +1 -0
  3. data/lib/cql/byte_buffer.rb +23 -7
  4. data/lib/cql/client.rb +11 -6
  5. data/lib/cql/client/asynchronous_client.rb +37 -83
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
  7. data/lib/cql/client/column_metadata.rb +16 -0
  8. data/lib/cql/client/request_runner.rb +46 -0
  9. data/lib/cql/future.rb +4 -5
  10. data/lib/cql/io.rb +2 -5
  11. data/lib/cql/io/connection.rb +220 -0
  12. data/lib/cql/io/io_reactor.rb +213 -185
  13. data/lib/cql/protocol.rb +1 -0
  14. data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
  15. data/lib/cql/protocol/decoding.rb +6 -31
  16. data/lib/cql/protocol/encoding.rb +1 -5
  17. data/lib/cql/protocol/request.rb +4 -0
  18. data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
  19. data/lib/cql/protocol/type_converter.rb +56 -76
  20. data/lib/cql/time_uuid.rb +104 -0
  21. data/lib/cql/uuid.rb +4 -2
  22. data/lib/cql/version.rb +1 -1
  23. data/spec/cql/client/asynchronous_client_spec.rb +47 -71
  24. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
  25. data/spec/cql/client/client_shared.rb +3 -3
  26. data/spec/cql/client/column_metadata_spec.rb +80 -0
  27. data/spec/cql/client/request_runner_spec.rb +120 -0
  28. data/spec/cql/future_spec.rb +26 -11
  29. data/spec/cql/io/connection_spec.rb +460 -0
  30. data/spec/cql/io/io_reactor_spec.rb +212 -265
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
  32. data/spec/cql/protocol/decoding_spec.rb +9 -28
  33. data/spec/cql/protocol/encoding_spec.rb +0 -5
  34. data/spec/cql/protocol/request_spec.rb +16 -0
  35. data/spec/cql/protocol/response_frame_spec.rb +2 -2
  36. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
  37. data/spec/cql/time_uuid_spec.rb +136 -0
  38. data/spec/cql/uuid_spec.rb +1 -5
  39. data/spec/integration/client_spec.rb +34 -38
  40. data/spec/integration/io_spec.rb +283 -0
  41. data/spec/integration/protocol_spec.rb +53 -113
  42. data/spec/integration/regression_spec.rb +124 -0
  43. data/spec/integration/uuid_spec.rb +76 -0
  44. data/spec/spec_helper.rb +12 -9
  45. data/spec/support/fake_io_reactor.rb +52 -21
  46. data/spec/support/fake_server.rb +2 -2
  47. metadata +33 -10
  48. checksums.yaml +0 -15
  49. data/lib/cql/io/node_connection.rb +0 -209
  50. data/spec/cql/protocol/type_converter_spec.rb +0 -52
@@ -36,10 +36,6 @@ module Cql
36
36
  Uuid.new(276263553384940695775376958868900023510).should eql(Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'))
37
37
  end
38
38
 
39
- it 'is not equal when anything other than a Uuid is passed' do
40
- [nil, 123, 'test'].each { |v| Uuid.new(276263553384940695775376958868900023510).should_not eql(v) }
41
- end
42
-
43
39
  it 'aliases #== to #eql?' do
44
40
  Uuid.new(276263553384940695775376958868900023510).should == Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6')
45
41
  end
@@ -77,4 +73,4 @@ module Cql
77
73
  end
78
74
  end
79
75
  end
80
- end
76
+ end
@@ -12,38 +12,36 @@ describe 'A CQL client' do
12
12
  Cql::Client.connect(connection_options)
13
13
  end
14
14
 
15
- before do
16
- client.connect
17
- end
18
-
19
15
  after do
20
- client.close
16
+ client.close rescue nil
21
17
  end
22
18
 
23
- it 'executes a query and returns the result' do
24
- result = client.execute('SELECT * FROM system.schema_keyspaces')
25
- result.should_not be_empty
26
- end
19
+ context 'with common operations' do
20
+ it 'executes a query and returns the result' do
21
+ result = client.execute('SELECT * FROM system.schema_keyspaces')
22
+ result.should_not be_empty
23
+ end
27
24
 
28
- it 'knows which keyspace it\'s in' do
29
- client.use('system')
30
- client.keyspace.should == 'system'
31
- client.use('system_auth')
32
- client.keyspace.should == 'system_auth'
33
- end
25
+ it 'knows which keyspace it\'s in' do
26
+ client.use('system')
27
+ client.keyspace.should == 'system'
28
+ client.use('system_auth')
29
+ client.keyspace.should == 'system_auth'
30
+ end
34
31
 
35
- it 'is not in a keyspace initially' do
36
- client.keyspace.should be_nil
37
- end
32
+ it 'is not in a keyspace initially' do
33
+ client.keyspace.should be_nil
34
+ end
38
35
 
39
- it 'can be initialized with a keyspace' do
40
- c = Cql::Client.connect(connection_options.merge(:keyspace => 'system'))
41
- c.connect
42
- begin
43
- c.keyspace.should == 'system'
44
- expect { c.execute('SELECT * FROM schema_keyspaces') }.to_not raise_error
45
- ensure
46
- c.close
36
+ it 'can be initialized with a keyspace' do
37
+ c = Cql::Client.connect(connection_options.merge(:keyspace => 'system'))
38
+ c.connect
39
+ begin
40
+ c.keyspace.should == 'system'
41
+ expect { c.execute('SELECT * FROM schema_keyspaces') }.to_not raise_error
42
+ ensure
43
+ c.close
44
+ end
47
45
  end
48
46
  end
49
47
 
@@ -77,15 +75,14 @@ describe 'A CQL client' do
77
75
 
78
76
  before do
79
77
  client.close
80
- multi_client.connect
81
- multi_client.use('system')
82
78
  end
83
79
 
84
80
  after do
85
- multi_client.close
81
+ multi_client.close rescue nil
86
82
  end
87
83
 
88
84
  it 'handles keyspace changes with #use' do
85
+ multi_client.use('system')
89
86
  100.times do
90
87
  result = multi_client.execute(%<SELECT * FROM schema_keyspaces WHERE keyspace_name = 'system'>)
91
88
  result.should have(1).item
@@ -93,6 +90,7 @@ describe 'A CQL client' do
93
90
  end
94
91
 
95
92
  it 'handles keyspace changes with #execute' do
93
+ multi_client.execute('USE system')
96
94
  100.times do
97
95
  result = multi_client.execute(%<SELECT * FROM schema_keyspaces WHERE keyspace_name = 'system'>)
98
96
  result.should have(1).item
@@ -100,6 +98,7 @@ describe 'A CQL client' do
100
98
  end
101
99
 
102
100
  it 'executes a prepared statement' do
101
+ multi_client.use('system')
103
102
  statement = multi_client.prepare('SELECT * FROM system.schema_keyspaces WHERE keyspace_name = ?')
104
103
  100.times do
105
104
  result = statement.execute('system')
@@ -122,25 +121,22 @@ describe 'A CQL client' do
122
121
  end
123
122
  end
124
123
 
125
- it 'send credentials given in :credentials' do
124
+ it 'sends credentials given in :credentials' do
126
125
  client = Cql::Client.connect(connection_options.merge(credentials: {username: 'cassandra', password: 'cassandra'}))
127
126
  client.execute('SELECT * FROM system.schema_keyspaces')
128
127
  end
129
128
 
130
129
  it 'raises an error when no credentials have been given' do
131
- if authentication_enabled
130
+ pending('authentication not configured', unless: authentication_enabled) do
132
131
  expect { Cql::Client.connect(connection_options.merge(credentials: nil)) }.to raise_error(Cql::AuthenticationError)
133
- else
134
- pending 'authentication not configured'
135
132
  end
136
133
  end
137
134
 
138
135
  it 'raises an error when the credentials are bad' do
139
- if authentication_enabled
140
- client = Cql::Client.connect(connection_options.merge(credentials: {username: 'foo', password: 'bar'}))
141
- expect { client.connect }.to raise_error(Cql::AuthenticationError)
142
- else
143
- pending 'authentication not configured'
136
+ pending('authentication not configured', unless: authentication_enabled) do
137
+ expect {
138
+ Cql::Client.connect(connection_options.merge(credentials: {username: 'foo', password: 'bar'}))
139
+ }.to raise_error(Cql::AuthenticationError)
144
140
  end
145
141
  end
146
142
  end
@@ -0,0 +1,283 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ describe 'An IO reactor' do
7
+ context 'with a generic server' do
8
+ let :io_reactor do
9
+ Cql::Io::IoReactor.new(IoSpec::TestConnection)
10
+ end
11
+
12
+ let :fake_server do
13
+ FakeServer.new
14
+ end
15
+
16
+ before do
17
+ fake_server.start!
18
+ io_reactor.start
19
+ end
20
+
21
+ after do
22
+ io_reactor.stop
23
+ fake_server.stop!
24
+ end
25
+
26
+ it 'connects to the server' do
27
+ io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1)
28
+ fake_server.await_connects!(1)
29
+ end
30
+
31
+ it 'receives data' do
32
+ protocol_handler = io_reactor.connect(ENV['CASSANDRA_HOST'], fake_server.port, 1).get
33
+ fake_server.await_connects!(1)
34
+ fake_server.broadcast!('hello world')
35
+ await { protocol_handler.data.bytesize > 0 }
36
+ protocol_handler.data.should == 'hello world'
37
+ end
38
+
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 }
41
+ fake_server.await_connects!(10)
42
+ fake_server.broadcast!('hello world')
43
+ await { protocol_handlers.all? { |c| c.data.bytesize > 0 } }
44
+ protocol_handlers.sample.data.should == 'hello world'
45
+ end
46
+ end
47
+
48
+ context 'when talking to Redis' do
49
+ let :io_reactor do
50
+ Cql::Io::IoReactor.new(IoSpec::RedisProtocolHandler)
51
+ end
52
+
53
+ let :protocol_handler do
54
+ begin
55
+ io_reactor.connect(ENV['CASSANDRA_HOST'], 6379, 1).get
56
+ rescue Cql::Io::ConnectionError
57
+ nil
58
+ end
59
+ end
60
+
61
+ before do
62
+ io_reactor.start.get
63
+ end
64
+
65
+ after do
66
+ io_reactor.stop.get
67
+ end
68
+
69
+ it 'can set a value' do
70
+ pending('Redis not running', unless: protocol_handler)
71
+ response = protocol_handler.send_request('SET', 'foo', 'bar').get
72
+ response.should == 'OK'
73
+ end
74
+
75
+ it 'can get a value' do
76
+ pending('Redis not running', unless: protocol_handler)
77
+ f = protocol_handler.send_request('SET', 'foo', 'bar').flat_map do
78
+ protocol_handler.send_request('GET', 'foo')
79
+ end
80
+ f.get.should == 'bar'
81
+ end
82
+
83
+ it 'can delete values' do
84
+ pending('Redis not running', unless: protocol_handler)
85
+ f = protocol_handler.send_request('SET', 'hello', 'world').flat_map do
86
+ protocol_handler.send_request('DEL', 'hello')
87
+ end
88
+ f.get.should == 1
89
+ end
90
+
91
+ it 'handles nil values' do
92
+ pending('Redis not running', unless: protocol_handler)
93
+ f = protocol_handler.send_request('DEL', 'hello').flat_map do
94
+ protocol_handler.send_request('GET', 'hello')
95
+ end
96
+ f.get.should be_nil
97
+ end
98
+
99
+ it 'handles errors' do
100
+ pending('Redis not running', unless: protocol_handler)
101
+ f = protocol_handler.send_request('SET', 'foo')
102
+ expect { f.get }.to raise_error("ERR wrong number of arguments for 'set' command")
103
+ end
104
+
105
+ it 'handles replies with multiple elements' do
106
+ pending('Redis not running', unless: protocol_handler)
107
+ f = protocol_handler.send_request('DEL', 'stuff')
108
+ f.get
109
+ f = protocol_handler.send_request('RPUSH', 'stuff', 'hello', 'world')
110
+ f.get.should == 2
111
+ f = protocol_handler.send_request('LRANGE', 'stuff', 0, 2)
112
+ f.get.should == ['hello', 'world']
113
+ end
114
+
115
+ it 'handles nil values when reading multiple elements' do
116
+ pending('Redis not running', unless: protocol_handler)
117
+ protocol_handler.send_request('DEL', 'things')
118
+ protocol_handler.send_request('HSET', 'things', 'hello', 'world')
119
+ f = protocol_handler.send_request('HMGET', 'things', 'hello', 'foo')
120
+ f.get.should == ['world', nil]
121
+ end
122
+ end
123
+ end
124
+
125
+ module IoSpec
126
+ class TestConnection
127
+ def initialize(connection)
128
+ @connection = connection
129
+ @connection.on_data(&method(:receive_data))
130
+ @lock = Mutex.new
131
+ @data = Cql::ByteBuffer.new
132
+ end
133
+
134
+ def data
135
+ @lock.synchronize { @data.to_s }
136
+ end
137
+
138
+ private
139
+
140
+ def receive_data(new_data)
141
+ @lock.synchronize { @data << new_data }
142
+ end
143
+ end
144
+
145
+ class LineProtocolHandler
146
+ def initialize(connection)
147
+ @connection = connection
148
+ @connection.on_data(&method(:process_data))
149
+ @lock = Mutex.new
150
+ @buffer = ''
151
+ @requests = []
152
+ end
153
+
154
+ def on_line(&listener)
155
+ @line_listener = listener
156
+ end
157
+
158
+ def write(command_string)
159
+ @connection.write(command_string)
160
+ end
161
+
162
+ def process_data(new_data)
163
+ lines = []
164
+ @lock.synchronize do
165
+ @buffer << new_data
166
+ while newline_index = @buffer.index("\r\n")
167
+ line = @buffer.slice!(0, newline_index + 2)
168
+ line.chomp!
169
+ lines << line
170
+ end
171
+ end
172
+ lines.each do |line|
173
+ @line_listener.call(line) if @line_listener
174
+ end
175
+ end
176
+ end
177
+
178
+ class RedisProtocolHandler
179
+ def initialize(connection)
180
+ @line_protocol = LineProtocolHandler.new(connection)
181
+ @line_protocol.on_line(&method(:handle_line))
182
+ @lock = Mutex.new
183
+ @responses = []
184
+ @state = BaseState.new(method(:handle_response))
185
+ end
186
+
187
+ def send_request(*args)
188
+ future = Cql::Future.new
189
+ @lock.synchronize do
190
+ @responses << future
191
+ end
192
+ request = "*#{args.size}\r\n"
193
+ args.each do |arg|
194
+ arg_str = arg.to_s
195
+ request << "$#{arg_str.bytesize}\r\n#{arg_str}\r\n"
196
+ end
197
+ @line_protocol.write(request)
198
+ future
199
+ end
200
+
201
+ def handle_response(result, error=false)
202
+ future = @lock.synchronize do
203
+ @responses.shift
204
+ end
205
+ if error
206
+ future.fail!(StandardError.new(result))
207
+ else
208
+ future.complete!(result)
209
+ end
210
+ end
211
+
212
+ def handle_line(line)
213
+ @state = @state.handle_line(line)
214
+ end
215
+
216
+ class State
217
+ def initialize(result_handler)
218
+ @result_handler = result_handler
219
+ end
220
+
221
+ def complete!(result)
222
+ @result_handler.call(result)
223
+ end
224
+
225
+ def fail!(message)
226
+ @result_handler.call(message, true)
227
+ end
228
+ end
229
+
230
+ class BulkState < State
231
+ def handle_line(line)
232
+ complete!(line)
233
+ BaseState.new(@result_handler)
234
+ end
235
+ end
236
+
237
+ class MultiBulkState < State
238
+ def initialize(result_handler, expected_elements)
239
+ super(result_handler)
240
+ @expected_elements = expected_elements
241
+ @elements = []
242
+ end
243
+
244
+ def handle_line(line)
245
+ if line.start_with?('$')
246
+ line.slice!(0, 1)
247
+ if line.to_i == -1
248
+ @elements << nil
249
+ end
250
+ else
251
+ @elements << line
252
+ end
253
+ if @elements.size == @expected_elements
254
+ complete!(@elements)
255
+ BaseState.new(@result_handler)
256
+ else
257
+ self
258
+ end
259
+ end
260
+ end
261
+
262
+ class BaseState < State
263
+ def handle_line(line)
264
+ next_state = self
265
+ first_char = line.slice!(0, 1)
266
+ case first_char
267
+ when '+' then complete!(line)
268
+ when ':' then complete!(line.to_i)
269
+ when '-' then fail!(line)
270
+ when '$'
271
+ if line.to_i == -1
272
+ complete!(nil)
273
+ else
274
+ next_state = BulkState.new(@result_handler)
275
+ end
276
+ when '*'
277
+ next_state = MultiBulkState.new(@result_handler, line.to_i)
278
+ end
279
+ next_state
280
+ end
281
+ end
282
+ end
283
+ end
@@ -4,23 +4,31 @@ require 'spec_helper'
4
4
 
5
5
 
6
6
  describe 'Protocol parsing and communication' do
7
- let :io_reactor do
8
- ir = Cql::Io::IoReactor.new
7
+ let! :io_reactor do
8
+ ir = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
9
9
  ir.start
10
- ir.add_connection(ENV['CASSANDRA_HOST'], 9042).get
10
+ connections << ir.connect(ENV['CASSANDRA_HOST'], 9042, 5).get
11
11
  ir
12
12
  end
13
13
 
14
+ let :connections do
15
+ []
16
+ end
17
+
14
18
  let :keyspace_name do
15
19
  "cql_rb_#{rand(1000)}"
16
20
  end
17
21
 
18
22
  after do
19
- io_reactor.stop.get if io_reactor.running?
23
+ if io_reactor.running?
24
+ drop_keyspace! rescue nil
25
+ io_reactor.stop.get rescue nil
26
+ end
20
27
  end
21
28
 
22
29
  def raw_execute_request(request)
23
- io_reactor.queue_request(request).get.first
30
+ connection = connections.first
31
+ connection.send_request(request).get
24
32
  end
25
33
 
26
34
  def execute_request(request)
@@ -41,7 +49,7 @@ describe 'Protocol parsing and communication' do
41
49
  end
42
50
 
43
51
  def create_keyspace!
44
- query("CREATE KEYSPACE #{keyspace_name} WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}")
52
+ query("CREATE KEYSPACE #{keyspace_name} WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1}")
45
53
  end
46
54
 
47
55
  def use_keyspace!
@@ -68,8 +76,9 @@ describe 'Protocol parsing and communication' do
68
76
  ensure
69
77
  begin
70
78
  drop_keyspace!
71
- rescue Errno::EPIPE => e
72
- # ignore since we're shutting down
79
+ rescue Cql::NotConnectedError, Errno::EPIPE => e
80
+ # ignore since we're shutting down, and these errors are likely caused
81
+ # by the code under test, re-raising them would mask the real errors
73
82
  end
74
83
  end
75
84
  end
@@ -109,56 +118,46 @@ describe 'Protocol parsing and communication' do
109
118
 
110
119
  context 'when authentication is required' do
111
120
  let :authentication_enabled do
112
- ir = Cql::Io::IoReactor.new
121
+ ir = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
113
122
  ir.start
114
- connected = ir.add_connection(ENV['CASSANDRA_HOST'], 9042)
115
- started = connected.flat_map do
116
- ir.queue_request(Cql::Protocol::StartupRequest.new)
123
+ connected = ir.connect(ENV['CASSANDRA_HOST'], 9042, 5)
124
+ started = connected.flat_map do |connection|
125
+ connection.send_request(Cql::Protocol::StartupRequest.new)
117
126
  end
118
- response = started.get.first
127
+ response = started.get
119
128
  required = response.is_a?(Cql::Protocol::AuthenticateResponse)
120
129
  ir.stop.get
121
130
  required
122
131
  end
123
132
 
124
133
  it 'sends STARTUP and receives AUTHENTICATE' do
125
- if authentication_enabled
134
+ pending('authentication not configured', unless: authentication_enabled) do
126
135
  response = raw_execute_request(Cql::Protocol::StartupRequest.new)
127
136
  response.should be_a(Cql::Protocol::AuthenticateResponse)
128
- else
129
- pending 'authentication not configured'
130
137
  end
131
138
  end
132
139
 
133
140
  it 'ignores the AUTHENTICATE response and receives ERROR' do
134
- if authentication_enabled
141
+ pending('authentication not configured', unless: authentication_enabled) do
135
142
  raw_execute_request(Cql::Protocol::StartupRequest.new)
136
143
  response = raw_execute_request(Cql::Protocol::RegisterRequest.new('TOPOLOGY_CHANGE'))
137
144
  response.code.should == 10
138
145
  response.message.should include('needs authentication')
139
- else
140
- pending 'authentication not configured'
141
146
  end
142
147
  end
143
148
 
144
149
  it 'sends STARTUP followed by CREDENTIALS and receives READY' do
145
- if authentication_enabled
146
- raw_execute_request(Cql::Protocol::StartupRequest.new)
147
- response = raw_execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra'))
148
- response.should be_a(Cql::Protocol::ReadyResponse)
149
- else
150
- pending 'authentication not configured'
151
- end
150
+ raw_execute_request(Cql::Protocol::StartupRequest.new)
151
+ response = raw_execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra'))
152
+ response.should be_a(Cql::Protocol::ReadyResponse)
152
153
  end
153
154
 
154
155
  it 'sends bad username and password in CREDENTIALS and receives ERROR' do
155
- if authentication_enabled
156
+ pending('authentication not configured', unless: authentication_enabled) do
156
157
  raw_execute_request(Cql::Protocol::StartupRequest.new)
157
158
  response = raw_execute_request(Cql::Protocol::CredentialsRequest.new('username' => 'foo', 'password' => 'bar'))
158
159
  response.code.should == 0x100
159
160
  response.message.should include('Username and/or password are incorrect')
160
- else
161
- pending 'authentication not configured'
162
161
  end
163
162
  end
164
163
  end
@@ -180,7 +179,7 @@ describe 'Protocol parsing and communication' do
180
179
  semaphore = Queue.new
181
180
  event = nil
182
181
  execute_request(Cql::Protocol::RegisterRequest.new('SCHEMA_CHANGE'))
183
- io_reactor.add_event_listener do |event_response|
182
+ connections.first.on_event do |event_response|
184
183
  event = event_response
185
184
  semaphore << :ping
186
185
  end
@@ -285,76 +284,6 @@ describe 'Protocol parsing and communication' do
285
284
  end
286
285
  end
287
286
 
288
- it 'decodes positive counters' do
289
- in_keyspace_with_counters_table do
290
- max_long = (1 << 63) -1
291
- response = query(%<UPDATE counters SET c1 = c1 + 1, c2 = c2 +#{max_long} WHERE id = 'positive'>)
292
- response = query(%<SELECT * FROM counters>, :quorum)
293
- response.rows.should == [
294
- {'id' => 'positive', 'c1' => 1, 'c2' => max_long},
295
- ]
296
- end
297
- end
298
-
299
- it 'decodes negative counters' do
300
- in_keyspace_with_counters_table do
301
- min_long = 0 - (1 << 63)
302
- response = query(%<UPDATE counters SET c1 = c1 - 1 , c2 = c2 -#{min_long.abs} WHERE id = 'negative'>)
303
- response = query(%<SELECT * FROM counters>, :quorum)
304
- response.rows.should == [
305
- {'id' => 'negative', 'c1' => -1 , 'c2' => min_long}
306
- ]
307
- end
308
- end
309
-
310
- it 'decodes positive bigints' do
311
- in_keyspace do
312
- max_long = (1 << 63) -1
313
- response = query('CREATE TABLE stuff (id VARCHAR, c1 bigint, c2 bigint, PRIMARY KEY (id))')
314
- response = query(%<UPDATE stuff SET c1 = 1, c2 = #{max_long} WHERE id = 'stuff'>)
315
- response = query(%<SELECT * FROM stuff>, :quorum)
316
- response.rows.should == [
317
- {'id' => 'stuff', 'c1' => 1, 'c2' => max_long}
318
- ]
319
- end
320
- end
321
-
322
- it 'decodes negative bigints' do
323
- in_keyspace do
324
- min_long = 0 - (1 << 63)
325
- response = query('CREATE TABLE stuff (id VARCHAR, c1 bigint, c2 bigint, PRIMARY KEY (id))')
326
- response = query(%<UPDATE stuff SET c1 = -1, c2 = #{min_long} WHERE id = 'stuff'>)
327
- response = query(%<SELECT * FROM stuff>, :quorum)
328
- response.rows.should == [
329
- {'id' => 'stuff', 'c1' => -1, 'c2' => min_long}
330
- ]
331
- end
332
- end
333
-
334
- it 'decodes positive ints' do
335
- in_keyspace do
336
- max_int = (1 << 31) -1
337
- response = query('CREATE TABLE stuff (id VARCHAR, c1 int, c2 int, PRIMARY KEY (id))')
338
- response = query(%<UPDATE stuff SET c1 = 1, c2 = #{max_int} WHERE id = 'stuff'>)
339
- response = query(%<SELECT * FROM stuff>, :quorum)
340
- response.rows.should == [
341
- {'id' => 'stuff', 'c1' => 1, 'c2' => max_int}
342
- ]
343
- end
344
- end
345
-
346
- it 'decodes negative ints' do
347
- in_keyspace do
348
- min_int = 0 - (1 << 31)
349
- response = query('CREATE TABLE stuff (id VARCHAR, c1 int, c2 int, PRIMARY KEY (id))')
350
- response = query(%<UPDATE stuff SET c1 = -1, c2 = #{min_int} WHERE id = 'stuff'>)
351
- response = query(%<SELECT * FROM stuff>, :quorum)
352
- response.rows.should == [
353
- {'id' => 'stuff', 'c1' => -1, 'c2' => min_int}
354
- ]
355
- end
356
- end
357
-
358
287
  it 'sends a DELETE command' do
359
288
  in_keyspace_with_table do
360
289
  response = query(%<DELETE email FROM users WHERE user_name = 'sue'>)
@@ -418,13 +347,17 @@ describe 'Protocol parsing and communication' do
418
347
  end
419
348
 
420
349
  context 'with pipelining' do
350
+ let :connection do
351
+ connections.first
352
+ end
353
+
421
354
  it 'handles multiple concurrent requests' do
422
355
  in_keyspace_with_table do
423
356
  futures = 10.times.map do
424
- io_reactor.queue_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
357
+ connection.send_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
425
358
  end
426
359
 
427
- futures << io_reactor.queue_request(Cql::Protocol::QueryRequest.new(%<INSERT INTO users (user_name, email) VALUES ('sam', 'sam@ham.com')>, :one))
360
+ futures << connection.send_request(Cql::Protocol::QueryRequest.new(%<INSERT INTO users (user_name, email) VALUES ('sam', 'sam@ham.com')>, :one))
428
361
 
429
362
  Cql::Future.combine(*futures).get
430
363
  end
@@ -432,11 +365,15 @@ describe 'Protocol parsing and communication' do
432
365
 
433
366
  it 'handles lots of concurrent requests' do
434
367
  in_keyspace_with_table do
435
- futures = 2000.times.map do
436
- io_reactor.queue_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
368
+ threads = Array.new(10) do
369
+ Thread.new do
370
+ futures = 200.times.map do
371
+ connection.send_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
372
+ end
373
+ Cql::Future.combine(*futures).get
374
+ end
437
375
  end
438
-
439
- Cql::Future.combine(*futures).get
376
+ threads.each(&:join)
440
377
  end
441
378
  end
442
379
  end
@@ -445,20 +382,23 @@ describe 'Protocol parsing and communication' do
445
382
 
446
383
  context 'in special circumstances' do
447
384
  it 'raises an exception when it cannot connect to Cassandra' do
448
- io_reactor = Cql::Io::IoReactor.new(connection_timeout: 0.1)
385
+ io_reactor = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
449
386
  io_reactor.start.get
450
- expect { io_reactor.add_connection('example.com', 9042).get }.to raise_error(Cql::Io::ConnectionError)
451
- expect { io_reactor.add_connection('blackhole', 9042).get }.to raise_error(Cql::Io::ConnectionError)
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)
452
389
  io_reactor.stop.get
453
390
  end
454
391
 
455
392
  it 'does nothing the second time #start is called' do
456
- io_reactor = Cql::Io::IoReactor.new
393
+ io_reactor = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
457
394
  io_reactor.start.get
458
- io_reactor.add_connection(ENV['CASSANDRA_HOST'], 9042)
459
- io_reactor.queue_request(Cql::Protocol::StartupRequest.new).get
395
+ connection = io_reactor.connect(ENV['CASSANDRA_HOST'], 9042, 0.1).get
396
+ response = connection.send_request(Cql::Protocol::StartupRequest.new).get
397
+ if response.is_a?(Cql::Protocol::AuthenticateResponse)
398
+ connection.send_request(Cql::Protocol::CredentialsRequest.new('username' => 'cassandra', 'password' => 'cassandra')).get
399
+ end
460
400
  io_reactor.start.get
461
- response = io_reactor.queue_request(Cql::Protocol::QueryRequest.new('USE system', :any)).get
401
+ response = connection.send_request(Cql::Protocol::QueryRequest.new('USE system', :any)).get
462
402
  response.should_not be_a(Cql::Protocol::ErrorResponse)
463
403
  end
464
404
  end