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.
- checksums.yaml +7 -0
- data/.yardopts +4 -0
- data/README.md +125 -0
- data/lib/cassandra/auth/providers/password.rb +73 -0
- data/lib/cassandra/auth/providers.rb +16 -0
- data/lib/cassandra/auth.rb +97 -0
- data/lib/cassandra/client/batch.rb +212 -0
- data/lib/cassandra/client/client.rb +591 -0
- data/lib/cassandra/client/column_metadata.rb +54 -0
- data/lib/cassandra/client/connection_manager.rb +72 -0
- data/lib/cassandra/client/connector.rb +277 -0
- data/lib/cassandra/client/execute_options_decoder.rb +59 -0
- data/lib/cassandra/client/null_logger.rb +37 -0
- data/lib/cassandra/client/peer_discovery.rb +50 -0
- data/lib/cassandra/client/prepared_statement.rb +314 -0
- data/lib/cassandra/client/query_result.rb +230 -0
- data/lib/cassandra/client/request_runner.rb +71 -0
- data/lib/cassandra/client/result_metadata.rb +48 -0
- data/lib/cassandra/client/void_result.rb +78 -0
- data/lib/cassandra/client.rb +144 -0
- data/lib/cassandra/cluster/client.rb +768 -0
- data/lib/cassandra/cluster/connector.rb +244 -0
- data/lib/cassandra/cluster/control_connection.rb +425 -0
- data/lib/cassandra/cluster/metadata.rb +124 -0
- data/lib/cassandra/cluster/options.rb +42 -0
- data/lib/cassandra/cluster/registry.rb +198 -0
- data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +47 -0
- data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
- data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
- data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
- data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +92 -0
- data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
- data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
- data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
- data/lib/cassandra/cluster/schema/type_parser.rb +138 -0
- data/lib/cassandra/cluster/schema.rb +340 -0
- data/lib/cassandra/cluster.rb +215 -0
- data/lib/cassandra/column.rb +92 -0
- data/lib/cassandra/compression/compressors/lz4.rb +72 -0
- data/lib/cassandra/compression/compressors/snappy.rb +66 -0
- data/lib/cassandra/compression.rb +66 -0
- data/lib/cassandra/driver.rb +111 -0
- data/lib/cassandra/errors.rb +79 -0
- data/lib/cassandra/execution/info.rb +51 -0
- data/lib/cassandra/execution/options.rb +80 -0
- data/lib/cassandra/execution/trace.rb +152 -0
- data/lib/cassandra/future.rb +675 -0
- data/lib/cassandra/host.rb +79 -0
- data/lib/cassandra/keyspace.rb +133 -0
- data/lib/cassandra/listener.rb +87 -0
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +149 -0
- data/lib/cassandra/load_balancing/policies/round_robin.rb +132 -0
- data/lib/cassandra/load_balancing/policies/token_aware.rb +119 -0
- data/lib/cassandra/load_balancing/policies/white_list.rb +90 -0
- data/lib/cassandra/load_balancing/policies.rb +19 -0
- data/lib/cassandra/load_balancing.rb +113 -0
- data/lib/cassandra/protocol/cql_byte_buffer.rb +307 -0
- data/lib/cassandra/protocol/cql_protocol_handler.rb +323 -0
- data/lib/cassandra/protocol/frame_decoder.rb +128 -0
- data/lib/cassandra/protocol/frame_encoder.rb +48 -0
- data/lib/cassandra/protocol/request.rb +38 -0
- data/lib/cassandra/protocol/requests/auth_response_request.rb +47 -0
- data/lib/cassandra/protocol/requests/batch_request.rb +76 -0
- data/lib/cassandra/protocol/requests/credentials_request.rb +47 -0
- data/lib/cassandra/protocol/requests/execute_request.rb +103 -0
- data/lib/cassandra/protocol/requests/options_request.rb +39 -0
- data/lib/cassandra/protocol/requests/prepare_request.rb +50 -0
- data/lib/cassandra/protocol/requests/query_request.rb +153 -0
- data/lib/cassandra/protocol/requests/register_request.rb +38 -0
- data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
- data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
- data/lib/cassandra/protocol/response.rb +38 -0
- data/lib/cassandra/protocol/responses/auth_challenge_response.rb +41 -0
- data/lib/cassandra/protocol/responses/auth_success_response.rb +41 -0
- data/lib/cassandra/protocol/responses/authenticate_response.rb +41 -0
- data/lib/cassandra/protocol/responses/detailed_error_response.rb +60 -0
- data/lib/cassandra/protocol/responses/error_response.rb +50 -0
- data/lib/cassandra/protocol/responses/event_response.rb +39 -0
- data/lib/cassandra/protocol/responses/prepared_result_response.rb +64 -0
- data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +43 -0
- data/lib/cassandra/protocol/responses/ready_response.rb +44 -0
- data/lib/cassandra/protocol/responses/result_response.rb +48 -0
- data/lib/cassandra/protocol/responses/rows_result_response.rb +139 -0
- data/lib/cassandra/protocol/responses/schema_change_event_response.rb +60 -0
- data/lib/cassandra/protocol/responses/schema_change_result_response.rb +57 -0
- data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +42 -0
- data/lib/cassandra/protocol/responses/status_change_event_response.rb +44 -0
- data/lib/cassandra/protocol/responses/supported_response.rb +41 -0
- data/lib/cassandra/protocol/responses/topology_change_event_response.rb +34 -0
- data/lib/cassandra/protocol/responses/void_result_response.rb +39 -0
- data/lib/cassandra/protocol/type_converter.rb +384 -0
- data/lib/cassandra/protocol.rb +93 -0
- data/lib/cassandra/reconnection/policies/constant.rb +48 -0
- data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
- data/lib/cassandra/reconnection/policies.rb +20 -0
- data/lib/cassandra/reconnection.rb +49 -0
- data/lib/cassandra/result.rb +215 -0
- data/lib/cassandra/retry/policies/default.rb +47 -0
- data/lib/cassandra/retry/policies/downgrading_consistency.rb +71 -0
- data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
- data/lib/cassandra/retry/policies.rb +21 -0
- data/lib/cassandra/retry.rb +142 -0
- data/lib/cassandra/session.rb +202 -0
- data/lib/cassandra/statement.rb +22 -0
- data/lib/cassandra/statements/batch.rb +95 -0
- data/lib/cassandra/statements/bound.rb +48 -0
- data/lib/cassandra/statements/prepared.rb +81 -0
- data/lib/cassandra/statements/simple.rb +58 -0
- data/lib/cassandra/statements/void.rb +33 -0
- data/lib/cassandra/statements.rb +23 -0
- data/lib/cassandra/table.rb +299 -0
- data/lib/cassandra/time_uuid.rb +142 -0
- data/lib/cassandra/util.rb +167 -0
- data/lib/cassandra/uuid.rb +104 -0
- data/lib/cassandra/version.rb +21 -0
- data/lib/cassandra.rb +428 -0
- data/lib/cassandra_murmur3.jar +0 -0
- 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
|