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