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

Sign up to get free protection for your applications and to get access to all the features.
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