cassandra-driver 1.0.0.beta.2-java

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