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.
- checksums.yaml +15 -0
- data/lib/cql/client.rb +2 -0
- data/lib/cql/client/asynchronous_client.rb +11 -8
- data/lib/cql/client/asynchronous_prepared_statement.rb +9 -6
- data/lib/cql/client/connection_helper.rb +7 -4
- data/lib/cql/client/execute_options_decoder.rb +24 -0
- data/lib/cql/client/request_runner.rb +2 -2
- data/lib/cql/io/io_reactor.rb +2 -2
- data/lib/cql/protocol/cql_protocol_handler.rb +38 -6
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +26 -3
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +28 -10
- data/spec/cql/client/connection_helper_spec.rb +18 -2
- data/spec/cql/client/keyspace_changer_spec.rb +3 -3
- data/spec/cql/client/request_runner_spec.rb +8 -1
- data/spec/cql/io/io_reactor_spec.rb +8 -2
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +49 -2
- data/spec/integration/client_spec.rb +1 -0
- data/spec/integration/io_spec.rb +4 -4
- data/spec/support/fake_io_reactor.rb +3 -3
- metadata +6 -7
checksums.yaml
ADDED
@@ -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=
|
data/lib/cql/client.rb
CHANGED
@@ -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
|
-
|
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,
|
92
|
+
def execute(cql, options_or_consistency=nil)
|
91
93
|
with_failure_handler do
|
92
|
-
|
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, @
|
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,
|
8
|
+
def initialize(cql, execute_options_decoder, connection_manager, logger)
|
9
9
|
@cql = cql
|
10
|
-
@
|
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,
|
17
|
-
statement = new(cql,
|
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
|
-
|
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.
|
71
|
-
|
72
|
-
|
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)
|
data/lib/cql/io/io_reactor.rb
CHANGED
@@ -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.
|
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
|
-
|
202
|
-
|
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
|
data/lib/cql/version.rb
CHANGED
@@ -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
|
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.
|
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
|
data/spec/integration/io_spec.rb
CHANGED
@@ -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.
|
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-
|
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.
|
134
|
+
rubygems_version: 2.1.5
|
136
135
|
signing_key:
|
137
|
-
specification_version:
|
136
|
+
specification_version: 4
|
138
137
|
summary: Cassandra CQL3 driver
|
139
138
|
test_files:
|
140
139
|
- spec/cql/byte_buffer_spec.rb
|