cql-rb 1.0.6 → 1.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/README.md +4 -9
  2. data/lib/cql.rb +1 -0
  3. data/lib/cql/byte_buffer.rb +23 -7
  4. data/lib/cql/client.rb +11 -6
  5. data/lib/cql/client/asynchronous_client.rb +37 -83
  6. data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
  7. data/lib/cql/client/column_metadata.rb +16 -0
  8. data/lib/cql/client/request_runner.rb +46 -0
  9. data/lib/cql/future.rb +4 -5
  10. data/lib/cql/io.rb +2 -5
  11. data/lib/cql/io/connection.rb +220 -0
  12. data/lib/cql/io/io_reactor.rb +213 -185
  13. data/lib/cql/protocol.rb +1 -0
  14. data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
  15. data/lib/cql/protocol/decoding.rb +6 -31
  16. data/lib/cql/protocol/encoding.rb +1 -5
  17. data/lib/cql/protocol/request.rb +4 -0
  18. data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
  19. data/lib/cql/protocol/type_converter.rb +56 -76
  20. data/lib/cql/time_uuid.rb +104 -0
  21. data/lib/cql/uuid.rb +4 -2
  22. data/lib/cql/version.rb +1 -1
  23. data/spec/cql/client/asynchronous_client_spec.rb +47 -71
  24. data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
  25. data/spec/cql/client/client_shared.rb +3 -3
  26. data/spec/cql/client/column_metadata_spec.rb +80 -0
  27. data/spec/cql/client/request_runner_spec.rb +120 -0
  28. data/spec/cql/future_spec.rb +26 -11
  29. data/spec/cql/io/connection_spec.rb +460 -0
  30. data/spec/cql/io/io_reactor_spec.rb +212 -265
  31. data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
  32. data/spec/cql/protocol/decoding_spec.rb +9 -28
  33. data/spec/cql/protocol/encoding_spec.rb +0 -5
  34. data/spec/cql/protocol/request_spec.rb +16 -0
  35. data/spec/cql/protocol/response_frame_spec.rb +2 -2
  36. data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
  37. data/spec/cql/time_uuid_spec.rb +136 -0
  38. data/spec/cql/uuid_spec.rb +1 -5
  39. data/spec/integration/client_spec.rb +34 -38
  40. data/spec/integration/io_spec.rb +283 -0
  41. data/spec/integration/protocol_spec.rb +53 -113
  42. data/spec/integration/regression_spec.rb +124 -0
  43. data/spec/integration/uuid_spec.rb +76 -0
  44. data/spec/spec_helper.rb +12 -9
  45. data/spec/support/fake_io_reactor.rb +52 -21
  46. data/spec/support/fake_server.rb +2 -2
  47. metadata +33 -10
  48. checksums.yaml +0 -15
  49. data/lib/cql/io/node_connection.rb +0 -209
  50. data/spec/cql/protocol/type_converter_spec.rb +0 -52
@@ -0,0 +1,104 @@
1
+ # encoding: utf-8
2
+
3
+ module Cql
4
+ # A variant of UUID which can extract its time component.
5
+ #
6
+ class TimeUuid < Uuid
7
+ # Returns the time component from this UUID as a Time.
8
+ #
9
+ # @return [Time]
10
+ #
11
+ def to_time
12
+ n = (value >> 64)
13
+ t = 0
14
+ t |= (n & 0x0000000000000fff) << 48
15
+ t |= (n & 0x00000000ffff0000) << 16
16
+ t |= (n & 0xffffffff00000000) >> 32
17
+ t -= GREGORIAN_OFFSET
18
+ seconds = t/10_000_000
19
+ microseconds = (t - seconds * 10_000_000)/10.0
20
+ Time.at(seconds, microseconds).utc
21
+ end
22
+
23
+ # A UUID version 1, variant 1 generator. It can generate a sequence of UUIDs
24
+ # with reasonable uniqueness guarantees:
25
+ #
26
+ # * The clock ID and node ID components are set to random numbers when the
27
+ # generator is created.
28
+ # * If two calls to {#next} happen within the time afforded by the system
29
+ # clock resolution a counter is incremented and added to the time
30
+ # component.
31
+ # * If the clock moves backwards the clock ID is reset to a new random
32
+ # number.
33
+ #
34
+ # Instances of this class are absolutely not threadsafe. You should
35
+ # never share instances between threads.
36
+ #
37
+ class Generator
38
+ # Create a new UUID generator.
39
+ #
40
+ # @param [Integer] node_id an alternate node ID (defaults to a random number)
41
+ # @param [Integer] clock_id an alternate clock ID (defaults to a random number)
42
+ #
43
+ def initialize(node_id=nil, clock_id=nil, clock=Time)
44
+ @node_id = node_id || (rand(2**47) | 0x010000000000)
45
+ @clock_id = clock_id || rand(2**16)
46
+ @clock = clock
47
+ end
48
+
49
+ # Returns a new UUID with a time component that is the current time.
50
+ #
51
+ # @return [Cql::TimeUuid] a new UUID
52
+ #
53
+ def next
54
+ now = @clock.now
55
+ usecs = now.to_i * 1_000_000 + now.usec
56
+ if @last_usecs && @last_usecs - @sequence <= usecs && usecs <= @last_usecs
57
+ @sequence += 1
58
+ elsif @last_usecs && @last_usecs > usecs
59
+ @sequence = 0
60
+ @clock_id = rand(2**16)
61
+ else
62
+ @sequence = 0
63
+ end
64
+ @last_usecs = usecs + @sequence
65
+ from_usecs(@last_usecs)
66
+ end
67
+
68
+ # Returns a new UUID with a time component based on the specified Time.
69
+ # A piece of jitter is added to ensure that multiple calls with the same
70
+ # time do not generate the same UUID (if you want determinism you can set
71
+ # the second parameter to zero).
72
+ #
73
+ # @param [Time] time the time to encode into the UUID
74
+ # @param [Integer] jitter a number of microseconds to add to the time to make it unique
75
+ # @return [Cql::TimeUuid] a new UUID
76
+ #
77
+ def from_time(time, jitter=rand(2**16))
78
+ usecs = time.to_i * 1_000_000 + time.usec + jitter
79
+ from_usecs(usecs)
80
+ end
81
+
82
+ # @private
83
+ def from_usecs(usecs)
84
+ t = GREGORIAN_OFFSET + usecs * 10
85
+ time_hi = t & 0x0fff000000000000
86
+ time_mid = t & 0x0000ffff00000000
87
+ time_low = t & 0x00000000ffffffff
88
+ version = 1
89
+ clock_id = @clock_id & 0x3fff
90
+ node_id = @node_id & 0xffffffffffff
91
+ variant = 0x8000
92
+ n = (time_low << 96) | (time_mid << 48) | (time_hi << 16)
93
+ n |= version << 76
94
+ n |= (clock_id | variant) << 48
95
+ n |= node_id
96
+ TimeUuid.new(n)
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ GREGORIAN_OFFSET = 122192928000000000
103
+ end
104
+ end
data/lib/cql/uuid.rb CHANGED
@@ -6,6 +6,8 @@ module Cql
6
6
  # This is a very basic implementation of UUIDs and exists more or less just
7
7
  # to encode and decode UUIDs from and to Cassandra.
8
8
  #
9
+ # If you want to generate UUIDs see {Cql::TimeUuid::Generator}.
10
+ #
9
11
  class Uuid
10
12
  # Creates a new UUID either from a string (expected to be on the standard
11
13
  # 8-4-4-4-12 form, or just 32 characters without hyphens), or from a
@@ -50,7 +52,7 @@ module Cql
50
52
 
51
53
  # @private
52
54
  def eql?(other)
53
- other.kind_of?(Uuid) && self.value == other.value
55
+ self.value == other.value
54
56
  end
55
57
  alias_method :==, :eql?
56
58
 
@@ -69,4 +71,4 @@ module Cql
69
71
  n
70
72
  end
71
73
  end
72
- end
74
+ end
data/lib/cql/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.0.6'.freeze
4
+ VERSION = '1.1.0.pre0'.freeze
5
5
  end
@@ -39,10 +39,15 @@ module Cql
39
39
  client.connect.get.should equal(client)
40
40
  end
41
41
 
42
- it 'forwards the host and port' do
42
+ it 'connects to the right host and port' do
43
43
  client.connect.get
44
- connection[:host].should == 'example.com'
45
- connection[:port].should == 12321
44
+ last_connection.host.should == 'example.com'
45
+ last_connection.port.should == 12321
46
+ end
47
+
48
+ it 'connects with the default connection timeout' do
49
+ client.connect.get
50
+ last_connection.timeout.should == 10
46
51
  end
47
52
 
48
53
  it 'sends a startup request' do
@@ -58,7 +63,7 @@ module Cql
58
63
  c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
59
64
  c.connect.get
60
65
  connections.each do |cc|
61
- cc[:requests].last.should be_a(Protocol::StartupRequest)
66
+ cc.requests.last.should be_a(Protocol::StartupRequest)
62
67
  end
63
68
  end
64
69
 
@@ -80,12 +85,12 @@ module Cql
80
85
  end
81
86
 
82
87
  it 're-raises any errors raised' do
83
- io_reactor.stub(:add_connection).and_raise(ArgumentError)
88
+ io_reactor.stub(:connect).and_raise(ArgumentError)
84
89
  expect { client.connect.get }.to raise_error(ArgumentError)
85
90
  end
86
91
 
87
92
  it 'is not connected if an error is raised' do
88
- io_reactor.stub(:add_connection).and_raise(ArgumentError)
93
+ io_reactor.stub(:connect).and_raise(ArgumentError)
89
94
  client.connect.get rescue nil
90
95
  client.should_not be_connected
91
96
  io_reactor.should_not be_running
@@ -97,18 +102,18 @@ module Cql
97
102
  end
98
103
 
99
104
  it 'is not connected while connecting' do
100
- pending 'the fake reactor needs to be made asynchronous' do
101
- io_reactor.stop.get
102
- f = client.connect
103
- client.should_not be_connected
104
- io_reactor.start.get
105
- f.get
106
- end
105
+ io_reactor.stop.get
106
+ f = client.connect
107
+ client.should_not be_connected
108
+ io_reactor.start.get
109
+ f.get
107
110
  end
108
111
 
109
112
  context 'when the server requests authentication' do
110
113
  before do
111
- io_reactor.queue_response(Protocol::AuthenticateResponse.new('com.example.Auth'))
114
+ io_reactor.on_connection do |connection|
115
+ connection.queue_response(Protocol::AuthenticateResponse.new('com.example.Auth'))
116
+ end
112
117
  end
113
118
 
114
119
  it 'sends credentials' do
@@ -123,13 +128,17 @@ module Cql
123
128
  end
124
129
 
125
130
  it 'raises an error when the server responds with an error to the credentials request' do
126
- io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
131
+ io_reactor.on_connection do |connection|
132
+ connection.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
133
+ end
127
134
  client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
128
135
  expect { client.connect.get }.to raise_error(AuthenticationError)
129
136
  end
130
137
 
131
138
  it 'shuts down the client when there is an authentication error' do
132
- io_reactor.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
139
+ io_reactor.on_connection do |connection|
140
+ connection.queue_response(Protocol::ErrorResponse.new(256, 'No way, José'))
141
+ end
133
142
  client = described_class.new(connection_options.merge(credentials: {'username' => 'foo', 'password' => 'bar'}))
134
143
  client.connect.get rescue nil
135
144
  client.should_not be_connected
@@ -171,7 +180,9 @@ module Cql
171
180
  end
172
181
 
173
182
  it 'executes a USE query' do
174
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
183
+ io_reactor.on_connection do |connection|
184
+ connection.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
185
+ end
175
186
  client.use('system').get
176
187
  last_request.should == Protocol::QueryRequest.new('USE system', :one)
177
188
  end
@@ -185,7 +196,7 @@ module Cql
185
196
  c.connect.get
186
197
 
187
198
  c.use('system').get
188
- last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
199
+ last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
189
200
  last_requests.should == [
190
201
  Protocol::QueryRequest.new('USE system', :one),
191
202
  Protocol::QueryRequest.new('USE system', :one),
@@ -194,7 +205,7 @@ module Cql
194
205
  end
195
206
 
196
207
  it 'knows which keyspace it changed to' do
197
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
208
+ last_connection.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
198
209
  client.use('system').get
199
210
  client.keyspace.should == 'system'
200
211
  end
@@ -202,13 +213,6 @@ module Cql
202
213
  it 'raises an error if the keyspace name is not valid' do
203
214
  expect { client.use('system; DROP KEYSPACE system').get }.to raise_error(InvalidKeyspaceNameError)
204
215
  end
205
-
206
- it 'allows the keyspace name to be quoted' do
207
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
208
- client.connect.get
209
- client.use('"system"').get
210
- client.keyspace.should == "system"
211
- end
212
216
  end
213
217
 
214
218
  describe '#execute' do
@@ -228,7 +232,7 @@ module Cql
228
232
 
229
233
  context 'with a void CQL query' do
230
234
  it 'returns nil' do
231
- io_reactor.queue_response(Protocol::VoidResultResponse.new)
235
+ last_connection.queue_response(Protocol::VoidResultResponse.new)
232
236
  result = client.execute('UPDATE stuff SET thing = 1 WHERE id = 3').get
233
237
  result.should be_nil
234
238
  end
@@ -236,13 +240,13 @@ module Cql
236
240
 
237
241
  context 'with a USE query' do
238
242
  it 'returns nil' do
239
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
243
+ last_connection.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
240
244
  result = client.execute('USE system').get
241
245
  result.should be_nil
242
246
  end
243
247
 
244
248
  it 'knows which keyspace it changed to' do
245
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
249
+ last_connection.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
246
250
  client.execute('USE system').get
247
251
  client.keyspace.should == 'system'
248
252
  end
@@ -255,14 +259,14 @@ module Cql
255
259
  c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
256
260
  c.connect.get
257
261
 
258
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h1.example.com' }[:host])
259
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h2.example.com' }[:host])
260
- io_reactor.queue_response(Protocol::SetKeyspaceResultResponse.new('system'), connections.find { |c| c[:host] == 'h3.example.com' }[:host])
262
+ connections.find { |c| c.host == 'h1.example.com' }.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
263
+ connections.find { |c| c.host == 'h2.example.com' }.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
264
+ connections.find { |c| c.host == 'h3.example.com' }.queue_response(Protocol::SetKeyspaceResultResponse.new('system'))
261
265
 
262
266
  c.execute('USE system', :one).get
263
267
  c.keyspace.should == 'system'
264
268
 
265
- last_requests = connections.select { |c| c[:host] =~ /^h\d\.example\.com$/ }.sort_by { |c| c[:host] }.map { |c| c[:requests].last }
269
+ last_requests = connections.select { |c| c.host =~ /^h\d\.example\.com$/ }.sort_by(&:host).map { |c| c.requests.last }
266
270
  last_requests.should == [
267
271
  Protocol::QueryRequest.new('USE system', :one),
268
272
  Protocol::QueryRequest.new('USE system', :one),
@@ -281,7 +285,7 @@ module Cql
281
285
  end
282
286
 
283
287
  let :result do
284
- io_reactor.queue_response(Protocol::RowsResultResponse.new(rows, metadata))
288
+ last_connection.queue_response(Protocol::RowsResultResponse.new(rows, metadata))
285
289
  client.execute('SELECT * FROM things').get
286
290
  end
287
291
 
@@ -324,12 +328,12 @@ module Cql
324
328
 
325
329
  context 'when the response is an error' do
326
330
  it 'raises an error' do
327
- io_reactor.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
331
+ last_connection.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
328
332
  expect { client.execute('SELECT * FROM things').get }.to raise_error(QueryError, 'Blurgh')
329
333
  end
330
334
 
331
335
  it 'decorates the error with the CQL that caused it' do
332
- io_reactor.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
336
+ last_connection.queue_response(Protocol::ErrorResponse.new(0xabcd, 'Blurgh'))
333
337
  begin
334
338
  client.execute('SELECT * FROM things').get
335
339
  rescue QueryError => e
@@ -360,59 +364,31 @@ module Cql
360
364
  end
361
365
 
362
366
  it 'returns a prepared statement' do
363
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, [['stuff', 'things', 'item', :varchar]]))
367
+ last_connection.queue_response(Protocol::PreparedResultResponse.new('A' * 32, [['stuff', 'things', 'item', :varchar]]))
364
368
  statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?').get
365
369
  statement.should_not be_nil
366
370
  end
367
371
 
368
372
  it 'executes a prepared statement' do
369
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
373
+ last_connection.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
370
374
  statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?').get
371
375
  statement.execute('foo').get
372
376
  last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['foo'], :quorum)
373
377
  end
374
378
 
375
379
  it 'returns a prepared statement that knows the metadata' do
376
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
380
+ last_connection.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
377
381
  statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?').get
378
382
  statement.metadata['item'].type == :varchar
379
383
  end
380
384
 
381
385
  it 'executes a prepared statement with a specific consistency level' do
382
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
386
+ last_connection.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
383
387
  statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?').get
384
388
  statement.execute('thing', :local_quorum).get
385
389
  last_request.should == Protocol::ExecuteRequest.new(id, metadata, ['thing'], :local_quorum)
386
390
  end
387
391
 
388
- it 'executes a prepared statement using the right connection' do
389
- client.close.get
390
- io_reactor.stop.get
391
- io_reactor.start.get
392
-
393
- c = described_class.new(connection_options.merge(host: 'h1.example.com,h2.example.com,h3.example.com'))
394
- c.connect.get
395
-
396
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, metadata))
397
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('B' * 32, metadata))
398
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('C' * 32, metadata))
399
-
400
- statement1 = c.prepare('SELECT * FROM stuff.things WHERE item = ?').get
401
- statement1_connection = io_reactor.last_used_connection
402
- statement2 = c.prepare('SELECT * FROM stuff.things WHERE item = ?').get
403
- statement2_connection = io_reactor.last_used_connection
404
- statement3 = c.prepare('SELECT * FROM stuff.things WHERE item = ?').get
405
- statement3_connection = io_reactor.last_used_connection
406
-
407
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo1'}], metadata), statement1_connection[:host])
408
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo2'}], metadata), statement2_connection[:host])
409
- io_reactor.queue_response(Protocol::RowsResultResponse.new([{'thing' => 'foo3'}], metadata), statement3_connection[:host])
410
-
411
- statement1.execute('foo').get.first.should == {'thing' => 'foo1'}
412
- statement2.execute('foo').get.first.should == {'thing' => 'foo2'}
413
- statement3.execute('foo').get.first.should == {'thing' => 'foo3'}
414
- end
415
-
416
392
  context 'when there is an error creating the request' do
417
393
  it 'returns a failed future' do
418
394
  f = client.prepare(nil)
@@ -422,7 +398,7 @@ module Cql
422
398
 
423
399
  context 'when there is an error preparing the request' do
424
400
  it 'returns a failed future' do
425
- io_reactor.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
401
+ last_connection.queue_response(Protocol::PreparedResultResponse.new(id, metadata))
426
402
  statement = client.prepare('SELECT * FROM stuff.things WHERE item = ?').get
427
403
  f = statement.execute
428
404
  expect { f.get }.to raise_error(ArgumentError)
@@ -473,7 +449,7 @@ module Cql
473
449
 
474
450
  it 'complains when #execute of a prepared statement is called after #close' do
475
451
  client.connect.get
476
- io_reactor.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
452
+ last_connection.queue_response(Protocol::PreparedResultResponse.new('A' * 32, []))
477
453
  statement = client.prepare('DELETE FROM stuff WHERE id = 3').get
478
454
  client.close.get
479
455
  expect { statement.execute.get }.to raise_error(NotConnectedError)
@@ -481,4 +457,4 @@ module Cql
481
457
  end
482
458
  end
483
459
  end
484
- end
460
+ end
@@ -0,0 +1,68 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ module Client
8
+ describe AsynchronousPreparedStatement do
9
+ let :statement do
10
+ described_class.new(connection, statement_id, raw_metadata)
11
+ end
12
+
13
+ let :connection do
14
+ stub(:connection)
15
+ end
16
+
17
+ let :statement_id do
18
+ "\x2a"
19
+ end
20
+
21
+ let :raw_metadata do
22
+ [
23
+ ['my_keyspace', 'my_table', 'my_column', :int],
24
+ ['my_keyspace', 'my_table', 'my_other_column', :text],
25
+ ]
26
+ end
27
+
28
+ let :rows do
29
+ [
30
+ {'my_column' => 11, 'my_other_column' => 'hello'},
31
+ {'my_column' => 22, 'my_other_column' => 'foo'},
32
+ {'my_column' => 33, 'my_other_column' => 'bar'},
33
+ ]
34
+ end
35
+
36
+ describe '#metadata' do
37
+ it 'returns the interpreted metadata' do
38
+ statement.metadata.should be_a(ResultMetadata)
39
+ statement.metadata['my_column'].should be_a(ColumnMetadata)
40
+ end
41
+ end
42
+
43
+ describe '#execute' do
44
+ it 'creates and sends an EXECUTE request' do
45
+ expected_request = Cql::Protocol::ExecuteRequest.new(statement_id, raw_metadata, [11, 'hello'], :one)
46
+ connection.should_receive(:send_request).with(expected_request)
47
+ statement.execute(11, 'hello', :one)
48
+ end
49
+
50
+ it 'returns a future that resolves to a QueryResult' do
51
+ request = Cql::Protocol::ExecuteRequest.new(statement_id, raw_metadata, [11, 'hello'], :two)
52
+ response = Cql::Protocol::RowsResultResponse.new(rows, raw_metadata)
53
+ connection.stub(:send_request).with(request).and_return(Future.completed(response))
54
+ result = statement.execute(11, 'hello', :two).get
55
+ result.metadata['my_other_column'].should == ColumnMetadata.new('my_keyspace', 'my_table', 'my_other_column', :text)
56
+ result.first.should == {'my_column' => 11, 'my_other_column' => 'hello'}
57
+ end
58
+
59
+ it 'returns a failed future when the number of arguments is wrong' do
60
+ f1 = statement.execute(11, :one)
61
+ f2 = statement.execute(11, 'foo', 22, :one)
62
+ expect { f1.get }.to raise_error
63
+ expect { f2.get }.to raise_error
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end