cassandra-driver 1.0.0.beta.3 → 1.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +13 -5
  2. data/README.md +8 -6
  3. data/lib/cassandra.rb +99 -13
  4. data/lib/cassandra/address_resolution.rb +36 -0
  5. data/lib/cassandra/address_resolution/policies.rb +2 -0
  6. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +56 -0
  7. data/lib/cassandra/address_resolution/policies/none.rb +35 -0
  8. data/lib/cassandra/auth.rb +1 -1
  9. data/lib/cassandra/auth/providers/password.rb +1 -1
  10. data/lib/cassandra/client.rb +2 -2
  11. data/lib/cassandra/client/batch.rb +3 -3
  12. data/lib/cassandra/client/client.rb +12 -12
  13. data/lib/cassandra/client/connection_manager.rb +2 -2
  14. data/lib/cassandra/client/connector.rb +6 -10
  15. data/lib/cassandra/client/prepared_statement.rb +4 -4
  16. data/lib/cassandra/client/request_runner.rb +1 -2
  17. data/lib/cassandra/cluster.rb +15 -5
  18. data/lib/cassandra/cluster/client.rb +158 -96
  19. data/lib/cassandra/cluster/connector.rb +42 -27
  20. data/lib/cassandra/cluster/control_connection.rb +384 -132
  21. data/lib/cassandra/cluster/options.rb +5 -2
  22. data/lib/cassandra/cluster/registry.rb +19 -9
  23. data/lib/cassandra/compression.rb +1 -1
  24. data/lib/cassandra/compression/compressors/lz4.rb +1 -1
  25. data/lib/cassandra/compression/compressors/snappy.rb +1 -1
  26. data/lib/cassandra/driver.rb +28 -20
  27. data/lib/cassandra/errors.rb +325 -35
  28. data/lib/cassandra/future.rb +3 -3
  29. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +7 -3
  30. data/lib/cassandra/load_balancing/policies/token_aware.rb +1 -1
  31. data/lib/cassandra/protocol.rb +0 -16
  32. data/lib/cassandra/protocol/cql_byte_buffer.rb +18 -18
  33. data/lib/cassandra/protocol/cql_protocol_handler.rb +74 -8
  34. data/lib/cassandra/protocol/frame_decoder.rb +2 -2
  35. data/lib/cassandra/protocol/frame_encoder.rb +1 -1
  36. data/lib/cassandra/protocol/response.rb +1 -1
  37. data/lib/cassandra/protocol/responses/detailed_error_response.rb +16 -1
  38. data/lib/cassandra/protocol/responses/error_response.rb +17 -0
  39. data/lib/cassandra/protocol/responses/event_response.rb +1 -1
  40. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +1 -1
  41. data/lib/cassandra/protocol/responses/result_response.rb +1 -1
  42. data/lib/cassandra/protocol/responses/rows_result_response.rb +1 -1
  43. data/lib/cassandra/protocol/type_converter.rb +4 -3
  44. data/lib/cassandra/reconnection.rb +1 -1
  45. data/lib/cassandra/retry.rb +3 -5
  46. data/lib/cassandra/session.rb +11 -5
  47. data/lib/cassandra/table.rb +1 -1
  48. data/lib/cassandra/time_uuid.rb +21 -83
  49. data/lib/cassandra/util.rb +1 -1
  50. data/lib/cassandra/uuid.rb +6 -4
  51. data/lib/cassandra/uuid/generator.rb +207 -0
  52. data/lib/cassandra/version.rb +1 -1
  53. metadata +24 -19
@@ -22,7 +22,7 @@ module Cassandra
22
22
  # Auth provider used for Cassandra's built in authentication.
23
23
  #
24
24
  # @note No need to instantiate this class manually, use `:username` and
25
- # `:password` options when calling {Cassandra.connect} and one will be
25
+ # `:password` options when calling {Cassandra.cluster} and one will be
26
26
  # created automatically for you.
27
27
  class Password < Provider
28
28
  # Authenticator used for Cassandra's built in authentication,
@@ -118,9 +118,9 @@ module Cassandra
118
118
  # significant events pass an object implementing the standard Ruby logger
119
119
  # interface (e.g. quacks like `Logger` from the standard library) with
120
120
  # this option.
121
- # @raise Cassandra::Io::ConnectionError when a connection couldn't be established
121
+ # @raise [Cassandra::Errors::IOError] when a connection couldn't be established
122
122
  # to any node
123
- # @raise Cassandra::Errors::QueryError when the specified keyspace does not exist
123
+ # @raise [Cassandra::Errors::ExecutionError] when the specified keyspace does not exist
124
124
  # or when the specifed CQL version is not supported.
125
125
  # @return [Cassandra::Client::Client]
126
126
  def self.connect(options={})
@@ -54,7 +54,7 @@ module Cassandra
54
54
  # @param [Hash] options an options hash or a symbol (as a shortcut for
55
55
  # specifying the consistency), see {Cassandra::Client::Client#execute} for
56
56
  # full details about how this value is interpreted.
57
- # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
57
+ # @raise [Cassandra::Errors::ExecutionError] raised when there is an error on the server side
58
58
  # @raise [Cassandra::Errors::NotPreparedError] raised in the unlikely event that a
59
59
  # prepared statement was not prepared on the chosen connection
60
60
  # @return [Cassandra::Client::VoidResult] a batch always returns a void result
@@ -77,7 +77,7 @@ module Cassandra
77
77
  #
78
78
  # Execute the batch and return the result.
79
79
  #
80
- # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
80
+ # @raise [Cassandra::Errors::ExecutionError] raised when there is an error on the server side
81
81
  # @raise [Cassandra::Errors::NotPreparedError] raised in the unlikely event that a
82
82
  # prepared statement was not prepared on the chosen connection
83
83
  # @return [Cassandra::Client::VoidResult] a batch always returns a void result
@@ -86,7 +86,7 @@ module Cassandra
86
86
  # @private
87
87
  class AsynchronousBatch < Batch
88
88
  def initialize(type, execute_options_decoder, connection_manager, options=nil)
89
- raise ArgumentError, "Unknown batch type: #{type}" unless BATCH_TYPES.include?(type)
89
+ raise ::ArgumentError, "Unknown batch type: #{type}" unless BATCH_TYPES.include?(type)
90
90
  @type = type
91
91
  @execute_options_decoder = execute_options_decoder
92
92
  @connection_manager = connection_manager
@@ -67,7 +67,7 @@ module Cassandra
67
67
  # The the second parameter is meant for internal use only.
68
68
  #
69
69
  # @param [String] keyspace
70
- # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
70
+ # @raise [Cassandra::Errors::ClientError] raised when the client is not connected
71
71
  # @return [nil]
72
72
 
73
73
  # @!method execute(cql, *values, options={})
@@ -135,7 +135,7 @@ module Cassandra
135
135
  # to be `:serial` by the server if none is specified. Ignored for non-
136
136
  # conditional queries.
137
137
  # @option options [Integer] :timeout (nil) How long to wait
138
- # for a response. If this timeout expires a {Cassandra::TimeoutError} will
138
+ # for a response. If this timeout expires a {Cassandra::Errors::TimeoutError} will
139
139
  # be raised.
140
140
  # @option options [Boolean] :trace (false) Request tracing
141
141
  # for this request. See {Cassandra::Client::QueryResult} and
@@ -152,10 +152,10 @@ module Cassandra
152
152
  # element should be the type of the corresponding value, or nil if you
153
153
  # prefer the encoder to guess. The types should be provided as lower
154
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
155
+ # @raise [Cassandra::Errors::ClientError] raised when the client is not connected
156
+ # @raise [Cassandra::Errors::TimeoutError] raised when a timeout was specified and no
157
157
  # response was received within the timeout.
158
- # @raise [Cassandra::Errors::QueryError] raised when the CQL has syntax errors or for
158
+ # @raise [Cassandra::Errors::ExecutionError] raised when the CQL has syntax errors or for
159
159
  # other situations when the server complains.
160
160
  # @return [nil, Cassandra::Client::QueryResult, Cassandra::Client::VoidResult] Some
161
161
  # queries have no result and return `nil`, but `SELECT` statements
@@ -170,10 +170,10 @@ module Cassandra
170
170
  #
171
171
  # @see Cassandra::Client::PreparedStatement
172
172
  # @param [String] cql The CQL to prepare
173
- # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
173
+ # @raise [Cassandra::Errors::ClientError] raised when the client is not connected
174
174
  # @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
175
175
  # if the server suddenly closes the connection
176
- # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server
176
+ # @raise [Cassandra::Errors::ExecutionError] raised when there is an error on the server
177
177
  # side, for example when you specify a malformed CQL query
178
178
  # @return [Cassandra::Client::PreparedStatement] an object encapsulating the
179
179
  # prepared statement
@@ -230,7 +230,7 @@ module Cassandra
230
230
  @cql_version = options[:cql_version]
231
231
  @logger = options[:logger] || NullLogger.new
232
232
  @protocol_version = options[:protocol_version] || 2
233
- @io_reactor = options[:io_reactor] || Io::IoReactor.new
233
+ @io_reactor = options[:io_reactor] || Ione::Io::IoReactor.new
234
234
  @hosts = extract_hosts(options)
235
235
  @initial_keyspace = options[:keyspace]
236
236
  @connections_per_node = options[:connections_per_node] || 1
@@ -380,12 +380,12 @@ module Cassandra
380
380
  def connect_with_protocol_version_fallback
381
381
  f = create_cluster_connector.connect_all(@hosts, @connections_per_node)
382
382
  f.fallback do |error|
383
- if error.is_a?(Errors::QueryError) && error.code == 0x0a && @protocol_version > 1
383
+ if error.is_a?(Errors::ProtocolError) && @protocol_version > 1
384
384
  @logger.warn('Could not connect using protocol version %d (will try again with %d): %s' % [@protocol_version, @protocol_version - 1, error.message])
385
385
  @protocol_version -= 1
386
386
  connect_with_protocol_version_fallback
387
387
  else
388
- raise error
388
+ raise(error, error.message, error.backtrace)
389
389
  end
390
390
  end
391
391
  end
@@ -447,7 +447,7 @@ module Cassandra
447
447
  end
448
448
 
449
449
  def with_failure_handler
450
- return Ione::Future.failed(Errors::NotConnectedError.new) unless can_execute?
450
+ return Ione::Future.failed(Errors::ClientError.new) unless can_execute?
451
451
  yield
452
452
  rescue => e
453
453
  Ione::Future.failed(e)
@@ -472,7 +472,7 @@ module Cassandra
472
472
  if connected?
473
473
  begin
474
474
  register_event_listener(@connection_manager.random_connection)
475
- rescue Errors::NotConnectedError
475
+ rescue Errors::IOError
476
476
  # we had started closing down after the connection check
477
477
  end
478
478
  end
@@ -53,7 +53,7 @@ module Cassandra
53
53
  end
54
54
 
55
55
  def random_connection
56
- raise Errors::NotConnectedError unless connected?
56
+ raise Errors::IOError, 'Not connected' unless connected?
57
57
  @lock.synchronize do
58
58
  @connections.sample
59
59
  end
@@ -61,7 +61,7 @@ module Cassandra
61
61
 
62
62
  def each_connection(&callback)
63
63
  return self unless block_given?
64
- raise Errors::NotConnectedError unless connected?
64
+ raise Errors::IOError, 'Not connected' unless connected?
65
65
  @lock.synchronize do
66
66
  @connections.each(&callback)
67
67
  end
@@ -39,11 +39,8 @@ module Cassandra
39
39
  Ione::Future.all(*connections).map do |connections|
40
40
  connected_connections = connections.select(&:connected?)
41
41
  if connected_connections.empty?
42
- e = connections.first.error
43
- if e.is_a?(Cassandra::Errors::QueryError) && e.code == 0x100
44
- e = Errors::AuthenticationError.new(e.message)
45
- end
46
- raise e
42
+ error = connections.first.error
43
+ raise(error, error.message, error.backtrace)
47
44
  end
48
45
  connected_connections
49
46
  end
@@ -131,10 +128,9 @@ module Cassandra
131
128
  compression = @compressor && @compressor.algorithm
132
129
  supported_algorithms = pending_connection[:compression]
133
130
  if compression && !supported_algorithms.include?(compression)
134
- @logger.warn(%[Compression algorithm "#{compression}" not supported (server supports "#{supported_algorithms.join('", "')}")])
131
+ @logger.info("Compression with #{compression.inspect} is not supported by host at #{pending_connection.host}, supported algorithms are #{supported_algorithms.inspect}")
135
132
  compression = nil
136
133
  end
137
- @logger.debug('Using "%s" compression' % compression) if compression
138
134
  supported_cql_versions = pending_connection[:cql_version]
139
135
  cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ? supported_cql_versions.first : '3.1.0'
140
136
 
@@ -165,10 +161,10 @@ module Cassandra
165
161
  elsif @auth_provider
166
162
  Ione::Future.failed(Errors::AuthenticationError.new('Auth provider does not support the required authentication class "%s" and/or protocol version %d' % [pending_connection.authentication_class, @protocol_version]))
167
163
  else
168
- Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but no auth provider found'))
164
+ Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
169
165
  end
170
166
  rescue => e
171
- Ione::Future.failed(Errors::AuthenticationError.new('Auth provider raised an error: %s' % e.message))
167
+ Ione::Future.failed(Errors::AuthenticationError.new('Auth provider error (%s: %s)' % [e.class.name, e.message]))
172
168
  end
173
169
  else
174
170
  Ione::Future.resolved(pending_connection)
@@ -205,7 +201,7 @@ module Cassandra
205
201
  request = Protocol::CredentialsRequest.new(@credentials)
206
202
  pending_connection.execute(request).map(pending_connection)
207
203
  else
208
- Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but no credentials provided'))
204
+ Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate'))
209
205
  end
210
206
  else
211
207
  Ione::Future.resolved(pending_connection)
@@ -90,7 +90,7 @@ module Cassandra
90
90
  # for details on which options are available.
91
91
  # @raise [ArgumentError] raised when number of argument does not match
92
92
  # the number of parameters needed to be bound to the statement.
93
- # @raise [Cassandra::Errors::NotConnectedError] raised when the client is not connected
93
+ # @raise [Cassandra::Errors::ClientError] raised when the client is not connected
94
94
  # @raise [Cassandra::Errors::IoError] raised when there is an IO error, for example
95
95
  # if the server suddenly closes the connection
96
96
  # @raise [Cassandra::Errors::QueryError] raised when there is an error on the server side
@@ -221,10 +221,10 @@ module Cassandra
221
221
  def add_to_batch(batch, connection, bound_args)
222
222
  statement_id = connection[self]
223
223
  unless statement_id
224
- raise Errors::NotPreparedError
224
+ raise ::ArgumentError, "Statement was not prepared"
225
225
  end
226
226
  unless bound_args.size == @raw_metadata.size
227
- raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
227
+ raise ::ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
228
228
  end
229
229
  batch.add_prepared(statement_id, @raw_metadata, bound_args)
230
230
  end
@@ -234,7 +234,7 @@ module Cassandra
234
234
  def run(args, connection)
235
235
  bound_args = args.shift(@raw_metadata.size)
236
236
  unless bound_args.size == @raw_metadata.size && args.size <= 1
237
- raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
237
+ raise ::ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
238
238
  end
239
239
  options = @execute_options_decoder.decode_options(args.last)
240
240
  statement_id = connection[self]
@@ -31,8 +31,7 @@ module Cassandra
31
31
  response.trace_id ? VoidResult.new(response.trace_id) : VoidResult::INSTANCE
32
32
  when Protocol::ErrorResponse
33
33
  cql = request.is_a?(Protocol::QueryRequest) ? request.cql : nil
34
- details = response.respond_to?(:details) ? response.details : nil
35
- raise Errors::QueryError.new(response.code, response.message, cql, details)
34
+ raise response.to_error(cql)
36
35
  when Protocol::SetKeyspaceResultResponse
37
36
  KeyspaceChanged.new(response.keyspace)
38
37
  when Protocol::AuthenticateResponse
@@ -63,7 +63,7 @@ module Cassandra
63
63
  def_delegators :@metadata, :name, :find_replicas
64
64
 
65
65
  # @private
66
- def initialize(logger, io_reactor, control_connection, cluster_registry, cluster_schema, cluster_metadata, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, connector, futures_factory)
66
+ def initialize(logger, io_reactor, control_connection, cluster_registry, cluster_schema, cluster_metadata, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, address_resolution_policy, connector, futures_factory)
67
67
  @logger = logger
68
68
  @io_reactor = io_reactor
69
69
  @control_connection = control_connection
@@ -75,6 +75,7 @@ module Cassandra
75
75
  @load_balancing_policy = load_balancing_policy
76
76
  @reconnection_policy = reconnection_policy
77
77
  @retry_policy = retry_policy
78
+ @address_resolver = address_resolution_policy
78
79
  @connector = connector
79
80
  @futures = futures_factory
80
81
  end
@@ -133,19 +134,22 @@ module Cassandra
133
134
  #
134
135
  # @return [Cassandra::Future<Cassandra::Session>] a future new session that
135
136
  # can prepare and execute statements
137
+ #
138
+ # @see Cassandra::Cluster#connect A list of possible exceptions that this
139
+ # future can be resolved with
136
140
  def connect_async(keyspace = nil)
137
141
  if !keyspace.nil? && !keyspace.is_a?(::String)
138
142
  return @futures.error(::ArgumentError.new("keyspace must be a string, #{keyspace.inspect} given"))
139
143
  end
140
144
 
141
- client = Client.new(@logger, @registry, @schema, @io_reactor, @connector, @load_balancing_policy, @reconnection_policy, @retry_policy, @connection_options, @futures)
145
+ client = Client.new(@logger, @registry, @schema, @io_reactor, @connector, @load_balancing_policy, @reconnection_policy, @retry_policy, @address_resolver, @connection_options, @futures)
142
146
  session = Session.new(client, @execution_options, @futures)
143
147
  promise = @futures.promise
144
148
 
145
149
  client.connect.on_complete do |f|
146
150
  if f.resolved?
147
151
  if keyspace
148
- f = session.execute_async("USE #{keyspace}")
152
+ f = session.execute_async("USE #{Util.escape_name(keyspace)}")
149
153
 
150
154
  f.on_success {promise.fulfill(session)}
151
155
  f.on_failure {|e| promise.break(e)}
@@ -162,8 +166,14 @@ module Cassandra
162
166
 
163
167
  # Synchronously create a new session, optionally scoped to a keyspace
164
168
  #
165
- # @param keyspace [String] optional keyspace to scope session to
166
- # @raise [ArgumentError] if keyspace is not a String
169
+ # @param keyspace [String] optional keyspace to scope the session to
170
+ #
171
+ # @raise [ArgumentError] if keyspace is not a String
172
+ # @raise [Cassandra::Errors::NoHostsAvailable] when all hosts failed
173
+ # @raise [Cassandra::Errors::AuthenticationError] when authentication fails
174
+ # @raise [Cassandra::Errors::ProtocolError] when protocol negotiation fails
175
+ # @raise [Cassandra::Error] other unexpected errors
176
+ #
167
177
  # @return [Cassandra::Session] a new session that can prepare and execute
168
178
  # statements
169
179
  #
@@ -24,7 +24,7 @@ module Cassandra
24
24
 
25
25
  attr_reader :keyspace
26
26
 
27
- def initialize(logger, cluster_registry, cluster_schema, io_reactor, connector, load_balancing_policy, reconnection_policy, retry_policy, connection_options, futures_factory)
27
+ def initialize(logger, cluster_registry, cluster_schema, io_reactor, connector, load_balancing_policy, reconnection_policy, retry_policy, address_resolution_policy, connection_options, futures_factory)
28
28
  @logger = logger
29
29
  @registry = cluster_registry
30
30
  @schema = cluster_schema
@@ -33,9 +33,10 @@ module Cassandra
33
33
  @load_balancing_policy = load_balancing_policy
34
34
  @reconnection_policy = reconnection_policy
35
35
  @retry_policy = retry_policy
36
+ @address_resolver = address_resolution_policy
36
37
  @connection_options = connection_options
37
38
  @futures = futures_factory
38
- @connecting_hosts = ::Set.new
39
+ @connecting_hosts = ::Hash.new
39
40
  @connections = ::Hash.new
40
41
  @prepared_statements = ::Hash.new
41
42
  @preparing_statements = ::Hash.new
@@ -51,14 +52,20 @@ module Cassandra
51
52
  return @connected_future if @state == :connecting || @state == :connected
52
53
 
53
54
  @state = :connecting
54
- @connecting_hosts.merge(@registry.hosts)
55
+ @registry.each_host do |host|
56
+ distance = @load_balancing_policy.distance(host)
57
+ next if distance == :ignore
58
+
59
+ @connecting_hosts[host] = distance
60
+ end
55
61
  end
56
62
 
57
63
  @connected_future = begin
64
+ @logger.info('Creating session')
58
65
  @registry.add_listener(self)
59
66
 
60
- futures = @connecting_hosts.map do |host|
61
- f = connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host))
67
+ futures = @connecting_hosts.map do |(host, distance)|
68
+ f = connect_to_host(host, distance)
62
69
  f.recover do |error|
63
70
  Cassandra::Client::FailedConnection.new(error, host)
64
71
  end
@@ -116,12 +123,18 @@ module Cassandra
116
123
  end
117
124
 
118
125
  def host_up(host)
126
+ distance = nil
127
+
119
128
  synchronize do
120
129
  return Ione::Future.resolved if @connecting_hosts.include?(host)
121
- @connecting_hosts << host
130
+
131
+ distance = @load_balancing_policy.distance(host)
132
+ return Ione::Future.resolved if distance == :ignore
133
+
134
+ @connecting_hosts[host] = distance
122
135
  end
123
136
 
124
- connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host)).map(nil)
137
+ connect_to_host_maybe_retry(host, distance).map(nil)
125
138
  end
126
139
 
127
140
  def host_down(host)
@@ -130,7 +143,6 @@ module Cassandra
130
143
  synchronize do
131
144
  return Ione::Future.resolved if !@connections.has_key?(host) && !@connecting_hosts.include?(host)
132
145
 
133
- @logger.info("Session disconnecting from ip=#{host.ip}")
134
146
  @connecting_hosts.delete(host)
135
147
  @prepared_statements.delete(host)
136
148
  @preparing_statements.delete(host)
@@ -209,13 +221,17 @@ module Cassandra
209
221
  :unlogged => Protocol::BatchRequest::UNLOGGED_TYPE,
210
222
  :counter => Protocol::BatchRequest::COUNTER_TYPE,
211
223
  }.freeze
212
- CLIENT_CLOSED = Ione::Future.failed(Errors::ClientError.new('Cannot connect a closed client'))
213
- CLIENT_NOT_CONNECTED = Ione::Future.failed(Errors::ClientError.new('Cannot close a not connected client'))
214
- NOT_CONNECTED = Errors::NotConnectedError.new
224
+ CLIENT_CLOSED = Ione::Future.failed(Errors::ClientError.new('Client closed'))
225
+ NOT_CONNECTED = Errors::ClientError.new('Client not connected')
226
+ CLIENT_NOT_CONNECTED = Ione::Future.failed(NOT_CONNECTED)
215
227
 
216
228
  UNAVAILABLE_ERROR_CODE = 0x1000
217
229
  WRITE_TIMEOUT_ERROR_CODE = 0x1100
218
230
  READ_TIMEOUT_ERROR_CODE = 0x1200
231
+ OVERLOADED_ERROR_CODE = 0x1001
232
+ SERVER_ERROR_CODE = 0x0000
233
+ BOOTSTRAPPING_ERROR_CODE = 0x1002
234
+ UNPREPARED_ERROR_CODE = 0x2500
219
235
 
220
236
  SELECT_SCHEMA_PEERS = Protocol::QueryRequest.new("SELECT peer, rpc_address, schema_version FROM system.peers", nil, nil, :one)
221
237
  SELECT_SCHEMA_LOCAL = Protocol::QueryRequest.new("SELECT schema_version FROM system.local WHERE key='local'", nil, nil, :one)
@@ -226,14 +242,14 @@ module Cassandra
226
242
  @state = :connected
227
243
  end
228
244
 
229
- @logger.info('Session connected')
245
+ @logger.info('Session created')
230
246
  else
231
247
  synchronize do
232
248
  @state = :defunct
233
249
  end
234
250
 
235
251
  f.on_failure do |e|
236
- @logger.error('Session connect failed: %s' % e.message)
252
+ @logger.error("Session failed to connect (#{e.class.name}: #{e.message})")
237
253
  end
238
254
 
239
255
  close
@@ -248,20 +264,17 @@ module Cassandra
248
264
  @logger.info('Session closed')
249
265
  else
250
266
  f.on_failure do |e|
251
- @logger.error('Session close failed: %s' % e.message)
267
+ @logger.error("Session failed to close (#{e.class.name}: #{e.message})")
252
268
  end
253
269
  end
254
270
  end
255
271
  end
256
272
 
257
273
  def close_connections
258
- @logger.info('Session closing')
259
-
260
274
  futures = []
261
275
  synchronize do
262
276
  @connections.each do |host, connections|
263
277
  connections.snapshot.each do |c|
264
- @logger.info("Disconnecting ip=#{c.host}")
265
278
  futures << c.close
266
279
  end
267
280
  end.clear
@@ -274,7 +287,7 @@ module Cassandra
274
287
  f = connect_to_host(host, distance)
275
288
 
276
289
  f.on_failure do |e|
277
- connect_to_host_with_retry(host, @reconnection_policy.schedule) if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
290
+ connect_to_host_with_retry(host, @reconnection_policy.schedule)
278
291
  end
279
292
 
280
293
  f
@@ -283,21 +296,23 @@ module Cassandra
283
296
  def connect_to_host_with_retry(host, schedule)
284
297
  interval = schedule.next
285
298
 
286
- @logger.info("Session started reconnecting to ip=#{host.ip} delay=#{interval}")
299
+ @logger.debug("Reconnecting to #{host.ip} in #{interval} seconds")
287
300
 
288
301
  f = @reactor.schedule_timer(interval)
289
302
  f.flat_map do
290
- if synchronize { @connecting_hosts.include?(host) }
291
- connect_to_host(host, @load_balancing_policy.distance(host)).fallback do |e|
292
- if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
293
- connect_to_host_with_retry(host, schedule)
294
- else
295
- Ione::Future.failed(e)
296
- end
303
+ distance = nil
304
+
305
+ synchronize do
306
+ if @connecting_hosts.include?(host)
307
+ distance = @load_balancing_policy.distance(host)
297
308
  end
298
- else
299
- @logger.info("Session reconnection to ip=#{host.ip} cancelled")
309
+ end
300
310
 
311
+ if distance && distance != :ignore
312
+ connect_to_host(host, distance).fallback do |e|
313
+ connect_to_host_with_retry(host, schedule)
314
+ end
315
+ else
301
316
  NO_CONNECTIONS
302
317
  end
303
318
  end
@@ -312,18 +327,16 @@ module Cassandra
312
327
  when :remote
313
328
  pool_size = @connection_options.connections_per_remote_node
314
329
  else
315
- raise ::ArgumentError, "distance must be one of :ignore, :local or :remote, #{distance.inspect} given"
330
+ @logger.error("Invalid load balancing distance, not connecting to #{host.ip}. Distance must be one of #{LoadBalancing::DISTANCES.inspect}, #{distance.inspect} given")
331
+ return NO_CONNECTIONS
316
332
  end
317
333
 
318
- @logger.info("Session connecting to ip=#{host.ip}")
319
-
320
334
  f = @connector.connect_many(host, pool_size)
321
335
 
322
336
  f.on_value do |connections|
323
337
  manager = nil
324
338
 
325
339
  synchronize do
326
- @logger.info("Session connected to ip=#{host.ip}")
327
340
  @connecting_hosts.delete(host)
328
341
  @prepared_statements[host] = {}
329
342
  @preparing_statements[host] = {}
@@ -338,7 +351,7 @@ module Cassandra
338
351
 
339
352
  def execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
340
353
  unless plan.has_next?
341
- promise.break(Errors::NoHostsAvailable.new(errors || {}))
354
+ promise.break(Errors::NoHostsAvailable.new(errors))
342
355
  return
343
356
  end
344
357
 
@@ -361,12 +374,13 @@ module Cassandra
361
374
  prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
362
375
  else
363
376
  s.on_failure do |e|
364
- if e.is_a?(Errors::QueryError)
365
- promise.break(e)
366
- else
377
+ case e
378
+ when Errors::HostError
367
379
  errors ||= {}
368
380
  errors[host] = e
369
381
  execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
382
+ else
383
+ promise.break(e)
370
384
  end
371
385
  end
372
386
  end
@@ -395,12 +409,13 @@ module Cassandra
395
409
  do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
396
410
  else
397
411
  prepare.on_failure do |e|
398
- if e.is_a?(Errors::QueryError)
399
- promise.break(e)
400
- else
412
+ case e
413
+ when Errors::HostError
401
414
  errors ||= {}
402
415
  errors[host] = e
403
416
  execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
417
+ else
418
+ promise.break(e)
404
419
  end
405
420
  end
406
421
  end
@@ -410,7 +425,7 @@ module Cassandra
410
425
 
411
426
  def batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors = nil, hosts = [])
412
427
  unless plan.has_next?
413
- promise.break(Errors::NoHostsAvailable.new(errors || {}))
428
+ promise.break(Errors::NoHostsAvailable.new(errors))
414
429
  return
415
430
  end
416
431
 
@@ -433,12 +448,13 @@ module Cassandra
433
448
  batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
434
449
  else
435
450
  s.on_failure do |e|
436
- if e.is_a?(Errors::QueryError)
437
- promise.break(e)
438
- else
451
+ case e
452
+ when Errors::HostError
439
453
  errors ||= {}
440
454
  errors[host] = e
441
455
  batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
456
+ else
457
+ promise.break(e)
442
458
  end
443
459
  end
444
460
  end
@@ -492,12 +508,13 @@ module Cassandra
492
508
  do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
493
509
  else
494
510
  f.on_failure do |e|
495
- if e.is_a?(Errors::QueryError)
496
- promise.break(e)
497
- else
511
+ case e
512
+ when Errors::HostError
498
513
  errors ||= {}
499
514
  errors[host] = e
500
515
  batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
516
+ else
517
+ promise.break(e)
501
518
  end
502
519
  end
503
520
  end
@@ -507,7 +524,7 @@ module Cassandra
507
524
 
508
525
  def send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
509
526
  unless plan.has_next?
510
- promise.break(Errors::NoHostsAvailable.new(errors || {}))
527
+ promise.break(Errors::NoHostsAvailable.new(errors))
511
528
  return
512
529
  end
513
530
 
@@ -530,12 +547,13 @@ module Cassandra
530
547
  do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
531
548
  else
532
549
  s.on_failure do |e|
533
- if e.is_a?(Errors::QueryError)
534
- promise.break(e)
535
- else
550
+ case e
551
+ when Errors::HostError
536
552
  errors ||= {}
537
553
  errors[host] = e
538
554
  send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
555
+ else
556
+ promise.break(e)
539
557
  end
540
558
  end
541
559
  end
@@ -564,32 +582,78 @@ module Cassandra
564
582
  when UNAVAILABLE_ERROR_CODE
565
583
  @retry_policy.unavailable(statement, details[:cl], details[:required], details[:alive], retries)
566
584
  when WRITE_TIMEOUT_ERROR_CODE
567
- details[:write_type] = write_type = details[:write_type].downcase!.to_sym
568
- @retry_policy.write_timeout(statement, details[:cl], write_type, details[:blockfor], details[:received], retries)
585
+ @retry_policy.write_timeout(statement, details[:cl], details[:write_type], details[:blockfor], details[:received], retries)
569
586
  when READ_TIMEOUT_ERROR_CODE
570
587
  @retry_policy.read_timeout(statement, details[:cl], details[:blockfor], details[:received], details[:data_present], retries)
588
+ when UNPREPARED_ERROR_CODE
589
+ cql = statement.cql
590
+
591
+ synchronize do
592
+ @preparing_statements[host].delete(cql)
593
+ @prepared_statements[host].delete(cql)
594
+ end
595
+
596
+ prepare = prepare_statement(host, connection, cql, timeout)
597
+ prepare.on_complete do |_|
598
+ if prepare.resolved?
599
+ request.id = prepare.value
600
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
601
+ else
602
+ prepare.on_failure do |e|
603
+ case e
604
+ when Errors::HostError
605
+ errors ||= {}
606
+ errors[host] = e
607
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
608
+ else
609
+ promise.break(e)
610
+ end
611
+ end
612
+ end
613
+ end
614
+
615
+ nil
571
616
  else
572
- promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, details))
573
- break
617
+ promise.break(r.to_error(statement))
618
+
619
+ nil
574
620
  end
575
621
  rescue => e
576
622
  promise.break(e)
577
- break
623
+
624
+ nil
578
625
  end
579
626
 
580
- case decision
581
- when Retry::Decisions::Retry
582
- request.consistency = decision.consistency
583
- do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries + 1)
584
- when Retry::Decisions::Ignore
585
- promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
586
- when Retry::Decisions::Reraise
587
- promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
588
- else
589
- promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
627
+ if decision
628
+ case decision
629
+ when Retry::Decisions::Retry
630
+ request.consistency = decision.consistency
631
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries + 1)
632
+ when Retry::Decisions::Ignore
633
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
634
+ when Retry::Decisions::Reraise
635
+ promise.break(r.to_error(statement))
636
+ else
637
+ promise.break(r.to_error(statement))
638
+ end
590
639
  end
591
640
  when Protocol::ErrorResponse
592
- promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, nil))
641
+ case r.code
642
+ when OVERLOADED_ERROR_CODE, SERVER_ERROR_CODE, BOOTSTRAPPING_ERROR_CODE
643
+ errors ||= {}
644
+ errors[host] = r.to_error(statement)
645
+
646
+ case request
647
+ when Protocol::QueryRequest, Protocol::PrepareRequest
648
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
649
+ when Protocol::ExecuteRequest
650
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
651
+ when Protocol::BatchRequest
652
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
653
+ end
654
+ else
655
+ promise.break(r.to_error(statement))
656
+ end
593
657
  when Protocol::SetKeyspaceResultResponse
594
658
  @keyspace = r.keyspace
595
659
  promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
@@ -611,10 +675,11 @@ module Cassandra
611
675
  @keyspace = nil
612
676
  end
613
677
 
678
+ @logger.debug('Waiting for schema to propagate to all hosts after a change')
614
679
  wait_for_schema_agreement(connection, @reconnection_policy.schedule).on_complete do |f|
615
680
  unless f.resolved?
616
681
  f.on_failure do |e|
617
- @logger.error("Schema agreement error: #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
682
+ @logger.error("Schema agreement failure (#{e.class.name}: #{e.message})")
618
683
  end
619
684
  end
620
685
  promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
@@ -642,42 +707,35 @@ module Cassandra
642
707
  end
643
708
 
644
709
  def wait_for_schema_agreement(connection, schedule)
645
- @logger.info('Fetching schema versions')
646
-
647
- peers = connection.send_request(SELECT_SCHEMA_PEERS)
648
- local = connection.send_request(SELECT_SCHEMA_LOCAL)
710
+ peers = send_select_request(connection, SELECT_SCHEMA_PEERS)
711
+ local = send_select_request(connection, SELECT_SCHEMA_LOCAL)
649
712
 
650
713
  Ione::Future.all(peers, local).flat_map do |(peers, local)|
651
- @logger.info('Fetched schema versions')
652
-
653
- peers = peers.rows
654
- local = local.rows
655
-
656
714
  versions = ::Set.new
657
715
 
658
716
  unless local.empty?
659
717
  host = @registry.host(connection.host)
660
718
 
661
- if host && host.up?
719
+ if host && @load_balancing_policy.distance(host) != :ignore
662
720
  versions << version = local.first['schema_version']
663
- @logger.debug("Host #{host.ip} schema version: #{version}")
721
+ @logger.debug("Host #{host.ip} schema version is #{version}")
664
722
  end
665
723
  end
666
724
 
667
725
  peers.each do |row|
668
726
  host = @registry.host(peer_ip(row))
669
- next unless host && host.up?
727
+ next unless host && @load_balancing_policy.distance(host) != :ignore
670
728
 
671
729
  versions << version = row['schema_version']
672
- @logger.debug("Host #{host.ip} schema version: #{version}")
730
+ @logger.debug("Host #{host.ip} schema version is #{version}")
673
731
  end
674
732
 
675
733
  if versions.one?
676
- @logger.info('All hosts have the same schema version')
734
+ @logger.debug('All hosts have the same schema')
677
735
  Ione::Future.resolved
678
736
  else
679
737
  interval = schedule.next
680
- @logger.info("Hosts don't yet agree on schema: #{versions.to_a.inspect}, retrying in #{interval} seconds")
738
+ @logger.info("Hosts have different schema versions: #{versions.to_a.inspect}, retrying in #{interval} seconds")
681
739
  @reactor.schedule_timer(interval).flat_map do
682
740
  wait_for_schema_agreement(connection, schedule)
683
741
  end
@@ -688,7 +746,8 @@ module Cassandra
688
746
  def peer_ip(data)
689
747
  ip = data['rpc_address']
690
748
  ip = data['peer'] if ip == '0.0.0.0'
691
- ip
749
+
750
+ @address_resolver.resolve(ip)
692
751
  end
693
752
 
694
753
  def switch_keyspace(connection, keyspace, timeout)
@@ -697,19 +756,17 @@ module Cassandra
697
756
 
698
757
  return pending_switch || Ione::Future.resolved if pending_keyspace == keyspace
699
758
 
700
- request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
759
+ request = Protocol::QueryRequest.new("USE #{Util.escape_name(keyspace)}", nil, nil, :one)
701
760
 
702
761
  f = connection.send_request(request, timeout).map do |r|
703
762
  case r
704
763
  when Protocol::SetKeyspaceResultResponse
705
764
  @keyspace = r.keyspace
706
765
  nil
707
- when Protocol::DetailedErrorResponse
708
- raise Errors::QueryError.new(r.code, r.message, request.cql, r.details)
709
766
  when Protocol::ErrorResponse
710
- raise Errors::QueryError.new(r.code, r.message, request.cql, nil)
767
+ raise r.to_error(Statements::Simple.new("USE #{Util.escape_name(keyspace)}"))
711
768
  else
712
- raise "unexpected response #{r.inspect}"
769
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
713
770
  end
714
771
  end
715
772
 
@@ -742,12 +799,10 @@ module Cassandra
742
799
  @preparing_statements[host].delete(cql)
743
800
  end
744
801
  id
745
- when Protocol::DetailedErrorResponse
746
- raise Errors::QueryError.new(r.code, r.message, cql, r.details)
747
802
  when Protocol::ErrorResponse
748
- raise Errors::QueryError.new(r.code, r.message, cql, nil)
803
+ raise r.to_error(VOID_STATEMENT)
749
804
  else
750
- raise "unexpected response #{r.inspect}"
805
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
751
806
  end
752
807
  end
753
808
 
@@ -758,10 +813,17 @@ module Cassandra
758
813
  f
759
814
  end
760
815
 
761
- def create_execution_info(keyspace, statement, options, request, response, hosts)
762
- trace_id = response.trace_id
763
- trace = trace_id ? Execution::Trace.new(trace_id, self) : nil
764
- info = Execution::Info.new(keyspace, statement, options, hosts, request.consistency, request.retries, trace)
816
+ def send_select_request(connection, request)
817
+ connection.send_request(request).map do |r|
818
+ case r
819
+ when Protocol::RowsResultResponse
820
+ r.rows
821
+ when Protocol::ErrorResponse
822
+ raise r.to_error(VOID_STATEMENT)
823
+ else
824
+ raise Errors::InternalError, "Unexpected response #{r.inspect}"
825
+ end
826
+ end
765
827
  end
766
828
  end
767
829
  end