cql-rb 2.1.0.pre1 → 2.1.0.pre2
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.
- 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
|