yugabyte-ycql-driver 3.2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +13 -0
  3. data/README.md +242 -0
  4. data/ext/cassandra_murmur3/cassandra_murmur3.c +178 -0
  5. data/ext/cassandra_murmur3/extconf.rb +2 -0
  6. data/lib/cassandra/address_resolution.rb +36 -0
  7. data/lib/cassandra/address_resolution/policies.rb +2 -0
  8. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
  9. data/lib/cassandra/address_resolution/policies/none.rb +35 -0
  10. data/lib/cassandra/aggregate.rb +123 -0
  11. data/lib/cassandra/argument.rb +51 -0
  12. data/lib/cassandra/attr_boolean.rb +33 -0
  13. data/lib/cassandra/auth.rb +100 -0
  14. data/lib/cassandra/auth/providers.rb +17 -0
  15. data/lib/cassandra/auth/providers/password.rb +65 -0
  16. data/lib/cassandra/cassandra_logger.rb +80 -0
  17. data/lib/cassandra/cluster.rb +331 -0
  18. data/lib/cassandra/cluster/client.rb +1612 -0
  19. data/lib/cassandra/cluster/connection_pool.rb +78 -0
  20. data/lib/cassandra/cluster/connector.rb +372 -0
  21. data/lib/cassandra/cluster/control_connection.rb +962 -0
  22. data/lib/cassandra/cluster/failed_connection.rb +35 -0
  23. data/lib/cassandra/cluster/metadata.rb +142 -0
  24. data/lib/cassandra/cluster/options.rb +145 -0
  25. data/lib/cassandra/cluster/registry.rb +284 -0
  26. data/lib/cassandra/cluster/schema.rb +405 -0
  27. data/lib/cassandra/cluster/schema/cql_type_parser.rb +112 -0
  28. data/lib/cassandra/cluster/schema/fetchers.rb +1627 -0
  29. data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +175 -0
  30. data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
  31. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +45 -0
  32. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
  33. data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
  34. data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
  35. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +102 -0
  36. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
  37. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
  38. data/lib/cassandra/column.rb +66 -0
  39. data/lib/cassandra/column_container.rb +326 -0
  40. data/lib/cassandra/compression.rb +69 -0
  41. data/lib/cassandra/compression/compressors/lz4.rb +73 -0
  42. data/lib/cassandra/compression/compressors/snappy.rb +69 -0
  43. data/lib/cassandra/custom_data.rb +53 -0
  44. data/lib/cassandra/driver.rb +260 -0
  45. data/lib/cassandra/errors.rb +784 -0
  46. data/lib/cassandra/execution/info.rb +69 -0
  47. data/lib/cassandra/execution/options.rb +267 -0
  48. data/lib/cassandra/execution/profile.rb +153 -0
  49. data/lib/cassandra/execution/profile_manager.rb +71 -0
  50. data/lib/cassandra/execution/trace.rb +192 -0
  51. data/lib/cassandra/executors.rb +113 -0
  52. data/lib/cassandra/function.rb +156 -0
  53. data/lib/cassandra/function_collection.rb +85 -0
  54. data/lib/cassandra/future.rb +794 -0
  55. data/lib/cassandra/host.rb +102 -0
  56. data/lib/cassandra/index.rb +118 -0
  57. data/lib/cassandra/keyspace.rb +473 -0
  58. data/lib/cassandra/listener.rb +87 -0
  59. data/lib/cassandra/load_balancing.rb +121 -0
  60. data/lib/cassandra/load_balancing/policies.rb +20 -0
  61. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +172 -0
  62. data/lib/cassandra/load_balancing/policies/round_robin.rb +141 -0
  63. data/lib/cassandra/load_balancing/policies/token_aware.rb +149 -0
  64. data/lib/cassandra/load_balancing/policies/white_list.rb +100 -0
  65. data/lib/cassandra/materialized_view.rb +92 -0
  66. data/lib/cassandra/null_logger.rb +56 -0
  67. data/lib/cassandra/protocol.rb +102 -0
  68. data/lib/cassandra/protocol/coder.rb +1085 -0
  69. data/lib/cassandra/protocol/cql_byte_buffer.rb +418 -0
  70. data/lib/cassandra/protocol/cql_protocol_handler.rb +448 -0
  71. data/lib/cassandra/protocol/request.rb +41 -0
  72. data/lib/cassandra/protocol/requests/auth_response_request.rb +51 -0
  73. data/lib/cassandra/protocol/requests/batch_request.rb +117 -0
  74. data/lib/cassandra/protocol/requests/credentials_request.rb +51 -0
  75. data/lib/cassandra/protocol/requests/execute_request.rb +122 -0
  76. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  77. data/lib/cassandra/protocol/requests/prepare_request.rb +59 -0
  78. data/lib/cassandra/protocol/requests/query_request.rb +112 -0
  79. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  80. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  81. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  82. data/lib/cassandra/protocol/response.rb +28 -0
  83. data/lib/cassandra/protocol/responses/already_exists_error_response.rb +50 -0
  84. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +36 -0
  85. data/lib/cassandra/protocol/responses/auth_success_response.rb +36 -0
  86. data/lib/cassandra/protocol/responses/authenticate_response.rb +36 -0
  87. data/lib/cassandra/protocol/responses/error_response.rb +142 -0
  88. data/lib/cassandra/protocol/responses/event_response.rb +30 -0
  89. data/lib/cassandra/protocol/responses/function_failure_error_response.rb +52 -0
  90. data/lib/cassandra/protocol/responses/prepared_result_response.rb +62 -0
  91. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +59 -0
  92. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +71 -0
  93. data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +61 -0
  94. data/lib/cassandra/protocol/responses/ready_response.rb +43 -0
  95. data/lib/cassandra/protocol/responses/result_response.rb +42 -0
  96. data/lib/cassandra/protocol/responses/rows_result_response.rb +39 -0
  97. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +73 -0
  98. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +70 -0
  99. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +37 -0
  100. data/lib/cassandra/protocol/responses/status_change_event_response.rb +39 -0
  101. data/lib/cassandra/protocol/responses/supported_response.rb +36 -0
  102. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +33 -0
  103. data/lib/cassandra/protocol/responses/unavailable_error_response.rb +58 -0
  104. data/lib/cassandra/protocol/responses/unprepared_error_response.rb +48 -0
  105. data/lib/cassandra/protocol/responses/void_result_response.rb +34 -0
  106. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +73 -0
  107. data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +63 -0
  108. data/lib/cassandra/protocol/v1.rb +326 -0
  109. data/lib/cassandra/protocol/v3.rb +358 -0
  110. data/lib/cassandra/protocol/v4.rb +478 -0
  111. data/lib/cassandra/reconnection.rb +49 -0
  112. data/lib/cassandra/reconnection/policies.rb +20 -0
  113. data/lib/cassandra/reconnection/policies/constant.rb +46 -0
  114. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  115. data/lib/cassandra/result.rb +276 -0
  116. data/lib/cassandra/retry.rb +154 -0
  117. data/lib/cassandra/retry/policies.rb +21 -0
  118. data/lib/cassandra/retry/policies/default.rb +53 -0
  119. data/lib/cassandra/retry/policies/downgrading_consistency.rb +73 -0
  120. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  121. data/lib/cassandra/session.rb +270 -0
  122. data/lib/cassandra/statement.rb +32 -0
  123. data/lib/cassandra/statements.rb +23 -0
  124. data/lib/cassandra/statements/batch.rb +146 -0
  125. data/lib/cassandra/statements/bound.rb +65 -0
  126. data/lib/cassandra/statements/prepared.rb +235 -0
  127. data/lib/cassandra/statements/simple.rb +118 -0
  128. data/lib/cassandra/statements/void.rb +38 -0
  129. data/lib/cassandra/table.rb +240 -0
  130. data/lib/cassandra/time.rb +103 -0
  131. data/lib/cassandra/time_uuid.rb +78 -0
  132. data/lib/cassandra/timestamp_generator.rb +37 -0
  133. data/lib/cassandra/timestamp_generator/simple.rb +38 -0
  134. data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
  135. data/lib/cassandra/trigger.rb +67 -0
  136. data/lib/cassandra/tuple.rb +131 -0
  137. data/lib/cassandra/types.rb +1704 -0
  138. data/lib/cassandra/udt.rb +443 -0
  139. data/lib/cassandra/util.rb +464 -0
  140. data/lib/cassandra/uuid.rb +110 -0
  141. data/lib/cassandra/uuid/generator.rb +212 -0
  142. data/lib/cassandra/version.rb +21 -0
  143. data/lib/datastax/cassandra.rb +47 -0
  144. data/lib/ycql.rb +842 -0
  145. 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