cql-rb 1.0.6 → 1.1.0.pre0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +4 -9
- data/lib/cql.rb +1 -0
- data/lib/cql/byte_buffer.rb +23 -7
- data/lib/cql/client.rb +11 -6
- data/lib/cql/client/asynchronous_client.rb +37 -83
- data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
- data/lib/cql/client/column_metadata.rb +16 -0
- data/lib/cql/client/request_runner.rb +46 -0
- data/lib/cql/future.rb +4 -5
- data/lib/cql/io.rb +2 -5
- data/lib/cql/io/connection.rb +220 -0
- data/lib/cql/io/io_reactor.rb +213 -185
- data/lib/cql/protocol.rb +1 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
- data/lib/cql/protocol/decoding.rb +6 -31
- data/lib/cql/protocol/encoding.rb +1 -5
- data/lib/cql/protocol/request.rb +4 -0
- data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
- data/lib/cql/protocol/type_converter.rb +56 -76
- data/lib/cql/time_uuid.rb +104 -0
- data/lib/cql/uuid.rb +4 -2
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +47 -71
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
- data/spec/cql/client/client_shared.rb +3 -3
- data/spec/cql/client/column_metadata_spec.rb +80 -0
- data/spec/cql/client/request_runner_spec.rb +120 -0
- data/spec/cql/future_spec.rb +26 -11
- data/spec/cql/io/connection_spec.rb +460 -0
- data/spec/cql/io/io_reactor_spec.rb +212 -265
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
- data/spec/cql/protocol/decoding_spec.rb +9 -28
- data/spec/cql/protocol/encoding_spec.rb +0 -5
- data/spec/cql/protocol/request_spec.rb +16 -0
- data/spec/cql/protocol/response_frame_spec.rb +2 -2
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
- data/spec/cql/time_uuid_spec.rb +136 -0
- data/spec/cql/uuid_spec.rb +1 -5
- data/spec/integration/client_spec.rb +34 -38
- data/spec/integration/io_spec.rb +283 -0
- data/spec/integration/protocol_spec.rb +53 -113
- data/spec/integration/regression_spec.rb +124 -0
- data/spec/integration/uuid_spec.rb +76 -0
- data/spec/spec_helper.rb +12 -9
- data/spec/support/fake_io_reactor.rb +52 -21
- data/spec/support/fake_server.rb +2 -2
- metadata +33 -10
- checksums.yaml +0 -15
- data/lib/cql/io/node_connection.rb +0 -209
- data/spec/cql/protocol/type_converter_spec.rb +0 -52
data/spec/cql/uuid_spec.rb
CHANGED
@@ -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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
32
|
+
it 'is not in a keyspace initially' do
|
33
|
+
client.keyspace.should be_nil
|
34
|
+
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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 '
|
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
|
-
|
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
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
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.
|
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
|
-
|
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
|
-
|
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
|
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
|
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.
|
115
|
-
started = connected.flat_map do
|
116
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
357
|
+
connection.send_request(Cql::Protocol::QueryRequest.new('SELECT * FROM users', :quorum))
|
425
358
|
end
|
426
359
|
|
427
|
-
futures <<
|
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
|
-
|
436
|
-
|
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(
|
385
|
+
io_reactor = Cql::Io::IoReactor.new(Cql::Protocol::CqlProtocolHandler)
|
449
386
|
io_reactor.start.get
|
450
|
-
expect { io_reactor.
|
451
|
-
expect { io_reactor.
|
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.
|
459
|
-
|
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 =
|
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
|