yugabyte-ycql-driver 3.2.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +13 -0
- data/README.md +242 -0
- data/ext/cassandra_murmur3/cassandra_murmur3.c +178 -0
- data/ext/cassandra_murmur3/extconf.rb +2 -0
- data/lib/cassandra/address_resolution.rb +36 -0
- data/lib/cassandra/address_resolution/policies.rb +2 -0
- data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
- data/lib/cassandra/address_resolution/policies/none.rb +35 -0
- data/lib/cassandra/aggregate.rb +123 -0
- data/lib/cassandra/argument.rb +51 -0
- data/lib/cassandra/attr_boolean.rb +33 -0
- data/lib/cassandra/auth.rb +100 -0
- data/lib/cassandra/auth/providers.rb +17 -0
- data/lib/cassandra/auth/providers/password.rb +65 -0
- data/lib/cassandra/cassandra_logger.rb +80 -0
- data/lib/cassandra/cluster.rb +331 -0
- data/lib/cassandra/cluster/client.rb +1612 -0
- data/lib/cassandra/cluster/connection_pool.rb +78 -0
- data/lib/cassandra/cluster/connector.rb +372 -0
- data/lib/cassandra/cluster/control_connection.rb +962 -0
- data/lib/cassandra/cluster/failed_connection.rb +35 -0
- data/lib/cassandra/cluster/metadata.rb +142 -0
- data/lib/cassandra/cluster/options.rb +145 -0
- data/lib/cassandra/cluster/registry.rb +284 -0
- data/lib/cassandra/cluster/schema.rb +405 -0
- data/lib/cassandra/cluster/schema/cql_type_parser.rb +112 -0
- data/lib/cassandra/cluster/schema/fetchers.rb +1627 -0
- data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +175 -0
- data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
- data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +45 -0
- data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
- data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
- data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
- data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +102 -0
- data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
- data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
- data/lib/cassandra/column.rb +66 -0
- data/lib/cassandra/column_container.rb +326 -0
- data/lib/cassandra/compression.rb +69 -0
- data/lib/cassandra/compression/compressors/lz4.rb +73 -0
- data/lib/cassandra/compression/compressors/snappy.rb +69 -0
- data/lib/cassandra/custom_data.rb +53 -0
- data/lib/cassandra/driver.rb +260 -0
- data/lib/cassandra/errors.rb +784 -0
- data/lib/cassandra/execution/info.rb +69 -0
- data/lib/cassandra/execution/options.rb +267 -0
- data/lib/cassandra/execution/profile.rb +153 -0
- data/lib/cassandra/execution/profile_manager.rb +71 -0
- data/lib/cassandra/execution/trace.rb +192 -0
- data/lib/cassandra/executors.rb +113 -0
- data/lib/cassandra/function.rb +156 -0
- data/lib/cassandra/function_collection.rb +85 -0
- data/lib/cassandra/future.rb +794 -0
- data/lib/cassandra/host.rb +102 -0
- data/lib/cassandra/index.rb +118 -0
- data/lib/cassandra/keyspace.rb +473 -0
- data/lib/cassandra/listener.rb +87 -0
- data/lib/cassandra/load_balancing.rb +121 -0
- data/lib/cassandra/load_balancing/policies.rb +20 -0
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +172 -0
- data/lib/cassandra/load_balancing/policies/round_robin.rb +141 -0
- data/lib/cassandra/load_balancing/policies/token_aware.rb +149 -0
- data/lib/cassandra/load_balancing/policies/white_list.rb +100 -0
- data/lib/cassandra/materialized_view.rb +92 -0
- data/lib/cassandra/null_logger.rb +56 -0
- data/lib/cassandra/protocol.rb +102 -0
- data/lib/cassandra/protocol/coder.rb +1085 -0
- data/lib/cassandra/protocol/cql_byte_buffer.rb +418 -0
- data/lib/cassandra/protocol/cql_protocol_handler.rb +448 -0
- data/lib/cassandra/protocol/request.rb +41 -0
- data/lib/cassandra/protocol/requests/auth_response_request.rb +51 -0
- data/lib/cassandra/protocol/requests/batch_request.rb +117 -0
- data/lib/cassandra/protocol/requests/credentials_request.rb +51 -0
- data/lib/cassandra/protocol/requests/execute_request.rb +122 -0
- data/lib/cassandra/protocol/requests/options_request.rb +39 -0
- data/lib/cassandra/protocol/requests/prepare_request.rb +59 -0
- data/lib/cassandra/protocol/requests/query_request.rb +112 -0
- data/lib/cassandra/protocol/requests/register_request.rb +38 -0
- data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
- data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
- data/lib/cassandra/protocol/response.rb +28 -0
- data/lib/cassandra/protocol/responses/already_exists_error_response.rb +50 -0
- data/lib/cassandra/protocol/responses/auth_challenge_response.rb +36 -0
- data/lib/cassandra/protocol/responses/auth_success_response.rb +36 -0
- data/lib/cassandra/protocol/responses/authenticate_response.rb +36 -0
- data/lib/cassandra/protocol/responses/error_response.rb +142 -0
- data/lib/cassandra/protocol/responses/event_response.rb +30 -0
- data/lib/cassandra/protocol/responses/function_failure_error_response.rb +52 -0
- data/lib/cassandra/protocol/responses/prepared_result_response.rb +62 -0
- data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +59 -0
- data/lib/cassandra/protocol/responses/read_failure_error_response.rb +71 -0
- data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +61 -0
- data/lib/cassandra/protocol/responses/ready_response.rb +43 -0
- data/lib/cassandra/protocol/responses/result_response.rb +42 -0
- data/lib/cassandra/protocol/responses/rows_result_response.rb +39 -0
- data/lib/cassandra/protocol/responses/schema_change_event_response.rb +73 -0
- data/lib/cassandra/protocol/responses/schema_change_result_response.rb +70 -0
- data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +37 -0
- data/lib/cassandra/protocol/responses/status_change_event_response.rb +39 -0
- data/lib/cassandra/protocol/responses/supported_response.rb +36 -0
- data/lib/cassandra/protocol/responses/topology_change_event_response.rb +33 -0
- data/lib/cassandra/protocol/responses/unavailable_error_response.rb +58 -0
- data/lib/cassandra/protocol/responses/unprepared_error_response.rb +48 -0
- data/lib/cassandra/protocol/responses/void_result_response.rb +34 -0
- data/lib/cassandra/protocol/responses/write_failure_error_response.rb +73 -0
- data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +63 -0
- data/lib/cassandra/protocol/v1.rb +326 -0
- data/lib/cassandra/protocol/v3.rb +358 -0
- data/lib/cassandra/protocol/v4.rb +478 -0
- data/lib/cassandra/reconnection.rb +49 -0
- data/lib/cassandra/reconnection/policies.rb +20 -0
- data/lib/cassandra/reconnection/policies/constant.rb +46 -0
- data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
- data/lib/cassandra/result.rb +276 -0
- data/lib/cassandra/retry.rb +154 -0
- data/lib/cassandra/retry/policies.rb +21 -0
- data/lib/cassandra/retry/policies/default.rb +53 -0
- data/lib/cassandra/retry/policies/downgrading_consistency.rb +73 -0
- data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
- data/lib/cassandra/session.rb +270 -0
- data/lib/cassandra/statement.rb +32 -0
- data/lib/cassandra/statements.rb +23 -0
- data/lib/cassandra/statements/batch.rb +146 -0
- data/lib/cassandra/statements/bound.rb +65 -0
- data/lib/cassandra/statements/prepared.rb +235 -0
- data/lib/cassandra/statements/simple.rb +118 -0
- data/lib/cassandra/statements/void.rb +38 -0
- data/lib/cassandra/table.rb +240 -0
- data/lib/cassandra/time.rb +103 -0
- data/lib/cassandra/time_uuid.rb +78 -0
- data/lib/cassandra/timestamp_generator.rb +37 -0
- data/lib/cassandra/timestamp_generator/simple.rb +38 -0
- data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
- data/lib/cassandra/trigger.rb +67 -0
- data/lib/cassandra/tuple.rb +131 -0
- data/lib/cassandra/types.rb +1704 -0
- data/lib/cassandra/udt.rb +443 -0
- data/lib/cassandra/util.rb +464 -0
- data/lib/cassandra/uuid.rb +110 -0
- data/lib/cassandra/uuid/generator.rb +212 -0
- data/lib/cassandra/version.rb +21 -0
- data/lib/datastax/cassandra.rb +47 -0
- data/lib/ycql.rb +842 -0
- metadata +243 -0
@@ -0,0 +1,418 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright DataStax, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Cassandra
|
20
|
+
module Protocol
|
21
|
+
class CqlByteBuffer < Ione::ByteBuffer
|
22
|
+
# @private
|
23
|
+
MINUS = '-'.freeze
|
24
|
+
# @private
|
25
|
+
ZERO = '0'.freeze
|
26
|
+
# @private
|
27
|
+
DECIMAL_POINT = '.'.freeze
|
28
|
+
# @private
|
29
|
+
FLOAT_STRING_FORMAT = 'F'.freeze
|
30
|
+
# @private
|
31
|
+
NO_CHAR = ''.freeze
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} #{to_str.inspect}>"
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_unsigned_byte
|
38
|
+
read_byte
|
39
|
+
rescue RangeError => e
|
40
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_varint(len = bytesize, signed = true)
|
44
|
+
bytes = read(len)
|
45
|
+
n = 0
|
46
|
+
bytes.each_byte do |b|
|
47
|
+
n = (n << 8) | b
|
48
|
+
end
|
49
|
+
n -= 2**(bytes.length * 8) if signed && bytes.getbyte(0) & 0x80 == 0x80
|
50
|
+
n
|
51
|
+
rescue RangeError => e
|
52
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_decimal(len = bytesize)
|
56
|
+
scale = read_signed_int
|
57
|
+
number_string = read_varint(len - 4).to_s
|
58
|
+
if scale <= 0
|
59
|
+
# Special case where the actual scale is positive; scale in the protocol is actually negative of
|
60
|
+
# reality.
|
61
|
+
BigDecimal.new(number_string + '0' * -scale)
|
62
|
+
else
|
63
|
+
if number_string.length <= scale
|
64
|
+
if number_string.start_with?(MINUS)
|
65
|
+
number_string = number_string[1, number_string.length - 1]
|
66
|
+
fraction_string = MINUS + ZERO << DECIMAL_POINT
|
67
|
+
else
|
68
|
+
fraction_string = ZERO + DECIMAL_POINT
|
69
|
+
end
|
70
|
+
(scale - number_string.length).times { fraction_string << ZERO }
|
71
|
+
fraction_string << number_string
|
72
|
+
else
|
73
|
+
fraction_string = number_string[0, number_string.length - scale]
|
74
|
+
fraction_string << DECIMAL_POINT
|
75
|
+
fraction_string <<
|
76
|
+
number_string[number_string.length - scale, number_string.length]
|
77
|
+
end
|
78
|
+
BigDecimal.new(fraction_string)
|
79
|
+
end
|
80
|
+
rescue Errors::DecodingError => e
|
81
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_long
|
85
|
+
top, bottom = read(8).unpack(Formats::TWO_INTS_FORMAT)
|
86
|
+
return (top << 32) | bottom if top <= 0x7fffffff
|
87
|
+
top ^= 0xffffffff
|
88
|
+
bottom ^= 0xffffffff
|
89
|
+
-((top << 32) | bottom) - 1
|
90
|
+
rescue RangeError => e
|
91
|
+
raise Errors::DecodingError, e.message, e.backtrace
|
92
|
+
end
|
93
|
+
|
94
|
+
def read_double
|
95
|
+
read(8).unpack(Formats::DOUBLE_FORMAT).first
|
96
|
+
rescue RangeError => e
|
97
|
+
raise Errors::DecodingError,
|
98
|
+
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_double_le
|
102
|
+
read(8).unpack(Formats::DOUBLE_FORMAT_LE).first
|
103
|
+
rescue RangeError => e
|
104
|
+
raise Errors::DecodingError,
|
105
|
+
"Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
106
|
+
end
|
107
|
+
|
108
|
+
def read_float
|
109
|
+
read(4).unpack(Formats::FLOAT_FORMAT).first
|
110
|
+
rescue RangeError => e
|
111
|
+
raise Errors::DecodingError,
|
112
|
+
"Not enough bytes available to decode a float: #{e.message}", e.backtrace
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_signed_int
|
116
|
+
n = read_int
|
117
|
+
return n if n <= 0x7fffffff
|
118
|
+
n - 0xffffffff - 1
|
119
|
+
rescue RangeError => e
|
120
|
+
raise Errors::DecodingError,
|
121
|
+
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
122
|
+
end
|
123
|
+
|
124
|
+
def read_unsigned_int_le
|
125
|
+
read(4).unpack(Formats::INT_FORMAT_LE).first
|
126
|
+
rescue RangeError => e
|
127
|
+
raise Errors::DecodingError,
|
128
|
+
"Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
129
|
+
end
|
130
|
+
|
131
|
+
def read_unsigned_short
|
132
|
+
read_short
|
133
|
+
rescue RangeError => e
|
134
|
+
raise Errors::DecodingError,
|
135
|
+
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
136
|
+
end
|
137
|
+
|
138
|
+
def read_unsigned_short_le
|
139
|
+
read(2).unpack(Formats::SHORT_FORMAT_LE).first
|
140
|
+
rescue RangeError => e
|
141
|
+
raise Errors::DecodingError,
|
142
|
+
"Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
143
|
+
end
|
144
|
+
|
145
|
+
def read_string
|
146
|
+
length = read_unsigned_short
|
147
|
+
string = read(length)
|
148
|
+
string.force_encoding(::Encoding::UTF_8)
|
149
|
+
string
|
150
|
+
rescue RangeError => e
|
151
|
+
raise Errors::DecodingError,
|
152
|
+
"Not enough bytes available to decode a string: #{e.message}", e.backtrace
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_long_string
|
156
|
+
length = read_signed_int
|
157
|
+
string = read(length)
|
158
|
+
string.force_encoding(::Encoding::UTF_8)
|
159
|
+
string
|
160
|
+
rescue RangeError => e
|
161
|
+
raise Errors::DecodingError,
|
162
|
+
"Not enough bytes available to decode a long string: #{e.message}",
|
163
|
+
e.backtrace
|
164
|
+
end
|
165
|
+
|
166
|
+
def read_uuid(impl = Uuid)
|
167
|
+
impl.new(read_varint(16, false))
|
168
|
+
rescue Errors::DecodingError => e
|
169
|
+
raise Errors::DecodingError,
|
170
|
+
"Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
|
171
|
+
end
|
172
|
+
|
173
|
+
def read_string_list
|
174
|
+
size = read_unsigned_short
|
175
|
+
Array.new(size) { read_string }
|
176
|
+
end
|
177
|
+
|
178
|
+
def read_bytes
|
179
|
+
size = read_signed_int
|
180
|
+
return nil if size & 0x80000000 == 0x80000000
|
181
|
+
read(size)
|
182
|
+
rescue RangeError => e
|
183
|
+
raise Errors::DecodingError,
|
184
|
+
"Not enough bytes available to decode a bytes: #{e.message}", e.backtrace
|
185
|
+
end
|
186
|
+
|
187
|
+
def read_short_bytes
|
188
|
+
read(read_unsigned_short)
|
189
|
+
rescue RangeError => e
|
190
|
+
raise Errors::DecodingError,
|
191
|
+
"Not enough bytes available to decode a short bytes: #{e.message}",
|
192
|
+
e.backtrace
|
193
|
+
end
|
194
|
+
|
195
|
+
def read_option
|
196
|
+
id = read_unsigned_short
|
197
|
+
value = nil
|
198
|
+
value = yield id, self if block_given?
|
199
|
+
[id, value]
|
200
|
+
end
|
201
|
+
|
202
|
+
def read_inet
|
203
|
+
size = read_byte
|
204
|
+
ip_addr = IPAddr.new_ntoh(read(size))
|
205
|
+
port = read_int
|
206
|
+
[ip_addr, port]
|
207
|
+
rescue RangeError => e
|
208
|
+
raise Errors::DecodingError,
|
209
|
+
"Not enough bytes available to decode an INET: #{e.message}",
|
210
|
+
e.backtrace
|
211
|
+
end
|
212
|
+
|
213
|
+
def read_inet_addr
|
214
|
+
size = read_byte
|
215
|
+
IPAddr.new_ntoh(read(size))
|
216
|
+
rescue RangeError => e
|
217
|
+
raise Errors::DecodingError,
|
218
|
+
"Not enough bytes available to decode an INET addr: #{e.message}",
|
219
|
+
e.backtrace
|
220
|
+
end
|
221
|
+
|
222
|
+
def read_consistency
|
223
|
+
index = read_unsigned_short
|
224
|
+
if index >= CONSISTENCIES.size || CONSISTENCIES[index].nil?
|
225
|
+
raise Errors::DecodingError, "Unknown consistency index #{index}"
|
226
|
+
end
|
227
|
+
CONSISTENCIES[index]
|
228
|
+
end
|
229
|
+
|
230
|
+
def read_string_map
|
231
|
+
map = {}
|
232
|
+
map_size = read_unsigned_short
|
233
|
+
map_size.times do
|
234
|
+
key = read_string
|
235
|
+
map[key] = read_string
|
236
|
+
end
|
237
|
+
map
|
238
|
+
end
|
239
|
+
|
240
|
+
def read_bytes_map
|
241
|
+
map = {}
|
242
|
+
map_size = read_unsigned_short
|
243
|
+
map_size.times do
|
244
|
+
key = read_string
|
245
|
+
map[key] = read_bytes
|
246
|
+
end
|
247
|
+
map
|
248
|
+
end
|
249
|
+
|
250
|
+
def read_reason_map
|
251
|
+
# reason_map is new in v5. Starts with an int indicating the number of key-value pairs, followed by
|
252
|
+
# the key-value pairs. Keys are inet's, values are short int's.
|
253
|
+
map = {}
|
254
|
+
map_size = read_int
|
255
|
+
map_size.times do
|
256
|
+
key = read_inet_addr
|
257
|
+
map[key] = read_short
|
258
|
+
end
|
259
|
+
map
|
260
|
+
end
|
261
|
+
|
262
|
+
def read_string_multimap
|
263
|
+
map = {}
|
264
|
+
map_size = read_unsigned_short
|
265
|
+
map_size.times do
|
266
|
+
key = read_string
|
267
|
+
map[key] = read_string_list
|
268
|
+
end
|
269
|
+
map
|
270
|
+
end
|
271
|
+
|
272
|
+
def read_smallint
|
273
|
+
n = read_short
|
274
|
+
return n if n <= 0x7fff
|
275
|
+
n - 0xffff - 1
|
276
|
+
rescue RangeError => e
|
277
|
+
raise Errors::DecodingError,
|
278
|
+
"Not enough bytes available to decode a smallint: #{e.message}", e.backtrace
|
279
|
+
end
|
280
|
+
|
281
|
+
def read_tinyint
|
282
|
+
n = read_byte
|
283
|
+
return n if n <= 0x7f
|
284
|
+
n - 0xff - 1
|
285
|
+
rescue RangeError => e
|
286
|
+
raise Errors::DecodingError,
|
287
|
+
"Not enough bytes available to decode a tinyint: #{e.message}", e.backtrace
|
288
|
+
end
|
289
|
+
|
290
|
+
def append_tinyint(n)
|
291
|
+
append([n].pack(Formats::CHAR_FORMAT))
|
292
|
+
end
|
293
|
+
|
294
|
+
def append_smallint(n)
|
295
|
+
append_short(n)
|
296
|
+
end
|
297
|
+
|
298
|
+
def append_int(n)
|
299
|
+
append([n].pack(Formats::INT_FORMAT))
|
300
|
+
end
|
301
|
+
|
302
|
+
def append_short(n)
|
303
|
+
append([n].pack(Formats::SHORT_FORMAT))
|
304
|
+
end
|
305
|
+
|
306
|
+
def append_string(str)
|
307
|
+
str = str.to_s
|
308
|
+
append_short(str.bytesize)
|
309
|
+
append(str)
|
310
|
+
end
|
311
|
+
|
312
|
+
def append_long_string(str)
|
313
|
+
append_int(str.bytesize)
|
314
|
+
append(str)
|
315
|
+
end
|
316
|
+
|
317
|
+
def append_uuid(uuid)
|
318
|
+
v = uuid.value
|
319
|
+
append_int((v >> 96) & 0xffffffff)
|
320
|
+
append_int((v >> 64) & 0xffffffff)
|
321
|
+
append_int((v >> 32) & 0xffffffff)
|
322
|
+
append_int((v >> 0) & 0xffffffff)
|
323
|
+
end
|
324
|
+
|
325
|
+
def append_string_list(strs)
|
326
|
+
append_short(strs.size)
|
327
|
+
strs.each do |str|
|
328
|
+
append_string(str)
|
329
|
+
end
|
330
|
+
self
|
331
|
+
end
|
332
|
+
|
333
|
+
def append_bytes(bytes)
|
334
|
+
if bytes
|
335
|
+
append_int(bytes.bytesize)
|
336
|
+
append(bytes)
|
337
|
+
else
|
338
|
+
append_int(-1)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
def append_short_bytes(bytes)
|
343
|
+
if bytes
|
344
|
+
append_short(bytes.bytesize)
|
345
|
+
append(bytes)
|
346
|
+
else
|
347
|
+
append_short(-1)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def append_consistency(consistency)
|
352
|
+
index = CONSISTENCIES.index(consistency)
|
353
|
+
raise Errors::EncodingError, %(Unknown consistency "#{consistency}") if index.nil? || CONSISTENCIES[index].nil?
|
354
|
+
append_short(index)
|
355
|
+
end
|
356
|
+
|
357
|
+
def append_string_map(map)
|
358
|
+
append_short(map.size)
|
359
|
+
map.each do |key, value|
|
360
|
+
append_string(key)
|
361
|
+
append_string(value)
|
362
|
+
end
|
363
|
+
self
|
364
|
+
end
|
365
|
+
|
366
|
+
def append_bytes_map(map)
|
367
|
+
append_short(map.size)
|
368
|
+
map.each do |key, value|
|
369
|
+
append_string(key)
|
370
|
+
append_bytes(value)
|
371
|
+
end
|
372
|
+
self
|
373
|
+
end
|
374
|
+
|
375
|
+
def append_long(n)
|
376
|
+
top = n >> 32
|
377
|
+
bottom = n & 0xffffffff
|
378
|
+
append_int(top)
|
379
|
+
append_int(bottom)
|
380
|
+
end
|
381
|
+
|
382
|
+
def append_varint(n)
|
383
|
+
num = n
|
384
|
+
bytes = []
|
385
|
+
loop do
|
386
|
+
bytes << (num & 0xff)
|
387
|
+
num >>= 8
|
388
|
+
break if (num == 0 || num == -1) && (bytes.last[7] == num[7])
|
389
|
+
end
|
390
|
+
append(bytes.reverse.pack(Formats::BYTES_FORMAT))
|
391
|
+
end
|
392
|
+
|
393
|
+
def append_decimal(n)
|
394
|
+
str = n.to_s(FLOAT_STRING_FORMAT)
|
395
|
+
size = str.index(DECIMAL_POINT)
|
396
|
+
number_string = str.gsub(DECIMAL_POINT, NO_CHAR)
|
397
|
+
|
398
|
+
num = number_string.to_i
|
399
|
+
raw = self.class.new.append_varint(num)
|
400
|
+
append_int(number_string.length - size)
|
401
|
+
append(raw)
|
402
|
+
end
|
403
|
+
|
404
|
+
def append_double(n)
|
405
|
+
append([n].pack(Formats::DOUBLE_FORMAT))
|
406
|
+
end
|
407
|
+
|
408
|
+
def append_float(n)
|
409
|
+
append([n].pack(Formats::FLOAT_FORMAT))
|
410
|
+
end
|
411
|
+
|
412
|
+
def eql?(other)
|
413
|
+
other.eql?(to_str)
|
414
|
+
end
|
415
|
+
alias == eql?
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
@@ -0,0 +1,448 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
#--
|
4
|
+
# Copyright DataStax, Inc.
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#++
|
18
|
+
|
19
|
+
module Cassandra
|
20
|
+
module Protocol
|
21
|
+
# This class wraps a single connection and translates between request/
|
22
|
+
# response frames and raw bytes.
|
23
|
+
#
|
24
|
+
# You send requests with #send_request, and receive responses through the
|
25
|
+
# returned future.
|
26
|
+
#
|
27
|
+
# Instances of this class are thread safe.
|
28
|
+
#
|
29
|
+
# @example Sending an OPTIONS request
|
30
|
+
# future = protocol_handler.send_request(Cassandra::Protocol::OptionsRequest.new)
|
31
|
+
# response = future.get
|
32
|
+
# puts "These options are supported: #{response.options}"
|
33
|
+
class CqlProtocolHandler
|
34
|
+
# @return [String] the current keyspace for the underlying connection
|
35
|
+
attr_reader :keyspace
|
36
|
+
|
37
|
+
# @return [Exception] outstanding error, from a failed connection.
|
38
|
+
attr_reader :error
|
39
|
+
|
40
|
+
# @return [Integer] the version of the protocol to use in communicating with C*.
|
41
|
+
attr_reader :protocol_version
|
42
|
+
|
43
|
+
def initialize(connection,
|
44
|
+
scheduler,
|
45
|
+
protocol_version,
|
46
|
+
compressor = nil,
|
47
|
+
heartbeat_interval = 30,
|
48
|
+
idle_timeout = 60,
|
49
|
+
requests_per_connection = 128,
|
50
|
+
custom_type_handlers = {})
|
51
|
+
@protocol_version = protocol_version
|
52
|
+
@connection = connection
|
53
|
+
@scheduler = scheduler
|
54
|
+
@compressor = compressor
|
55
|
+
@connection.on_data(&method(:receive_data))
|
56
|
+
@connection.on_closed(&method(:socket_closed))
|
57
|
+
|
58
|
+
@streams = Array.new(requests_per_connection) {|i| i}
|
59
|
+
|
60
|
+
@promises = {}
|
61
|
+
|
62
|
+
if protocol_version > 3
|
63
|
+
@frame_encoder = V4::Encoder.new(@compressor, protocol_version)
|
64
|
+
@frame_decoder = V4::Decoder.new(self, @compressor, custom_type_handlers)
|
65
|
+
elsif protocol_version > 2
|
66
|
+
@frame_encoder = V3::Encoder.new(@compressor, protocol_version)
|
67
|
+
@frame_decoder = V3::Decoder.new(self, @compressor)
|
68
|
+
else
|
69
|
+
@frame_encoder = V1::Encoder.new(@compressor, protocol_version)
|
70
|
+
@frame_decoder = V1::Decoder.new(self, @compressor)
|
71
|
+
end
|
72
|
+
|
73
|
+
@request_queue_in = []
|
74
|
+
@request_queue_out = []
|
75
|
+
@event_listeners = []
|
76
|
+
@data = {}
|
77
|
+
@lock = Mutex.new
|
78
|
+
@closed_promise = Ione::Promise.new
|
79
|
+
@keyspace = nil
|
80
|
+
@heartbeat = nil
|
81
|
+
@terminate = nil
|
82
|
+
@heartbeat_interval = heartbeat_interval
|
83
|
+
@idle_timeout = idle_timeout
|
84
|
+
@error = nil
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns the hostname of the underlying connection
|
88
|
+
#
|
89
|
+
# @return [String] the hostname
|
90
|
+
def host
|
91
|
+
@connection.host
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the port of the underlying connection
|
95
|
+
#
|
96
|
+
# @return [Integer] the port
|
97
|
+
def port
|
98
|
+
@connection.port
|
99
|
+
end
|
100
|
+
|
101
|
+
# Associate arbitrary data with this protocol handler object. This is
|
102
|
+
# useful in situations where additional metadata can be loaded after the
|
103
|
+
# connection has been set up, or to keep statistics specific to the
|
104
|
+
# connection this protocol handler wraps.
|
105
|
+
def []=(key, value)
|
106
|
+
@lock.lock
|
107
|
+
@data[key] = value
|
108
|
+
ensure
|
109
|
+
@lock.unlock
|
110
|
+
end
|
111
|
+
|
112
|
+
# @see {#[]=}
|
113
|
+
# @return the value associated with the key
|
114
|
+
def [](key)
|
115
|
+
@lock.lock
|
116
|
+
@data[key]
|
117
|
+
ensure
|
118
|
+
@lock.unlock
|
119
|
+
end
|
120
|
+
|
121
|
+
# @return [true, false] true if the underlying connection is connected
|
122
|
+
def connected?
|
123
|
+
@connection.connected?
|
124
|
+
end
|
125
|
+
|
126
|
+
# @return [true, false] true if the underlying connection is closed
|
127
|
+
def closed?
|
128
|
+
@connection.closed?
|
129
|
+
end
|
130
|
+
|
131
|
+
# Register to receive notification when the underlying connection has
|
132
|
+
# closed. If the connection closed abruptly the error will be passed
|
133
|
+
# to the listener, otherwise it will not receive any parameters.
|
134
|
+
#
|
135
|
+
# @yieldparam error [nil, Error] the error that caused the connection to
|
136
|
+
# close, if any
|
137
|
+
def on_closed(&listener)
|
138
|
+
@closed_promise.future.on_value(&listener)
|
139
|
+
@closed_promise.future.on_failure(&listener)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Register to receive server sent events, like schema changes, nodes going
|
143
|
+
# up or down, etc. To actually receive events you also need to send a
|
144
|
+
# REGISTER request for the events you wish to receive.
|
145
|
+
#
|
146
|
+
# @yieldparam event [Cassandra::Protocol::EventResponse] an event sent by the server
|
147
|
+
def on_event(&listener)
|
148
|
+
@lock.lock
|
149
|
+
@event_listeners += [listener]
|
150
|
+
ensure
|
151
|
+
@lock.unlock
|
152
|
+
end
|
153
|
+
|
154
|
+
# Serializes and send a request over the underlying connection.
|
155
|
+
#
|
156
|
+
# Returns a future that will resolve to the response. When the connection
|
157
|
+
# closes the futures of all active requests will be failed with the error
|
158
|
+
# that caused the connection to close, or nil.
|
159
|
+
#
|
160
|
+
# When `timeout` is specified the future will fail with
|
161
|
+
# {Cassandra::Errors::TimeoutError} after that many seconds have passed. If a
|
162
|
+
# response arrives after that time it will be lost. If a response never arrives
|
163
|
+
# for the request the channel occupied by the request will _not_ be reused.
|
164
|
+
#
|
165
|
+
# @param [Cassandra::Protocol::Request] request
|
166
|
+
# @param [Float] timeout an optional number of seconds to wait until
|
167
|
+
# failing the request
|
168
|
+
# @return [Ione::Future<Cassandra::Protocol::Response>] a future that resolves to
|
169
|
+
# the response
|
170
|
+
def send_request(request, timeout = nil, with_heartbeat = true)
|
171
|
+
return Ione::Future.failed(Errors::IOError.new('Connection closed')) if closed?
|
172
|
+
schedule_heartbeat if with_heartbeat
|
173
|
+
promise = RequestPromise.new(request, timeout, @scheduler)
|
174
|
+
id = nil
|
175
|
+
@lock.lock
|
176
|
+
begin
|
177
|
+
if (id = next_stream_id)
|
178
|
+
@promises[id] = promise
|
179
|
+
end
|
180
|
+
ensure
|
181
|
+
@lock.unlock
|
182
|
+
end
|
183
|
+
if id
|
184
|
+
write_request(id, promise)
|
185
|
+
else
|
186
|
+
@lock.lock
|
187
|
+
begin
|
188
|
+
@request_queue_in << promise
|
189
|
+
ensure
|
190
|
+
@lock.unlock
|
191
|
+
end
|
192
|
+
end
|
193
|
+
promise.future
|
194
|
+
end
|
195
|
+
|
196
|
+
# Closes the underlying connection.
|
197
|
+
#
|
198
|
+
# @return [Ione::Future] a future that completes when the connection has closed
|
199
|
+
def close(cause = nil)
|
200
|
+
if @heartbeat
|
201
|
+
@scheduler.cancel_timer(@heartbeat)
|
202
|
+
@heartbeat = nil
|
203
|
+
end
|
204
|
+
|
205
|
+
if @terminate
|
206
|
+
@scheduler.cancel_timer(@terminate)
|
207
|
+
@terminate = nil
|
208
|
+
end
|
209
|
+
|
210
|
+
@scheduler.schedule_timer(0).on_value do
|
211
|
+
@connection.close(cause)
|
212
|
+
end
|
213
|
+
|
214
|
+
@closed_promise.future
|
215
|
+
end
|
216
|
+
|
217
|
+
def notify_event_listeners(event_response)
|
218
|
+
event_listeners = nil
|
219
|
+
@lock.lock
|
220
|
+
begin
|
221
|
+
event_listeners = @event_listeners
|
222
|
+
return if event_listeners.empty?
|
223
|
+
ensure
|
224
|
+
@lock.unlock
|
225
|
+
end
|
226
|
+
event_listeners.each do |listener|
|
227
|
+
listener.call(event_response)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def complete_request(id, response)
|
232
|
+
promise = nil
|
233
|
+
@lock.lock
|
234
|
+
begin
|
235
|
+
promise = @promises.delete(id)
|
236
|
+
@streams.unshift(id)
|
237
|
+
ensure
|
238
|
+
@lock.unlock
|
239
|
+
end
|
240
|
+
@keyspace = response.keyspace if response.is_a?(Protocol::SetKeyspaceResultResponse)
|
241
|
+
if response.is_a?(Protocol::SchemaChangeResultResponse) &&
|
242
|
+
response.change == 'DROPPED' &&
|
243
|
+
response.keyspace == @keyspace &&
|
244
|
+
response.target == Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
|
245
|
+
@keyspace = nil
|
246
|
+
end
|
247
|
+
flush_request_queue
|
248
|
+
promise.fulfill(response) unless promise.timed_out?
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
# @private
|
254
|
+
class RequestPromise < Ione::Promise
|
255
|
+
extend AttrBoolean
|
256
|
+
|
257
|
+
attr_reader :request, :timeout
|
258
|
+
attr_boolean :timed_out
|
259
|
+
attr_accessor :timer
|
260
|
+
|
261
|
+
def initialize(request, timeout, scheduler)
|
262
|
+
@request = request
|
263
|
+
@timeout = timeout
|
264
|
+
@timed_out = false
|
265
|
+
@scheduler = scheduler
|
266
|
+
@lock = Mutex.new
|
267
|
+
@timer = nil
|
268
|
+
super()
|
269
|
+
end
|
270
|
+
|
271
|
+
def time_out!
|
272
|
+
unless future.completed?
|
273
|
+
@timed_out = true
|
274
|
+
fail(Errors::TimeoutError.new('Timed out'))
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
def maybe_start_timer
|
279
|
+
# This is more complicated than one would expect. First, we want to start a timer
|
280
|
+
# if a timeout is set. But there is a race condition where send_request creates
|
281
|
+
# a fresh promise and adds it to @promises, but another thread handles a socket
|
282
|
+
# closure event and fails all known promises. When a promise fails, we want to cancel
|
283
|
+
# the timer, if set. So, we synchronize access to @timer to be sure we don't set up
|
284
|
+
# and cancel the timer at the same time. However, if promise.fail runs first, there
|
285
|
+
# will be no timer to cancel, and then when maybe_start_timer gets called in the other
|
286
|
+
# thread, it'll create a timer on a promise that no one is going to action on going forward.
|
287
|
+
# So, that leads to leaking the timer until it times out. To avoid this, we want to
|
288
|
+
# check that the future of the promise isn't completed before starting the timer.
|
289
|
+
|
290
|
+
return if @timeout.nil?
|
291
|
+
return if @future.completed?
|
292
|
+
|
293
|
+
if @timer.nil?
|
294
|
+
@lock.synchronize do
|
295
|
+
if @timer.nil?
|
296
|
+
return if @future.completed?
|
297
|
+
@timer = @scheduler.schedule_timer(@timeout)
|
298
|
+
@timer.on_value { time_out! }
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def fulfill(response)
|
305
|
+
super
|
306
|
+
maybe_cancel_timer
|
307
|
+
end
|
308
|
+
|
309
|
+
def fail(cause)
|
310
|
+
super
|
311
|
+
maybe_cancel_timer
|
312
|
+
end
|
313
|
+
|
314
|
+
def maybe_cancel_timer
|
315
|
+
return if @timeout.nil?
|
316
|
+
timer = nil
|
317
|
+
if @timer
|
318
|
+
@lock.synchronize do
|
319
|
+
if @timer
|
320
|
+
timer = @timer
|
321
|
+
@timer = nil
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
@scheduler.cancel_timer(timer) if timer
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def receive_data(data)
|
330
|
+
reschedule_termination
|
331
|
+
@frame_decoder << data
|
332
|
+
end
|
333
|
+
|
334
|
+
def flush_request_queue
|
335
|
+
@lock.lock
|
336
|
+
begin
|
337
|
+
if @request_queue_out.empty? && !@request_queue_in.empty?
|
338
|
+
@request_queue_out = @request_queue_in
|
339
|
+
@request_queue_in = []
|
340
|
+
end
|
341
|
+
ensure
|
342
|
+
@lock.unlock
|
343
|
+
end
|
344
|
+
loop do
|
345
|
+
id = nil
|
346
|
+
promise = nil
|
347
|
+
@lock.lock
|
348
|
+
begin
|
349
|
+
if @request_queue_out.any? && (id = next_stream_id)
|
350
|
+
promise = @request_queue_out.shift
|
351
|
+
next if promise.timed_out?
|
352
|
+
@promises[id] = promise
|
353
|
+
end
|
354
|
+
ensure
|
355
|
+
@lock.unlock
|
356
|
+
end
|
357
|
+
|
358
|
+
break unless id
|
359
|
+
write_request(id, promise)
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def write_request(id, request_promise)
|
364
|
+
@connection.write do |buffer|
|
365
|
+
@frame_encoder.encode(buffer, request_promise.request, id)
|
366
|
+
end
|
367
|
+
request_promise.maybe_start_timer
|
368
|
+
end
|
369
|
+
|
370
|
+
def socket_closed(cause)
|
371
|
+
if cause
|
372
|
+
e = Errors::IOError.new(cause.message)
|
373
|
+
e.set_backtrace(cause.backtrace)
|
374
|
+
|
375
|
+
cause = e
|
376
|
+
end
|
377
|
+
@error = cause
|
378
|
+
|
379
|
+
request_failure_cause = cause || Errors::IOError.new('Connection closed')
|
380
|
+
promises_to_fail = nil
|
381
|
+
@lock.synchronize do
|
382
|
+
@scheduler.cancel_timer(@heartbeat) if @heartbeat
|
383
|
+
@scheduler.cancel_timer(@terminate) if @terminate
|
384
|
+
|
385
|
+
@heartbeat = nil
|
386
|
+
@terminate = nil
|
387
|
+
|
388
|
+
promises_to_fail = @promises.values
|
389
|
+
promises_to_fail.concat(@request_queue_in)
|
390
|
+
promises_to_fail.concat(@request_queue_out)
|
391
|
+
@promises.clear
|
392
|
+
@request_queue_in.clear
|
393
|
+
@request_queue_out.clear
|
394
|
+
end
|
395
|
+
promises_to_fail.each do |promise|
|
396
|
+
promise.fail(request_failure_cause) unless promise.timed_out?
|
397
|
+
end
|
398
|
+
if cause
|
399
|
+
@closed_promise.fail(cause)
|
400
|
+
else
|
401
|
+
@closed_promise.fulfill
|
402
|
+
end
|
403
|
+
end
|
404
|
+
|
405
|
+
def schedule_heartbeat
|
406
|
+
return unless @heartbeat_interval
|
407
|
+
|
408
|
+
timer = nil
|
409
|
+
|
410
|
+
@lock.synchronize do
|
411
|
+
@scheduler.cancel_timer(@heartbeat) if @heartbeat && !@heartbeat.resolved?
|
412
|
+
|
413
|
+
@heartbeat = timer = @scheduler.schedule_timer(@heartbeat_interval)
|
414
|
+
end
|
415
|
+
|
416
|
+
timer.on_value do
|
417
|
+
send_request(HEARTBEAT, nil, false).on_value do
|
418
|
+
schedule_heartbeat
|
419
|
+
end
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def reschedule_termination
|
424
|
+
return unless @idle_timeout
|
425
|
+
|
426
|
+
timer = nil
|
427
|
+
|
428
|
+
@lock.synchronize do
|
429
|
+
@scheduler.cancel_timer(@terminate) if @terminate
|
430
|
+
|
431
|
+
@terminate = timer = @scheduler.schedule_timer(@idle_timeout)
|
432
|
+
end
|
433
|
+
|
434
|
+
timer.on_value do
|
435
|
+
@terminate = nil
|
436
|
+
@connection.close(TERMINATED)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def next_stream_id
|
441
|
+
@streams.shift
|
442
|
+
end
|
443
|
+
|
444
|
+
HEARTBEAT = OptionsRequest.new
|
445
|
+
TERMINATED = Errors::TimeoutError.new('Terminated due to inactivity')
|
446
|
+
end
|
447
|
+
end
|
448
|
+
end
|