cassandra-driver 1.0.0 → 1.1.0

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.
@@ -23,24 +23,31 @@ module Cassandra
23
23
  attr_reader :credentials, :auth_provider, :compressor, :port,
24
24
  :connect_timeout, :ssl, :connections_per_local_node,
25
25
  :connections_per_remote_node, :heartbeat_interval,
26
- :idle_timeout
26
+ :idle_timeout, :schema_refresh_delay, :schema_refresh_timeout
27
27
  attr_accessor :protocol_version
28
28
 
29
- def initialize(protocol_version, credentials, auth_provider, compressor, port, connect_timeout, ssl, connections_per_local_node, connections_per_remote_node, heartbeat_interval, idle_timeout)
30
- @protocol_version = protocol_version
31
- @credentials = credentials
32
- @auth_provider = auth_provider
33
- @compressor = compressor
34
- @port = port
35
- @connect_timeout = connect_timeout
36
- @ssl = ssl
37
- @heartbeat_interval = heartbeat_interval
38
- @idle_timeout = idle_timeout
29
+ def initialize(protocol_version, credentials, auth_provider, compressor, port, connect_timeout, ssl, connections_per_local_node, connections_per_remote_node, heartbeat_interval, idle_timeout, synchronize_schema, schema_refresh_delay, schema_refresh_timeout)
30
+ @protocol_version = protocol_version
31
+ @credentials = credentials
32
+ @auth_provider = auth_provider
33
+ @compressor = compressor
34
+ @port = port
35
+ @connect_timeout = connect_timeout
36
+ @ssl = ssl
37
+ @heartbeat_interval = heartbeat_interval
38
+ @idle_timeout = idle_timeout
39
+ @synchronize_schema = synchronize_schema
40
+ @schema_refresh_delay = schema_refresh_delay
41
+ @schema_refresh_timeout = schema_refresh_timeout
39
42
 
40
43
  @connections_per_local_node = connections_per_local_node
41
44
  @connections_per_remote_node = connections_per_remote_node
42
45
  end
43
46
 
47
+ def synchronize_schema?
48
+ @synchronize_schema
49
+ end
50
+
44
51
  def compression
45
52
  @compressor && @compressor.algorithm
46
53
  end
@@ -130,6 +130,9 @@ module Cassandra
130
130
 
131
131
  return self unless keyspace
132
132
 
133
+ columns = columns.each_with_object(::Hash.new) do |row, index|
134
+ index[row['column_name']] = row
135
+ end
133
136
  table = create_table(table, columns, host.release_version)
134
137
  keyspace = keyspace.update_table(table)
135
138
 
@@ -139,7 +142,25 @@ module Cassandra
139
142
  @keyspaces = keyspaces
140
143
  end
141
144
 
142
- keyspace_updated(keyspace)
145
+ keyspace_changed(keyspace)
146
+
147
+ self
148
+ end
149
+
150
+ def delete_table(keyspace_name, table_name)
151
+ keyspace = @keyspaces[keyspace_name]
152
+
153
+ return self unless keyspace
154
+
155
+ keyspace = keyspace.delete_table(table_name)
156
+
157
+ synchronize do
158
+ keyspaces = @keyspaces.dup
159
+ keyspaces[keyspace_name] = keyspace
160
+ @keyspaces = keyspaces
161
+ end
162
+
163
+ keyspace_changed(keyspace)
143
164
 
144
165
  self
145
166
  end
@@ -20,7 +20,7 @@ module Cassandra
20
20
  # @private
21
21
  class Driver
22
22
  def self.let(name, &block)
23
- define_method(name) { @instances[name] ||= @defaults.fetch(name) { instance_eval(&block) } }
23
+ define_method(name) { @instances.has_key?(name) ? @instances[name] : @instances[name] = instance_eval(&block) }
24
24
  define_method(:"#{name}=") { |object| @instances[name] = object }
25
25
  end
26
26
 
@@ -42,7 +42,9 @@ module Cassandra
42
42
  no_replication_strategy
43
43
  )
44
44
  }
45
- let(:futures_factory) { Future }
45
+
46
+ let(:executor) { Executors::ThreadPool.new(thread_pool_size) }
47
+ let(:futures_factory) { Future::Factory.new(executor) }
46
48
 
47
49
  let(:schema_type_parser) { Cluster::Schema::TypeParser.new }
48
50
 
@@ -56,9 +58,9 @@ module Cassandra
56
58
 
57
59
  let(:connector) { Cluster::Connector.new(logger, io_reactor, cluster_registry, connection_options, execution_options) }
58
60
 
59
- let(:control_connection) { Cluster::ControlConnection.new(logger, io_reactor, cluster_registry, cluster_schema, cluster_metadata, load_balancing_policy, reconnection_policy, address_resolution_policy, connector) }
61
+ let(:control_connection) { Cluster::ControlConnection.new(logger, io_reactor, cluster_registry, cluster_schema, cluster_metadata, load_balancing_policy, reconnection_policy, address_resolution_policy, connector, connection_options) }
60
62
 
61
- let(:cluster) { Cluster.new(logger, io_reactor, control_connection, cluster_registry, cluster_schema, cluster_metadata, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, address_resolution_policy, connector, futures_factory) }
63
+ let(:cluster) { Cluster.new(logger, io_reactor, executor, control_connection, cluster_registry, cluster_schema, cluster_metadata, execution_options, connection_options, load_balancing_policy, reconnection_policy, retry_policy, address_resolution_policy, connector, futures_factory) }
62
64
 
63
65
  let(:execution_options) do
64
66
  Execution::Options.new({
@@ -69,7 +71,24 @@ module Cassandra
69
71
  })
70
72
  end
71
73
 
72
- let(:connection_options) { Cluster::Options.new(protocol_version, credentials, auth_provider, compressor, port, connect_timeout, ssl, connections_per_local_node, connections_per_remote_node, heartbeat_interval, idle_timeout) }
74
+ let(:connection_options) do
75
+ Cluster::Options.new(
76
+ protocol_version,
77
+ credentials,
78
+ auth_provider,
79
+ compressor,
80
+ port,
81
+ connect_timeout,
82
+ ssl,
83
+ connections_per_local_node,
84
+ connections_per_remote_node,
85
+ heartbeat_interval,
86
+ idle_timeout,
87
+ synchronize_schema,
88
+ schema_refresh_delay,
89
+ schema_refresh_timeout
90
+ )
91
+ end
73
92
 
74
93
  let(:port) { 9042 }
75
94
  let(:protocol_version) { 2 }
@@ -80,7 +99,7 @@ module Cassandra
80
99
  let(:credentials) { nil }
81
100
  let(:auth_provider) { nil }
82
101
  let(:datacenter) { nil }
83
- let(:load_balancing_policy) { LoadBalancing::Policies::TokenAware.new(LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, 0)) }
102
+ let(:load_balancing_policy) { LoadBalancing::Policies::TokenAware.new(LoadBalancing::Policies::DCAwareRoundRobin.new(datacenter, 0), shuffle_replicas) }
84
103
  let(:reconnection_policy) { Reconnection::Policies::Exponential.new(0.5, 30, 2) }
85
104
  let(:retry_policy) { Retry::Policies::Default.new }
86
105
  let(:address_resolution_policy) { AddressResolution::Policies::None.new }
@@ -90,6 +109,11 @@ module Cassandra
90
109
  let(:heartbeat_interval) { 30 }
91
110
  let(:idle_timeout) { 60 }
92
111
  let(:timeout) { 10 }
112
+ let(:synchronize_schema) { true }
113
+ let(:schema_refresh_delay) { 1 }
114
+ let(:schema_refresh_timeout) { 10 }
115
+ let(:thread_pool_size) { 4 }
116
+ let(:shuffle_replicas) { true }
93
117
 
94
118
  let(:connections_per_local_node) { 2 }
95
119
  let(:connections_per_remote_node) { 1 }
@@ -97,8 +121,7 @@ module Cassandra
97
121
  let(:listeners) { [] }
98
122
 
99
123
  def initialize(defaults = {})
100
- @defaults = defaults
101
- @instances = {}
124
+ @instances = defaults
102
125
  end
103
126
 
104
127
  def connect(addresses)
@@ -113,7 +136,18 @@ module Cassandra
113
136
  addresses.each {|address| cluster_registry.host_found(address)}
114
137
 
115
138
  logger.info('Establishing control connection')
116
- control_connection.connect_async.map(cluster)
139
+
140
+ promise = futures_factory.promise
141
+
142
+ control_connection.connect_async.on_complete do |f|
143
+ if f.resolved?
144
+ promise.fulfill(cluster)
145
+ else
146
+ f.on_failure {|e| promise.break(e)}
147
+ end
148
+ end
149
+
150
+ promise.future
117
151
  end
118
152
  end
119
153
  end
@@ -31,6 +31,20 @@ module Cassandra
31
31
  # @return [Numeric] request timeout interval
32
32
  attr_reader :timeout
33
33
 
34
+ # @return [String] paging state
35
+ #
36
+ # @note Although this feature exists to allow web applications to store
37
+ # paging state in an [HTTP cookie](http://en.wikipedia.org/wiki/HTTP_cookie), **it is not safe to
38
+ # expose without encrypting or otherwise securing it**. Paging state
39
+ # contains information internal to the Apache Cassandra cluster, such as
40
+ # partition key and data. Additionally, if a paging state is sent with CQL
41
+ # statement, different from the original, the behavior of Cassandra is
42
+ # undefined and will likely cause a server process of the coordinator of
43
+ # such request to abort.
44
+ #
45
+ # @see Cassandra::Result#paging_state
46
+ attr_reader :paging_state
47
+
34
48
  # @private
35
49
  def initialize(options)
36
50
  consistency = options[:consistency]
@@ -38,6 +52,7 @@ module Cassandra
38
52
  trace = options[:trace]
39
53
  timeout = options[:timeout]
40
54
  serial_consistency = options[:serial_consistency]
55
+ paging_state = options[:paging_state]
41
56
 
42
57
  Util.assert_one_of(CONSISTENCIES, consistency) { ":consistency must be one of #{CONSISTENCIES.inspect}, #{consistency.inspect} given" }
43
58
 
@@ -46,7 +61,7 @@ module Cassandra
46
61
  end
47
62
 
48
63
  unless page_size.nil?
49
- page_size = options[:page_size] = Integer(page_size)
64
+ page_size = Integer(page_size)
50
65
  Util.assert(page_size > 0) { ":page_size must be a positive integer, #{page_size.inspect} given" }
51
66
  end
52
67
 
@@ -55,11 +70,17 @@ module Cassandra
55
70
  Util.assert(timeout > 0) { ":timeout must be greater than 0, #{timeout} given" }
56
71
  end
57
72
 
73
+ unless paging_state.nil?
74
+ paging_state = String(paging_state)
75
+ Util.assert_not_empty(paging_state) { ":paging_state must not be empty" }
76
+ end
77
+
58
78
  @consistency = consistency
59
79
  @page_size = page_size
60
80
  @trace = !!trace
61
81
  @timeout = timeout
62
82
  @serial_consistency = serial_consistency
83
+ @paging_state = paging_state
63
84
  end
64
85
 
65
86
  # @return [Boolean] whether request tracing was enabled
@@ -68,8 +89,14 @@ module Cassandra
68
89
  end
69
90
 
70
91
  # @private
71
- def override(options)
72
- Options.new(to_h.merge!(options))
92
+ def override(*options)
93
+ merged = options.unshift(to_h).inject do |base, opts|
94
+ next base unless opts
95
+ Util.assert_instance_of(::Hash, opts) { "options must be a Hash, #{options.inspect} given" }
96
+ base.merge!(opts)
97
+ end
98
+
99
+ Options.new(merged)
73
100
  end
74
101
 
75
102
  # @private
@@ -0,0 +1,111 @@
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
+ module Executors
22
+ class ThreadPool
23
+ # @private
24
+ class Task
25
+ def initialize(*args, &block)
26
+ @args = args
27
+ @block = block
28
+ end
29
+
30
+ def run
31
+ @block.call(*@args)
32
+ rescue ::Exception
33
+ ensure
34
+ @args = @block = nil
35
+ end
36
+ end
37
+
38
+ include MonitorMixin
39
+
40
+ def initialize(size)
41
+ mon_initialize
42
+
43
+ @cond = new_cond
44
+ @tasks = ::Array.new
45
+ @waiting = 0
46
+ @pool = ::Array.new(size, &method(:spawn_thread))
47
+ @term = false
48
+ end
49
+
50
+ def execute(*args, &block)
51
+ synchronize do
52
+ @tasks << Task.new(*args, &block)
53
+ @cond.signal if @waiting > 0
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ def shutdown
60
+ execute do
61
+ synchronize do
62
+ @term = true
63
+ @cond.broadcast if @waiting > 0
64
+ end
65
+ end
66
+
67
+ nil
68
+ end
69
+
70
+ private
71
+
72
+ def spawn_thread(i)
73
+ Thread.new(&method(:run))
74
+ end
75
+
76
+ def run
77
+ Thread.current.abort_on_exception = true
78
+
79
+ loop do
80
+ tasks = nil
81
+
82
+ synchronize do
83
+ @waiting += 1
84
+ @cond.wait while !@term && @tasks.empty?
85
+ @waiting -= 1
86
+
87
+ return if @tasks.empty?
88
+
89
+ tasks = @tasks
90
+ @tasks = ::Array.new
91
+ end
92
+
93
+ tasks.each(&:run).clear
94
+ end
95
+ end
96
+ end
97
+
98
+ class SameThread
99
+ def execute(*args, &block)
100
+ yield(*args)
101
+ nil
102
+ rescue ::Exception
103
+ nil
104
+ end
105
+
106
+ def shutdown
107
+ nil
108
+ end
109
+ end
110
+ end
111
+ end
@@ -48,8 +48,6 @@ module Cassandra
48
48
  def initialize(error)
49
49
  raise ::ArgumentError, "error must be an exception, #{error.inspect} given" unless error.is_a?(::Exception)
50
50
 
51
- error.set_backtrace(caller) unless error.backtrace
52
-
53
51
  @error = error
54
52
  end
55
53
 
@@ -163,18 +161,64 @@ module Cassandra
163
161
  end
164
162
  end
165
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
+
166
210
  # Returns a future resolved to a given value
167
211
  # @param value [Object] value for the future
168
212
  # @return [Cassandra::Future<Object>] a future value
169
213
  def self.value(value)
170
- Value.new(value)
214
+ @@factory.value(value)
171
215
  end
172
216
 
173
217
  # Returns a future resolved to a given error
174
218
  # @param error [Exception] error for the future
175
219
  # @return [Cassandra::Future<Exception>] a future error
176
220
  def self.error(error)
177
- Error.new(error)
221
+ @@factory.error(error)
178
222
  end
179
223
 
180
224
  # Returns a future that resolves with values of all futures
@@ -186,35 +230,7 @@ module Cassandra
186
230
  # combine
187
231
  # @return [Cassandra::Future<Array<Object>>] a combined future
188
232
  def self.all(*futures)
189
- futures = Array(futures.first) if futures.one?
190
- monitor = Monitor.new
191
- promise = Promise.new
192
- remaining = futures.length
193
- values = Array.new(remaining)
194
-
195
- futures.each_with_index do |future, i|
196
- future.on_complete do |v, e|
197
- if e
198
- promise.break(e)
199
- else
200
- done = false
201
- monitor.synchronize do
202
- remaining -= 1
203
- done = (remaining == 0)
204
- values[i] = v
205
- end
206
- promise.fulfill(values) if done
207
- end
208
- end
209
- end
210
-
211
- promise.future
212
- end
213
-
214
- # @private
215
- # Returns a new promise instance
216
- def self.promise
217
- Promise.new
233
+ @@factory.all(*futures)
218
234
  end
219
235
 
220
236
  # @private
@@ -222,24 +238,6 @@ module Cassandra
222
238
  @signal = signal
223
239
  end
224
240
 
225
- # Returns future value or raises future error
226
- # @note This method blocks until a future is resolved
227
- # @raise [Exception] error used to resolve this future if any
228
- # @return [Object] value used to resolve this future if any
229
- def get
230
- @signal.get
231
- end
232
-
233
- # Block until the future has been resolved
234
- # @note This method blocks until a future is resolved
235
- # @note This method won't raise any errors or return anything but the
236
- # future itself
237
- # @return [self]
238
- def join
239
- @signal.join
240
- self
241
- end
242
-
243
241
  # Run block when future resolves to a value
244
242
  # @note The block can be called synchronously from current thread if the
245
243
  # future has already been resolved, or, asynchronously, from background
@@ -350,6 +348,25 @@ module Cassandra
350
348
  raise ::ArgumentError, "no block given" unless block_given?
351
349
  @signal.fallback(&block)
352
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
353
370
  end
354
371
 
355
372
  # @private
@@ -457,10 +474,11 @@ module Cassandra
457
474
 
458
475
  include MonitorMixin
459
476
 
460
- def initialize
477
+ def initialize(executor)
461
478
  mon_initialize
462
479
 
463
480
  @cond = new_cond
481
+ @executor = executor
464
482
  @state = :pending
465
483
  @waiting = 0
466
484
  @error = nil
@@ -473,8 +491,6 @@ module Cassandra
473
491
  raise ::ArgumentError, "error must be an exception, #{error.inspect} given"
474
492
  end
475
493
 
476
- error.set_backtrace(caller) unless error.backtrace
477
-
478
494
  return unless @state == :pending
479
495
 
480
496
  listeners = nil
@@ -488,12 +504,14 @@ module Cassandra
488
504
  listeners, @listeners = @listeners, nil
489
505
  end
490
506
 
491
- listeners.each do |listener|
492
- listener.failure(error) rescue nil
493
- end
507
+ @executor.execute do
508
+ listeners.each do |listener|
509
+ listener.failure(error) rescue nil
510
+ end
494
511
 
495
- synchronize do
496
- @cond.broadcast if @waiting > 0
512
+ synchronize do
513
+ @cond.broadcast if @waiting > 0
514
+ end
497
515
  end
498
516
 
499
517
  self
@@ -513,12 +531,14 @@ module Cassandra
513
531
  listeners, @listeners = @listeners, nil
514
532
  end
515
533
 
516
- listeners.each do |listener|
517
- listener.success(value) rescue nil
518
- end
534
+ @executor.execute do
535
+ listeners.each do |listener|
536
+ listener.success(value) rescue nil
537
+ end
519
538
 
520
- synchronize do
521
- @cond.broadcast if @waiting > 0
539
+ synchronize do
540
+ @cond.broadcast if @waiting > 0
541
+ end
522
542
  end
523
543
 
524
544
  self
@@ -612,7 +632,7 @@ module Cassandra
612
632
  if @state == :pending
613
633
  synchronize do
614
634
  if @state == :pending
615
- promise = Promise.new
635
+ promise = Promise.new(@executor)
616
636
  listener = Listeners::Then.new(promise, &block)
617
637
  @listeners << listener
618
638
  return promise.future
@@ -635,7 +655,7 @@ module Cassandra
635
655
  if @state == :pending
636
656
  synchronize do
637
657
  if @state == :pending
638
- promise = Promise.new
658
+ promise = Promise.new(@executor)
639
659
  listener = Listeners::Fallback.new(promise, &block)
640
660
  @listeners << listener
641
661
  return promise.future
@@ -657,8 +677,8 @@ module Cassandra
657
677
 
658
678
  attr_reader :future
659
679
 
660
- def initialize
661
- @signal = Signal.new
680
+ def initialize(executor)
681
+ @signal = Signal.new(executor)
662
682
  @future = Future.new(@signal)
663
683
  end
664
684