cassandra-driver 3.0.0.beta.1 → 3.0.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +90 -38
  3. data/ext/cassandra_murmur3/cassandra_murmur3.c +1 -1
  4. data/lib/cassandra.rb +327 -130
  5. data/lib/cassandra/address_resolution.rb +1 -1
  6. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +1 -1
  7. data/lib/cassandra/address_resolution/policies/none.rb +1 -1
  8. data/lib/cassandra/aggregate.rb +21 -7
  9. data/lib/cassandra/argument.rb +2 -2
  10. data/lib/cassandra/auth.rb +4 -4
  11. data/lib/cassandra/auth/providers.rb +1 -1
  12. data/lib/cassandra/auth/providers/password.rb +9 -5
  13. data/lib/cassandra/cassandra_logger.rb +80 -0
  14. data/lib/cassandra/cluster.rb +38 -9
  15. data/lib/cassandra/cluster/client.rb +801 -205
  16. data/lib/cassandra/cluster/connection_pool.rb +2 -2
  17. data/lib/cassandra/cluster/connector.rb +74 -25
  18. data/lib/cassandra/cluster/control_connection.rb +217 -82
  19. data/lib/cassandra/cluster/failed_connection.rb +1 -1
  20. data/lib/cassandra/cluster/metadata.rb +12 -4
  21. data/lib/cassandra/cluster/options.rb +60 -11
  22. data/lib/cassandra/cluster/registry.rb +69 -16
  23. data/lib/cassandra/cluster/schema.rb +25 -7
  24. data/lib/cassandra/cluster/schema/cql_type_parser.rb +15 -10
  25. data/lib/cassandra/cluster/schema/fetchers.rb +263 -106
  26. data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +41 -36
  27. data/lib/cassandra/cluster/schema/partitioners.rb +1 -1
  28. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +3 -3
  29. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +1 -1
  30. data/lib/cassandra/cluster/schema/partitioners/random.rb +1 -1
  31. data/lib/cassandra/cluster/schema/replication_strategies.rb +1 -1
  32. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +19 -18
  33. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +1 -1
  34. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +1 -1
  35. data/lib/cassandra/column.rb +3 -3
  36. data/lib/cassandra/compression.rb +1 -1
  37. data/lib/cassandra/compression/compressors/lz4.rb +4 -3
  38. data/lib/cassandra/compression/compressors/snappy.rb +4 -3
  39. data/lib/cassandra/driver.rb +103 -41
  40. data/lib/cassandra/errors.rb +265 -30
  41. data/lib/cassandra/execution/info.rb +16 -5
  42. data/lib/cassandra/execution/options.rb +99 -54
  43. data/lib/cassandra/execution/trace.rb +16 -9
  44. data/lib/cassandra/executors.rb +1 -1
  45. data/lib/cassandra/function.rb +19 -13
  46. data/lib/cassandra/function_collection.rb +85 -0
  47. data/lib/cassandra/future.rb +106 -48
  48. data/lib/cassandra/host.rb +10 -4
  49. data/lib/cassandra/keyspace.rb +90 -33
  50. data/lib/cassandra/listener.rb +1 -1
  51. data/lib/cassandra/load_balancing.rb +2 -2
  52. data/lib/cassandra/load_balancing/policies.rb +1 -1
  53. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +18 -18
  54. data/lib/cassandra/load_balancing/policies/round_robin.rb +1 -1
  55. data/lib/cassandra/load_balancing/policies/token_aware.rb +15 -13
  56. data/lib/cassandra/load_balancing/policies/white_list.rb +11 -5
  57. data/lib/cassandra/null_logger.rb +27 -6
  58. data/lib/cassandra/protocol.rb +1 -1
  59. data/lib/cassandra/protocol/coder.rb +78 -39
  60. data/lib/cassandra/protocol/cql_byte_buffer.rb +50 -33
  61. data/lib/cassandra/protocol/cql_protocol_handler.rb +44 -45
  62. data/lib/cassandra/protocol/request.rb +2 -2
  63. data/lib/cassandra/protocol/requests/auth_response_request.rb +3 -3
  64. data/lib/cassandra/protocol/requests/batch_request.rb +16 -7
  65. data/lib/cassandra/protocol/requests/credentials_request.rb +3 -3
  66. data/lib/cassandra/protocol/requests/execute_request.rb +41 -20
  67. data/lib/cassandra/protocol/requests/options_request.rb +1 -1
  68. data/lib/cassandra/protocol/requests/prepare_request.rb +5 -5
  69. data/lib/cassandra/protocol/requests/query_request.rb +27 -22
  70. data/lib/cassandra/protocol/requests/register_request.rb +2 -2
  71. data/lib/cassandra/protocol/requests/startup_request.rb +6 -4
  72. data/lib/cassandra/protocol/requests/void_query_request.rb +1 -1
  73. data/lib/cassandra/protocol/response.rb +2 -2
  74. data/lib/cassandra/protocol/responses/already_exists_error_response.rb +12 -2
  75. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +1 -1
  76. data/lib/cassandra/protocol/responses/auth_success_response.rb +1 -1
  77. data/lib/cassandra/protocol/responses/authenticate_response.rb +1 -1
  78. data/lib/cassandra/protocol/responses/error_response.rb +101 -13
  79. data/lib/cassandra/protocol/responses/event_response.rb +1 -1
  80. data/lib/cassandra/protocol/responses/function_failure_error_response.rb +13 -2
  81. data/lib/cassandra/protocol/responses/prepared_result_response.rb +11 -5
  82. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +14 -9
  83. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +26 -4
  84. data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +22 -3
  85. data/lib/cassandra/protocol/responses/ready_response.rb +3 -3
  86. data/lib/cassandra/protocol/responses/result_response.rb +4 -2
  87. data/lib/cassandra/protocol/responses/rows_result_response.rb +5 -3
  88. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +5 -4
  89. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +16 -9
  90. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +2 -2
  91. data/lib/cassandra/protocol/responses/status_change_event_response.rb +2 -2
  92. data/lib/cassandra/protocol/responses/supported_response.rb +1 -1
  93. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +1 -1
  94. data/lib/cassandra/protocol/responses/unavailable_error_response.rb +20 -3
  95. data/lib/cassandra/protocol/responses/unprepared_error_response.rb +11 -2
  96. data/lib/cassandra/protocol/responses/void_result_response.rb +1 -1
  97. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +26 -4
  98. data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +22 -3
  99. data/lib/cassandra/protocol/v1.rb +101 -36
  100. data/lib/cassandra/protocol/v3.rb +124 -51
  101. data/lib/cassandra/protocol/v4.rb +172 -68
  102. data/lib/cassandra/reconnection.rb +1 -1
  103. data/lib/cassandra/reconnection/policies.rb +1 -1
  104. data/lib/cassandra/reconnection/policies/constant.rb +2 -4
  105. data/lib/cassandra/reconnection/policies/exponential.rb +6 -6
  106. data/lib/cassandra/result.rb +53 -19
  107. data/lib/cassandra/retry.rb +8 -8
  108. data/lib/cassandra/retry/policies.rb +1 -1
  109. data/lib/cassandra/retry/policies/default.rb +1 -1
  110. data/lib/cassandra/retry/policies/downgrading_consistency.rb +7 -3
  111. data/lib/cassandra/retry/policies/fallthrough.rb +1 -1
  112. data/lib/cassandra/session.rb +22 -16
  113. data/lib/cassandra/statement.rb +1 -1
  114. data/lib/cassandra/statements.rb +1 -1
  115. data/lib/cassandra/statements/batch.rb +16 -10
  116. data/lib/cassandra/statements/bound.rb +10 -3
  117. data/lib/cassandra/statements/prepared.rb +59 -15
  118. data/lib/cassandra/statements/simple.rb +23 -10
  119. data/lib/cassandra/statements/void.rb +1 -1
  120. data/lib/cassandra/table.rb +79 -30
  121. data/lib/cassandra/time.rb +11 -6
  122. data/lib/cassandra/time_uuid.rb +7 -7
  123. data/lib/cassandra/tuple.rb +16 -8
  124. data/lib/cassandra/types.rb +20 -9
  125. data/lib/cassandra/udt.rb +32 -36
  126. data/lib/cassandra/util.rb +20 -13
  127. data/lib/cassandra/uuid.rb +22 -15
  128. data/lib/cassandra/uuid/generator.rb +7 -5
  129. data/lib/cassandra/version.rb +2 -2
  130. data/lib/datastax/cassandra.rb +1 -1
  131. metadata +5 -3
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  #--
4
- # Copyright 2013-2015 DataStax, Inc.
4
+ # Copyright 2013-2016 DataStax, Inc.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -72,7 +72,7 @@ module Cassandra
72
72
  @connections.each(&callback)
73
73
  end
74
74
  end
75
- alias_method :each, :each_connection
75
+ alias each each_connection
76
76
  end
77
77
  end
78
78
  end
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  #--
4
- # Copyright 2013-2015 DataStax, Inc.
4
+ # Copyright 2013-2016 DataStax, Inc.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -22,7 +22,11 @@ module Cassandra
22
22
  class Connector
23
23
  include MonitorMixin
24
24
 
25
- def initialize(logger, io_reactor, cluster_registry, connection_options, execution_options)
25
+ def initialize(logger,
26
+ io_reactor,
27
+ cluster_registry,
28
+ connection_options,
29
+ execution_options)
26
30
  @logger = logger
27
31
  @reactor = io_reactor
28
32
  @registry = cluster_registry
@@ -82,7 +86,7 @@ module Cassandra
82
86
 
83
87
  synchronize do
84
88
  @open_connections[host] ||= []
85
- @open_connections[host] << connection
89
+ @open_connections[host] << connection
86
90
  end
87
91
 
88
92
  timer = @reactor.schedule_timer(UNCLAIMED_TIMEOUT)
@@ -112,7 +116,10 @@ module Cassandra
112
116
  UNCLAIMED_TIMEOUT = 5 # close unclaimed connections in five seconds
113
117
 
114
118
  def do_connect(host)
115
- @reactor.connect(host.ip.to_s, @connection_options.port, {:timeout => @connection_options.connect_timeout, :ssl => @connection_options.ssl}) do |connection|
119
+ @reactor.connect(host.ip.to_s,
120
+ @connection_options.port,
121
+ timeout: @connection_options.connect_timeout,
122
+ ssl: @connection_options.ssl) do |connection|
116
123
  raise Errors::ClientError, 'Not connected, reactor stopped' unless connection
117
124
 
118
125
  if @connection_options.nodelay?
@@ -121,7 +128,13 @@ module Cassandra
121
128
  connection.to_io.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 0)
122
129
  end
123
130
 
124
- Protocol::CqlProtocolHandler.new(connection, @reactor, @connection_options.protocol_version, @connection_options.compressor, @connection_options.heartbeat_interval, @connection_options.idle_timeout)
131
+ Protocol::CqlProtocolHandler.new(connection,
132
+ @reactor,
133
+ @connection_options.protocol_version,
134
+ @connection_options.compressor,
135
+ @connection_options.heartbeat_interval,
136
+ @connection_options.idle_timeout,
137
+ @connection_options.requests_per_connection)
125
138
  end.flat_map do |connection|
126
139
  f = request_options(connection)
127
140
  f = f.flat_map do |options|
@@ -129,12 +142,16 @@ module Cassandra
129
142
  supported_algorithms = options['COMPRESSION']
130
143
 
131
144
  if compression && !supported_algorithms.include?(compression)
132
- @logger.warn("Compression with #{compression.inspect} is not supported by host at #{host.ip}, supported algorithms are #{supported_algorithms.inspect}")
145
+ @logger.warn("Compression with #{compression.inspect} is not supported " \
146
+ "by host at #{host.ip}, supported algorithms are " \
147
+ "#{supported_algorithms.inspect}")
133
148
  compression = nil
134
149
  end
135
150
 
136
151
  supported_cql_versions = options['CQL_VERSION']
137
- cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ? supported_cql_versions.first : '3.1.0'
152
+ cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ?
153
+ supported_cql_versions.first :
154
+ '3.1.0'
138
155
 
139
156
  startup_connection(connection, cql_version, compression)
140
157
  end
@@ -143,7 +160,8 @@ module Cassandra
143
160
  when Errors::ProtocolError
144
161
  synchronize do
145
162
  if @connection_options.protocol_version > 1
146
- @logger.info("Host #{host.ip} doesn't support protocol version #{@connection_options.protocol_version}, downgrading")
163
+ @logger.info("Host #{host.ip} doesn't support protocol version " \
164
+ "#{@connection_options.protocol_version}, downgrading")
147
165
  @connection_options.protocol_version -= 1
148
166
  do_connect(host)
149
167
  else
@@ -152,7 +170,7 @@ module Cassandra
152
170
  end
153
171
  when Errors::TimeoutError
154
172
  future = Ione::CompletableFuture.new
155
- connection.close(error).on_complete do |f|
173
+ connection.close(error).on_complete do |_f|
156
174
  future.fail(error)
157
175
  end
158
176
  future
@@ -173,7 +191,8 @@ module Cassandra
173
191
  end
174
192
 
175
193
  def startup_connection(connection, cql_version, compression)
176
- connection.send_request(Protocol::StartupRequest.new(cql_version, compression), @execution_options.timeout).flat_map do |r|
194
+ connection.send_request(Protocol::StartupRequest.new(cql_version, compression),
195
+ @execution_options.timeout).flat_map do |r|
177
196
  case r
178
197
  when Protocol::AuthenticateResponse
179
198
  if @connection_options.protocol_version == 1
@@ -181,28 +200,48 @@ module Cassandra
181
200
  if credentials
182
201
  send_credentials(connection, credentials)
183
202
  else
184
- Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate', nil, nil, nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
203
+ Ione::Future.failed(cannot_authenticate_error)
185
204
  end
186
205
  else
187
- authenticator = @connection_options.create_authenticator(r.authentication_class)
206
+ authenticator = @connection_options.create_authenticator(
207
+ r.authentication_class)
188
208
  if authenticator
189
- challenge_response_cycle(connection, authenticator, authenticator.initial_response)
209
+ challenge_response_cycle(connection,
210
+ authenticator,
211
+ authenticator.initial_response)
190
212
  else
191
- Ione::Future.failed(Errors::AuthenticationError.new('Server requested authentication, but client was not configured to authenticate', nil, nil, nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
213
+ Ione::Future.failed(cannot_authenticate_error)
192
214
  end
193
215
  end
194
216
  when Protocol::ReadyResponse
195
217
  ::Ione::Future.resolved(connection)
196
218
  when Protocol::ErrorResponse
197
- ::Ione::Future.failed(r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
219
+ ::Ione::Future.failed(
220
+ r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
198
221
  else
199
- ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
222
+ ::Ione::Future.failed(
223
+ Errors::InternalError.new("Unexpected response #{r.inspect}"))
200
224
  end
201
225
  end
202
226
  end
203
227
 
228
+ def cannot_authenticate_error
229
+ Errors::AuthenticationError.new(
230
+ 'Server requested authentication, but client was not configured to ' \
231
+ 'authenticate',
232
+ nil,
233
+ nil,
234
+ nil,
235
+ VOID_STATEMENT,
236
+ VOID_OPTIONS,
237
+ EMPTY_LIST,
238
+ :one,
239
+ 0)
240
+ end
241
+
204
242
  def request_options(connection)
205
- connection.send_request(Protocol::OptionsRequest.new, @execution_options.timeout).map do |r|
243
+ connection.send_request(Protocol::OptionsRequest.new,
244
+ @execution_options.timeout).map do |r|
206
245
  case r
207
246
  when Protocol::SupportedResponse
208
247
  r.options
@@ -215,7 +254,8 @@ module Cassandra
215
254
  end
216
255
 
217
256
  def send_credentials(connection, credentials)
218
- connection.send_request(Protocol::CredentialsRequest.new(credentials), @execution_options.timeout).map do |r|
257
+ connection.send_request(Protocol::CredentialsRequest.new(credentials),
258
+ @execution_options.timeout).map do |r|
219
259
  case r
220
260
  when Protocol::ReadyResponse
221
261
  connection
@@ -228,18 +268,25 @@ module Cassandra
228
268
  end
229
269
 
230
270
  def challenge_response_cycle(connection, authenticator, token)
231
- connection.send_request(Protocol::AuthResponseRequest.new(token), @execution_options.timeout).flat_map do |r|
271
+ connection.send_request(Protocol::AuthResponseRequest.new(token),
272
+ @execution_options.timeout).flat_map do |r|
232
273
  case r
233
274
  when Protocol::AuthChallengeResponse
234
275
  token = authenticator.challenge_response(r.token)
235
276
  challenge_response_cycle(pending_connection, authenticator, token)
236
277
  when Protocol::AuthSuccessResponse
237
- authenticator.authentication_successful(r.token) rescue nil
278
+ begin
279
+ authenticator.authentication_successful(r.token)
280
+ rescue
281
+ nil
282
+ end
238
283
  ::Ione::Future.resolved(connection)
239
284
  when Protocol::ErrorResponse
240
- ::Ione::Future.failed(r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
285
+ ::Ione::Future.failed(
286
+ r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
241
287
  else
242
- ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
288
+ ::Ione::Future.failed(
289
+ Errors::InternalError.new("Unexpected response #{r.inspect}"))
243
290
  end
244
291
  end
245
292
  end
@@ -282,7 +329,8 @@ module Cassandra
282
329
  end
283
330
  end
284
331
 
285
- @logger.debug("Host #{host.ip} closed connection (#{error.class.name}: #{error.message})") if error
332
+ @logger.debug("Host #{host.ip} closed connection (#{error.class.name}: " \
333
+ "#{error.message})") if error
286
334
 
287
335
  if notify
288
336
  @logger.warn("Host #{host.ip} closed all connections")
@@ -296,10 +344,11 @@ module Cassandra
296
344
  notify = false
297
345
 
298
346
  synchronize do
299
- notify = !error.nil? && !@connections.has_key?(host)
347
+ notify = !error.nil? && !@connections.key?(host)
300
348
  end
301
349
 
302
- @logger.debug("Host #{host.ip} refused connection (#{error.class.name}: #{error.message})")
350
+ @logger.debug("Host #{host.ip} refused connection (#{error.class.name}: " \
351
+ "#{error.message})")
303
352
 
304
353
  if notify
305
354
  @logger.warn("Host #{host.ip} refused all connections")
@@ -1,7 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  #--
4
- # Copyright 2013-2015 DataStax, Inc.
4
+ # Copyright 2013-2016 DataStax, Inc.
5
5
  #
6
6
  # Licensed under the Apache License, Version 2.0 (the "License");
7
7
  # you may not use this file except in compliance with the License.
@@ -84,8 +84,11 @@ module Cassandra
84
84
  timer = @refreshing_statuses.delete(host)
85
85
  @io_reactor.cancel_timer(timer) if timer
86
86
 
87
- unless @connection || (@status == :closing || @status == :closed) || @load_balancing_policy.distance(host) == :ignore
88
- return connect_to_first_available(@load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS))
87
+ unless @connection ||
88
+ (@status == :closing || @status == :closed) ||
89
+ @load_balancing_policy.distance(host) == :ignore
90
+ return connect_to_first_available(
91
+ @load_balancing_policy.plan(nil, VOID_STATEMENT, VOID_OPTIONS))
89
92
  end
90
93
  end
91
94
 
@@ -97,18 +100,22 @@ module Cassandra
97
100
  timer = nil
98
101
 
99
102
  synchronize do
100
- return Ione::Future.resolved if @refreshing_statuses[host] || @load_balancing_policy.distance(host) == :ignore
103
+ if @refreshing_statuses[host] ||
104
+ @load_balancing_policy.distance(host) == :ignore
105
+ return Ione::Future.resolved
106
+ end
101
107
 
102
108
  schedule = @reconnection_policy.schedule
103
109
  timeout = schedule.next
104
110
 
105
- @logger.debug("Starting to continuously refresh status of #{host.ip} in #{timeout} seconds")
111
+ @logger.debug("Starting to continuously refresh status of #{host.ip} in " \
112
+ "#{timeout} seconds")
106
113
 
107
114
  @refreshing_statuses[host] = timer = @io_reactor.schedule_timer(timeout)
108
115
  end
109
116
 
110
117
  timer.on_value do
111
- refresh_host_status(host).fallback do |e|
118
+ refresh_host_status(host).fallback do |_e|
112
119
  refresh_host_status_with_retry(timer, host, schedule)
113
120
  end
114
121
  end
@@ -134,13 +141,28 @@ module Cassandra
134
141
  end
135
142
 
136
143
  def inspect
137
- "#<#{self.class.name}:0x#{self.object_id.to_s(16)}>"
144
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}>"
138
145
  end
139
146
 
140
147
  private
141
148
 
142
- SELECT_LOCAL = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, release_version, tokens, partitioner FROM system.local', EMPTY_LIST, EMPTY_LIST, :one)
143
- SELECT_PEERS = Protocol::QueryRequest.new('SELECT peer, rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers', EMPTY_LIST, EMPTY_LIST, :one)
149
+ SELECT_LOCAL = Protocol::QueryRequest.new(
150
+ 'SELECT rack, data_center, host_id, release_version, tokens, partitioner ' \
151
+ 'FROM system.local',
152
+ EMPTY_LIST,
153
+ EMPTY_LIST,
154
+ :one)
155
+ SELECT_PEERS = Protocol::QueryRequest.new(
156
+ 'SELECT peer, rack, data_center, host_id, rpc_address, release_version, tokens ' \
157
+ 'FROM system.peers',
158
+ EMPTY_LIST,
159
+ EMPTY_LIST,
160
+ :one)
161
+
162
+ SELECT_PEER_QUERY =
163
+ 'SELECT rack, data_center, host_id, rpc_address, release_version, tokens ' \
164
+ 'FROM system.peers ' \
165
+ "WHERE peer = '%s'".freeze
144
166
 
145
167
  def reconnect_async(schedule)
146
168
  timeout = schedule.next
@@ -159,25 +181,28 @@ module Cassandra
159
181
  f.fallback do |e|
160
182
  @logger.error("Control connection failed (#{e.class.name}: #{e.message})")
161
183
 
162
- if synchronize { @status == :reconnecting }
163
- reconnect_async(schedule)
164
- else
165
- return Ione::Future.resolved
166
- end
184
+ return Ione::Future.resolved unless synchronize { @status == :reconnecting }
185
+
186
+ # We're reconnecting...
187
+ reconnect_async(schedule)
167
188
  end
168
189
  end
169
190
 
170
191
  def register_async
171
192
  connection = @connection
172
193
 
173
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
194
+ if connection.nil?
195
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
196
+ end
174
197
 
175
198
  request = Protocol::RegisterRequest.new(
176
199
  Protocol::TopologyChangeEventResponse::TYPE,
177
200
  Protocol::StatusChangeEventResponse::TYPE
178
201
  )
179
202
 
180
- request.events << Protocol::SchemaChangeEventResponse::TYPE if @connection_options.synchronize_schema?
203
+ if @connection_options.synchronize_schema?
204
+ request.events << Protocol::SchemaChangeEventResponse::TYPE
205
+ end
181
206
 
182
207
  f = connection.send_request(request)
183
208
  f = f.map do |r|
@@ -190,7 +215,7 @@ module Cassandra
190
215
  raise Errors::InternalError, "Unexpected response #{r.inspect}"
191
216
  end
192
217
  end
193
- f = f.map do
218
+ f.map do
194
219
  connection.on_event do |event|
195
220
  @logger.debug("Event received #{event}")
196
221
 
@@ -205,7 +230,7 @@ module Cassandra
205
230
  @registry.host_up(address)
206
231
  else
207
232
  refresh_host_async_maybe_retry(address)
208
- refresh_maybe_retry(:schema)
233
+ refresh_schema_async_wrapper
209
234
  end
210
235
  when 'DOWN'
211
236
  @registry.host_down(event.address)
@@ -214,11 +239,11 @@ module Cassandra
214
239
 
215
240
  unless @registry.has_host?(address)
216
241
  refresh_host_async_maybe_retry(address)
217
- refresh_maybe_retry(:schema)
242
+ refresh_schema_async_wrapper
218
243
  end
219
244
  when 'REMOVED_NODE'
220
245
  @registry.host_lost(event.address)
221
- refresh_maybe_retry(:schema)
246
+ refresh_schema_async_wrapper
222
247
  end
223
248
  end
224
249
  end
@@ -230,21 +255,25 @@ module Cassandra
230
255
  def refresh_schema_async
231
256
  connection = @connection
232
257
 
233
- @logger.info("Refreshing schema")
258
+ @logger.info('Refreshing schema')
234
259
 
235
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
260
+ if connection.nil?
261
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
262
+ end
236
263
 
237
264
  @schema_fetcher.fetch(connection).map do |keyspaces|
238
265
  @schema.replace(keyspaces)
239
266
  @metadata.rebuild_token_map
240
- @logger.info("Schema refreshed")
267
+ @logger.info('Schema refreshed')
241
268
  end
242
269
  end
243
270
 
244
271
  def refresh_keyspace_async(keyspace_name)
245
272
  connection = @connection
246
273
 
247
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
274
+ if connection.nil?
275
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
276
+ end
248
277
 
249
278
  @logger.info("Refreshing keyspace \"#{keyspace_name}\"")
250
279
 
@@ -262,7 +291,9 @@ module Cassandra
262
291
  def refresh_table_async(keyspace_name, table_name)
263
292
  connection = @connection
264
293
 
265
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
294
+ if connection.nil?
295
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
296
+ end
266
297
 
267
298
  @logger.info("Refreshing table \"#{keyspace_name}.#{table_name}\"")
268
299
 
@@ -280,7 +311,9 @@ module Cassandra
280
311
  def refresh_type_async(keyspace_name, type_name)
281
312
  connection = @connection
282
313
 
283
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
314
+ if connection.nil?
315
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
316
+ end
284
317
 
285
318
  @logger.info("Refreshing user-defined type \"#{keyspace_name}.#{type_name}\"")
286
319
 
@@ -298,40 +331,57 @@ module Cassandra
298
331
  def refresh_function_async(keyspace_name, function_name, function_args)
299
332
  connection = @connection
300
333
 
301
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
334
+ if connection.nil?
335
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
336
+ end
302
337
 
303
- @logger.info("Refreshing user-defined function \"#{keyspace_name}.#{function_name}\"")
338
+ @logger.info('Refreshing user-defined function ' \
339
+ "\"#{keyspace_name}.#{function_name}\"")
304
340
 
305
341
  # function_args is an array of string, and we need an array of parsed types.
306
- parsed_function_args = @schema_fetcher.parse_argument_types(connection, keyspace_name, function_args)
307
- @schema_fetcher.fetch_function(connection, keyspace_name, function_name, parsed_function_args).map do |function|
342
+ parsed_function_args =
343
+ @schema_fetcher.parse_argument_types(connection, keyspace_name, function_args)
344
+ @schema_fetcher.fetch_function(connection,
345
+ keyspace_name,
346
+ function_name,
347
+ parsed_function_args).map do |function|
308
348
  if function
309
349
  @schema.replace_function(function)
310
350
  else
311
351
  @schema.delete_function(keyspace_name, function_name, parsed_function_args)
312
352
  end
313
353
 
314
- @logger.info("Refreshed user-defined function \"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"")
354
+ @logger.info('Refreshed user-defined function ' \
355
+ "\"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"")
315
356
  end
316
357
  end
317
358
 
318
359
  def refresh_aggregate_async(keyspace_name, aggregate_name, aggregate_args)
319
360
  connection = @connection
320
361
 
321
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
362
+ if connection.nil?
363
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
364
+ end
322
365
 
323
- @logger.info("Refreshing user-defined aggregate \"#{keyspace_name}.#{aggregate_name}\"")
366
+ @logger.info('Refreshing user-defined aggregate ' \
367
+ "\"#{keyspace_name}.#{aggregate_name}\"")
324
368
 
325
369
  # aggregate_args is an array of string, and we need an array of parsed types.
326
- parsed_aggregate_args = @schema_fetcher.parse_argument_types(connection, keyspace_name, aggregate_args)
327
- @schema_fetcher.fetch_aggregate(connection, keyspace_name, aggregate_name, parsed_aggregate_args).map do |aggregate|
370
+ parsed_aggregate_args = @schema_fetcher.parse_argument_types(connection,
371
+ keyspace_name,
372
+ aggregate_args)
373
+ @schema_fetcher.fetch_aggregate(connection,
374
+ keyspace_name,
375
+ aggregate_name,
376
+ parsed_aggregate_args).map do |aggregate|
328
377
  if aggregate
329
378
  @schema.replace_aggregate(aggregate)
330
379
  else
331
380
  @schema.delete_aggregate(keyspace_name, aggregate_name, parsed_aggregate_args)
332
381
  end
333
382
 
334
- @logger.info("Refreshed user-defined aggregate \"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"")
383
+ @logger.info('Refreshed user-defined aggregate ' \
384
+ "\"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"")
335
385
  end
336
386
  end
337
387
 
@@ -360,7 +410,8 @@ module Cassandra
360
410
 
361
411
  def refresh_peers_async_retry(error, schedule)
362
412
  timeout = schedule.next
363
- @logger.info("Failed to refresh hosts (#{error.class.name}: #{error.message}), retrying in #{timeout}")
413
+ @logger.info("Failed to refresh hosts (#{error.class.name}: " \
414
+ "#{error.message}), retrying in #{timeout}")
364
415
 
365
416
  timer = @io_reactor.schedule_timer(timeout)
366
417
  timer.flat_map do
@@ -381,9 +432,11 @@ module Cassandra
381
432
  def refresh_peers_async
382
433
  connection = @connection
383
434
 
384
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
435
+ if connection.nil?
436
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
437
+ end
385
438
 
386
- @logger.info("Refreshing peers metadata")
439
+ @logger.info('Refreshing peers metadata')
387
440
 
388
441
  send_select_request(connection, SELECT_PEERS).map do |peers|
389
442
  @logger.debug("#{peers.size} peer(s) found")
@@ -403,7 +456,7 @@ module Cassandra
403
456
  @registry.host_lost(host.ip) unless ips.include?(host.ip)
404
457
  end
405
458
 
406
- @logger.info("Refreshed peers metadata")
459
+ @logger.info('Refreshed peers metadata')
407
460
 
408
461
  nil
409
462
  end
@@ -412,7 +465,9 @@ module Cassandra
412
465
  def refresh_metadata_async
413
466
  connection = @connection
414
467
 
415
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
468
+ if connection.nil?
469
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
470
+ end
416
471
 
417
472
  @logger.info("Refreshing connected host's metadata")
418
473
 
@@ -452,7 +507,7 @@ module Cassandra
452
507
  end
453
508
 
454
509
  timer.on_value do
455
- refresh_host_status(host).fallback do |e|
510
+ refresh_host_status(host).fallback do |_e|
456
511
  refresh_host_status_with_retry(timer, host, schedule)
457
512
  end
458
513
  end
@@ -483,7 +538,8 @@ module Cassandra
483
538
 
484
539
  def refresh_host_async_retry(address, error, schedule)
485
540
  timeout = schedule.next
486
- @logger.info("Failed to refresh host #{address.to_s} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
541
+ @logger.info("Failed to refresh host #{address} (#{error.class.name}: " \
542
+ "#{error.message}), retrying in #{timeout}")
487
543
 
488
544
  timer = @io_reactor.schedule_timer(timeout)
489
545
  timer.flat_map do
@@ -503,17 +559,22 @@ module Cassandra
503
559
 
504
560
  def refresh_host_async(address)
505
561
  connection = @connection
506
- return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
562
+ if connection.nil?
563
+ return Ione::Future.failed(Errors::ClientError.new('Not connected'))
564
+ end
507
565
 
508
566
  ip = address.to_s
509
567
 
510
568
  @logger.info("Refreshing host metadata: #{ip}")
511
569
 
512
- if ip == connection.host
513
- request = SELECT_LOCAL
514
- else
515
- request = Protocol::QueryRequest.new("SELECT rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers WHERE peer = '%s'" % ip, EMPTY_LIST, EMPTY_LIST, :one)
516
- end
570
+ request = if ip == connection.host
571
+ SELECT_LOCAL
572
+ else
573
+ Protocol::QueryRequest.new(SELECT_PEER_QUERY % ip,
574
+ EMPTY_LIST,
575
+ EMPTY_LIST,
576
+ :one)
577
+ end
517
578
 
518
579
  send_select_request(connection, request).map do |rows|
519
580
  if rows.empty?
@@ -568,9 +629,10 @@ Control connection failed and is unlikely to recover.
568
629
  end
569
630
 
570
631
  if cause
571
- @logger.info("Control connection closed (#{cause.class.name}: #{cause.message})")
632
+ @logger.info('Control connection closed ' \
633
+ "(#{cause.class.name}: #{cause.message})")
572
634
  else
573
- @logger.info("Control connection closed")
635
+ @logger.info('Control connection closed')
574
636
  end
575
637
 
576
638
  @connection = nil
@@ -585,9 +647,12 @@ Control connection failed and is unlikely to recover.
585
647
  end
586
648
  f = f.flat_map { register_async }
587
649
  f = f.flat_map { refresh_peers_async_maybe_retry }
588
- f = f.flat_map { refresh_maybe_retry(:schema) } if @connection_options.synchronize_schema?
650
+ if @connection_options.synchronize_schema?
651
+ f = f.flat_map { refresh_maybe_retry(:schema) }
652
+ end
589
653
  f = f.fallback do |error|
590
- @logger.debug("Connection to #{host.ip} failed (#{error.class.name}: #{error.message})")
654
+ @logger.debug("Connection to #{host.ip} failed " \
655
+ "(#{error.class.name}: #{error.message})")
591
656
 
592
657
  case error
593
658
  when Errors::HostError, Errors::TimeoutError
@@ -599,8 +664,8 @@ Control connection failed and is unlikely to recover.
599
664
  end
600
665
  end
601
666
 
602
- f.on_complete do |f|
603
- @logger.info('Control connection established') if f.resolved?
667
+ f.on_complete do |connection_future|
668
+ @logger.info('Control connection established') if connection_future.resolved?
604
669
  end
605
670
 
606
671
  f
@@ -631,7 +696,7 @@ Control connection failed and is unlikely to recover.
631
696
  schema_changes.each do |change|
632
697
  keyspace = change.keyspace
633
698
 
634
- next if refresh_keyspaces.has_key?(keyspace)
699
+ next if refresh_keyspaces.key?(keyspace)
635
700
 
636
701
  case change.target
637
702
  when Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
@@ -675,13 +740,15 @@ Control connection failed and is unlikely to recover.
675
740
 
676
741
  refresh_functions.each do |(keyspace, function_changes)|
677
742
  function_changes.each do |change|
678
- futures << refresh_maybe_retry(:function, keyspace, change.name, change.arguments)
743
+ futures <<
744
+ refresh_maybe_retry(:function, keyspace, change.name, change.arguments)
679
745
  end
680
746
  end
681
747
 
682
748
  refresh_aggregates.each do |(keyspace, aggregate_changes)|
683
749
  aggregate_changes.each do |change|
684
- futures << refresh_maybe_retry(:aggregate, keyspace, change.name, change.arguments)
750
+ futures <<
751
+ refresh_maybe_retry(:aggregate, keyspace, change.name, change.arguments)
685
752
  end
686
753
  end
687
754
 
@@ -704,7 +771,8 @@ Control connection failed and is unlikely to recover.
704
771
 
705
772
  def refresh_retry(what, error, schedule, *args)
706
773
  timeout = schedule.next
707
- @logger.info("Failed to refresh #{what} #{args.inspect} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
774
+ @logger.info("Failed to refresh #{what} #{args.inspect} " \
775
+ "(#{error.class.name}: #{error.message}), retrying in #{timeout}")
708
776
 
709
777
  timer = @io_reactor.schedule_timer(timeout)
710
778
  timer.flat_map do
@@ -722,38 +790,105 @@ Control connection failed and is unlikely to recover.
722
790
  end
723
791
  end
724
792
 
725
- def handle_schema_change(change)
726
- timer = nil
727
- expiration_timer = nil
728
-
793
+ def refresh_schema_async_wrapper
794
+ # This is kinda tricky. We want to start refreshing the schema asynchronously.
795
+ # However, if we're already in the process of doing so, return the future
796
+ # representing that result rather than starting another schema refresh.
797
+ #
798
+ # A few other nuances while a refresh is in progress:
799
+ # * if a new attempt is made to refresh, keep track of that and schedule another
800
+ # refresh after the current one completes.
801
+ # * we don't want schema_change events to be processed since the full refresh
802
+ # may overwrite the results of handling the schema_change events with older
803
+ # data. That said, we don't want to lose track of schema_change events; just
804
+ # delay processing them until after the full refresh is done.
805
+ #
806
+ # Finally, when a full refresh begins, clear out any pending changes in
807
+ # @schema_changes because the full refresh subsumes them. This has two benefits:
808
+ # 1. avoid round trips to Cassandra to get details related to those schema
809
+ # changes.
810
+ # 2. avoid race conditions where those updates may return older data than our
811
+ # full refresh and might win as last writer with that potentially older data.
729
812
  synchronize do
730
- @schema_changes << change
813
+ if @refresh_schema_future
814
+ @pending_schema_refresh = true
815
+ return @refresh_schema_future
816
+ end
817
+
818
+ # Fresh refresh; prep this connection!
731
819
 
820
+ # Since we're starting a new refresh, there can be no pending refresh request.
821
+ @pending_schema_refresh = false
822
+
823
+ # Clear outstanding schema changes and timers.
824
+ @schema_changes = []
732
825
  @io_reactor.cancel_timer(@schema_refresh_timer) if @schema_refresh_timer
733
- timer = @schema_refresh_timer = @io_reactor.schedule_timer(@connection_options.schema_refresh_delay)
826
+ @schema_refresh_timer = nil
827
+ @io_reactor.cancel_timer(@schema_refresh_window) if @schema_refresh_window
828
+ @schema_refresh_window = nil
829
+
830
+ # Start refreshing..
831
+ @refresh_schema_future = refresh_maybe_retry(:schema)
832
+ @refresh_schema_future.on_complete do
833
+ pending = false
834
+ synchronize do
835
+ # We're done refreshing. If we have a pending refresh, launch it now.
836
+ @refresh_schema_future = nil
837
+ pending = @pending_schema_refresh
838
+ @pending_schema_refresh = false
839
+ unless pending
840
+ # Restore timers if there are pending schema changes.
841
+ handle_schema_change(nil)
842
+ end
843
+ end
734
844
 
735
- unless @schema_refresh_window
736
- expiration_timer = @schema_refresh_window = @io_reactor.schedule_timer(@connection_options.schema_refresh_timeout)
845
+ refresh_schema_async_wrapper if pending
737
846
  end
847
+
848
+ # Return the (now cached) future
849
+ @refresh_schema_future
738
850
  end
851
+ end
739
852
 
740
- if expiration_timer
741
- expiration_timer.on_value do
742
- schema_changes = nil
853
+ def handle_schema_change(change)
854
+ timer = nil
855
+ expiration_timer = nil
743
856
 
744
- synchronize do
745
- @io_reactor.cancel_timer(@schema_refresh_timer)
857
+ synchronize do
858
+ # If change is nil, it means we want to set up timers (if there are pending
859
+ # changes). Otherwise, we definitely have a change and want to set up timers.
860
+ # Also, we only want to set up timers if we're not in the middle of a full
861
+ # refresh.
862
+ @schema_changes << change if change
863
+
864
+ unless @schema_changes.empty? || @refresh_schema_future
865
+ @io_reactor.cancel_timer(@schema_refresh_timer) if @schema_refresh_timer
866
+ timer = @schema_refresh_timer =
867
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_delay)
868
+
869
+ unless @schema_refresh_window
870
+ @schema_refresh_window =
871
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_timeout)
872
+ expiration_timer = @schema_refresh_window
873
+ end
874
+ end
875
+ end
746
876
 
747
- @schema_refresh_window = nil
748
- @schema_refresh_timer = nil
877
+ expiration_timer.on_value do
878
+ schema_changes = nil
749
879
 
750
- schema_changes = @schema_changes
751
- @schema_changes = ::Array.new
752
- end
880
+ synchronize do
881
+ @io_reactor.cancel_timer(@schema_refresh_timer)
753
882
 
754
- process_schema_changes(schema_changes)
883
+ @schema_refresh_window = nil
884
+ @schema_refresh_timer = nil
885
+
886
+ schema_changes = @schema_changes
887
+ @schema_changes = ::Array.new
755
888
  end
756
- end
889
+
890
+ process_schema_changes(schema_changes)
891
+ end if expiration_timer
757
892
 
758
893
  timer.on_value do
759
894
  schema_changes = nil
@@ -762,14 +897,14 @@ Control connection failed and is unlikely to recover.
762
897
  @io_reactor.cancel_timer(@schema_refresh_window)
763
898
 
764
899
  @schema_refresh_window = nil
765
- @schema_refresh_timer = nil
900
+ @schema_refresh_timer = nil
766
901
 
767
- schema_changes = @schema_changes
902
+ schema_changes = @schema_changes
768
903
  @schema_changes = ::Array.new
769
904
  end
770
905
 
771
906
  process_schema_changes(schema_changes)
772
- end
907
+ end if timer
773
908
 
774
909
  nil
775
910
  end