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

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 (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