cassandra-driver 1.0.0.beta.1 → 1.0.0.beta.2

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -12
  3. data/ext/cassandra_murmur3/cassandra_murmur3.c +178 -0
  4. data/ext/cassandra_murmur3/extconf.rb +2 -0
  5. data/lib/cassandra.rb +132 -24
  6. data/lib/cassandra/auth.rb +5 -5
  7. data/lib/cassandra/client/connector.rb +11 -6
  8. data/lib/cassandra/cluster.rb +47 -23
  9. data/lib/cassandra/cluster/client.rb +5 -4
  10. data/lib/cassandra/cluster/connector.rb +17 -4
  11. data/lib/cassandra/cluster/control_connection.rb +21 -16
  12. data/lib/cassandra/cluster/metadata.rb +124 -0
  13. data/lib/cassandra/cluster/options.rb +5 -3
  14. data/lib/cassandra/cluster/registry.rb +26 -9
  15. data/lib/cassandra/cluster/schema.rb +23 -4
  16. data/lib/cassandra/cluster/schema/partitioners.rb +21 -0
  17. data/lib/cassandra/cluster/schema/partitioners/murmur3.rb +47 -0
  18. data/lib/cassandra/cluster/schema/partitioners/ordered.rb +37 -0
  19. data/lib/cassandra/cluster/schema/partitioners/random.rb +37 -0
  20. data/lib/cassandra/cluster/schema/replication_strategies.rb +21 -0
  21. data/lib/cassandra/cluster/schema/replication_strategies/network_topology.rb +92 -0
  22. data/lib/cassandra/cluster/schema/replication_strategies/none.rb +39 -0
  23. data/lib/cassandra/cluster/schema/replication_strategies/simple.rb +44 -0
  24. data/lib/cassandra/cluster/schema/type_parser.rb +1 -1
  25. data/lib/cassandra/compression.rb +1 -1
  26. data/lib/cassandra/driver.rb +29 -4
  27. data/lib/cassandra/execution/options.rb +3 -0
  28. data/lib/cassandra/host.rb +9 -5
  29. data/lib/cassandra/keyspace.rb +17 -4
  30. data/lib/cassandra/listener.rb +3 -3
  31. data/lib/cassandra/load_balancing.rb +12 -11
  32. data/lib/cassandra/load_balancing/policies.rb +2 -1
  33. data/lib/cassandra/load_balancing/policies/dc_aware_round_robin.rb +1 -1
  34. data/lib/cassandra/load_balancing/policies/round_robin.rb +37 -0
  35. data/lib/cassandra/load_balancing/policies/token_aware.rb +119 -0
  36. data/lib/cassandra/protocol/cql_protocol_handler.rb +2 -2
  37. data/lib/cassandra/reconnection.rb +5 -5
  38. data/lib/cassandra/retry.rb +3 -3
  39. data/lib/cassandra/session.rb +7 -0
  40. data/lib/cassandra/statements/bound.rb +4 -2
  41. data/lib/cassandra/statements/prepared.rb +28 -6
  42. data/lib/cassandra/table.rb +49 -4
  43. data/lib/cassandra/time_uuid.rb +1 -0
  44. data/lib/cassandra/util.rb +0 -2
  45. data/lib/cassandra/version.rb +1 -1
  46. metadata +50 -45
@@ -46,7 +46,7 @@ module Cassandra
46
46
  # @param authentication_class [String] the authentication class used by
47
47
  # the server.
48
48
  # @return [Cassandra::Auth::Authenticator, nil] an object with an
49
- # interface matching {Cassandra::Auth::Authenticator} or nil if the
49
+ # interface matching {Cassandra::Auth::Authenticator} or `nil` if the
50
50
  # authentication class is not supported.
51
51
  end
52
52
 
@@ -55,12 +55,12 @@ module Cassandra
55
55
  # block. If any of the method calls block, the whole connection process
56
56
  # will be blocked.
57
57
  #
58
- # @note Authenticators created by auth providers don't need to be subclasses
59
- # of this class, but need to implement the same methods. This class exists
60
- # only for documentation purposes.
58
+ # @abstract Authenticators created by auth providers don't need to be
59
+ # subclasses of this class, but need to implement the same methods. This
60
+ # class exists only for documentation purposes.
61
61
  #
62
62
  # @see https://github.com/apache/cassandra/blob/trunk/doc/native_protocol_v2.spec#L257-L273 Cassandra native protocol v2 SASL authentication
63
- # @see Cassandra::Auth::Provider
63
+ # @see Cassandra::Auth::Provider#create_authenticator
64
64
  class Authenticator
65
65
  # @!method initial_response
66
66
  #
@@ -88,16 +88,16 @@ module Cassandra
88
88
 
89
89
  # @private
90
90
  class ConnectStep
91
- def initialize(io_reactor, protocol_handler_factory, port, connection_timeout, logger)
91
+ def initialize(io_reactor, protocol_handler_factory, port, connection_options, logger)
92
92
  @io_reactor = io_reactor
93
93
  @protocol_handler_factory = protocol_handler_factory
94
94
  @port = port
95
- @connection_timeout = connection_timeout
95
+ @connection_options = connection_options
96
96
  @logger = logger
97
97
  end
98
98
 
99
99
  def run(pending_connection)
100
- @io_reactor.connect(pending_connection.host, @port, @connection_timeout, &@protocol_handler_factory).map do |connection|
100
+ @io_reactor.connect(pending_connection.host, @port, @connection_options, &@protocol_handler_factory).map do |connection|
101
101
  pending_connection.with_connection(connection)
102
102
  end
103
103
  end
@@ -105,12 +105,17 @@ module Cassandra
105
105
 
106
106
  # @private
107
107
  class CacheOptionsStep
108
+ def initialize(timeout = nil)
109
+ @timeout = timeout
110
+ end
111
+
108
112
  def run(pending_connection)
109
- f = pending_connection.execute(Protocol::OptionsRequest.new)
113
+ f = pending_connection.execute(Protocol::OptionsRequest.new, @timeout)
110
114
  f.on_value do |supported_options|
111
115
  pending_connection[:cql_version] = supported_options['CQL_VERSION']
112
116
  pending_connection[:compression] = supported_options['COMPRESSION']
113
117
  end
118
+
114
119
  f.map(pending_connection)
115
120
  end
116
121
  end
@@ -250,8 +255,8 @@ module Cassandra
250
255
  @connection[key] = value
251
256
  end
252
257
 
253
- def execute(request, &block)
254
- @request_runner.execute(@connection, request, nil, nil, &block)
258
+ def execute(request, timeout = nil, &block)
259
+ @request_runner.execute(@connection, request, timeout, nil, &block)
255
260
  end
256
261
  end
257
262
 
@@ -26,15 +26,6 @@ module Cassandra
26
26
  class Cluster
27
27
  extend Forwardable
28
28
 
29
- # @!method each_host
30
- # Yield or enumerate each member of this cluster
31
- # @overload each_host
32
- # @yieldparam host [Cassandra::Host] current host
33
- # @return [Array<Cassandra::Host>] a list of hosts
34
- # @overload each_host
35
- # @return [Enumerator<Cassandra::Host>] an enumerator
36
- # @!parse alias :hosts :each_host
37
- #
38
29
  # @!method host(address)
39
30
  # Find a host by its address
40
31
  # @param address [IPAddr, String] ip address
@@ -44,17 +35,8 @@ module Cassandra
44
35
  # Determine if a host by a given address exists
45
36
  # @param address [IPAddr, String] ip address
46
37
  # @return [Boolean] true or false
47
- def_delegators :@registry, :hosts, :each_host, :host, :has_host?
48
-
49
- # @!method each_keyspace
50
- # Yield or enumerate each keyspace defined in this cluster
51
- # @overload each_keyspace
52
- # @yieldparam keyspace [Cassandra::Keyspace] current keyspace
53
- # @return [Array<Cassandra::Keyspace>] a list of keyspaces
54
- # @overload each_keyspace
55
- # @return [Enumerator<Cassandra::Keyspace>] an enumerator
56
- # @!parse alias :keyspaces :each_keyspace
57
- #
38
+ def_delegators :@registry, :host, :has_host?
39
+
58
40
  # @!method keyspace(name)
59
41
  # Find a keyspace by name
60
42
  # @param name [String] keyspace name
@@ -64,15 +46,30 @@ module Cassandra
64
46
  # Determine if a keyspace by a given name exists
65
47
  # @param name [String] keyspace name
66
48
  # @return [Boolean] true or false
67
- def_delegators :@schema, :keyspaces, :each_keyspace, :keyspace, :has_keyspace?
49
+ def_delegators :@schema, :keyspace, :has_keyspace?
50
+
51
+ # @!method name
52
+ # Return cluster's name
53
+ # @return [String] cluster's name
54
+ #
55
+ # @!method find_replicas(keyspace, statement)
56
+ # Return replicas for a given statement and keyspace
57
+ # @note an empty list is returned when statement/keyspace information is
58
+ # not enough to determine replica list.
59
+ # @param keyspace [String] keyspace name
60
+ # @param statement [Cassandra::Statement] statement for which to find
61
+ # replicas
62
+ # @return [Array<Cassandra::Host>] a list of replicas
63
+ def_delegators :@metadata, :name, :find_replicas
68
64
 
69
65
  # @private
70
- def initialize(logger, io_reactor, control_connection, cluster_registry, cluster_schema, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, connector, futures_factory)
66
+ def initialize(logger, io_reactor, control_connection, cluster_registry, cluster_schema, cluster_metadata, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, connector, futures_factory)
71
67
  @logger = logger
72
68
  @io_reactor = io_reactor
73
69
  @control_connection = control_connection
74
70
  @registry = cluster_registry
75
71
  @schema = cluster_schema
72
+ @metadata = cluster_metadata
76
73
  @execution_options = execution_options
77
74
  @connection_options = connection_options
78
75
  @load_balancing_policy = load_balancing_policy
@@ -104,6 +101,32 @@ module Cassandra
104
101
  self
105
102
  end
106
103
 
104
+ # Yield or enumerate each member of this cluster
105
+ # @overload each_host
106
+ # @yieldparam host [Cassandra::Host] current host
107
+ # @return [Cassandra::Cluster] self
108
+ # @overload each_host
109
+ # @return [Array<Cassandra::Host>] a list of hosts
110
+ def each_host(&block)
111
+ r = @registry.each_host(&block)
112
+ return self if r == @registry
113
+ r
114
+ end
115
+ alias :hosts :each_host
116
+
117
+ # Yield or enumerate each keyspace defined in this cluster
118
+ # @overload each_keyspace
119
+ # @yieldparam keyspace [Cassandra::Keyspace] current keyspace
120
+ # @return [Cassandra::Cluster] self
121
+ # @overload each_keyspace
122
+ # @return [Array<Cassandra::Keyspace>] a list of keyspaces
123
+ def each_keyspace(&block)
124
+ r = @schema.each_keyspace(&block)
125
+ return self if r == @schema
126
+ r
127
+ end
128
+ alias :keyspaces :each_keyspace
129
+
107
130
  # Asynchronously create a new session, optionally scoped to a keyspace
108
131
  #
109
132
  # @param keyspace [String] optional keyspace to scope session to
@@ -115,7 +138,7 @@ module Cassandra
115
138
  return @futures.error(::ArgumentError.new("keyspace must be a string, #{keyspace.inspect} given"))
116
139
  end
117
140
 
118
- client = Client.new(@logger, @registry, @io_reactor, @connector, @load_balancing_policy, @reconnection_policy, @retry_policy, @connection_options, @futures)
141
+ client = Client.new(@logger, @registry, @schema, @io_reactor, @connector, @load_balancing_policy, @reconnection_policy, @retry_policy, @connection_options, @futures)
119
142
  session = Session.new(client, @execution_options, @futures)
120
143
  promise = @futures.promise
121
144
 
@@ -186,6 +209,7 @@ end
186
209
  require 'cassandra/cluster/client'
187
210
  require 'cassandra/cluster/connector'
188
211
  require 'cassandra/cluster/control_connection'
212
+ require 'cassandra/cluster/metadata'
189
213
  require 'cassandra/cluster/options'
190
214
  require 'cassandra/cluster/registry'
191
215
  require 'cassandra/cluster/schema'
@@ -22,9 +22,12 @@ module Cassandra
22
22
  class Client
23
23
  include MonitorMixin
24
24
 
25
- def initialize(logger, cluster_registry, io_reactor, connector, load_balancing_policy, reconnection_policy, retry_policy, connection_options, futures_factory)
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)
26
28
  @logger = logger
27
29
  @registry = cluster_registry
30
+ @schema = cluster_schema
28
31
  @reactor = io_reactor
29
32
  @connector = connector
30
33
  @load_balancing_policy = load_balancing_policy
@@ -597,9 +600,7 @@ module Cassandra
597
600
  @preparing_statements[host].delete(cql)
598
601
  end
599
602
 
600
- execution_info = create_execution_info(keyspace, statement, options, request, r, hosts)
601
-
602
- promise.fulfill(Statements::Prepared.new(cql, r.metadata, r.result_metadata, execution_info))
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))
603
604
  when Protocol::RawRowsResultResponse
604
605
  r.materialize(statement.result_metadata)
605
606
  promise.fulfill(Results::Paged.new(r.rows, r.paging_state, r.trace_id, keyspace, statement, options, hosts, request.consistency, retries, self, @futures))
@@ -69,7 +69,11 @@ module Cassandra
69
69
  end
70
70
 
71
71
  def refresh_status(host)
72
- return Future.resolved if synchronize { @connections[host] }
72
+ if synchronize { @connections[host] }
73
+ @registry.host_up(host.ip)
74
+
75
+ return Future.resolved
76
+ end
73
77
 
74
78
  @logger.info("Refreshing host status ip=#{host.ip}")
75
79
  f = do_connect(host)
@@ -130,11 +134,20 @@ module Cassandra
130
134
 
131
135
  def create_connector
132
136
  authentication_step = @options.protocol_version == 1 ? Cassandra::Client::CredentialsAuthenticationStep.new(@options.credentials) : Cassandra::Client::SaslAuthenticationStep.new(@options.auth_provider)
133
- protocol_handler_factory = lambda { |connection| Protocol::CqlProtocolHandler.new(connection, @reactor, @options.protocol_version, @options.compressor) }
137
+ protocol_handler_factory = lambda do |connection|
138
+ raise "no connection given" unless connection
139
+ Protocol::CqlProtocolHandler.new(connection, @reactor, @options.protocol_version, @options.compressor)
140
+ end
134
141
 
135
142
  Cassandra::Client::Connector.new([
136
- Cassandra::Client::ConnectStep.new(@reactor, protocol_handler_factory, @options.port, @options.connection_timeout, @logger),
137
- Cassandra::Client::CacheOptionsStep.new,
143
+ Cassandra::Client::ConnectStep.new(
144
+ @reactor,
145
+ protocol_handler_factory,
146
+ @options.port,
147
+ {:timeout => @options.connect_timeout, :ssl => @options.ssl},
148
+ @logger
149
+ ),
150
+ Cassandra::Client::CacheOptionsStep.new(@options.connect_timeout),
138
151
  Cassandra::Client::InitializeStep.new(@options.compressor, @logger),
139
152
  authentication_step,
140
153
  Cassandra::Client::CachePropertiesStep.new,
@@ -22,11 +22,12 @@ module Cassandra
22
22
  class ControlConnection
23
23
  include MonitorMixin
24
24
 
25
- def initialize(logger, io_reactor, cluster_registry, cluster_schema, load_balancing_policy, reconnection_policy, connector)
25
+ def initialize(logger, io_reactor, cluster_registry, cluster_schema, cluster_metadata, load_balancing_policy, reconnection_policy, connector)
26
26
  @logger = logger
27
27
  @io_reactor = io_reactor
28
28
  @registry = cluster_registry
29
29
  @schema = cluster_schema
30
+ @metadata = cluster_metadata
30
31
  @load_balancing_policy = load_balancing_policy
31
32
  @reconnection_policy = reconnection_policy
32
33
  @connector = connector
@@ -96,8 +97,8 @@ module Cassandra
96
97
 
97
98
  private
98
99
 
99
- SELECT_LOCAL = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, release_version FROM system.local', nil, nil, :one)
100
- SELECT_PEERS = Protocol::QueryRequest.new('SELECT peer, rack, data_center, host_id, rpc_address, release_version FROM system.peers', nil, nil, :one)
100
+ SELECT_LOCAL = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, release_version, tokens, partitioner FROM system.local', nil, nil, :one)
101
+ SELECT_PEERS = Protocol::QueryRequest.new('SELECT peer, rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers', nil, nil, :one)
101
102
  SELECT_KEYSPACES = Protocol::QueryRequest.new('SELECT * FROM system.schema_keyspaces', nil, nil, :one)
102
103
  SELECT_TABLES = Protocol::QueryRequest.new('SELECT * FROM system.schema_columnfamilies', nil, nil, :one)
103
104
  SELECT_COLUMNS = Protocol::QueryRequest.new('SELECT * FROM system.schema_columns', nil, nil, :one)
@@ -178,9 +179,13 @@ module Cassandra
178
179
  when 'NEW_NODE'
179
180
  address = event.address
180
181
 
181
- refresh_host_async(address) unless @registry.has_host?(address)
182
+ unless @registry.has_host?(address)
183
+ refresh_host_async(address)
184
+ refresh_schema_async
185
+ end
182
186
  when 'REMOVED_NODE'
183
187
  @registry.host_lost(event.address)
188
+ refresh_schema_async
184
189
  end
185
190
  end
186
191
  end
@@ -206,6 +211,7 @@ module Cassandra
206
211
  host = @registry.host(connection.host)
207
212
 
208
213
  @schema.update_keyspaces(host, keyspaces.rows, tables.rows, columns.rows)
214
+ @metadata.rebuild_token_map
209
215
  end
210
216
  end
211
217
 
@@ -268,32 +274,31 @@ module Cassandra
268
274
 
269
275
  raise NO_HOSTS if local.empty? && peers.empty?
270
276
 
271
- local_ip = connection.host
272
- ips = ::Set.new
273
-
274
- unless local.empty?
275
- ips << local_ip
276
- @registry.host_found(IPAddr.new(local_ip), local.first)
277
- end
277
+ ips = ::Set.new
278
278
 
279
279
  peers.each do |data|
280
- ip = peer_ip(data)
281
- ips << ip.to_s
280
+ ips << ip = peer_ip(data)
282
281
  @registry.host_found(ip, data)
283
282
  end
284
283
 
284
+ ips << ip = IPAddr.new(connection.host)
285
+ data = local.first
286
+ @registry.host_found(ip, data)
287
+
285
288
  futures = []
286
289
 
287
290
  @registry.each_host do |host|
288
- if ips.include?(host.ip.to_s)
291
+ if ips.include?(host.ip)
289
292
  futures << refresh_host_status(host) if host.down?
290
293
  else
291
294
  @registry.host_lost(host.ip)
292
295
  end
293
296
  end
294
297
 
298
+ @metadata.update(data)
299
+
295
300
  if futures.empty?
296
- Ione::Future.resolved(self)
301
+ Ione::Future.resolved
297
302
  else
298
303
  Ione::Future.all(*futures)
299
304
  end
@@ -332,7 +337,7 @@ module Cassandra
332
337
  if ip == connection.host
333
338
  request = SELECT_LOCAL
334
339
  else
335
- request = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, rpc_address, release_version FROM system.peers WHERE peer = ?', [address], nil, :one)
340
+ request = Protocol::QueryRequest.new('SELECT rack, data_center, host_id, rpc_address, release_version, tokens FROM system.peers WHERE peer = ?', [address], nil, :one)
336
341
  end
337
342
 
338
343
  connection.send_request(request).map do |response|
@@ -0,0 +1,124 @@
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 Metadata
23
+ include MonitorMixin
24
+
25
+ attr_reader :name
26
+
27
+ def initialize(cluster_registry, cluster_schema, schema_partitioners, replication_strategies, default_replication_strategy)
28
+ @registry = cluster_registry
29
+ @schema = cluster_schema
30
+ @partitioners = schema_partitioners
31
+ @strategies = replication_strategies
32
+ @default_strategy = default_replication_strategy
33
+ end
34
+
35
+ def find_replicas(keyspace, statement)
36
+ unless statement.respond_to?(:partition_key) && statement.respond_to?(:keyspace)
37
+ return EMPTY_LIST
38
+ end
39
+
40
+ keyspace = String(statement.keyspace || keyspace)
41
+ partition_key = statement.partition_key
42
+ return EMPTY_LIST unless keyspace && partition_key
43
+
44
+ partitioner = @partitioner
45
+ return EMPTY_LIST unless partitioner
46
+
47
+ keyspace_hosts = @token_replicas[keyspace]
48
+ return EMPTY_LIST if keyspace_hosts.nil? || keyspace_hosts.empty?
49
+
50
+ token = partitioner.create_token(partition_key)
51
+ index = insertion_point(@token_ring, token)
52
+ index = 0 if index >= @token_ring.size
53
+ hosts = keyspace_hosts[@token_ring[index]]
54
+ return EMPTY_LIST unless hosts
55
+
56
+ hosts
57
+ end
58
+
59
+ def update(data)
60
+ @name = data['name']
61
+ @partitioner = @partitioners[data['partitioner']]
62
+
63
+ self
64
+ end
65
+
66
+ def rebuild_token_map
67
+ partitioner = @partitioner
68
+ return self unless partitioner
69
+
70
+ tokens = ::SortedSet.new
71
+ token_to_host = ::Hash.new
72
+
73
+ @registry.each_host do |host|
74
+ host.tokens.each do |token|
75
+ token = partitioner.parse_token(token) rescue next
76
+ tokens.add(token)
77
+ token_to_host[token] = host
78
+ end
79
+ end
80
+
81
+ token_ring = tokens.to_a
82
+ token_replicas = ::Hash.new
83
+
84
+ @schema.each_keyspace do |keyspace|
85
+ replication = keyspace.replication
86
+ strategy = @strategies[replication.klass] || @default_strategy
87
+
88
+ token_replicas[keyspace.name] = strategy.replication_map(
89
+ token_to_host,
90
+ token_ring,
91
+ replication.options
92
+ )
93
+ end
94
+
95
+ @token_replicas = token_replicas
96
+ @token_ring = token_ring
97
+
98
+ self
99
+ end
100
+
101
+ private
102
+
103
+ def insertion_point(list, item)
104
+ min = 0
105
+ max = list.size - 1
106
+
107
+ while min <= max do
108
+ idx = (min + max) / 2
109
+ val = list[idx]
110
+
111
+ if val < item
112
+ min = idx + 1
113
+ elsif val > item
114
+ max = idx - 1
115
+ else
116
+ return idx # item found
117
+ end
118
+ end
119
+
120
+ min # item not found.
121
+ end
122
+ end
123
+ end
124
+ end