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.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +4 -0
  3. data/README.md +139 -17
  4. data/lib/cql/client.rb +237 -8
  5. data/lib/cql/client/asynchronous_client.rb +138 -54
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +41 -6
  7. data/lib/cql/client/authenticators.rb +46 -0
  8. data/lib/cql/client/batch.rb +115 -0
  9. data/lib/cql/client/connector.rb +255 -0
  10. data/lib/cql/client/execute_options_decoder.rb +25 -9
  11. data/lib/cql/client/keyspace_changer.rb +5 -5
  12. data/lib/cql/client/peer_discovery.rb +33 -0
  13. data/lib/cql/client/query_result.rb +124 -1
  14. data/lib/cql/client/request_runner.rb +4 -2
  15. data/lib/cql/client/synchronous_client.rb +14 -2
  16. data/lib/cql/client/synchronous_prepared_statement.rb +19 -1
  17. data/lib/cql/future.rb +97 -50
  18. data/lib/cql/io/connection.rb +0 -1
  19. data/lib/cql/io/io_reactor.rb +1 -1
  20. data/lib/cql/protocol.rb +8 -1
  21. data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
  22. data/lib/cql/protocol/decoding.rb +10 -15
  23. data/lib/cql/protocol/frame_decoder.rb +2 -1
  24. data/lib/cql/protocol/frame_encoder.rb +5 -4
  25. data/lib/cql/protocol/requests/auth_response_request.rb +31 -0
  26. data/lib/cql/protocol/requests/batch_request.rb +59 -0
  27. data/lib/cql/protocol/requests/credentials_request.rb +1 -1
  28. data/lib/cql/protocol/requests/execute_request.rb +45 -17
  29. data/lib/cql/protocol/requests/options_request.rb +1 -1
  30. data/lib/cql/protocol/requests/prepare_request.rb +1 -1
  31. data/lib/cql/protocol/requests/query_request.rb +97 -5
  32. data/lib/cql/protocol/requests/register_request.rb +1 -1
  33. data/lib/cql/protocol/requests/startup_request.rb +4 -4
  34. data/lib/cql/protocol/response.rb +2 -2
  35. data/lib/cql/protocol/responses/auth_challenge_response.rb +25 -0
  36. data/lib/cql/protocol/responses/auth_success_response.rb +25 -0
  37. data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
  38. data/lib/cql/protocol/responses/detailed_error_response.rb +1 -1
  39. data/lib/cql/protocol/responses/error_response.rb +3 -2
  40. data/lib/cql/protocol/responses/event_response.rb +3 -2
  41. data/lib/cql/protocol/responses/prepared_result_response.rb +10 -6
  42. data/lib/cql/protocol/responses/raw_rows_result_response.rb +27 -0
  43. data/lib/cql/protocol/responses/ready_response.rb +1 -1
  44. data/lib/cql/protocol/responses/result_response.rb +2 -2
  45. data/lib/cql/protocol/responses/rows_result_response.rb +43 -23
  46. data/lib/cql/protocol/responses/schema_change_event_response.rb +1 -1
  47. data/lib/cql/protocol/responses/schema_change_result_response.rb +1 -1
  48. data/lib/cql/protocol/responses/set_keyspace_result_response.rb +1 -1
  49. data/lib/cql/protocol/responses/status_change_event_response.rb +1 -1
  50. data/lib/cql/protocol/responses/supported_response.rb +1 -1
  51. data/lib/cql/protocol/responses/void_result_response.rb +1 -1
  52. data/lib/cql/protocol/type_converter.rb +2 -2
  53. data/lib/cql/uuid.rb +2 -2
  54. data/lib/cql/version.rb +1 -1
  55. data/spec/cql/client/asynchronous_client_spec.rb +493 -50
  56. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +193 -11
  57. data/spec/cql/client/authenticators_spec.rb +56 -0
  58. data/spec/cql/client/batch_spec.rb +277 -0
  59. data/spec/cql/client/connector_spec.rb +606 -0
  60. data/spec/cql/client/execute_options_decoder_spec.rb +95 -0
  61. data/spec/cql/client/keyspace_changer_spec.rb +8 -8
  62. data/spec/cql/client/peer_discovery_spec.rb +92 -0
  63. data/spec/cql/client/query_result_spec.rb +352 -0
  64. data/spec/cql/client/request_runner_spec.rb +31 -5
  65. data/spec/cql/client/synchronous_client_spec.rb +44 -1
  66. data/spec/cql/client/synchronous_prepared_statement_spec.rb +63 -1
  67. data/spec/cql/future_spec.rb +50 -2
  68. data/spec/cql/protocol/cql_protocol_handler_spec.rb +16 -5
  69. data/spec/cql/protocol/decoding_spec.rb +16 -6
  70. data/spec/cql/protocol/encoding_spec.rb +3 -1
  71. data/spec/cql/protocol/frame_encoder_spec.rb +99 -50
  72. data/spec/cql/protocol/requests/auth_response_request_spec.rb +62 -0
  73. data/spec/cql/protocol/requests/batch_request_spec.rb +155 -0
  74. data/spec/cql/protocol/requests/credentials_request_spec.rb +1 -1
  75. data/spec/cql/protocol/requests/execute_request_spec.rb +184 -71
  76. data/spec/cql/protocol/requests/options_request_spec.rb +1 -1
  77. data/spec/cql/protocol/requests/prepare_request_spec.rb +1 -1
  78. data/spec/cql/protocol/requests/query_request_spec.rb +255 -32
  79. data/spec/cql/protocol/requests/register_request_spec.rb +1 -1
  80. data/spec/cql/protocol/requests/startup_request_spec.rb +12 -6
  81. data/spec/cql/protocol/responses/auth_challenge_response_spec.rb +31 -0
  82. data/spec/cql/protocol/responses/auth_success_response_spec.rb +31 -0
  83. data/spec/cql/protocol/responses/authenticate_response_spec.rb +2 -1
  84. data/spec/cql/protocol/responses/detailed_error_response_spec.rb +14 -7
  85. data/spec/cql/protocol/responses/error_response_spec.rb +4 -2
  86. data/spec/cql/protocol/responses/event_response_spec.rb +7 -4
  87. data/spec/cql/protocol/responses/prepared_result_response_spec.rb +89 -34
  88. data/spec/cql/protocol/responses/raw_rows_result_response_spec.rb +66 -0
  89. data/spec/cql/protocol/responses/ready_response_spec.rb +1 -1
  90. data/spec/cql/protocol/responses/result_response_spec.rb +19 -7
  91. data/spec/cql/protocol/responses/rows_result_response_spec.rb +56 -11
  92. data/spec/cql/protocol/responses/schema_change_event_response_spec.rb +2 -1
  93. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +2 -1
  94. data/spec/cql/protocol/responses/set_keyspace_result_response_spec.rb +1 -1
  95. data/spec/cql/protocol/responses/status_change_event_response_spec.rb +2 -1
  96. data/spec/cql/protocol/responses/supported_response_spec.rb +2 -1
  97. data/spec/cql/protocol/responses/topology_change_event_response_spec.rb +2 -1
  98. data/spec/cql/protocol/responses/void_result_response_spec.rb +1 -1
  99. data/spec/cql/protocol/type_converter_spec.rb +21 -4
  100. data/spec/cql/uuid_spec.rb +10 -3
  101. data/spec/integration/client_spec.rb +251 -28
  102. data/spec/integration/protocol_spec.rb +213 -62
  103. data/spec/integration/regression_spec.rb +4 -1
  104. data/spec/integration/uuid_spec.rb +4 -1
  105. data/spec/support/fake_io_reactor.rb +5 -5
  106. metadata +36 -7
  107. data/lib/cql/client/connection_helper.rb +0 -181
  108. 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
- Protocol::RowsResultResponse.new(rows, raw_metadata, nil)
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