cql-rb 1.0.0.pre0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +13 -0
- data/bin/cqlexec +135 -0
- data/lib/cql.rb +11 -0
- data/lib/cql/client.rb +196 -0
- data/lib/cql/future.rb +176 -0
- data/lib/cql/io.rb +13 -0
- data/lib/cql/io/io_reactor.rb +351 -0
- data/lib/cql/protocol.rb +39 -0
- data/lib/cql/protocol/decoding.rb +156 -0
- data/lib/cql/protocol/encoding.rb +109 -0
- data/lib/cql/protocol/request_frame.rb +228 -0
- data/lib/cql/protocol/response_frame.rb +551 -0
- data/lib/cql/uuid.rb +46 -0
- data/lib/cql/version.rb +5 -0
- data/spec/cql/client_spec.rb +368 -0
- data/spec/cql/future_spec.rb +297 -0
- data/spec/cql/io/io_reactor_spec.rb +290 -0
- data/spec/cql/protocol/decoding_spec.rb +464 -0
- data/spec/cql/protocol/encoding_spec.rb +338 -0
- data/spec/cql/protocol/request_frame_spec.rb +359 -0
- data/spec/cql/protocol/response_frame_spec.rb +746 -0
- data/spec/cql/uuid_spec.rb +40 -0
- data/spec/integration/client_spec.rb +101 -0
- data/spec/integration/protocol_spec.rb +326 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/fake_io_reactor.rb +55 -0
- data/spec/support/fake_server.rb +95 -0
- metadata +87 -0
@@ -0,0 +1,746 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Protocol
|
8
|
+
describe ResponseFrame do
|
9
|
+
let :frame do
|
10
|
+
described_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when fed no data' do
|
14
|
+
it 'has a header length' do
|
15
|
+
frame.header_length.should == 8
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'has no body length' do
|
19
|
+
frame.body_length.should be_nil
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is not complete' do
|
23
|
+
frame.should_not be_complete
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when fed a header' do
|
28
|
+
before do
|
29
|
+
frame << "\x81\x00\x00\x02\x00\x00\x00\x16"
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'knows the frame body length' do
|
33
|
+
frame.body_length.should == 22
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'when fed a header in pieces' do
|
38
|
+
before do
|
39
|
+
frame << "\x81\x00"
|
40
|
+
frame << "\x00\x02\x00\x00\x00"
|
41
|
+
frame << "\x16"
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'knows the body length' do
|
45
|
+
frame.body_length.should == 22
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'knows the stream ID' do
|
49
|
+
frame.stream_id.should == 0
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'when fed a header with a non-zero stream ID' do
|
54
|
+
before do
|
55
|
+
frame << "\x81\x00\x20\x02\x00\x00\x00\x16"
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'knows the stream ID' do
|
59
|
+
frame.stream_id.should == 0x20
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when fed a header with the stream ID -1' do
|
64
|
+
before do
|
65
|
+
frame << "\x81\x00\xff\x02\x00\x00\x00\x16"
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'knows the stream ID' do
|
69
|
+
frame.stream_id.should == -1
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'when fed a request frame header' do
|
74
|
+
it 'raises an UnsupportedFrameTypeError' do
|
75
|
+
expect { frame << "\x01\x00\x00\x00\x00\x00\x00\x00" }.to raise_error(UnsupportedFrameTypeError)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when fed a request frame header' do
|
80
|
+
it 'raises an UnsupportedFrameTypeError' do
|
81
|
+
expect { frame << "\x01\x00\x00\x00\x00\x00\x00\x00" }.to raise_error(UnsupportedFrameTypeError)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when fed a header and a partial body' do
|
86
|
+
before do
|
87
|
+
frame << "\x81\x00"
|
88
|
+
frame << "\x00\x06"
|
89
|
+
frame << "\x00\x00\x00\x16"
|
90
|
+
frame << [rand(255), rand(255), rand(255), rand(255), rand(255), rand(255), rand(255), rand(255)].pack('C')
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'knows the body length' do
|
94
|
+
frame.body_length.should == 22
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'is not complete' do
|
98
|
+
frame.should_not be_complete
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'when fed a complete ERROR frame' do
|
103
|
+
before do
|
104
|
+
frame << "\x81\x00\x00\x00\x00\x00\x00V\x00\x00\x00\n\x00PProvided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)"
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'is complete' do
|
108
|
+
frame.should be_complete
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'has an error code' do
|
112
|
+
frame.body.code.should == 10
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'has an error message' do
|
116
|
+
frame.body.message.should == 'Provided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)'
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'has a pretty #to_s representation' do
|
120
|
+
frame.body.to_s.should == 'ERROR 10 "Provided version 4.0.0 is not supported by this server (supported: 2.0.0, 3.0.0)"'
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when fed an ERROR frame with details' do
|
125
|
+
it 'has more details for a unavailable error' do
|
126
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x1c\x00\x00\x10\x00\x00\x0cUnavailable!\x00\x05\x00\x00\x00\x03\x00\x00\x00\x02")
|
127
|
+
frame.body.details.should == {
|
128
|
+
:cl => :all,
|
129
|
+
:required => 3,
|
130
|
+
:alive => 2
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'has more details for a write_timeout error' do
|
135
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00K\x00\x00\x11\x00\x000Operation timed out - received only 0 responses.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\tBATCH_LOG")
|
136
|
+
frame.body.details.should == {
|
137
|
+
:cl => :one,
|
138
|
+
:received => 0,
|
139
|
+
:blockfor => 1,
|
140
|
+
:write_type => 'BATCH_LOG'
|
141
|
+
}
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'has more details for a read_timeout error' do
|
145
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x25\x00\x00\x12\x00\x00\x14Operation timed out.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x01")
|
146
|
+
frame.body.details.should == {
|
147
|
+
:cl => :one,
|
148
|
+
:received => 0,
|
149
|
+
:blockfor => 1,
|
150
|
+
:data_present => true
|
151
|
+
}
|
152
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x25\x00\x00\x12\x00\x00\x14Operation timed out.\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00")
|
153
|
+
frame.body.details.should == {
|
154
|
+
:cl => :one,
|
155
|
+
:received => 0,
|
156
|
+
:blockfor => 1,
|
157
|
+
:data_present => false
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'has more details for an already_exists error with a keyspace' do
|
162
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x1e\x00\x00\x24\x00\x00\x10Keyspace exists!\x00\x05stuff\x00\x00")
|
163
|
+
frame.body.details.should == {
|
164
|
+
:ks => 'stuff',
|
165
|
+
:table => '',
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
it 'has more details for an already_exists error with a keyspace and table' do
|
170
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x24\x00\x00\x24\x00\x00\x10Keyspace exists!\x00\x05stuff\x00\x06things")
|
171
|
+
frame.body.details.should == {
|
172
|
+
:ks => 'stuff',
|
173
|
+
:table => 'things',
|
174
|
+
}
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'has more details for an unprepared error' do
|
178
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x33\x00\x00\x25\x00\x00\x1bUnknown prepared statement!\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/")
|
179
|
+
frame.body.details.should == {
|
180
|
+
:id => "\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/"
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'has a pretty #to_s representation' do
|
185
|
+
frame = described_class.new("\x81\x00\x00\x00\x00\x00\x00\x33\x00\x00\x25\x00\x00\x1bUnknown prepared statement!\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/")
|
186
|
+
frame.body.to_s.should match(/^ERROR 9472 "Unknown prepared statement!" \{:id=>".+?"\}$/)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'when fed a complete READY frame' do
|
191
|
+
before do
|
192
|
+
frame << [0x81, 0, 0, 0x02, 0].pack('C4N')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'is complete' do
|
196
|
+
frame.should be_complete
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'has a pretty #to_s representation' do
|
200
|
+
frame.body.to_s.should == 'READY'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
context 'when fed a complete SUPPORTED frame' do
|
205
|
+
before do
|
206
|
+
frame << "\x81\x00\x00\x06\x00\x00\x00\x27"
|
207
|
+
frame << "\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'is complete' do
|
211
|
+
frame.should be_complete
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'has options' do
|
215
|
+
frame.body.options.should == {'CQL_VERSION' => ['3.0.0'], 'COMPRESSION' => []}
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'has a pretty #to_s representation' do
|
219
|
+
frame.body.to_s.should == 'SUPPORTED {"CQL_VERSION"=>["3.0.0"], "COMPRESSION"=>[]}'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context 'when fed a complete RESULT frame' do
|
224
|
+
context 'when it\'s a set_keyspace' do
|
225
|
+
before do
|
226
|
+
frame << "\x81\x00\x00\b\x00\x00\x00\f"
|
227
|
+
frame << "\x00\x00\x00\x03\x00\x06system"
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'has a keyspace' do
|
231
|
+
frame.body.keyspace.should == 'system'
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'is not void' do
|
235
|
+
frame.body.should_not be_void
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'has a pretty #to_s representation' do
|
239
|
+
frame.body.to_s.should == 'RESULT SET_KEYSPACE "system"'
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
context 'when it\'s a schema_change' do
|
244
|
+
context 'when it\'s a keyspace change' do
|
245
|
+
before do
|
246
|
+
frame << "\x81\x00\x00\b\x00\x00\x00\e\x00\x00\x00\x05\x00\aCREATED\x00\ncql_rb_477\x00\x00"
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'has a change description' do
|
250
|
+
frame.body.change.should == 'CREATED'
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'has a keyspace' do
|
254
|
+
frame.body.keyspace.should == 'cql_rb_477'
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'has no table' do
|
258
|
+
frame.body.table.should be_empty
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'is not void' do
|
262
|
+
frame.body.should_not be_void
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'has a pretty #to_s representation' do
|
266
|
+
frame.body.to_s.should == 'RESULT SCHEMA_CHANGE CREATED "cql_rb_477" ""'
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context 'when it\'s a table change' do
|
271
|
+
before do
|
272
|
+
frame << "\x81\x00\x00\b\x00\x00\x00 \x00\x00\x00\x05\x00\aUPDATED\x00\ncql_rb_973\x00\x05users"
|
273
|
+
end
|
274
|
+
|
275
|
+
it 'has a change description' do
|
276
|
+
frame.body.change.should == 'UPDATED'
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'has a keyspace' do
|
280
|
+
frame.body.keyspace.should == 'cql_rb_973'
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'has a table' do
|
284
|
+
frame.body.table.should == 'users'
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'is not void' do
|
288
|
+
frame.body.should_not be_void
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'has a pretty #to_s representation' do
|
292
|
+
frame.body.to_s.should == 'RESULT SCHEMA_CHANGE UPDATED "cql_rb_973" "users"'
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'when it\'s a void' do
|
298
|
+
before do
|
299
|
+
frame << "\x81\x00\x00\b\x00\x00\x00\x04\x00\x00\x00\x01"
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'is has a body' do
|
303
|
+
frame.body.should_not be_nil
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'is void' do
|
307
|
+
frame.body.should be_void
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'has a pretty #to_s representation' do
|
311
|
+
frame.body.to_s.should == 'RESULT VOID'
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
context 'when it\'s rows' do
|
316
|
+
before do
|
317
|
+
frame << "\x81\x00\x00\b\x00\x00\x00~\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_126\x00\x05users\x00\tuser_name\x00\r\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
|
318
|
+
end
|
319
|
+
|
320
|
+
it 'has rows that are hashes of column name => column value' do
|
321
|
+
frame.body.rows.should == [
|
322
|
+
{'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
|
323
|
+
{'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
|
324
|
+
]
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'has column metadata' do
|
328
|
+
frame.body.metadata.should == [
|
329
|
+
['cql_rb_126', 'users', 'user_name', :varchar],
|
330
|
+
['cql_rb_126', 'users', 'email', :varchar],
|
331
|
+
['cql_rb_126', 'users', 'password', :varchar]
|
332
|
+
]
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'is not void' do
|
336
|
+
frame.body.should_not be_void
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'has a pretty #to_s representation' do
|
340
|
+
frame.body.to_s.should == 'RESULT ROWS [["cql_rb_126", "users", "user_name", :varchar], ["cql_rb_126", "users", "email", :varchar], ["cql_rb_126", "users", "password", :varchar]] [{"user_name"=>"phil", "email"=>"phil@heck.com", "password"=>nil}, {"user_name"=>"sue", "email"=>"sue@inter.net", "password"=>nil}]'
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
context 'when it\'s rows from different keyspaces' do
|
345
|
+
before do
|
346
|
+
# TODO: not sure if this is really how it would be for real
|
347
|
+
# this frame was constructed from the spec not from an actual result
|
348
|
+
frame << "\x81\x00\x00\b\x00\x00\x00\xa7"
|
349
|
+
frame << "\x00\x00\x00\x02"
|
350
|
+
frame << "\x00\x00\x00\x00"
|
351
|
+
frame << "\x00\x00\x00\x03"
|
352
|
+
frame << "\x00\ncql_rb_126\x00\x06users1\x00\tuser_name\x00\r"
|
353
|
+
frame << "\x00\ncql_rb_127\x00\x06users2\x00\x05email\x00\r"
|
354
|
+
frame << "\x00\ncql_rb_128\x00\x06users3\x00\bpassword\x00\r"
|
355
|
+
frame << "\x00\x00\x00\x02\x00\x00\x00\x04phil\x00\x00\x00\rphil@heck.com\xFF\xFF\xFF\xFF\x00\x00\x00\x03sue\x00\x00\x00\rsue@inter.net\xFF\xFF\xFF\xFF"
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'has rows that are hashes of column name => column value' do
|
359
|
+
frame.body.rows.should == [
|
360
|
+
{'user_name' => 'phil', 'email' => 'phil@heck.com', 'password' => nil},
|
361
|
+
{'user_name' => 'sue', 'email' => 'sue@inter.net', 'password' => nil}
|
362
|
+
]
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'has column metadata' do
|
366
|
+
frame.body.metadata.should == [
|
367
|
+
['cql_rb_126', 'users1', 'user_name', :varchar],
|
368
|
+
['cql_rb_127', 'users2', 'email', :varchar],
|
369
|
+
['cql_rb_128', 'users3', 'password', :varchar]
|
370
|
+
]
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'when it\'s a prepared' do
|
375
|
+
before do
|
376
|
+
frame << "\x81\x00\x00\b\x00\x00\x00>"
|
377
|
+
frame << "\x00\x00\x00\x04\x00\x10\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/\x00\x00\x00\x01\x00\x00\x00\x01\x00\ncql_rb_911\x00\x05users\x00\tuser_name\x00\r"
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'has an id' do
|
381
|
+
frame.body.id.should == "\xCAH\x7F\x1Ez\x82\xD2<N\x8A\xF35Qq\xA5/"
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'has column metadata' do
|
385
|
+
frame.body.metadata.should == [['cql_rb_911', 'users', 'user_name', :varchar]]
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'has a pretty #to_s representation' do
|
389
|
+
frame.body.to_s.should match(/^RESULT PREPARED [0-9a-f]{32} \[\["cql_rb_911", "users", "user_name", :varchar\]\]$/)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
context 'with different column types' do
|
394
|
+
before do
|
395
|
+
# The following test was created by intercepting the frame for the
|
396
|
+
# SELECT statement in this CQL exchange
|
397
|
+
#
|
398
|
+
# CREATE TABLE lots_of_types (
|
399
|
+
# ascii_column ASCII,
|
400
|
+
# bigint_column BIGINT,
|
401
|
+
# blob_column BLOB,
|
402
|
+
# boolean_column BOOLEAN,
|
403
|
+
# decimal_column DECIMAL,
|
404
|
+
# double_column DOUBLE,
|
405
|
+
# float_column FLOAT,
|
406
|
+
# int_column INT,
|
407
|
+
# text_column TEXT,
|
408
|
+
# timestamp_column TIMESTAMP,
|
409
|
+
# uuid_column UUID,
|
410
|
+
# varchar_column VARCHAR,
|
411
|
+
# varint_column VARINT,
|
412
|
+
# timeuuid_column TIMEUUID,
|
413
|
+
# inet_column INET,
|
414
|
+
# list_column LIST<ASCII>,
|
415
|
+
# map_column MAP<TEXT, BOOLEAN>,
|
416
|
+
# set_column SET<BLOB>,
|
417
|
+
#
|
418
|
+
# PRIMARY KEY (ascii_column)
|
419
|
+
# );
|
420
|
+
#
|
421
|
+
# INSERT INTO lots_of_types (ascii_column, bigint_column, blob_column, boolean_column, decimal_column, double_column, float_column, int_column, text_column, timestamp_column, uuid_column, varchar_column, varint_column, timeuuid_column, inet_column, list_column, map_column, set_column)
|
422
|
+
# VALUES (
|
423
|
+
# 'hello',
|
424
|
+
# 1012312312414123,
|
425
|
+
# 'fab45e3456',
|
426
|
+
# true,
|
427
|
+
# 1042342234234.123423435647768234,
|
428
|
+
# 10000.123123123,
|
429
|
+
# 12.13,
|
430
|
+
# 12348098,
|
431
|
+
# 'hello world',
|
432
|
+
# 1358013521.123,
|
433
|
+
# cfd66ccc-d857-4e90-b1e5-df98a3d40cd6,
|
434
|
+
# 'foo',
|
435
|
+
# 1231312312331283012830129382342342412123,
|
436
|
+
# a4a70900-24e1-11df-8924-001ff3591711,
|
437
|
+
# 167772418,
|
438
|
+
# ['foo', 'foo', 'bar'],
|
439
|
+
# {'foo': true, 'hello': false},
|
440
|
+
# {'ab4321', 'afd87ecd'}
|
441
|
+
# );
|
442
|
+
#
|
443
|
+
# SELECT * FROM lots_of_types WHERE ascii_column = 'hello';
|
444
|
+
|
445
|
+
frame << "\x81\x00\x00\b\x00\x00\x02K"
|
446
|
+
frame << "\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x12\x00\x04test\x00\rlots_of_types\x00\fascii_column\x00\x01\x00\rbigint_column\x00\x02\x00\vblob_column\x00\x03\x00\x0Eboolean_column\x00\x04\x00\x0Edecimal_column\x00\x06\x00\rdouble_column\x00\a\x00\ffloat_column\x00\b\x00\vinet_column\x00\x10\x00\nint_column\x00\t\x00\vlist_column\x00 \x00\x01\x00\nmap_column\x00!\x00\r\x00\x04\x00\nset_column\x00\"\x00\x03\x00\vtext_column\x00\r\x00\x10timestamp_column\x00\v\x00\x0Ftimeuuid_column\x00\x0F\x00\vuuid_column\x00\f\x00\x0Evarchar_column\x00\r\x00\rvarint_column\x00\x0E\x00\x00\x00\x01\x00\x00\x00\x05hello\x00\x00\x00\b\x00\x03\x98\xB1S\xC8\x7F\xAB\x00\x00\x00\x05\xFA\xB4^4V\x00\x00\x00\x01\x01\x00\x00\x00\x11\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA\x00\x00\x00\b@\xC3\x88\x0F\xC2\x7F\x9DU\x00\x00\x00\x04AB\x14{\x00\x00\x00\x04\n\x00\x01\x02\x00\x00\x00\x04\x00\xBCj\xC2\x00\x00\x00\x11\x00\x03\x00\x03foo\x00\x03foo\x00\x03bar\x00\x00\x00\x14\x00\x02\x00\x03foo\x00\x01\x01\x00\x05hello\x00\x01\x00\x00\x00\x00\r\x00\x02\x00\x03\xABC!\x00\x04\xAF\xD8~\xCD\x00\x00\x00\vhello world\x00\x00\x00\b\x00\x00\x01</\xE9\xDC\xE3\x00\x00\x00\x10\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11\x00\x00\x00\x10\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6\x00\x00\x00\x03foo\x00\x00\x00\x11\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["
|
447
|
+
end
|
448
|
+
|
449
|
+
it 'decodes ASCII as an ASCII encoded string' do
|
450
|
+
frame.body.rows.first['ascii_column'].should == 'hello'
|
451
|
+
frame.body.rows.first['ascii_column'].encoding.should == ::Encoding::ASCII
|
452
|
+
end
|
453
|
+
|
454
|
+
it 'decodes BIGINT as a number' do
|
455
|
+
frame.body.rows.first['bigint_column'].should == 1012312312414123
|
456
|
+
end
|
457
|
+
|
458
|
+
it 'decodes BLOB as a ASCII-8BIT string' do
|
459
|
+
frame.body.rows.first['blob_column'].should == "\xfa\xb4\x5e\x34\x56"
|
460
|
+
frame.body.rows.first['blob_column'].encoding.should == ::Encoding::BINARY
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'decodes BOOLEAN as a boolean' do
|
464
|
+
frame.body.rows.first['boolean_column'].should equal(true)
|
465
|
+
end
|
466
|
+
|
467
|
+
it 'decodes DECIMAL as a number' do
|
468
|
+
frame.body.rows.first['decimal_column'].should == BigDecimal.new('1042342234234.123423435647768234')
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'decodes DOUBLE as a number' do
|
472
|
+
frame.body.rows.first['double_column'].should == 10000.123123123
|
473
|
+
end
|
474
|
+
|
475
|
+
it 'decodes FLOAT as a number' do
|
476
|
+
frame.body.rows.first['float_column'].should be_within(0.001).of(12.13)
|
477
|
+
end
|
478
|
+
|
479
|
+
it 'decodes INT as a number' do
|
480
|
+
frame.body.rows.first['int_column'].should == 12348098
|
481
|
+
end
|
482
|
+
|
483
|
+
it 'decodes TEXT as a UTF-8 encoded string' do
|
484
|
+
frame.body.rows.first['text_column'].should == 'hello world'
|
485
|
+
frame.body.rows.first['text_column'].encoding.should == ::Encoding::UTF_8
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'decodes TIMESTAMP as a Time' do
|
489
|
+
frame.body.rows.first['timestamp_column'].should == Time.at(1358013521.123)
|
490
|
+
end
|
491
|
+
|
492
|
+
it 'decodes UUID as a Uuid' do
|
493
|
+
frame.body.rows.first['uuid_column'].should == Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6')
|
494
|
+
end
|
495
|
+
|
496
|
+
it 'decodes VARCHAR as a UTF-8 encoded string' do
|
497
|
+
frame.body.rows.first['varchar_column'].should == 'foo'
|
498
|
+
frame.body.rows.first['varchar_column'].encoding.should == ::Encoding::UTF_8
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'decodes VARINT as a number' do
|
502
|
+
frame.body.rows.first['varint_column'].should == 1231312312331283012830129382342342412123
|
503
|
+
end
|
504
|
+
|
505
|
+
it 'decodes TIMEUUID as a Uuid' do
|
506
|
+
frame.body.rows.first['timeuuid_column'].should == Uuid.new('a4a70900-24e1-11df-8924-001ff3591711')
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'decodes INET as a IPAddr' do
|
510
|
+
frame.body.rows.first['inet_column'].should == IPAddr.new('10.0.1.2')
|
511
|
+
end
|
512
|
+
|
513
|
+
it 'decodes LIST<ASCII> as an array of ASCII strings' do
|
514
|
+
frame.body.rows.first['list_column'].should == ['foo', 'foo', 'bar'].map { |s| s.force_encoding(::Encoding::ASCII) }
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'decodes MAP<TEXT, BOOLEAN> as a hash of UTF-8 strings to booleans' do
|
518
|
+
frame.body.rows.first['map_column'].should == {'foo' => true, 'hello' => false}
|
519
|
+
end
|
520
|
+
|
521
|
+
it 'decodes SET<BLOB> as a set of binary strings' do
|
522
|
+
frame.body.rows.first['set_column'].should == Set.new(["\xab\x43\x21", "\xaf\xd8\x7e\xcd"].map { |s| s.force_encoding(::Encoding::BINARY) })
|
523
|
+
end
|
524
|
+
|
525
|
+
it 'decodes COUNTER as a number' do
|
526
|
+
frame = described_class.new("\x81\x00\x00\b\x00\x00\x00N\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\x04test\x00\x04cnts\x00\x02id\x00\r\x00\x02c1\x00\x05\x00\x02c2\x00\x05\x00\x00\x00\x01\x00\x00\x00\x04theo\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\b\x00\x00\x00\x00\x00\x00\x00\x01")
|
527
|
+
frame.body.rows.first['c1'].should == 3
|
528
|
+
end
|
529
|
+
|
530
|
+
it 'raises an error when encountering an unknown column type' do
|
531
|
+
frame = described_class.new
|
532
|
+
frame << "\x81\x00\x00\b\x00\x00\x00E"
|
533
|
+
frame << "\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_328\x00\x05users"
|
534
|
+
expect { frame << "\x00\tuser_name\x00\xff\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x00" }.to raise_error(UnsupportedColumnTypeError)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
|
538
|
+
context 'when it\'s an unknown type' do
|
539
|
+
it 'raises an error' do
|
540
|
+
expect { frame << "\x81\x00\x00\b\x00\x00\x00\x05\x00\x00\x00\xffhello" }.to raise_error(UnsupportedResultKindError)
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
context 'when fed a SCHEMA_CHANGE EVENT frame' do
|
546
|
+
before do
|
547
|
+
frame << "\x81\x00\xFF\f\x00\x00\x00+\x00\rSCHEMA_CHANGE\x00\aDROPPED\x00\ncql_rb_609\x00\x05users"
|
548
|
+
end
|
549
|
+
|
550
|
+
it 'has the stream ID -1' do
|
551
|
+
frame.stream_id.should == -1
|
552
|
+
end
|
553
|
+
|
554
|
+
it 'has an event type' do
|
555
|
+
frame.body.type.should == 'SCHEMA_CHANGE'
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'has a change' do
|
559
|
+
frame.body.change.should == 'DROPPED'
|
560
|
+
end
|
561
|
+
|
562
|
+
it 'has a keyspace' do
|
563
|
+
frame.body.keyspace.should == 'cql_rb_609'
|
564
|
+
end
|
565
|
+
|
566
|
+
it 'has a table' do
|
567
|
+
frame.body.table.should == 'users'
|
568
|
+
end
|
569
|
+
|
570
|
+
it 'has a pretty #to_s representation' do
|
571
|
+
frame.body.to_s.should == 'EVENT SCHEMA_CHANGE DROPPED "cql_rb_609" "users"'
|
572
|
+
end
|
573
|
+
end
|
574
|
+
|
575
|
+
context 'when fed a STATUS_CHANGE EVENT frame' do
|
576
|
+
before do
|
577
|
+
frame << "\x81\x00\xFF\f\x00\x00\x00\x1E\x00\rSTATUS_CHANGE\x00\x04DOWN\x04\x00\x00\x00\x00\x00\x00#R"
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'has the stream ID -1' do
|
581
|
+
frame.stream_id.should == -1
|
582
|
+
end
|
583
|
+
|
584
|
+
it 'has an event type' do
|
585
|
+
frame.body.type.should == 'STATUS_CHANGE'
|
586
|
+
end
|
587
|
+
|
588
|
+
it 'has a change' do
|
589
|
+
frame.body.change.should == 'DOWN'
|
590
|
+
end
|
591
|
+
|
592
|
+
it 'has an address' do
|
593
|
+
frame.body.address.should == IPAddr.new('0.0.0.0')
|
594
|
+
end
|
595
|
+
|
596
|
+
it 'has a port' do
|
597
|
+
frame.body.port.should == 9042
|
598
|
+
end
|
599
|
+
|
600
|
+
it 'has a pretty #to_s representation' do
|
601
|
+
frame.body.to_s.should == 'EVENT STATUS_CHANGE DOWN 0.0.0.0:9042'
|
602
|
+
end
|
603
|
+
end
|
604
|
+
|
605
|
+
context 'when fed a TOPOLOGY_CHANGE EVENT frame' do
|
606
|
+
before do
|
607
|
+
frame << "\x81\x00\xFF\f\x00\x00\x00(\x00\x0FTOPOLOGY_CHANGE\x00\fREMOVED_NODE\x04\x00\x00\x00\x00\x00\x00#R"
|
608
|
+
end
|
609
|
+
|
610
|
+
it 'has the stream ID -1' do
|
611
|
+
frame.stream_id.should == -1
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'has an event type' do
|
615
|
+
frame.body.type.should == 'TOPOLOGY_CHANGE'
|
616
|
+
end
|
617
|
+
|
618
|
+
it 'has a change' do
|
619
|
+
frame.body.change.should == 'REMOVED_NODE'
|
620
|
+
end
|
621
|
+
|
622
|
+
it 'has an address' do
|
623
|
+
frame.body.address.should == IPAddr.new('0.0.0.0')
|
624
|
+
end
|
625
|
+
|
626
|
+
it 'has a port' do
|
627
|
+
frame.body.port.should == 9042
|
628
|
+
end
|
629
|
+
|
630
|
+
it 'has a pretty #to_s representation' do
|
631
|
+
frame.body.to_s.should == 'EVENT TOPOLOGY_CHANGE REMOVED_NODE 0.0.0.0:9042'
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
context 'when fed an unsupported event type' do
|
636
|
+
it 'raises an exception' do
|
637
|
+
frame = "\x81\x00\xFF\f\x00\x00\x00\x06\x00\x04PING"
|
638
|
+
expect { described_class.new(frame) }.to raise_error(UnsupportedEventTypeError, /PING/)
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
context 'when fed an non-existent opcode' do
|
643
|
+
it 'raises an UnsupportedOperationError' do
|
644
|
+
expect { frame << "\x81\x00\x00\x99\x00\x00\x00\x39" }.to raise_error(UnsupportedOperationError)
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
context 'when fed more bytes than needed' do
|
649
|
+
it 'it consumes its bytes, leaving the rest' do
|
650
|
+
buffer = "\x81\x00\x00\x06\x00\x00\x00\x27\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
|
651
|
+
buffer << "\x81\x00\x00\x00"
|
652
|
+
described_class.new(buffer)
|
653
|
+
buffer.should == "\x81\x00\x00\x00"
|
654
|
+
end
|
655
|
+
end
|
656
|
+
|
657
|
+
context 'when fed a frame that is longer than the specification specifies' do
|
658
|
+
it 'it consumes the body length, leaving the rest' do
|
659
|
+
buffer = "\x81\x00\x00\x06\x00\x00\x00\x2a"
|
660
|
+
buffer << "\x00\x02\x00\x0bCQL_VERSION\x00\x01\x00\x053.0.0\x00\x0bCOMPRESSION\x00\x00"
|
661
|
+
buffer << "\xab\xcd\xef\x99"
|
662
|
+
described_class.new(buffer)
|
663
|
+
buffer.should == "\x99"
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
describe ReadyResponse do
|
668
|
+
describe '#eql?' do
|
669
|
+
it 'is equal to all other ready responses' do
|
670
|
+
ReadyResponse.new.should eql(ReadyResponse.new)
|
671
|
+
end
|
672
|
+
|
673
|
+
it 'aliased as ==' do
|
674
|
+
ReadyResponse.new.should == ReadyResponse.new
|
675
|
+
end
|
676
|
+
end
|
677
|
+
|
678
|
+
describe '#hash' do
|
679
|
+
it 'has the same hash code as all other ready responses' do
|
680
|
+
ReadyResponse.new.hash.should == ReadyResponse.new.hash
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
describe SchemaChangeEventResponse do
|
686
|
+
describe '#eql?' do
|
687
|
+
it 'is equal to an identical response' do
|
688
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
689
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
690
|
+
r1.should eql(r2)
|
691
|
+
end
|
692
|
+
|
693
|
+
it 'is not equal when the change is different' do
|
694
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
695
|
+
r2 = SchemaChangeEventResponse.new('CREATED', 'keyspace_name', 'table_name')
|
696
|
+
r1.should_not eql(r2)
|
697
|
+
end
|
698
|
+
|
699
|
+
it 'is not equal when the keyspace is different' do
|
700
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
701
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'eman_ecapsyek', 'table_name')
|
702
|
+
r1.should_not eql(r2)
|
703
|
+
end
|
704
|
+
|
705
|
+
it 'is not equal when the table is different' do
|
706
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
707
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'eman_elbat')
|
708
|
+
r1.should_not eql(r2)
|
709
|
+
end
|
710
|
+
|
711
|
+
it 'is aliased as ==' do
|
712
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
713
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
714
|
+
r1.should == r2
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
describe '#hash' do
|
719
|
+
it 'is the same for an identical response' do
|
720
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
721
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
722
|
+
r1.hash.should == r2.hash
|
723
|
+
end
|
724
|
+
|
725
|
+
it 'is not the same when the change is different' do
|
726
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
727
|
+
r2 = SchemaChangeEventResponse.new('CREATED', 'keyspace_name', 'table_name')
|
728
|
+
r1.hash.should_not == r2.hash
|
729
|
+
end
|
730
|
+
|
731
|
+
it 'is not the same when the keyspace is different' do
|
732
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
733
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'eman_ecapsyek', 'table_name')
|
734
|
+
r1.hash.should_not == r2.hash
|
735
|
+
end
|
736
|
+
|
737
|
+
it 'is not the same when the table is different' do
|
738
|
+
r1 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'table_name')
|
739
|
+
r2 = SchemaChangeEventResponse.new('DELETED', 'keyspace_name', 'eman_elbat')
|
740
|
+
r1.hash.should_not == r2.hash
|
741
|
+
end
|
742
|
+
end
|
743
|
+
end
|
744
|
+
end
|
745
|
+
end
|
746
|
+
end
|