cql-rb 1.0.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.
- data/README.md +13 -0
- data/bin/cqlexec +135 -0
- data/lib/cql.rb +11 -0
- data/lib/cql/client.rb +196 -0
- data/lib/cql/future.rb +176 -0
- data/lib/cql/io.rb +13 -0
- data/lib/cql/io/io_reactor.rb +351 -0
- data/lib/cql/protocol.rb +39 -0
- data/lib/cql/protocol/decoding.rb +156 -0
- data/lib/cql/protocol/encoding.rb +109 -0
- data/lib/cql/protocol/request_frame.rb +228 -0
- data/lib/cql/protocol/response_frame.rb +551 -0
- data/lib/cql/uuid.rb +46 -0
- data/lib/cql/version.rb +5 -0
- data/spec/cql/client_spec.rb +368 -0
- data/spec/cql/future_spec.rb +297 -0
- data/spec/cql/io/io_reactor_spec.rb +290 -0
- data/spec/cql/protocol/decoding_spec.rb +464 -0
- data/spec/cql/protocol/encoding_spec.rb +338 -0
- data/spec/cql/protocol/request_frame_spec.rb +359 -0
- data/spec/cql/protocol/response_frame_spec.rb +746 -0
- data/spec/cql/uuid_spec.rb +40 -0
- data/spec/integration/client_spec.rb +101 -0
- data/spec/integration/protocol_spec.rb +326 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/fake_io_reactor.rb +55 -0
- data/spec/support/fake_server.rb +95 -0
- metadata +87 -0
@@ -0,0 +1,109 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cql
|
4
|
+
module Protocol
|
5
|
+
module Encoding
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def write_int(buffer, n)
|
9
|
+
buffer << [n].pack(Formats::INT_FORMAT)
|
10
|
+
end
|
11
|
+
|
12
|
+
def write_short(buffer, n)
|
13
|
+
buffer << [n].pack(Formats::SHORT_FORMAT)
|
14
|
+
end
|
15
|
+
|
16
|
+
def write_string(buffer, str)
|
17
|
+
buffer << [str.length].pack(Formats::SHORT_FORMAT)
|
18
|
+
buffer << str
|
19
|
+
buffer
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_long_string(buffer, str)
|
23
|
+
buffer << [str.length].pack(Formats::INT_FORMAT)
|
24
|
+
buffer << str
|
25
|
+
buffer
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_uuid(buffer, uuid)
|
29
|
+
write_varint(buffer, uuid.value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def write_string_list(buffer, strs)
|
33
|
+
buffer << [strs.size].pack(Formats::SHORT_FORMAT)
|
34
|
+
strs.each do |str|
|
35
|
+
write_string(buffer, str)
|
36
|
+
end
|
37
|
+
buffer
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_bytes(buffer, bytes)
|
41
|
+
if bytes
|
42
|
+
write_int(buffer, bytes.length)
|
43
|
+
buffer << bytes
|
44
|
+
else
|
45
|
+
write_int(buffer, -1)
|
46
|
+
end
|
47
|
+
buffer
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_short_bytes(buffer, bytes)
|
51
|
+
if bytes
|
52
|
+
write_short(buffer, bytes.length)
|
53
|
+
buffer << bytes
|
54
|
+
else
|
55
|
+
write_short(buffer, -1)
|
56
|
+
end
|
57
|
+
buffer
|
58
|
+
end
|
59
|
+
|
60
|
+
def write_consistency(buffer, consistency)
|
61
|
+
index = CONSISTENCIES.index(consistency)
|
62
|
+
raise EncodingError, %(Unknown consistency "#{consistency}") unless index
|
63
|
+
write_short(buffer, index)
|
64
|
+
end
|
65
|
+
|
66
|
+
def write_string_map(buffer, map)
|
67
|
+
buffer << [map.size].pack(Formats::SHORT_FORMAT)
|
68
|
+
map.each do |key, value|
|
69
|
+
write_string(buffer, key)
|
70
|
+
write_string(buffer, value)
|
71
|
+
end
|
72
|
+
buffer
|
73
|
+
end
|
74
|
+
|
75
|
+
def write_long(buffer, n)
|
76
|
+
top = n >> 32
|
77
|
+
bottom = n & 0xffffffff
|
78
|
+
write_int(buffer, top)
|
79
|
+
write_int(buffer, bottom)
|
80
|
+
end
|
81
|
+
|
82
|
+
def write_varint(buffer, n)
|
83
|
+
num = n
|
84
|
+
bytes = []
|
85
|
+
until num == 0 || num == -1
|
86
|
+
bytes << (num & 0xff)
|
87
|
+
num = num >> 8
|
88
|
+
end
|
89
|
+
buffer << bytes.reverse.pack(Formats::BYTES_FORMAT)
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_decimal(buffer, n)
|
93
|
+
sign, number_string, _, size = n.split
|
94
|
+
num = number_string.to_i
|
95
|
+
raw = write_varint('', num)
|
96
|
+
write_int(buffer, number_string.length - size)
|
97
|
+
buffer << raw
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_double(buffer, n)
|
101
|
+
buffer << [n].pack(Formats::DOUBLE_FORMAT)
|
102
|
+
end
|
103
|
+
|
104
|
+
def write_float(buffer, n)
|
105
|
+
buffer << [n].pack(Formats::FLOAT_FORMAT)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cql
|
4
|
+
module Protocol
|
5
|
+
class RequestFrame
|
6
|
+
def initialize(body, stream_id=0)
|
7
|
+
@body = body
|
8
|
+
@stream_id = stream_id
|
9
|
+
raise InvalidStreamIdError, 'The stream ID must be between 0 and 127' unless 0 <= @stream_id && @stream_id < 128
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(io)
|
13
|
+
buffer = [1, 0, @stream_id, @body.opcode, 0].pack(Formats::HEADER_FORMAT)
|
14
|
+
buffer = @body.write(buffer)
|
15
|
+
buffer[4, 4] = [buffer.length - 8].pack(Formats::INT_FORMAT)
|
16
|
+
io << buffer
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class RequestBody
|
21
|
+
include Encoding
|
22
|
+
|
23
|
+
attr_reader :opcode
|
24
|
+
|
25
|
+
def initialize(opcode)
|
26
|
+
@opcode = opcode
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class StartupRequest < RequestBody
|
31
|
+
def initialize(cql_version='3.0.0', compression=nil)
|
32
|
+
super(1)
|
33
|
+
@arguments = {CQL_VERSION => cql_version}
|
34
|
+
@arguments[COMPRESSION] = compression if compression
|
35
|
+
end
|
36
|
+
|
37
|
+
def write(io)
|
38
|
+
write_string_map(io, @arguments)
|
39
|
+
io
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
%(STARTUP #@arguments)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
CQL_VERSION = 'CQL_VERSION'.freeze
|
49
|
+
COMPRESSION = 'COMPRESSION'.freeze
|
50
|
+
end
|
51
|
+
|
52
|
+
class OptionsRequest < RequestBody
|
53
|
+
def initialize
|
54
|
+
super(5)
|
55
|
+
end
|
56
|
+
|
57
|
+
def write(io)
|
58
|
+
io
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
%(OPTIONS)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class RegisterRequest < RequestBody
|
67
|
+
def initialize(*events)
|
68
|
+
super(11)
|
69
|
+
@events = events
|
70
|
+
end
|
71
|
+
|
72
|
+
def write(io)
|
73
|
+
write_string_list(io, @events)
|
74
|
+
end
|
75
|
+
|
76
|
+
def to_s
|
77
|
+
%(REGISTER #@events)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class QueryRequest < RequestBody
|
82
|
+
attr_reader :cql, :consistency
|
83
|
+
|
84
|
+
def initialize(cql, consistency)
|
85
|
+
super(7)
|
86
|
+
@cql = cql
|
87
|
+
@consistency = consistency
|
88
|
+
end
|
89
|
+
|
90
|
+
def write(io)
|
91
|
+
write_long_string(io, @cql)
|
92
|
+
write_consistency(io, @consistency)
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_s
|
96
|
+
%(QUERY "#@cql" #{@consistency.to_s.upcase})
|
97
|
+
end
|
98
|
+
|
99
|
+
def eql?(rq)
|
100
|
+
self.class === rq && rq.cql.eql?(self.cql) && rq.consistency.eql?(self.consistency)
|
101
|
+
end
|
102
|
+
alias_method :==, :eql?
|
103
|
+
|
104
|
+
def hash
|
105
|
+
@h ||= (@cql.hash * 31) ^ consistency.hash
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class PrepareRequest < RequestBody
|
110
|
+
attr_reader :cql
|
111
|
+
|
112
|
+
def initialize(cql)
|
113
|
+
super(9)
|
114
|
+
@cql = cql
|
115
|
+
end
|
116
|
+
|
117
|
+
def write(io)
|
118
|
+
write_long_string(io, @cql)
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_s
|
122
|
+
%(PREPARE "#@cql")
|
123
|
+
end
|
124
|
+
|
125
|
+
def eql?(rq)
|
126
|
+
self.class === rq && rq.cql == self.cql
|
127
|
+
end
|
128
|
+
alias_method :==, :eql?
|
129
|
+
|
130
|
+
def hash
|
131
|
+
@h ||= @cql.hash
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
class ExecuteRequest < RequestBody
|
136
|
+
attr_reader :id, :metadata, :values, :consistency
|
137
|
+
|
138
|
+
def initialize(id, metadata, values, consistency)
|
139
|
+
super(10)
|
140
|
+
raise ArgumentError, "Metadata for #{metadata.size} columns, but #{values.size} values given" if metadata.size != values.size
|
141
|
+
@id = id
|
142
|
+
@metadata = metadata
|
143
|
+
@values = values
|
144
|
+
@consistency = consistency
|
145
|
+
end
|
146
|
+
|
147
|
+
def write(io)
|
148
|
+
write_short_bytes(io, @id)
|
149
|
+
write_short(io, @metadata.size)
|
150
|
+
@metadata.each_with_index do |(_, _, _, type), index|
|
151
|
+
write_value(io, @values[index], type)
|
152
|
+
end
|
153
|
+
write_consistency(io, @consistency)
|
154
|
+
end
|
155
|
+
|
156
|
+
def to_s
|
157
|
+
id = @id.each_byte.map { |x| x.to_s(16) }.join('')
|
158
|
+
%(EXECUTE #{id} #@values #{@consistency.to_s.upcase})
|
159
|
+
end
|
160
|
+
|
161
|
+
def eql?(rq)
|
162
|
+
self.class === rq && rq.id == self.id && rq.metadata == self.metadata && rq.values == self.values && rq.consistency == self.consistency
|
163
|
+
end
|
164
|
+
alias_method :==, :eql?
|
165
|
+
|
166
|
+
def hash
|
167
|
+
@h ||= begin
|
168
|
+
h = 0
|
169
|
+
h = ((h & 33554431) * 31) ^ @id.hash
|
170
|
+
h = ((h & 33554431) * 31) ^ @metadata.hash
|
171
|
+
h = ((h & 33554431) * 31) ^ @values.hash
|
172
|
+
h = ((h & 33554431) * 31) ^ @consistency.hash
|
173
|
+
h
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def write_value(io, value, type)
|
180
|
+
case type
|
181
|
+
when :ascii
|
182
|
+
write_bytes(io, value.encode(::Encoding::ASCII))
|
183
|
+
when :bigint
|
184
|
+
write_int(io, 8)
|
185
|
+
write_long(io, value)
|
186
|
+
when :blob
|
187
|
+
write_bytes(io, value.encode(::Encoding::BINARY))
|
188
|
+
when :boolean
|
189
|
+
write_int(io, 1)
|
190
|
+
io << (value ? Constants::TRUE_BYTE : Constants::FALSE_BYTE)
|
191
|
+
when :decimal
|
192
|
+
raw = write_decimal('', value)
|
193
|
+
write_int(io, raw.size)
|
194
|
+
io << raw
|
195
|
+
when :double
|
196
|
+
write_int(io, 8)
|
197
|
+
write_double(io, value)
|
198
|
+
when :float
|
199
|
+
write_int(io, 4)
|
200
|
+
write_float(io, value)
|
201
|
+
when :inet
|
202
|
+
write_int(io, value.ipv6? ? 16 : 4)
|
203
|
+
io << value.hton
|
204
|
+
when :int
|
205
|
+
write_int(io, 4)
|
206
|
+
write_int(io, value)
|
207
|
+
when :text, :varchar
|
208
|
+
write_bytes(io, value.encode(::Encoding::UTF_8))
|
209
|
+
when :timestamp
|
210
|
+
ms = (value.to_f * 1000).to_i
|
211
|
+
write_int(io, 8)
|
212
|
+
write_long(io, ms)
|
213
|
+
when :timeuuid, :uuid
|
214
|
+
write_int(io, 16)
|
215
|
+
write_uuid(io, value)
|
216
|
+
when :varint
|
217
|
+
raw = write_varint('', value)
|
218
|
+
write_int(io, raw.length)
|
219
|
+
io << raw
|
220
|
+
else
|
221
|
+
raise UnsupportedColumnTypeError, %(Unsupported column type: #{type})
|
222
|
+
end
|
223
|
+
rescue TypeError => e
|
224
|
+
raise TypeError, %("#{value}" cannot be encoded as #{type.to_s.upcase}: #{e.message}), e.backtrace
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
@@ -0,0 +1,551 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
require 'bigdecimal'
|
5
|
+
require 'set'
|
6
|
+
|
7
|
+
|
8
|
+
module Cql
|
9
|
+
module Protocol
|
10
|
+
class ResponseFrame
|
11
|
+
def initialize(buffer='')
|
12
|
+
@headers = FrameHeaders.new(buffer)
|
13
|
+
check_complete!
|
14
|
+
end
|
15
|
+
|
16
|
+
def stream_id
|
17
|
+
@headers && @headers.stream_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def header_length
|
21
|
+
8
|
22
|
+
end
|
23
|
+
|
24
|
+
def body_length
|
25
|
+
@headers && @headers.length
|
26
|
+
end
|
27
|
+
|
28
|
+
def body
|
29
|
+
@body.response
|
30
|
+
end
|
31
|
+
|
32
|
+
def complete?
|
33
|
+
@body && @body.complete?
|
34
|
+
end
|
35
|
+
|
36
|
+
def <<(str)
|
37
|
+
if @body
|
38
|
+
@body << str
|
39
|
+
else
|
40
|
+
@headers << str
|
41
|
+
check_complete!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def check_complete!
|
48
|
+
if @headers.complete?
|
49
|
+
@body = create_body
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def create_body
|
54
|
+
body_type = begin
|
55
|
+
case @headers.opcode
|
56
|
+
when 0x00 then ErrorResponse
|
57
|
+
when 0x02 then ReadyResponse
|
58
|
+
when 0x06 then SupportedResponse
|
59
|
+
when 0x08 then ResultResponse
|
60
|
+
when 0x0c then EventResponse
|
61
|
+
else
|
62
|
+
raise UnsupportedOperationError, "The operation #{@headers.opcode} is not supported"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
FrameBody.new(@headers.buffer, @headers.length, body_type)
|
66
|
+
end
|
67
|
+
|
68
|
+
class FrameHeaders
|
69
|
+
attr_reader :buffer, :protocol_version, :stream_id, :opcode, :length
|
70
|
+
|
71
|
+
def initialize(buffer)
|
72
|
+
@buffer = buffer
|
73
|
+
check_complete!
|
74
|
+
end
|
75
|
+
|
76
|
+
def <<(str)
|
77
|
+
@buffer << str
|
78
|
+
check_complete!
|
79
|
+
end
|
80
|
+
|
81
|
+
def complete?
|
82
|
+
!!@protocol_version
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def check_complete!
|
88
|
+
if @buffer.length >= 8
|
89
|
+
@protocol_version, @flags, @stream_id, @opcode, @length = @buffer.slice!(0, 8).unpack(Formats::HEADER_FORMAT)
|
90
|
+
raise UnsupportedFrameTypeError, 'Request frames are not supported' if @protocol_version > 0
|
91
|
+
@protocol_version &= 0x7f
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class FrameBody
|
97
|
+
attr_reader :response, :buffer
|
98
|
+
|
99
|
+
def initialize(buffer, length, type)
|
100
|
+
@buffer = buffer
|
101
|
+
@length = length
|
102
|
+
@type = type
|
103
|
+
check_complete!
|
104
|
+
end
|
105
|
+
|
106
|
+
def <<(str)
|
107
|
+
@buffer << str
|
108
|
+
check_complete!
|
109
|
+
end
|
110
|
+
|
111
|
+
def complete?
|
112
|
+
!!@response
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def check_complete!
|
118
|
+
if @buffer.length >= @length
|
119
|
+
extra_length = @buffer.length - @length
|
120
|
+
@response = @type.decode!(@buffer)
|
121
|
+
if @buffer.length > extra_length
|
122
|
+
@buffer.slice!(0, @buffer.length - extra_length)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class ResponseBody
|
130
|
+
extend Decoding
|
131
|
+
|
132
|
+
def self.decode!(buffer)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class ErrorResponse < ResponseBody
|
137
|
+
attr_reader :code, :message
|
138
|
+
|
139
|
+
def initialize(*args)
|
140
|
+
@code, @message = args
|
141
|
+
end
|
142
|
+
|
143
|
+
def self.decode!(buffer)
|
144
|
+
code = read_int!(buffer)
|
145
|
+
message = read_string!(buffer)
|
146
|
+
case code
|
147
|
+
when 0x1000, 0x1100, 0x1200, 0x2400, 0x2500
|
148
|
+
DetailedErrorResponse.decode!(code, message, buffer)
|
149
|
+
else
|
150
|
+
new(code, message)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def to_s
|
155
|
+
%(ERROR #@code "#@message")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class DetailedErrorResponse < ErrorResponse
|
160
|
+
attr_reader :details
|
161
|
+
|
162
|
+
def initialize(code, message, details)
|
163
|
+
super(code, message)
|
164
|
+
@details = details
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.decode!(code, message, buffer)
|
168
|
+
details = {}
|
169
|
+
case code
|
170
|
+
when 0x1000 # unavailable
|
171
|
+
details[:cl] = read_consistency!(buffer)
|
172
|
+
details[:required] = read_int!(buffer)
|
173
|
+
details[:alive] = read_int!(buffer)
|
174
|
+
when 0x1100 # write_timeout
|
175
|
+
details[:cl] = read_consistency!(buffer)
|
176
|
+
details[:received] = read_int!(buffer)
|
177
|
+
details[:blockfor] = read_int!(buffer)
|
178
|
+
details[:write_type] = read_string!(buffer)
|
179
|
+
when 0x1200 # read_timeout
|
180
|
+
details[:cl] = read_consistency!(buffer)
|
181
|
+
details[:received] = read_int!(buffer)
|
182
|
+
details[:blockfor] = read_int!(buffer)
|
183
|
+
details[:data_present] = read_byte!(buffer) != 0
|
184
|
+
when 0x2400 # already_exists
|
185
|
+
details[:ks] = read_string!(buffer)
|
186
|
+
details[:table] = read_string!(buffer)
|
187
|
+
when 0x2500
|
188
|
+
details[:id] = read_short_bytes!(buffer)
|
189
|
+
end
|
190
|
+
new(code, message, details)
|
191
|
+
end
|
192
|
+
|
193
|
+
def to_s
|
194
|
+
%(ERROR #@code "#@message" #@details)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class ReadyResponse < ResponseBody
|
199
|
+
def self.decode!(buffer)
|
200
|
+
new
|
201
|
+
end
|
202
|
+
|
203
|
+
def eql?(rs)
|
204
|
+
self.class === rs
|
205
|
+
end
|
206
|
+
alias_method :==, :eql?
|
207
|
+
|
208
|
+
def hash
|
209
|
+
@h ||= to_s.hash ^ 0xbadc0de
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_s
|
213
|
+
'READY'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class SupportedResponse < ResponseBody
|
218
|
+
attr_reader :options
|
219
|
+
|
220
|
+
def initialize(options)
|
221
|
+
@options = options
|
222
|
+
end
|
223
|
+
|
224
|
+
def self.decode!(buffer)
|
225
|
+
new(read_string_multimap!(buffer))
|
226
|
+
end
|
227
|
+
|
228
|
+
def to_s
|
229
|
+
%(SUPPORTED #{options})
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
class ResultResponse < ResponseBody
|
234
|
+
def self.decode!(buffer)
|
235
|
+
kind = read_int!(buffer)
|
236
|
+
case kind
|
237
|
+
when 0x01
|
238
|
+
VoidResultResponse.decode!(buffer)
|
239
|
+
when 0x02
|
240
|
+
RowsResultResponse.decode!(buffer)
|
241
|
+
when 0x03
|
242
|
+
SetKeyspaceResultResponse.decode!(buffer)
|
243
|
+
when 0x04
|
244
|
+
PreparedResultResponse.decode!(buffer)
|
245
|
+
when 0x05
|
246
|
+
SchemaChangeResultResponse.decode!(buffer)
|
247
|
+
else
|
248
|
+
raise UnsupportedResultKindError, %(Unsupported result kind: #{kind})
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def void?
|
253
|
+
false
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class VoidResultResponse < ResultResponse
|
258
|
+
def self.decode!(buffer)
|
259
|
+
new
|
260
|
+
end
|
261
|
+
|
262
|
+
def to_s
|
263
|
+
%(RESULT VOID)
|
264
|
+
end
|
265
|
+
|
266
|
+
def void?
|
267
|
+
true
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
class RowsResultResponse < ResultResponse
|
272
|
+
attr_reader :rows, :metadata
|
273
|
+
|
274
|
+
def initialize(*args)
|
275
|
+
@rows, @metadata = args
|
276
|
+
end
|
277
|
+
|
278
|
+
def self.decode!(buffer)
|
279
|
+
column_specs = read_metadata!(buffer)
|
280
|
+
new(read_rows!(buffer, column_specs), column_specs)
|
281
|
+
end
|
282
|
+
|
283
|
+
def to_s
|
284
|
+
%(RESULT ROWS #@metadata #@rows)
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
def self.read_column_type!(buffer)
|
290
|
+
id, type = read_option!(buffer) do |id, b|
|
291
|
+
case id
|
292
|
+
when 0x01 then :ascii
|
293
|
+
when 0x02 then :bigint
|
294
|
+
when 0x03 then :blob
|
295
|
+
when 0x04 then :boolean
|
296
|
+
when 0x05 then :counter
|
297
|
+
when 0x06 then :decimal
|
298
|
+
when 0x07 then :double
|
299
|
+
when 0x08 then :float
|
300
|
+
when 0x09 then :int
|
301
|
+
# when 0x0a then :text
|
302
|
+
when 0x0b then :timestamp
|
303
|
+
when 0x0c then :uuid
|
304
|
+
when 0x0d then :varchar
|
305
|
+
when 0x0e then :varint
|
306
|
+
when 0x0f then :timeuuid
|
307
|
+
when 0x10 then :inet
|
308
|
+
when 0x20
|
309
|
+
sub_type = read_column_type!(buffer)
|
310
|
+
[:list, sub_type]
|
311
|
+
when 0x21
|
312
|
+
key_type = read_column_type!(buffer)
|
313
|
+
value_type = read_column_type!(buffer)
|
314
|
+
[:map, key_type, value_type]
|
315
|
+
when 0x22
|
316
|
+
sub_type = read_column_type!(buffer)
|
317
|
+
[:set, sub_type]
|
318
|
+
else
|
319
|
+
raise UnsupportedColumnTypeError, %(Unsupported column type: #{id})
|
320
|
+
end
|
321
|
+
end
|
322
|
+
type
|
323
|
+
end
|
324
|
+
|
325
|
+
def self.read_metadata!(buffer)
|
326
|
+
flags = read_int!(buffer)
|
327
|
+
columns_count = read_int!(buffer)
|
328
|
+
if flags & 0x01 == 0x01
|
329
|
+
global_keyspace_name = read_string!(buffer)
|
330
|
+
global_table_name = read_string!(buffer)
|
331
|
+
end
|
332
|
+
column_specs = columns_count.times.map do
|
333
|
+
if global_keyspace_name
|
334
|
+
keyspace_name = global_keyspace_name
|
335
|
+
table_name = global_table_name
|
336
|
+
else
|
337
|
+
keyspace_name = read_string!(buffer)
|
338
|
+
table_name = read_string!(buffer)
|
339
|
+
end
|
340
|
+
column_name = read_string!(buffer)
|
341
|
+
type = read_column_type!(buffer)
|
342
|
+
[keyspace_name, table_name, column_name, type]
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def self.convert_type(bytes, type)
|
347
|
+
return nil unless bytes
|
348
|
+
case type
|
349
|
+
when :ascii
|
350
|
+
bytes.force_encoding(::Encoding::ASCII)
|
351
|
+
when :bigint
|
352
|
+
read_long!(bytes)
|
353
|
+
when :blob
|
354
|
+
bytes
|
355
|
+
when :boolean
|
356
|
+
bytes == Constants::TRUE_BYTE
|
357
|
+
when :counter
|
358
|
+
read_long!(bytes)
|
359
|
+
when :decimal
|
360
|
+
read_decimal!(bytes)
|
361
|
+
when :double
|
362
|
+
read_double!(bytes)
|
363
|
+
when :float
|
364
|
+
read_float!(bytes)
|
365
|
+
when :int
|
366
|
+
read_int!(bytes)
|
367
|
+
when :timestamp
|
368
|
+
timestamp = read_long!(bytes)
|
369
|
+
Time.at(timestamp/1000.0)
|
370
|
+
when :varchar, :text
|
371
|
+
bytes.force_encoding(::Encoding::UTF_8)
|
372
|
+
when :varint
|
373
|
+
read_varint!(bytes)
|
374
|
+
when :timeuuid, :uuid
|
375
|
+
read_uuid!(bytes)
|
376
|
+
when :inet
|
377
|
+
IPAddr.new_ntoh(bytes)
|
378
|
+
when Array
|
379
|
+
case type.first
|
380
|
+
when :list
|
381
|
+
list = []
|
382
|
+
size = read_short!(bytes)
|
383
|
+
size.times do
|
384
|
+
list << convert_type(read_short_bytes!(bytes), type.last)
|
385
|
+
end
|
386
|
+
list
|
387
|
+
when :map
|
388
|
+
map = {}
|
389
|
+
size = read_short!(bytes)
|
390
|
+
size.times do
|
391
|
+
key = convert_type(read_short_bytes!(bytes), type[1])
|
392
|
+
value = convert_type(read_short_bytes!(bytes), type[2])
|
393
|
+
map[key] = value
|
394
|
+
end
|
395
|
+
map
|
396
|
+
when :set
|
397
|
+
set = Set.new
|
398
|
+
size = read_short!(bytes)
|
399
|
+
size.times do
|
400
|
+
set << convert_type(read_short_bytes!(bytes), type.last)
|
401
|
+
end
|
402
|
+
set
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
def self.read_rows!(buffer, column_specs)
|
408
|
+
rows_count = read_int!(buffer)
|
409
|
+
rows = []
|
410
|
+
rows_count.times do |row_index|
|
411
|
+
row = {}
|
412
|
+
column_specs.each do |column_spec|
|
413
|
+
column_value = read_bytes!(buffer)
|
414
|
+
row[column_spec[2]] = convert_type(column_value, column_spec[3])
|
415
|
+
end
|
416
|
+
rows << row
|
417
|
+
end
|
418
|
+
rows
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
class SetKeyspaceResultResponse < ResultResponse
|
423
|
+
attr_reader :keyspace
|
424
|
+
|
425
|
+
def initialize(keyspace)
|
426
|
+
@keyspace = keyspace
|
427
|
+
end
|
428
|
+
|
429
|
+
def self.decode!(buffer)
|
430
|
+
new(read_string!(buffer))
|
431
|
+
end
|
432
|
+
|
433
|
+
def to_s
|
434
|
+
%(RESULT SET_KEYSPACE "#@keyspace")
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
class PreparedResultResponse < ResultResponse
|
439
|
+
attr_reader :id, :metadata
|
440
|
+
|
441
|
+
def initialize(*args)
|
442
|
+
@id, @metadata = args
|
443
|
+
end
|
444
|
+
|
445
|
+
def self.decode!(buffer)
|
446
|
+
id = read_short_bytes!(buffer)
|
447
|
+
metadata = RowsResultResponse.read_metadata!(buffer)
|
448
|
+
new(id, metadata)
|
449
|
+
end
|
450
|
+
|
451
|
+
def to_s
|
452
|
+
%(RESULT PREPARED #{id.each_byte.map { |x| x.to_s(16) }.join('')} #@metadata)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
class SchemaChangeResultResponse < ResultResponse
|
457
|
+
attr_reader :change, :keyspace, :table
|
458
|
+
|
459
|
+
def initialize(*args)
|
460
|
+
@change, @keyspace, @table = args
|
461
|
+
end
|
462
|
+
|
463
|
+
def self.decode!(buffer)
|
464
|
+
new(read_string!(buffer), read_string!(buffer), read_string!(buffer))
|
465
|
+
end
|
466
|
+
|
467
|
+
def to_s
|
468
|
+
%(RESULT SCHEMA_CHANGE #@change "#@keyspace" "#@table")
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
class EventResponse < ResultResponse
|
473
|
+
def self.decode!(buffer)
|
474
|
+
type = read_string!(buffer)
|
475
|
+
case type
|
476
|
+
when SchemaChangeEventResponse::TYPE
|
477
|
+
SchemaChangeEventResponse.decode!(buffer)
|
478
|
+
when StatusChangeEventResponse::TYPE
|
479
|
+
StatusChangeEventResponse.decode!(buffer)
|
480
|
+
when TopologyChangeEventResponse::TYPE
|
481
|
+
TopologyChangeEventResponse.decode!(buffer)
|
482
|
+
else
|
483
|
+
raise UnsupportedEventTypeError, %(Unsupported event type: "#{type}")
|
484
|
+
end
|
485
|
+
end
|
486
|
+
end
|
487
|
+
|
488
|
+
class SchemaChangeEventResponse < EventResponse
|
489
|
+
TYPE = 'SCHEMA_CHANGE'.freeze
|
490
|
+
|
491
|
+
attr_reader :type, :change, :keyspace, :table
|
492
|
+
|
493
|
+
def initialize(*args)
|
494
|
+
@change, @keyspace, @table = args
|
495
|
+
@type = TYPE
|
496
|
+
end
|
497
|
+
|
498
|
+
def self.decode!(buffer)
|
499
|
+
new(read_string!(buffer), read_string!(buffer), read_string!(buffer))
|
500
|
+
end
|
501
|
+
|
502
|
+
def eql?(rs)
|
503
|
+
rs.type == self.type && rs.change == self.change && rs.keyspace == self.keyspace && rs.table == self.table
|
504
|
+
end
|
505
|
+
alias_method :==, :eql?
|
506
|
+
|
507
|
+
def hash
|
508
|
+
@h ||= begin
|
509
|
+
h = 0
|
510
|
+
h = ((h & 33554431) * 31) ^ @type.hash
|
511
|
+
h = ((h & 33554431) * 31) ^ @change.hash
|
512
|
+
h = ((h & 33554431) * 31) ^ @keyspace.hash
|
513
|
+
h = ((h & 33554431) * 31) ^ @table.hash
|
514
|
+
h
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
def to_s
|
519
|
+
%(EVENT #@type #@change "#@keyspace" "#@table")
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
class StatusChangeEventResponse < EventResponse
|
524
|
+
TYPE = 'STATUS_CHANGE'.freeze
|
525
|
+
|
526
|
+
attr_reader :type, :change, :address, :port
|
527
|
+
|
528
|
+
def initialize(*args)
|
529
|
+
@change, @address, @port = args
|
530
|
+
@type = TYPE
|
531
|
+
end
|
532
|
+
|
533
|
+
def self.decode!(buffer)
|
534
|
+
new(read_string!(buffer), *read_inet!(buffer))
|
535
|
+
end
|
536
|
+
|
537
|
+
def to_s
|
538
|
+
%(EVENT #@type #@change #@address:#@port)
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
class TopologyChangeEventResponse < StatusChangeEventResponse
|
543
|
+
TYPE = 'TOPOLOGY_CHANGE'.freeze
|
544
|
+
|
545
|
+
def initialize(*args)
|
546
|
+
super
|
547
|
+
@type = TYPE
|
548
|
+
end
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|