cql-rb 1.2.2 → 2.0.0.pre0
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.
- checksums.yaml +4 -4
- data/.yardopts +4 -0
- data/README.md +139 -17
- data/lib/cql/client.rb +237 -8
- data/lib/cql/client/asynchronous_client.rb +138 -54
- data/lib/cql/client/asynchronous_prepared_statement.rb +41 -6
- data/lib/cql/client/authenticators.rb +46 -0
- data/lib/cql/client/batch.rb +115 -0
- data/lib/cql/client/connector.rb +255 -0
- data/lib/cql/client/execute_options_decoder.rb +25 -9
- data/lib/cql/client/keyspace_changer.rb +5 -5
- data/lib/cql/client/peer_discovery.rb +33 -0
- data/lib/cql/client/query_result.rb +124 -1
- data/lib/cql/client/request_runner.rb +4 -2
- data/lib/cql/client/synchronous_client.rb +14 -2
- data/lib/cql/client/synchronous_prepared_statement.rb +19 -1
- data/lib/cql/future.rb +97 -50
- data/lib/cql/io/connection.rb +0 -1
- data/lib/cql/io/io_reactor.rb +1 -1
- data/lib/cql/protocol.rb +8 -1
- data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
- data/lib/cql/protocol/decoding.rb +10 -15
- data/lib/cql/protocol/frame_decoder.rb +2 -1
- data/lib/cql/protocol/frame_encoder.rb +5 -4
- data/lib/cql/protocol/requests/auth_response_request.rb +31 -0
- data/lib/cql/protocol/requests/batch_request.rb +59 -0
- data/lib/cql/protocol/requests/credentials_request.rb +1 -1
- data/lib/cql/protocol/requests/execute_request.rb +45 -17
- data/lib/cql/protocol/requests/options_request.rb +1 -1
- data/lib/cql/protocol/requests/prepare_request.rb +1 -1
- data/lib/cql/protocol/requests/query_request.rb +97 -5
- data/lib/cql/protocol/requests/register_request.rb +1 -1
- data/lib/cql/protocol/requests/startup_request.rb +4 -4
- data/lib/cql/protocol/response.rb +2 -2
- data/lib/cql/protocol/responses/auth_challenge_response.rb +25 -0
- data/lib/cql/protocol/responses/auth_success_response.rb +25 -0
- data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
- data/lib/cql/protocol/responses/detailed_error_response.rb +1 -1
- data/lib/cql/protocol/responses/error_response.rb +3 -2
- data/lib/cql/protocol/responses/event_response.rb +3 -2
- data/lib/cql/protocol/responses/prepared_result_response.rb +10 -6
- data/lib/cql/protocol/responses/raw_rows_result_response.rb +27 -0
- data/lib/cql/protocol/responses/ready_response.rb +1 -1
- data/lib/cql/protocol/responses/result_response.rb +2 -2
- data/lib/cql/protocol/responses/rows_result_response.rb +43 -23
- data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
- data/lib/cql/protocol/responses/schema_change_result_response.rb +1 -1
- data/lib/cql/protocol/responses/set_keyspace_result_response.rb +1 -1
- data/lib/cql/protocol/responses/status_change_event_response.rb +1 -1
- data/lib/cql/protocol/responses/supported_response.rb +1 -1
- data/lib/cql/protocol/responses/void_result_response.rb +1 -1
- data/lib/cql/protocol/type_converter.rb +2 -2
- data/lib/cql/uuid.rb +2 -2
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +493 -50
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +193 -11
- data/spec/cql/client/authenticators_spec.rb +56 -0
- data/spec/cql/client/batch_spec.rb +277 -0
- data/spec/cql/client/connector_spec.rb +606 -0
- data/spec/cql/client/execute_options_decoder_spec.rb +95 -0
- data/spec/cql/client/keyspace_changer_spec.rb +8 -8
- data/spec/cql/client/peer_discovery_spec.rb +92 -0
- data/spec/cql/client/query_result_spec.rb +352 -0
- data/spec/cql/client/request_runner_spec.rb +31 -5
- data/spec/cql/client/synchronous_client_spec.rb +44 -1
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +63 -1
- data/spec/cql/future_spec.rb +50 -2
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +16 -5
- data/spec/cql/protocol/decoding_spec.rb +16 -6
- data/spec/cql/protocol/encoding_spec.rb +3 -1
- data/spec/cql/protocol/frame_encoder_spec.rb +99 -50
- data/spec/cql/protocol/requests/auth_response_request_spec.rb +62 -0
- data/spec/cql/protocol/requests/batch_request_spec.rb +155 -0
- data/spec/cql/protocol/requests/credentials_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/execute_request_spec.rb +184 -71
- data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/prepare_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/query_request_spec.rb +255 -32
- data/spec/cql/protocol/requests/register_request_spec.rb +1 -1
- data/spec/cql/protocol/requests/startup_request_spec.rb +12 -6
- data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +31 -0
- data/spec/cql/protocol/responses/auth_success_response_spec.rb +31 -0
- data/spec/cql/protocol/responses/authenticate_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/detailed_error_response_spec.rb +14 -7
- data/spec/cql/protocol/responses/error_response_spec.rb +4 -2
- data/spec/cql/protocol/responses/event_response_spec.rb +7 -4
- data/spec/cql/protocol/responses/prepared_result_response_spec.rb +89 -34
- data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +66 -0
- data/spec/cql/protocol/responses/ready_response_spec.rb +1 -1
- data/spec/cql/protocol/responses/result_response_spec.rb +19 -7
- data/spec/cql/protocol/responses/rows_result_response_spec.rb +56 -11
- data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +1 -1
- data/spec/cql/protocol/responses/status_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/supported_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +2 -1
- data/spec/cql/protocol/responses/void_result_response_spec.rb +1 -1
- data/spec/cql/protocol/type_converter_spec.rb +21 -4
- data/spec/cql/uuid_spec.rb +10 -3
- data/spec/integration/client_spec.rb +251 -28
- data/spec/integration/protocol_spec.rb +213 -62
- data/spec/integration/regression_spec.rb +4 -1
- data/spec/integration/uuid_spec.rb +4 -1
- data/spec/support/fake_io_reactor.rb +5 -5
- metadata +36 -7
- data/lib/cql/client/connection_helper.rb +0 -181
- data/spec/cql/client/connection_helper_spec.rb +0 -429
@@ -21,14 +21,34 @@ module Cql
|
|
21
21
|
]
|
22
22
|
end
|
23
23
|
|
24
|
+
let :raw_result_metadata do
|
25
|
+
raw_metadata + [
|
26
|
+
['my_keyspace', 'my_table', 'a_third_column', :double],
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
24
30
|
let :rows do
|
25
31
|
[
|
26
|
-
{'my_column' => 11, 'my_other_column' => 'hello'},
|
27
|
-
{'my_column' => 22, 'my_other_column' => 'foo'},
|
28
|
-
{'my_column' => 33, 'my_other_column' => 'bar'},
|
32
|
+
{'my_column' => 11, 'my_other_column' => 'hello', 'a_third_column' => 0.0},
|
33
|
+
{'my_column' => 22, 'my_other_column' => 'foo', 'a_third_column' => 0.0},
|
34
|
+
{'my_column' => 33, 'my_other_column' => 'bar', 'a_third_column' => 0.0},
|
29
35
|
]
|
30
36
|
end
|
31
37
|
|
38
|
+
let :raw_rows do
|
39
|
+
buffer = ByteBuffer.new("\x00\x00\x00\x03")
|
40
|
+
buffer << "\x00\x00\x00\x04\x00\x00\x00\x0b"
|
41
|
+
buffer << "\x00\x00\x00\x05hello"
|
42
|
+
buffer << "\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"
|
43
|
+
buffer << "\x00\x00\x00\x04\x00\x00\x00\x18"
|
44
|
+
buffer << "\x00\x00\x00\x03foo"
|
45
|
+
buffer << "\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"
|
46
|
+
buffer << "\x00\x00\x00\x04\x00\x00\x00\x21"
|
47
|
+
buffer << "\x00\x00\x00\x03bar"
|
48
|
+
buffer << "\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00"
|
49
|
+
buffer
|
50
|
+
end
|
51
|
+
|
32
52
|
let :cql do
|
33
53
|
'SELECT * FROM my_table'
|
34
54
|
end
|
@@ -41,14 +61,24 @@ module Cql
|
|
41
61
|
]
|
42
62
|
end
|
43
63
|
|
64
|
+
let :protocol_version do
|
65
|
+
'v2'
|
66
|
+
end
|
67
|
+
|
44
68
|
def handle_request(connection, request, timeout)
|
45
69
|
case request
|
46
70
|
when Protocol::PrepareRequest
|
47
71
|
statement_id = Array.new(16) { [rand(255)].pack('c') }.join('')
|
48
72
|
connection[:last_prepared_statement_id] = statement_id
|
49
|
-
Protocol::PreparedResultResponse.new(statement_id, raw_metadata, nil)
|
73
|
+
Protocol::PreparedResultResponse.new(statement_id, raw_metadata, protocol_version == 'v1' ? nil : raw_result_metadata, nil)
|
50
74
|
when Protocol::ExecuteRequest
|
51
|
-
|
75
|
+
if request.request_metadata
|
76
|
+
Protocol::RowsResultResponse.new(rows, raw_metadata, request.paging_state ? 'page2' : nil, nil)
|
77
|
+
else
|
78
|
+
Protocol::RawRowsResultResponse.new(protocol_version[1].to_i, raw_rows, request.paging_state ? 'page2' : nil, nil)
|
79
|
+
end
|
80
|
+
when Protocol::BatchRequest
|
81
|
+
Protocol::VoidResultResponse.new(nil)
|
52
82
|
else
|
53
83
|
raise %(Unexpected request: #{request})
|
54
84
|
end
|
@@ -101,13 +131,29 @@ module Cql
|
|
101
131
|
end
|
102
132
|
end
|
103
133
|
|
134
|
+
describe '#result_metadata' do
|
135
|
+
let :statement do
|
136
|
+
described_class.prepare(cql, ExecuteOptionsDecoder.new(:all), connection_manager, logger).value
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'returns the interpreted result metadata' do
|
140
|
+
statement.result_metadata.should be_a(ResultMetadata)
|
141
|
+
statement.result_metadata['a_third_column'].should be_a(ColumnMetadata)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'is nil when there is no result metadata' do
|
145
|
+
protocol_version.replace('v1')
|
146
|
+
statement.result_metadata.should be_nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
104
150
|
describe '#execute' do
|
105
151
|
let :statement do
|
106
152
|
described_class.prepare(cql, ExecuteOptionsDecoder.new(:local_quorum), connection_manager, logger).value
|
107
153
|
end
|
108
154
|
|
109
155
|
it 'executes itself on one of the connections' do
|
110
|
-
statement.execute(11, 'hello')
|
156
|
+
statement.execute(11, 'hello').value
|
111
157
|
requests = connections.flat_map(&:requests).select { |r| r.is_a?(Protocol::ExecuteRequest) }
|
112
158
|
requests.should have(1).item
|
113
159
|
requests.first.metadata.should == raw_metadata
|
@@ -115,29 +161,46 @@ module Cql
|
|
115
161
|
end
|
116
162
|
|
117
163
|
it 'uses the right statement ID for the connection' do
|
118
|
-
statement.execute(11, 'hello')
|
164
|
+
statement.execute(11, 'hello').value
|
119
165
|
connection, request = connections.map { |c| [c, c.requests.find { |r| r.is_a?(Protocol::ExecuteRequest) }] }.find { |c, r| r }
|
120
166
|
request.id.should == connection[:last_prepared_statement_id]
|
121
167
|
end
|
122
168
|
|
123
169
|
it 'sends the default consistency level' do
|
124
|
-
statement.execute(11, 'hello')
|
170
|
+
statement.execute(11, 'hello').value
|
125
171
|
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
126
172
|
request.consistency.should == :local_quorum
|
127
173
|
end
|
128
174
|
|
129
175
|
it 'sends the consistency given as last argument' do
|
130
|
-
statement.execute(11, 'hello', :two)
|
176
|
+
statement.execute(11, 'hello', :two).value
|
131
177
|
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
132
178
|
request.consistency.should == :two
|
133
179
|
end
|
134
180
|
|
135
181
|
it 'sends the consistency given as an option' do
|
136
|
-
statement.execute(11, 'hello', consistency: :two)
|
182
|
+
statement.execute(11, 'hello', consistency: :two).value
|
137
183
|
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
138
184
|
request.consistency.should == :two
|
139
185
|
end
|
140
186
|
|
187
|
+
it 'sends the serial consistency given as an option' do
|
188
|
+
statement.execute(11, 'hello', serial_consistency: :local_serial).value
|
189
|
+
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
190
|
+
request.serial_consistency.should == :local_serial
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'asks the server not to send metadata' do
|
194
|
+
statement.execute(11, 'hello', consistency: :two).value
|
195
|
+
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
196
|
+
request.request_metadata.should be_false
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'passes the metadata to the request runner' do
|
200
|
+
response = statement.execute(11, 'hello', consistency: :two).value
|
201
|
+
response.count.should == 3
|
202
|
+
end
|
203
|
+
|
141
204
|
it 'uses the specified timeout' do
|
142
205
|
sent_timeout = nil
|
143
206
|
connections.each do |c|
|
@@ -146,10 +209,38 @@ module Cql
|
|
146
209
|
handle_request(c, r, t)
|
147
210
|
end
|
148
211
|
end
|
149
|
-
statement.execute(11, 'hello', timeout: 3)
|
212
|
+
statement.execute(11, 'hello', timeout: 3).value
|
150
213
|
sent_timeout.should == 3
|
151
214
|
end
|
152
215
|
|
216
|
+
context 'when paging' do
|
217
|
+
it 'sends the page size given as an option' do
|
218
|
+
statement.execute(11, 'hello', page_size: 10).value
|
219
|
+
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
220
|
+
request.page_size.should == 10
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'sends the page size and paging state given as options' do
|
224
|
+
statement.execute(11, 'hello', page_size: 10, paging_state: 'foo').value
|
225
|
+
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
226
|
+
request.page_size.should == 10
|
227
|
+
request.paging_state.should == 'foo'
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'returns a result which can load the next page' do
|
231
|
+
result = statement.execute(11, 'foo', page_size: 2).value
|
232
|
+
result.next_page.value
|
233
|
+
request = connections.flat_map(&:requests).find { |r| r.is_a?(Protocol::ExecuteRequest) }
|
234
|
+
request.paging_state.should == result.paging_state
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'returns a result which knows when there are no more pages' do
|
238
|
+
result = statement.execute(11, 'foo', page_size: 2).value
|
239
|
+
result = result.next_page.value
|
240
|
+
result.should be_last_page
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
153
244
|
context 'when it receives a new connection from the connection manager' do
|
154
245
|
let :new_connection do
|
155
246
|
FakeConnection.new('h3.example.com', 1234, 5)
|
@@ -206,6 +297,97 @@ module Cql
|
|
206
297
|
tracing.should be_true
|
207
298
|
end
|
208
299
|
end
|
300
|
+
|
301
|
+
describe '#batch' do
|
302
|
+
let :statement do
|
303
|
+
described_class.prepare('UPDATE x SET y = ? WHERE z = ?', ExecuteOptionsDecoder.new(:one), connection_manager, logger).value
|
304
|
+
end
|
305
|
+
|
306
|
+
def requests
|
307
|
+
connections.flat_map(&:requests).select { |r| r.is_a?(Protocol::BatchRequest) }
|
308
|
+
end
|
309
|
+
|
310
|
+
context 'when called witout a block' do
|
311
|
+
it 'returns a batch' do
|
312
|
+
batch = statement.batch
|
313
|
+
batch.add(1, 'foo')
|
314
|
+
batch.execute.value
|
315
|
+
requests.first.should be_a(Protocol::BatchRequest)
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'creates a batch of the right type' do
|
319
|
+
batch = statement.batch(:unlogged)
|
320
|
+
batch.add(1, 'foo')
|
321
|
+
batch.execute.value
|
322
|
+
requests.first.type.should == Protocol::BatchRequest::UNLOGGED_TYPE
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'passes the options to the batch' do
|
326
|
+
batch = statement.batch(trace: true)
|
327
|
+
batch.add(1, 'foo')
|
328
|
+
batch.execute.value
|
329
|
+
requests.first.trace.should be_true
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'when called with a block' do
|
334
|
+
it 'yields and executes a batch' do
|
335
|
+
f = statement.batch do |batch|
|
336
|
+
batch.add(5, 'foo')
|
337
|
+
batch.add(6, 'bar')
|
338
|
+
end
|
339
|
+
f.value
|
340
|
+
requests.first.should be_a(Protocol::BatchRequest)
|
341
|
+
end
|
342
|
+
|
343
|
+
it 'passes the options to the batch\'s #execute' do
|
344
|
+
f = statement.batch(:unlogged, trace: true) do |batch|
|
345
|
+
batch.add(4, 'baz')
|
346
|
+
end
|
347
|
+
f.value
|
348
|
+
requests.first.trace.should be_true
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
describe '#add_to_batch' do
|
354
|
+
let :statement do
|
355
|
+
described_class.prepare('UPDATE x SET y = ? WHERE z = ?', ExecuteOptionsDecoder.new(:one), connection_manager, logger).value
|
356
|
+
end
|
357
|
+
|
358
|
+
let :batch do
|
359
|
+
double(:batch)
|
360
|
+
end
|
361
|
+
|
362
|
+
let :additions do
|
363
|
+
[]
|
364
|
+
end
|
365
|
+
|
366
|
+
before do
|
367
|
+
connections.pop(2)
|
368
|
+
batch.stub(:add_prepared) do |*args|
|
369
|
+
additions << args
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
it 'calls #add_prepared with the statement ID, metadata and bound variables' do
|
374
|
+
statement.add_to_batch(batch, connections.first, [11, 'foo'])
|
375
|
+
statement_id, metadata, bound_args = additions.first
|
376
|
+
statement_id.should == connections.first[:last_prepared_statement_id]
|
377
|
+
metadata.should == raw_metadata
|
378
|
+
bound_args.should == [11, 'foo']
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'raises an error when the number of bound arguments is not right' do
|
382
|
+
expect { statement.add_to_batch(batch, connections.first, [11, 'foo', 22]) }.to raise_error(ArgumentError)
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'raises an error when the statement has not been prepared on the specified connection' do
|
386
|
+
connection = double(:connection)
|
387
|
+
connection.stub(:[]).with(statement).and_return(nil)
|
388
|
+
expect { statement.add_to_batch(batch, connection, [11, 'foo']) }.to raise_error(NotPreparedError)
|
389
|
+
end
|
390
|
+
end
|
209
391
|
end
|
210
392
|
end
|
211
393
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Client
|
8
|
+
describe PlainTextAuthProvider do
|
9
|
+
let :auth_provider do
|
10
|
+
described_class.new('foo', 'bar')
|
11
|
+
end
|
12
|
+
|
13
|
+
let :standard_authentication_class do
|
14
|
+
'org.apache.cassandra.auth.PasswordAuthenticator'
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#create_authenticator' do
|
18
|
+
it 'creates a PlainTextAuthenticator' do
|
19
|
+
authenticator = auth_provider.create_authenticator(standard_authentication_class)
|
20
|
+
authenticator.initial_response.should == "\x00foo\x00bar"
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns nil when the authentication class is not o.a.c.a.PasswordAuthenticator' do
|
24
|
+
authenticator = auth_provider.create_authenticator('org.acme.Foo')
|
25
|
+
authenticator.should be_nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe PlainTextAuthenticator do
|
31
|
+
describe '#initial_response' do
|
32
|
+
it 'encodes the username and password' do
|
33
|
+
response = described_class.new('user', 'pass').initial_response
|
34
|
+
response.should == "\x00user\x00pass"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#challenge_response' do
|
39
|
+
it 'returns nil' do
|
40
|
+
authenticator = described_class.new('user', 'pass')
|
41
|
+
authenticator.initial_response
|
42
|
+
authenticator.challenge_response('?').should be_nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#authentication_successful' do
|
47
|
+
it 'does nothing' do
|
48
|
+
authenticator = described_class.new('user', 'pass')
|
49
|
+
authenticator.initial_response
|
50
|
+
authenticator.challenge_response('?')
|
51
|
+
authenticator.authentication_successful('ok')
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,277 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Client
|
8
|
+
describe AsynchronousBatch do
|
9
|
+
let :batch do
|
10
|
+
described_class.new(:unlogged, execute_options_decoder, connection_manager)
|
11
|
+
end
|
12
|
+
|
13
|
+
let :execute_options_decoder do
|
14
|
+
ExecuteOptionsDecoder.new(:two)
|
15
|
+
end
|
16
|
+
|
17
|
+
let :connection_manager do
|
18
|
+
double(:connection_manager)
|
19
|
+
end
|
20
|
+
|
21
|
+
let :connection do
|
22
|
+
double(:connection)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#initialize' do
|
26
|
+
it 'raises an error when the type is not :logged, :unlogged or :counter' do
|
27
|
+
expect { described_class.new(:foobar, execute_options_decoder, connection_manager) }.to raise_error(ArgumentError)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#add' do
|
32
|
+
it 'returns nil' do
|
33
|
+
result = batch.add('UPDATE x SET y = 1 WHER z = 2')
|
34
|
+
result.should be_nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '#execute' do
|
39
|
+
let :requests do
|
40
|
+
[]
|
41
|
+
end
|
42
|
+
|
43
|
+
def last_request
|
44
|
+
requests.last[0]
|
45
|
+
end
|
46
|
+
|
47
|
+
def last_timeout
|
48
|
+
requests.last[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
let :prepared_statement do
|
52
|
+
double(:prepared_statement)
|
53
|
+
end
|
54
|
+
|
55
|
+
let :metadata do
|
56
|
+
[['ks', 'tbl', 'col1', :bigint], ['ks', 'tbl', 'col2', :text]]
|
57
|
+
end
|
58
|
+
|
59
|
+
before do
|
60
|
+
connection_manager.stub(:random_connection).and_return(connection)
|
61
|
+
connection.stub(:send_request) do |request, timeout|
|
62
|
+
requests << [request, timeout]
|
63
|
+
Future.resolved(Protocol::VoidResultResponse.new(nil))
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'creates a BATCH request and executes it on a random connection' do
|
68
|
+
batch.execute.value
|
69
|
+
connection.should have_received(:send_request).with(an_instance_of(Protocol::BatchRequest), nil)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'creates a BATCH request from the added parts' do
|
73
|
+
prepared_statement.stub(:add_to_batch) do |batch_request, connection, bound_args|
|
74
|
+
batch_request.add_prepared('XXXXXXXXXXXXXXXX', metadata, bound_args)
|
75
|
+
end
|
76
|
+
prepared_statement.stub(:metadata).and_return(metadata)
|
77
|
+
batch.add('UPDATE x SET y = 1 WHERE z = 2')
|
78
|
+
batch.add('UPDATE x SET y = 2 WHERE z = ?', 3)
|
79
|
+
batch.add(prepared_statement, 3, 'foo')
|
80
|
+
batch.execute.value
|
81
|
+
encoded_frame = last_request.write(1, '')
|
82
|
+
encoded_frame.should include('UPDATE x SET y = 1 WHERE z = 2')
|
83
|
+
encoded_frame.should include('UPDATE x SET y = 2 WHERE z = ?')
|
84
|
+
encoded_frame.should include(Protocol::QueryRequest.encode_values('', [3], nil))
|
85
|
+
encoded_frame.should include('XXXXXXXXXXXXXXXX')
|
86
|
+
encoded_frame.should include(Protocol::ExecuteRequest.encode_values('', metadata, [3, 'foo']))
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'uses the provided type hints' do
|
90
|
+
batch.add('UPDATE x SET y = 2 WHERE z = ?', 3, type_hints: [:int])
|
91
|
+
batch.execute.value
|
92
|
+
encoded_frame = last_request.write(1, '')
|
93
|
+
encoded_frame.should include(Protocol::QueryRequest.encode_values('', [3], [:int]))
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'tries again when a prepared statement raises NotPreparedError' do
|
97
|
+
connection1 = double(:connection1)
|
98
|
+
connection2 = double(:connection2)
|
99
|
+
connection2.stub(:send_request).and_return(Cql::Future.resolved(Protocol::VoidResultResponse.new(nil)))
|
100
|
+
connection_manager.stub(:random_connection).and_return(connection1, connection2)
|
101
|
+
prepared_statement.stub(:add_to_batch).with(anything, connection1, anything).and_raise(NotPreparedError)
|
102
|
+
prepared_statement.stub(:add_to_batch).with(anything, connection2, anything)
|
103
|
+
batch.add(prepared_statement, 3, 'foo')
|
104
|
+
expect { batch.execute.value }.to_not raise_error
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'gives up when the prepared statement has raised NotPreparedError three times' do
|
108
|
+
prepared_statement.stub(:add_to_batch).with(anything, connection, anything).and_raise(NotPreparedError)
|
109
|
+
batch.add(prepared_statement, 3, 'foo')
|
110
|
+
expect { batch.execute.value }.to raise_error(NotPreparedError)
|
111
|
+
prepared_statement.should have_received(:add_to_batch).exactly(3).times
|
112
|
+
end
|
113
|
+
|
114
|
+
it 'returns a future that resolves to the response' do
|
115
|
+
f = batch.execute
|
116
|
+
f.value.should equal(VoidResult::INSTANCE)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'accepts a timeout' do
|
120
|
+
batch.execute(timeout: 10).value
|
121
|
+
last_timeout.should == 10
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'accepts a consistency' do
|
125
|
+
batch.execute(consistency: :three).value
|
126
|
+
last_request.consistency.should == :three
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'accepts consistency as a symbol' do
|
130
|
+
batch.execute(:three).value
|
131
|
+
last_request.consistency.should == :three
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'uses the default consistency' do
|
135
|
+
batch.execute.value
|
136
|
+
last_request.consistency.should == :two
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'enables tracing' do
|
140
|
+
batch.execute(trace: true).value
|
141
|
+
last_request.trace.should be_true
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'creates a batch of the right type' do
|
145
|
+
b1 = described_class.new(:logged, execute_options_decoder, connection_manager)
|
146
|
+
b2 = described_class.new(:unlogged, execute_options_decoder, connection_manager)
|
147
|
+
b3 = described_class.new(:counter, execute_options_decoder, connection_manager)
|
148
|
+
b1.execute.value
|
149
|
+
last_request.type.should == Protocol::BatchRequest::LOGGED_TYPE
|
150
|
+
b2.execute.value
|
151
|
+
last_request.type.should == Protocol::BatchRequest::UNLOGGED_TYPE
|
152
|
+
b3.execute.value
|
153
|
+
last_request.type.should == Protocol::BatchRequest::COUNTER_TYPE
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'uses the options given in the constructor' do
|
157
|
+
b = described_class.new(:unlogged, execute_options_decoder, connection_manager, timeout: 4)
|
158
|
+
b.execute.value
|
159
|
+
last_timeout.should == 4
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'merges the options with the options given in the constructor' do
|
163
|
+
b = described_class.new(:unlogged, execute_options_decoder, connection_manager, timeout: 4, trace: true)
|
164
|
+
b.execute(trace: false).value
|
165
|
+
last_timeout.should == 4
|
166
|
+
last_request.trace.should be_false
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe SynchronousBatch do
|
172
|
+
let :batch do
|
173
|
+
described_class.new(asynchronous_batch)
|
174
|
+
end
|
175
|
+
|
176
|
+
let :asynchronous_batch do
|
177
|
+
double(:asynchronous_batch)
|
178
|
+
end
|
179
|
+
|
180
|
+
before do
|
181
|
+
asynchronous_batch.stub(:add)
|
182
|
+
asynchronous_batch.stub(:execute).and_return(Cql::Future.resolved(VoidResult::INSTANCE))
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#async' do
|
186
|
+
it 'returns the asynchronous batch' do
|
187
|
+
batch.async.should equal(asynchronous_batch)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe '#add' do
|
192
|
+
it 'delegates to the asynchronous batch' do
|
193
|
+
batch.add('UPDATE x SET y = ? WHERE z = ?', 3, 4)
|
194
|
+
asynchronous_batch.should have_received(:add).with('UPDATE x SET y = ? WHERE z = ?', 3, 4)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
describe '#execute' do
|
199
|
+
it 'delegates to the asynchronous batch' do
|
200
|
+
batch.execute(trace: true)
|
201
|
+
asynchronous_batch.should have_received(:execute).with(trace: true)
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'waits for the response' do
|
205
|
+
batch.execute.should == VoidResult::INSTANCE
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe AsynchronousPreparedStatementBatch do
|
211
|
+
let :prepared_statement_batch do
|
212
|
+
described_class.new(prepared_statement, batch)
|
213
|
+
end
|
214
|
+
|
215
|
+
let :prepared_statement do
|
216
|
+
double(:prepared_statement)
|
217
|
+
end
|
218
|
+
|
219
|
+
let :batch do
|
220
|
+
double(:batch)
|
221
|
+
end
|
222
|
+
|
223
|
+
before do
|
224
|
+
batch.stub(:add)
|
225
|
+
batch.stub(:execute).and_return(Cql::Future.resolved(VoidResult::INSTANCE))
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#add' do
|
229
|
+
it 'passes the statement and the given arguments to the batch' do
|
230
|
+
prepared_statement_batch.add('foo', 3)
|
231
|
+
batch.should have_received(:add).with(prepared_statement, 'foo', 3)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe '#execute' do
|
236
|
+
it 'delegates to the batch' do
|
237
|
+
result = prepared_statement_batch.execute(trace: true)
|
238
|
+
batch.should have_received(:execute).with(trace: true)
|
239
|
+
result.value.should == VoidResult::INSTANCE
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe SynchronousPreparedStatementBatch do
|
245
|
+
let :batch do
|
246
|
+
described_class.new(asynchronous_batch)
|
247
|
+
end
|
248
|
+
|
249
|
+
let :asynchronous_batch do
|
250
|
+
double(:asynchronous_batch)
|
251
|
+
end
|
252
|
+
|
253
|
+
before do
|
254
|
+
asynchronous_batch.stub(:add)
|
255
|
+
asynchronous_batch.stub(:execute).and_return(Cql::Future.resolved(VoidResult::INSTANCE))
|
256
|
+
end
|
257
|
+
|
258
|
+
describe '#add' do
|
259
|
+
it 'delegates to the async batch' do
|
260
|
+
batch.add(3, 'foo', 9)
|
261
|
+
asynchronous_batch.should have_received(:add).with(3, 'foo', 9)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#execute' do
|
266
|
+
it 'delegates to the async batch' do
|
267
|
+
batch.execute(trace: true)
|
268
|
+
asynchronous_batch.should have_received(:execute).with(trace: true)
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'waits for the response' do
|
272
|
+
batch.execute.should == VoidResult::INSTANCE
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|