cassandra-driver 1.0.0.beta.2-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +4 -0
  3. data/README.md +125 -0
  4. data/lib/cassandra/auth/providers/password.rb +73 -0
  5. data/lib/cassandra/auth/providers.rb +16 -0
  6. data/lib/cassandra/auth.rb +97 -0
  7. data/lib/cassandra/client/batch.rb +212 -0
  8. data/lib/cassandra/client/client.rb +591 -0
  9. data/lib/cassandra/client/column_metadata.rb +54 -0
  10. data/lib/cassandra/client/connection_manager.rb +72 -0
  11. data/lib/cassandra/client/connector.rb +277 -0
  12. data/lib/cassandra/client/execute_options_decoder.rb +59 -0
  13. data/lib/cassandra/client/null_logger.rb +37 -0
  14. data/lib/cassandra/client/peer_discovery.rb +50 -0
  15. data/lib/cassandra/client/prepared_statement.rb +314 -0
  16. data/lib/cassandra/client/query_result.rb +230 -0
  17. data/lib/cassandra/client/request_runner.rb +71 -0
  18. data/lib/cassandra/client/result_metadata.rb +48 -0
  19. data/lib/cassandra/client/void_result.rb +78 -0
  20. data/lib/cassandra/client.rb +144 -0
  21. data/lib/cassandra/cluster/client.rb +768 -0
  22. data/lib/cassandra/cluster/connector.rb +244 -0
  23. data/lib/cassandra/cluster/control_connection.rb +425 -0
  24. data/lib/cassandra/cluster/metadata.rb +124 -0
  25. data/lib/cassandra/cluster/options.rb +42 -0
  26. data/lib/cassandra/cluster/registry.rb +198 -0
  27. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +47 -0
  28. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
  29. data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
  30. data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
  31. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +92 -0
  32. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
  33. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
  34. data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
  35. data/lib/cassandra/cluster/schema/type_parser.rb +138 -0
  36. data/lib/cassandra/cluster/schema.rb +340 -0
  37. data/lib/cassandra/cluster.rb +215 -0
  38. data/lib/cassandra/column.rb +92 -0
  39. data/lib/cassandra/compression/compressors/lz4.rb +72 -0
  40. data/lib/cassandra/compression/compressors/snappy.rb +66 -0
  41. data/lib/cassandra/compression.rb +66 -0
  42. data/lib/cassandra/driver.rb +111 -0
  43. data/lib/cassandra/errors.rb +79 -0
  44. data/lib/cassandra/execution/info.rb +51 -0
  45. data/lib/cassandra/execution/options.rb +80 -0
  46. data/lib/cassandra/execution/trace.rb +152 -0
  47. data/lib/cassandra/future.rb +675 -0
  48. data/lib/cassandra/host.rb +79 -0
  49. data/lib/cassandra/keyspace.rb +133 -0
  50. data/lib/cassandra/listener.rb +87 -0
  51. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +149 -0
  52. data/lib/cassandra/load_balancing/policies/round_robin.rb +132 -0
  53. data/lib/cassandra/load_balancing/policies/token_aware.rb +119 -0
  54. data/lib/cassandra/load_balancing/policies/white_list.rb +90 -0
  55. data/lib/cassandra/load_balancing/policies.rb +19 -0
  56. data/lib/cassandra/load_balancing.rb +113 -0
  57. data/lib/cassandra/protocol/cql_byte_buffer.rb +307 -0
  58. data/lib/cassandra/protocol/cql_protocol_handler.rb +323 -0
  59. data/lib/cassandra/protocol/frame_decoder.rb +128 -0
  60. data/lib/cassandra/protocol/frame_encoder.rb +48 -0
  61. data/lib/cassandra/protocol/request.rb +38 -0
  62. data/lib/cassandra/protocol/requests/auth_response_request.rb +47 -0
  63. data/lib/cassandra/protocol/requests/batch_request.rb +76 -0
  64. data/lib/cassandra/protocol/requests/credentials_request.rb +47 -0
  65. data/lib/cassandra/protocol/requests/execute_request.rb +103 -0
  66. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  67. data/lib/cassandra/protocol/requests/prepare_request.rb +50 -0
  68. data/lib/cassandra/protocol/requests/query_request.rb +153 -0
  69. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  70. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  71. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  72. data/lib/cassandra/protocol/response.rb +38 -0
  73. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +41 -0
  74. data/lib/cassandra/protocol/responses/auth_success_response.rb +41 -0
  75. data/lib/cassandra/protocol/responses/authenticate_response.rb +41 -0
  76. data/lib/cassandra/protocol/responses/detailed_error_response.rb +60 -0
  77. data/lib/cassandra/protocol/responses/error_response.rb +50 -0
  78. data/lib/cassandra/protocol/responses/event_response.rb +39 -0
  79. data/lib/cassandra/protocol/responses/prepared_result_response.rb +64 -0
  80. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +43 -0
  81. data/lib/cassandra/protocol/responses/ready_response.rb +44 -0
  82. data/lib/cassandra/protocol/responses/result_response.rb +48 -0
  83. data/lib/cassandra/protocol/responses/rows_result_response.rb +139 -0
  84. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +60 -0
  85. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +57 -0
  86. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +42 -0
  87. data/lib/cassandra/protocol/responses/status_change_event_response.rb +44 -0
  88. data/lib/cassandra/protocol/responses/supported_response.rb +41 -0
  89. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +34 -0
  90. data/lib/cassandra/protocol/responses/void_result_response.rb +39 -0
  91. data/lib/cassandra/protocol/type_converter.rb +384 -0
  92. data/lib/cassandra/protocol.rb +93 -0
  93. data/lib/cassandra/reconnection/policies/constant.rb +48 -0
  94. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  95. data/lib/cassandra/reconnection/policies.rb +20 -0
  96. data/lib/cassandra/reconnection.rb +49 -0
  97. data/lib/cassandra/result.rb +215 -0
  98. data/lib/cassandra/retry/policies/default.rb +47 -0
  99. data/lib/cassandra/retry/policies/downgrading_consistency.rb +71 -0
  100. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  101. data/lib/cassandra/retry/policies.rb +21 -0
  102. data/lib/cassandra/retry.rb +142 -0
  103. data/lib/cassandra/session.rb +202 -0
  104. data/lib/cassandra/statement.rb +22 -0
  105. data/lib/cassandra/statements/batch.rb +95 -0
  106. data/lib/cassandra/statements/bound.rb +48 -0
  107. data/lib/cassandra/statements/prepared.rb +81 -0
  108. data/lib/cassandra/statements/simple.rb +58 -0
  109. data/lib/cassandra/statements/void.rb +33 -0
  110. data/lib/cassandra/statements.rb +23 -0
  111. data/lib/cassandra/table.rb +299 -0
  112. data/lib/cassandra/time_uuid.rb +142 -0
  113. data/lib/cassandra/util.rb +167 -0
  114. data/lib/cassandra/uuid.rb +104 -0
  115. data/lib/cassandra/version.rb +21 -0
  116. data/lib/cassandra.rb +428 -0
  117. data/lib/cassandra_murmur3.jar +0 -0
  118. metadata +211 -0
@@ -0,0 +1,591 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ module Client
21
+ # A CQL client manages connections to one or more Cassandra nodes and you use
22
+ # it run queries, insert and update data, prepare statements and switch
23
+ # keyspaces.
24
+ #
25
+ # To get a reference to a client you call {Cassandra::Client.connect}. When you
26
+ # don't need the client anymore you can call {#close} to close all connections.
27
+ #
28
+ # Internally the client runs an IO reactor in a background thread. The reactor
29
+ # handles all IO and manages the connections to the Cassandra nodes. This
30
+ # makes it possible for the client to handle highly concurrent applications
31
+ # very efficiently.
32
+ #
33
+ # Client instances are threadsafe and you only need a single instance for in
34
+ # an application. Using multiple instances is more likely to lead to worse
35
+ # performance than better.
36
+ #
37
+ # Because the client opens sockets, and runs threads it cannot be used by
38
+ # the child created when forking a process. If your application forks (for
39
+ # example applications running in the Unicorn application server or Resque
40
+ # task queue) you _must_ connect after forking.
41
+ #
42
+ # @see Cassandra::Client.connect
43
+ class Client
44
+ # @!method close
45
+ #
46
+ # Disconnect from all nodes.
47
+ #
48
+ # @return [Cassandra::Client]
49
+
50
+ # @!method connected?
51
+ #
52
+ # Returns whether or not the client is connected.
53
+ #
54
+ # @return [true, false]
55
+
56
+ # @!method keyspace
57
+ #
58
+ # Returns the name of the current keyspace, or `nil` if no keyspace has been
59
+ # set yet.
60
+ #
61
+ # @return [String]
62
+
63
+ # @!method use(keyspace)
64
+ #
65
+ # Changes keyspace by sending a `USE` statement to all connections.
66
+ #
67
+ # The the second parameter is meant for internal use only.
68
+ #
69
+ # @param [String] keyspace
70
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
71
+ # @return [nil]
72
+
73
+ # @!method execute(cql, *values, options={})
74
+ #
75
+ # Execute a CQL statement, optionally passing bound values.
76
+ #
77
+ # When passing bound values the request encoder will have to guess what
78
+ # types to encode the values as. For most types this will be no problem,
79
+ # but for integers and floating point numbers the larger size will be
80
+ # chosen (e.g. `BIGINT` and `DOUBLE` and not `INT` and `FLOAT`). You can
81
+ # override the guessing with the `:type_hint` option. Don't use on-the-fly
82
+ # bound values when you will issue the request multiple times, prepared
83
+ # statements are almost always a better choice.
84
+ #
85
+ # _Please note that on-the-fly bound values are only supported by Cassandra
86
+ # 2.0 and above._
87
+ #
88
+ # @example A simple CQL query
89
+ # result = client.execute("SELECT * FROM users WHERE user_name = 'sue'")
90
+ # result.each do |row|
91
+ # p row
92
+ # end
93
+ #
94
+ # @example Using on-the-fly bound values
95
+ # client.execute('INSERT INTO users (user_name, full_name) VALUES (?, ?)', 'sue', 'Sue Smith')
96
+ #
97
+ # @example Using on-the-fly bound values with type hints
98
+ # client.execute('INSERT INTO users (user_name, age) VALUES (?, ?)', 'sue', 33, type_hints: [nil, :int])
99
+ #
100
+ # @example Specifying the consistency as a symbol
101
+ # client.execute("UPDATE users SET full_name = 'Sue S. Smith' WHERE user_name = 'sue'", consistency: :one)
102
+ #
103
+ # @example Specifying the consistency and other options
104
+ # client.execute("SELECT * FROM users", consistency: :all, timeout: 1.5)
105
+ #
106
+ # @example Loading a big result page by page
107
+ # result_page = client.execute("SELECT * FROM large_table WHERE id = 'partition_with_lots_of_data'", page_size: 100)
108
+ # while result_page
109
+ # result_page.each do |row|
110
+ # p row
111
+ # end
112
+ # result_page = result_page.next_page
113
+ # end
114
+ #
115
+ # @example Activating tracing for a query
116
+ # result = client.execute("SELECT * FROM users", tracing: true)
117
+ # p result.trace_id
118
+ #
119
+ # @param [String] cql
120
+ # @param [Array] values Values to bind to any binding markers in the
121
+ # query (i.e. "?" placeholders) -- using this feature is similar to
122
+ # using a prepared statement, but without the type checking. The client
123
+ # needs to guess which data types to encode the values as, and will err
124
+ # on the side of caution, using types like BIGINT instead of INT for
125
+ # integers, and DOUBLE instead of FLOAT for floating point numbers. It
126
+ # is not recommended to use this feature for anything but convenience,
127
+ # and the algorithm used to guess types is to be considered experimental.
128
+ # @param [Hash] options
129
+ # @option options [Symbol] :consistency (:quorum) The
130
+ # consistency to use for this query.
131
+ # @option options [Symbol] :serial_consistency (nil) The
132
+ # consistency to use for conditional updates (`:serial` or
133
+ # `:local_serial`), see the CQL documentation for the semantics of
134
+ # serial consistencies and conditional updates. The default is assumed
135
+ # to be `:serial` by the server if none is specified. Ignored for non-
136
+ # conditional queries.
137
+ # @option options [Integer] :timeout (nil) How long to wait
138
+ # for a response. If this timeout expires a {Cassandra::TimeoutError} will
139
+ # be raised.
140
+ # @option options [Boolean] :trace (false) Request tracing
141
+ # for this request. See {Cassandra::Client::QueryResult} and
142
+ # {Cassandra::Client::VoidResult} for how to retrieve the tracing data.
143
+ # @option options [Integer] :page_size (nil) Instead of
144
+ # returning all rows, return the response in pages of this size. The
145
+ # first result will contain the first page, to load subsequent pages
146
+ # use {Cassandra::Client::QueryResult#next_page}.
147
+ # @option options [Array] :type_hints (nil) When passing
148
+ # on-the-fly bound values the request encoder will have to guess what
149
+ # types to encode the values as. Using this option you can give it hints
150
+ # and avoid it guessing wrong. The hints must be an array that has the
151
+ # same number of arguments as the number of bound values, and each
152
+ # element should be the type of the corresponding value, or nil if you
153
+ # prefer the encoder to guess. The types should be provided as lower
154
+ # case symbols, e.g. `:int`, `:time_uuid`, etc.
155
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
156
+ # @raise [Cassandra::TimeoutError] raised when a timeout was specified and no
157
+ # response was received within the timeout.
158
+ # @raise [Cassandra::Errors::QueryError] raised when the CQL has syntax errors or for
159
+ # other situations when the server complains.
160
+ # @return [nil, Cassandra::Client::QueryResult, Cassandra::Client::VoidResult] Some
161
+ # queries have no result and return `nil`, but `SELECT` statements
162
+ # return an `Enumerable` of rows (see {Cassandra::Client::QueryResult}), and
163
+ # `INSERT` and `UPDATE` return a similar type
164
+ # (see {Cassandra::Client::VoidResult}).
165
+
166
+ # @!method prepare(cql)
167
+ #
168
+ # Returns a prepared statement that can be run over and over again with
169
+ # different bound values.
170
+ #
171
+ # @see Cassandra::Client::PreparedStatement
172
+ # @param [String] cql The CQL to prepare
173
+ # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
174
+ # @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
175
+ # if the server suddenly closes the connection
176
+ # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server
177
+ # side, for example when you specify a malformed CQL query
178
+ # @return [Cassandra::Client::PreparedStatement] an object encapsulating the
179
+ # prepared statement
180
+
181
+ # @!method batch(type=:logged, options={})
182
+ #
183
+ # Yields a batch when called with a block. The batch is automatically
184
+ # executed at the end of the block and the result is returned.
185
+ #
186
+ # Returns a batch when called wihtout a block. The batch will remember
187
+ # the options given and merge these with any additional options given
188
+ # when {Cassandra::Client::Batch#execute} is called.
189
+ #
190
+ # Please note that the batch object returned by this method _is not thread
191
+ # safe_.
192
+ #
193
+ # The type parameter can be ommitted and the options can then be given
194
+ # as first parameter.
195
+ #
196
+ # @example Executing queries in a batch
197
+ # client.batch do |batch|
198
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (1234, NOW(), 23423)))
199
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2346, NOW(), 13)))
200
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (2342, NOW(), 2367)))
201
+ # batch.add(%(INSERT INTO metrics (id, time, value) VALUES (4562, NOW(), 1231)))
202
+ # end
203
+ #
204
+ # @example Using the returned batch object
205
+ # batch = client.batch(:counter, trace: true)
206
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, 87654)
207
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 3, 6572)
208
+ # result = batch.execute(timeout: 10)
209
+ # puts result.trace_id
210
+ #
211
+ # @example Providing type hints for on-the-fly bound values
212
+ # batch = client.batch
213
+ # batch.add('UPDATE counts SET value = value + ? WHERE id = ?', 4, type_hints: [:int])
214
+ # batch.execute
215
+ #
216
+ # @see Cassandra::Client::Batch
217
+ # @param [Symbol] type the type of batch, must be one of `:logged`,
218
+ # `:unlogged` and `:counter`. The precise meaning of these is defined
219
+ # in the CQL specification.
220
+ # @yieldparam [Cassandra::Client::Batch] batch the batch
221
+ # @return [Cassandra::Client::VoidResult, Cassandra::Client::Batch] when no block is
222
+ # given the batch is returned, when a block is given the result of
223
+ # executing the batch is returned (see {Cassandra::Client::Batch#execute}).
224
+ end
225
+
226
+ # @private
227
+ class AsynchronousClient < Client
228
+ def initialize(options={})
229
+ @compressor = options[:compressor]
230
+ @cql_version = options[:cql_version]
231
+ @logger = options[:logger] || NullLogger.new
232
+ @protocol_version = options[:protocol_version] || 2
233
+ @io_reactor = options[:io_reactor] || Io::IoReactor.new
234
+ @hosts = extract_hosts(options)
235
+ @initial_keyspace = options[:keyspace]
236
+ @connections_per_node = options[:connections_per_node] || 1
237
+ @lock = Mutex.new
238
+ @request_runner = RequestRunner.new
239
+ @connection_manager = ConnectionManager.new
240
+ @execute_options_decoder = ExecuteOptionsDecoder.new(options[:default_consistency] || DEFAULT_CONSISTENCY)
241
+ @port = options[:port] || DEFAULT_PORT
242
+ @connection_timeout = options[:connection_timeout] || DEFAULT_CONNECTION_TIMEOUT
243
+ @credentials = options[:credentials]
244
+ @auth_provider = options[:auth_provider] || @credentials && Auth::Providers::Password.new(*@credentials.values_at(:username, :password))
245
+ @connected = false
246
+ @connecting = false
247
+ @closing = false
248
+ end
249
+
250
+ def connect
251
+ @lock.synchronize do
252
+ raise Errors::ClientError, 'Cannot connect a closed client' if @closing || @closed
253
+ return @connected_future if can_execute?
254
+ @connecting = true
255
+ @connected_future = begin
256
+ f = @io_reactor.start
257
+ f = f.flat_map { connect_with_protocol_version_fallback }
258
+ f = f.flat_map { |connections| connect_to_all_peers(connections) }
259
+ f = f.flat_map do |connections|
260
+ @connection_manager.add_connections(connections)
261
+ register_event_listener(@connection_manager.random_connection)
262
+ end
263
+ f = f.flat_map { use_keyspace(@connection_manager.snapshot, @initial_keyspace) }
264
+ f.map(self)
265
+ end
266
+ end
267
+ @connected_future.on_complete(&method(:connected))
268
+ @connected_future
269
+ end
270
+
271
+ def close
272
+ @lock.synchronize do
273
+ return @closed_future if @closing
274
+ @closing = true
275
+ @closed_future = begin
276
+ if @connecting
277
+ f = @connected_future.recover
278
+ f = f.flat_map { @io_reactor.stop }
279
+ f = f.map(self)
280
+ f
281
+ else
282
+ f = @io_reactor.stop
283
+ f = f.map(self)
284
+ f
285
+ end
286
+ end
287
+ end
288
+ @closed_future.on_complete(&method(:closed))
289
+ @closed_future
290
+ end
291
+
292
+ def connected?
293
+ @connected
294
+ end
295
+
296
+ def keyspace
297
+ @connection_manager.random_connection.keyspace
298
+ end
299
+
300
+ def use(keyspace)
301
+ with_failure_handler do
302
+ connections = @connection_manager.reject { |c| c.keyspace == keyspace }
303
+ return Ione::Future.resolved if connections.empty?
304
+ use_keyspace(connections, keyspace).map(nil)
305
+ end
306
+ end
307
+
308
+ def execute(cql, *args)
309
+ with_failure_handler do
310
+ options_or_consistency = nil
311
+ if args.last.is_a?(Symbol) || args.last.is_a?(Hash)
312
+ options_or_consistency = args.pop
313
+ end
314
+ options = @execute_options_decoder.decode_options(options_or_consistency)
315
+ request = Protocol::QueryRequest.new(cql, args, options[:type_hints], options[:consistency], options[:serial_consistency], options[:page_size], options[:paging_state], options[:trace])
316
+ f = execute_request(request, options[:timeout])
317
+ if options.include?(:page_size)
318
+ f = f.map { |result| AsynchronousQueryPagedQueryResult.new(self, request, result, options) }
319
+ end
320
+ f
321
+ end
322
+ end
323
+
324
+ def prepare(cql)
325
+ with_failure_handler do
326
+ AsynchronousPreparedStatement.prepare(cql, @execute_options_decoder, @connection_manager, @logger)
327
+ end
328
+ end
329
+
330
+ def batch(type=:logged, options=nil)
331
+ if type.is_a?(Hash)
332
+ options = type
333
+ type = :logged
334
+ end
335
+ b = AsynchronousBatch.new(type, @execute_options_decoder, @connection_manager, options)
336
+ if block_given?
337
+ yield b
338
+ b.execute
339
+ else
340
+ b
341
+ end
342
+ end
343
+
344
+ private
345
+
346
+ DEFAULT_CQL_VERSIONS = {1 => '3.0.0'}
347
+ DEFAULT_CQL_VERSIONS.default = '3.1.0'
348
+ DEFAULT_CQL_VERSIONS.freeze
349
+ DEFAULT_CONSISTENCY = :quorum
350
+ DEFAULT_PORT = 9042
351
+ DEFAULT_CONNECTION_TIMEOUT = 10
352
+ MAX_RECONNECTION_ATTEMPTS = 5
353
+
354
+ def extract_hosts(options)
355
+ if options[:hosts] && options[:hosts].any?
356
+ options[:hosts].uniq
357
+ elsif options[:host]
358
+ options[:host].split(',').uniq
359
+ else
360
+ %w[localhost]
361
+ end
362
+ end
363
+
364
+ def create_cluster_connector
365
+ cql_version = @cql_version || DEFAULT_CQL_VERSIONS[@protocol_version]
366
+ authentication_step = @protocol_version == 1 ? CredentialsAuthenticationStep.new(@credentials) : SaslAuthenticationStep.new(@auth_provider)
367
+ protocol_handler_factory = lambda { |connection| Protocol::CqlProtocolHandler.new(connection, @io_reactor, @protocol_version, @compressor) }
368
+ ClusterConnector.new(
369
+ Connector.new([
370
+ ConnectStep.new(@io_reactor, protocol_handler_factory, @port, @connection_timeout, @logger),
371
+ CacheOptionsStep.new,
372
+ InitializeStep.new(@compressor, @logger),
373
+ authentication_step,
374
+ CachePropertiesStep.new,
375
+ ]),
376
+ @logger
377
+ )
378
+ end
379
+
380
+ def connect_with_protocol_version_fallback
381
+ f = create_cluster_connector.connect_all(@hosts, @connections_per_node)
382
+ f.fallback do |error|
383
+ if error.is_a?(Errors::QueryError) && error.code == 0x0a && @protocol_version > 1
384
+ @logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@protocol_version, @protocol_version - 1, error.message])
385
+ @protocol_version -= 1
386
+ connect_with_protocol_version_fallback
387
+ else
388
+ raise error
389
+ end
390
+ end
391
+ end
392
+
393
+ def connect_to_all_peers(seed_connections, initial_keyspace=@initial_keyspace)
394
+ @logger.debug('Looking for additional nodes')
395
+ peer_discovery = PeerDiscovery.new(seed_connections)
396
+ peer_discovery.new_hosts.flat_map do |hosts|
397
+ if hosts.empty?
398
+ @logger.debug('No additional nodes found')
399
+ Ione::Future.resolved(seed_connections)
400
+ else
401
+ @logger.debug('%d additional nodes found' % hosts.size)
402
+ f = create_cluster_connector.connect_all(hosts, @connections_per_node)
403
+ f = f.map do |discovered_connections|
404
+ seed_connections + discovered_connections
405
+ end
406
+ f.recover(seed_connections)
407
+ end
408
+ end
409
+ end
410
+
411
+ def connected(f)
412
+ if f.resolved?
413
+ @lock.synchronize do
414
+ @connecting = false
415
+ @connected = true
416
+ end
417
+ @logger.info('Cluster connection complete')
418
+ else
419
+ @lock.synchronize do
420
+ @connecting = false
421
+ @connected = false
422
+ end
423
+ f.on_failure do |e|
424
+ @logger.error('Failed connecting to cluster: %s' % e.message)
425
+ end
426
+ close
427
+ end
428
+ end
429
+
430
+ def closed(f)
431
+ @lock.synchronize do
432
+ @closing = false
433
+ @closed = true
434
+ @connected = false
435
+ if f.resolved?
436
+ @logger.info('Cluster disconnect complete')
437
+ else
438
+ f.on_failure do |e|
439
+ @logger.error('Cluster disconnect failed: %s' % e.message)
440
+ end
441
+ end
442
+ end
443
+ end
444
+
445
+ def can_execute?
446
+ !@closing && (@connecting || (@connected && @connection_manager.connected?))
447
+ end
448
+
449
+ def with_failure_handler
450
+ return Ione::Future.failed(Errors::NotConnectedError.new) unless can_execute?
451
+ yield
452
+ rescue => e
453
+ Ione::Future.failed(e)
454
+ end
455
+
456
+ def use_keyspace(connections, keyspace)
457
+ return Ione::Future.resolved(connections) unless keyspace
458
+ return Ione::Future.failed(InvalidKeyspaceNameError.new(%("#{keyspace}" is not a valid keyspace name))) unless keyspace =~ /^\w[\w\d_]*$|^"\w[\w\d_]*"$/
459
+
460
+ futures = connections.map do |connection|
461
+ request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
462
+ @request_runner.execute(connection, request).map(connection)
463
+ end
464
+ Ione::Future.all(*futures)
465
+ end
466
+
467
+ def register_event_listener(connection)
468
+ register_request = Protocol::RegisterRequest.new(Protocol::TopologyChangeEventResponse::TYPE, Protocol::StatusChangeEventResponse::TYPE)
469
+ f = execute_request(register_request, nil, connection)
470
+ f.on_value do
471
+ connection.on_closed do
472
+ if connected?
473
+ begin
474
+ register_event_listener(@connection_manager.random_connection)
475
+ rescue Errors::NotConnectedError
476
+ # we had started closing down after the connection check
477
+ end
478
+ end
479
+ end
480
+ connection.on_event do |event|
481
+ if event.change == 'UP' || event.change == 'NEW_NODE'
482
+ @logger.debug('Received %s event' % event.change)
483
+ unless @looking_for_nodes
484
+ @looking_for_nodes = true
485
+ handle_topology_change.on_complete do |f|
486
+ @looking_for_nodes = false
487
+ end
488
+ end
489
+ end
490
+ end
491
+ end
492
+ f
493
+ end
494
+
495
+ def handle_topology_change(remaning_attempts=MAX_RECONNECTION_ATTEMPTS)
496
+ with_failure_handler do
497
+ seed_connections = @connection_manager.snapshot
498
+ f = connect_to_all_peers(seed_connections, keyspace)
499
+ f.flat_map do |all_connections|
500
+ new_connections = all_connections - seed_connections
501
+ if new_connections.size > 0
502
+ f = use_keyspace(new_connections, keyspace)
503
+ f.on_value do
504
+ @connection_manager.add_connections(new_connections)
505
+ end
506
+ f
507
+ elsif remaning_attempts > 0
508
+ timeout = 2**(MAX_RECONNECTION_ATTEMPTS - remaning_attempts)
509
+ @logger.debug('Scheduling new peer discovery in %ds' % timeout)
510
+ f = @io_reactor.schedule_timer(timeout)
511
+ f.flat_map do
512
+ handle_topology_change(remaning_attempts - 1)
513
+ end
514
+ else
515
+ @logger.warn('Giving up looking for additional nodes')
516
+ Ione::Future.resolved
517
+ end
518
+ end
519
+ end
520
+ end
521
+
522
+ def execute_request(request, timeout=nil, connection=nil)
523
+ f = @request_runner.execute(connection || @connection_manager.random_connection, request, timeout)
524
+ f.map do |result|
525
+ if result.is_a?(KeyspaceChanged)
526
+ use(result.keyspace)
527
+ nil
528
+ else
529
+ result
530
+ end
531
+ end
532
+ end
533
+ end
534
+
535
+ # @private
536
+ class SynchronousClient < Client
537
+ include SynchronousBacktrace
538
+
539
+ def initialize(async_client)
540
+ @async_client = async_client
541
+ end
542
+
543
+ def connect
544
+ synchronous_backtrace { @async_client.connect.value }
545
+ self
546
+ end
547
+
548
+ def close
549
+ synchronous_backtrace { @async_client.close.value }
550
+ self
551
+ end
552
+
553
+ def connected?
554
+ @async_client.connected?
555
+ end
556
+
557
+ def keyspace
558
+ @async_client.keyspace
559
+ end
560
+
561
+ def use(keyspace)
562
+ synchronous_backtrace { @async_client.use(keyspace).value }
563
+ end
564
+
565
+ def execute(cql, *args)
566
+ synchronous_backtrace do
567
+ result = @async_client.execute(cql, *args).value
568
+ result = SynchronousPagedQueryResult.new(result) if result.is_a?(PagedQueryResult)
569
+ result
570
+ end
571
+ end
572
+
573
+ def prepare(cql)
574
+ async_statement = synchronous_backtrace { @async_client.prepare(cql).value }
575
+ SynchronousPreparedStatement.new(async_statement)
576
+ end
577
+
578
+ def batch(type=:logged, options={}, &block)
579
+ if block_given?
580
+ synchronous_backtrace { @async_client.batch(type, options, &block).value }
581
+ else
582
+ SynchronousBatch.new(@async_client.batch(type, options))
583
+ end
584
+ end
585
+
586
+ def async
587
+ @async_client
588
+ end
589
+ end
590
+ end
591
+ end
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ module Client
21
+ # Represents metadata about a column in a query result set or prepared
22
+ # statement. Apart from the keyspace, table and column names there's also
23
+ # the type as a symbol (e.g. `:varchar`, `:int`, `:date`).
24
+ class ColumnMetadata
25
+ attr_reader :keyspace, :table, :column_name, :type
26
+
27
+ # @private
28
+ def initialize(*args)
29
+ @keyspace, @table, @column_name, @type = args
30
+ end
31
+
32
+ # @private
33
+ def to_ary
34
+ [@keyspace, @table, @column_name, @type]
35
+ end
36
+
37
+ def eql?(other)
38
+ self.keyspace == other.keyspace && self.table == other.table && self.column_name == other.column_name && self.type == other.type
39
+ end
40
+ alias_method :==, :eql?
41
+
42
+ def hash
43
+ @h ||= begin
44
+ h = 0
45
+ h = ((h & 33554431) * 31) ^ @keyspace.hash
46
+ h = ((h & 33554431) * 31) ^ @table.hash
47
+ h = ((h & 33554431) * 31) ^ @column_name.hash
48
+ h = ((h & 33554431) * 31) ^ @type.hash
49
+ h
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end