cql-rb 1.1.0.pre1 → 1.1.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Ruby CQL3 driver
2
2
 
3
3
  [![Build Status](https://travis-ci.org/iconara/cql-rb.png?branch=master)](https://travis-ci.org/iconara/cql-rb)
4
- [![Coverage Status](https://coveralls.io/repos/iconara/cql-rb/badge.png?branch=io_reactor_rewrite)](https://coveralls.io/r/iconara/cql-rb?branch=io_reactor_rewrite)
4
+ [![Coverage Status](https://coveralls.io/repos/iconara/cql-rb/badge.png)](https://coveralls.io/r/iconara/cql-rb)
5
5
 
6
6
  # Requirements
7
7
 
@@ -28,7 +28,7 @@ rows.each do |row|
28
28
  end
29
29
  ```
30
30
 
31
- when you're done you can call `#close` to disconnect from Cassandra. You can connect to multiple Cassandra nodes by passing multiple comma separated host names to the `:host` option.
31
+ when you're done you can call `#close` to disconnect from Cassandra. You can connect to multiple Cassandra nodes by passing multiple comma separated host names to the `:host` option -- _this is deprecated, in v1.1.0 there is a new option `:hosts` that takes a list of host names_.
32
32
 
33
33
  # Usage
34
34
 
@@ -203,19 +203,36 @@ Yes it is, and your data is probably safe. cql-rb is just not completely there y
203
203
 
204
204
  ## Something else is not working
205
205
 
206
- Open an issue and I'll do my best to help you. Please include the gem version, Casandra version and Ruby version, and explain as much about what you're doing as you can, preferably the smallest piece of code that reliably triggers the problem.
206
+ Open an issue and someone will try to help you out. Please include the gem version, Casandra version and Ruby version, and explain as much about what you're doing as you can, preferably the smallest piece of code that reliably triggers the problem. The more information you give, the better the chances you will get help.
207
+
208
+ # Changelog & versioning
209
+
210
+ Check out the [releases on GitHub](https://github.com/iconara/cql-rb/releases). Version numbering follows the [semantic versioning](http://semver.org/) scheme.
207
211
 
208
212
  # Known bugs & limitations
209
213
 
210
- * No automatic peer discovery.
211
- * No automatic reconnection on connection failures.
212
- * JRuby 1.6.8 and earlier is not supported, although it probably works fine. The only known issue is that connection failures aren't handled gracefully.
213
- * Compression is not supported.
214
+ * No automatic peer discovery -- _this is in HEAD and will be released with v1.1.0_.
215
+ * No automatic reconnection on connection failures -- _this is planned for v1.1.0_.
216
+ * No support for request timeouts (other than server-initiated), but requests to a node fail when that node goes down.
217
+ * No support for compression.
218
+ * No support for request tracing.
219
+ * JRuby 1.6.8 and earlier is not supported, the tests pass in 1.6.8, but 1.6.4 is known not to work. Travis does not support JRuby 1.6.x so there's no way to get good coverage of what works and not. The only known issue in 1.6.8 is that connection failures aren't handled correctly.
214
220
  * Large results are buffered in memory until the whole response has been loaded, the protocol makes it possible to start to deliver rows to the client code as soon as the metadata is loaded, but this is not supported yet.
215
221
  * There is no cluster introspection utilities (like the `DESCRIBE` commands in `cqlsh`).
216
- * No support for request tracing.
217
222
 
218
- ## Copyright
223
+ # How to contribute
224
+
225
+ Fork the repository, make your changes in a topic branch that branches off from the right place in the history (HEAD isn't necessarily always right), make your changes and finally submit a pull request.
226
+
227
+ Follow the style of the existing code, make sure that existing tests pass, and that everything new has good test coverage. Put some effort into writing clear and concise commit messages, and write a good pull request description.
228
+
229
+ It takes time to understand other people's code, and even more time to understand a patch, so do as much as you can to make the maintainers' work easier. Be prepared for rejection, many times a feature is already planned, or the proposed design would be in the way of other planned features, or the maintainers' just feel that it will be faster to implement the features themselves than to try to integrate your patch.
230
+
231
+ Feel free to open a pull request before the feature is finished, that way you can have a conversation with the maintainers' during the development, and you can make adjustments to the design as you go along instead of having your whole feature rejected because of reasons such as those above. If you do, please make it clear that the pull request is a work in progress, or a request for comment.
232
+
233
+ Always remember that the maintainers' work on this project in their free time and that they don't work for you, or for your benefit. They have no obligation to do what you think is right -- but if you're nice they might anyway.
234
+
235
+ # Copyright
219
236
 
220
237
  Copyright 2013 Theo Hultberg/Iconara
221
238
 
@@ -108,8 +108,9 @@ module Cql
108
108
 
109
109
  private
110
110
 
111
- KEYSPACE_NAME_PATTERN = /^\w[\w\d_]*$/
111
+ KEYSPACE_NAME_PATTERN = /^\w[\w\d_]*$|^"\w[\w\d_]*"$/
112
112
  DEFAULT_CONSISTENCY_LEVEL = :quorum
113
+ BIND_ALL_IP = '0.0.0.0'.freeze
113
114
 
114
115
  class FailedConnection
115
116
  attr_reader :error
@@ -170,14 +171,21 @@ module Cql
170
171
  connected_seeds = seed_connections.select(&:connected?)
171
172
  connection = connected_seeds.sample
172
173
  return Future.completed([]) unless connection
173
- request = Protocol::QueryRequest.new('SELECT data_center, host_id, rpc_address FROM system.peers', :one)
174
+ request = Protocol::QueryRequest.new('SELECT peer, data_center, host_id, rpc_address FROM system.peers', :one)
174
175
  peer_info = execute_request(request, connection)
175
176
  peer_info.flat_map do |result|
176
177
  seed_dcs = connected_seeds.map { |c| c[:data_center] }.uniq
177
178
  unconnected_peers = result.select do |row|
178
179
  seed_dcs.include?(row['data_center']) && connected_seeds.none? { |c| c[:host_id] == row['host_id'] }
179
180
  end
180
- node_addresses = unconnected_peers.map { |row| row['rpc_address'].to_s }
181
+ node_addresses = unconnected_peers.map do |row|
182
+ rpc_address = row['rpc_address'].to_s
183
+ if rpc_address == BIND_ALL_IP
184
+ row['peer']
185
+ else
186
+ rpc_address
187
+ end
188
+ end
181
189
  if node_addresses.any?
182
190
  connect_to_hosts(node_addresses, initial_keyspace, false)
183
191
  else
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cql
4
4
  IoError = Class.new(CqlError)
5
+ CancelledError = Class.new(CqlError)
5
6
 
6
7
  module Io
7
8
  ConnectionError = Class.new(IoError)
@@ -13,13 +13,13 @@ module Cql
13
13
  attr_reader :host, :port, :connection_timeout
14
14
 
15
15
  # @private
16
- def initialize(host, port, connection_timeout, unblocker, socket_impl=Socket, clock=Time)
16
+ def initialize(host, port, connection_timeout, unblocker, clock, socket_impl=Socket)
17
17
  @host = host
18
18
  @port = port
19
19
  @connection_timeout = connection_timeout
20
20
  @unblocker = unblocker
21
- @socket_impl = socket_impl
22
21
  @clock = clock
22
+ @socket_impl = socket_impl
23
23
  @lock = Mutex.new
24
24
  @connected = false
25
25
  @write_buffer = ByteBuffer.new
@@ -89,6 +89,7 @@ module Cql
89
89
  #
90
90
  def initialize(protocol_handler_factory, options={})
91
91
  @protocol_handler_factory = protocol_handler_factory
92
+ @clock = options[:clock] || Time
92
93
  @unblocker = Unblocker.new
93
94
  @io_loop = IoLoopBody.new(options)
94
95
  @io_loop.add_socket(@unblocker)
@@ -138,6 +139,7 @@ module Cql
138
139
  @io_loop.tick until @stopped
139
140
  ensure
140
141
  @io_loop.close_sockets
142
+ @io_loop.cancel_timers
141
143
  @running = false
142
144
  if $!
143
145
  @stopped_future.fail!($!)
@@ -180,7 +182,7 @@ module Cql
180
182
  # object that will be your interface to interact with the connection
181
183
  #
182
184
  def connect(host, port, timeout)
183
- connection = Connection.new(host, port, timeout, @unblocker)
185
+ connection = Connection.new(host, port, timeout, @unblocker, @clock)
184
186
  f = connection.connect
185
187
  protocol_handler = @protocol_handler_factory.new(connection)
186
188
  @io_loop.add_socket(connection)
@@ -188,6 +190,18 @@ module Cql
188
190
  f.map { protocol_handler }
189
191
  end
190
192
 
193
+ # Returns a future that completes after the specified number of seconds.
194
+ #
195
+ # @param timeout [Float] the number of seconds to wait until the returned
196
+ # future is completed
197
+ # @return [Cql::Future] a future that completes when the timer expires
198
+ #
199
+ def schedule_timer(timeout)
200
+ f = Future.new
201
+ @io_loop.schedule_timer(timeout, f)
202
+ f
203
+ end
204
+
191
205
  def to_s
192
206
  @io_loop.to_s
193
207
  end
@@ -250,8 +264,10 @@ module Cql
250
264
  class IoLoopBody
251
265
  def initialize(options={})
252
266
  @selector = options[:selector] || IO
267
+ @clock = options[:clock] || Time
253
268
  @lock = Mutex.new
254
269
  @sockets = []
270
+ @timers = []
255
271
  end
256
272
 
257
273
  def add_socket(socket)
@@ -262,6 +278,15 @@ module Cql
262
278
  end
263
279
  end
264
280
 
281
+ def schedule_timer(timeout, future)
282
+ @lock.synchronize do
283
+ timers = @timers.dup
284
+ timers << [@clock.now + timeout, future]
285
+ timers.sort_by(&:first)
286
+ @timers = timers
287
+ end
288
+ end
289
+
265
290
  def close_sockets
266
291
  @sockets.each do |s|
267
292
  begin
@@ -272,7 +297,27 @@ module Cql
272
297
  end
273
298
  end
274
299
 
300
+ def cancel_timers
301
+ @timers.each do |pair|
302
+ if pair[1]
303
+ pair[1].fail!(CancelledError.new)
304
+ pair[1] = nil
305
+ end
306
+ end
307
+ end
308
+
275
309
  def tick(timeout=1)
310
+ check_sockets!(timeout)
311
+ check_timers!
312
+ end
313
+
314
+ def to_s
315
+ %(#<#{IoReactor.name} @connections=[#{@sockets.map(&:to_s).join(', ')}]>)
316
+ end
317
+
318
+ private
319
+
320
+ def check_sockets!(timeout)
276
321
  readables, writables, connecting = [], [], []
277
322
  sockets = @sockets
278
323
  sockets.reject! { |s| s.closed? }
@@ -287,8 +332,14 @@ module Cql
287
332
  w && w.each(&:flush)
288
333
  end
289
334
 
290
- def to_s
291
- %(#<#{IoReactor.name} @connections=[#{@sockets.map(&:to_s).join(', ')}]>)
335
+ def check_timers!
336
+ timers = @timers
337
+ timers.each do |pair|
338
+ break unless pair[0] <= @clock.now && pair[1]
339
+ pair[1].complete!
340
+ pair[1] = nil
341
+ end
342
+ timers.reject! { |pair| pair[1].nil? }
292
343
  end
293
344
  end
294
345
  end
@@ -31,15 +31,34 @@ 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
- fraction_string = number_string[0, number_string.length - size] << DECIMAL_POINT << number_string[number_string.length - size, number_string.length]
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
35
48
  BigDecimal.new(fraction_string)
36
49
  rescue RangeError => e
37
50
  raise DecodingError, e.message, e.backtrace
38
51
  end
39
52
 
40
53
  def read_long!(buffer)
41
- top, bottom = buffer.read(8).unpack(Formats::TWO_INTS_FORMAT)
42
- (top << 32) | bottom
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
43
62
  rescue RangeError => e
44
63
  raise DecodingError, e.message, e.backtrace
45
64
  end
@@ -57,7 +76,11 @@ module Cql
57
76
  end
58
77
 
59
78
  def read_int!(buffer)
60
- buffer.read_int
79
+ val = buffer.read_int
80
+ if (val > 0x7fffffff)
81
+ val = 0 - ((val - 1) ^ 0xffffffff)
82
+ end
83
+ val
61
84
  rescue RangeError => e
62
85
  raise DecodingError, "Not enough bytes available to decode an int: #{e.message}", e.backtrace
63
86
  end
@@ -161,6 +184,8 @@ module Cql
161
184
 
162
185
  private
163
186
 
187
+ MINUS = '-'.freeze
188
+ ZERO = '0'.freeze
164
189
  DECIMAL_POINT = '.'.freeze
165
190
  end
166
191
  end
@@ -36,27 +36,35 @@ module Cql
36
36
  def to_bytes(io, type, value, size_bytes=4)
37
37
  case type
38
38
  when Array
39
- unless value.is_a?(Enumerable)
39
+ unless value.nil? || 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
- raw = ''
46
- write_short(raw, value.size)
47
- value.each do |element|
48
- to_bytes(raw, sub_type, element, 2)
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)
49
54
  end
50
- write_bytes(io, raw)
51
55
  when :map
52
56
  _, key_type, value_type = type
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)
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)
58
67
  end
59
- write_bytes(io, raw)
60
68
  else
61
69
  raise UnsupportedColumnTypeError, %(Unsupported column collection type: #{type.first})
62
70
  end
@@ -229,7 +237,7 @@ module Cql
229
237
  end
230
238
 
231
239
  def ascii_to_bytes(io, value, size_bytes)
232
- v = value.encode(::Encoding::ASCII)
240
+ v = value && value.encode(::Encoding::ASCII)
233
241
  if size_bytes == 4
234
242
  write_bytes(io, v)
235
243
  else
@@ -238,16 +246,16 @@ module Cql
238
246
  end
239
247
 
240
248
  def bigint_to_bytes(io, value, size_bytes)
241
- if size_bytes == 4
242
- write_int(io, 8)
249
+ if value
250
+ size_to_bytes(io, 8, size_bytes)
251
+ write_long(io, value)
243
252
  else
244
- write_short(io, 8)
253
+ nil_to_bytes(io, size_bytes)
245
254
  end
246
- write_long(io, value)
247
255
  end
248
256
 
249
257
  def blob_to_bytes(io, value, size_bytes)
250
- v = value.encode(::Encoding::BINARY)
258
+ v = value && value.encode(::Encoding::BINARY)
251
259
  if size_bytes == 4
252
260
  write_bytes(io, v)
253
261
  else
@@ -256,16 +264,16 @@ module Cql
256
264
  end
257
265
 
258
266
  def boolean_to_bytes(io, value, size_bytes)
259
- if size_bytes == 4
260
- write_int(io, 1)
267
+ if !value.nil?
268
+ size_to_bytes(io, 1, size_bytes)
269
+ io << (value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
261
270
  else
262
- write_short(io, 1)
271
+ nil_to_bytes(io, size_bytes)
263
272
  end
264
- io << (value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
265
273
  end
266
274
 
267
275
  def decimal_to_bytes(io, value, size_bytes)
268
- raw = write_decimal('', value)
276
+ raw = value && write_decimal('', value)
269
277
  if size_bytes == 4
270
278
  write_bytes(io, raw)
271
279
  else
@@ -274,43 +282,43 @@ module Cql
274
282
  end
275
283
 
276
284
  def double_to_bytes(io, value, size_bytes)
277
- if size_bytes == 4
278
- write_int(io, 8)
285
+ if value
286
+ size_to_bytes(io, 8, size_bytes)
287
+ write_double(io, value)
279
288
  else
280
- write_short(io, 8)
289
+ nil_to_bytes(io, size_bytes)
281
290
  end
282
- write_double(io, value)
283
291
  end
284
292
 
285
293
  def float_to_bytes(io, value, size_bytes)
286
- if size_bytes == 4
287
- write_int(io, 4)
294
+ if value
295
+ size_to_bytes(io, 4, size_bytes)
296
+ write_float(io, value)
288
297
  else
289
- write_short(io, 4)
298
+ nil_to_bytes(io, size_bytes)
290
299
  end
291
- write_float(io, value)
292
300
  end
293
301
 
294
302
  def inet_to_bytes(io, value, size_bytes)
295
- if size_bytes == 4
296
- write_int(io, value.ipv6? ? 16 : 4)
303
+ if value
304
+ size_to_bytes(io, value.ipv6? ? 16 : 4, size_bytes)
305
+ io << value.hton
297
306
  else
298
- write_short(io, value.ipv6? ? 16 : 4)
307
+ nil_to_bytes(io, size_bytes)
299
308
  end
300
- io << value.hton
301
309
  end
302
310
 
303
311
  def int_to_bytes(io, value, size_bytes)
304
- if size_bytes == 4
305
- write_int(io, 4)
312
+ if value
313
+ size_to_bytes(io, 4, size_bytes)
314
+ write_int(io, value)
306
315
  else
307
- write_short(io, 4)
316
+ nil_to_bytes(io, size_bytes)
308
317
  end
309
- write_int(io, value)
310
318
  end
311
319
 
312
320
  def varchar_to_bytes(io, value, size_bytes)
313
- v = value.encode(::Encoding::UTF_8)
321
+ v = value && value.encode(::Encoding::UTF_8)
314
322
  if size_bytes == 4
315
323
  write_bytes(io, v)
316
324
  else
@@ -319,32 +327,48 @@ module Cql
319
327
  end
320
328
 
321
329
  def timestamp_to_bytes(io, value, size_bytes)
322
- ms = (value.to_f * 1000).to_i
323
- if size_bytes == 4
324
- write_int(io, 8)
330
+ if value
331
+ ms = (value.to_f * 1000).to_i
332
+ size_to_bytes(io, 8, size_bytes)
333
+ write_long(io, ms)
325
334
  else
326
- write_short(io, 8)
335
+ nil_to_bytes(io, size_bytes)
327
336
  end
328
- write_long(io, ms)
329
337
  end
330
338
 
331
339
  def uuid_to_bytes(io, value, size_bytes)
332
- if size_bytes == 4
333
- write_int(io, 16)
340
+ if value
341
+ size_to_bytes(io, 16, size_bytes)
342
+ write_uuid(io, value)
334
343
  else
335
- write_short(io, 16)
344
+ nil_to_bytes(io, size_bytes)
336
345
  end
337
- write_uuid(io, value)
338
346
  end
339
347
 
340
348
  def varint_to_bytes(io, value, size_bytes)
341
- raw = write_varint('', value)
349
+ raw = value && write_varint('', value)
342
350
  if size_bytes == 4
343
351
  write_bytes(io, raw)
344
352
  else
345
353
  write_short_bytes(io, raw)
346
354
  end
347
355
  end
356
+
357
+ def size_to_bytes(io, size, size_bytes)
358
+ if size_bytes == 4
359
+ write_int(io, size)
360
+ else
361
+ write_short(io, size)
362
+ end
363
+ end
364
+
365
+ def nil_to_bytes(io, size_bytes)
366
+ if size_bytes == 4
367
+ write_int(io, -1)
368
+ else
369
+ write_short(io, -1)
370
+ end
371
+ end
348
372
  end
349
373
  end
350
374
  end
@@ -52,7 +52,7 @@ module Cql
52
52
 
53
53
  # @private
54
54
  def eql?(other)
55
- self.value == other.value
55
+ other.kind_of?(Uuid) && self.value == other.value
56
56
  end
57
57
  alias_method :==, :eql?
58
58
 
@@ -71,4 +71,4 @@ module Cql
71
71
  n
72
72
  end
73
73
  end
74
- end
74
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.1.0.pre1'.freeze
4
+ VERSION = '1.1.0.pre2'.freeze
5
5
  end
@@ -188,6 +188,10 @@ module Cql
188
188
  Array.new(5) { IPAddr.new("127.0.#{rand(255)}.#{rand(255)}") }
189
189
  end
190
190
 
191
+ let :bind_all_rpc_addresses do
192
+ false
193
+ end
194
+
191
195
  before do
192
196
  uuid_generator = TimeUuid::Generator.new
193
197
  additional_rpc_addresses = additional_nodes.dup
@@ -210,7 +214,12 @@ module Cql
210
214
  end
211
215
  rows = other_host_ids.map do |host_id|
212
216
  ip = additional_rpc_addresses.shift
213
- {'host_id' => host_id, 'data_center' => data_centers[ip], 'rpc_address' => ip}
217
+ {
218
+ 'peer' => ip,
219
+ 'host_id' => host_id,
220
+ 'data_center' => data_centers[ip],
221
+ 'rpc_address' => bind_all_rpc_addresses ? IPAddr.new('0.0.0.0') : ip
222
+ }
214
223
  end
215
224
  Protocol::RowsResultResponse.new(rows, peer_metadata)
216
225
  end
@@ -224,6 +233,17 @@ module Cql
224
233
  connections.should have(3).items
225
234
  end
226
235
 
236
+ context 'when the nodes have 0.0.0.0 as rpc_address' do
237
+ let :bind_all_rpc_addresses do
238
+ true
239
+ end
240
+
241
+ it 'falls back on using the peer column' do
242
+ client.connect.get
243
+ connections.should have(3).items
244
+ end
245
+ end
246
+
227
247
  it 'connects to the other nodes in the same data center' do
228
248
  data_centers[additional_nodes[1]] = 'dc2'
229
249
  client.connect.get
@@ -408,6 +428,17 @@ module Cql
408
428
  client.connect.get
409
429
  expect { client.use('system; DROP KEYSPACE system').get }.to raise_error(InvalidKeyspaceNameError)
410
430
  end
431
+
432
+ it 'allows the keyspace name to be quoted' do
433
+ handle_request do |request|
434
+ if request.is_a?(Protocol::QueryRequest) && request.cql == 'USE "system"'
435
+ Protocol::SetKeyspaceResultResponse.new('system')
436
+ end
437
+ end
438
+ client.connect.get
439
+ client.use('"system"').get
440
+ client.keyspace.should == "system"
441
+ end
411
442
  end
412
443
 
413
444
  describe '#execute' do
@@ -691,4 +722,4 @@ module Cql
691
722
  end
692
723
  end
693
724
  end
694
- end
725
+ end
@@ -7,7 +7,7 @@ module Cql
7
7
  module Io
8
8
  describe Connection do
9
9
  let :handler do
10
- described_class.new('example.com', 55555, 5, unblocker, socket_impl, clock)
10
+ described_class.new('example.com', 55555, 5, unblocker, clock, socket_impl)
11
11
  end
12
12
 
13
13
  let :unblocker do
@@ -7,7 +7,7 @@ module Cql
7
7
  module Io
8
8
  describe IoReactor do
9
9
  let :reactor do
10
- described_class.new(protocol_handler_factory, selector: selector)
10
+ described_class.new(protocol_handler_factory, selector: selector, clock: clock)
11
11
  end
12
12
 
13
13
  let :protocol_handler_factory do
@@ -18,6 +18,10 @@ module Cql
18
18
  IoReactorSpec::FakeSelector.new
19
19
  end
20
20
 
21
+ let :clock do
22
+ stub(:clock, now: 0)
23
+ end
24
+
21
25
  describe '#start' do
22
26
  after do
23
27
  reactor.stop.get if reactor.running?
@@ -84,6 +88,20 @@ module Cql
84
88
  reactor.stop.get
85
89
  connection.should be_closed
86
90
  end
91
+
92
+ it 'cancels all active timers' do
93
+ reactor.start.get
94
+ clock.stub(:now).and_return(1)
95
+ expired_timer = reactor.schedule_timer(1)
96
+ active_timer1 = reactor.schedule_timer(999)
97
+ active_timer2 = reactor.schedule_timer(111)
98
+ expired_timer.should_not_receive(:fail!)
99
+ clock.stub(:now).and_return(2)
100
+ await { expired_timer.complete? }
101
+ reactor.stop.get
102
+ active_timer1.should be_failed
103
+ active_timer2.should be_failed
104
+ end
87
105
  end
88
106
 
89
107
  describe '#on_error' do
@@ -163,6 +181,23 @@ module Cql
163
181
  end
164
182
  end
165
183
 
184
+ describe '#schedule_timer' do
185
+ before do
186
+ reactor.start.get
187
+ end
188
+
189
+ after do
190
+ reactor.stop.get
191
+ end
192
+
193
+ it 'returns a future that completes after the specified duration' do
194
+ clock.stub(:now).and_return(1)
195
+ f = reactor.schedule_timer(0.1)
196
+ clock.stub(:now).and_return(1.1)
197
+ await { f.complete? }
198
+ end
199
+ end
200
+
166
201
  describe '#to_s' do
167
202
  context 'returns a string that' do
168
203
  it 'includes the class name' do
@@ -179,13 +214,17 @@ module Cql
179
214
 
180
215
  describe IoLoopBody do
181
216
  let :loop_body do
182
- described_class.new(selector: selector)
217
+ described_class.new(selector: selector, clock: clock)
183
218
  end
184
219
 
185
220
  let :selector do
186
221
  stub(:selector)
187
222
  end
188
223
 
224
+ let :clock do
225
+ stub(:clock, now: 0)
226
+ end
227
+
189
228
  let :socket do
190
229
  stub(:socket, connected?: false, connecting?: false, writable?: false, closed?: false)
191
230
  end
@@ -252,6 +291,30 @@ module Cql
252
291
  selector.should_receive(:select).with(anything, anything, anything, 99).and_return([[], [], []])
253
292
  loop_body.tick(99)
254
293
  end
294
+
295
+ it 'completes timers that have expired' do
296
+ selector.stub(:select).and_return([nil, nil, nil])
297
+ clock.stub(:now).and_return(1)
298
+ future = Future.new
299
+ loop_body.schedule_timer(1, future)
300
+ loop_body.tick
301
+ future.should_not be_complete
302
+ clock.stub(:now).and_return(2)
303
+ loop_body.tick
304
+ future.should be_complete
305
+ end
306
+
307
+ it 'clears out timers that have expired' do
308
+ selector.stub(:select).and_return([nil, nil, nil])
309
+ clock.stub(:now).and_return(1)
310
+ future = Future.new
311
+ loop_body.schedule_timer(1, future)
312
+ clock.stub(:now).and_return(2)
313
+ loop_body.tick
314
+ future.should be_complete
315
+ future.should_not_receive(:complete!)
316
+ loop_body.tick
317
+ end
255
318
  end
256
319
 
257
320
  describe '#close_sockets' do
@@ -282,6 +345,29 @@ module Cql
282
345
  loop_body.close_sockets
283
346
  end
284
347
  end
348
+
349
+ describe '#cancel_timers' do
350
+ before do
351
+ selector.stub(:select).and_return([nil, nil, nil])
352
+ end
353
+
354
+ it 'fails all active timers with a CancelledError' do
355
+ f1 = Future.new
356
+ f2 = Future.new
357
+ f3 = Future.new
358
+ clock.stub(:now).and_return(1)
359
+ loop_body.schedule_timer(1, f1)
360
+ loop_body.schedule_timer(3, f2)
361
+ loop_body.schedule_timer(3, f3)
362
+ clock.stub(:now).and_return(2)
363
+ loop_body.tick
364
+ loop_body.cancel_timers
365
+ f1.should be_complete
366
+ f2.should be_failed
367
+ f3.should be_failed
368
+ expect { f3.get }.to raise_error(CancelledError)
369
+ end
370
+ end
285
371
  end
286
372
  end
287
373
  end
@@ -62,6 +62,21 @@ module Cql
62
62
  Decoding.read_decimal!(buffer).should == BigDecimal.new('1042342234234.123423435647768234')
63
63
  end
64
64
 
65
+ it 'decodes a negative decimal' do
66
+ buffer = ByteBuffer.new("\x00\x00\x00\x12\xF2\xD8\x02\xB6R\x7F\x99\xEE\x98#\x99\xA9V")
67
+ Decoding.read_decimal!(buffer).should == BigDecimal.new('-1042342234234.123423435647768234')
68
+ end
69
+
70
+ it 'decodes a positive decimal with only fractions' do
71
+ buffer = ByteBuffer.new("\x00\x00\x00\x13*\xF8\xC4\xDF\xEB]o")
72
+ Decoding.read_decimal!(buffer).should == BigDecimal.new('0.0012095473475870063')
73
+ end
74
+
75
+ it 'decodes a negative decimal with only fractions' do
76
+ buffer = ByteBuffer.new("\x00\x00\x00\x13\xD5\a;\x20\x14\xA2\x91")
77
+ Decoding.read_decimal!(buffer).should == BigDecimal.new('-0.0012095473475870063')
78
+ end
79
+
65
80
  it 'consumes the bytes' do
66
81
  buffer << 'HELLO'
67
82
  Decoding.read_decimal!(buffer, buffer.length - 5)
@@ -79,11 +94,16 @@ module Cql
79
94
  end
80
95
 
81
96
  describe '#read_long!' do
82
- it 'decodes a long' do
97
+ it 'decodes a positive long' do
83
98
  buffer = ByteBuffer.new("\x00\x00\xca\xfe\xba\xbe\x00\x00")
84
99
  Decoding.read_long!(buffer).should == 0x0000cafebabe0000
85
100
  end
86
101
 
102
+ it 'decodes a negative long' do
103
+ buffer = ByteBuffer.new("\xff\xff\xff\xff\xff\xff\xff\xff")
104
+ Decoding.read_long!(buffer).should == -1
105
+ end
106
+
87
107
  it 'consumes the bytes' do
88
108
  buffer = ByteBuffer.new("\xca\xfe\xba\xbe\xca\xfe\xba\xbe\xca\xfe\xba\xbe")
89
109
  Decoding.read_long!(buffer)
@@ -137,10 +157,15 @@ module Cql
137
157
  ByteBuffer.new("\x00\xff\x00\xff")
138
158
  end
139
159
 
140
- it 'decodes an int' do
160
+ it 'decodes a positive int' do
141
161
  Decoding.read_int!(buffer).should == 0x00ff00ff
142
162
  end
143
163
 
164
+ it 'decodes a negative int' do
165
+ buffer = ByteBuffer.new("\xff\xff\xff\xff")
166
+ Decoding.read_int!(buffer).should == -1
167
+ end
168
+
144
169
  it 'consumes the bytes' do
145
170
  buffer << "\xab\xcd"
146
171
  Decoding.read_int!(buffer)
@@ -0,0 +1,52 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Protocol
8
+ describe TypeConverter do
9
+ let :converter do
10
+ described_class.new
11
+ end
12
+
13
+ let :buffer do
14
+ ''
15
+ end
16
+
17
+ TYPES = [:ascii, :bigint, :blob, :boolean, :decimal, :double, :float, :inet, :int, :text, :varchar, :timestamp, :timeuuid, :uuid, :varint].freeze
18
+
19
+ describe '#to_bytes' do
20
+ context 'when encoding normal value' do
21
+ TYPES.each do |type|
22
+ it "encodes a null #{type.upcase}" do
23
+ converter.to_bytes(buffer, type, nil, 4).should == "\xff\xff\xff\xff"
24
+ end
25
+ end
26
+
27
+ it 'encodes a null LIST' do
28
+ converter.to_bytes(buffer, [:list, :int], nil, 4).should == "\xff\xff\xff\xff"
29
+ end
30
+
31
+ it 'encodes a null MAP' do
32
+ converter.to_bytes(buffer, [:map, :text, :text], nil, 4).should == "\xff\xff\xff\xff"
33
+
34
+ end
35
+
36
+ it 'encodes a null SET' do
37
+ converter.to_bytes(buffer, [:set, :uuid], nil, 4).should == "\xff\xff\xff\xff"
38
+
39
+ end
40
+ end
41
+
42
+ context 'when encoding collection values' do
43
+ TYPES.each do |type|
44
+ it "encodes a null #{type.upcase}" do
45
+ converter.to_bytes(buffer, type, nil, 2).should == "\xff\xff"
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -36,6 +36,10 @@ module Cql
36
36
  Uuid.new(276263553384940695775376958868900023510).should eql(Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'))
37
37
  end
38
38
 
39
+ it 'is not equal when anything other than a Uuid is passed' do
40
+ [nil, 123, 'test'].each { |v| Uuid.new(276263553384940695775376958868900023510).should_not eql(v) }
41
+ end
42
+
39
43
  it 'aliases #== to #eql?' do
40
44
  Uuid.new(276263553384940695775376958868900023510).should == Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6')
41
45
  end
@@ -73,4 +77,4 @@ module Cql
73
77
  end
74
78
  end
75
79
  end
76
- end
80
+ end
@@ -291,12 +291,12 @@ describe 'Protocol parsing and communication' do
291
291
  end
292
292
  end
293
293
 
294
- # it 'sends a TRUNCATE command' do
295
- # in_keyspace_with_table do
296
- # response = query(%<TRUNCATE users>)
297
- # response.should be_void
298
- # end
299
- # end
294
+ it 'sends a TRUNCATE command' do
295
+ in_keyspace_with_table do
296
+ response = query(%<TRUNCATE users>)
297
+ response.should be_void
298
+ end
299
+ end
300
300
 
301
301
  it 'sends a BATCH command' do
302
302
  in_keyspace_with_table do
@@ -65,15 +65,7 @@ describe 'Regressions' do
65
65
  end
66
66
 
67
67
  context 'with null values' do
68
- it 'decodes null counters' do
69
- client.execute(%<CREATE TABLE counters (id ASCII, counter1 COUNTER, counter2 COUNTER, PRIMARY KEY (id))>)
70
- client.execute(%<UPDATE counters SET counter1 = counter1 + 1 WHERE id = 'foo'>)
71
- result = client.execute(%<SELECT counter1, counter2 FROM counters WHERE id = 'foo'>)
72
- result.first['counter1'].should == 1
73
- result.first['counter2'].should be_nil
74
- end
75
-
76
- it 'decodes null values' do
68
+ before do
77
69
  client.execute(<<-CQL)
78
70
  CREATE TABLE lots_of_types (
79
71
  id INT,
@@ -98,6 +90,24 @@ describe 'Regressions' do
98
90
  PRIMARY KEY (id)
99
91
  )
100
92
  CQL
93
+ client.execute(<<-CQL)
94
+ CREATE TABLE counters (
95
+ id ASCII,
96
+ counter1 COUNTER,
97
+ counter2 COUNTER,
98
+ PRIMARY KEY (id)
99
+ )
100
+ CQL
101
+ end
102
+
103
+ it 'decodes null counters' do
104
+ client.execute(%<UPDATE counters SET counter1 = counter1 + 1 WHERE id = 'foo'>)
105
+ result = client.execute(%<SELECT counter1, counter2 FROM counters WHERE id = 'foo'>)
106
+ result.first['counter1'].should == 1
107
+ result.first['counter2'].should be_nil
108
+ end
109
+
110
+ it 'decodes null values' do
101
111
  client.execute(%<INSERT INTO lots_of_types (id) VALUES (3)>)
102
112
  result = client.execute(%<SELECT * FROM lots_of_types WHERE id = 3>)
103
113
  row = result.first
@@ -120,5 +130,79 @@ describe 'Regressions' do
120
130
  row['map_column'].should be_nil
121
131
  row['set_column'].should be_nil
122
132
  end
133
+
134
+ it 'encodes null values' do
135
+ statement = client.prepare(<<-CQL)
136
+ UPDATE lots_of_types
137
+ SET
138
+ ascii_column = ?,
139
+ bigint_column = ?,
140
+ blob_column = ?,
141
+ boolean_column = ?,
142
+ decimal_column = ?,
143
+ double_column = ?,
144
+ float_column = ?,
145
+ int_column = ?,
146
+ text_column = ?,
147
+ timestamp_column = ?,
148
+ uuid_column = ?,
149
+ varchar_column = ?,
150
+ varint_column = ?,
151
+ timeuuid_column = ?,
152
+ inet_column = ?,
153
+ list_column = ?,
154
+ map_column = ?,
155
+ set_column = ?
156
+ WHERE id = 1
157
+ CQL
158
+ statement.execute(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
159
+ end
160
+ end
161
+
162
+ context 'with negative numbers' do
163
+ it 'decodes negative counters' do
164
+ client.execute(%<CREATE TABLE counters (id ASCII, counter1 COUNTER, PRIMARY KEY (id))>)
165
+ client.execute(%<UPDATE counters SET counter1 = counter1 - 1 WHERE id = 'foo'>)
166
+ result = client.execute(%<SELECT counter1 FROM counters WHERE id = 'foo'>)
167
+ result.first['counter1'].should == -1
168
+ end
169
+
170
+ it 'decodes negative numbers' do
171
+ client.execute(<<-CQL)
172
+ CREATE TABLE lots_of_types (
173
+ id INT,
174
+ bigint_column BIGINT,
175
+ decimal_column DECIMAL,
176
+ double_column DOUBLE,
177
+ float_column FLOAT,
178
+ int_column INT,
179
+ varint_column VARINT,
180
+ PRIMARY KEY (id)
181
+ )
182
+ CQL
183
+ client.execute(%<INSERT INTO lots_of_types (id, bigint_column, decimal_column, double_column, float_column, int_column, varint_column) VALUES (0, -1, -1, -1, -1, -1, -1)>)
184
+ client.execute(%<INSERT INTO lots_of_types (id, bigint_column, decimal_column, double_column, float_column, int_column, varint_column) VALUES (1, -9223372036854775808, -0.0012095473475870063, -2.2250738585072014e-308, -1.175494351e-38, -2147483648, -23454545674351234123365765786894351234567456)>)
185
+ result = client.execute(%<SELECT * FROM lots_of_types WHERE id IN (0, 1)>)
186
+ row0, row1 = result.to_a
187
+ row0['bigint_column'].should == -1
188
+ row0['decimal_column'].should == -1
189
+ row0['double_column'].should == -1
190
+ row0['float_column'].should == -1
191
+ row0['int_column'].should == -1
192
+ row0['varint_column'].should == -1
193
+ row1['bigint_column'].should == -9223372036854775808
194
+ row1['decimal_column'].should == BigDecimal.new('-0.0012095473475870063')
195
+ row1['double_column'].should == be_within(1.0e-308).of(-2.2250738585072014e-308)
196
+ row1['float_column'].should be_within(1.0e-38).of(-1.175494351e-38)
197
+ row1['int_column'].should == -2147483648
198
+ row1['varint_column'].should == -23454545674351234123365765786894351234567456
199
+ end
200
+ end
201
+
202
+ context 'with quoted keyspace names' do
203
+ it 'handles quoted keyspace names' do
204
+ client.use('"system"')
205
+ client.keyspace.should == 'system'
206
+ end
123
207
  end
124
208
  end
@@ -23,7 +23,9 @@ class FakeIoReactor
23
23
  end
24
24
 
25
25
  def connect(host, port, timeout)
26
- if @down_nodes.include?(host)
26
+ if host == '0.0.0.0'
27
+ Cql::Future.failed(Cql::Io::ConnectionError.new('Can\'t connect to 0.0.0.0'))
28
+ elsif @down_nodes.include?(host)
27
29
  Cql::Future.failed(Cql::Io::ConnectionError.new('Node down'))
28
30
  else
29
31
  connection = FakeConnection.new(host, port, timeout)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cql-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.pre1
4
+ version: 1.1.0.pre2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-07-24 00:00:00.000000000 Z
12
+ date: 2013-08-27 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A pure Ruby CQL3 driver for Cassandra
15
15
  email:
@@ -91,6 +91,7 @@ files:
91
91
  - spec/cql/protocol/requests/startup_request_spec.rb
92
92
  - spec/cql/protocol/response_frame_spec.rb
93
93
  - spec/cql/protocol/responses/schema_change_result_response_spec.rb
94
+ - spec/cql/protocol/type_converter_spec.rb
94
95
  - spec/cql/time_uuid_spec.rb
95
96
  - spec/cql/uuid_spec.rb
96
97
  - spec/integration/client_spec.rb
@@ -105,7 +106,7 @@ files:
105
106
  - spec/support/fake_server.rb
106
107
  homepage: http://github.com/iconara/cql-rb
107
108
  licenses:
108
- - Apache
109
+ - Apache License 2.0
109
110
  post_install_message:
110
111
  rdoc_options: []
111
112
  require_paths:
@@ -152,6 +153,7 @@ test_files:
152
153
  - spec/cql/protocol/requests/startup_request_spec.rb
153
154
  - spec/cql/protocol/response_frame_spec.rb
154
155
  - spec/cql/protocol/responses/schema_change_result_response_spec.rb
156
+ - spec/cql/protocol/type_converter_spec.rb
155
157
  - spec/cql/time_uuid_spec.rb
156
158
  - spec/cql/uuid_spec.rb
157
159
  - spec/integration/client_spec.rb