cql-rb 1.1.0.pre6 → 1.1.0.pre7

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.
@@ -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