cql-rb 2.1.0.pre1 → 2.1.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cql.rb +1 -0
- data/lib/cql/client.rb +2 -0
- data/lib/cql/client/client.rb +1 -1
- data/lib/cql/client/connection_manager.rb +17 -16
- data/lib/cql/client/connector.rb +1 -1
- data/lib/cql/client/prepared_statement.rb +11 -4
- data/lib/cql/error_codes.rb +96 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +20 -18
- data/lib/cql/protocol/responses/detailed_error_response.rb +5 -5
- data/lib/cql/protocol/responses/error_response.rb +3 -1
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/prepared_statement_spec.rb +19 -0
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +60 -23
- data/spec/integration/client_spec.rb +8 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5feed4b6caaa0042eda3f44328ce6f2132519fad
|
4
|
+
data.tar.gz: 975d84488e3f0e023d1846e9facd8c293a43634f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9b8860656c26696c6ac5447e02385b56096ba65b400fc686d77fd66682c74a85f135c98d1364e106f9f2b7a3e8dbb4f0ece9512c2be2ac62fc15452c40724e4
|
7
|
+
data.tar.gz: 4fa9ce7ca66ea3e84a99206fb11df31a5acbee4e4efdbc7bee346af64709cd65520d692d33dbd930b38b342824f3b3c635d25e0d82b00249c0269524eb872c9f
|
data/lib/cql.rb
CHANGED
data/lib/cql/client.rb
CHANGED
data/lib/cql/client/client.rb
CHANGED
@@ -367,7 +367,7 @@ module Cql
|
|
367
367
|
def connect_with_protocol_version_fallback
|
368
368
|
f = create_cluster_connector.connect_all(@hosts, @connections_per_node)
|
369
369
|
f.fallback do |error|
|
370
|
-
if error.is_a?(QueryError) && error.code ==
|
370
|
+
if error.is_a?(QueryError) && error.code == QueryError::PROTOCOL_ERROR && @protocol_version > 1
|
371
371
|
@logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@protocol_version, @protocol_version - 1, error.message])
|
372
372
|
@protocol_version -= 1
|
373
373
|
connect_with_protocol_version_fallback
|
@@ -7,17 +7,17 @@ module Cql
|
|
7
7
|
include Enumerable
|
8
8
|
|
9
9
|
def initialize
|
10
|
-
@connections = []
|
10
|
+
@connections = [].freeze
|
11
11
|
@lock = Mutex.new
|
12
12
|
end
|
13
13
|
|
14
14
|
def add_connections(connections)
|
15
15
|
@lock.synchronize do
|
16
|
-
@connections
|
16
|
+
@connections = (@connections + connections).freeze
|
17
17
|
connections.each do |connection|
|
18
18
|
connection.on_closed do
|
19
19
|
@lock.synchronize do
|
20
|
-
@connections
|
20
|
+
@connections = (@connections - [connection]).freeze
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -25,30 +25,31 @@ module Cql
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def connected?
|
28
|
-
|
29
|
-
@connections.any?
|
30
|
-
end
|
28
|
+
!snapshot.empty?
|
31
29
|
end
|
32
30
|
|
33
31
|
def snapshot
|
34
|
-
|
35
|
-
|
32
|
+
connections = nil
|
33
|
+
@lock.lock
|
34
|
+
begin
|
35
|
+
connections = @connections
|
36
|
+
ensure
|
37
|
+
@lock.unlock
|
36
38
|
end
|
39
|
+
connections
|
37
40
|
end
|
38
41
|
|
39
42
|
def random_connection
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
43
|
+
connections = snapshot
|
44
|
+
raise NotConnectedError if connections.empty?
|
45
|
+
connections.sample
|
44
46
|
end
|
45
47
|
|
46
48
|
def each_connection(&callback)
|
47
49
|
return self unless block_given?
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
50
|
+
connections = snapshot
|
51
|
+
raise NotConnectedError if connections.empty?
|
52
|
+
connections.each(&callback)
|
52
53
|
end
|
53
54
|
alias_method :each, :each_connection
|
54
55
|
end
|
data/lib/cql/client/connector.rb
CHANGED
@@ -24,7 +24,7 @@ module Cql
|
|
24
24
|
connected_connections = connections.select(&:connected?)
|
25
25
|
if connected_connections.empty?
|
26
26
|
e = connections.first.error
|
27
|
-
if e.is_a?(
|
27
|
+
if e.is_a?(QueryError) && e.code == QueryError::BAD_CREDENTIALS
|
28
28
|
e = AuthenticationError.new(e.message)
|
29
29
|
end
|
30
30
|
raise e
|
@@ -150,7 +150,13 @@ module Cql
|
|
150
150
|
def execute(*args)
|
151
151
|
connection = @connection_manager.random_connection
|
152
152
|
if connection[self]
|
153
|
-
run(args, connection)
|
153
|
+
f = run(args, connection)
|
154
|
+
f.fallback do |e|
|
155
|
+
raise e unless e.is_a?(QueryError) && e.code == QueryError::UNPREPARED
|
156
|
+
prepare(connection).flat_map do
|
157
|
+
run(args, connection)
|
158
|
+
end
|
159
|
+
end
|
154
160
|
else
|
155
161
|
prepare(connection).flat_map do
|
156
162
|
run(args, connection)
|
@@ -216,11 +222,12 @@ module Cql
|
|
216
222
|
private
|
217
223
|
|
218
224
|
def run(args, connection)
|
219
|
-
bound_args = args.
|
220
|
-
|
225
|
+
bound_args = args.take(@raw_metadata.size)
|
226
|
+
remaining_args = args.drop(@raw_metadata.size)
|
227
|
+
unless bound_args.size == @raw_metadata.size && remaining_args.size <= 1
|
221
228
|
raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
|
222
229
|
end
|
223
|
-
options = @execute_options_decoder.decode_options(
|
230
|
+
options = @execute_options_decoder.decode_options(remaining_args.last)
|
224
231
|
statement_id = connection[self]
|
225
232
|
request_metadata = @raw_result_metadata.nil?
|
226
233
|
request = Protocol::ExecuteRequest.new(statement_id, @raw_metadata, bound_args, request_metadata, options[:consistency], options[:serial_consistency], options[:page_size], options[:paging_state], options[:trace])
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cql
|
4
|
+
module ErrorCodes
|
5
|
+
# Something unexpected happened. This indicates a server-side bug.
|
6
|
+
SERVER_ERROR = 0x0000
|
7
|
+
|
8
|
+
# Some client message triggered a protocol violation (for instance a QUERY
|
9
|
+
# message is sent before a STARTUP one has been sent).
|
10
|
+
PROTOCOL_ERROR = 0x000A
|
11
|
+
|
12
|
+
# CREDENTIALS request failed because Cassandra did not accept the provided credentials.
|
13
|
+
BAD_CREDENTIALS = 0x0100
|
14
|
+
|
15
|
+
# Unavailable exception.
|
16
|
+
#
|
17
|
+
# Details:
|
18
|
+
#
|
19
|
+
# * `:cl` - The consistency level of the query having triggered the exception.
|
20
|
+
# * `:required` - An int representing the number of nodes that should be alive to respect `:cl`.
|
21
|
+
# * `:alive` - An int representing the number of replica that were known to be
|
22
|
+
# alive when the request has been processed (since an unavailable
|
23
|
+
# exception has been triggered, there will be `:alive` < `:required`.
|
24
|
+
UNAVAILABLE = 0x1000
|
25
|
+
|
26
|
+
# The request cannot be processed because the coordinator node is overloaded.
|
27
|
+
OVERLOADED = 0x1001
|
28
|
+
|
29
|
+
# The request was a read request but the coordinator node is bootstrapping.
|
30
|
+
IS_BOOTSTRAPPING = 0x1002
|
31
|
+
|
32
|
+
# Error during a truncation error.
|
33
|
+
TRUNCATE_ERROR = 0x1003
|
34
|
+
|
35
|
+
# Timeout exception during a write request.
|
36
|
+
#
|
37
|
+
# Details:
|
38
|
+
#
|
39
|
+
# * `:cl` - The consistency level of the query having triggered the exception.
|
40
|
+
# * `:received` - An int representing the number of nodes having acknowledged the request.
|
41
|
+
# * `:blockfor` - The number of replica whose acknowledgement is required to achieve `:cl`.
|
42
|
+
# * `:write_type` - A string that describe the type of the write that timeouted. The value of that string can be one of:
|
43
|
+
# - `"SIMPLE"`: the write was a non-batched non-counter write.
|
44
|
+
# - `"BATCH"`: the write was a (logged) batch write. If this type is received, it means the batch log
|
45
|
+
# has been successfully written (otherwise a `"BATCH_LOG"` type would have been send instead).
|
46
|
+
# - `"UNLOGGED_BATCH"`: the write was an unlogged batch. Not batch log write has been attempted.
|
47
|
+
# - `"COUNTER"`: the write was a counter write (batched or not).
|
48
|
+
# - `"BATCH_LOG"`: the timeout occured during the write to the batch log when a (logged) batch write was requested.
|
49
|
+
WRITE_TIMEOUT = 0x1100
|
50
|
+
|
51
|
+
# Timeout exception during a read request.
|
52
|
+
#
|
53
|
+
# Details:
|
54
|
+
#
|
55
|
+
# * `:cl` - The consistency level of the query having triggered the exception
|
56
|
+
# * `:received` - An int representing the number of nodes having answered the request.
|
57
|
+
# * `:blockfor` - The number of replica whose response is required to achieve `:cl`.
|
58
|
+
# Please note that it is possible to have `:received` >= `:blockfor` if
|
59
|
+
# `:data_present` is false. And also in the (unlikely) case were `:cl` is
|
60
|
+
# achieved but the coordinator node timeout while waiting for read-repair
|
61
|
+
# acknowledgement.
|
62
|
+
# * `:data_present` - If `true`, it means the replica that was asked for data has not responded.
|
63
|
+
READ_TIMEOUT = 0x1200
|
64
|
+
|
65
|
+
# The submitted query has a syntax error.
|
66
|
+
SYNTAX_ERROR = 0x2000
|
67
|
+
|
68
|
+
# The logged user doesn't have the right to perform the query.
|
69
|
+
UNAUTHORIZED = 0x2100
|
70
|
+
|
71
|
+
# The query is syntactically correct but invalid.
|
72
|
+
INVALID = 0x2200
|
73
|
+
|
74
|
+
# The query is invalid because of some configuration issue.
|
75
|
+
CONFIG_ERROR = 0x2300
|
76
|
+
|
77
|
+
# The query attempted to create a keyspace or a table that was already existing.
|
78
|
+
#
|
79
|
+
# Details:
|
80
|
+
#
|
81
|
+
# * `:ks` - A string representing either the keyspace that already exists, or the
|
82
|
+
# keyspace in which the table that already exists is.
|
83
|
+
# * `:table` - A string representing the name of the table that already exists. If the
|
84
|
+
# query was attempting to create a keyspace, `:table` will be present but
|
85
|
+
# will be the empty string.
|
86
|
+
ALREADY_EXISTS = 0x2400
|
87
|
+
|
88
|
+
# Can be thrown while a prepared statement tries to be executed if the
|
89
|
+
# provide prepared statement ID is not known by this host.
|
90
|
+
#
|
91
|
+
# Details:
|
92
|
+
#
|
93
|
+
# * `:id` - The unknown ID.
|
94
|
+
UNPREPARED = 0x2500
|
95
|
+
end
|
96
|
+
end
|
@@ -26,6 +26,7 @@ module Cql
|
|
26
26
|
@connection.on_data(&method(:receive_data))
|
27
27
|
@connection.on_closed(&method(:socket_closed))
|
28
28
|
@promises = Array.new(128) { nil }
|
29
|
+
@free_promises = (0...@promises.size).to_a
|
29
30
|
@read_buffer = CqlByteBuffer.new
|
30
31
|
@frame_encoder = FrameEncoder.new(protocol_version, @compressor)
|
31
32
|
@frame_decoder = FrameDecoder.new(@compressor)
|
@@ -128,7 +129,7 @@ module Cql
|
|
128
129
|
id = nil
|
129
130
|
@lock.lock
|
130
131
|
begin
|
131
|
-
if (id =
|
132
|
+
if (id = @free_promises.pop)
|
132
133
|
@promises[id] = promise
|
133
134
|
end
|
134
135
|
ensure
|
@@ -195,15 +196,25 @@ module Cql
|
|
195
196
|
def receive_data(data)
|
196
197
|
@read_buffer << data
|
197
198
|
@current_frame = @frame_decoder.decode_frame(@read_buffer, @current_frame)
|
199
|
+
promise_responses = []
|
198
200
|
while @current_frame.complete?
|
199
201
|
id = @current_frame.stream_id
|
202
|
+
body = @current_frame.body
|
200
203
|
if id == -1
|
201
|
-
notify_event_listeners(
|
204
|
+
notify_event_listeners(body)
|
202
205
|
else
|
203
|
-
complete_request(id,
|
206
|
+
promise_responses << complete_request(id, body)
|
204
207
|
end
|
205
208
|
@current_frame = @frame_decoder.decode_frame(@read_buffer)
|
206
209
|
end
|
210
|
+
unless promise_responses.empty?
|
211
|
+
flush_request_queue
|
212
|
+
promise_responses.each do |(promise, response)|
|
213
|
+
unless promise.timed_out?
|
214
|
+
promise.fulfill(response)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
207
218
|
end
|
208
219
|
|
209
220
|
def notify_event_listeners(event_response)
|
@@ -211,12 +222,11 @@ module Cql
|
|
211
222
|
@lock.lock
|
212
223
|
begin
|
213
224
|
event_listeners = @event_listeners
|
214
|
-
return if event_listeners.empty?
|
215
225
|
ensure
|
216
226
|
@lock.unlock
|
217
227
|
end
|
218
228
|
event_listeners.each do |listener|
|
219
|
-
listener.call(
|
229
|
+
listener.call(event_response) rescue nil
|
220
230
|
end
|
221
231
|
end
|
222
232
|
|
@@ -226,16 +236,14 @@ module Cql
|
|
226
236
|
begin
|
227
237
|
promise = @promises[id]
|
228
238
|
@promises[id] = nil
|
239
|
+
@free_promises << id
|
229
240
|
ensure
|
230
241
|
@lock.unlock
|
231
242
|
end
|
232
243
|
if response.is_a?(Protocol::SetKeyspaceResultResponse)
|
233
244
|
@keyspace = response.keyspace
|
234
245
|
end
|
235
|
-
|
236
|
-
unless promise.timed_out?
|
237
|
-
promise.fulfill(response)
|
238
|
-
end
|
246
|
+
[promise, response]
|
239
247
|
end
|
240
248
|
|
241
249
|
def flush_request_queue
|
@@ -253,9 +261,10 @@ module Cql
|
|
253
261
|
frame = nil
|
254
262
|
@lock.lock
|
255
263
|
begin
|
256
|
-
if @request_queue_out.any? && (id =
|
264
|
+
if @request_queue_out.any? && (id = @free_promises.pop)
|
257
265
|
promise = @request_queue_out.shift
|
258
266
|
if promise.timed_out?
|
267
|
+
@free_promises << id
|
259
268
|
next
|
260
269
|
else
|
261
270
|
frame = promise.frame
|
@@ -282,6 +291,7 @@ module Cql
|
|
282
291
|
promises_to_fail.concat(@request_queue_in)
|
283
292
|
promises_to_fail.concat(@request_queue_out)
|
284
293
|
@promises.fill(nil)
|
294
|
+
@free_promises = (0...@promises.size).to_a
|
285
295
|
@request_queue_in.clear
|
286
296
|
@request_queue_out.clear
|
287
297
|
end
|
@@ -294,14 +304,6 @@ module Cql
|
|
294
304
|
@closed_promise.fulfill
|
295
305
|
end
|
296
306
|
end
|
297
|
-
|
298
|
-
def next_stream_id
|
299
|
-
if (stream_id = @promises.index(nil))
|
300
|
-
stream_id
|
301
|
-
else
|
302
|
-
nil
|
303
|
-
end
|
304
|
-
end
|
305
307
|
end
|
306
308
|
end
|
307
309
|
end
|
@@ -13,24 +13,24 @@ module Cql
|
|
13
13
|
def self.decode(code, message, protocol_version, buffer, length, trace_id=nil)
|
14
14
|
details = {}
|
15
15
|
case code
|
16
|
-
when
|
16
|
+
when UNAVAILABLE
|
17
17
|
details[:cl] = buffer.read_consistency
|
18
18
|
details[:required] = buffer.read_int
|
19
19
|
details[:alive] = buffer.read_int
|
20
|
-
when
|
20
|
+
when WRITE_TIMEOUT
|
21
21
|
details[:cl] = buffer.read_consistency
|
22
22
|
details[:received] = buffer.read_int
|
23
23
|
details[:blockfor] = buffer.read_int
|
24
24
|
details[:write_type] = buffer.read_string
|
25
|
-
when
|
25
|
+
when READ_TIMEOUT
|
26
26
|
details[:cl] = buffer.read_consistency
|
27
27
|
details[:received] = buffer.read_int
|
28
28
|
details[:blockfor] = buffer.read_int
|
29
29
|
details[:data_present] = buffer.read_byte != 0
|
30
|
-
when
|
30
|
+
when ALREADY_EXISTS
|
31
31
|
details[:ks] = buffer.read_string
|
32
32
|
details[:table] = buffer.read_string
|
33
|
-
when
|
33
|
+
when UNPREPARED
|
34
34
|
details[:id] = buffer.read_short_bytes
|
35
35
|
end
|
36
36
|
new(code, message, details)
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Cql
|
4
4
|
module Protocol
|
5
5
|
class ErrorResponse < Response
|
6
|
+
include ErrorCodes
|
7
|
+
|
6
8
|
attr_reader :code, :message
|
7
9
|
|
8
10
|
def initialize(*args)
|
@@ -13,7 +15,7 @@ module Cql
|
|
13
15
|
code = buffer.read_int
|
14
16
|
message = buffer.read_string
|
15
17
|
case code
|
16
|
-
when
|
18
|
+
when UNAVAILABLE, WRITE_TIMEOUT, READ_TIMEOUT, ALREADY_EXISTS, UNPREPARED
|
17
19
|
new_length = length - 4 - 4 - message.bytesize
|
18
20
|
DetailedErrorResponse.decode(code, message, protocol_version, buffer, new_length)
|
19
21
|
else
|
data/lib/cql/version.rb
CHANGED
@@ -295,6 +295,25 @@ module Cql
|
|
295
295
|
statement.execute(11, 'hello', trace: true).value
|
296
296
|
tracing.should be_true
|
297
297
|
end
|
298
|
+
|
299
|
+
it 'prepares the statement again when it is lost' do
|
300
|
+
prepare_requests = 0
|
301
|
+
connections.each do |c|
|
302
|
+
c[:num_prepare] = 0
|
303
|
+
c.handle_request do |r, t|
|
304
|
+
if r.is_a?(Protocol::ExecuteRequest) && c[:num_prepare] == 1
|
305
|
+
Protocol::ErrorResponse.new(0x2500, 'Unprepared')
|
306
|
+
else
|
307
|
+
if r == Protocol::PrepareRequest.new(cql)
|
308
|
+
c[:num_prepare] += 1
|
309
|
+
end
|
310
|
+
handle_request(c, r, t)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
statement.execute(11, 'hello').value
|
315
|
+
connections.map { |c| c[:num_prepare] }.should include(2)
|
316
|
+
end
|
298
317
|
end
|
299
318
|
|
300
319
|
describe '#batch' do
|
@@ -69,20 +69,40 @@ module Cql
|
|
69
69
|
|
70
70
|
describe '#send_request' do
|
71
71
|
before do
|
72
|
-
connection.stub(:write)
|
72
|
+
connection.stub(:write) do |*args, &block|
|
73
|
+
if block
|
74
|
+
block.call(buffer)
|
75
|
+
else
|
76
|
+
buffer.append(*args)
|
77
|
+
end
|
78
|
+
end
|
73
79
|
connection.stub(:closed?).and_return(false)
|
74
80
|
connection.stub(:connected?).and_return(true)
|
75
81
|
end
|
76
82
|
|
77
83
|
it 'encodes a request frame and writes to the socket handler' do
|
78
84
|
protocol_handler.send_request(request)
|
79
|
-
|
85
|
+
version, flags, stream_id, opcode, size, body = *buffer.to_s.unpack('C4Na*')
|
86
|
+
version.should eq(1)
|
87
|
+
flags.should eq(0)
|
88
|
+
stream_id.should be_between(0, 127)
|
89
|
+
opcode.should eq(5)
|
90
|
+
size.should eq(0)
|
91
|
+
body.should be_empty
|
80
92
|
end
|
81
93
|
|
82
94
|
it 'encodes a frame with the next available stream ID' do
|
83
95
|
protocol_handler.send_request(request)
|
84
96
|
protocol_handler.send_request(request)
|
85
|
-
|
97
|
+
version1, flags1, stream_id1, opcode1, size1,
|
98
|
+
version2, flags2, stream_id2, opcode2, size2, body2 = *buffer.to_s.unpack('C4NC4Na*')
|
99
|
+
version2.should eq(version1)
|
100
|
+
flags2.should eq(flags1)
|
101
|
+
stream_id2.should be_between(0, 127)
|
102
|
+
stream_id2.should_not eq(stream_id1)
|
103
|
+
opcode2.should eq(opcode1)
|
104
|
+
size2.should eq(size1)
|
105
|
+
body2.should be_empty
|
86
106
|
end
|
87
107
|
|
88
108
|
it 'returns a future' do
|
@@ -91,34 +111,40 @@ module Cql
|
|
91
111
|
|
92
112
|
it 'succeeds the future when it receives a response frame with the corresponding stream ID' do
|
93
113
|
3.times { protocol_handler.send_request(request) }
|
114
|
+
buffer.discard(buffer.size)
|
94
115
|
future = protocol_handler.send_request(request)
|
95
|
-
|
116
|
+
stream_id = buffer.to_s.getbyte(2)
|
117
|
+
connection.data_listener.call([0x81, 0, stream_id, 2, 0].pack('C4N'))
|
96
118
|
await(0.1) { future.resolved? }
|
97
119
|
end
|
98
120
|
|
99
121
|
it 'handles multiple response frames in the same data packet' do
|
100
122
|
futures = Array.new(4) { protocol_handler.send_request(request) }
|
101
|
-
|
123
|
+
stream_ids = buffer.to_s.unpack('C4NC4NC4NC4N').values_at(2, 7, 12, 17)
|
124
|
+
connection.data_listener.call([0x81, 0, stream_ids[2], 2, 0].pack('C4N') + [0x81, 0, stream_ids[3], 2, 0].pack('C4N'))
|
102
125
|
await(0.1) { futures[2].resolved? && futures[3].resolved? }
|
103
126
|
end
|
104
127
|
|
105
128
|
it 'queues the request when there are too many in flight, sending it as soon as a stream is available' do
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
futures[
|
110
|
-
futures
|
111
|
-
|
112
|
-
|
129
|
+
futures = Array.new(128) { protocol_handler.send_request(request) }
|
130
|
+
buffer.discard(buffer.size)
|
131
|
+
delayed = Array.new(2) { protocol_handler.send_request(request) }
|
132
|
+
futures.size.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
|
133
|
+
futures.each { |future| future.should be_resolved }
|
134
|
+
delayed.each { |future| future.should_not be_resolved }
|
135
|
+
stream_ids = buffer.to_s.unpack('C4NC4N').values_at(2, 7)
|
136
|
+
stream_ids.each { |id| connection.data_listener.call([0x81, 0, id, 2, 0].pack('C4N')) }
|
137
|
+
delayed.each { |future| future.should be_resolved }
|
113
138
|
end
|
114
139
|
|
115
140
|
it 'flushes the request queue before it resolves the future of the just completed request' do
|
116
|
-
|
117
|
-
|
118
|
-
|
141
|
+
f = protocol_handler.send_request(request)
|
142
|
+
stream_id = buffer.to_s.getbyte(2)
|
143
|
+
129.times { protocol_handler.send_request(request) }
|
144
|
+
f = f.map do
|
119
145
|
connection.should have_received(:write).exactly(129).times
|
120
146
|
end
|
121
|
-
connection.data_listener.call([0x81, 0,
|
147
|
+
connection.data_listener.call([0x81, 0, stream_id, 2, 0].pack('C4N'))
|
122
148
|
f.value
|
123
149
|
end
|
124
150
|
|
@@ -142,7 +168,13 @@ module Cql
|
|
142
168
|
|
143
169
|
it 'compresses request frames' do
|
144
170
|
protocol_handler.send_request(request)
|
145
|
-
|
171
|
+
version, flags, stream_id, opcode, size, body = *buffer.to_s.unpack('C4Na*')
|
172
|
+
version.should eq(1)
|
173
|
+
flags.should eq(1)
|
174
|
+
stream_id.should be_between(0, 127)
|
175
|
+
opcode.should eq(9)
|
176
|
+
size.should eq(18)
|
177
|
+
body.should eq('FAKECOMPRESSEDBODY')
|
146
178
|
end
|
147
179
|
|
148
180
|
it 'compresses queued request frames' do
|
@@ -155,8 +187,10 @@ module Cql
|
|
155
187
|
compressor.stub(:decompress).with('FAKECOMPRESSEDBODY').and_return("\x00\x00\x00\x04" + "\x00\x10" + id + "\x00\x00\x00\x01\x00\x00\x00\x01\x00\ncql_rb_911\x00\x05users\x00\tuser_name\x00\r")
|
156
188
|
f1 = protocol_handler.send_request(request)
|
157
189
|
f2 = protocol_handler.send_request(request)
|
158
|
-
|
159
|
-
|
190
|
+
stream_ids = buffer.to_s.unpack('C4Na18C4N').values_at(2, 8)
|
191
|
+
stream_ids.each do |id|
|
192
|
+
connection.data_listener.call([0x81, 1, id, 8, 18, 'FAKECOMPRESSEDBODY'].pack('C4Na*'))
|
193
|
+
end
|
160
194
|
f1.value.should == Protocol::PreparedResultResponse.new(id, [["cql_rb_911", "users", "user_name", :varchar]], nil, nil)
|
161
195
|
f2.value.should == Protocol::PreparedResultResponse.new(id, [["cql_rb_911", "users", "user_name", :varchar]], nil, nil)
|
162
196
|
end
|
@@ -189,7 +223,8 @@ module Cql
|
|
189
223
|
it 'fails all requests with ConnectionClosedError if there is no specific error' do
|
190
224
|
protocol_handler.send_request(request)
|
191
225
|
future = protocol_handler.send_request(request)
|
192
|
-
|
226
|
+
stream_id = buffer.to_s.getbyte(2)
|
227
|
+
connection.data_listener.call([0x81, 0, stream_id, 2, 0].pack('C4N'))
|
193
228
|
connection.closed_listener.call(nil)
|
194
229
|
begin
|
195
230
|
future.value
|
@@ -234,8 +269,9 @@ module Cql
|
|
234
269
|
|
235
270
|
it 'does not attempt to fulfill the promise when the request has already timed out' do
|
236
271
|
f = protocol_handler.send_request(request, 3)
|
272
|
+
stream_id = buffer.to_s.getbyte(2)
|
237
273
|
timer_promise.fulfill
|
238
|
-
expect { connection.data_listener.call([0x81, 0,
|
274
|
+
expect { connection.data_listener.call([0x81, 0, stream_id, 2, 0].pack('C4N')) }.to_not raise_error
|
239
275
|
end
|
240
276
|
|
241
277
|
it 'never sends a request when it has already timed out' do
|
@@ -300,7 +336,7 @@ module Cql
|
|
300
336
|
before do
|
301
337
|
connection.stub(:closed?).and_return(false)
|
302
338
|
connection.stub(:connected?).and_return(true)
|
303
|
-
connection.stub(:write)
|
339
|
+
connection.stub(:write).and_yield(buffer)
|
304
340
|
end
|
305
341
|
|
306
342
|
it 'is not in a keyspace initially' do
|
@@ -309,7 +345,8 @@ module Cql
|
|
309
345
|
|
310
346
|
it 'registers the keyspace it has changed to' do
|
311
347
|
f = protocol_handler.send_request(Protocol::QueryRequest.new('USE hello', nil, nil, :one))
|
312
|
-
|
348
|
+
stream_id = buffer.to_s.getbyte(2)
|
349
|
+
connection.data_listener.call([0x81, 0, stream_id, 8, 4 + 2 + 5, 3, 5].pack('C4N2n') + 'hello')
|
313
350
|
f.value
|
314
351
|
protocol_handler.keyspace.should == 'hello'
|
315
352
|
end
|
@@ -112,6 +112,13 @@ describe 'A CQL client' do
|
|
112
112
|
counters = result.each_with_object({}) { |row, acc| acc[row['id']] = row['count'] }
|
113
113
|
counters.should eql('foo' => 11, 'bar' => 3)
|
114
114
|
end
|
115
|
+
|
116
|
+
it 'handles altered tables' do
|
117
|
+
result1 = statement.execute('sue')
|
118
|
+
client.execute('ALTER TABLE users ADD password VARCHAR')
|
119
|
+
result2 = statement.execute('sue')
|
120
|
+
result2.to_a.should eql(result1.to_a)
|
121
|
+
end
|
115
122
|
end
|
116
123
|
|
117
124
|
context 'with multiple connections' do
|
@@ -512,7 +519,7 @@ describe 'A CQL client' do
|
|
512
519
|
end
|
513
520
|
|
514
521
|
it 'fails gracefully when connecting to something that does not run C*' do
|
515
|
-
expect { Cql::Client.connect(host: '
|
522
|
+
expect { Cql::Client.connect(host: 'yahoo.com') }.to raise_error(Cql::Io::ConnectionTimeoutError)
|
516
523
|
end
|
517
524
|
end
|
518
525
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cql-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.1.0.
|
4
|
+
version: 2.1.0.pre2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Theo Hultberg
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-05-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ione
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: 1.3.0.pre2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: 1.3.0.pre2
|
27
27
|
description: A pure Ruby CQL3 driver for Cassandra
|
28
28
|
email:
|
29
29
|
- theo@iconara.net
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- lib/cql/compression.rb
|
55
55
|
- lib/cql/compression/lz4_compressor.rb
|
56
56
|
- lib/cql/compression/snappy_compressor.rb
|
57
|
+
- lib/cql/error_codes.rb
|
57
58
|
- lib/cql/protocol.rb
|
58
59
|
- lib/cql/protocol/cql_byte_buffer.rb
|
59
60
|
- lib/cql/protocol/cql_protocol_handler.rb
|