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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a2e1e800f2f73c9c9dd0c2beedaf55851174362
4
- data.tar.gz: bf02fef5b52739fd1954f3ffcc4322a5c7ff1c01
3
+ metadata.gz: 5feed4b6caaa0042eda3f44328ce6f2132519fad
4
+ data.tar.gz: 975d84488e3f0e023d1846e9facd8c293a43634f
5
5
  SHA512:
6
- metadata.gz: edfc218ddb4b1b5b1ca3f8d0a4fd0888e9bfa9bdc9b1ac974cf881e1dc9b853adc44d5b940fe3f953d6647beac297f99d8614fd36e1dc8ad1892fde752f2e9f1
7
- data.tar.gz: f6ced79805a70ea44f4b3844480f56dbbd82bcbaaacbe86c2ad75725e9563d5c5f2d78a08d15bfd763ec4964251c1461eadb9f4cfde4112b0841729deba7bfc5
6
+ metadata.gz: f9b8860656c26696c6ac5447e02385b56096ba65b400fc686d77fd66682c74a85f135c98d1364e106f9f2b7a3e8dbb4f0ece9512c2be2ac62fc15452c40724e4
7
+ data.tar.gz: 4fa9ce7ca66ea3e84a99206fb11df31a5acbee4e4efdbc7bee346af64709cd65520d692d33dbd930b38b342824f3b3c635d25e0d82b00249c0269524eb872c9f
data/lib/cql.rb CHANGED
@@ -17,6 +17,7 @@ module Cql
17
17
  Io = Ione::Io
18
18
  end
19
19
 
20
+ require 'cql/error_codes'
20
21
  require 'cql/uuid'
21
22
  require 'cql/time_uuid'
22
23
  require 'cql/compression'
@@ -6,6 +6,8 @@ module Cql
6
6
  # if any. `message` contains the human readable error message sent by the
7
7
  # server.
8
8
  class QueryError < CqlError
9
+ include ErrorCodes
10
+
9
11
  attr_reader :code, :cql, :details
10
12
 
11
13
  def initialize(code, message, cql=nil, details=nil)
@@ -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 == 0x0a && @protocol_version > 1
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.concat(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.delete(connection)
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
- @lock.synchronize do
29
- @connections.any?
30
- end
28
+ !snapshot.empty?
31
29
  end
32
30
 
33
31
  def snapshot
34
- @lock.synchronize do
35
- @connections.dup
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
- raise NotConnectedError unless connected?
41
- @lock.synchronize do
42
- @connections.sample
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
- raise NotConnectedError unless connected?
49
- @lock.synchronize do
50
- @connections.each(&callback)
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
@@ -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?(Cql::QueryError) && e.code == 0x100
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.shift(@raw_metadata.size)
220
- unless bound_args.size == @raw_metadata.size && args.size <= 1
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(args.last)
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 = next_stream_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(@current_frame.body)
204
+ notify_event_listeners(body)
202
205
  else
203
- complete_request(id, @current_frame.body)
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(@current_frame.body) rescue nil
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
- flush_request_queue
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 = next_stream_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 0x1000 # unavailable
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 0x1100 # write_timeout
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 0x1200 # read_timeout
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 0x2400 # already_exists
30
+ when ALREADY_EXISTS
31
31
  details[:ks] = buffer.read_string
32
32
  details[:table] = buffer.read_string
33
- when 0x2500
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 0x1000, 0x1100, 0x1200, 0x2400, 0x2500
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '2.1.0.pre1'.freeze
4
+ VERSION = '2.1.0.pre2'.freeze
5
5
  end
@@ -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).and_yield(buffer)
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
- buffer.to_s.should == [1, 0, 0, 5, 0].pack('C4N')
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
- buffer.to_s.should == [1, 0, 0, 5, 0].pack('C4N') + [1, 0, 1, 5, 0].pack('C4N')
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
- connection.data_listener.call([0x81, 0, 3, 2, 0].pack('C4N'))
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
- connection.data_listener.call([0x81, 0, 2, 2, 0].pack('C4N') + [0x81, 0, 3, 2, 0].pack('C4N'))
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
- connection.stub(:write)
107
- futures = Array.new(130) { protocol_handler.send_request(request) }
108
- 128.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
109
- futures[127].should be_resolved
110
- futures[128].should_not be_resolved
111
- 2.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
112
- futures[128].should be_resolved
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
- connection.stub(:write)
117
- futures = Array.new(130) { protocol_handler.send_request(request) }
118
- f = futures[0].map do
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, 0, 2, 0].pack('C4N'))
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
- buffer.to_s.should == [1, 1, 0, 9, 18].pack('C4N') + 'FAKECOMPRESSEDBODY'
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
- connection.data_listener.call("\x81\x01\x00\x08\x00\x00\x00\x12FAKECOMPRESSEDBODY")
159
- connection.data_listener.call("\x81\x01\x01\x08\x00\x00\x00\x12FAKECOMPRESSEDBODY")
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
- connection.data_listener.call([0x81, 0, 0, 2, 0].pack('C4N'))
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, 0, 2, 0].pack('C4N')) }.to_not raise_error
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
- connection.data_listener.call([0x81, 0, 0, 8, 4 + 2 + 5, 3, 5].pack('C4N2n') + 'hello')
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: 'google.com') }.to raise_error(Cql::Io::ConnectionTimeoutError)
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.pre1
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: 2014-10-26 00:00:00.000000000 Z
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.2.0.pre3
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.2.0.pre3
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