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

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