cql-rb 1.1.0.pre6 → 1.1.0.pre7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ N2VjM2JjM2ZiNjU3MTkzOTk4MTU2MjUyMWViZWIwYTExMjc4MTQ0MA==
5
+ data.tar.gz: !binary |-
6
+ OWQ2NWYxY2UwN2YxNjM0NmYxZmRlMmViMmI3OGJlMDdmZTE2NDY3Zg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTZkNmMzNjk5MDRhODdjZmNkMzljZDMxMzA2ZWQ1MWFjM2EyNWI3NmZmMjI2
10
+ OWZlMzY3NTczOTZiYzY2YmQ2ZWFmZWQ0ZTM1M2NjODBkOTI0MzMwODc2YmEx
11
+ OTBhNzdiZWNjYWY0YzA4NTQyNzc2NThhNTY2ZWM1ZDFmYmRjMDg=
12
+ data.tar.gz: !binary |-
13
+ NGM4NmMyZDRhOTE3MTg4NmNjOGFmYmZhNmU5MWRiMTkzOWM4NzhlYzE2YTRk
14
+ ZGFmODJmMGNiMGRlNjFmZTNhMTZhYjdmYzhmOTMwNmQwOWFjMDc5NTk5Y2Jj
15
+ OTRiYWVkMWY2ZTYxMzUxMmRiODY4ZGQ5MzE3NWRkYzk5MTVlMGU=
@@ -12,6 +12,7 @@ module Cql
12
12
  end
13
13
 
14
14
  NotConnectedError = Class.new(CqlError)
15
+ TimeoutError = Class.new(CqlError)
15
16
  ClientError = Class.new(CqlError)
16
17
  AuthenticationError = Class.new(ClientError)
17
18
 
@@ -171,6 +172,7 @@ require 'cql/client/null_logger'
171
172
  require 'cql/client/column_metadata'
172
173
  require 'cql/client/result_metadata'
173
174
  require 'cql/client/query_result'
175
+ require 'cql/client/execute_options_decoder'
174
176
  require 'cql/client/keyspace_changer'
175
177
  require 'cql/client/asynchronous_client'
176
178
  require 'cql/client/asynchronous_prepared_statement'
@@ -9,7 +9,6 @@ module Cql
9
9
  @io_reactor = options[:io_reactor] || Io::IoReactor.new(Protocol::CqlProtocolHandler)
10
10
  @hosts = extract_hosts(options)
11
11
  @initial_keyspace = options[:keyspace]
12
- @default_consistency = options[:default_consistency] || DEFAULT_CONSISTENCY
13
12
  @lock = Mutex.new
14
13
  @connected = false
15
14
  @connecting = false
@@ -19,8 +18,11 @@ module Cql
19
18
  @connection_manager = ConnectionManager.new
20
19
  port = options[:port] || DEFAULT_PORT
21
20
  credentials = options[:credentials]
21
+ connections_per_node = options[:connections_per_node] || 1
22
22
  connection_timeout = options[:connection_timeout] || DEFAULT_CONNECTION_TIMEOUT
23
- @connection_helper = ConnectionHelper.new(@io_reactor, port, credentials, connection_timeout, @logger)
23
+ default_consistency = options[:default_consistency] || DEFAULT_CONSISTENCY
24
+ @execute_options_decoder = ExecuteOptionsDecoder.new(default_consistency)
25
+ @connection_helper = ConnectionHelper.new(@io_reactor, port, credentials, connections_per_node, connection_timeout, @logger)
24
26
  end
25
27
 
26
28
  def connect
@@ -87,15 +89,16 @@ module Cql
87
89
  end
88
90
  end
89
91
 
90
- def execute(cql, consistency=nil)
92
+ def execute(cql, options_or_consistency=nil)
91
93
  with_failure_handler do
92
- execute_request(Protocol::QueryRequest.new(cql, consistency || @default_consistency))
94
+ consistency, timeout = @execute_options_decoder.decode_options(options_or_consistency)
95
+ execute_request(Protocol::QueryRequest.new(cql, consistency), timeout)
93
96
  end
94
97
  end
95
98
 
96
99
  def prepare(cql)
97
100
  with_failure_handler do
98
- AsynchronousPreparedStatement.prepare(cql, @default_consistency, @connection_manager, @logger)
101
+ AsynchronousPreparedStatement.prepare(cql, @execute_options_decoder, @connection_manager, @logger)
99
102
  end
100
103
  end
101
104
 
@@ -161,7 +164,7 @@ module Cql
161
164
 
162
165
  def register_event_listener(connection)
163
166
  register_request = Protocol::RegisterRequest.new(Protocol::TopologyChangeEventResponse::TYPE, Protocol::StatusChangeEventResponse::TYPE)
164
- execute_request(register_request, connection)
167
+ execute_request(register_request, nil, connection)
165
168
  connection.on_closed do
166
169
  if connected?
167
170
  begin
@@ -198,8 +201,8 @@ module Cql
198
201
  end
199
202
  end
200
203
 
201
- def execute_request(request, connection=nil)
202
- f = @request_runner.execute(connection || @connection_manager.random_connection, request)
204
+ def execute_request(request, timeout=nil, connection=nil)
205
+ f = @request_runner.execute(connection || @connection_manager.random_connection, request, timeout)
203
206
  f.map do |result|
204
207
  if result.is_a?(KeyspaceChanged)
205
208
  use(result.keyspace)
@@ -5,16 +5,16 @@ module Cql
5
5
  # @private
6
6
  class AsynchronousPreparedStatement < PreparedStatement
7
7
  # @private
8
- def initialize(cql, default_consistency, connection_manager, logger)
8
+ def initialize(cql, execute_options_decoder, connection_manager, logger)
9
9
  @cql = cql
10
- @default_consistency = default_consistency
10
+ @execute_options_decoder = execute_options_decoder
11
11
  @connection_manager = connection_manager
12
12
  @logger = logger
13
13
  @request_runner = RequestRunner.new
14
14
  end
15
15
 
16
- def self.prepare(cql, default_consistency, connection_manager, logger)
17
- statement = new(cql, default_consistency, connection_manager, logger)
16
+ def self.prepare(cql, execute_options_decoder, connection_manager, logger)
17
+ statement = new(cql, execute_options_decoder, connection_manager, logger)
18
18
  futures = connection_manager.map do |connection|
19
19
  statement.prepare(connection)
20
20
  end
@@ -57,10 +57,13 @@ module Cql
57
57
  def run(args, connection)
58
58
  statement_id = connection[self]
59
59
  bound_args = args.shift(@raw_metadata.size)
60
- consistency = args.shift || @default_consistency
60
+ unless bound_args.size == @raw_metadata.size && args.size <= 1
61
+ raise ArgumentError, "Expected #{@raw_metadata.size} arguments, got #{bound_args.size}"
62
+ end
63
+ consistency, timeout = @execute_options_decoder.decode_options(args.last)
61
64
  statement_id = connection[self]
62
65
  request = Protocol::ExecuteRequest.new(statement_id, @raw_metadata, bound_args, consistency)
63
- @request_runner.execute(connection, request)
66
+ @request_runner.execute(connection, request, timeout)
64
67
  end
65
68
  end
66
69
  end
@@ -4,10 +4,11 @@ module Cql
4
4
  module Client
5
5
  # @private
6
6
  class ConnectionHelper
7
- def initialize(io_reactor, port, credentials, connection_timeout, logger)
7
+ def initialize(io_reactor, port, credentials, connections_per_node, connection_timeout, logger)
8
8
  @io_reactor = io_reactor
9
9
  @port = port
10
10
  @credentials = credentials
11
+ @connections_per_node = connections_per_node
11
12
  @connection_timeout = connection_timeout
12
13
  @logger = logger
13
14
  @request_runner = RequestRunner.new
@@ -67,9 +68,11 @@ module Cql
67
68
  private
68
69
 
69
70
  def connect_to_hosts(hosts, initial_keyspace, peer_discovery)
70
- connection_futures = hosts.map do |host|
71
- connect_to_host(host, initial_keyspace).recover do |error|
72
- FailedConnection.new(error, host, @port)
71
+ connection_futures = hosts.flat_map do |host|
72
+ Array.new(@connections_per_node) do
73
+ connect_to_host(host, initial_keyspace).recover do |error|
74
+ FailedConnection.new(error, host, @port)
75
+ end
73
76
  end
74
77
  end
75
78
  connection_futures.each do |cf|
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ module Client
5
+ class ExecuteOptionsDecoder
6
+ def initialize(default_consistency)
7
+ @default_consistency = default_consistency
8
+ end
9
+
10
+ def decode_options(options_or_consistency)
11
+ consistency = @default_consistency
12
+ timeout = nil
13
+ case options_or_consistency
14
+ when Symbol
15
+ consistency = options_or_consistency
16
+ when Hash
17
+ consistency = options_or_consistency[:consistency] || consistency
18
+ timeout = options_or_consistency[:timeout]
19
+ end
20
+ return consistency, timeout
21
+ end
22
+ end
23
+ end
24
+ end
@@ -4,8 +4,8 @@ module Cql
4
4
  module Client
5
5
  # @private
6
6
  class RequestRunner
7
- def execute(connection, request)
8
- connection.send_request(request).map do |response|
7
+ def execute(connection, request, timeout=nil)
8
+ connection.send_request(request, timeout).map do |response|
9
9
  case response
10
10
  when Protocol::RowsResultResponse
11
11
  QueryResult.new(response.metadata, response.rows)
@@ -32,7 +32,7 @@ module Cql
32
32
  # @example A protocol handler that processes whole lines
33
33
  #
34
34
  # class LineProtocolHandler
35
- # def initialize(connection)
35
+ # def initialize(connection, scheduler)
36
36
  # @connection = connection
37
37
  # # register a listener method for new data, this must be done in the
38
38
  # # in the constructor, and only one listener can be registered
@@ -184,7 +184,7 @@ module Cql
184
184
  def connect(host, port, timeout)
185
185
  connection = Connection.new(host, port, timeout, @unblocker, @clock)
186
186
  f = connection.connect
187
- protocol_handler = @protocol_handler_factory.new(connection)
187
+ protocol_handler = @protocol_handler_factory.new(connection, self)
188
188
  @io_loop.add_socket(connection)
189
189
  @unblocker.unblock!
190
190
  f.map { protocol_handler }
@@ -19,8 +19,9 @@ module Cql
19
19
  # @return [String] the current keyspace for the underlying connection
20
20
  attr_reader :keyspace
21
21
 
22
- def initialize(connection)
22
+ def initialize(connection, scheduler)
23
23
  @connection = connection
24
+ @scheduler = scheduler
24
25
  @connection.on_data(&method(:receive_data))
25
26
  @connection.on_closed(&method(:socket_closed))
26
27
  @promises = Array.new(128) { nil }
@@ -98,11 +99,19 @@ module Cql
98
99
  #
99
100
  # Returns a future that will resolve to the response. When the connection
100
101
  # closes the futures of all active requests will be failed with the error
101
- # that caused the connection to close, or nil
102
+ # that caused the connection to close, or nil.
102
103
  #
104
+ # When `timeout` is specified the future will fail with {Cql::TimeoutError}
105
+ # after that many seconds have passed. If a response arrives after that
106
+ # time it will be lost. If a response never arrives for the request the
107
+ # channel occupied by the request will _not_ be reused.
108
+ #
109
+ # @param [Cql::Protocol::Request] request
110
+ # @param [Float] timeout an optional number of seconds to wait until
111
+ # failing the request
103
112
  # @return [Cql::Future<Cql::Protocol::Response>] a future that resolves to
104
113
  # the response
105
- def send_request(request)
114
+ def send_request(request, timeout=nil)
106
115
  return Future.failed(NotConnectedError.new) if closed?
107
116
  promise = RequestPromise.new(request)
108
117
  id = nil
@@ -121,6 +130,11 @@ module Cql
121
130
  @request_queue_in << promise
122
131
  end
123
132
  end
133
+ if timeout
134
+ @scheduler.schedule_timer(timeout).on_value do
135
+ promise.time_out!
136
+ end
137
+ end
124
138
  promise.future
125
139
  end
126
140
 
@@ -140,9 +154,21 @@ module Cql
140
154
 
141
155
  def initialize(request)
142
156
  @request = request
157
+ @timed_out = false
143
158
  super()
144
159
  end
145
160
 
161
+ def timed_out?
162
+ @timed_out
163
+ end
164
+
165
+ def time_out!
166
+ unless future.completed?
167
+ @timed_out = true
168
+ fail(TimeoutError.new)
169
+ end
170
+ end
171
+
146
172
  def encode_frame!
147
173
  @frame = @request.encode_frame(0)
148
174
  end
@@ -182,7 +208,9 @@ module Cql
182
208
  if response.is_a?(Protocol::SetKeyspaceResultResponse)
183
209
  @keyspace = response.keyspace
184
210
  end
185
- promise.fulfill(response)
211
+ unless promise.timed_out?
212
+ promise.fulfill(response)
213
+ end
186
214
  end
187
215
 
188
216
  def flush_request_queue
@@ -198,8 +226,12 @@ module Cql
198
226
  @lock.synchronize do
199
227
  if @request_queue_out.any? && (id = next_stream_id)
200
228
  promise = @request_queue_out.shift
201
- frame = promise.frame
202
- @promises[id] = promise
229
+ if promise.timed_out?
230
+ id = nil
231
+ else
232
+ frame = promise.frame
233
+ @promises[id] = promise
234
+ end
203
235
  end
204
236
  end
205
237
  if id
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.1.0.pre6'.freeze
4
+ VERSION = '1.1.0.pre7'.freeze
5
5
  end
@@ -44,10 +44,10 @@ module Cql
44
44
 
45
45
  before do
46
46
  io_reactor.on_connection do |connection|
47
- connection.handle_request do |request|
47
+ connection.handle_request do |request, timeout|
48
48
  response = nil
49
49
  if @request_handler
50
- response = @request_handler.call(request, connection, proc { connection.default_request_handler(request) })
50
+ response = @request_handler.call(request, connection, proc { connection.default_request_handler(request) }, timeout)
51
51
  end
52
52
  unless response
53
53
  response = connection.default_request_handler(request)
@@ -170,6 +170,12 @@ module Cql
170
170
  connections.should have(2).items
171
171
  end
172
172
 
173
+ it 'connects to each host the specifie number of times' do
174
+ c = described_class.new(connection_options.merge(hosts: %w[h1.example.com h2.example.com], connections_per_node: 3))
175
+ c.connect.value
176
+ connections.should have(6).items
177
+ end
178
+
173
179
  it 'succeeds even if only one of the connections succeeded' do
174
180
  io_reactor.node_down('h1.example.com')
175
181
  io_reactor.node_down('h3.example.com')
@@ -474,11 +480,16 @@ module Cql
474
480
  last_request.should == Protocol::QueryRequest.new(cql, :all)
475
481
  end
476
482
 
477
- it 'uses the specified consistency' do
483
+ it 'uses the consistency given as last argument' do
478
484
  client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', :three).value
479
485
  last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :three)
480
486
  end
481
487
 
488
+ it 'uses the consistency given as an option' do
489
+ client.execute('UPDATE stuff SET thing = 1 WHERE id = 3', consistency: :local_quorum).value
490
+ last_request.should == Protocol::QueryRequest.new('UPDATE stuff SET thing = 1 WHERE id = 3', :local_quorum)
491
+ end
492
+
482
493
  context 'with a void CQL query' do
483
494
  it 'returns nil' do
484
495
  handle_request do |request|
@@ -619,6 +630,18 @@ module Cql
619
630
  end
620
631
  end
621
632
  end
633
+
634
+ context 'with a timeout' do
635
+ it 'passes the timeout along with the request' do
636
+ sent_timeout = nil
637
+ handle_request do |request, _, _, timeout|
638
+ sent_timeout = timeout
639
+ nil
640
+ end
641
+ client.execute(cql, timeout: 3).value
642
+ sent_timeout.should == 3
643
+ end
644
+ end
622
645
  end
623
646
 
624
647
  describe '#prepare' do
@@ -41,7 +41,7 @@ module Cql
41
41
  ]
42
42
  end
43
43
 
44
- def handle_request(connection, request)
44
+ def handle_request(connection, request, timeout)
45
45
  case request
46
46
  when Protocol::PrepareRequest
47
47
  statement_id = [rand(2**31)].pack('c*')
@@ -56,14 +56,14 @@ module Cql
56
56
 
57
57
  before do
58
58
  connections.each do |c|
59
- c.handle_request { |r| handle_request(c, r) }
59
+ c.handle_request { |r, t| handle_request(c, r, t) }
60
60
  end
61
61
  connection_manager.add_connections(connections)
62
62
  end
63
63
 
64
64
  describe '.prepare' do
65
65
  it 'prepares a statement on all connections' do
66
- f = described_class.prepare(cql, :one, connection_manager, logger)
66
+ f = described_class.prepare(cql, ExecuteOptionsDecoder.new(:one), connection_manager, logger)
67
67
  f.value
68
68
  connections.each do |c|
69
69
  c.requests.should include(Protocol::PrepareRequest.new(cql))
@@ -71,13 +71,13 @@ module Cql
71
71
  end
72
72
 
73
73
  it 'returns a prepared statement object' do
74
- f = described_class.prepare(cql, :two, connection_manager, logger)
74
+ f = described_class.prepare(cql, ExecuteOptionsDecoder.new(:two), connection_manager, logger)
75
75
  f.value.should be_a(PreparedStatement)
76
76
  end
77
77
 
78
78
  it 'returns a failed future when something goes wrong in the preparation' do
79
79
  connections.each(&:close)
80
- f = described_class.prepare(cql, :three, connection_manager, logger)
80
+ f = described_class.prepare(cql, ExecuteOptionsDecoder.new(:three), connection_manager, logger)
81
81
  expect { f.value }.to raise_error(NotConnectedError)
82
82
  end
83
83
 
@@ -92,7 +92,7 @@ module Cql
92
92
 
93
93
  describe '#metadata' do
94
94
  let :statement do
95
- described_class.prepare(cql, :all, connection_manager, logger).value
95
+ described_class.prepare(cql, ExecuteOptionsDecoder.new(:all), connection_manager, logger).value
96
96
  end
97
97
 
98
98
  it 'returns the interpreted metadata' do
@@ -103,7 +103,7 @@ module Cql
103
103
 
104
104
  describe '#execute' do
105
105
  let :statement do
106
- described_class.prepare(cql, :local_quorum, connection_manager, logger).value
106
+ described_class.prepare(cql, ExecuteOptionsDecoder.new(:local_quorum), connection_manager, logger).value
107
107
  end
108
108
 
109
109
  it 'executes itself on one of the connections' do
@@ -132,6 +132,24 @@ module Cql
132
132
  request.consistency.should == :two
133
133
  end
134
134
 
135
+ it 'sends the consistency given as an option' do
136
+ statement.execute(11, 'hello', consistency: :two)
137
+ request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
138
+ request.consistency.should == :two
139
+ end
140
+
141
+ it 'uses the specified timeout' do
142
+ sent_timeout = nil
143
+ connections.each do |c|
144
+ c.handle_request do |r, t|
145
+ sent_timeout = t
146
+ handle_request(c, r, t)
147
+ end
148
+ end
149
+ statement.execute(11, 'hello', timeout: 3)
150
+ sent_timeout.should == 3
151
+ end
152
+
135
153
  context 'when it receives a new connection from the connection manager' do
136
154
  let :new_connection do
137
155
  FakeConnection.new('h3.example.com', 1234, 5)
@@ -139,7 +157,7 @@ module Cql
139
157
 
140
158
  before do
141
159
  statement
142
- new_connection.handle_request { |r| handle_request(new_connection, r) }
160
+ new_connection.handle_request { |r, t| handle_request(new_connection, r, t) }
143
161
  connections.each(&:close)
144
162
  connection_manager.add_connections([new_connection])
145
163
  end
@@ -169,8 +187,8 @@ module Cql
169
187
  it 'returns a failed future when the number of arguments is wrong' do
170
188
  f1 = statement.execute(11, :one)
171
189
  f2 = statement.execute(11, 'foo', 22, :one)
172
- expect { f1.value }.to raise_error
173
- expect { f2.value }.to raise_error
190
+ expect { f1.value }.to raise_error(NoMethodError)
191
+ expect { f2.value }.to raise_error(ArgumentError)
174
192
  end
175
193
  end
176
194
  end
@@ -7,7 +7,7 @@ module Cql
7
7
  module Client
8
8
  describe ConnectionHelper do
9
9
  let :connection_helper do
10
- described_class.new(io_reactor, 9876, nil, 7, logger)
10
+ described_class.new(io_reactor, 9876, nil, 1, 7, logger)
11
11
  end
12
12
 
13
13
  let :io_reactor do
@@ -114,7 +114,7 @@ module Cql
114
114
 
115
115
  it 'authenticates when authentication is required and credentials were specified' do
116
116
  credentials = {'username' => 'foo', 'password' => 'bar'}
117
- connection_helper = described_class.new(io_reactor, 9876, credentials, 7, logger)
117
+ connection_helper = described_class.new(io_reactor, 9876, credentials, 1, 7, logger)
118
118
  connection = FakeConnection.new('host0', 9876, 7)
119
119
  authentication_sent = false
120
120
  connection.handle_request do |request|
@@ -178,6 +178,13 @@ module Cql
178
178
  connection_helper.connect(hosts, 'some_keyspace')
179
179
  connection_helper.should have_received(:discover_peers).with([connection], 'some_keyspace')
180
180
  end
181
+
182
+ it 'connects to each node a configurable number of times' do
183
+ connection_helper = described_class.new(io_reactor, 9876, nil, connections_per_node = 3, 7, logger)
184
+ connection_helper.connect(hosts, nil)
185
+ io_reactor.should have_received(:connect).with('host0', 9876, 7).exactly(3).times
186
+ io_reactor.should have_received(:connect).with('host1', 9876, 7).exactly(3).times
187
+ end
181
188
  end
182
189
 
183
190
  describe '#discover_peers' do
@@ -329,6 +336,15 @@ module Cql
329
336
  f = connection_helper.discover_peers(seed_connections, nil)
330
337
  f.value
331
338
  end
339
+
340
+ it 'connects to each node a configurable number of times' do
341
+ connection_helper = described_class.new(io_reactor, 9876, nil, connections_per_node = 3, 7, logger)
342
+ connection = FakeConnection.new('host3', 9876, 7)
343
+ io_reactor.stub(:connect).with('1.0.0.3', 9876, 7).and_return(Future.resolved(connection))
344
+ peer_request_response { seed_connection_rows + extra_connection_rows.take(1) }
345
+ connection_helper.discover_peers(seed_connections, nil).value
346
+ io_reactor.should have_received(:connect).with('1.0.0.3', 9876, 7).exactly(3).times
347
+ end
332
348
  end
333
349
  end
334
350
  end
@@ -16,13 +16,13 @@ module Cql
16
16
 
17
17
  describe '#use_keyspace' do
18
18
  it 'sends a query request with a USE statement' do
19
- connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one)).and_return(Future.resolved)
19
+ connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one), nil).and_return(Future.resolved)
20
20
  f = keyspace_changer.use_keyspace('important_stuff', connection)
21
21
  connection.should have_received(:send_request)
22
22
  end
23
23
 
24
24
  it 'accepts quoted keyspace names' do
25
- connection.stub(:send_request).with(Protocol::QueryRequest.new('USE "ImportantStuff"', :one)).and_return(Future.resolved)
25
+ connection.stub(:send_request).with(Protocol::QueryRequest.new('USE "ImportantStuff"', :one), nil).and_return(Future.resolved)
26
26
  f = keyspace_changer.use_keyspace('"ImportantStuff"', connection)
27
27
  connection.should have_received(:send_request)
28
28
  end
@@ -39,7 +39,7 @@ module Cql
39
39
  end
40
40
 
41
41
  it 'resolves to the given connection' do
42
- connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one)).and_return(Future.resolved)
42
+ connection.stub(:send_request).with(Protocol::QueryRequest.new('USE important_stuff', :one), nil).and_return(Future.resolved)
43
43
  f = keyspace_changer.use_keyspace('important_stuff', connection)
44
44
  f.value.should equal(connection)
45
45
  end
@@ -64,8 +64,15 @@ module Cql
64
64
  end
65
65
 
66
66
  it 'executes the request' do
67
- connection.should_receive(:send_request).and_return(Future.resolved(rows_response))
67
+ connection.stub(:send_request).and_return(Future.resolved(rows_response))
68
68
  runner.execute(connection, request)
69
+ connection.should have_received(:send_request)
70
+ end
71
+
72
+ it 'executes the request with the specified timeout' do
73
+ connection.stub(:send_request).and_return(Future.resolved(rows_response))
74
+ runner.execute(connection, request, 7)
75
+ connection.should have_received(:send_request).with(request, 7)
69
76
  end
70
77
 
71
78
  it 'transforms a RowsResultResponse to a query result' do
@@ -79,7 +79,7 @@ module Cql
79
79
 
80
80
  it 'closes all sockets' do
81
81
  connection = nil
82
- protocol_handler_factory.stub(:new) do |sh|
82
+ protocol_handler_factory.stub(:new) do |sh, _|
83
83
  connection = sh
84
84
  double(:protocol_handler)
85
85
  end
@@ -141,7 +141,7 @@ module Cql
141
141
  end
142
142
 
143
143
  before do
144
- protocol_handler_factory.stub(:new) do |connection|
144
+ protocol_handler_factory.stub(:new) do |connection, _|
145
145
  connection.to_io.stub(:connect_nonblock)
146
146
  protocol_handler.stub(:connection).and_return(connection)
147
147
  protocol_handler
@@ -165,6 +165,12 @@ module Cql
165
165
  reactor.stop if reactor.running?
166
166
  end
167
167
 
168
+ it 'calls #new on the protocol handler factory with the connection and the reactor itself' do
169
+ reactor.start.value
170
+ reactor.connect('example.com', 9999, 5).value
171
+ protocol_handler_factory.should have_received(:new).with(an_instance_of(Connection), reactor)
172
+ end
173
+
168
174
  it 'returns a future that resolves to a new protocol handler' do
169
175
  reactor.start.value
170
176
  f = reactor.connect('example.com', 9999, 5)
@@ -7,13 +7,17 @@ module Cql
7
7
  module Protocol
8
8
  describe CqlProtocolHandler do
9
9
  let :protocol_handler do
10
- described_class.new(connection)
10
+ described_class.new(connection, scheduler)
11
11
  end
12
12
 
13
13
  let :connection do
14
14
  double(:connection)
15
15
  end
16
16
 
17
+ let :scheduler do
18
+ double(:scheduler)
19
+ end
20
+
17
21
  let :request do
18
22
  Protocol::OptionsRequest.new
19
23
  end
@@ -32,6 +36,7 @@ module Cql
32
36
  connection.stub(:on_connected) do |&h|
33
37
  connection.stub(:connected_listener).and_return(h)
34
38
  end
39
+ scheduler.stub(:schedule_timer).and_return(Promise.new.future)
35
40
  protocol_handler
36
41
  end
37
42
 
@@ -66,7 +71,6 @@ module Cql
66
71
  before do
67
72
  connection.stub(:write).and_yield(buffer)
68
73
  connection.stub(:closed?).and_return(false)
69
-
70
74
  connection.stub(:connected?).and_return(true)
71
75
  end
72
76
 
@@ -151,6 +155,49 @@ module Cql
151
155
  expect { f.value }.to raise_error(NotConnectedError)
152
156
  end
153
157
  end
158
+
159
+ context 'when the request times out' do
160
+ let :timer_promise do
161
+ Promise.new
162
+ end
163
+
164
+ before do
165
+ scheduler.stub(:schedule_timer).with(3).and_return(timer_promise.future)
166
+ end
167
+
168
+ it 'raises a TimeoutError' do
169
+ f = protocol_handler.send_request(request, 3)
170
+ timer_promise.fulfill
171
+ expect { f.value }.to raise_error(TimeoutError)
172
+ end
173
+
174
+ it 'does not attempt to fulfill the promise when the request has already timed out' do
175
+ f = protocol_handler.send_request(request, 3)
176
+ timer_promise.fulfill
177
+ expect { connection.data_listener.call([0x81, 0, 0, 2, 0].pack('C4N')) }.to_not raise_error
178
+ end
179
+
180
+ it 'never sends a request when it has already timed out' do
181
+ write_count = 0
182
+ connection.stub(:write) do |s, &h|
183
+ write_count += 1
184
+ if h
185
+ h.call(buffer)
186
+ else
187
+ buffer << s
188
+ end
189
+ end
190
+ 128.times do
191
+ scheduler.stub(:schedule_timer).with(5).and_return(Promise.new.future)
192
+ protocol_handler.send_request(request)
193
+ end
194
+ scheduler.stub(:schedule_timer).with(5).and_return(timer_promise.future)
195
+ f = protocol_handler.send_request(request, 5)
196
+ timer_promise.fulfill
197
+ 128.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
198
+ write_count.should == 128
199
+ end
200
+ end
154
201
  end
155
202
 
156
203
  describe '#close' do
@@ -70,6 +70,7 @@ describe 'A CQL client' do
70
70
  let :multi_client do
71
71
  opts = connection_options.dup
72
72
  opts[:host] = ([opts[:host]] * 10).join(',')
73
+ opts[:connections_per_node] = 3
73
74
  Cql::Client.connect(opts)
74
75
  end
75
76
 
@@ -124,7 +124,7 @@ end
124
124
 
125
125
  module IoSpec
126
126
  class TestConnection
127
- def initialize(connection)
127
+ def initialize(connection, scheduler)
128
128
  @connection = connection
129
129
  @connection.on_data(&method(:receive_data))
130
130
  @lock = Mutex.new
@@ -143,7 +143,7 @@ module IoSpec
143
143
  end
144
144
 
145
145
  class LineProtocolHandler
146
- def initialize(connection)
146
+ def initialize(connection, scheduler)
147
147
  @connection = connection
148
148
  @connection.on_data(&method(:process_data))
149
149
  @lock = Mutex.new
@@ -176,8 +176,8 @@ module IoSpec
176
176
  end
177
177
 
178
178
  class RedisProtocolHandler
179
- def initialize(connection)
180
- @line_protocol = LineProtocolHandler.new(connection)
179
+ def initialize(connection, scheduler)
180
+ @line_protocol = LineProtocolHandler.new(connection, scheduler)
181
181
  @line_protocol.on_line(&method(:handle_line))
182
182
  @lock = Mutex.new
183
183
  @responses = []
@@ -127,7 +127,7 @@ class FakeConnection
127
127
  @event_listeners.any? && @registered_event_types.any?
128
128
  end
129
129
 
130
- def send_request(request)
130
+ def send_request(request, timeout=nil)
131
131
  if @closed
132
132
  Cql::Future.failed(Cql::NotConnectedError.new)
133
133
  else
@@ -136,7 +136,7 @@ class FakeConnection
136
136
  when Cql::Protocol::RegisterRequest
137
137
  @registered_event_types.concat(request.events)
138
138
  end
139
- response = @request_handler.call(request)
139
+ response = @request_handler.call(request, timeout)
140
140
  if response.is_a?(Cql::Protocol::SetKeyspaceResultResponse)
141
141
  @keyspace = response.keyspace
142
142
  end
@@ -144,7 +144,7 @@ class FakeConnection
144
144
  end
145
145
  end
146
146
 
147
- def default_request_handler(request)
147
+ def default_request_handler(request, timeout=nil)
148
148
  response = @responses.shift
149
149
  unless response
150
150
  case request
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cql-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.pre6
5
- prerelease: 6
4
+ version: 1.1.0.pre7
6
5
  platform: ruby
7
6
  authors:
8
7
  - Theo Hultberg
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-10-01 00:00:00.000000000 Z
11
+ date: 2013-10-07 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: A pure Ruby CQL3 driver for Cassandra
15
14
  email:
@@ -24,6 +23,7 @@ files:
24
23
  - lib/cql/client/column_metadata.rb
25
24
  - lib/cql/client/connection_helper.rb
26
25
  - lib/cql/client/connection_manager.rb
26
+ - lib/cql/client/execute_options_decoder.rb
27
27
  - lib/cql/client/keyspace_changer.rb
28
28
  - lib/cql/client/null_logger.rb
29
29
  - lib/cql/client/query_result.rb
@@ -114,27 +114,26 @@ files:
114
114
  homepage: http://github.com/iconara/cql-rb
115
115
  licenses:
116
116
  - Apache License 2.0
117
+ metadata: {}
117
118
  post_install_message:
118
119
  rdoc_options: []
119
120
  require_paths:
120
121
  - lib
121
122
  required_ruby_version: !ruby/object:Gem::Requirement
122
- none: false
123
123
  requirements:
124
124
  - - ! '>='
125
125
  - !ruby/object:Gem::Version
126
126
  version: 1.9.2
127
127
  required_rubygems_version: !ruby/object:Gem::Requirement
128
- none: false
129
128
  requirements:
130
129
  - - ! '>'
131
130
  - !ruby/object:Gem::Version
132
131
  version: 1.3.1
133
132
  requirements: []
134
133
  rubyforge_project:
135
- rubygems_version: 1.8.23
134
+ rubygems_version: 2.1.5
136
135
  signing_key:
137
- specification_version: 3
136
+ specification_version: 4
138
137
  summary: Cassandra CQL3 driver
139
138
  test_files:
140
139
  - spec/cql/byte_buffer_spec.rb