cassandra-driver 1.0.0.rc.1-java → 1.1.0-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -18
  3. data/lib/cassandra.rb +132 -93
  4. data/lib/cassandra/auth.rb +3 -3
  5. data/lib/cassandra/cluster.rb +65 -39
  6. data/lib/cassandra/cluster/client.rb +67 -28
  7. data/lib/cassandra/{client/connection_manager.rb → cluster/connection_pool.rb} +9 -3
  8. data/lib/cassandra/cluster/connector.rb +101 -30
  9. data/lib/cassandra/cluster/control_connection.rb +160 -96
  10. data/lib/cassandra/{client/null_logger.rb → cluster/failed_connection.rb} +12 -14
  11. data/lib/cassandra/cluster/options.rb +26 -11
  12. data/lib/cassandra/cluster/schema.rb +22 -1
  13. data/lib/cassandra/column.rb +5 -0
  14. data/lib/cassandra/driver.rb +46 -12
  15. data/lib/cassandra/errors.rb +5 -5
  16. data/lib/cassandra/execution/options.rb +42 -8
  17. data/lib/cassandra/execution/trace.rb +4 -4
  18. data/lib/cassandra/executors.rb +111 -0
  19. data/lib/cassandra/future.rb +88 -64
  20. data/lib/cassandra/keyspace.rb +12 -0
  21. data/lib/cassandra/load_balancing.rb +10 -0
  22. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +10 -5
  23. data/lib/cassandra/load_balancing/policies/round_robin.rb +7 -5
  24. data/lib/cassandra/load_balancing/policies/token_aware.rb +31 -10
  25. data/lib/cassandra/load_balancing/policies/white_list.rb +4 -7
  26. data/lib/cassandra/null_logger.rb +35 -0
  27. data/lib/cassandra/protocol/cql_protocol_handler.rb +8 -1
  28. data/lib/cassandra/protocol/requests/query_request.rb +1 -11
  29. data/lib/cassandra/result.rb +34 -9
  30. data/lib/cassandra/session.rb +6 -0
  31. data/lib/cassandra/statements/prepared.rb +5 -1
  32. data/lib/cassandra/table.rb +5 -0
  33. data/lib/cassandra/util.rb +130 -0
  34. data/lib/cassandra/version.rb +1 -1
  35. metadata +40 -50
  36. data/lib/cassandra/client.rb +0 -144
  37. data/lib/cassandra/client/batch.rb +0 -212
  38. data/lib/cassandra/client/client.rb +0 -591
  39. data/lib/cassandra/client/column_metadata.rb +0 -54
  40. data/lib/cassandra/client/connector.rb +0 -273
  41. data/lib/cassandra/client/execute_options_decoder.rb +0 -59
  42. data/lib/cassandra/client/peer_discovery.rb +0 -50
  43. data/lib/cassandra/client/prepared_statement.rb +0 -314
  44. data/lib/cassandra/client/query_result.rb +0 -230
  45. data/lib/cassandra/client/request_runner.rb +0 -70
  46. data/lib/cassandra/client/result_metadata.rb +0 -48
  47. data/lib/cassandra/client/void_result.rb +0 -78
@@ -161,18 +161,64 @@ module Cassandra
161
161
  end
162
162
  end
163
163
 
164
+ # @private
165
+ class Factory
166
+ def initialize(executor)
167
+ @executor = executor
168
+ end
169
+
170
+ def value(value)
171
+ Value.new(value)
172
+ end
173
+
174
+ def error(error)
175
+ Error.new(error)
176
+ end
177
+
178
+ def promise
179
+ Promise.new(@executor)
180
+ end
181
+
182
+ def all(*futures)
183
+ futures = Array(futures.first) if futures.one?
184
+ monitor = Monitor.new
185
+ promise = Promise.new(@executor)
186
+ remaining = futures.length
187
+ values = Array.new(remaining)
188
+
189
+ futures.each_with_index do |future, i|
190
+ future.on_complete do |v, e|
191
+ if e
192
+ promise.break(e)
193
+ else
194
+ done = false
195
+ monitor.synchronize do
196
+ remaining -= 1
197
+ done = (remaining == 0)
198
+ values[i] = v
199
+ end
200
+ promise.fulfill(values) if done
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+
207
+ # @private
208
+ @@factory = Factory.new(Executors::SameThread.new)
209
+
164
210
  # Returns a future resolved to a given value
165
211
  # @param value [Object] value for the future
166
212
  # @return [Cassandra::Future<Object>] a future value
167
213
  def self.value(value)
168
- Value.new(value)
214
+ @@factory.value(value)
169
215
  end
170
216
 
171
217
  # Returns a future resolved to a given error
172
218
  # @param error [Exception] error for the future
173
219
  # @return [Cassandra::Future<Exception>] a future error
174
220
  def self.error(error)
175
- Error.new(error)
221
+ @@factory.error(error)
176
222
  end
177
223
 
178
224
  # Returns a future that resolves with values of all futures
@@ -184,35 +230,7 @@ module Cassandra
184
230
  # combine
185
231
  # @return [Cassandra::Future<Array<Object>>] a combined future
186
232
  def self.all(*futures)
187
- futures = Array(futures.first) if futures.one?
188
- monitor = Monitor.new
189
- promise = Promise.new
190
- remaining = futures.length
191
- values = Array.new(remaining)
192
-
193
- futures.each_with_index do |future, i|
194
- future.on_complete do |v, e|
195
- if e
196
- promise.break(e)
197
- else
198
- done = false
199
- monitor.synchronize do
200
- remaining -= 1
201
- done = (remaining == 0)
202
- values[i] = v
203
- end
204
- promise.fulfill(values) if done
205
- end
206
- end
207
- end
208
-
209
- promise.future
210
- end
211
-
212
- # @private
213
- # Returns a new promise instance
214
- def self.promise
215
- Promise.new
233
+ @@factory.all(*futures)
216
234
  end
217
235
 
218
236
  # @private
@@ -220,24 +238,6 @@ module Cassandra
220
238
  @signal = signal
221
239
  end
222
240
 
223
- # Returns future value or raises future error
224
- # @note This method blocks until a future is resolved
225
- # @raise [Exception] error used to resolve this future if any
226
- # @return [Object] value used to resolve this future if any
227
- def get
228
- @signal.get
229
- end
230
-
231
- # Block until the future has been resolved
232
- # @note This method blocks until a future is resolved
233
- # @note This method won't raise any errors or return anything but the
234
- # future itself
235
- # @return [self]
236
- def join
237
- @signal.join
238
- self
239
- end
240
-
241
241
  # Run block when future resolves to a value
242
242
  # @note The block can be called synchronously from current thread if the
243
243
  # future has already been resolved, or, asynchronously, from background
@@ -348,6 +348,25 @@ module Cassandra
348
348
  raise ::ArgumentError, "no block given" unless block_given?
349
349
  @signal.fallback(&block)
350
350
  end
351
+
352
+ # Returns future value or raises future error
353
+ # @note This method blocks until a future is resolved
354
+ # @raise [Exception] error used to resolve this future if any
355
+ # @return [Object] value used to resolve this future if any
356
+ def get
357
+ @signal.get
358
+
359
+ end
360
+
361
+ # Block until the future has been resolved
362
+ # @note This method blocks until a future is resolved
363
+ # @note This method won't raise any errors or return anything but the
364
+ # future itself
365
+ # @return [self]
366
+ def join
367
+ @signal.join
368
+ self
369
+ end
351
370
  end
352
371
 
353
372
  # @private
@@ -455,10 +474,11 @@ module Cassandra
455
474
 
456
475
  include MonitorMixin
457
476
 
458
- def initialize
477
+ def initialize(executor)
459
478
  mon_initialize
460
479
 
461
480
  @cond = new_cond
481
+ @executor = executor
462
482
  @state = :pending
463
483
  @waiting = 0
464
484
  @error = nil
@@ -484,12 +504,14 @@ module Cassandra
484
504
  listeners, @listeners = @listeners, nil
485
505
  end
486
506
 
487
- listeners.each do |listener|
488
- listener.failure(error) rescue nil
489
- end
507
+ @executor.execute do
508
+ listeners.each do |listener|
509
+ listener.failure(error) rescue nil
510
+ end
490
511
 
491
- synchronize do
492
- @cond.broadcast if @waiting > 0
512
+ synchronize do
513
+ @cond.broadcast if @waiting > 0
514
+ end
493
515
  end
494
516
 
495
517
  self
@@ -509,12 +531,14 @@ module Cassandra
509
531
  listeners, @listeners = @listeners, nil
510
532
  end
511
533
 
512
- listeners.each do |listener|
513
- listener.success(value) rescue nil
514
- end
534
+ @executor.execute do
535
+ listeners.each do |listener|
536
+ listener.success(value) rescue nil
537
+ end
515
538
 
516
- synchronize do
517
- @cond.broadcast if @waiting > 0
539
+ synchronize do
540
+ @cond.broadcast if @waiting > 0
541
+ end
518
542
  end
519
543
 
520
544
  self
@@ -608,7 +632,7 @@ module Cassandra
608
632
  if @state == :pending
609
633
  synchronize do
610
634
  if @state == :pending
611
- promise = Promise.new
635
+ promise = Promise.new(@executor)
612
636
  listener = Listeners::Then.new(promise, &block)
613
637
  @listeners << listener
614
638
  return promise.future
@@ -631,7 +655,7 @@ module Cassandra
631
655
  if @state == :pending
632
656
  synchronize do
633
657
  if @state == :pending
634
- promise = Promise.new
658
+ promise = Promise.new(@executor)
635
659
  listener = Listeners::Fallback.new(promise, &block)
636
660
  @listeners << listener
637
661
  return promise.future
@@ -653,8 +677,8 @@ module Cassandra
653
677
 
654
678
  attr_reader :future
655
679
 
656
- def initialize
657
- @signal = Signal.new
680
+ def initialize(executor)
681
+ @signal = Signal.new(executor)
658
682
  @future = Future.new(@signal)
659
683
  end
660
684
 
@@ -106,6 +106,11 @@ module Cassandra
106
106
  end
107
107
  alias :== :eql?
108
108
 
109
+ # @return [String] a CLI-friendly keyspace representation
110
+ def inspect
111
+ "#<#{self.class.name}:0x#{self.object_id.to_s(16)} @name=#{@name}>"
112
+ end
113
+
109
114
  # @private
110
115
  def update_table(table)
111
116
  tables = @tables.dup
@@ -113,6 +118,13 @@ module Cassandra
113
118
  Keyspace.new(@name, @durable_writes, @replication, tables)
114
119
  end
115
120
 
121
+ # @private
122
+ def delete_table(table_name)
123
+ tables = @tables.dup
124
+ tables.delete(table_name)
125
+ Keyspace.new(@name, @durable_writes, @replication, tables)
126
+ end
127
+
116
128
  # @private
117
129
  def create_partition_key(table, values)
118
130
  table = @tables[table]
@@ -28,11 +28,21 @@ module Cassandra
28
28
  class Policy
29
29
  # Allows policy to initialize with the cluster instance. This method is
30
30
  # called once before connecting to the cluster.
31
+ #
31
32
  # @param cluster [Cassandra::Cluster] current cluster instance
32
33
  # @return [void]
33
34
  def setup(cluster)
34
35
  end
35
36
 
37
+ # Allows policy to release any external resources it might be holding.
38
+ # This method is called once the cluster has been terminated after calling
39
+ # {Cassandra::Cluster#close}
40
+ #
41
+ # @param cluster [Cassandra::Cluster] current cluster instance
42
+ # @return [void]
43
+ def teardown(cluster)
44
+ end
45
+
36
46
  # @see Cassandra::Listener#host_up
37
47
  def host_up(host)
38
48
  end
@@ -60,8 +60,13 @@ module Cassandra
60
60
  datacenter = datacenter && String(datacenter)
61
61
  max_remote_hosts_to_use = max_remote_hosts_to_use && Integer(max_remote_hosts_to_use)
62
62
 
63
- raise ::ArgumentError, "datacenter cannot be empty" if datacenter && datacenter.empty?
64
- raise ::ArgumentError, "max_remote_hosts_to_use must be nil or >= 0" if max_remote_hosts_to_use && max_remote_hosts_to_use < 0
63
+ unless datacenter.nil?
64
+ Util.assert_not_empty(datacenter) { "datacenter cannot be empty" }
65
+ end
66
+
67
+ unless max_remote_hosts_to_use.nil?
68
+ Util.assert(max_remote_hosts_to_use >= 0) { "max_remote_hosts_to_use must be nil or >= 0" }
69
+ end
65
70
 
66
71
  @datacenter = datacenter
67
72
  @max_remote = max_remote_hosts_to_use
@@ -131,12 +136,12 @@ module Cassandra
131
136
  remote = @remote
132
137
  end
133
138
 
134
- position = @position
135
- total = local.size + remote.size
139
+ total = local.size + remote.size
136
140
 
137
141
  return EMPTY_PLAN if total == 0
138
142
 
139
- @position = (@position + 1) % total
143
+ position = @position % total
144
+ @position = position + 1
140
145
 
141
146
  Plan.new(local, remote, position)
142
147
  end
@@ -37,7 +37,8 @@ module Cassandra
37
37
  return if @remaining == 0
38
38
 
39
39
  @remaining -= 1
40
- index, @index = @index, (@index + 1) % @total
40
+ index = @index
41
+ @index = (index + 1) % @total
41
42
 
42
43
  @hosts[index]
43
44
  end
@@ -117,12 +118,13 @@ module Cassandra
117
118
  # @return [Cassandra::LoadBalancing::Plan] a rotated load balancing plan
118
119
  # @see Cassandra::LoadBalancing::Policy#plan
119
120
  def plan(keyspace, statement, options)
120
- hosts = @hosts
121
- position = @position
122
- total = hosts.size
121
+ hosts = @hosts
122
+ total = hosts.size
123
+
123
124
  return EMPTY_PLAN if total == 0
124
125
 
125
- @position = (position + 1) % total
126
+ position = @position % total
127
+ @position = position + 1
126
128
 
127
129
  Plan.new(hosts, position)
128
130
  end
@@ -88,15 +88,24 @@ module Cassandra
88
88
  # @see Cassandra::LoadBalancing::Policy#host_lost
89
89
  def_delegators :@policy, :distance, :host_found, :host_up, :host_down, :host_lost
90
90
 
91
- # @param wrapped_policy [Cassandra::LoadBalancing::Policy] actual policy to filter
92
- def initialize(wrapped_policy)
93
- methods = [:host_up, :host_down, :host_found, :host_lost, :distance, :plan]
94
-
95
- unless methods.all? {|method| wrapped_policy.respond_to?(method)}
96
- raise ::ArgumentError, "supplied policy must be a Cassandra::LoadBalancing::Policy, #{wrapped_policy.inspect} given"
97
- end
98
-
99
- @policy = wrapped_policy
91
+ # @param wrapped_policy [Cassandra::LoadBalancing::Policy] actual
92
+ # policy to filter
93
+ # @param shuffle [Boolean] (true) whether or not to shuffle the replicas
94
+ #
95
+ # @note If replicas are not shuffled (`shuffle = false`), then it is
96
+ # possibile to create hotspots in a write-heavy scenario, where most
97
+ # of the write requests will be handled by the same node(s). The
98
+ # default behavior of shuffling replicas helps mitigate this by
99
+ # universally distributing write load between replicas. However, it
100
+ # under-utilizes read caching and forces multiple replicas to cache
101
+ # the same read statements.
102
+ def initialize(wrapped_policy, shuffle = true)
103
+ methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, :distance, :plan]
104
+
105
+ Util.assert_responds_to_all(methods, wrapped_policy) { "supplied policy must respond to #{methods.inspect}, but doesn't" }
106
+
107
+ @policy = wrapped_policy
108
+ @shuffle = !!shuffle
100
109
  end
101
110
 
102
111
  def setup(cluster)
@@ -105,13 +114,25 @@ module Cassandra
105
114
  nil
106
115
  end
107
116
 
117
+ def teardown(cluster)
118
+ @cluster = nil
119
+ @policy.teardown(cluster)
120
+ nil
121
+ end
122
+
108
123
  def plan(keyspace, statement, options)
109
124
  return @policy.plan(keyspace, statement, options) unless @cluster
110
125
 
111
126
  replicas = @cluster.find_replicas(keyspace, statement)
112
127
  return @policy.plan(keyspace, statement, options) if replicas.empty?
113
128
 
114
- Plan.new(replicas.shuffle, @policy, keyspace, statement, options)
129
+ if @shuffle
130
+ replicas = replicas.shuffle
131
+ else
132
+ replicas = replicas.dup
133
+ end
134
+
135
+ Plan.new(replicas, @policy, keyspace, statement, options)
115
136
  end
116
137
  end
117
138
  end
@@ -35,12 +35,9 @@ module Cassandra
35
35
  # @param wrapped_policy [Cassandra::LoadBalancing::Policy] actual policy to filter
36
36
  # @raise [ArgumentError] if arguments are of unexpected types
37
37
  def initialize(ips, wrapped_policy)
38
- raise ::ArgumentError, "ips must be enumerable" unless ips.respond_to?(:each)
39
- methods = [:host_up, :host_down, :host_found, :host_lost, :distance, :plan]
40
-
41
- unless methods.all? {|method| wrapped_policy.respond_to?(method)}
42
- raise ::ArgumentError, "supplied policy must be a Cassandra::LoadBalancing::Policy, #{wrapped_policy.inspect} given"
43
- end
38
+ Util.assert_instance_of(::Enumerable, ips) { "ips must be an Enumerable, #{ips.inspect} given" }
39
+ methods = [:host_up, :host_down, :host_found, :host_lost, :setup, :teardown, :distance, :plan]
40
+ Util.assert_responds_to_all(methods, wrapped_policy) { "supplied policy must respond to #{methods.inspect}, but doesn't" }
44
41
 
45
42
  @ips = ::Set.new
46
43
  @policy = wrapped_policy
@@ -52,7 +49,7 @@ module Cassandra
52
49
  when ::String
53
50
  @ips << ::IPAddr.new(ip)
54
51
  else
55
- raise ::ArgumentError, "ips must contain only instance of String or IPAddr, #{ip.inspect} given"
52
+ raise ::ArgumentError, "each ip must be a String or IPAddr, #{ip.inspect} given"
56
53
  end
57
54
  end
58
55
  end
@@ -0,0 +1,35 @@
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
+ # @private
21
+ class NullLogger
22
+ def close(*); end
23
+ def debug(*); end
24
+ def debug?; false end
25
+ def error(*); end
26
+ def error?; false end
27
+ def fatal(*); end
28
+ def fatal?; false end
29
+ def info(*); end
30
+ def info?; false end
31
+ def unknown(*); end
32
+ def warn(*); end
33
+ def warn?; false end
34
+ end
35
+ end