yugabyte-ycql-driver 3.2.3.1

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