cql-rb 1.0.6 → 1.1.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 (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