yugabyte-ycql-driver 3.2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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