cql-rb 1.1.3 → 1.2.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
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