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,80 @@
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
+ require 'logger'
20
+ module Cassandra
21
+ # This class is a logger that may be used by the client to log the driver's actions.
22
+ # It is a subclass of the standard Ruby Logger class, so it is instantiated the
23
+ # same way.
24
+ #
25
+ # The format of log output is set to include the timestamp, thread-id, log severity,
26
+ # and message. <b>This format may change in newer versions of the driver to account
27
+ # for new/deprecated metadata.</b>
28
+ #
29
+ # @example Configuring {Cassandra::Cluster} to use a logger.
30
+ # cluster = Cassandra.cluster(logger: Cassandra::Logger.new($stderr))
31
+ #
32
+ # @example The log format may be changed the same way as in the standard Ruby Logger class
33
+ # logger = Cassandra::Logger.new($stderr)
34
+ # logger.formatter = proc { |severity, time, program_name, message|
35
+ # "[%s]: %s\n" % [severity, message]
36
+ # }
37
+ #
38
+ # @example Create a logger and use it in your own business logic
39
+ # logger = Cassandra::Logger.new($stderr)
40
+ # cluster = Cassandra.cluster(logger: logger)
41
+ # <various logic>
42
+ # logger.debug("something interesting happened.")
43
+
44
+ class Logger < ::Logger
45
+ # @private
46
+ # This class is mostly copied from the Ruby Logger::Format class.
47
+ class Formatter
48
+ Format = "[%s#%d] %5s: %s\n".freeze
49
+
50
+ def call(severity, time, _, msg)
51
+ format(Format,
52
+ format_datetime(time),
53
+ Thread.current.object_id,
54
+ severity,
55
+ msg2str(msg))
56
+ end
57
+
58
+ def format_datetime(time)
59
+ time.strftime('%H:%M:%S.') << format('%06d ', time.usec)
60
+ end
61
+
62
+ def msg2str(msg)
63
+ case msg
64
+ when ::String
65
+ msg
66
+ when ::Exception
67
+ "#{msg.message} (#{msg.class})\n" <<
68
+ (msg.backtrace || []).join("\n")
69
+ else
70
+ msg.inspect
71
+ end
72
+ end
73
+ end
74
+
75
+ def initialize(*args)
76
+ super(*args)
77
+ self.formatter = Formatter.new
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,331 @@
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
+ # Cluster represents a cassandra cluster. It serves as a
21
+ # {Cassandra::Session session factory} factory and a collection of metadata.
22
+ #
23
+ # @see Cassandra::Cluster#connect Creating a new session
24
+ # @see Cassandra::Cluster#each_host Getting all peers in the cluster
25
+ # @see Cassandra::Cluster#each_keyspace Getting all existing keyspaces
26
+ class Cluster
27
+ extend Forwardable
28
+
29
+ # @private
30
+ def initialize(logger,
31
+ io_reactor,
32
+ executor,
33
+ control_connection,
34
+ cluster_registry,
35
+ cluster_schema,
36
+ cluster_metadata,
37
+ execution_options,
38
+ connection_options,
39
+ profile_manager,
40
+ reconnection_policy,
41
+ address_resolution_policy,
42
+ connector,
43
+ futures_factory,
44
+ timestamp_generator)
45
+ @logger = logger
46
+ @io_reactor = io_reactor
47
+ @executor = executor
48
+ @control_connection = control_connection
49
+ @registry = cluster_registry
50
+ @schema = cluster_schema
51
+ @metadata = cluster_metadata
52
+ @execution_options = execution_options
53
+ @connection_options = connection_options
54
+ @profile_manager = profile_manager
55
+ @reconnection_policy = reconnection_policy
56
+ @address_resolver = address_resolution_policy
57
+ @connector = connector
58
+ @futures = futures_factory
59
+ @timestamp_generator = timestamp_generator
60
+
61
+ @control_connection.on_close do |_cause|
62
+ begin
63
+ @profile_manager.teardown(self)
64
+ rescue
65
+ nil
66
+ end
67
+ end
68
+ end
69
+
70
+ # @!method name
71
+ # Return cluster's name
72
+ # @return [String] cluster's name
73
+ #
74
+ # @!method find_replicas(keyspace, statement)
75
+ # Return replicas for a given statement and keyspace
76
+ # @note an empty list is returned when statement/keyspace information is
77
+ # not enough to determine replica list.
78
+ # @param keyspace [String] keyspace name
79
+ # @param statement [Cassandra::Statement] statement for which to find
80
+ # replicas
81
+ # @return [Array<Cassandra::Host>] a list of replicas
82
+ def_delegators :@metadata, :name, :find_replicas
83
+
84
+ # Register a cluster state listener. State listener will start receiving
85
+ # notifications about topology and schema changes
86
+ #
87
+ # @param listener [Cassandra::Listener] cluster state listener
88
+ # @return [self]
89
+ def register(listener)
90
+ @registry.add_listener(listener)
91
+ @schema.add_listener(listener)
92
+ self
93
+ end
94
+
95
+ # Unregister a cluster state listener. State listener will stop receiving
96
+ # notifications about topology and schema changes
97
+ #
98
+ # @param listener [Cassandra::Listener] cluster state listener
99
+ # @return [self]
100
+ def unregister(listener)
101
+ @registry.remove_listener(listener)
102
+ @schema.remove_listener(listener)
103
+ self
104
+ end
105
+
106
+ # Yield or enumerate each member of this cluster
107
+ # @overload each_host
108
+ # @yieldparam host [Cassandra::Host] current host
109
+ # @return [Cassandra::Cluster] self
110
+ # @overload each_host
111
+ # @return [Array<Cassandra::Host>] a list of hosts
112
+ def each_host(&block)
113
+ r = @registry.each_host(&block)
114
+ return self if r == @registry
115
+ r
116
+ end
117
+ alias hosts each_host
118
+
119
+ # @!method host(address)
120
+ # Find a host by its address
121
+ # @param address [IPAddr, String] ip address
122
+ # @return [Cassandra::Host, nil] host or nil
123
+ #
124
+ # @!method has_host?(address)
125
+ # Determine if a host by a given address exists
126
+ # @param address [IPAddr, String] ip address
127
+ # @return [Boolean] true or false
128
+ def_delegators :@registry, :host, :has_host?
129
+
130
+ # Yield or enumerate each keyspace defined in this cluster
131
+ # @overload each_keyspace
132
+ # @yieldparam keyspace [Cassandra::Keyspace] current keyspace
133
+ # @return [Cassandra::Cluster] self
134
+ # @overload each_keyspace
135
+ # @return [Array<Cassandra::Keyspace>] a list of keyspaces
136
+ def each_keyspace(&block)
137
+ r = @schema.each_keyspace(&block)
138
+ return self if r == @schema
139
+ r
140
+ end
141
+ alias keyspaces each_keyspace
142
+
143
+ # @!method keyspace(name)
144
+ # Find a keyspace by name
145
+ # @param name [String] keyspace name
146
+ # @return [Cassandra::Keyspace, nil] keyspace or nil
147
+ #
148
+ # @!method has_keyspace?(name)
149
+ # Determine if a keyspace by a given name exists
150
+ # @param name [String] keyspace name
151
+ # @return [Boolean] true or false
152
+ def_delegators :@schema, :keyspace, :has_keyspace?
153
+
154
+ # @return [Integer] Cassandra native protocol port
155
+ def port
156
+ @connection_options.port
157
+ end
158
+
159
+ # @return [Integer] the version of the native protocol used in communication with nodes
160
+ def protocol_version
161
+ @connection_options.protocol_version
162
+ end
163
+
164
+ # @param name [String] Name of profile to retrieve
165
+ # @return [Cassandra::Execution::Profile] execution profile of the given name
166
+ def execution_profile(name)
167
+ @profile_manager.profiles[name]
168
+ end
169
+
170
+ # Yield or enumerate each execution profile defined in this cluster
171
+ # @overload each_execution_profile
172
+ # @yieldparam name [String, Symbol] name of current profile
173
+ # @yieldparam profile [Cassandra::Execution::Profile] current profile
174
+ # @return [Cassandra::Cluster] self
175
+ # @overload each_execution_profile
176
+ # @return [Hash<String, Cassandra::Execution::Profile>] a hash of profiles keyed on name
177
+ def each_execution_profile(&block)
178
+ if block_given?
179
+ @profile_manager.profiles.each_pair(&block)
180
+ self
181
+ else
182
+ # Return a dup of the hash to prevent the user from adding/removing profiles from the profile-manager.
183
+ @profile_manager.profiles.dup
184
+ end
185
+ end
186
+ alias execution_profiles each_execution_profile
187
+
188
+ # @!method refresh_schema_async
189
+ # Trigger an asynchronous schema metadata refresh
190
+ # @return [Cassandra::Future<nil>] a future that will be fulfilled when
191
+ # schema metadata has been refreshed
192
+ def refresh_schema_async
193
+ promise = @futures.promise
194
+ @control_connection.send(:refresh_maybe_retry, :schema).on_complete do |f|
195
+ if f.resolved?
196
+ promise.fulfill(nil)
197
+ else
198
+ f.on_failure do |e|
199
+ promise.break(e)
200
+ end
201
+ end
202
+ end
203
+ promise.future
204
+ end
205
+
206
+ # Synchronously refresh schema metadata
207
+ #
208
+ # @return [nil] nothing
209
+ # @raise [Cassandra::Errors::ClientError] when cluster is disconnected
210
+ # @raise [Cassandra::Error] other unexpected errors
211
+ #
212
+ # @see Cassandra::Cluster#refresh_schema_async
213
+ def refresh_schema
214
+ refresh_schema_async.get
215
+ end
216
+
217
+ # Asynchronously create a new session, optionally scoped to a keyspace
218
+ #
219
+ # @param keyspace [String] optional keyspace to scope session to
220
+ #
221
+ # @return [Cassandra::Future<Cassandra::Session>] a future new session that
222
+ # can prepare and execute statements
223
+ #
224
+ # @see Cassandra::Cluster#connect A list of possible exceptions that this
225
+ # future can be resolved with
226
+ def connect_async(keyspace = nil)
227
+ if !keyspace.nil? && !keyspace.is_a?(::String)
228
+ return @futures.error(::ArgumentError.new("keyspace must be a string, #{keyspace.inspect} given"))
229
+ end
230
+
231
+ client = Client.new(@logger,
232
+ @registry,
233
+ @schema,
234
+ @io_reactor,
235
+ @connector,
236
+ @profile_manager,
237
+ @reconnection_policy,
238
+ @address_resolver,
239
+ @connection_options,
240
+ @futures,
241
+ @timestamp_generator)
242
+ session = Session.new(client, @execution_options, @futures, @profile_manager)
243
+ promise = @futures.promise
244
+
245
+ client.connect.on_complete do |f|
246
+ if f.resolved?
247
+ if keyspace
248
+ f = session.execute_async("USE #{Util.escape_name(keyspace)}")
249
+
250
+ f.on_success {promise.fulfill(session)}
251
+ f.on_failure {|e| promise.break(e)}
252
+ else
253
+ promise.fulfill(session)
254
+ end
255
+ else
256
+ f.on_failure {|e| promise.break(e)}
257
+ end
258
+ end
259
+
260
+ promise.future
261
+ end
262
+
263
+ # Synchronously create a new session, optionally scoped to a keyspace
264
+ #
265
+ # @param keyspace [String] optional keyspace to scope the session to
266
+ #
267
+ # @raise [ArgumentError] if keyspace is not a String
268
+ # @raise [Cassandra::Errors::NoHostsAvailable] when all hosts failed
269
+ # @raise [Cassandra::Errors::AuthenticationError] when authentication fails
270
+ # @raise [Cassandra::Errors::ProtocolError] when protocol negotiation fails
271
+ # @raise [Cassandra::Error] other unexpected errors
272
+ #
273
+ # @return [Cassandra::Session] a new session that can prepare and execute
274
+ # statements
275
+ #
276
+ # @see Cassandra::Cluster#connect_async
277
+ def connect(keyspace = nil)
278
+ connect_async(keyspace).get
279
+ end
280
+
281
+ # Asynchronously closes all sessions managed by this cluster
282
+ #
283
+ # @return [Cassandra::Future<Cassandra::Cluster>] a future that resolves to
284
+ # self once closed
285
+ def close_async
286
+ promise = @futures.promise
287
+
288
+ @control_connection.close_async.on_complete do |f|
289
+ if f.resolved?
290
+ promise.fulfill(self)
291
+ else
292
+ f.on_failure {|e| promise.break(e)}
293
+ end
294
+
295
+ @executor.shutdown
296
+ end
297
+
298
+ promise.future
299
+ end
300
+
301
+ # Synchronously closes all sessions managed by this cluster
302
+ #
303
+ # @return [self] this cluster
304
+ #
305
+ # @see Cassandra::Cluster#close_async
306
+ def close
307
+ close_async.get
308
+ end
309
+
310
+ # @private
311
+ def inspect
312
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} " \
313
+ "name=#{name.inspect}, " \
314
+ "port=#{@connection_options.port}, " \
315
+ "protocol_version=#{@connection_options.protocol_version}, " \
316
+ "execution_profiles=#{@profile_manager.profiles.inspect}, " \
317
+ "hosts=#{hosts.inspect}, " \
318
+ "keyspaces=#{keyspaces.inspect}>"
319
+ end
320
+ end
321
+ end
322
+
323
+ require 'cassandra/cluster/client'
324
+ require 'cassandra/cluster/connection_pool'
325
+ require 'cassandra/cluster/connector'
326
+ require 'cassandra/cluster/control_connection'
327
+ require 'cassandra/cluster/failed_connection'
328
+ require 'cassandra/cluster/metadata'
329
+ require 'cassandra/cluster/options'
330
+ require 'cassandra/cluster/registry'
331
+ require 'cassandra/cluster/schema'
@@ -0,0 +1,1612 @@
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
+ class Cluster
21
+ # @private
22
+ class Client
23
+ include MonitorMixin
24
+
25
+ attr_reader :keyspace
26
+
27
+ def initialize(logger,
28
+ cluster_registry,
29
+ cluster_schema,
30
+ io_reactor,
31
+ connector,
32
+ profile_manager,
33
+ reconnection_policy,
34
+ address_resolution_policy,
35
+ connection_options,
36
+ futures_factory,
37
+ timestamp_generator)
38
+ @logger = logger
39
+ @registry = cluster_registry
40
+ @schema = cluster_schema
41
+ @reactor = io_reactor
42
+ @connector = connector
43
+ @profile_manager = profile_manager
44
+ @reconnection_policy = reconnection_policy
45
+ @address_resolver = address_resolution_policy
46
+ @connection_options = connection_options
47
+ @futures = futures_factory
48
+ @connections = ::Hash.new
49
+ @prepared_statements = ::Hash.new
50
+ @preparing_statements = ::Hash.new {|hash, host| hash[host] = {}}
51
+ @pending_connections = ::Hash.new
52
+ @keyspace = nil
53
+ @state = :idle
54
+ @timestamp_generator = timestamp_generator
55
+
56
+ mon_initialize
57
+ end
58
+
59
+ def connect
60
+ connecting_hosts = ::Hash.new
61
+
62
+ synchronize do
63
+ return CLIENT_CLOSED if @state == :closed || @state == :closing
64
+ return @connected_future if @state == :connecting || @state == :connected
65
+
66
+ @state = :connecting
67
+ @registry.each_host do |host|
68
+ distance = @profile_manager.distance(host)
69
+
70
+ case distance
71
+ when :ignore
72
+ next
73
+ when :local
74
+ pool_size = @connection_options.connections_per_local_node
75
+ when :remote
76
+ pool_size = @connection_options.connections_per_remote_node
77
+ else
78
+ @logger.error("Not connecting to #{host.ip} - invalid load balancing " \
79
+ 'distance. Distance must be one of ' \
80
+ "#{LoadBalancing::DISTANCES.inspect}, #{distance.inspect} given")
81
+ next
82
+ end
83
+
84
+ connecting_hosts[host] = pool_size
85
+ @pending_connections[host] = 0
86
+ @preparing_statements[host] = {}
87
+ @connections[host] = ConnectionPool.new
88
+ end
89
+ end
90
+
91
+ @connected_future = begin
92
+ @logger.info('Creating session')
93
+ @registry.add_listener(self)
94
+ @schema.add_listener(self)
95
+
96
+ futures = connecting_hosts.map do |(host, pool_size)|
97
+ f = connect_to_host(host, pool_size)
98
+ f.recover do |error|
99
+ FailedConnection.new(error, host)
100
+ end
101
+ end
102
+
103
+ Ione::Future.all(*futures).map do |connections|
104
+ connections.flatten!
105
+ raise NO_HOSTS if connections.empty?
106
+
107
+ failed_connections = connections.reject(&:connected?)
108
+
109
+ # convert Cassandra::Protocol::CqlProtocolHandler to something with a real host
110
+ failed_connections.map! do |c|
111
+ if c.host.is_a?(String)
112
+ host = @registry.each_host.detect { |h| h.ip.to_s == c.host } || raise("Unable to find host #{c.host}")
113
+ FailedConnection.new(c.error, host)
114
+ else
115
+ c
116
+ end
117
+ end
118
+
119
+ if failed_connections.size == connections.size
120
+ errors = {}
121
+ connections.each {|c| errors[c.host] = c.error unless c.error.nil?}
122
+ raise Errors::NoHostsAvailable.new(errors)
123
+ else
124
+ failed_connections.each do |f|
125
+ connect_to_host_with_retry(f.host,
126
+ connecting_hosts[f.host],
127
+ @reconnection_policy.schedule)
128
+ end
129
+ end
130
+
131
+ self
132
+ end
133
+ end
134
+ @connected_future.on_complete(&method(:connected))
135
+ @connected_future
136
+ end
137
+
138
+ def close
139
+ state = nil
140
+
141
+ synchronize do
142
+ return CLIENT_NOT_CONNECTED if @state == :idle
143
+ return @closed_future if @state == :closed || @state == :closing
144
+
145
+ state = @state
146
+ @state = :closing
147
+ end
148
+
149
+ @closed_future = begin
150
+ @registry.remove_listener(self)
151
+ @schema.remove_listener(self)
152
+
153
+ f = if state == :connecting
154
+ @connected_future.recover.flat_map { close_connections }
155
+ else
156
+ close_connections
157
+ end
158
+
159
+ f.map(self)
160
+ end
161
+ @closed_future.on_complete(&method(:closed))
162
+ @closed_future
163
+ end
164
+
165
+ # These methods shall be called from inside reactor thread only
166
+ def host_found(host)
167
+ nil
168
+ end
169
+
170
+ def host_lost(host)
171
+ nil
172
+ end
173
+
174
+ def host_up(host)
175
+ pool_size = 0
176
+
177
+ synchronize do
178
+ distance = @profile_manager.distance(host)
179
+ case distance
180
+ when :ignore
181
+ return Ione::Future.resolved
182
+ when :local
183
+ pool_size = @connection_options.connections_per_local_node
184
+ when :remote
185
+ pool_size = @connection_options.connections_per_remote_node
186
+ else
187
+ @logger.error("Not connecting to #{host.ip} - " \
188
+ 'invalid load balancing distance. Distance must be one of ' \
189
+ "#{LoadBalancing::DISTANCES.inspect}, #{distance.inspect} given")
190
+ return Ione::Future.resolved
191
+ end
192
+
193
+ @pending_connections[host] ||= 0
194
+ @preparing_statements[host] = {}
195
+ @connections[host] = ConnectionPool.new
196
+ end
197
+
198
+ connect_to_host_maybe_retry(host, pool_size)
199
+ end
200
+
201
+ def host_down(host)
202
+ pool = nil
203
+
204
+ synchronize do
205
+ return Ione::Future.resolved unless @connections.key?(host)
206
+
207
+ @pending_connections.delete(host) unless @pending_connections[host] > 0
208
+ @preparing_statements.delete(host)
209
+ pool = @connections.delete(host)
210
+ end
211
+
212
+ if pool
213
+ Ione::Future.all(*pool.snapshot.map!(&:close)).map(nil)
214
+ else
215
+ Ione::Future.resolved
216
+ end
217
+ end
218
+
219
+ def keyspace_created(keyspace)
220
+ end
221
+
222
+ def keyspace_changed(keyspace)
223
+ end
224
+
225
+ def keyspace_dropped(keyspace)
226
+ @keyspace = nil if @keyspace == keyspace.name
227
+ nil
228
+ end
229
+
230
+ def query(statement, options)
231
+ if !statement.params.empty? && @connection_options.protocol_version == 1
232
+ return @futures.error(
233
+ Errors::ClientError.new(
234
+ 'Positional arguments are not supported by the current version of ' \
235
+ 'Apache Cassandra'
236
+ )
237
+ )
238
+ end
239
+
240
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
241
+ payload = nil
242
+ payload = options.payload if @connection_options.protocol_version >= 4
243
+ request = Protocol::QueryRequest.new(statement.cql,
244
+ statement.params,
245
+ statement.params_types,
246
+ options.consistency,
247
+ options.serial_consistency,
248
+ options.page_size,
249
+ options.paging_state,
250
+ options.trace?,
251
+ statement.params_names,
252
+ timestamp,
253
+ payload)
254
+ timeout = options.timeout
255
+ promise = @futures.promise
256
+
257
+ keyspace = @keyspace
258
+ plan = options.load_balancing_policy.plan(keyspace, statement, options)
259
+
260
+ send_request_by_plan(promise,
261
+ keyspace,
262
+ statement,
263
+ options,
264
+ request,
265
+ plan,
266
+ timeout)
267
+
268
+ promise.future
269
+ end
270
+
271
+ def prepare(cql, options)
272
+ payload = nil
273
+ payload = options.payload if @connection_options.protocol_version >= 4
274
+ request = Protocol::PrepareRequest.new(cql, options.trace?, payload)
275
+ timeout = options.timeout
276
+ promise = @futures.promise
277
+
278
+ keyspace = @keyspace
279
+ statement = VOID_STATEMENT
280
+ plan = options.load_balancing_policy.plan(keyspace, statement, options)
281
+
282
+ send_request_by_plan(promise,
283
+ keyspace,
284
+ statement,
285
+ options,
286
+ request,
287
+ plan,
288
+ timeout)
289
+
290
+ promise.future
291
+ end
292
+
293
+ def execute(statement, options)
294
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
295
+ payload = nil
296
+ payload = options.payload if @connection_options.protocol_version >= 4
297
+ timeout = options.timeout
298
+ result_metadata = statement.result_metadata
299
+ request = Protocol::ExecuteRequest.new(nil,
300
+ statement.params_types,
301
+ statement.params,
302
+ result_metadata.nil?,
303
+ options.consistency,
304
+ options.serial_consistency,
305
+ options.page_size,
306
+ options.paging_state,
307
+ options.trace?,
308
+ timestamp,
309
+ payload)
310
+ promise = @futures.promise
311
+
312
+ keyspace = @keyspace
313
+ plan = options.load_balancing_policy.plan(keyspace, statement, options)
314
+
315
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout)
316
+
317
+ promise.future
318
+ end
319
+
320
+ def batch(statement, options)
321
+ if @connection_options.protocol_version < 2
322
+ return @futures.error(
323
+ Errors::ClientError.new(
324
+ 'Batch statements are not supported by the current version of ' \
325
+ 'Apache Cassandra'
326
+ )
327
+ )
328
+ end
329
+
330
+ timestamp = @timestamp_generator.next if @timestamp_generator && @connection_options.protocol_version > 2
331
+ payload = nil
332
+ payload = options.payload if @connection_options.protocol_version >= 4
333
+ timeout = options.timeout
334
+ request = Protocol::BatchRequest.new(BATCH_TYPES[statement.type],
335
+ options.consistency,
336
+ options.trace?,
337
+ options.serial_consistency,
338
+ timestamp,
339
+ payload)
340
+ keyspace = @keyspace
341
+ plan = options.load_balancing_policy.plan(keyspace, statement, options)
342
+ promise = @futures.promise
343
+
344
+ batch_by_plan(promise, keyspace, statement, options, request, plan, timeout)
345
+
346
+ promise.future
347
+ end
348
+
349
+ def inspect
350
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
351
+ end
352
+
353
+ private
354
+
355
+ NO_CONNECTIONS = Ione::Future.resolved([])
356
+ BATCH_TYPES = {
357
+ logged: Protocol::BatchRequest::LOGGED_TYPE,
358
+ unlogged: Protocol::BatchRequest::UNLOGGED_TYPE,
359
+ counter: Protocol::BatchRequest::COUNTER_TYPE
360
+ }.freeze
361
+ CLIENT_CLOSED = Ione::Future.failed(Errors::ClientError.new('Client closed'))
362
+ NOT_CONNECTED = Errors::ClientError.new('Client not connected')
363
+ CLIENT_NOT_CONNECTED = Ione::Future.failed(NOT_CONNECTED)
364
+
365
+ UNAVAILABLE_ERROR_CODE = 0x1000
366
+ WRITE_TIMEOUT_ERROR_CODE = 0x1100
367
+ READ_TIMEOUT_ERROR_CODE = 0x1200
368
+ OVERLOADED_ERROR_CODE = 0x1001
369
+ SERVER_ERROR_CODE = 0x0000
370
+ BOOTSTRAPPING_ERROR_CODE = 0x1002
371
+ UNPREPARED_ERROR_CODE = 0x2500
372
+
373
+ SELECT_SCHEMA_PEERS =
374
+ Protocol::QueryRequest.new(
375
+ 'SELECT peer, rpc_address, schema_version FROM system.peers',
376
+ EMPTY_LIST,
377
+ EMPTY_LIST,
378
+ :quorum
379
+ )
380
+ SELECT_SCHEMA_LOCAL =
381
+ Protocol::QueryRequest.new(
382
+ "SELECT schema_version FROM system.local WHERE key='local'",
383
+ EMPTY_LIST,
384
+ EMPTY_LIST,
385
+ :quorum
386
+ )
387
+
388
+ def connected(f)
389
+ if f.resolved?
390
+ synchronize do
391
+ @state = :connected
392
+ end
393
+
394
+ @logger.info('Session created')
395
+ else
396
+ synchronize do
397
+ @state = :defunct
398
+ end
399
+
400
+ f.on_failure do |e|
401
+ @logger.error("Session failed to connect (#{e.class.name}: #{e.message})")
402
+ end
403
+
404
+ close
405
+ end
406
+ end
407
+
408
+ def closed(f)
409
+ synchronize do
410
+ @state = :closed
411
+
412
+ if f.resolved?
413
+ @logger.info('Session closed')
414
+ else
415
+ f.on_failure do |e|
416
+ @logger.error("Session failed to close (#{e.class.name}: #{e.message})")
417
+ end
418
+ end
419
+ end
420
+ end
421
+
422
+ def close_connections
423
+ futures = []
424
+ synchronize do
425
+ @connections.each do |_host, connections|
426
+ connections.snapshot.each do |c|
427
+ futures << c.close
428
+ end
429
+ end.clear
430
+ end
431
+
432
+ Ione::Future.all(*futures).map(self)
433
+ end
434
+
435
+ def connect_to_host_maybe_retry(host, pool_size)
436
+ connect_to_host(host, pool_size).fallback do |e|
437
+ @logger.error('Scheduling initial connection retry to ' \
438
+ "#{host.ip} (#{e.class.name}: #{e.message})")
439
+ connect_to_host_with_retry(host, pool_size, @reconnection_policy.schedule)
440
+ end.map(nil)
441
+ end
442
+
443
+ def connect_to_host_with_retry(host, pool_size, schedule)
444
+ interval = schedule.next
445
+
446
+ @logger.debug("Reconnecting to #{host.ip} in #{interval} seconds")
447
+
448
+ f = @reactor.schedule_timer(interval)
449
+ f.flat_map do
450
+ connect_to_host(host, pool_size).fallback do |e|
451
+ @logger.error('Scheduling connection retry to ' \
452
+ "#{host.ip} (#{e.class.name}: #{e.message})")
453
+ connect_to_host_with_retry(host, pool_size, schedule)
454
+ end
455
+ end
456
+ end
457
+
458
+ def connect_to_host(host, pool_size)
459
+ size = 0
460
+
461
+ synchronize do
462
+ unless @connections.include?(host)
463
+ @logger.info("Not connecting to #{host.ip} - host is currently down")
464
+ return NO_CONNECTIONS
465
+ end
466
+
467
+ pool = @connections[host]
468
+ size = pool_size - pool.size
469
+
470
+ if size <= 0
471
+ @logger.info("Not connecting to #{host.ip} - host is already fully connected")
472
+ return NO_CONNECTIONS
473
+ end
474
+
475
+ size -= @pending_connections[host]
476
+
477
+ if size <= 0
478
+ @logger.info("Not connecting to #{host.ip} - " \
479
+ 'host is already pending connections')
480
+ return NO_CONNECTIONS
481
+ end
482
+
483
+ @pending_connections[host] += size
484
+ end
485
+
486
+ @logger.debug("Creating #{size} request connections to #{host.ip}")
487
+ futures = size.times.map do
488
+ @connector.connect(host).recover do |e|
489
+ FailedConnection.new(e, host)
490
+ end
491
+ end
492
+
493
+ Ione::Future.all(*futures).flat_map do |connections|
494
+ error = nil
495
+
496
+ connections.reject! do |connection|
497
+ if connection.connected?
498
+ false
499
+ else
500
+ error = connection.error
501
+ true
502
+ end
503
+ end
504
+
505
+ @logger.debug("Created #{connections.size} request connections to #{host.ip}")
506
+
507
+ pool = nil
508
+
509
+ synchronize do
510
+ @pending_connections[host] -= size
511
+
512
+ if @connections.include?(host)
513
+ pool = @connections[host]
514
+ else
515
+ @pending_connections.delete(host) unless @pending_connections[host] > 0
516
+ end
517
+ end
518
+
519
+ if pool
520
+ pool.add_connections(connections)
521
+
522
+ connections.each do |connection|
523
+ connection.on_closed do |cause|
524
+ if cause
525
+ @logger.info('Request connection closed ' \
526
+ "(#{cause.class.name}: #{cause.message})")
527
+ else
528
+ @logger.info('Request connection closed')
529
+ end
530
+ connect_to_host_maybe_retry(host, pool_size) if cause
531
+ end
532
+ end
533
+ else
534
+ connections.each(&:close)
535
+ end
536
+
537
+ if error
538
+ Ione::Future.failed(error)
539
+ else
540
+ Ione::Future.resolved(connections)
541
+ end
542
+ end
543
+ end
544
+
545
+ def execute_by_plan(promise,
546
+ keyspace,
547
+ statement,
548
+ options,
549
+ request,
550
+ plan,
551
+ timeout,
552
+ errors = nil,
553
+ hosts = [],
554
+ retries = -1)
555
+ unless plan.has_next?
556
+ promise.break(Errors::NoHostsAvailable.new(errors))
557
+ return
558
+ end
559
+
560
+ hosts << host = plan.next
561
+ retries += 1
562
+
563
+ pool = nil
564
+ synchronize { pool = @connections[host] }
565
+
566
+ unless pool
567
+ errors ||= {}
568
+ errors[host] = NOT_CONNECTED
569
+ return execute_by_plan(promise,
570
+ keyspace,
571
+ statement,
572
+ options,
573
+ request,
574
+ plan,
575
+ timeout,
576
+ errors,
577
+ hosts,
578
+ retries)
579
+ end
580
+
581
+ connection = pool.random_connection
582
+
583
+ if keyspace && connection.keyspace != keyspace
584
+ switch = switch_keyspace(connection, keyspace, timeout)
585
+ switch.on_complete do |s|
586
+ if s.resolved?
587
+ prepare_and_send_request_by_plan(host,
588
+ connection,
589
+ promise,
590
+ keyspace,
591
+ statement,
592
+ options,
593
+ request,
594
+ plan,
595
+ timeout,
596
+ errors,
597
+ hosts,
598
+ retries)
599
+ else
600
+ s.on_failure do |e|
601
+ if e.is_a?(Errors::HostError) ||
602
+ (e.is_a?(Errors::TimeoutError) && statement.idempotent?)
603
+ errors ||= {}
604
+ errors[host] = e
605
+ execute_by_plan(promise,
606
+ keyspace,
607
+ statement,
608
+ options,
609
+ request,
610
+ plan,
611
+ timeout,
612
+ errors,
613
+ hosts,
614
+ retries)
615
+ else
616
+ promise.break(e)
617
+ end
618
+ end
619
+ end
620
+ end
621
+ else
622
+ prepare_and_send_request_by_plan(host,
623
+ connection,
624
+ promise,
625
+ keyspace,
626
+ statement,
627
+ options,
628
+ request,
629
+ plan,
630
+ timeout,
631
+ errors,
632
+ hosts,
633
+ retries)
634
+ end
635
+ rescue => e
636
+ errors ||= {}
637
+ errors[host] = e
638
+ execute_by_plan(promise,
639
+ keyspace,
640
+ statement,
641
+ options,
642
+ request,
643
+ plan,
644
+ timeout,
645
+ errors,
646
+ hosts,
647
+ retries)
648
+ end
649
+
650
+ def prepare_and_send_request_by_plan(host,
651
+ connection,
652
+ promise,
653
+ keyspace,
654
+ statement,
655
+ options,
656
+ request,
657
+ plan,
658
+ timeout,
659
+ errors,
660
+ hosts,
661
+ retries)
662
+ cql = statement.cql
663
+
664
+ # Get the prepared statement id for this statement from our cache if possible. We are optimistic
665
+ # that the statement has previously been prepared on all hosts, so the id will be valid. However, if
666
+ # we're in the midst of preparing the statement on the given host, we know that executing with the id
667
+ # will fail. So, act like we don't have the prepared-statement id in that case.
668
+
669
+ id = synchronize { @preparing_statements[host][cql] ? nil : @prepared_statements[cql] }
670
+
671
+ if id
672
+ request.id = id
673
+ do_send_request_by_plan(host,
674
+ connection,
675
+ promise,
676
+ keyspace,
677
+ statement,
678
+ options,
679
+ request,
680
+ plan,
681
+ timeout,
682
+ errors,
683
+ hosts,
684
+ retries)
685
+ else
686
+ prepare = prepare_statement(host, connection, cql, timeout)
687
+ prepare.on_complete do |_|
688
+ if prepare.resolved?
689
+ request.id = prepare.value
690
+ do_send_request_by_plan(host,
691
+ connection,
692
+ promise,
693
+ keyspace,
694
+ statement,
695
+ options,
696
+ request,
697
+ plan,
698
+ timeout,
699
+ errors,
700
+ hosts,
701
+ retries)
702
+ else
703
+ prepare.on_failure do |e|
704
+ if e.is_a?(Errors::HostError) ||
705
+ (e.is_a?(Errors::TimeoutError) && statement.idempotent?)
706
+ errors ||= {}
707
+ errors[host] = e
708
+ execute_by_plan(promise,
709
+ keyspace,
710
+ statement,
711
+ options,
712
+ request,
713
+ plan,
714
+ timeout,
715
+ errors,
716
+ hosts,
717
+ retries)
718
+ else
719
+ promise.break(e)
720
+ end
721
+ end
722
+ end
723
+ end
724
+ end
725
+ rescue => e
726
+ promise.break(e)
727
+ end
728
+
729
+ def batch_by_plan(promise,
730
+ keyspace,
731
+ statement,
732
+ options,
733
+ request,
734
+ plan,
735
+ timeout,
736
+ errors = nil,
737
+ hosts = [],
738
+ retries = -1)
739
+ unless plan.has_next?
740
+ promise.break(Errors::NoHostsAvailable.new(errors))
741
+ return
742
+ end
743
+
744
+ hosts << host = plan.next
745
+ retries += 1
746
+ pool = nil
747
+ synchronize { pool = @connections[host] }
748
+
749
+ unless pool
750
+ errors ||= {}
751
+ errors[host] = NOT_CONNECTED
752
+ return batch_by_plan(promise,
753
+ keyspace,
754
+ statement,
755
+ options,
756
+ request,
757
+ plan,
758
+ timeout,
759
+ errors,
760
+ hosts,
761
+ retries)
762
+ end
763
+
764
+ connection = pool.random_connection
765
+
766
+ if keyspace && connection.keyspace != keyspace
767
+ switch = switch_keyspace(connection, keyspace, timeout)
768
+ switch.on_complete do |s|
769
+ if s.resolved?
770
+ batch_and_send_request_by_plan(host,
771
+ connection,
772
+ promise,
773
+ keyspace,
774
+ statement,
775
+ request,
776
+ options,
777
+ plan,
778
+ timeout,
779
+ errors,
780
+ hosts,
781
+ retries)
782
+ else
783
+ s.on_failure do |e|
784
+ if e.is_a?(Errors::HostError) ||
785
+ (e.is_a?(Errors::TimeoutError) && statement.idempotent?)
786
+ errors ||= {}
787
+ errors[host] = e
788
+ batch_by_plan(promise,
789
+ keyspace,
790
+ statement,
791
+ options,
792
+ request,
793
+ plan,
794
+ timeout,
795
+ errors,
796
+ hosts,
797
+ retries)
798
+ else
799
+ promise.break(e)
800
+ end
801
+ end
802
+ end
803
+ end
804
+ else
805
+ batch_and_send_request_by_plan(host,
806
+ connection,
807
+ promise,
808
+ keyspace,
809
+ statement,
810
+ request,
811
+ options,
812
+ plan,
813
+ timeout,
814
+ errors,
815
+ hosts,
816
+ retries)
817
+ end
818
+ rescue => e
819
+ errors ||= {}
820
+ errors[host] = e
821
+ batch_by_plan(promise,
822
+ keyspace,
823
+ statement,
824
+ options,
825
+ request,
826
+ plan,
827
+ timeout,
828
+ errors,
829
+ hosts,
830
+ retries)
831
+ end
832
+
833
+ def batch_and_send_request_by_plan(host,
834
+ connection,
835
+ promise,
836
+ keyspace,
837
+ batch_statement,
838
+ request,
839
+ options,
840
+ plan,
841
+ timeout,
842
+ errors,
843
+ hosts,
844
+ retries)
845
+ request.clear
846
+ unprepared = Hash.new {|hash, cql| hash[cql] = []}
847
+
848
+ batch_statement.statements.each do |statement|
849
+ cql = statement.cql
850
+
851
+ if statement.is_a?(Statements::Bound)
852
+ # Get the prepared statement id for this statement from our cache if possible. We are optimistic
853
+ # that the statement has previously been prepared on all hosts, so the id will be valid. However, if
854
+ # we're in the midst of preparing the statement on the given host, we know that executing with the id
855
+ # will fail. So, act like we don't have the prepared-statement id in that case.
856
+
857
+ id = synchronize { @preparing_statements[host][cql] ? nil : @prepared_statements[cql] }
858
+
859
+ if id
860
+ request.add_prepared(id, statement.params, statement.params_types)
861
+ else
862
+ unprepared[cql] << statement
863
+ end
864
+ else
865
+ request.add_query(cql, statement.params, statement.params_types)
866
+ end
867
+ end
868
+
869
+ if unprepared.empty?
870
+ do_send_request_by_plan(host,
871
+ connection,
872
+ promise,
873
+ keyspace,
874
+ batch_statement,
875
+ options,
876
+ request,
877
+ plan,
878
+ timeout,
879
+ errors,
880
+ hosts,
881
+ retries)
882
+ else
883
+ to_prepare = unprepared.to_a
884
+ futures = to_prepare.map do |cql, _|
885
+ prepare_statement(host, connection, cql, timeout)
886
+ end
887
+
888
+ Ione::Future.all(*futures).on_complete do |f|
889
+ if f.resolved?
890
+ prepared_ids = f.value
891
+ to_prepare.each_with_index do |(_, statements), i|
892
+ statements.each do |statement|
893
+ request.add_prepared(prepared_ids[i],
894
+ statement.params,
895
+ statement.params_types)
896
+ end
897
+ end
898
+
899
+ do_send_request_by_plan(host,
900
+ connection,
901
+ promise,
902
+ keyspace,
903
+ batch_statement,
904
+ options,
905
+ request,
906
+ plan,
907
+ timeout,
908
+ errors,
909
+ hosts,
910
+ retries)
911
+ else
912
+ f.on_failure do |e|
913
+ if e.is_a?(Errors::HostError) ||
914
+ (e.is_a?(Errors::TimeoutError) && batch_statement.idempotent?)
915
+ errors ||= {}
916
+ errors[host] = e
917
+ batch_by_plan(promise,
918
+ keyspace,
919
+ batch_statement,
920
+ options,
921
+ request,
922
+ plan,
923
+ timeout,
924
+ errors,
925
+ hosts,
926
+ retries)
927
+ else
928
+ promise.break(e)
929
+ end
930
+ end
931
+ end
932
+ end
933
+ end
934
+ end
935
+
936
+ def send_request_by_plan(promise,
937
+ keyspace,
938
+ statement,
939
+ options,
940
+ request,
941
+ plan,
942
+ timeout,
943
+ errors = nil,
944
+ hosts = [],
945
+ retries = -1)
946
+ unless plan.has_next?
947
+ promise.break(Errors::NoHostsAvailable.new(errors))
948
+ return
949
+ end
950
+
951
+ hosts << host = plan.next
952
+ retries += 1
953
+ pool = nil
954
+ synchronize { pool = @connections[host] }
955
+
956
+ unless pool
957
+ errors ||= {}
958
+ errors[host] = NOT_CONNECTED
959
+ return send_request_by_plan(promise,
960
+ keyspace,
961
+ statement,
962
+ options,
963
+ request,
964
+ plan,
965
+ timeout,
966
+ errors,
967
+ hosts,
968
+ retries)
969
+ end
970
+
971
+ connection = pool.random_connection
972
+
973
+ if keyspace && connection.keyspace != keyspace
974
+ switch = switch_keyspace(connection, keyspace, timeout)
975
+ switch.on_complete do |s|
976
+ if s.resolved?
977
+ do_send_request_by_plan(host,
978
+ connection,
979
+ promise,
980
+ keyspace,
981
+ statement,
982
+ options,
983
+ request,
984
+ plan,
985
+ timeout,
986
+ errors,
987
+ hosts,
988
+ retries)
989
+ else
990
+ s.on_failure do |e|
991
+ if e.is_a?(Errors::HostError) ||
992
+ (e.is_a?(Errors::TimeoutError) && statement.idempotent?)
993
+ errors ||= {}
994
+ errors[host] = e
995
+ send_request_by_plan(promise,
996
+ keyspace,
997
+ statement,
998
+ options,
999
+ request,
1000
+ plan,
1001
+ timeout,
1002
+ errors,
1003
+ hosts,
1004
+ retries)
1005
+ else
1006
+ promise.break(e)
1007
+ end
1008
+ end
1009
+ end
1010
+ end
1011
+ else
1012
+ do_send_request_by_plan(host,
1013
+ connection,
1014
+ promise,
1015
+ keyspace,
1016
+ statement,
1017
+ options,
1018
+ request,
1019
+ plan,
1020
+ timeout,
1021
+ errors,
1022
+ hosts,
1023
+ retries)
1024
+ end
1025
+ rescue => e
1026
+ errors ||= {}
1027
+ errors[host] = e
1028
+ send_request_by_plan(promise,
1029
+ keyspace,
1030
+ statement,
1031
+ options,
1032
+ request,
1033
+ plan,
1034
+ timeout,
1035
+ errors,
1036
+ hosts,
1037
+ retries)
1038
+ end
1039
+
1040
+ def do_send_request_by_plan(host,
1041
+ connection,
1042
+ promise,
1043
+ keyspace,
1044
+ statement,
1045
+ options,
1046
+ request,
1047
+ plan,
1048
+ timeout,
1049
+ errors,
1050
+ hosts,
1051
+ retries)
1052
+ request.retries = retries
1053
+
1054
+ f = connection.send_request(request, timeout)
1055
+ f.on_complete do |response_future|
1056
+ errors ||= {}
1057
+ handle_response(response_future,
1058
+ host,
1059
+ connection,
1060
+ promise,
1061
+ keyspace,
1062
+ statement,
1063
+ options,
1064
+ request,
1065
+ plan,
1066
+ timeout,
1067
+ errors,
1068
+ hosts,
1069
+ retries)
1070
+ end
1071
+ rescue => e
1072
+ promise.break(e)
1073
+ end
1074
+
1075
+ def handle_response(response_future,
1076
+ host,
1077
+ connection,
1078
+ promise,
1079
+ keyspace,
1080
+ statement,
1081
+ options,
1082
+ request,
1083
+ plan,
1084
+ timeout,
1085
+ errors,
1086
+ hosts,
1087
+ retries)
1088
+ if response_future.resolved?
1089
+ r = response_future.value
1090
+
1091
+ begin
1092
+ decision = nil
1093
+
1094
+ case r
1095
+ when Protocol::UnavailableErrorResponse
1096
+ decision = options.retry_policy.unavailable(statement,
1097
+ r.consistency,
1098
+ r.required,
1099
+ r.alive,
1100
+ retries)
1101
+ when Protocol::WriteTimeoutErrorResponse
1102
+ decision = options.retry_policy.write_timeout(statement,
1103
+ r.consistency,
1104
+ r.write_type,
1105
+ r.blockfor,
1106
+ r.received,
1107
+ retries)
1108
+ when Protocol::ReadTimeoutErrorResponse
1109
+ decision = options.retry_policy.read_timeout(statement,
1110
+ r.consistency,
1111
+ r.blockfor,
1112
+ r.received,
1113
+ r.data_present,
1114
+ retries)
1115
+ when Protocol::UnpreparedErrorResponse
1116
+ cql = nil
1117
+ if statement.is_a?(Cassandra::Statements::Batch)
1118
+ # Find the prepared statement with the prepared-statement-id reported by the node.
1119
+ unprepared_child = statement.statements.select do |s|
1120
+ (s.is_a?(Cassandra::Statements::Prepared) || s.is_a?(Cassandra::Statements::Bound)) && s.id == r.id
1121
+ end.first
1122
+ cql = unprepared_child ? unprepared_child.cql : nil
1123
+ else
1124
+ # This is a normal statement, so we have everything we need.
1125
+ cql = statement.cql
1126
+ synchronize { @preparing_statements[host].delete(cql) }
1127
+ end
1128
+
1129
+ prepare = prepare_statement(host, connection, cql, timeout)
1130
+ prepare.on_complete do |_|
1131
+ if prepare.resolved?
1132
+ request.id = prepare.value unless request.is_a?(Cassandra::Protocol::BatchRequest)
1133
+ do_send_request_by_plan(host,
1134
+ connection,
1135
+ promise,
1136
+ keyspace,
1137
+ statement,
1138
+ options,
1139
+ request,
1140
+ plan,
1141
+ timeout,
1142
+ errors,
1143
+ hosts,
1144
+ retries)
1145
+ else
1146
+ prepare.on_failure do |e|
1147
+ if e.is_a?(Errors::HostError) ||
1148
+ (e.is_a?(Errors::TimeoutError) && statement.idempotent?)
1149
+ errors[host] = e
1150
+ execute_by_plan(promise,
1151
+ keyspace,
1152
+ statement,
1153
+ options,
1154
+ request,
1155
+ plan,
1156
+ timeout,
1157
+ errors,
1158
+ hosts,
1159
+ retries)
1160
+ else
1161
+ promise.break(e)
1162
+ end
1163
+ end
1164
+ end
1165
+ end
1166
+ when Protocol::ErrorResponse
1167
+ error = r.to_error(keyspace,
1168
+ statement,
1169
+ options,
1170
+ hosts,
1171
+ request.consistency,
1172
+ retries)
1173
+
1174
+ if error.is_a?(Errors::HostError) ||
1175
+ (error.is_a?(Errors::TimeoutError) && statement.idempotent?)
1176
+ errors[host] = error
1177
+
1178
+ case request
1179
+ when Protocol::QueryRequest, Protocol::PrepareRequest
1180
+ send_request_by_plan(promise,
1181
+ keyspace,
1182
+ statement,
1183
+ options,
1184
+ request,
1185
+ plan,
1186
+ timeout,
1187
+ errors,
1188
+ hosts,
1189
+ retries)
1190
+ when Protocol::ExecuteRequest
1191
+ execute_by_plan(promise,
1192
+ keyspace,
1193
+ statement,
1194
+ options,
1195
+ request,
1196
+ plan,
1197
+ timeout,
1198
+ errors,
1199
+ hosts,
1200
+ retries)
1201
+ when Protocol::BatchRequest
1202
+ batch_by_plan(promise,
1203
+ keyspace,
1204
+ statement,
1205
+ options,
1206
+ request,
1207
+ plan,
1208
+ timeout,
1209
+ errors,
1210
+ hosts,
1211
+ retries)
1212
+ end
1213
+ else
1214
+ promise.break(error)
1215
+ end
1216
+ when Protocol::SetKeyspaceResultResponse
1217
+ @keyspace = r.keyspace
1218
+ promise.fulfill(Cassandra::Results::Void.new(r.custom_payload,
1219
+ r.warnings,
1220
+ r.trace_id,
1221
+ keyspace,
1222
+ statement,
1223
+ options,
1224
+ hosts,
1225
+ request.consistency,
1226
+ retries,
1227
+ self,
1228
+ @futures))
1229
+ when Protocol::PreparedResultResponse
1230
+ cql = request.cql
1231
+ synchronize do
1232
+ @prepared_statements[cql] = r.id
1233
+ @preparing_statements[host].delete(cql)
1234
+ end
1235
+
1236
+ metadata = r.metadata
1237
+ pk_idx = r.pk_idx
1238
+ pk_idx ||= @schema.get_pk_idx(metadata)
1239
+
1240
+ promise.fulfill(
1241
+ Statements::Prepared.new(r.id,
1242
+ r.custom_payload,
1243
+ r.warnings,
1244
+ cql,
1245
+ metadata,
1246
+ r.result_metadata,
1247
+ pk_idx,
1248
+ r.trace_id,
1249
+ keyspace,
1250
+ statement,
1251
+ options,
1252
+ hosts,
1253
+ request.consistency,
1254
+ retries,
1255
+ self,
1256
+ @connection_options)
1257
+ )
1258
+ when Protocol::RawRowsResultResponse
1259
+ r.materialize(statement.result_metadata)
1260
+ promise.fulfill(
1261
+ Results::Paged.new(r.custom_payload,
1262
+ r.warnings,
1263
+ r.rows,
1264
+ r.paging_state,
1265
+ r.trace_id,
1266
+ keyspace,
1267
+ statement,
1268
+ options,
1269
+ hosts,
1270
+ request.consistency,
1271
+ retries,
1272
+ self,
1273
+ @futures)
1274
+ )
1275
+ when Protocol::RowsResultResponse
1276
+ promise.fulfill(
1277
+ Results::Paged.new(r.custom_payload,
1278
+ r.warnings,
1279
+ r.rows,
1280
+ r.paging_state,
1281
+ r.trace_id,
1282
+ keyspace,
1283
+ statement,
1284
+ options,
1285
+ hosts,
1286
+ request.consistency,
1287
+ retries,
1288
+ self,
1289
+ @futures)
1290
+ )
1291
+ when Protocol::SchemaChangeResultResponse
1292
+ if r.change == 'DROPPED' &&
1293
+ r.target == Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
1294
+ @schema.delete_keyspace(r.keyspace)
1295
+ end
1296
+
1297
+ @logger.debug('Waiting for schema to propagate to all hosts after a change')
1298
+ wait_for_schema_agreement(connection,
1299
+ @reconnection_policy.schedule).on_complete do |f|
1300
+ unless f.resolved?
1301
+ f.on_failure do |e|
1302
+ @logger.error(
1303
+ "Schema agreement failure (#{e.class.name}: #{e.message})"
1304
+ )
1305
+ end
1306
+ end
1307
+ promise.fulfill(
1308
+ Results::Void.new(r.custom_payload,
1309
+ r.warnings,
1310
+ r.trace_id,
1311
+ keyspace,
1312
+ statement,
1313
+ options,
1314
+ hosts,
1315
+ request.consistency,
1316
+ retries,
1317
+ self,
1318
+ @futures)
1319
+ )
1320
+ end
1321
+ else
1322
+ promise.fulfill(Results::Void.new(r.custom_payload,
1323
+ r.warnings,
1324
+ r.trace_id,
1325
+ keyspace,
1326
+ statement,
1327
+ options,
1328
+ hosts,
1329
+ request.consistency,
1330
+ retries,
1331
+ self,
1332
+ @futures))
1333
+ end
1334
+
1335
+ if decision
1336
+ case decision
1337
+ when Retry::Decisions::Retry
1338
+ request.consistency = decision.consistency
1339
+ do_send_request_by_plan(host,
1340
+ connection,
1341
+ promise,
1342
+ keyspace,
1343
+ statement,
1344
+ options,
1345
+ request,
1346
+ plan,
1347
+ timeout,
1348
+ errors,
1349
+ hosts,
1350
+ retries + 1)
1351
+ when Retry::Decisions::TryNextHost
1352
+ errors[host] = r.to_error(keyspace,
1353
+ statement,
1354
+ options,
1355
+ hosts,
1356
+ request.consistency,
1357
+ retries)
1358
+ case request
1359
+ when Protocol::QueryRequest, Protocol::PrepareRequest
1360
+ send_request_by_plan(promise,
1361
+ keyspace,
1362
+ statement,
1363
+ options,
1364
+ request,
1365
+ plan,
1366
+ timeout,
1367
+ errors,
1368
+ hosts,
1369
+ retries)
1370
+ when Protocol::ExecuteRequest
1371
+ execute_by_plan(promise,
1372
+ keyspace,
1373
+ statement,
1374
+ options,
1375
+ request,
1376
+ plan,
1377
+ timeout,
1378
+ errors,
1379
+ hosts,
1380
+ retries)
1381
+ when Protocol::BatchRequest
1382
+ batch_by_plan(promise,
1383
+ keyspace,
1384
+ statement,
1385
+ options,
1386
+ request,
1387
+ plan,
1388
+ timeout,
1389
+ errors,
1390
+ hosts,
1391
+ retries)
1392
+ else
1393
+ promise.break(e)
1394
+ end
1395
+ when Retry::Decisions::Ignore
1396
+ promise.fulfill(
1397
+ Results::Void.new(r.custom_payload,
1398
+ r.warnings,
1399
+ nil,
1400
+ keyspace,
1401
+ statement,
1402
+ options,
1403
+ hosts,
1404
+ request.consistency,
1405
+ retries,
1406
+ self,
1407
+ @futures)
1408
+ )
1409
+ when Retry::Decisions::Reraise
1410
+ promise.break(
1411
+ r.to_error(keyspace,
1412
+ statement,
1413
+ options,
1414
+ hosts,
1415
+ request.consistency,
1416
+ retries)
1417
+ )
1418
+ else
1419
+ promise.break(
1420
+ r.to_error(keyspace,
1421
+ statement,
1422
+ options,
1423
+ hosts,
1424
+ request.consistency,
1425
+ retries)
1426
+ )
1427
+ end
1428
+ end
1429
+ rescue => e
1430
+ promise.break(e)
1431
+ end
1432
+ else
1433
+ response_future.on_failure do |ex|
1434
+ if ex.is_a?(Errors::HostError) ||
1435
+ (ex.is_a?(Errors::TimeoutError) && statement.idempotent?)
1436
+
1437
+ errors[host] = ex
1438
+ case request
1439
+ when Protocol::QueryRequest, Protocol::PrepareRequest
1440
+ send_request_by_plan(promise,
1441
+ keyspace,
1442
+ statement,
1443
+ options,
1444
+ request,
1445
+ plan,
1446
+ timeout,
1447
+ errors,
1448
+ hosts,
1449
+ retries)
1450
+ when Protocol::ExecuteRequest
1451
+ execute_by_plan(promise,
1452
+ keyspace,
1453
+ statement,
1454
+ options,
1455
+ request,
1456
+ plan,
1457
+ timeout,
1458
+ errors,
1459
+ hosts,
1460
+ retries)
1461
+ when Protocol::BatchRequest
1462
+ batch_by_plan(promise,
1463
+ keyspace,
1464
+ statement,
1465
+ options,
1466
+ request,
1467
+ plan,
1468
+ timeout,
1469
+ errors,
1470
+ hosts,
1471
+ retries)
1472
+ else
1473
+ promise.break(ex)
1474
+ end
1475
+ else
1476
+ promise.break(ex)
1477
+ end
1478
+ end
1479
+ end
1480
+ end
1481
+
1482
+ def wait_for_schema_agreement(connection, schedule)
1483
+ peers_future = send_select_request(connection, SELECT_SCHEMA_PEERS)
1484
+ local_future = send_select_request(connection, SELECT_SCHEMA_LOCAL)
1485
+
1486
+ Ione::Future.all(peers_future, local_future).flat_map do |(peers, local)|
1487
+ versions = ::Set.new
1488
+
1489
+ unless local.empty?
1490
+ host = @registry.host(connection.host)
1491
+
1492
+ if host && @profile_manager.distance(host) != :ignore
1493
+ versions << version = local.first['schema_version']
1494
+ @logger.debug("Host #{host.ip} schema version is #{version}")
1495
+ end
1496
+ end
1497
+
1498
+ peers.each do |row|
1499
+ host = @registry.host(peer_ip(row))
1500
+ next unless host && @profile_manager.distance(host) != :ignore
1501
+
1502
+ versions << version = row['schema_version']
1503
+ @logger.debug("Host #{host.ip} schema version is #{version}")
1504
+ end
1505
+
1506
+ if versions.one?
1507
+ @logger.debug('All hosts have the same schema')
1508
+ Ione::Future.resolved
1509
+ else
1510
+ interval = schedule.next
1511
+ @logger.info('Hosts have different schema versions: ' \
1512
+ "#{versions.to_a.inspect}, retrying in #{interval} seconds")
1513
+ @reactor.schedule_timer(interval).flat_map do
1514
+ wait_for_schema_agreement(connection, schedule)
1515
+ end
1516
+ end
1517
+ end
1518
+ end
1519
+
1520
+ def peer_ip(data)
1521
+ ip = data['rpc_address']
1522
+ ip = data['peer'] if ip == '0.0.0.0'
1523
+
1524
+ @address_resolver.resolve(ip)
1525
+ end
1526
+
1527
+ def switch_keyspace(connection, keyspace, timeout)
1528
+ pending_keyspace = connection[:pending_keyspace]
1529
+ pending_switch = connection[:pending_switch]
1530
+
1531
+ return pending_switch || Ione::Future.resolved if pending_keyspace == keyspace
1532
+
1533
+ request = Protocol::QueryRequest.new("USE #{Util.escape_name(keyspace)}",
1534
+ EMPTY_LIST,
1535
+ EMPTY_LIST,
1536
+ :quorum)
1537
+
1538
+ f = connection.send_request(request, timeout).map do |r|
1539
+ case r
1540
+ when Protocol::SetKeyspaceResultResponse
1541
+ @keyspace = r.keyspace
1542
+ nil
1543
+ when Protocol::ErrorResponse
1544
+ raise r.to_error(nil,
1545
+ Statements::Simple.new("USE #{Util.escape_name(keyspace)}"),
1546
+ VOID_OPTIONS,
1547
+ EMPTY_LIST,
1548
+ :quorum,
1549
+ 0)
1550
+ else
1551
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
1552
+ end
1553
+ end
1554
+
1555
+ connection[:pending_keyspace] = keyspace
1556
+ connection[:pending_switch] = f
1557
+
1558
+ f.on_complete do |_f|
1559
+ connection[:pending_switch] = nil
1560
+ connection[:pending_keyspace] = nil
1561
+ end
1562
+
1563
+ f
1564
+ end
1565
+
1566
+ def prepare_statement(host, connection, cql, timeout)
1567
+ synchronize do
1568
+ pending = @preparing_statements[host]
1569
+
1570
+ return pending[cql] if pending.key?(cql)
1571
+ end
1572
+
1573
+ request = Protocol::PrepareRequest.new(cql, false)
1574
+
1575
+ f = connection.send_request(request, timeout).map do |r|
1576
+ case r
1577
+ when Protocol::PreparedResultResponse
1578
+ id = r.id
1579
+ synchronize do
1580
+ @prepared_statements[cql] = id
1581
+ @preparing_statements[host].delete(cql)
1582
+ end
1583
+ id
1584
+ when Protocol::ErrorResponse
1585
+ raise r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :quorum, 0)
1586
+ else
1587
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
1588
+ end
1589
+ end
1590
+
1591
+ synchronize do
1592
+ @preparing_statements[host][cql] = f
1593
+ end
1594
+
1595
+ f
1596
+ end
1597
+
1598
+ def send_select_request(connection, request)
1599
+ connection.send_request(request).map do |r|
1600
+ case r
1601
+ when Protocol::RowsResultResponse
1602
+ r.rows
1603
+ when Protocol::ErrorResponse
1604
+ raise r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :quorum, 0)
1605
+ else
1606
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
1607
+ end
1608
+ end
1609
+ end
1610
+ end
1611
+ end
1612
+ end