cassandra-driver 1.0.0.beta.2-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 (118) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +4 -0
  3. data/README.md +125 -0
  4. data/lib/cassandra/auth/providers/password.rb +73 -0
  5. data/lib/cassandra/auth/providers.rb +16 -0
  6. data/lib/cassandra/auth.rb +97 -0
  7. data/lib/cassandra/client/batch.rb +212 -0
  8. data/lib/cassandra/client/client.rb +591 -0
  9. data/lib/cassandra/client/column_metadata.rb +54 -0
  10. data/lib/cassandra/client/connection_manager.rb +72 -0
  11. data/lib/cassandra/client/connector.rb +277 -0
  12. data/lib/cassandra/client/execute_options_decoder.rb +59 -0
  13. data/lib/cassandra/client/null_logger.rb +37 -0
  14. data/lib/cassandra/client/peer_discovery.rb +50 -0
  15. data/lib/cassandra/client/prepared_statement.rb +314 -0
  16. data/lib/cassandra/client/query_result.rb +230 -0
  17. data/lib/cassandra/client/request_runner.rb +71 -0
  18. data/lib/cassandra/client/result_metadata.rb +48 -0
  19. data/lib/cassandra/client/void_result.rb +78 -0
  20. data/lib/cassandra/client.rb +144 -0
  21. data/lib/cassandra/cluster/client.rb +768 -0
  22. data/lib/cassandra/cluster/connector.rb +244 -0
  23. data/lib/cassandra/cluster/control_connection.rb +425 -0
  24. data/lib/cassandra/cluster/metadata.rb +124 -0
  25. data/lib/cassandra/cluster/options.rb +42 -0
  26. data/lib/cassandra/cluster/registry.rb +198 -0
  27. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +47 -0
  28. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
  29. data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
  30. data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
  31. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +92 -0
  32. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
  33. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
  34. data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
  35. data/lib/cassandra/cluster/schema/type_parser.rb +138 -0
  36. data/lib/cassandra/cluster/schema.rb +340 -0
  37. data/lib/cassandra/cluster.rb +215 -0
  38. data/lib/cassandra/column.rb +92 -0
  39. data/lib/cassandra/compression/compressors/lz4.rb +72 -0
  40. data/lib/cassandra/compression/compressors/snappy.rb +66 -0
  41. data/lib/cassandra/compression.rb +66 -0
  42. data/lib/cassandra/driver.rb +111 -0
  43. data/lib/cassandra/errors.rb +79 -0
  44. data/lib/cassandra/execution/info.rb +51 -0
  45. data/lib/cassandra/execution/options.rb +80 -0
  46. data/lib/cassandra/execution/trace.rb +152 -0
  47. data/lib/cassandra/future.rb +675 -0
  48. data/lib/cassandra/host.rb +79 -0
  49. data/lib/cassandra/keyspace.rb +133 -0
  50. data/lib/cassandra/listener.rb +87 -0
  51. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +149 -0
  52. data/lib/cassandra/load_balancing/policies/round_robin.rb +132 -0
  53. data/lib/cassandra/load_balancing/policies/token_aware.rb +119 -0
  54. data/lib/cassandra/load_balancing/policies/white_list.rb +90 -0
  55. data/lib/cassandra/load_balancing/policies.rb +19 -0
  56. data/lib/cassandra/load_balancing.rb +113 -0
  57. data/lib/cassandra/protocol/cql_byte_buffer.rb +307 -0
  58. data/lib/cassandra/protocol/cql_protocol_handler.rb +323 -0
  59. data/lib/cassandra/protocol/frame_decoder.rb +128 -0
  60. data/lib/cassandra/protocol/frame_encoder.rb +48 -0
  61. data/lib/cassandra/protocol/request.rb +38 -0
  62. data/lib/cassandra/protocol/requests/auth_response_request.rb +47 -0
  63. data/lib/cassandra/protocol/requests/batch_request.rb +76 -0
  64. data/lib/cassandra/protocol/requests/credentials_request.rb +47 -0
  65. data/lib/cassandra/protocol/requests/execute_request.rb +103 -0
  66. data/lib/cassandra/protocol/requests/options_request.rb +39 -0
  67. data/lib/cassandra/protocol/requests/prepare_request.rb +50 -0
  68. data/lib/cassandra/protocol/requests/query_request.rb +153 -0
  69. data/lib/cassandra/protocol/requests/register_request.rb +38 -0
  70. data/lib/cassandra/protocol/requests/startup_request.rb +49 -0
  71. data/lib/cassandra/protocol/requests/void_query_request.rb +24 -0
  72. data/lib/cassandra/protocol/response.rb +38 -0
  73. data/lib/cassandra/protocol/responses/auth_challenge_response.rb +41 -0
  74. data/lib/cassandra/protocol/responses/auth_success_response.rb +41 -0
  75. data/lib/cassandra/protocol/responses/authenticate_response.rb +41 -0
  76. data/lib/cassandra/protocol/responses/detailed_error_response.rb +60 -0
  77. data/lib/cassandra/protocol/responses/error_response.rb +50 -0
  78. data/lib/cassandra/protocol/responses/event_response.rb +39 -0
  79. data/lib/cassandra/protocol/responses/prepared_result_response.rb +64 -0
  80. data/lib/cassandra/protocol/responses/raw_rows_result_response.rb +43 -0
  81. data/lib/cassandra/protocol/responses/ready_response.rb +44 -0
  82. data/lib/cassandra/protocol/responses/result_response.rb +48 -0
  83. data/lib/cassandra/protocol/responses/rows_result_response.rb +139 -0
  84. data/lib/cassandra/protocol/responses/schema_change_event_response.rb +60 -0
  85. data/lib/cassandra/protocol/responses/schema_change_result_response.rb +57 -0
  86. data/lib/cassandra/protocol/responses/set_keyspace_result_response.rb +42 -0
  87. data/lib/cassandra/protocol/responses/status_change_event_response.rb +44 -0
  88. data/lib/cassandra/protocol/responses/supported_response.rb +41 -0
  89. data/lib/cassandra/protocol/responses/topology_change_event_response.rb +34 -0
  90. data/lib/cassandra/protocol/responses/void_result_response.rb +39 -0
  91. data/lib/cassandra/protocol/type_converter.rb +384 -0
  92. data/lib/cassandra/protocol.rb +93 -0
  93. data/lib/cassandra/reconnection/policies/constant.rb +48 -0
  94. data/lib/cassandra/reconnection/policies/exponential.rb +79 -0
  95. data/lib/cassandra/reconnection/policies.rb +20 -0
  96. data/lib/cassandra/reconnection.rb +49 -0
  97. data/lib/cassandra/result.rb +215 -0
  98. data/lib/cassandra/retry/policies/default.rb +47 -0
  99. data/lib/cassandra/retry/policies/downgrading_consistency.rb +71 -0
  100. data/lib/cassandra/retry/policies/fallthrough.rb +39 -0
  101. data/lib/cassandra/retry/policies.rb +21 -0
  102. data/lib/cassandra/retry.rb +142 -0
  103. data/lib/cassandra/session.rb +202 -0
  104. data/lib/cassandra/statement.rb +22 -0
  105. data/lib/cassandra/statements/batch.rb +95 -0
  106. data/lib/cassandra/statements/bound.rb +48 -0
  107. data/lib/cassandra/statements/prepared.rb +81 -0
  108. data/lib/cassandra/statements/simple.rb +58 -0
  109. data/lib/cassandra/statements/void.rb +33 -0
  110. data/lib/cassandra/statements.rb +23 -0
  111. data/lib/cassandra/table.rb +299 -0
  112. data/lib/cassandra/time_uuid.rb +142 -0
  113. data/lib/cassandra/util.rb +167 -0
  114. data/lib/cassandra/uuid.rb +104 -0
  115. data/lib/cassandra/version.rb +21 -0
  116. data/lib/cassandra.rb +428 -0
  117. data/lib/cassandra_murmur3.jar +0 -0
  118. metadata +211 -0
@@ -0,0 +1,768 @@
1
+ # encoding: utf-8
2
+
3
+ #--
4
+ # Copyright 2013-2014 DataStax, Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #++
18
+
19
+ module Cassandra
20
+ class Cluster
21
+ # @private
22
+ class Client
23
+ include MonitorMixin
24
+
25
+ attr_reader :keyspace
26
+
27
+ def initialize(logger, cluster_registry, cluster_schema, io_reactor, connector, load_balancing_policy, reconnection_policy, retry_policy, connection_options, futures_factory)
28
+ @logger = logger
29
+ @registry = cluster_registry
30
+ @schema = cluster_schema
31
+ @reactor = io_reactor
32
+ @connector = connector
33
+ @load_balancing_policy = load_balancing_policy
34
+ @reconnection_policy = reconnection_policy
35
+ @retry_policy = retry_policy
36
+ @connection_options = connection_options
37
+ @futures = futures_factory
38
+ @connecting_hosts = ::Set.new
39
+ @connections = ::Hash.new
40
+ @prepared_statements = ::Hash.new
41
+ @preparing_statements = ::Hash.new
42
+ @keyspace = nil
43
+ @state = :idle
44
+
45
+ mon_initialize
46
+ end
47
+
48
+ def connect
49
+ synchronize do
50
+ return CLIENT_CLOSED if @state == :closed || @state == :closing
51
+ return @connected_future if @state == :connecting || @state == :connected
52
+
53
+ @state = :connecting
54
+ @connecting_hosts.merge(@registry.hosts)
55
+ end
56
+
57
+ @connected_future = begin
58
+ @registry.add_listener(self)
59
+
60
+ futures = @connecting_hosts.map do |host|
61
+ f = connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host))
62
+ f.recover do |error|
63
+ Cassandra::Client::FailedConnection.new(error, host)
64
+ end
65
+ end
66
+
67
+ Ione::Future.all(*futures).map do |connections|
68
+ connections.flatten!
69
+ raise NO_HOSTS if connections.empty?
70
+
71
+ unless connections.any?(&:connected?)
72
+ errors = {}
73
+ connections.each {|c| errors[c.host] = c.error}
74
+ raise Errors::NoHostsAvailable.new(errors)
75
+ end
76
+
77
+ self
78
+ end
79
+ end
80
+ @connected_future.on_complete(&method(:connected))
81
+ @connected_future
82
+ end
83
+
84
+ def close
85
+ state = nil
86
+
87
+ synchronize do
88
+ return CLIENT_NOT_CONNECTED if @state == :idle
89
+ return @closed_future if @state == :closed || @state == :closing
90
+
91
+ state, @state = @state, :closing
92
+ end
93
+
94
+ @closed_future = begin
95
+ @registry.remove_listener(self)
96
+
97
+ if state == :connecting
98
+ f = @connected_future.recover.flat_map { close_connections }
99
+ else
100
+ f = close_connections
101
+ end
102
+
103
+ f.map(self)
104
+ end
105
+ @closed_future.on_complete(&method(:closed))
106
+ @closed_future
107
+ end
108
+
109
+ # These methods shall be called from inside reactor thread only
110
+ def host_found(host)
111
+ nil
112
+ end
113
+
114
+ def host_lost(host)
115
+ nil
116
+ end
117
+
118
+ def host_up(host)
119
+ synchronize do
120
+ return Ione::Future.resolved if @connecting_hosts.include?(host)
121
+ @connecting_hosts << host
122
+ end
123
+
124
+ connect_to_host_maybe_retry(host, @load_balancing_policy.distance(host)).map(nil)
125
+ end
126
+
127
+ def host_down(host)
128
+ manager = nil
129
+
130
+ synchronize do
131
+ return Ione::Future.resolved if !@connections.has_key?(host) && !@connecting_hosts.include?(host)
132
+
133
+ @logger.info("Session disconnecting from ip=#{host.ip}")
134
+ @connecting_hosts.delete(host)
135
+ @prepared_statements.delete(host)
136
+ @preparing_statements.delete(host)
137
+
138
+ manager = @connections.delete(host)
139
+ end
140
+
141
+ if manager
142
+ Ione::Future.all(*manager.snapshot.map! {|c| c.close}).map(nil)
143
+ else
144
+ Ione::Future.resolved
145
+ end
146
+ end
147
+
148
+ def query(statement, options, paging_state = nil)
149
+ request = Protocol::QueryRequest.new(statement.cql, statement.params, nil, options.consistency, options.serial_consistency, options.page_size, paging_state, options.trace?)
150
+ timeout = options.timeout
151
+ promise = @futures.promise
152
+
153
+ keyspace = @keyspace
154
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
155
+
156
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout)
157
+
158
+ promise.future
159
+ end
160
+
161
+ def prepare(cql, options)
162
+ request = Protocol::PrepareRequest.new(cql, options.trace?)
163
+ timeout = options.timeout
164
+ promise = @futures.promise
165
+
166
+ keyspace = @keyspace
167
+ statement = VOID_STATEMENT
168
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
169
+
170
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout)
171
+
172
+ promise.future
173
+ end
174
+
175
+ def execute(statement, options, paging_state = nil)
176
+ timeout = options.timeout
177
+ result_metadata = statement.result_metadata
178
+ request = Protocol::ExecuteRequest.new(nil, statement.params_metadata, statement.params, result_metadata.nil?, options.consistency, options.serial_consistency, options.page_size, paging_state, options.trace?)
179
+ promise = @futures.promise
180
+
181
+ keyspace = @keyspace
182
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
183
+
184
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout)
185
+
186
+ promise.future
187
+ end
188
+
189
+ def batch(statement, options)
190
+ timeout = options.timeout
191
+ keyspace = @keyspace
192
+ plan = @load_balancing_policy.plan(keyspace, statement, options)
193
+ promise = @futures.promise
194
+
195
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout)
196
+
197
+ promise.future
198
+ end
199
+
200
+ def inspect
201
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)}>"
202
+ end
203
+
204
+ private
205
+
206
+ NO_CONNECTIONS = Ione::Future.resolved([])
207
+ BATCH_TYPES = {
208
+ :logged => Protocol::BatchRequest::LOGGED_TYPE,
209
+ :unlogged => Protocol::BatchRequest::UNLOGGED_TYPE,
210
+ :counter => Protocol::BatchRequest::COUNTER_TYPE,
211
+ }.freeze
212
+ CLIENT_CLOSED = Ione::Future.failed(Errors::ClientError.new('Cannot connect a closed client'))
213
+ CLIENT_NOT_CONNECTED = Ione::Future.failed(Errors::ClientError.new('Cannot close a not connected client'))
214
+ NOT_CONNECTED = Errors::NotConnectedError.new
215
+
216
+ UNAVAILABLE_ERROR_CODE = 0x1000
217
+ WRITE_TIMEOUT_ERROR_CODE = 0x1100
218
+ READ_TIMEOUT_ERROR_CODE = 0x1200
219
+
220
+ SELECT_SCHEMA_PEERS = Protocol::QueryRequest.new("SELECT peer, rpc_address, schema_version FROM system.peers", nil, nil, :one)
221
+ SELECT_SCHEMA_LOCAL = Protocol::QueryRequest.new("SELECT schema_version FROM system.local WHERE key='local'", nil, nil, :one)
222
+
223
+ def connected(f)
224
+ if f.resolved?
225
+ synchronize do
226
+ @state = :connected
227
+ end
228
+
229
+ @logger.info('Session connected')
230
+ else
231
+ synchronize do
232
+ @state = :defunct
233
+ end
234
+
235
+ f.on_failure do |e|
236
+ @logger.error('Session connect failed: %s' % e.message)
237
+ end
238
+
239
+ close
240
+ end
241
+ end
242
+
243
+ def closed(f)
244
+ synchronize do
245
+ @state = :closed
246
+
247
+ if f.resolved?
248
+ @logger.info('Session closed')
249
+ else
250
+ f.on_failure do |e|
251
+ @logger.error('Session close failed: %s' % e.message)
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ def close_connections
258
+ @logger.info('Session closing')
259
+
260
+ futures = []
261
+ synchronize do
262
+ @connections.each do |host, connections|
263
+ connections.snapshot.each do |c|
264
+ @logger.info("Disconnecting ip=#{c.host}")
265
+ futures << c.close
266
+ end
267
+ end.clear
268
+ end
269
+
270
+ Ione::Future.all(*futures).map(self)
271
+ end
272
+
273
+ def connect_to_host_maybe_retry(host, distance)
274
+ f = connect_to_host(host, distance)
275
+
276
+ f.on_failure do |e|
277
+ connect_to_host_with_retry(host, @reconnection_policy.schedule) if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
278
+ end
279
+
280
+ f
281
+ end
282
+
283
+ def connect_to_host_with_retry(host, schedule)
284
+ interval = schedule.next
285
+
286
+ @logger.info("Session started reconnecting to ip=#{host.ip} delay=#{interval}")
287
+
288
+ f = @reactor.schedule_timer(interval)
289
+ f.flat_map do
290
+ if synchronize { @connecting_hosts.include?(host) }
291
+ connect_to_host(host, @load_balancing_policy.distance(host)).fallback do |e|
292
+ if e.is_a?(Io::ConnectionError) || e.is_a?(::SystemCallError) || e.is_a?(::SocketError)
293
+ connect_to_host_with_retry(host, schedule)
294
+ else
295
+ Ione::Future.failed(e)
296
+ end
297
+ end
298
+ else
299
+ @logger.info("Session reconnection to ip=#{host.ip} cancelled")
300
+
301
+ NO_CONNECTIONS
302
+ end
303
+ end
304
+ end
305
+
306
+ def connect_to_host(host, distance)
307
+ case distance
308
+ when :ignore
309
+ return NO_CONNECTIONS
310
+ when :local
311
+ pool_size = @connection_options.connections_per_local_node
312
+ when :remote
313
+ pool_size = @connection_options.connections_per_remote_node
314
+ else
315
+ raise ::ArgumentError, "distance must be one of :ignore, :local or :remote, #{distance.inspect} given"
316
+ end
317
+
318
+ @logger.info("Session connecting to ip=#{host.ip}")
319
+
320
+ f = @connector.connect_many(host, pool_size)
321
+
322
+ f.on_value do |connections|
323
+ manager = nil
324
+
325
+ synchronize do
326
+ @logger.info("Session connected to ip=#{host.ip}")
327
+ @connecting_hosts.delete(host)
328
+ @prepared_statements[host] = {}
329
+ @preparing_statements[host] = {}
330
+ manager = @connections[host] ||= Cassandra::Client::ConnectionManager.new
331
+ end
332
+
333
+ manager.add_connections(connections)
334
+ end
335
+
336
+ f
337
+ end
338
+
339
+ def execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
340
+ unless plan.has_next?
341
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
342
+ return
343
+ end
344
+
345
+ hosts << host = plan.next
346
+ manager = nil
347
+ synchronize { manager = @connections[host] }
348
+
349
+ unless manager
350
+ errors ||= {}
351
+ errors[host] = NOT_CONNECTED
352
+ return execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
353
+ end
354
+
355
+ connection = manager.random_connection
356
+
357
+ if keyspace && connection.keyspace != keyspace
358
+ switch = switch_keyspace(connection, keyspace, timeout)
359
+ switch.on_complete do |s|
360
+ if s.resolved?
361
+ prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
362
+ else
363
+ s.on_failure do |e|
364
+ if e.is_a?(Errors::QueryError)
365
+ promise.break(e)
366
+ else
367
+ errors ||= {}
368
+ errors[host] = e
369
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
370
+ end
371
+ end
372
+ end
373
+ end
374
+ else
375
+ prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
376
+ end
377
+ rescue => e
378
+ errors ||= {}
379
+ errors[host] = e
380
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
381
+ end
382
+
383
+ def prepare_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
384
+ cql = statement.cql
385
+ id = synchronize { @prepared_statements[host][cql] }
386
+
387
+ if id
388
+ request.id = id
389
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
390
+ else
391
+ prepare = prepare_statement(host, connection, cql, timeout)
392
+ prepare.on_complete do |_|
393
+ if prepare.resolved?
394
+ request.id = prepare.value
395
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
396
+ else
397
+ prepare.on_failure do |e|
398
+ if e.is_a?(Errors::QueryError)
399
+ promise.break(e)
400
+ else
401
+ errors ||= {}
402
+ errors[host] = e
403
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
404
+ end
405
+ end
406
+ end
407
+ end
408
+ end
409
+ end
410
+
411
+ def batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors = nil, hosts = [])
412
+ unless plan.has_next?
413
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
414
+ return
415
+ end
416
+
417
+ hosts << host = plan.next
418
+ manager = nil
419
+ synchronize { manager = @connections[host] }
420
+
421
+ unless manager
422
+ errors ||= {}
423
+ errors[host] = NOT_CONNECTED
424
+ return batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
425
+ end
426
+
427
+ connection = manager.random_connection
428
+
429
+ if keyspace && connection.keyspace != keyspace
430
+ switch = switch_keyspace(connection, keyspace, timeout)
431
+ switch.on_complete do |s|
432
+ if s.resolved?
433
+ batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
434
+ else
435
+ s.on_failure do |e|
436
+ if e.is_a?(Errors::QueryError)
437
+ promise.break(e)
438
+ else
439
+ errors ||= {}
440
+ errors[host] = e
441
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
442
+ end
443
+ end
444
+ end
445
+ end
446
+ else
447
+ batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
448
+ end
449
+ rescue => e
450
+ errors ||= {}
451
+ errors[host] = e
452
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
453
+ end
454
+
455
+ def batch_and_send_request_by_plan(host, connection, promise, keyspace, statement, options, plan, timeout, errors, hosts)
456
+ request = Protocol::BatchRequest.new(BATCH_TYPES[statement.type], options.consistency, options.trace?)
457
+ unprepared = Hash.new {|hash, cql| hash[cql] = []}
458
+
459
+ statement.statements.each do |statement|
460
+ cql = statement.cql
461
+
462
+ if statement.is_a?(Statements::Bound)
463
+ id = synchronize { @prepared_statements[host][cql] }
464
+
465
+ if id
466
+ request.add_prepared(id, statement.params_metadata, statement.params)
467
+ else
468
+ unprepared[cql] << statement
469
+ end
470
+ else
471
+ request.add_query(cql, statement.params)
472
+ end
473
+ end
474
+
475
+ if unprepared.empty?
476
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
477
+ else
478
+ to_prepare = unprepared.to_a
479
+ futures = to_prepare.map do |cql, _|
480
+ prepare_statement(host, connection, cql, timeout)
481
+ end
482
+
483
+ Ione::Future.all(*futures).on_complete do |f|
484
+ if f.resolved?
485
+ prepared_ids = f.value
486
+ to_prepare.each_with_index do |(_, statements), i|
487
+ statements.each do |statement|
488
+ request.add_prepared(prepared_ids[i], statement.params_metadata, statement.params)
489
+ end
490
+ end
491
+
492
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
493
+ else
494
+ f.on_failure do |e|
495
+ if e.is_a?(Errors::QueryError)
496
+ promise.break(e)
497
+ else
498
+ errors ||= {}
499
+ errors[host] = e
500
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
501
+ end
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end
507
+
508
+ def send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors = nil, hosts = [])
509
+ unless plan.has_next?
510
+ promise.break(Errors::NoHostsAvailable.new(errors || {}))
511
+ return
512
+ end
513
+
514
+ hosts << host = plan.next
515
+ manager = nil
516
+ synchronize { manager = @connections[host] }
517
+
518
+ unless manager
519
+ errors ||= {}
520
+ errors[host] = NOT_CONNECTED
521
+ return send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
522
+ end
523
+
524
+ connection = manager.random_connection
525
+
526
+ if keyspace && connection.keyspace != keyspace
527
+ switch = switch_keyspace(connection, keyspace, timeout)
528
+ switch.on_complete do |s|
529
+ if s.resolved?
530
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
531
+ else
532
+ s.on_failure do |e|
533
+ if e.is_a?(Errors::QueryError)
534
+ promise.break(e)
535
+ else
536
+ errors ||= {}
537
+ errors[host] = e
538
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
539
+ end
540
+ end
541
+ end
542
+ end
543
+ else
544
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
545
+ end
546
+ rescue => e
547
+ errors ||= {}
548
+ errors[host] = e
549
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
550
+ end
551
+
552
+ def do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries = 0)
553
+ request.retries = retries
554
+
555
+ f = connection.send_request(request, timeout)
556
+ f.on_complete do |f|
557
+ if f.resolved?
558
+ r = f.value
559
+ case r
560
+ when Protocol::DetailedErrorResponse
561
+ details = r.details
562
+ decision = begin
563
+ case r.code
564
+ when UNAVAILABLE_ERROR_CODE
565
+ @retry_policy.unavailable(statement, details[:cl], details[:required], details[:alive], retries)
566
+ when WRITE_TIMEOUT_ERROR_CODE
567
+ details[:write_type] = write_type = details[:write_type].downcase!.to_sym
568
+ @retry_policy.write_timeout(statement, details[:cl], write_type, details[:blockfor], details[:received], retries)
569
+ when READ_TIMEOUT_ERROR_CODE
570
+ @retry_policy.read_timeout(statement, details[:cl], details[:blockfor], details[:received], details[:data_present], retries)
571
+ else
572
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, details))
573
+ break
574
+ end
575
+ rescue => e
576
+ promise.break(e)
577
+ break
578
+ end
579
+
580
+ case decision
581
+ when Retry::Decisions::Retry
582
+ request.consistency = decision.consistency
583
+ do_send_request_by_plan(host, connection, promise, keyspace, statement, options, request, plan, timeout, errors, hosts, retries + 1)
584
+ when Retry::Decisions::Ignore
585
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
586
+ when Retry::Decisions::Reraise
587
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
588
+ else
589
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, r.details))
590
+ end
591
+ when Protocol::ErrorResponse
592
+ promise.break(Errors::QueryError.new(r.code, r.message, statement.cql, nil))
593
+ when Protocol::SetKeyspaceResultResponse
594
+ @keyspace = r.keyspace
595
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
596
+ when Protocol::PreparedResultResponse
597
+ cql = request.cql
598
+ synchronize do
599
+ @prepared_statements[host][cql] = r.id
600
+ @preparing_statements[host].delete(cql)
601
+ end
602
+
603
+ promise.fulfill(Statements::Prepared.new(cql, r.metadata, r.result_metadata, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures, @schema))
604
+ when Protocol::RawRowsResultResponse
605
+ r.materialize(statement.result_metadata)
606
+ promise.fulfill(Results::Paged.new(r.rows, r.paging_state, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
607
+ when Protocol::RowsResultResponse
608
+ promise.fulfill(Results::Paged.new(r.rows, r.paging_state, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
609
+ when Protocol::SchemaChangeResultResponse
610
+ if r.change == 'DROPPED' && r.keyspace == @keyspace && r.table.empty?
611
+ @keyspace = nil
612
+ end
613
+
614
+ wait_for_schema_agreement(connection, @reconnection_policy.schedule).on_complete do |f|
615
+ unless f.resolved?
616
+ f.on_failure do |e|
617
+ @logger.error("Schema agreement error: #{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}")
618
+ end
619
+ end
620
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
621
+ end
622
+ else
623
+ promise.fulfill(Results::Void.new(r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
624
+ end
625
+ else
626
+ f.on_failure do |e|
627
+ errors ||= {}
628
+ errors[host] = e
629
+ case request
630
+ when Protocol::QueryRequest, Protocol::PrepareRequest
631
+ send_request_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
632
+ when Protocol::ExecuteRequest
633
+ execute_by_plan(promise, keyspace, statement, options, request, plan, timeout, errors, hosts)
634
+ when Protocol::BatchRequest
635
+ batch_by_plan(promise, keyspace, statement, options, plan, timeout, errors, hosts)
636
+ else
637
+ promise.break(e)
638
+ end
639
+ end
640
+ end
641
+ end
642
+ end
643
+
644
+ def wait_for_schema_agreement(connection, schedule)
645
+ @logger.info('Fetching schema versions')
646
+
647
+ peers = connection.send_request(SELECT_SCHEMA_PEERS)
648
+ local = connection.send_request(SELECT_SCHEMA_LOCAL)
649
+
650
+ Ione::Future.all(peers, local).flat_map do |(peers, local)|
651
+ @logger.info('Fetched schema versions')
652
+
653
+ peers = peers.rows
654
+ local = local.rows
655
+
656
+ versions = ::Set.new
657
+
658
+ unless local.empty?
659
+ host = @registry.host(connection.host)
660
+
661
+ if host && host.up?
662
+ versions << version = local.first['schema_version']
663
+ @logger.debug("Host #{host.ip} schema version: #{version}")
664
+ end
665
+ end
666
+
667
+ peers.each do |row|
668
+ host = @registry.host(peer_ip(row))
669
+ next unless host && host.up?
670
+
671
+ versions << version = row['schema_version']
672
+ @logger.debug("Host #{host.ip} schema version: #{version}")
673
+ end
674
+
675
+ if versions.one?
676
+ @logger.info('All hosts have the same schema version')
677
+ Ione::Future.resolved
678
+ else
679
+ interval = schedule.next
680
+ @logger.info("Hosts don't yet agree on schema: #{versions.to_a.inspect}, retrying in #{interval} seconds")
681
+ @reactor.schedule_timer(interval).flat_map do
682
+ wait_for_schema_agreement(connection, schedule)
683
+ end
684
+ end
685
+ end
686
+ end
687
+
688
+ def peer_ip(data)
689
+ ip = data['rpc_address']
690
+ ip = data['peer'] if ip == '0.0.0.0'
691
+ ip
692
+ end
693
+
694
+ def switch_keyspace(connection, keyspace, timeout)
695
+ pending_keyspace = connection[:pending_keyspace]
696
+ pending_switch = connection[:pending_switch]
697
+
698
+ return pending_switch || Ione::Future.resolved if pending_keyspace == keyspace
699
+
700
+ request = Protocol::QueryRequest.new("USE #{keyspace}", nil, nil, :one)
701
+
702
+ f = connection.send_request(request, timeout).map do |r|
703
+ case r
704
+ when Protocol::SetKeyspaceResultResponse
705
+ @keyspace = r.keyspace
706
+ nil
707
+ when Protocol::DetailedErrorResponse
708
+ raise Errors::QueryError.new(r.code, r.message, request.cql, r.details)
709
+ when Protocol::ErrorResponse
710
+ raise Errors::QueryError.new(r.code, r.message, request.cql, nil)
711
+ else
712
+ raise "unexpected response #{r.inspect}"
713
+ end
714
+ end
715
+
716
+ connection[:pending_keyspace] = keyspace
717
+ connection[:pending_switch] = f
718
+
719
+ f.on_complete do |f|
720
+ connection[:pending_switch] = nil
721
+ connection[:pending_keyspace] = nil
722
+ end
723
+
724
+ f
725
+ end
726
+
727
+ def prepare_statement(host, connection, cql, timeout)
728
+ synchronize do
729
+ pending = @preparing_statements[host]
730
+
731
+ return pending[cql] if pending.has_key?(cql)
732
+ end
733
+
734
+ request = Protocol::PrepareRequest.new(cql, false)
735
+
736
+ f = connection.send_request(request, timeout).map do |r|
737
+ case r
738
+ when Protocol::PreparedResultResponse
739
+ id = r.id
740
+ synchronize do
741
+ @prepared_statements[host][cql] = id
742
+ @preparing_statements[host].delete(cql)
743
+ end
744
+ id
745
+ when Protocol::DetailedErrorResponse
746
+ raise Errors::QueryError.new(r.code, r.message, cql, r.details)
747
+ when Protocol::ErrorResponse
748
+ raise Errors::QueryError.new(r.code, r.message, cql, nil)
749
+ else
750
+ raise "unexpected response #{r.inspect}"
751
+ end
752
+ end
753
+
754
+ synchronize do
755
+ @preparing_statements[host][cql] = f
756
+ end
757
+
758
+ f
759
+ end
760
+
761
+ def create_execution_info(keyspace, statement, options, request, response, hosts)
762
+ trace_id = response.trace_id
763
+ trace = trace_id ? Execution::Trace.new(trace_id, self) : nil
764
+ info = Execution::Info.new(keyspace, statement, options, hosts, request.consistency, request.retries, trace)
765
+ end
766
+ end
767
+ end
768
+ end