cassandra-driver 3.0.0.beta.1 → 3.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 (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