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.
- data/README.md +4 -9
- data/lib/cql.rb +1 -0
- data/lib/cql/byte_buffer.rb +23 -7
- data/lib/cql/client.rb +11 -6
- data/lib/cql/client/asynchronous_client.rb +37 -83
- data/lib/cql/client/asynchronous_prepared_statement.rb +10 -4
- data/lib/cql/client/column_metadata.rb +16 -0
- data/lib/cql/client/request_runner.rb +46 -0
- data/lib/cql/future.rb +4 -5
- data/lib/cql/io.rb +2 -5
- data/lib/cql/io/connection.rb +220 -0
- data/lib/cql/io/io_reactor.rb +213 -185
- data/lib/cql/protocol.rb +1 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +201 -0
- data/lib/cql/protocol/decoding.rb +6 -31
- data/lib/cql/protocol/encoding.rb +1 -5
- data/lib/cql/protocol/request.rb +4 -0
- data/lib/cql/protocol/responses/schema_change_result_response.rb +15 -0
- data/lib/cql/protocol/type_converter.rb +56 -76
- data/lib/cql/time_uuid.rb +104 -0
- data/lib/cql/uuid.rb +4 -2
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/asynchronous_client_spec.rb +47 -71
- data/spec/cql/client/asynchronous_prepared_statement_spec.rb +68 -0
- data/spec/cql/client/client_shared.rb +3 -3
- data/spec/cql/client/column_metadata_spec.rb +80 -0
- data/spec/cql/client/request_runner_spec.rb +120 -0
- data/spec/cql/future_spec.rb +26 -11
- data/spec/cql/io/connection_spec.rb +460 -0
- data/spec/cql/io/io_reactor_spec.rb +212 -265
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +216 -0
- data/spec/cql/protocol/decoding_spec.rb +9 -28
- data/spec/cql/protocol/encoding_spec.rb +0 -5
- data/spec/cql/protocol/request_spec.rb +16 -0
- data/spec/cql/protocol/response_frame_spec.rb +2 -2
- data/spec/cql/protocol/responses/schema_change_result_response_spec.rb +70 -0
- data/spec/cql/time_uuid_spec.rb +136 -0
- data/spec/cql/uuid_spec.rb +1 -5
- data/spec/integration/client_spec.rb +34 -38
- data/spec/integration/io_spec.rb +283 -0
- data/spec/integration/protocol_spec.rb +53 -113
- data/spec/integration/regression_spec.rb +124 -0
- data/spec/integration/uuid_spec.rb +76 -0
- data/spec/spec_helper.rb +12 -9
- data/spec/support/fake_io_reactor.rb +52 -21
- data/spec/support/fake_server.rb +2 -2
- metadata +33 -10
- checksums.yaml +0 -15
- data/lib/cql/io/node_connection.rb +0 -209
- 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
|
-
|
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
@@ -39,10 +39,15 @@ module Cql
|
|
39
39
|
client.connect.get.should equal(client)
|
40
40
|
end
|
41
41
|
|
42
|
-
it '
|
42
|
+
it 'connects to the right host and port' do
|
43
43
|
client.connect.get
|
44
|
-
|
45
|
-
|
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
|
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(:
|
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(:
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
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.
|
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.
|
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|