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.
- checksums.yaml +4 -4
- data/README.md +106 -39
- data/lib/cassandra.rb +396 -148
- data/lib/cassandra/address_resolution.rb +1 -1
- data/lib/cassandra/address_resolution/policies/ec2_multi_region.rb +1 -1
- data/lib/cassandra/address_resolution/policies/none.rb +1 -1
- data/lib/cassandra/aggregate.rb +21 -7
- data/lib/cassandra/argument.rb +2 -2
- data/lib/cassandra/attr_boolean.rb +33 -0
- data/lib/cassandra/auth.rb +6 -5
- data/lib/cassandra/auth/providers.rb +1 -1
- data/lib/cassandra/auth/providers/password.rb +5 -13
- data/lib/cassandra/cassandra_logger.rb +80 -0
- data/lib/cassandra/cluster.rb +49 -9
- data/lib/cassandra/cluster/client.rb +835 -209
- data/lib/cassandra/cluster/connection_pool.rb +2 -2
- data/lib/cassandra/cluster/connector.rb +86 -27
- data/lib/cassandra/cluster/control_connection.rb +222 -95
- data/lib/cassandra/cluster/failed_connection.rb +1 -1
- data/lib/cassandra/cluster/metadata.rb +14 -8
- data/lib/cassandra/cluster/options.rb +68 -22
- data/lib/cassandra/cluster/registry.rb +81 -17
- data/lib/cassandra/cluster/schema.rb +70 -8
- data/lib/cassandra/cluster/schema/cql_type_parser.rb +15 -10
- data/lib/cassandra/cluster/schema/fetchers.rb +601 -241
- data/lib/cassandra/cluster/schema/fqcn_type_parser.rb +39 -38
- data/lib/cassandra/cluster/schema/partitioners.rb +1 -1
- data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +6 -8
- data/lib/cassandra/cluster/schema/partitioners/ordered.rb +1 -1
- data/lib/cassandra/cluster/schema/partitioners/random.rb +1 -1
- data/lib/cassandra/cluster/schema/replication_strategies.rb +1 -1
- data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +19 -18
- data/lib/cassandra/cluster/schema/replication_strategies/none.rb +1 -1
- data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +1 -1
- data/lib/cassandra/column.rb +4 -23
- data/lib/cassandra/column_container.rb +322 -0
- data/lib/cassandra/compression.rb +1 -1
- data/lib/cassandra/compression/compressors/lz4.rb +7 -8
- data/lib/cassandra/compression/compressors/snappy.rb +4 -3
- data/lib/cassandra/driver.rb +107 -46
- data/lib/cassandra/errors.rb +303 -52
- data/lib/cassandra/execution/info.rb +16 -5
- data/lib/cassandra/execution/options.rb +102 -55
- data/lib/cassandra/execution/trace.rb +16 -9
- data/lib/cassandra/executors.rb +1 -1
- data/lib/cassandra/function.rb +19 -13
- data/lib/cassandra/function_collection.rb +85 -0
- data/lib/cassandra/future.rb +101 -49
- data/lib/cassandra/host.rb +25 -5
- data/lib/cassandra/index.rb +118 -0
- data/lib/cassandra/keyspace.rb +169 -33
- data/lib/cassandra/listener.rb +1 -1
- data/lib/cassandra/load_balancing.rb +2 -2
- data/lib/cassandra/load_balancing/policies.rb +1 -1
- data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +39 -25
- data/lib/cassandra/load_balancing/policies/round_robin.rb +8 -1
- data/lib/cassandra/load_balancing/policies/token_aware.rb +22 -13
- data/lib/cassandra/load_balancing/policies/white_list.rb +18 -5
- data/lib/cassandra/materialized_view.rb +90 -0
- data/lib/cassandra/null_logger.rb +27 -6
- data/lib/cassandra/protocol.rb +1 -1
- data/lib/cassandra/protocol/coder.rb +81 -42
- data/lib/cassandra/protocol/cql_byte_buffer.rb +58 -44
- data/lib/cassandra/protocol/cql_protocol_handler.rb +57 -54
- data/lib/cassandra/protocol/request.rb +6 -7
- data/lib/cassandra/protocol/requests/auth_response_request.rb +3 -3
- data/lib/cassandra/protocol/requests/batch_request.rb +17 -8
- data/lib/cassandra/protocol/requests/credentials_request.rb +3 -3
- data/lib/cassandra/protocol/requests/execute_request.rb +39 -20
- data/lib/cassandra/protocol/requests/options_request.rb +1 -1
- data/lib/cassandra/protocol/requests/prepare_request.rb +5 -5
- data/lib/cassandra/protocol/requests/query_request.rb +28 -23
- data/lib/cassandra/protocol/requests/register_request.rb +2 -2
- data/lib/cassandra/protocol/requests/startup_request.rb +8 -8
- data/lib/cassandra/protocol/requests/void_query_request.rb +1 -1
- data/lib/cassandra/protocol/response.rb +3 -4
- data/lib/cassandra/protocol/responses/already_exists_error_response.rb +12 -2
- data/lib/cassandra/protocol/responses/auth_challenge_response.rb +4 -5
- data/lib/cassandra/protocol/responses/auth_success_response.rb +4 -5
- data/lib/cassandra/protocol/responses/authenticate_response.rb +4 -5
- data/lib/cassandra/protocol/responses/error_response.rb +104 -17
- data/lib/cassandra/protocol/responses/event_response.rb +3 -4
- data/lib/cassandra/protocol/responses/function_failure_error_response.rb +13 -2
- data/lib/cassandra/protocol/responses/prepared_result_response.rb +14 -9
- data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +14 -9
- data/lib/cassandra/protocol/responses/read_failure_error_response.rb +26 -4
- data/lib/cassandra/protocol/responses/read_timeout_error_response.rb +22 -3
- data/lib/cassandra/protocol/responses/ready_response.rb +6 -7
- data/lib/cassandra/protocol/responses/result_response.rb +11 -10
- data/lib/cassandra/protocol/responses/rows_result_response.rb +8 -7
- data/lib/cassandra/protocol/responses/schema_change_event_response.rb +8 -8
- data/lib/cassandra/protocol/responses/schema_change_result_response.rb +19 -13
- data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +5 -6
- data/lib/cassandra/protocol/responses/status_change_event_response.rb +5 -6
- data/lib/cassandra/protocol/responses/supported_response.rb +4 -5
- data/lib/cassandra/protocol/responses/topology_change_event_response.rb +4 -5
- data/lib/cassandra/protocol/responses/unavailable_error_response.rb +20 -3
- data/lib/cassandra/protocol/responses/unprepared_error_response.rb +11 -2
- data/lib/cassandra/protocol/responses/void_result_response.rb +4 -5
- data/lib/cassandra/protocol/responses/write_failure_error_response.rb +26 -4
- data/lib/cassandra/protocol/responses/write_timeout_error_response.rb +22 -3
- data/lib/cassandra/protocol/v1.rb +98 -37
- data/lib/cassandra/protocol/v3.rb +121 -50
- data/lib/cassandra/protocol/v4.rb +172 -68
- data/lib/cassandra/reconnection.rb +1 -1
- data/lib/cassandra/reconnection/policies.rb +1 -1
- data/lib/cassandra/reconnection/policies/constant.rb +2 -4
- data/lib/cassandra/reconnection/policies/exponential.rb +6 -6
- data/lib/cassandra/result.rb +55 -20
- data/lib/cassandra/retry.rb +8 -8
- data/lib/cassandra/retry/policies.rb +1 -1
- data/lib/cassandra/retry/policies/default.rb +1 -1
- data/lib/cassandra/retry/policies/downgrading_consistency.rb +4 -2
- data/lib/cassandra/retry/policies/fallthrough.rb +1 -1
- data/lib/cassandra/session.rb +24 -16
- data/lib/cassandra/statement.rb +1 -1
- data/lib/cassandra/statements.rb +1 -1
- data/lib/cassandra/statements/batch.rb +16 -10
- data/lib/cassandra/statements/bound.rb +10 -3
- data/lib/cassandra/statements/prepared.rb +62 -18
- data/lib/cassandra/statements/simple.rb +23 -10
- data/lib/cassandra/statements/void.rb +1 -1
- data/lib/cassandra/table.rb +53 -185
- data/lib/cassandra/time.rb +11 -6
- data/lib/cassandra/time_uuid.rb +12 -14
- data/lib/cassandra/timestamp_generator.rb +37 -0
- data/lib/cassandra/timestamp_generator/simple.rb +38 -0
- data/lib/cassandra/timestamp_generator/ticking_on_duplicate.rb +58 -0
- data/lib/cassandra/tuple.rb +4 -4
- data/lib/cassandra/types.rb +109 -71
- data/lib/cassandra/udt.rb +66 -50
- data/lib/cassandra/util.rb +155 -15
- data/lib/cassandra/uuid.rb +20 -21
- data/lib/cassandra/uuid/generator.rb +7 -5
- data/lib/cassandra/version.rb +2 -2
- data/lib/cassandra_murmur3.jar +0 -0
- data/lib/datastax/cassandra.rb +1 -1
- metadata +27 -16
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright 2013-
|
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
|
-
|
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-
|
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,
|
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]
|
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,
|
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,
|
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
|
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?) ?
|
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
|
-
|
146
|
-
|
147
|
-
@
|
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 |
|
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),
|
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(
|
213
|
+
Ione::Future.failed(cannot_authenticate_error)
|
185
214
|
end
|
186
215
|
else
|
187
|
-
authenticator = @connection_options.create_authenticator(
|
216
|
+
authenticator = @connection_options.create_authenticator(
|
217
|
+
r.authentication_class)
|
188
218
|
if authenticator
|
189
|
-
challenge_response_cycle(connection,
|
219
|
+
challenge_response_cycle(connection,
|
220
|
+
authenticator,
|
221
|
+
authenticator.initial_response)
|
190
222
|
else
|
191
|
-
Ione::Future.failed(
|
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(
|
229
|
+
::Ione::Future.failed(
|
230
|
+
r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
|
198
231
|
else
|
199
|
-
::Ione::Future.failed(
|
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,
|
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),
|
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),
|
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
|
-
|
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(
|
295
|
+
::Ione::Future.failed(
|
296
|
+
r.to_error(nil, VOID_STATEMENT, VOID_OPTIONS, EMPTY_LIST, :one, 0))
|
241
297
|
else
|
242
|
-
::Ione::Future.failed(
|
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}:
|
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.
|
357
|
+
notify = !error.nil? && !@connections.key?(host)
|
300
358
|
end
|
301
359
|
|
302
|
-
@logger.debug("Host #{host.ip} refused connection (#{error.class.name}:
|
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-
|
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 ||
|
88
|
-
|
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
|
-
|
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
|
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 |
|
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#{
|
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(
|
143
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
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
|
-
|
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
|
-
|
238
|
+
refresh_schema_async_wrapper
|
218
239
|
end
|
219
240
|
when 'REMOVED_NODE'
|
220
241
|
@registry.host_lost(event.address)
|
221
|
-
|
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(
|
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(
|
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("
|
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("
|
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("
|
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(
|
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 =
|
307
|
-
|
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(
|
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(
|
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,
|
327
|
-
|
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(
|
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}:
|
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(
|
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(
|
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
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
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("
|
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 |
|
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
|
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
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
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
|
-
|
521
|
-
|
522
|
-
|
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(
|
624
|
+
@logger.info('Control connection closed ' \
|
625
|
+
"(#{cause.class.name}: #{cause.message})")
|
572
626
|
else
|
573
|
-
@logger.info(
|
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
|
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 |
|
603
|
-
@logger.info('Control connection established') if
|
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
|
622
|
-
|
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.
|
689
|
+
next if refresh_keyspaces.key?(keyspace)
|
635
690
|
|
636
691
|
case change.target
|
637
692
|
when Protocol::Constants::SCHEMA_CHANGE_TARGET_KEYSPACE
|
638
|
-
|
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
|
-
|
645
|
-
|
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
|
-
|
665
|
-
|
666
|
-
futures << refresh_maybe_retry(:table, keyspace,
|
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 <<
|
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 <<
|
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}
|
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
|
726
|
-
|
727
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
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
|
-
|
741
|
-
|
742
|
-
|
845
|
+
def handle_schema_change(change)
|
846
|
+
timer = nil
|
847
|
+
expiration_timer = nil
|
743
848
|
|
744
|
-
|
745
|
-
|
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
|
-
|
748
|
-
|
869
|
+
expiration_timer.on_value do
|
870
|
+
schema_changes = nil
|
749
871
|
|
750
|
-
|
751
|
-
|
752
|
-
end
|
872
|
+
synchronize do
|
873
|
+
@io_reactor.cancel_timer(@schema_refresh_timer)
|
753
874
|
|
754
|
-
|
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
|
-
|
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
|
892
|
+
@schema_refresh_timer = nil
|
766
893
|
|
767
|
-
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
|