cql-rb 1.1.3 → 1.2.0.pre0

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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +19 -11
  3. data/lib/cql.rb +1 -0
  4. data/lib/cql/client.rb +41 -9
  5. data/lib/cql/client/asynchronous_client.rb +9 -4
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +2 -2
  7. data/lib/cql/client/connection_helper.rb +43 -20
  8. data/lib/cql/client/execute_options_decoder.rb +4 -1
  9. data/lib/cql/client/query_result.rb +7 -3
  10. data/lib/cql/client/query_trace.rb +46 -0
  11. data/lib/cql/client/request_runner.rb +7 -2
  12. data/lib/cql/client/void_result.rb +42 -0
  13. data/lib/cql/compression.rb +53 -0
  14. data/lib/cql/compression/snappy_compressor.rb +42 -0
  15. data/lib/cql/io/io_reactor.rb +9 -4
  16. data/lib/cql/protocol.rb +5 -3
  17. data/lib/cql/protocol/cql_protocol_handler.rb +14 -9
  18. data/lib/cql/protocol/frame_decoder.rb +106 -0
  19. data/lib/cql/protocol/frame_encoder.rb +31 -0
  20. data/lib/cql/protocol/request.rb +7 -11
  21. data/lib/cql/protocol/requests/execute_request.rb +2 -2
  22. data/lib/cql/protocol/requests/options_request.rb +4 -0
  23. data/lib/cql/protocol/requests/prepare_request.rb +2 -2
  24. data/lib/cql/protocol/requests/query_request.rb +2 -2
  25. data/lib/cql/protocol/requests/startup_request.rb +12 -5
  26. data/lib/cql/protocol/response.rb +13 -2
  27. data/lib/cql/protocol/responses/authenticate_response.rb +5 -1
  28. data/lib/cql/protocol/responses/detailed_error_response.rb +2 -2
  29. data/lib/cql/protocol/responses/error_response.rb +7 -2
  30. data/lib/cql/protocol/responses/event_response.rb +4 -2
  31. data/lib/cql/protocol/responses/prepared_result_response.rb +20 -4
  32. data/lib/cql/protocol/responses/ready_response.rb +5 -1
  33. data/lib/cql/protocol/responses/result_response.rb +10 -2
  34. data/lib/cql/protocol/responses/rows_result_response.rb +5 -4
  35. data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
  36. data/lib/cql/protocol/responses/schema_change_result_response.rb +5 -4
  37. data/lib/cql/protocol/responses/set_keyspace_result_response.rb +4 -3
  38. data/lib/cql/protocol/responses/{status_change_event_result_response.rb → status_change_event_response.rb} +1 -1
  39. data/lib/cql/protocol/responses/supported_response.rb +5 -1
  40. data/lib/cql/protocol/responses/{topology_change_event_result_response.rb → topology_change_event_response.rb} +0 -0
  41. data/lib/cql/protocol/responses/void_result_response.rb +2 -2
  42. data/lib/cql/version.rb +1 -1
  43. data/spec/cql/client/asynchronous_client_spec.rb +52 -31
  44. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +16 -2
  45. data/spec/cql/client/connection_helper_spec.rb +90 -12
  46. data/spec/cql/client/query_trace_spec.rb +138 -0
  47. data/spec/cql/client/request_runner_spec.rb +44 -7
  48. data/spec/cql/client/void_result_spec.rb +43 -0
  49. data/spec/cql/compression/compression_common.rb +59 -0
  50. data/spec/cql/compression/snappy_compressor_spec.rb +23 -0
  51. data/spec/cql/io/io_reactor_spec.rb +8 -1
  52. data/spec/cql/protocol/cql_protocol_handler_spec.rb +40 -0
  53. data/spec/cql/protocol/frame_decoder_spec.rb +132 -0
  54. data/spec/cql/protocol/frame_encoder_spec.rb +105 -0
  55. data/spec/cql/protocol/requests/credentials_request_spec.rb +2 -4
  56. data/spec/cql/protocol/requests/execute_request_spec.rb +5 -5
  57. data/spec/cql/protocol/requests/options_request_spec.rb +10 -4
  58. data/spec/cql/protocol/requests/prepare_request_spec.rb +3 -3
  59. data/spec/cql/protocol/requests/query_request_spec.rb +10 -5
  60. data/spec/cql/protocol/requests/register_request_spec.rb +3 -3
  61. data/spec/cql/protocol/requests/startup_request_spec.rb +11 -5
  62. data/spec/cql/protocol/responses/authenticate_response_spec.rb +27 -0
  63. data/spec/cql/protocol/responses/detailed_error_response_spec.rb +78 -0
  64. data/spec/cql/protocol/responses/error_response_spec.rb +36 -0
  65. data/spec/cql/protocol/responses/event_response_spec.rb +40 -0
  66. data/spec/cql/protocol/responses/prepared_result_response_spec.rb +108 -0
  67. data/spec/cql/protocol/responses/ready_response_spec.rb +39 -0
  68. data/spec/cql/protocol/responses/result_response_spec.rb +57 -0
  69. data/spec/cql/protocol/responses/rows_result_response_spec.rb +273 -0
  70. data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +93 -0
  71. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +51 -19
  72. data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +34 -0
  73. data/spec/cql/protocol/responses/status_change_event_response_spec.rb +35 -0
  74. data/spec/cql/protocol/responses/supported_response_spec.rb +27 -0
  75. data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +35 -0
  76. data/spec/cql/protocol/responses/void_result_response_spec.rb +29 -0
  77. data/spec/integration/client_spec.rb +45 -0
  78. data/spec/integration/protocol_spec.rb +46 -0
  79. data/spec/spec_helper.rb +2 -1
  80. data/spec/support/fake_io_reactor.rb +1 -1
  81. metadata +51 -10
  82. data/lib/cql/protocol/response_frame.rb +0 -129
  83. data/spec/cql/protocol/request_spec.rb +0 -45
  84. data/spec/cql/protocol/response_frame_spec.rb +0 -811
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Client
5
+ class VoidResult
6
+ include Enumerable
7
+
8
+ INSTANCE = self.new
9
+
10
+ # @return [ResultMetadata]
11
+ attr_reader :metadata
12
+
13
+ # The ID of the query trace associated with the query, if any.
14
+ #
15
+ # @return [Cql::Uuid]
16
+ attr_reader :trace_id
17
+
18
+ # @private
19
+ def initialize(trace_id=nil)
20
+ @trace_id = trace_id
21
+ @metadata = EMPTY_METADATA
22
+ end
23
+
24
+ # Always returns true
25
+ def empty?
26
+ true
27
+ end
28
+
29
+ # No-op for API compatibility with {QueryResult}.
30
+ #
31
+ # @return [Enumerable]
32
+ def each(&block)
33
+ self
34
+ end
35
+ alias_method :each_row, :each
36
+
37
+ private
38
+
39
+ EMPTY_METADATA = ResultMetadata.new([])
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Compression
5
+ CompressionError = Class.new(CqlError)
6
+
7
+ # @note Compressors given to {Cql::Client.connect} as the `:compressor`
8
+ # option don't need to be subclasses of this class, but need to
9
+ # implement the same methods. This class exists only for documentation
10
+ # purposes.
11
+ class Compressor
12
+ # @!method algorithm
13
+ #
14
+ # Returns the name of the algorithm this compressor supports,
15
+ # e.g. "snappy" or "lz4".
16
+ #
17
+ # @return [String]
18
+
19
+ # @!method compress?
20
+ #
21
+ # Before compressing a frame the compressor will be asked if it wants
22
+ # to compress it or not. One reason it could say no is if the frame is
23
+ # small enough that compression would be unlikely to decrease its size.
24
+ #
25
+ # If your operations consist mostly of small prepared statement
26
+ # executions it might not be useful to compress the frames being sent
27
+ # _to_ Cassandra, but enabling compression can still be useful on the
28
+ # frames coming _from_ Cassandra. Making this method always return
29
+ # false disables request compression, but will still make the client
30
+ # tell Cassandra that it supports compressed frames.
31
+ #
32
+ # The bytes given to {#compress?} are the same as to {#compress}
33
+ #
34
+ # @param [String] frame the bytes of the frame to be compressed
35
+ # @return [true, false]
36
+
37
+ # @!method compress
38
+ #
39
+ # Compresses the raw bytes of a frame.
40
+ #
41
+ # @param [String] frame the bytes of the frame to be compressed
42
+ # @return [String] the compressed frame
43
+
44
+ # @!method decompress
45
+ #
46
+ # Decompresses the raw bytes of a compressed frame.
47
+ #
48
+ # @param [String] compressed_frame the bytes of the compressed
49
+ # frame to be uncompressed
50
+ # @return [String]
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ begin
4
+ require 'snappy'
5
+ rescue LoadError => e
6
+ raise LoadError, %[Snappy support requires the "snappy" gem: #{e.message}], e.backtrace
7
+ end
8
+
9
+ module Cql
10
+ module Compression
11
+
12
+ # A compressor that uses the Snappy compression library.
13
+ #
14
+ # @note This compressor requires the [snappy](http://rubygems.org/gems/snappy)
15
+ # gem (v0.0.10 or later for JRuby support).
16
+ class SnappyCompressor
17
+ # @return [String]
18
+ attr_reader :algorithm
19
+
20
+ # @param [Integer] min_size (64) Don't compress frames smaller than
21
+ # this size (see {#compress?}).
22
+ def initialize(min_size=64)
23
+ @algorithm = 'snappy'.freeze
24
+ @min_size = min_size
25
+ end
26
+
27
+ # @return [true, false] will return false for frames smaller than the
28
+ # `min_size` given to the constructor.
29
+ def compress?(str)
30
+ str.bytesize > @min_size
31
+ end
32
+
33
+ def compress(str)
34
+ Snappy.deflate(str)
35
+ end
36
+
37
+ def decompress(str)
38
+ Snappy.inflate(str)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -83,12 +83,17 @@ module Cql
83
83
  class IoReactor
84
84
  # Initializes a new IO reactor.
85
85
  #
86
- # @param protocol_handler_factory [Object] a class that will be used
87
- # create the protocol handler objects returned by {#connect}
86
+ # @param protocol_handler_factory [Proc, Class] a Proc or class that
87
+ # will be used create the protocol handler objects returned by
88
+ # {#connect}
88
89
  # @param options [Hash] only used to inject behaviour during tests
89
90
  #
90
91
  def initialize(protocol_handler_factory, options={})
91
- @protocol_handler_factory = protocol_handler_factory
92
+ if protocol_handler_factory.respond_to?(:new)
93
+ @protocol_handler_factory = protocol_handler_factory.method(:new)
94
+ else
95
+ @protocol_handler_factory = protocol_handler_factory
96
+ end
92
97
  @clock = options[:clock] || Time
93
98
  @unblocker = Unblocker.new
94
99
  @io_loop = IoLoopBody.new(options)
@@ -184,7 +189,7 @@ module Cql
184
189
  def connect(host, port, timeout)
185
190
  connection = Connection.new(host, port, timeout, @unblocker, @clock)
186
191
  f = connection.connect
187
- protocol_handler = @protocol_handler_factory.new(connection, self)
192
+ protocol_handler = @protocol_handler_factory.call(connection, self)
188
193
  @io_loop.add_socket(connection)
189
194
  @unblocker.unblock!
190
195
  f.map { protocol_handler }
data/lib/cql/protocol.rb CHANGED
@@ -14,6 +14,7 @@ module Cql
14
14
  UnsupportedResultKindError = Class.new(ProtocolError)
15
15
  UnsupportedColumnTypeError = Class.new(ProtocolError)
16
16
  UnsupportedEventTypeError = Class.new(ProtocolError)
17
+ UnexpectedCompressionError = Class.new(ProtocolError)
17
18
 
18
19
  CONSISTENCIES = [:any, :one, :two, :three, :quorum, :all, :local_quorum, :each_quorum, nil, nil, nil, nil, nil, nil, nil, nil, :local_one].freeze
19
20
 
@@ -55,8 +56,8 @@ require 'cql/protocol/responses/prepared_result_response'
55
56
  require 'cql/protocol/responses/schema_change_result_response'
56
57
  require 'cql/protocol/responses/event_response'
57
58
  require 'cql/protocol/responses/schema_change_event_response'
58
- require 'cql/protocol/responses/status_change_event_result_response'
59
- require 'cql/protocol/responses/topology_change_event_result_response'
59
+ require 'cql/protocol/responses/status_change_event_response'
60
+ require 'cql/protocol/responses/topology_change_event_response'
60
61
  require 'cql/protocol/request'
61
62
  require 'cql/protocol/requests/startup_request'
62
63
  require 'cql/protocol/requests/credentials_request'
@@ -65,5 +66,6 @@ require 'cql/protocol/requests/register_request'
65
66
  require 'cql/protocol/requests/query_request'
66
67
  require 'cql/protocol/requests/prepare_request'
67
68
  require 'cql/protocol/requests/execute_request'
68
- require 'cql/protocol/response_frame'
69
+ require 'cql/protocol/frame_encoder'
70
+ require 'cql/protocol/frame_decoder'
69
71
  require 'cql/protocol/cql_protocol_handler'
@@ -19,14 +19,17 @@ module Cql
19
19
  # @return [String] the current keyspace for the underlying connection
20
20
  attr_reader :keyspace
21
21
 
22
- def initialize(connection, scheduler)
22
+ def initialize(connection, scheduler, compressor=nil)
23
23
  @connection = connection
24
24
  @scheduler = scheduler
25
+ @compressor = compressor
25
26
  @connection.on_data(&method(:receive_data))
26
27
  @connection.on_closed(&method(:socket_closed))
27
28
  @promises = Array.new(128) { nil }
28
29
  @read_buffer = ByteBuffer.new
29
- @current_frame = Protocol::ResponseFrame.new(@read_buffer)
30
+ @frame_encoder = FrameEncoder.new(@compressor)
31
+ @frame_decoder = FrameDecoder.new(@compressor)
32
+ @current_frame = FrameDecoder::NULL_FRAME
30
33
  @request_queue_in = []
31
34
  @request_queue_out = []
32
35
  @event_listeners = []
@@ -113,7 +116,7 @@ module Cql
113
116
  # the response
114
117
  def send_request(request, timeout=nil)
115
118
  return Future.failed(NotConnectedError.new) if closed?
116
- promise = RequestPromise.new(request)
119
+ promise = RequestPromise.new(request, @frame_encoder)
117
120
  id = nil
118
121
  @lock.synchronize do
119
122
  if (id = next_stream_id)
@@ -122,7 +125,7 @@ module Cql
122
125
  end
123
126
  if id
124
127
  @connection.write do |buffer|
125
- request.encode_frame(id, buffer)
128
+ @frame_encoder.encode_frame(request, id, buffer)
126
129
  end
127
130
  else
128
131
  @lock.synchronize do
@@ -152,8 +155,9 @@ module Cql
152
155
  class RequestPromise < Promise
153
156
  attr_reader :request, :frame
154
157
 
155
- def initialize(request)
158
+ def initialize(request, frame_encoder)
156
159
  @request = request
160
+ @frame_encoder = frame_encoder
157
161
  @timed_out = false
158
162
  super()
159
163
  end
@@ -170,12 +174,13 @@ module Cql
170
174
  end
171
175
 
172
176
  def encode_frame!
173
- @frame = @request.encode_frame(0)
177
+ @frame = @frame_encoder.encode_frame(@request)
174
178
  end
175
179
  end
176
180
 
177
181
  def receive_data(data)
178
- @current_frame << data
182
+ @read_buffer << data
183
+ @current_frame = @frame_decoder.decode_frame(@read_buffer, @current_frame)
179
184
  while @current_frame.complete?
180
185
  id = @current_frame.stream_id
181
186
  if id == -1
@@ -183,7 +188,7 @@ module Cql
183
188
  else
184
189
  complete_request(id, @current_frame.body)
185
190
  end
186
- @current_frame = Protocol::ResponseFrame.new(@read_buffer)
191
+ @current_frame = @frame_decoder.decode_frame(@read_buffer)
187
192
  flush_request_queue
188
193
  end
189
194
  end
@@ -235,7 +240,7 @@ module Cql
235
240
  end
236
241
  end
237
242
  if id
238
- Protocol::Request.change_stream_id(id, frame)
243
+ @frame_encoder.change_stream_id(id, frame)
239
244
  @connection.write(frame)
240
245
  else
241
246
  break
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ class FrameDecoder
6
+ def initialize(compressor=nil)
7
+ @compressor = compressor
8
+ end
9
+
10
+ def decode_frame(buffer, partial_frame=nil)
11
+ partial_frame ||= NULL_FRAME
12
+ if partial_frame == NULL_FRAME
13
+ buffer_length = buffer.length
14
+ return NULL_FRAME if buffer_length < 8
15
+ fields = buffer.read_int
16
+ size = buffer.read_int
17
+ if buffer_length >= size
18
+ actual_decode(buffer, fields, size)
19
+ else
20
+ PartialFrame.new(fields, size)
21
+ end
22
+ elsif buffer.length >= partial_frame.size
23
+ actual_decode(buffer, partial_frame.fields, partial_frame.size)
24
+ else
25
+ partial_frame
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def actual_decode(buffer, fields, size)
32
+ if (fields >> 24) & 0x80 == 0
33
+ raise UnsupportedFrameTypeError, 'Request frames are not supported'
34
+ end
35
+ protocol_version = (fields >> 24) & 0x7f
36
+ compression = (fields >> 16) & 0x01
37
+ tracing = (fields >> 16) & 0x02
38
+ stream_id = (fields >> 8) & 0xff
39
+ stream_id = (stream_id & 0x7f) - (stream_id & 0x80)
40
+ opcode = fields & 0xff
41
+ if compression == 1
42
+ buffer = decompress(buffer, size)
43
+ size = buffer.size
44
+ end
45
+ extra_length = buffer.length - size
46
+ trace_id = tracing == 2 ? Decoding.read_uuid!(buffer) : nil
47
+ response = Response.decode!(opcode, buffer, trace_id)
48
+ if buffer.length > extra_length
49
+ buffer.discard(buffer.length - extra_length)
50
+ end
51
+ CompleteFrame.new(stream_id, response)
52
+ end
53
+
54
+ def decompress(buffer, size)
55
+ if @compressor
56
+ compressed_body = buffer.read(size)
57
+ ByteBuffer.new(@compressor.decompress(compressed_body))
58
+ else
59
+ raise UnexpectedCompressionError, 'Compressed frame received, but no compressor configured'
60
+ end
61
+ end
62
+
63
+ class NullFrame
64
+ def size
65
+ nil
66
+ end
67
+
68
+ def complete?
69
+ false
70
+ end
71
+ end
72
+
73
+ class PartialFrame
74
+ attr_reader :fields, :size
75
+
76
+ def initialize(fields, size)
77
+ @fields = fields
78
+ @size = size
79
+ end
80
+
81
+ def stream_id
82
+ nil
83
+ end
84
+
85
+ def complete?
86
+ false
87
+ end
88
+ end
89
+
90
+ class CompleteFrame
91
+ attr_reader :stream_id, :body
92
+
93
+ def initialize(stream_id, body)
94
+ @stream_id = stream_id
95
+ @body = body
96
+ end
97
+
98
+ def complete?
99
+ true
100
+ end
101
+ end
102
+
103
+ NULL_FRAME = NullFrame.new
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Protocol
5
+ class FrameEncoder
6
+ def initialize(compressor=nil)
7
+ @compressor = compressor
8
+ end
9
+
10
+ def encode_frame(request, stream_id=0, buffer=nil)
11
+ raise InvalidStreamIdError, 'The stream ID must be between 0 and 127' unless 0 <= stream_id && stream_id < 128
12
+ buffer ||= ByteBuffer.new
13
+ offset = buffer.bytesize
14
+ flags = request.trace? ? 2 : 0
15
+ body = request.write(ByteBuffer.new)
16
+ if @compressor && request.compressable? && @compressor.compress?(body)
17
+ flags |= 1
18
+ body = @compressor.compress(body)
19
+ end
20
+ header = [1, flags, stream_id, request.opcode, body.bytesize]
21
+ buffer << header.pack(Formats::HEADER_FORMAT)
22
+ buffer << body
23
+ buffer
24
+ end
25
+
26
+ def change_stream_id(new_stream_id, buffer, offset=0)
27
+ buffer.update(offset + 2, new_stream_id.chr)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -5,23 +5,19 @@ module Cql
5
5
  class Request
6
6
  include Encoding
7
7
 
8
- attr_reader :opcode
8
+ attr_reader :opcode, :trace
9
9
 
10
- def initialize(opcode)
10
+ def initialize(opcode, trace=false)
11
11
  @opcode = opcode
12
+ @trace = trace
12
13
  end
13
14
 
14
- def encode_frame(stream_id=0, buffer=ByteBuffer.new)
15
- raise InvalidStreamIdError, 'The stream ID must be between 0 and 127' unless 0 <= stream_id && stream_id < 128
16
- offset = buffer.bytesize
17
- buffer << [1, 0, stream_id, opcode, 0].pack(Formats::HEADER_FORMAT)
18
- write(buffer)
19
- buffer.update(offset + 4, [(buffer.bytesize - offset - 8)].pack(Formats::INT_FORMAT))
20
- buffer
15
+ def trace?
16
+ @trace
21
17
  end
22
18
 
23
- def self.change_stream_id(new_stream_id, buffer, offset=0)
24
- buffer.update(offset + 2, new_stream_id.chr)
19
+ def compressable?
20
+ true
25
21
  end
26
22
  end
27
23
  end