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