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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +106 -39
  3. data/lib/cassandra.rb +396 -148
  4. data/lib/cassandra/address_resolution.rb +1 -1
  5. data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +1 -1
  6. data/lib/cassandra/address_resolution/policies/none.rb +1 -1
  7. data/lib/cassandra/aggregate.rb +21 -7
  8. data/lib/cassandra/argument.rb +2 -2
  9. data/lib/cassandra/attr_boolean.rb +33 -0
  10. data/lib/cassandra/auth.rb +6 -5
  11. data/lib/cassandra/auth/providers.rb +1 -1
  12. data/lib/cassandra/auth/providers/password.rb +5 -13
  13. data/lib/cassandra/cassandra_logger.rb +80 -0
  14. data/lib/cassandra/cluster.rb +49 -9
  15. data/lib/cassandra/cluster/client.rb +835 -209
  16. data/lib/cassandra/cluster/connection_pool.rb +2 -2
  17. data/lib/cassandra/cluster/connector.rb +86 -27
  18. data/lib/cassandra/cluster/control_connection.rb +222 -95
  19. data/lib/cassandra/cluster/failed_connection.rb +1 -1
  20. data/lib/cassandra/cluster/metadata.rb +14 -8
  21. data/lib/cassandra/cluster/options.rb +68 -22
  22. data/lib/cassandra/cluster/registry.rb +81 -17
  23. data/lib/cassandra/cluster/schema.rb +70 -8
  24. data/lib/cassandra/cluster/schema/cql_type_parser.rb +15 -10
  25. data/lib/cassandra/cluster/schema/fetchers.rb +601 -241
  26. data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +39 -38
  27. data/lib/cassandra/cluster/schema/partitioners.rb +1 -1
  28. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +6 -8
  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 +4 -23
  36. data/lib/cassandra/column_container.rb +322 -0
  37. data/lib/cassandra/compression.rb +1 -1
  38. data/lib/cassandra/compression/compressors/lz4.rb +7 -8
  39. data/lib/cassandra/compression/compressors/snappy.rb +4 -3
  40. data/lib/cassandra/driver.rb +107 -46
  41. data/lib/cassandra/errors.rb +303 -52
  42. data/lib/cassandra/execution/info.rb +16 -5
  43. data/lib/cassandra/execution/options.rb +102 -55
  44. data/lib/cassandra/execution/trace.rb +16 -9
  45. data/lib/cassandra/executors.rb +1 -1
  46. data/lib/cassandra/function.rb +19 -13
  47. data/lib/cassandra/function_collection.rb +85 -0
  48. data/lib/cassandra/future.rb +101 -49
  49. data/lib/cassandra/host.rb +25 -5
  50. data/lib/cassandra/index.rb +118 -0
  51. data/lib/cassandra/keyspace.rb +169 -33
  52. data/lib/cassandra/listener.rb +1 -1
  53. data/lib/cassandra/load_balancing.rb +2 -2
  54. data/lib/cassandra/load_balancing/policies.rb +1 -1
  55. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +39 -25
  56. data/lib/cassandra/load_balancing/policies/round_robin.rb +8 -1
  57. data/lib/cassandra/load_balancing/policies/token_aware.rb +22 -13
  58. data/lib/cassandra/load_balancing/policies/white_list.rb +18 -5
  59. data/lib/cassandra/materialized_view.rb +90 -0
  60. data/lib/cassandra/null_logger.rb +27 -6
  61. data/lib/cassandra/protocol.rb +1 -1
  62. data/lib/cassandra/protocol/coder.rb +81 -42
  63. data/lib/cassandra/protocol/cql_byte_buffer.rb +58 -44
  64. data/lib/cassandra/protocol/cql_protocol_handler.rb +57 -54
  65. data/lib/cassandra/protocol/request.rb +6 -7
  66. data/lib/cassandra/protocol/requests/auth_response_request.rb +3 -3
  67. data/lib/cassandra/protocol/requests/batch_request.rb +17 -8
  68. data/lib/cassandra/protocol/requests/credentials_request.rb +3 -3
  69. data/lib/cassandra/protocol/requests/execute_request.rb +39 -20
  70. data/lib/cassandra/protocol/requests/options_request.rb +1 -1
  71. data/lib/cassandra/protocol/requests/prepare_request.rb +5 -5
  72. data/lib/cassandra/protocol/requests/query_request.rb +28 -23
  73. data/lib/cassandra/protocol/requests/register_request.rb +2 -2
  74. data/lib/cassandra/protocol/requests/startup_request.rb +8 -8
  75. data/lib/cassandra/protocol/requests/void_query_request.rb +1 -1
  76. data/lib/cassandra/protocol/response.rb +3 -4
  77. data/lib/cassandra/protocol/responses/already_exists_error_response.rb +12 -2
  78. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +4 -5
  79. data/lib/cassandra/protocol/responses/auth_success_response.rb +4 -5
  80. data/lib/cassandra/protocol/responses/authenticate_response.rb +4 -5
  81. data/lib/cassandra/protocol/responses/error_response.rb +104 -17
  82. data/lib/cassandra/protocol/responses/event_response.rb +3 -4
  83. data/lib/cassandra/protocol/responses/function_failure_error_response.rb +13 -2
  84. data/lib/cassandra/protocol/responses/prepared_result_response.rb +14 -9
  85. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +14 -9
  86. data/lib/cassandra/protocol/responses/read_failure_error_response.rb +26 -4
  87. data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +22 -3
  88. data/lib/cassandra/protocol/responses/ready_response.rb +6 -7
  89. data/lib/cassandra/protocol/responses/result_response.rb +11 -10
  90. data/lib/cassandra/protocol/responses/rows_result_response.rb +8 -7
  91. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +8 -8
  92. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +19 -13
  93. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +5 -6
  94. data/lib/cassandra/protocol/responses/status_change_event_response.rb +5 -6
  95. data/lib/cassandra/protocol/responses/supported_response.rb +4 -5
  96. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +4 -5
  97. data/lib/cassandra/protocol/responses/unavailable_error_response.rb +20 -3
  98. data/lib/cassandra/protocol/responses/unprepared_error_response.rb +11 -2
  99. data/lib/cassandra/protocol/responses/void_result_response.rb +4 -5
  100. data/lib/cassandra/protocol/responses/write_failure_error_response.rb +26 -4
  101. data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +22 -3
  102. data/lib/cassandra/protocol/v1.rb +98 -37
  103. data/lib/cassandra/protocol/v3.rb +121 -50
  104. data/lib/cassandra/protocol/v4.rb +172 -68
  105. data/lib/cassandra/reconnection.rb +1 -1
  106. data/lib/cassandra/reconnection/policies.rb +1 -1
  107. data/lib/cassandra/reconnection/policies/constant.rb +2 -4
  108. data/lib/cassandra/reconnection/policies/exponential.rb +6 -6
  109. data/lib/cassandra/result.rb +55 -20
  110. data/lib/cassandra/retry.rb +8 -8
  111. data/lib/cassandra/retry/policies.rb +1 -1
  112. data/lib/cassandra/retry/policies/default.rb +1 -1
  113. data/lib/cassandra/retry/policies/downgrading_consistency.rb +4 -2
  114. data/lib/cassandra/retry/policies/fallthrough.rb +1 -1
  115. data/lib/cassandra/session.rb +24 -16
  116. data/lib/cassandra/statement.rb +1 -1
  117. data/lib/cassandra/statements.rb +1 -1
  118. data/lib/cassandra/statements/batch.rb +16 -10
  119. data/lib/cassandra/statements/bound.rb +10 -3
  120. data/lib/cassandra/statements/prepared.rb +62 -18
  121. data/lib/cassandra/statements/simple.rb +23 -10
  122. data/lib/cassandra/statements/void.rb +1 -1
  123. data/lib/cassandra/table.rb +53 -185
  124. data/lib/cassandra/time.rb +11 -6
  125. data/lib/cassandra/time_uuid.rb +12 -14
  126. data/lib/cassandra/timestamp_generator.rb +37 -0
  127. data/lib/cassandra/timestamp_generator/simple.rb +38 -0
  128. data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
  129. data/lib/cassandra/tuple.rb +4 -4
  130. data/lib/cassandra/types.rb +109 -71
  131. data/lib/cassandra/udt.rb +66 -50
  132. data/lib/cassandra/util.rb +155 -15
  133. data/lib/cassandra/uuid.rb +20 -21
  134. data/lib/cassandra/uuid/generator.rb +7 -5
  135. data/lib/cassandra/version.rb +2 -2
  136. data/lib/cassandra_murmur3.jar +0 -0
  137. data/lib/datastax/cassandra.rb +1 -1
  138. metadata +27 -16
@@ -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,20 +128,31 @@ 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|
139
+ # connection is a CqlProtocolHandler
126
140
  f = request_options(connection)
127
141
  f = f.flat_map do |options|
128
142
  compression = @connection_options.compression
129
143
  supported_algorithms = options['COMPRESSION']
130
144
 
131
145
  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}")
146
+ @logger.warn("Compression with #{compression.inspect} is not supported " \
147
+ "by host at #{host.ip}, supported algorithms are " \
148
+ "#{supported_algorithms.inspect}")
133
149
  compression = nil
134
150
  end
135
151
 
136
152
  supported_cql_versions = options['CQL_VERSION']
137
- cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ? supported_cql_versions.first : '3.1.0'
153
+ cql_version = (supported_cql_versions && !supported_cql_versions.empty?) ?
154
+ supported_cql_versions.first :
155
+ '3.1.0'
138
156
 
139
157
  startup_connection(connection, cql_version, compression)
140
158
  end
@@ -142,9 +160,19 @@ module Cassandra
142
160
  case error
143
161
  when Errors::ProtocolError
144
162
  synchronize do
145
- if @connection_options.protocol_version > 1
146
- @logger.info("Host #{host.ip} doesn't support protocol version #{@connection_options.protocol_version}, downgrading")
147
- @connection_options.protocol_version -= 1
163
+ current_version = connection.protocol_version
164
+ if current_version > 1 && @connection_options.protocol_negotiable?
165
+ @logger.info("Host #{host.ip} doesn't support protocol version " \
166
+ "#{current_version}, downgrading")
167
+
168
+ # This is tricky. We want to try with the next lower protocol version.
169
+ # However, the connection_options used for all connections may have
170
+ # already been updated due to other node connection failures. So,
171
+ # it may already have a lower protocol-version than our current-1. We
172
+ # don't want to accidentally raise it, so we update it to the min
173
+ # of itself and current-1.
174
+ @connection_options.protocol_version =
175
+ [@connection_options.protocol_version, current_version - 1].min
148
176
  do_connect(host)
149
177
  else
150
178
  Ione::Future.failed(error)
@@ -152,7 +180,7 @@ module Cassandra
152
180
  end
153
181
  when Errors::TimeoutError
154
182
  future = Ione::CompletableFuture.new
155
- connection.close(error).on_complete do |f|
183
+ connection.close(error).on_complete do |_|
156
184
  future.fail(error)
157
185
  end
158
186
  future
@@ -173,7 +201,8 @@ module Cassandra
173
201
  end
174
202
 
175
203
  def startup_connection(connection, cql_version, compression)
176
- connection.send_request(Protocol::StartupRequest.new(cql_version, compression), @execution_options.timeout).flat_map do |r|
204
+ connection.send_request(Protocol::StartupRequest.new(cql_version, compression),
205
+ @execution_options.timeout).flat_map do |r|
177
206
  case r
178
207
  when Protocol::AuthenticateResponse
179
208
  if @connection_options.protocol_version == 1
@@ -181,28 +210,48 @@ module Cassandra
181
210
  if credentials
182
211
  send_credentials(connection, credentials)
183
212
  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))
213
+ Ione::Future.failed(cannot_authenticate_error)
185
214
  end
186
215
  else
187
- authenticator = @connection_options.create_authenticator(r.authentication_class)
216
+ authenticator = @connection_options.create_authenticator(
217
+ r.authentication_class)
188
218
  if authenticator
189
- challenge_response_cycle(connection, authenticator, authenticator.initial_response)
219
+ challenge_response_cycle(connection,
220
+ authenticator,
221
+ authenticator.initial_response)
190
222
  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))
223
+ Ione::Future.failed(cannot_authenticate_error)
192
224
  end
193
225
  end
194
226
  when Protocol::ReadyResponse
195
227
  ::Ione::Future.resolved(connection)
196
228
  when Protocol::ErrorResponse
197
- ::Ione::Future.failed(r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
229
+ ::Ione::Future.failed(
230
+ r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
198
231
  else
199
- ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
232
+ ::Ione::Future.failed(
233
+ Errors::InternalError.new("Unexpected response #{r.inspect}"))
200
234
  end
201
235
  end
202
236
  end
203
237
 
238
+ def cannot_authenticate_error
239
+ Errors::AuthenticationError.new(
240
+ 'Server requested authentication, but client was not configured to ' \
241
+ 'authenticate',
242
+ nil,
243
+ nil,
244
+ nil,
245
+ VOID_STATEMENT,
246
+ VOID_OPTIONS,
247
+ EMPTY_LIST,
248
+ :one,
249
+ 0)
250
+ end
251
+
204
252
  def request_options(connection)
205
- connection.send_request(Protocol::OptionsRequest.new, @execution_options.timeout).map do |r|
253
+ connection.send_request(Protocol::OptionsRequest.new,
254
+ @execution_options.timeout).map do |r|
206
255
  case r
207
256
  when Protocol::SupportedResponse
208
257
  r.options
@@ -215,7 +264,8 @@ module Cassandra
215
264
  end
216
265
 
217
266
  def send_credentials(connection, credentials)
218
- connection.send_request(Protocol::CredentialsRequest.new(credentials), @execution_options.timeout).map do |r|
267
+ connection.send_request(Protocol::CredentialsRequest.new(credentials),
268
+ @execution_options.timeout).map do |r|
219
269
  case r
220
270
  when Protocol::ReadyResponse
221
271
  connection
@@ -228,18 +278,25 @@ module Cassandra
228
278
  end
229
279
 
230
280
  def challenge_response_cycle(connection, authenticator, token)
231
- connection.send_request(Protocol::AuthResponseRequest.new(token), @execution_options.timeout).flat_map do |r|
281
+ connection.send_request(Protocol::AuthResponseRequest.new(token),
282
+ @execution_options.timeout).flat_map do |r|
232
283
  case r
233
284
  when Protocol::AuthChallengeResponse
234
285
  token = authenticator.challenge_response(r.token)
235
286
  challenge_response_cycle(pending_connection, authenticator, token)
236
287
  when Protocol::AuthSuccessResponse
237
- authenticator.authentication_successful(r.token) rescue nil
288
+ begin
289
+ authenticator.authentication_successful(r.token)
290
+ rescue
291
+ nil
292
+ end
238
293
  ::Ione::Future.resolved(connection)
239
294
  when Protocol::ErrorResponse
240
- ::Ione::Future.failed(r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
295
+ ::Ione::Future.failed(
296
+ r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
241
297
  else
242
- ::Ione::Future.failed(Errors::InternalError.new("Unexpected response #{r.inspect}"))
298
+ ::Ione::Future.failed(
299
+ Errors::InternalError.new("Unexpected response #{r.inspect}"))
243
300
  end
244
301
  end
245
302
  end
@@ -282,7 +339,8 @@ module Cassandra
282
339
  end
283
340
  end
284
341
 
285
- @logger.debug("Host #{host.ip} closed connection (#{error.class.name}: #{error.message})") if error
342
+ @logger.debug("Host #{host.ip} closed connection (#{error.class.name}: " \
343
+ "#{error.message})") if error
286
344
 
287
345
  if notify
288
346
  @logger.warn("Host #{host.ip} closed all connections")
@@ -296,10 +354,11 @@ module Cassandra
296
354
  notify = false
297
355
 
298
356
  synchronize do
299
- notify = !error.nil? && !@connections.has_key?(host)
357
+ notify = !error.nil? && !@connections.key?(host)
300
358
  end
301
359
 
302
- @logger.debug("Host #{host.ip} refused connection (#{error.class.name}: #{error.message})")
360
+ @logger.debug("Host #{host.ip} refused connection (#{error.class.name}: " \
361
+ "#{error.message})")
303
362
 
304
363
  if notify
305
364
  @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 * ' \
151
+ 'FROM system.local',
152
+ EMPTY_LIST,
153
+ EMPTY_LIST,
154
+ :one)
155
+ SELECT_PEERS = Protocol::QueryRequest.new(
156
+ 'SELECT * ' \
157
+ 'FROM system.peers',
158
+ EMPTY_LIST,
159
+ EMPTY_LIST,
160
+ :one)
161
+
162
+ SELECT_PEER_QUERY =
163
+ 'SELECT * ' \
164
+ 'FROM system.peers ' \
165
+ "WHERE peer = '%s'".freeze
144
166
 
145
167
  def reconnect_async(schedule)
146
168
  timeout = schedule.next
@@ -159,11 +181,10 @@ 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
 
@@ -190,7 +211,7 @@ module Cassandra
190
211
  raise Errors::InternalError, "Unexpected response #{r.inspect}"
191
212
  end
192
213
  end
193
- f = f.map do
214
+ f.map do
194
215
  connection.on_event do |event|
195
216
  @logger.debug("Event received #{event}")
196
217
 
@@ -205,7 +226,7 @@ module Cassandra
205
226
  @registry.host_up(address)
206
227
  else
207
228
  refresh_host_async_maybe_retry(address)
208
- refresh_maybe_retry(:schema)
229
+ refresh_schema_async_wrapper
209
230
  end
210
231
  when 'DOWN'
211
232
  @registry.host_down(event.address)
@@ -214,11 +235,11 @@ module Cassandra
214
235
 
215
236
  unless @registry.has_host?(address)
216
237
  refresh_host_async_maybe_retry(address)
217
- refresh_maybe_retry(:schema)
238
+ refresh_schema_async_wrapper
218
239
  end
219
240
  when 'REMOVED_NODE'
220
241
  @registry.host_lost(event.address)
221
- refresh_maybe_retry(:schema)
242
+ refresh_schema_async_wrapper
222
243
  end
223
244
  end
224
245
  end
@@ -230,14 +251,14 @@ module Cassandra
230
251
  def refresh_schema_async
231
252
  connection = @connection
232
253
 
233
- @logger.info("Refreshing schema")
254
+ @logger.info('Refreshing schema')
234
255
 
235
256
  return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
236
257
 
237
258
  @schema_fetcher.fetch(connection).map do |keyspaces|
238
259
  @schema.replace(keyspaces)
239
260
  @metadata.rebuild_token_map
240
- @logger.info("Schema refreshed")
261
+ @logger.info('Schema refreshed')
241
262
  end
242
263
  end
243
264
 
@@ -255,7 +276,7 @@ module Cassandra
255
276
  @schema.delete_keyspace(keyspace_name)
256
277
  end
257
278
 
258
- @logger.info("Refreshed keyspace \"#{keyspace_name}\"")
279
+ @logger.info("Completed refreshing keyspace \"#{keyspace_name}\"")
259
280
  end
260
281
  end
261
282
 
@@ -273,7 +294,25 @@ module Cassandra
273
294
  @schema.delete_table(keyspace_name, table_name)
274
295
  end
275
296
 
276
- @logger.info("Refreshed table \"#{keyspace_name}.#{table_name}\"")
297
+ @logger.info("Completed refreshing table \"#{keyspace_name}.#{table_name}\"")
298
+ end
299
+ end
300
+
301
+ def refresh_materialized_view_async(keyspace_name, view_name)
302
+ connection = @connection
303
+
304
+ return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
305
+
306
+ @logger.info("Refreshing materialized view \"#{keyspace_name}.#{view_name}\"")
307
+
308
+ @schema_fetcher.fetch_materialized_view(connection, keyspace_name, view_name).map do |view|
309
+ if view
310
+ @schema.replace_materialized_view(view)
311
+ else
312
+ @schema.delete_materialized_view(keyspace_name, view_name)
313
+ end
314
+
315
+ @logger.info("Completed refreshing materialized view \"#{keyspace_name}.#{view_name}\"")
277
316
  end
278
317
  end
279
318
 
@@ -291,7 +330,7 @@ module Cassandra
291
330
  @schema.delete_type(keyspace_name, type_name)
292
331
  end
293
332
 
294
- @logger.info("Refreshed user-defined type \"#{keyspace_name}.#{type_name}\"")
333
+ @logger.info("Completed refreshing user-defined type \"#{keyspace_name}.#{type_name}\"")
295
334
  end
296
335
  end
297
336
 
@@ -300,18 +339,24 @@ module Cassandra
300
339
 
301
340
  return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
302
341
 
303
- @logger.info("Refreshing user-defined function \"#{keyspace_name}.#{function_name}\"")
342
+ @logger.info('Refreshing user-defined function ' \
343
+ "\"#{keyspace_name}.#{function_name}\"")
304
344
 
305
345
  # 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|
346
+ parsed_function_args =
347
+ @schema_fetcher.parse_argument_types(connection, keyspace_name, function_args)
348
+ @schema_fetcher.fetch_function(connection,
349
+ keyspace_name,
350
+ function_name,
351
+ parsed_function_args).map do |function|
308
352
  if function
309
353
  @schema.replace_function(function)
310
354
  else
311
355
  @schema.delete_function(keyspace_name, function_name, parsed_function_args)
312
356
  end
313
357
 
314
- @logger.info("Refreshed user-defined function \"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"")
358
+ @logger.info('Completed refreshing user-defined function ' \
359
+ "\"#{keyspace_name}.#{function_name}(#{function_args.join(',')})\"")
315
360
  end
316
361
  end
317
362
 
@@ -320,18 +365,25 @@ module Cassandra
320
365
 
321
366
  return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
322
367
 
323
- @logger.info("Refreshing user-defined aggregate \"#{keyspace_name}.#{aggregate_name}\"")
368
+ @logger.info('Refreshing user-defined aggregate ' \
369
+ "\"#{keyspace_name}.#{aggregate_name}\"")
324
370
 
325
371
  # 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|
372
+ parsed_aggregate_args = @schema_fetcher.parse_argument_types(connection,
373
+ keyspace_name,
374
+ aggregate_args)
375
+ @schema_fetcher.fetch_aggregate(connection,
376
+ keyspace_name,
377
+ aggregate_name,
378
+ parsed_aggregate_args).map do |aggregate|
328
379
  if aggregate
329
380
  @schema.replace_aggregate(aggregate)
330
381
  else
331
382
  @schema.delete_aggregate(keyspace_name, aggregate_name, parsed_aggregate_args)
332
383
  end
333
384
 
334
- @logger.info("Refreshed user-defined aggregate \"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"")
385
+ @logger.info('Completed refreshing user-defined aggregate ' \
386
+ "\"#{keyspace_name}.#{aggregate_name}(#{aggregate_args.join(',')})\"")
335
387
  end
336
388
  end
337
389
 
@@ -360,7 +412,8 @@ module Cassandra
360
412
 
361
413
  def refresh_peers_async_retry(error, schedule)
362
414
  timeout = schedule.next
363
- @logger.info("Failed to refresh hosts (#{error.class.name}: #{error.message}), retrying in #{timeout}")
415
+ @logger.info("Failed to refresh hosts (#{error.class.name}: " \
416
+ "#{error.message}), retrying in #{timeout}")
364
417
 
365
418
  timer = @io_reactor.schedule_timer(timeout)
366
419
  timer.flat_map do
@@ -383,7 +436,7 @@ module Cassandra
383
436
 
384
437
  return Ione::Future.failed(Errors::ClientError.new('Not connected')) if connection.nil?
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('Completed refreshing peers metadata')
407
460
 
408
461
  nil
409
462
  end
@@ -417,15 +470,13 @@ module Cassandra
417
470
  @logger.info("Refreshing connected host's metadata")
418
471
 
419
472
  send_select_request(connection, SELECT_LOCAL).map do |local|
420
- if local.empty?
421
- raise Errors::InternalError, "Unable to fetch connected host's metadata"
422
- else
423
- data = local.first
424
- @registry.host_found(IPAddr.new(connection.host), data)
425
- @metadata.update(data)
426
- end
473
+ raise Errors::InternalError, "Unable to fetch connected host's metadata" if local.empty?
474
+
475
+ data = local.first
476
+ @registry.host_found(IPAddr.new(connection.host), data)
477
+ @metadata.update(data)
427
478
 
428
- @logger.info("Refreshed connected host's metadata")
479
+ @logger.info("Completed refreshing connected host's metadata")
429
480
 
430
481
  nil
431
482
  end
@@ -452,7 +503,7 @@ module Cassandra
452
503
  end
453
504
 
454
505
  timer.on_value do
455
- refresh_host_status(host).fallback do |e|
506
+ refresh_host_status(host).fallback do |_e|
456
507
  refresh_host_status_with_retry(timer, host, schedule)
457
508
  end
458
509
  end
@@ -483,7 +534,8 @@ module Cassandra
483
534
 
484
535
  def refresh_host_async_retry(address, error, schedule)
485
536
  timeout = schedule.next
486
- @logger.info("Failed to refresh host #{address.to_s} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
537
+ @logger.info("Failed to refresh host #{address} (#{error.class.name}: " \
538
+ "#{error.message}), retrying in #{timeout}")
487
539
 
488
540
  timer = @io_reactor.schedule_timer(timeout)
489
541
  timer.flat_map do
@@ -509,19 +561,20 @@ module Cassandra
509
561
 
510
562
  @logger.info("Refreshing host metadata: #{ip}")
511
563
 
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
564
+ request = if ip == connection.host
565
+ SELECT_LOCAL
566
+ else
567
+ Protocol::QueryRequest.new(SELECT_PEER_QUERY % ip,
568
+ EMPTY_LIST,
569
+ EMPTY_LIST,
570
+ :one)
571
+ end
517
572
 
518
573
  send_select_request(connection, request).map do |rows|
519
- if rows.empty?
520
- raise Errors::InternalError, "Unable to find host metadata: #{ip}"
521
- else
522
- @logger.info("Refreshed host metadata: #{ip}")
523
- @registry.host_found(address, rows.first)
524
- end
574
+ raise Errors::InternalError, "Unable to find host metadata: #{ip}" if rows.empty?
575
+
576
+ @logger.info("Completed refreshing host metadata: #{ip}")
577
+ @registry.host_found(address, rows.first)
525
578
 
526
579
  self
527
580
  end
@@ -568,9 +621,10 @@ Control connection failed and is unlikely to recover.
568
621
  end
569
622
 
570
623
  if cause
571
- @logger.info("Control connection closed (#{cause.class.name}: #{cause.message})")
624
+ @logger.info('Control connection closed ' \
625
+ "(#{cause.class.name}: #{cause.message})")
572
626
  else
573
- @logger.info("Control connection closed")
627
+ @logger.info('Control connection closed')
574
628
  end
575
629
 
576
630
  @connection = nil
@@ -587,7 +641,8 @@ Control connection failed and is unlikely to recover.
587
641
  f = f.flat_map { refresh_peers_async_maybe_retry }
588
642
  f = f.flat_map { refresh_maybe_retry(:schema) } if @connection_options.synchronize_schema?
589
643
  f = f.fallback do |error|
590
- @logger.debug("Connection to #{host.ip} failed (#{error.class.name}: #{error.message})")
644
+ @logger.debug("Connection to #{host.ip} failed " \
645
+ "(#{error.class.name}: #{error.message})")
591
646
 
592
647
  case error
593
648
  when Errors::HostError, Errors::TimeoutError
@@ -599,8 +654,8 @@ Control connection failed and is unlikely to recover.
599
654
  end
600
655
  end
601
656
 
602
- f.on_complete do |f|
603
- @logger.info('Control connection established') if f.resolved?
657
+ f.on_complete do |connection_future|
658
+ @logger.info('Control connection established') if connection_future.resolved?
604
659
  end
605
660
 
606
661
  f
@@ -618,8 +673,8 @@ Control connection failed and is unlikely to recover.
618
673
  end
619
674
 
620
675
  def process_schema_changes(schema_changes)
621
- refresh_keyspaces = ::Hash.new
622
- refresh_tables = ::Hash.new
676
+ refresh_keyspaces = ::Hash.new
677
+ refresh_tables_and_views = ::Hash.new
623
678
  refresh_types = ::Hash.new
624
679
 
625
680
  # This hash is of the form <keyspace, [Change (for function changes)]>
@@ -631,18 +686,19 @@ Control connection failed and is unlikely to recover.
631
686
  schema_changes.each do |change|
632
687
  keyspace = change.keyspace
633
688
 
634
- next if refresh_keyspaces.has_key?(keyspace)
689
+ next if refresh_keyspaces.key?(keyspace)
635
690
 
636
691
  case change.target
637
692
  when Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
638
- refresh_tables.delete(keyspace)
693
+ refresh_tables_and_views.delete(keyspace)
639
694
  refresh_types.delete(keyspace)
640
695
  refresh_functions.delete(keyspace)
641
696
  refresh_aggregates.delete(keyspace)
642
697
  refresh_keyspaces[keyspace] = true
643
698
  when Protocol::Constants::SCHEMA_CHANGE_TARGET_TABLE
644
- tables = refresh_tables[keyspace] ||= ::Hash.new
645
- tables[change.name] = true
699
+ # We can't distinguish between table and view change events, so refresh both.
700
+ tables_and_views = refresh_tables_and_views[keyspace] ||= ::Hash.new
701
+ tables_and_views[change.name] = true
646
702
  when Protocol::Constants::SCHEMA_CHANGE_TARGET_UDT
647
703
  types = refresh_types[keyspace] ||= ::Hash.new
648
704
  types[change.name] = true
@@ -661,9 +717,10 @@ Control connection failed and is unlikely to recover.
661
717
  futures << refresh_maybe_retry(:keyspace, keyspace)
662
718
  end
663
719
 
664
- refresh_tables.each do |(keyspace, tables)|
665
- tables.each_key do |table|
666
- futures << refresh_maybe_retry(:table, keyspace, table)
720
+ refresh_tables_and_views.each do |(keyspace, tables_and_views)|
721
+ tables_and_views.each_key do |table_or_view|
722
+ futures << refresh_maybe_retry(:table, keyspace, table_or_view)
723
+ futures << refresh_maybe_retry(:materialized_view, keyspace, table_or_view)
667
724
  end
668
725
  end
669
726
 
@@ -675,13 +732,15 @@ Control connection failed and is unlikely to recover.
675
732
 
676
733
  refresh_functions.each do |(keyspace, function_changes)|
677
734
  function_changes.each do |change|
678
- futures << refresh_maybe_retry(:function, keyspace, change.name, change.arguments)
735
+ futures <<
736
+ refresh_maybe_retry(:function, keyspace, change.name, change.arguments)
679
737
  end
680
738
  end
681
739
 
682
740
  refresh_aggregates.each do |(keyspace, aggregate_changes)|
683
741
  aggregate_changes.each do |change|
684
- futures << refresh_maybe_retry(:aggregate, keyspace, change.name, change.arguments)
742
+ futures <<
743
+ refresh_maybe_retry(:aggregate, keyspace, change.name, change.arguments)
685
744
  end
686
745
  end
687
746
 
@@ -704,7 +763,8 @@ Control connection failed and is unlikely to recover.
704
763
 
705
764
  def refresh_retry(what, error, schedule, *args)
706
765
  timeout = schedule.next
707
- @logger.info("Failed to refresh #{what} #{args.inspect} (#{error.class.name}: #{error.message}), retrying in #{timeout}")
766
+ @logger.info("Failed to refresh #{what} #{args.inspect} " \
767
+ "(#{error.class.name}: #{error.message}), retrying in #{timeout}")
708
768
 
709
769
  timer = @io_reactor.schedule_timer(timeout)
710
770
  timer.flat_map do
@@ -722,38 +782,105 @@ Control connection failed and is unlikely to recover.
722
782
  end
723
783
  end
724
784
 
725
- def handle_schema_change(change)
726
- timer = nil
727
- expiration_timer = nil
728
-
785
+ def refresh_schema_async_wrapper
786
+ # This is kinda tricky. We want to start refreshing the schema asynchronously.
787
+ # However, if we're already in the process of doing so, return the future
788
+ # representing that result rather than starting another schema refresh.
789
+ #
790
+ # A few other nuances while a refresh is in progress:
791
+ # * if a new attempt is made to refresh, keep track of that and schedule another
792
+ # refresh after the current one completes.
793
+ # * we don't want schema_change events to be processed since the full refresh
794
+ # may overwrite the results of handling the schema_change events with older
795
+ # data. That said, we don't want to lose track of schema_change events; just
796
+ # delay processing them until after the full refresh is done.
797
+ #
798
+ # Finally, when a full refresh begins, clear out any pending changes in
799
+ # @schema_changes because the full refresh subsumes them. This has two benefits:
800
+ # 1. avoid round trips to Cassandra to get details related to those schema
801
+ # changes.
802
+ # 2. avoid race conditions where those updates may return older data than our
803
+ # full refresh and might win as last writer with that potentially older data.
729
804
  synchronize do
730
- @schema_changes << change
805
+ if @refresh_schema_future
806
+ @pending_schema_refresh = true
807
+ return @refresh_schema_future
808
+ end
809
+
810
+ # Fresh refresh; prep this connection!
731
811
 
812
+ # Since we're starting a new refresh, there can be no pending refresh request.
813
+ @pending_schema_refresh = false
814
+
815
+ # Clear outstanding schema changes and timers.
816
+ @schema_changes = []
732
817
  @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)
818
+ @schema_refresh_timer = nil
819
+ @io_reactor.cancel_timer(@schema_refresh_window) if @schema_refresh_window
820
+ @schema_refresh_window = nil
821
+
822
+ # Start refreshing..
823
+ @refresh_schema_future = refresh_maybe_retry(:schema)
824
+ @refresh_schema_future.on_complete do
825
+ pending = false
826
+ synchronize do
827
+ # We're done refreshing. If we have a pending refresh, launch it now.
828
+ @refresh_schema_future = nil
829
+ pending = @pending_schema_refresh
830
+ @pending_schema_refresh = false
831
+ unless pending
832
+ # Restore timers if there are pending schema changes.
833
+ handle_schema_change(nil)
834
+ end
835
+ end
734
836
 
735
- unless @schema_refresh_window
736
- expiration_timer = @schema_refresh_window = @io_reactor.schedule_timer(@connection_options.schema_refresh_timeout)
837
+ refresh_schema_async_wrapper if pending
737
838
  end
839
+
840
+ # Return the (now cached) future
841
+ @refresh_schema_future
738
842
  end
843
+ end
739
844
 
740
- if expiration_timer
741
- expiration_timer.on_value do
742
- schema_changes = nil
845
+ def handle_schema_change(change)
846
+ timer = nil
847
+ expiration_timer = nil
743
848
 
744
- synchronize do
745
- @io_reactor.cancel_timer(@schema_refresh_timer)
849
+ synchronize do
850
+ # If change is nil, it means we want to set up timers (if there are pending
851
+ # changes). Otherwise, we definitely have a change and want to set up timers.
852
+ # Also, we only want to set up timers if we're not in the middle of a full
853
+ # refresh.
854
+ @schema_changes << change if change
855
+
856
+ unless @schema_changes.empty? || @refresh_schema_future
857
+ @io_reactor.cancel_timer(@schema_refresh_timer) if @schema_refresh_timer
858
+ timer = @schema_refresh_timer =
859
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_delay)
860
+
861
+ unless @schema_refresh_window
862
+ @schema_refresh_window =
863
+ @io_reactor.schedule_timer(@connection_options.schema_refresh_timeout)
864
+ expiration_timer = @schema_refresh_window
865
+ end
866
+ end
867
+ end
746
868
 
747
- @schema_refresh_window = nil
748
- @schema_refresh_timer = nil
869
+ expiration_timer.on_value do
870
+ schema_changes = nil
749
871
 
750
- schema_changes = @schema_changes
751
- @schema_changes = ::Array.new
752
- end
872
+ synchronize do
873
+ @io_reactor.cancel_timer(@schema_refresh_timer)
753
874
 
754
- process_schema_changes(schema_changes)
875
+ @schema_refresh_window = nil
876
+ @schema_refresh_timer = nil
877
+
878
+ schema_changes = @schema_changes
879
+ @schema_changes = ::Array.new
755
880
  end
756
- end
881
+
882
+ process_schema_changes(schema_changes)
883
+ end if expiration_timer
757
884
 
758
885
  timer.on_value do
759
886
  schema_changes = nil
@@ -762,14 +889,14 @@ Control connection failed and is unlikely to recover.
762
889
  @io_reactor.cancel_timer(@schema_refresh_window)
763
890
 
764
891
  @schema_refresh_window = nil
765
- @schema_refresh_timer = nil
892
+ @schema_refresh_timer = nil
766
893
 
767
- schema_changes = @schema_changes
894
+ schema_changes = @schema_changes
768
895
  @schema_changes = ::Array.new
769
896
  end
770
897
 
771
898
  process_schema_changes(schema_changes)
772
- end
899
+ end if timer
773
900
 
774
901
  nil
775
902
  end