cql-rb 1.0.6 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.md +4 -9
  2. data/lib/cql.rb +1 -0
  3. data/lib/cql/byte_buffer.rb +23 -7
  4. data/lib/cql/client.rb +11 -6
  5. data/lib/cql/client/asynchronous_client.rb +37 -83
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
  7. data/lib/cql/client/column_metadata.rb +16 -0
  8. data/lib/cql/client/request_runner.rb +46 -0
  9. data/lib/cql/future.rb +4 -5
  10. data/lib/cql/io.rb +2 -5
  11. data/lib/cql/io/connection.rb +220 -0
  12. data/lib/cql/io/io_reactor.rb +213 -185
  13. data/lib/cql/protocol.rb +1 -0
  14. data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
  15. data/lib/cql/protocol/decoding.rb +6 -31
  16. data/lib/cql/protocol/encoding.rb +1 -5
  17. data/lib/cql/protocol/request.rb +4 -0
  18. data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
  19. data/lib/cql/protocol/type_converter.rb +56 -76
  20. data/lib/cql/time_uuid.rb +104 -0
  21. data/lib/cql/uuid.rb +4 -2
  22. data/lib/cql/version.rb +1 -1
  23. data/spec/cql/client/asynchronous_client_spec.rb +47 -71
  24. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
  25. data/spec/cql/client/client_shared.rb +3 -3
  26. data/spec/cql/client/column_metadata_spec.rb +80 -0
  27. data/spec/cql/client/request_runner_spec.rb +120 -0
  28. data/spec/cql/future_spec.rb +26 -11
  29. data/spec/cql/io/connection_spec.rb +460 -0
  30. data/spec/cql/io/io_reactor_spec.rb +212 -265
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
  32. data/spec/cql/protocol/decoding_spec.rb +9 -28
  33. data/spec/cql/protocol/encoding_spec.rb +0 -5
  34. data/spec/cql/protocol/request_spec.rb +16 -0
  35. data/spec/cql/protocol/response_frame_spec.rb +2 -2
  36. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
  37. data/spec/cql/time_uuid_spec.rb +136 -0
  38. data/spec/cql/uuid_spec.rb +1 -5
  39. data/spec/integration/client_spec.rb +34 -38
  40. data/spec/integration/io_spec.rb +283 -0
  41. data/spec/integration/protocol_spec.rb +53 -113
  42. data/spec/integration/regression_spec.rb +124 -0
  43. data/spec/integration/uuid_spec.rb +76 -0
  44. data/spec/spec_helper.rb +12 -9
  45. data/spec/support/fake_io_reactor.rb +52 -21
  46. data/spec/support/fake_server.rb +2 -2
  47. metadata +33 -10
  48. checksums.yaml +0 -15
  49. data/lib/cql/io/node_connection.rb +0 -209
  50. data/spec/cql/protocol/type_converter_spec.rb +0 -52
data/lib/cql/protocol.rb CHANGED
@@ -66,3 +66,4 @@ require 'cql/protocol/requests/query_request'
66
66
  require 'cql/protocol/requests/prepare_request'
67
67
  require 'cql/protocol/requests/execute_request'
68
68
  require 'cql/protocol/response_frame'
69
+ require 'cql/protocol/cql_protocol_handler'
@@ -0,0 +1,201 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ # This class wraps a single connection and translates between request/
6
+ # response frames and raw bytes.
7
+ #
8
+ # You send requests with #send_request, and receive responses through the
9
+ # returned future.
10
+ #
11
+ # Instances of this class are thread safe.
12
+ #
13
+ # @examle Sending an OPTIONS request
14
+ # future = protocol_handler.send_request(Cql::Protocol::OptionsRequest.new)
15
+ # response = future.get
16
+ # puts "These options are supported: #{response.options}"
17
+ #
18
+ class CqlProtocolHandler
19
+ # @return [String] the current keyspace for the underlying connection
20
+ attr_reader :keyspace
21
+
22
+ def initialize(connection)
23
+ @connection = connection
24
+ @connection.on_data(&method(:receive_data))
25
+ @connection.on_closed(&method(:socket_closed))
26
+ @responses = Array.new(128) { nil }
27
+ @read_buffer = ByteBuffer.new
28
+ @current_frame = Protocol::ResponseFrame.new(@read_buffer)
29
+ @request_queue_in = []
30
+ @request_queue_out = []
31
+ @event_listeners = []
32
+ @lock = Mutex.new
33
+ @closed_future = Future.new
34
+ @keyspace = nil
35
+ end
36
+
37
+ # @return [true, false] true if the underlying connection is connected
38
+ def connected?
39
+ @connection.connected?
40
+ end
41
+
42
+ # @return [true, false] true if the underlying connection is closed
43
+ def closed?
44
+ @connection.closed?
45
+ end
46
+
47
+ # Register to receive notification when the underlying connection has
48
+ # closed. If the connection closed abruptly the error will be passed
49
+ # to the listener, otherwise it will not receive any parameters.
50
+ #
51
+ # @yieldparam error [nil, Error] the error that caused the connection to
52
+ # close, if any
53
+ def on_closed(&listener)
54
+ @closed_future.on_complete(&listener)
55
+ @closed_future.on_failure(&listener)
56
+ end
57
+
58
+ # Register to receive server sent events, like schema changes, nodes going
59
+ # up or down, etc. To actually receive events you also need to send a
60
+ # REGISTER request for the events you wish to receive.
61
+ #
62
+ # @yieldparam event [Cql::Protocol::EventResponse] an event sent by the server
63
+ def on_event(&listener)
64
+ @lock.synchronize do
65
+ @event_listeners << listener
66
+ end
67
+ end
68
+
69
+ # Serializes and send a request over the underlying connection.
70
+ #
71
+ # Returns a future that will resolve to the response. When the connection
72
+ # closes the futures of all active requests will be failed with the error
73
+ # that caused the connection to close, or nil
74
+ #
75
+ # @return [Cql::Future<Cql::Protocol::Response>] a future that resolves to
76
+ # the response
77
+ def send_request(request)
78
+ return Future.failed(NotConnectedError.new) if closed?
79
+ future = Future.new
80
+ id = nil
81
+ @lock.synchronize do
82
+ if (id = next_stream_id)
83
+ @responses[id] = future
84
+ end
85
+ end
86
+ if id
87
+ @connection.write do |buffer|
88
+ request.encode_frame(id, buffer)
89
+ end
90
+ else
91
+ @lock.synchronize do
92
+ @request_queue_in << [request.encode_frame(0), future]
93
+ end
94
+ end
95
+ future
96
+ end
97
+
98
+ # Closes the underlying connection.
99
+ #
100
+ # @return [Cql::Future] a future that completes when the connection has closed
101
+ def close
102
+ @connection.close
103
+ @closed_future
104
+ end
105
+
106
+ private
107
+
108
+ def receive_data(data)
109
+ @current_frame << data
110
+ while @current_frame.complete?
111
+ id = @current_frame.stream_id
112
+ if id == -1
113
+ notify_event_listeners(@current_frame.body)
114
+ else
115
+ complete_request(id, @current_frame.body)
116
+ end
117
+ @current_frame = Protocol::ResponseFrame.new(@read_buffer)
118
+ flush_request_queue
119
+ end
120
+ end
121
+
122
+ def notify_event_listeners(event_response)
123
+ return if @event_listeners.empty?
124
+ @lock.synchronize do
125
+ @event_listeners.each do |listener|
126
+ listener.call(@current_frame.body) rescue nil
127
+ end
128
+ end
129
+ end
130
+
131
+ def complete_request(id, response)
132
+ future = @lock.synchronize do
133
+ future = @responses[id]
134
+ @responses[id] = nil
135
+ future
136
+ end
137
+ if response.is_a?(Protocol::SetKeyspaceResultResponse)
138
+ @keyspace = response.keyspace
139
+ end
140
+ future.complete!(response)
141
+ end
142
+
143
+ def flush_request_queue
144
+ @lock.synchronize do
145
+ if @request_queue_out.empty? && !@request_queue_in.empty?
146
+ @request_queue_out = @request_queue_in
147
+ @request_queue_in = []
148
+ end
149
+ end
150
+ while true
151
+ id = nil
152
+ request_buffer = nil
153
+ @lock.synchronize do
154
+ if @request_queue_out.any? && (id = next_stream_id)
155
+ request_buffer, future = @request_queue_out.shift
156
+ @responses[id] = future
157
+ end
158
+ end
159
+ if id
160
+ Protocol::Request.change_stream_id(id, request_buffer)
161
+ @connection.write(request_buffer)
162
+ else
163
+ break
164
+ end
165
+ end
166
+ end
167
+
168
+ def socket_closed(cause)
169
+ request_failure_cause = cause || Io::ConnectionClosedError.new
170
+ @lock.synchronize do
171
+ @responses.each_with_index do |future, i|
172
+ if future
173
+ @responses[i].fail!(request_failure_cause)
174
+ @responses[i] = nil
175
+ end
176
+ end
177
+ @request_queue_in.each do |_, future|
178
+ future.fail!(request_failure_cause)
179
+ end
180
+ @request_queue_in.clear
181
+ @request_queue_out.each do |_, future|
182
+ future.fail!(request_failure_cause)
183
+ end
184
+ @request_queue_out.clear
185
+ if cause
186
+ @closed_future.fail!(cause)
187
+ else
188
+ @closed_future.complete!
189
+ end
190
+ end
191
+ end
192
+
193
+ def next_stream_id
194
+ @responses.each_with_index do |task, index|
195
+ return index if task.nil?
196
+ end
197
+ nil
198
+ end
199
+ end
200
+ end
201
+ end
@@ -31,34 +31,15 @@ module Cql
31
31
  def read_decimal!(buffer, length=buffer.length)
32
32
  size = read_int!(buffer)
33
33
  number_string = read_varint!(buffer, length - 4).to_s
34
- if number_string.length < size
35
- if number_string.start_with?(MINUS)
36
- number_string = number_string[1, number_string.length - 1]
37
- fraction_string = MINUS + ZERO << DECIMAL_POINT
38
- else
39
- fraction_string = ZERO + DECIMAL_POINT
40
- end
41
- (size - number_string.length).times { fraction_string << ZERO }
42
- fraction_string << number_string
43
- else
44
- fraction_string = number_string[0, number_string.length - size]
45
- fraction_string << DECIMAL_POINT
46
- fraction_string << number_string[number_string.length - size, number_string.length]
47
- end
34
+ fraction_string = number_string[0, number_string.length - size] << DECIMAL_POINT << number_string[number_string.length - size, number_string.length]
48
35
  BigDecimal.new(fraction_string)
49
36
  rescue RangeError => e
50
37
  raise DecodingError, e.message, e.backtrace
51
38
  end
52
39
 
53
40
  def read_long!(buffer)
54
- hi, lo = buffer.read(8).unpack(Formats::TWO_INTS_FORMAT)
55
- if (hi > 0x7fffffff)
56
- hi ^= 0xffffffff
57
- lo ^= 0xffffffff
58
- 0 - (hi << 32) - lo - 1
59
- else
60
- (hi << 32) + lo
61
- end
41
+ top, bottom = buffer.read(8).unpack(Formats::TWO_INTS_FORMAT)
42
+ (top << 32) | bottom
62
43
  rescue RangeError => e
63
44
  raise DecodingError, e.message, e.backtrace
64
45
  end
@@ -76,11 +57,7 @@ module Cql
76
57
  end
77
58
 
78
59
  def read_int!(buffer)
79
- val = buffer.read_int
80
- if (val > 0x7fffffff)
81
- val = 0 - ((val - 1) ^ 0xffffffff)
82
- end
83
- val
60
+ buffer.read_int
84
61
  rescue RangeError => e
85
62
  raise DecodingError, "Not enough bytes available to decode an int: #{e.message}", e.backtrace
86
63
  end
@@ -109,8 +86,8 @@ module Cql
109
86
  raise DecodingError, "Not enough bytes available to decode a long string: #{e.message}", e.backtrace
110
87
  end
111
88
 
112
- def read_uuid!(buffer)
113
- Uuid.new(read_varint!(buffer, 16, false))
89
+ def read_uuid!(buffer, impl=Uuid)
90
+ impl.new(read_varint!(buffer, 16, false))
114
91
  rescue RangeError => e
115
92
  raise DecodingError, "Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
116
93
  end
@@ -184,8 +161,6 @@ module Cql
184
161
 
185
162
  private
186
163
 
187
- MINUS = '-'.freeze
188
- ZERO = '0'.freeze
189
164
  DECIMAL_POINT = '.'.freeze
190
165
  end
191
166
  end
@@ -27,11 +27,7 @@ module Cql
27
27
  end
28
28
 
29
29
  def write_uuid(buffer, uuid)
30
- v = uuid.value
31
- write_int(buffer, (v >> 96) & 0xffffffff)
32
- write_int(buffer, (v >> 64) & 0xffffffff)
33
- write_int(buffer, (v >> 32) & 0xffffffff)
34
- write_int(buffer, v & 0xffffffff)
30
+ write_varint(buffer, uuid.value)
35
31
  end
36
32
 
37
33
  def write_string_list(buffer, strs)
@@ -19,6 +19,10 @@ module Cql
19
19
  buffer.update(offset + 4, [(buffer.bytesize - offset - 8)].pack(Formats::INT_FORMAT))
20
20
  buffer
21
21
  end
22
+
23
+ def self.change_stream_id(new_stream_id, buffer, offset=0)
24
+ buffer.update(offset + 2, new_stream_id.chr)
25
+ end
22
26
  end
23
27
  end
24
28
  end
@@ -13,6 +13,21 @@ module Cql
13
13
  new(read_string!(buffer), read_string!(buffer), read_string!(buffer))
14
14
  end
15
15
 
16
+ def eql?(other)
17
+ self.change == other.change && self.keyspace == other.keyspace && self.table == other.table
18
+ end
19
+ alias_method :==, :eql?
20
+
21
+ def hash
22
+ @h ||= begin
23
+ h = 0
24
+ h = ((h & 0xffffffff) * 31) ^ @change.hash
25
+ h = ((h & 0xffffffff) * 31) ^ @keyspace.hash
26
+ h = ((h & 0xffffffff) * 31) ^ @table.hash
27
+ h
28
+ end
29
+ end
30
+
16
31
  def to_s
17
32
  %(RESULT SCHEMA_CHANGE #@change "#@keyspace" "#@table")
18
33
  end
@@ -36,35 +36,27 @@ module Cql
36
36
  def to_bytes(io, type, value, size_bytes=4)
37
37
  case type
38
38
  when Array
39
- unless value.nil? || value.is_a?(Enumerable)
39
+ unless value.is_a?(Enumerable)
40
40
  raise InvalidValueError, 'Value for collection must be enumerable'
41
41
  end
42
42
  case type.first
43
43
  when :list, :set
44
44
  _, sub_type = type
45
- if value
46
- raw = ''
47
- write_short(raw, value.size)
48
- value.each do |element|
49
- to_bytes(raw, sub_type, element, 2)
50
- end
51
- write_bytes(io, raw)
52
- else
53
- nil_to_bytes(io, size_bytes)
45
+ raw = ''
46
+ write_short(raw, value.size)
47
+ value.each do |element|
48
+ to_bytes(raw, sub_type, element, 2)
54
49
  end
50
+ write_bytes(io, raw)
55
51
  when :map
56
52
  _, key_type, value_type = type
57
- if value
58
- raw = ''
59
- write_short(raw, value.size)
60
- value.each do |key, value|
61
- to_bytes(raw, key_type, key, 2)
62
- to_bytes(raw, value_type, value, 2)
63
- end
64
- write_bytes(io, raw)
65
- else
66
- nil_to_bytes(io, size_bytes)
53
+ raw = ''
54
+ write_short(raw, value.size)
55
+ value.each do |key, value|
56
+ to_bytes(raw, key_type, key, 2)
57
+ to_bytes(raw, value_type, value, 2)
67
58
  end
59
+ write_bytes(io, raw)
68
60
  else
69
61
  raise UnsupportedColumnTypeError, %(Unsupported column collection type: #{type.first})
70
62
  end
@@ -96,7 +88,7 @@ module Cql
96
88
  :varchar => method(:bytes_to_varchar),
97
89
  :text => method(:bytes_to_varchar),
98
90
  :varint => method(:bytes_to_varint),
99
- :timeuuid => method(:bytes_to_uuid),
91
+ :timeuuid => method(:bytes_to_timeuuid),
100
92
  :uuid => method(:bytes_to_uuid),
101
93
  :inet => method(:bytes_to_inet),
102
94
  }
@@ -108,7 +100,6 @@ module Cql
108
100
  :bigint => method(:bigint_to_bytes),
109
101
  :blob => method(:blob_to_bytes),
110
102
  :boolean => method(:boolean_to_bytes),
111
- :counter => method(:bigint_to_bytes),
112
103
  :decimal => method(:decimal_to_bytes),
113
104
  :double => method(:double_to_bytes),
114
105
  :float => method(:float_to_bytes),
@@ -197,6 +188,11 @@ module Cql
197
188
  read_uuid!(buffer)
198
189
  end
199
190
 
191
+ def bytes_to_timeuuid(buffer, size_bytes)
192
+ return nil unless read_size(buffer, size_bytes)
193
+ read_uuid!(buffer, TimeUuid)
194
+ end
195
+
200
196
  def bytes_to_inet(buffer, size_bytes)
201
197
  size = read_size(buffer, size_bytes)
202
198
  return nil unless size
@@ -233,7 +229,7 @@ module Cql
233
229
  end
234
230
 
235
231
  def ascii_to_bytes(io, value, size_bytes)
236
- v = value && value.encode(::Encoding::ASCII)
232
+ v = value.encode(::Encoding::ASCII)
237
233
  if size_bytes == 4
238
234
  write_bytes(io, v)
239
235
  else
@@ -242,16 +238,16 @@ module Cql
242
238
  end
243
239
 
244
240
  def bigint_to_bytes(io, value, size_bytes)
245
- if value
246
- size_to_bytes(io, 8, size_bytes)
247
- write_long(io, value)
241
+ if size_bytes == 4
242
+ write_int(io, 8)
248
243
  else
249
- nil_to_bytes(io, size_bytes)
244
+ write_short(io, 8)
250
245
  end
246
+ write_long(io, value)
251
247
  end
252
248
 
253
249
  def blob_to_bytes(io, value, size_bytes)
254
- v = value && value.encode(::Encoding::BINARY)
250
+ v = value.encode(::Encoding::BINARY)
255
251
  if size_bytes == 4
256
252
  write_bytes(io, v)
257
253
  else
@@ -260,16 +256,16 @@ module Cql
260
256
  end
261
257
 
262
258
  def boolean_to_bytes(io, value, size_bytes)
263
- if !value.nil?
264
- size_to_bytes(io, 1, size_bytes)
265
- io << (value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
259
+ if size_bytes == 4
260
+ write_int(io, 1)
266
261
  else
267
- nil_to_bytes(io, size_bytes)
262
+ write_short(io, 1)
268
263
  end
264
+ io << (value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
269
265
  end
270
266
 
271
267
  def decimal_to_bytes(io, value, size_bytes)
272
- raw = value && write_decimal('', value)
268
+ raw = write_decimal('', value)
273
269
  if size_bytes == 4
274
270
  write_bytes(io, raw)
275
271
  else
@@ -278,43 +274,43 @@ module Cql
278
274
  end
279
275
 
280
276
  def double_to_bytes(io, value, size_bytes)
281
- if value
282
- size_to_bytes(io, 8, size_bytes)
283
- write_double(io, value)
277
+ if size_bytes == 4
278
+ write_int(io, 8)
284
279
  else
285
- nil_to_bytes(io, size_bytes)
280
+ write_short(io, 8)
286
281
  end
282
+ write_double(io, value)
287
283
  end
288
284
 
289
285
  def float_to_bytes(io, value, size_bytes)
290
- if value
291
- size_to_bytes(io, 4, size_bytes)
292
- write_float(io, value)
286
+ if size_bytes == 4
287
+ write_int(io, 4)
293
288
  else
294
- nil_to_bytes(io, size_bytes)
289
+ write_short(io, 4)
295
290
  end
291
+ write_float(io, value)
296
292
  end
297
293
 
298
294
  def inet_to_bytes(io, value, size_bytes)
299
- if value
300
- size_to_bytes(io, value.ipv6? ? 16 : 4, size_bytes)
301
- io << value.hton
295
+ if size_bytes == 4
296
+ write_int(io, value.ipv6? ? 16 : 4)
302
297
  else
303
- nil_to_bytes(io, size_bytes)
298
+ write_short(io, value.ipv6? ? 16 : 4)
304
299
  end
300
+ io << value.hton
305
301
  end
306
302
 
307
303
  def int_to_bytes(io, value, size_bytes)
308
- if value
309
- size_to_bytes(io, 4, size_bytes)
310
- write_int(io, value)
304
+ if size_bytes == 4
305
+ write_int(io, 4)
311
306
  else
312
- nil_to_bytes(io, size_bytes)
307
+ write_short(io, 4)
313
308
  end
309
+ write_int(io, value)
314
310
  end
315
311
 
316
312
  def varchar_to_bytes(io, value, size_bytes)
317
- v = value && value.encode(::Encoding::UTF_8)
313
+ v = value.encode(::Encoding::UTF_8)
318
314
  if size_bytes == 4
319
315
  write_bytes(io, v)
320
316
  else
@@ -323,48 +319,32 @@ module Cql
323
319
  end
324
320
 
325
321
  def timestamp_to_bytes(io, value, size_bytes)
326
- if value
327
- ms = (value.to_f * 1000).to_i
328
- size_to_bytes(io, 8, size_bytes)
329
- write_long(io, ms)
322
+ ms = (value.to_f * 1000).to_i
323
+ if size_bytes == 4
324
+ write_int(io, 8)
330
325
  else
331
- nil_to_bytes(io, size_bytes)
326
+ write_short(io, 8)
332
327
  end
328
+ write_long(io, ms)
333
329
  end
334
330
 
335
331
  def uuid_to_bytes(io, value, size_bytes)
336
- if value
337
- size_to_bytes(io, 16, size_bytes)
338
- write_uuid(io, value)
332
+ if size_bytes == 4
333
+ write_int(io, 16)
339
334
  else
340
- nil_to_bytes(io, size_bytes)
335
+ write_short(io, 16)
341
336
  end
337
+ write_uuid(io, value)
342
338
  end
343
339
 
344
340
  def varint_to_bytes(io, value, size_bytes)
345
- raw = value && write_varint('', value)
341
+ raw = write_varint('', value)
346
342
  if size_bytes == 4
347
343
  write_bytes(io, raw)
348
344
  else
349
345
  write_short_bytes(io, raw)
350
346
  end
351
347
  end
352
-
353
- def size_to_bytes(io, size, size_bytes)
354
- if size_bytes == 4
355
- write_int(io, size)
356
- else
357
- write_short(io, size)
358
- end
359
- end
360
-
361
- def nil_to_bytes(io, size_bytes)
362
- if size_bytes == 4
363
- write_int(io, -1)
364
- else
365
- write_short(io, -1)
366
- end
367
- end
368
348
  end
369
349
  end
370
350
  end