cql-rb 1.2.2 → 2.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
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