cql-rb 2.0.5 → 2.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.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/cql.rb +0 -1
- data/lib/cql/client.rb +0 -2
- data/lib/cql/client/client.rb +5 -3
- data/lib/cql/client/connector.rb +4 -4
- data/lib/cql/client/prepared_statement.rb +4 -11
- data/lib/cql/protocol.rb +1 -0
- data/lib/cql/protocol/cql_protocol_handler.rb +2 -2
- data/lib/cql/protocol/custom_type_parser.rb +114 -0
- data/lib/cql/protocol/responses/detailed_error_response.rb +5 -5
- data/lib/cql/protocol/responses/error_response.rb +1 -3
- data/lib/cql/protocol/responses/rows_result_response.rb +4 -1
- data/lib/cql/protocol/type_converter.rb +82 -24
- data/lib/cql/version.rb +1 -1
- data/spec/cql/client/client_spec.rb +7 -0
- data/spec/cql/client/connector_spec.rb +6 -2
- data/spec/cql/client/peer_discovery_spec.rb +3 -3
- data/spec/cql/client/prepared_statement_spec.rb +4 -23
- data/spec/cql/protocol/cql_protocol_handler_spec.rb +0 -31
- data/spec/cql/protocol/custom_type_parser_spec.rb +43 -0
- data/spec/cql/protocol/requests/execute_request_spec.rb +161 -37
- data/spec/cql/protocol/responses/prepared_result_response_spec.rb +22 -0
- data/spec/cql/protocol/responses/rows_result_response_spec.rb +187 -0
- data/spec/cql/protocol/type_converter_spec.rb +12 -0
- data/spec/integration/client_spec.rb +60 -8
- data/spec/support/fake_io_reactor.rb +6 -5
- metadata +11 -9
- data/lib/cql/error_codes.rb +0 -96
data/lib/cql/version.rb
CHANGED
@@ -361,6 +361,13 @@ module Cql
|
|
361
361
|
last_connection.timeout.should == 10
|
362
362
|
end
|
363
363
|
|
364
|
+
it 'enables client/node encryption when the :ssl option is set' do
|
365
|
+
ssl_context = double(:ssl_context)
|
366
|
+
client = described_class.new(connection_options.merge(ssl: ssl_context))
|
367
|
+
client.connect.value
|
368
|
+
last_connection.options.should include(ssl: ssl_context)
|
369
|
+
end
|
370
|
+
|
364
371
|
it 'is not in a keyspace' do
|
365
372
|
client.connect.value
|
366
373
|
client.keyspace.should be_nil
|
@@ -204,7 +204,11 @@ module Cql
|
|
204
204
|
|
205
205
|
describe ConnectStep do
|
206
206
|
let :step do
|
207
|
-
described_class.new(io_reactor, protocol_handler_factory, 1111,
|
207
|
+
described_class.new(io_reactor, protocol_handler_factory, 1111, connection_options, logger)
|
208
|
+
end
|
209
|
+
|
210
|
+
let :connection_options do
|
211
|
+
{:timeout => 8}
|
208
212
|
end
|
209
213
|
|
210
214
|
let :pending_connection do
|
@@ -246,7 +250,7 @@ module Cql
|
|
246
250
|
|
247
251
|
it 'connects using the connection details given' do
|
248
252
|
step.run(pending_connection)
|
249
|
-
io_reactor.should have_received(:connect).with('example.com', 1111,
|
253
|
+
io_reactor.should have_received(:connect).with('example.com', 1111, connection_options)
|
250
254
|
end
|
251
255
|
|
252
256
|
it 'appends the connection to the given argument and returns the result' do
|
@@ -13,9 +13,9 @@ module Cql
|
|
13
13
|
describe '#new_hosts' do
|
14
14
|
let :seed_connections do
|
15
15
|
[
|
16
|
-
FakeConnection.new('host0', 9042,
|
17
|
-
FakeConnection.new('host1', 9042,
|
18
|
-
FakeConnection.new('host2', 9042,
|
16
|
+
FakeConnection.new('host0', 9042, {}, {:data_center => 'dc0', :host_id => Uuid.new('00000000-0000-0000-0000-000000000000')}),
|
17
|
+
FakeConnection.new('host1', 9042, {}, {:data_center => 'dc0', :host_id => Uuid.new('11111111-1111-1111-1111-111111111111')}),
|
18
|
+
FakeConnection.new('host2', 9042, {}, {:data_center => 'dc0', :host_id => Uuid.new('22222222-2222-2222-2222-222222222222')}),
|
19
19
|
]
|
20
20
|
end
|
21
21
|
|
@@ -55,9 +55,9 @@ module Cql
|
|
55
55
|
|
56
56
|
let :connections do
|
57
57
|
[
|
58
|
-
FakeConnection.new('h0.example.com', 1234
|
59
|
-
FakeConnection.new('h1.example.com', 1234
|
60
|
-
FakeConnection.new('h2.example.com', 1234
|
58
|
+
FakeConnection.new('h0.example.com', 1234),
|
59
|
+
FakeConnection.new('h1.example.com', 1234),
|
60
|
+
FakeConnection.new('h2.example.com', 1234),
|
61
61
|
]
|
62
62
|
end
|
63
63
|
|
@@ -242,7 +242,7 @@ module Cql
|
|
242
242
|
|
243
243
|
context 'when it receives a new connection from the connection manager' do
|
244
244
|
let :new_connection do
|
245
|
-
FakeConnection.new('h3.example.com', 1234
|
245
|
+
FakeConnection.new('h3.example.com', 1234)
|
246
246
|
end
|
247
247
|
|
248
248
|
before do
|
@@ -295,25 +295,6 @@ module Cql
|
|
295
295
|
statement.execute(11, 'hello', trace: true).value
|
296
296
|
tracing.should be_true
|
297
297
|
end
|
298
|
-
|
299
|
-
it 'prepares the statement again when it is lost' do
|
300
|
-
prepare_requests = 0
|
301
|
-
connections.each do |c|
|
302
|
-
c[:num_prepare] = 0
|
303
|
-
c.handle_request do |r, t|
|
304
|
-
if r.is_a?(Protocol::ExecuteRequest) && c[:num_prepare] == 1
|
305
|
-
Protocol::ErrorResponse.new(0x2500, 'Unprepared')
|
306
|
-
else
|
307
|
-
if r == Protocol::PrepareRequest.new(cql)
|
308
|
-
c[:num_prepare] += 1
|
309
|
-
end
|
310
|
-
handle_request(c, r, t)
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
statement.execute(11, 'hello').value
|
315
|
-
connections.map { |c| c[:num_prepare] }.should include(2)
|
316
|
-
end
|
317
298
|
end
|
318
299
|
|
319
300
|
describe '#batch' do
|
@@ -112,16 +112,6 @@ module Cql
|
|
112
112
|
futures[128].should be_resolved
|
113
113
|
end
|
114
114
|
|
115
|
-
it 'flushes the request queue before it resolves the future of the just completed request' do
|
116
|
-
connection.stub(:write)
|
117
|
-
futures = Array.new(130) { protocol_handler.send_request(request) }
|
118
|
-
f = futures[0].map do
|
119
|
-
connection.should have_received(:write).exactly(129).times
|
120
|
-
end
|
121
|
-
connection.data_listener.call([0x81, 0, 0, 2, 0].pack('C4N'))
|
122
|
-
f.value
|
123
|
-
end
|
124
|
-
|
125
115
|
context 'when a compressor is specified' do
|
126
116
|
let :protocol_handler do
|
127
117
|
described_class.new(connection, scheduler, 1, compressor)
|
@@ -258,27 +248,6 @@ module Cql
|
|
258
248
|
128.times { |i| connection.data_listener.call([0x81, 0, i, 2, 0].pack('C4N')) }
|
259
249
|
write_count.should == 128
|
260
250
|
end
|
261
|
-
|
262
|
-
it 'does not stop sending queued requests even when one has timed out' do
|
263
|
-
write_count = 0
|
264
|
-
connection.stub(:write) do |s, &h|
|
265
|
-
write_count += 1
|
266
|
-
if h
|
267
|
-
h.call(buffer)
|
268
|
-
else
|
269
|
-
buffer << s
|
270
|
-
end
|
271
|
-
end
|
272
|
-
128.times do
|
273
|
-
protocol_handler.send_request(request)
|
274
|
-
end
|
275
|
-
scheduler.stub(:schedule_timer).with(5).and_return(timer_promise.future)
|
276
|
-
f1 = protocol_handler.send_request(request, 5)
|
277
|
-
f2 = protocol_handler.send_request(request)
|
278
|
-
timer_promise.fulfill
|
279
|
-
connection.data_listener.call([0x81, 0, 0, 2, 0].pack('C4N'))
|
280
|
-
write_count.should == 129
|
281
|
-
end
|
282
251
|
end
|
283
252
|
end
|
284
253
|
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Cql
|
7
|
+
module Protocol
|
8
|
+
describe CustomTypeParser do
|
9
|
+
let :parser do
|
10
|
+
described_class.new
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#type' do
|
14
|
+
it 'maps internal type names to protocol type names' do
|
15
|
+
type = parser.parse_type('org.apache.cassandra.db.marshal.DecimalType')
|
16
|
+
type.should == :decimal
|
17
|
+
type = parser.parse_type('org.apache.cassandra.db.marshal.LongType')
|
18
|
+
type.should == :bigint
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns custom types as-is' do
|
22
|
+
type = parser.parse_type('com.acme.Foo')
|
23
|
+
type.should == [:custom, 'com.acme.Foo']
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'parses flat user defined types' do
|
27
|
+
type = parser.parse_type('org.apache.cassandra.db.marshal.UserType(some_keyspace,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type)')
|
28
|
+
type.should == [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'parses nested user defined types' do
|
32
|
+
type = parser.parse_type('org.apache.cassandra.db.marshal.UserType(some_keyspace,636f6d70616e79,6e616d65:org.apache.cassandra.db.marshal.UTF8Type,616464726573736573:org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type)))')
|
33
|
+
type.should == [:udt, {'name' => :text, 'addresses' => [:list, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]]}]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'parses nested user defined types where the inner UDT is a map key' do
|
37
|
+
type = parser.parse_type('org.apache.cassandra.db.marshal.UserType(some_keyspace,636f6d70616e79,6e616d65:org.apache.cassandra.db.marshal.UTF8Type,616464726573736573:org.apache.cassandra.db.marshal.MapType(org.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type),org.apache.cassandra.db.marshal.Int32Type))')
|
38
|
+
type.should == [:udt, {'name' => :text, 'addresses' => [:map, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}], :int]}]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -3,6 +3,17 @@
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
5
|
|
6
|
+
module ExecuteRequestSpec
|
7
|
+
module ItEncodes
|
8
|
+
def it_encodes(description, type, value, expected_bytes)
|
9
|
+
it("encodes #{description}") do
|
10
|
+
bytes = encode_value(type, value)
|
11
|
+
bytes.should eql_bytes(expected_bytes)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
6
17
|
module Cql
|
7
18
|
module Protocol
|
8
19
|
describe ExecuteRequest do
|
@@ -64,6 +75,13 @@ module Cql
|
|
64
75
|
end
|
65
76
|
|
66
77
|
describe '#write' do
|
78
|
+
def encode_value(type, value)
|
79
|
+
request = described_class.new(id, [['ks', 'tbl', 'col', type]], [value], true, :one, nil, nil, nil, false)
|
80
|
+
buffer = request.write(1, CqlByteBuffer.new)
|
81
|
+
buffer.discard(2 + 16 + 2)
|
82
|
+
buffer.read(buffer.read_int)
|
83
|
+
end
|
84
|
+
|
67
85
|
context 'when the protocol version is 1' do
|
68
86
|
let :frame_bytes do
|
69
87
|
ExecuteRequest.new(id, column_metadata, ['hello', 42, 'foo'], true, :each_quorum, nil, nil, nil, false).write(1, CqlByteBuffer.new)
|
@@ -141,44 +159,150 @@ module Cql
|
|
141
159
|
end
|
142
160
|
end
|
143
161
|
|
144
|
-
context 'with
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
]
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
162
|
+
context 'with scalar types' do
|
163
|
+
extend ExecuteRequestSpec::ItEncodes
|
164
|
+
|
165
|
+
it_encodes 'ASCII strings', :ascii, 'test', "test"
|
166
|
+
it_encodes 'BIGINTs', :bigint, 1012312312414123, "\x00\x03\x98\xB1S\xC8\x7F\xAB"
|
167
|
+
it_encodes 'BLOBs', :blob, "\xab\xcd", "\xab\xcd"
|
168
|
+
it_encodes 'false BOOLEANs', :boolean, false, "\x00"
|
169
|
+
it_encodes 'true BOOLEANs', :boolean, true, "\x01"
|
170
|
+
it_encodes 'DECIMALs', :decimal, BigDecimal.new('1042342234234.123423435647768234'), "\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA"
|
171
|
+
it_encodes 'DOUBLEs', :double, 10000.123123123, "@\xC3\x88\x0F\xC2\x7F\x9DU"
|
172
|
+
it_encodes 'FLOATs', :float, 12.13, "AB\x14{"
|
173
|
+
it_encodes 'IPv4 INETs', :inet, IPAddr.new('8.8.8.8'), "\x08\x08\x08\x08"
|
174
|
+
it_encodes 'IPv6 INETs', :inet, IPAddr.new('::1'), "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
175
|
+
it_encodes 'INTs', :int, 12348098, "\x00\xBCj\xC2"
|
176
|
+
it_encodes 'TEXTs from UTF-8 strings', :text, 'ümlaut'.force_encoding(::Encoding::UTF_8), "\xc3\xbcmlaut"
|
177
|
+
it_encodes 'TIMESTAMPs from Times', :timestamp, Time.at(1358013521.123), "\x00\x00\x01</\xE9\xDC\xE3"
|
178
|
+
it_encodes 'TIMESTAMPs from floats', :timestamp, 1358013521.123, "\x00\x00\x01</\xE9\xDC\xE3"
|
179
|
+
it_encodes 'TIMESTAMPs from integers', :timestamp, 1358013521, "\x00\x00\x01</\xE9\xDCh"
|
180
|
+
it_encodes 'TIMEUUIDs', :timeuuid, Uuid.new('a4a70900-24e1-11df-8924-001ff3591711'), "\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11"
|
181
|
+
it_encodes 'UUIDs', :uuid, Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'), "\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6"
|
182
|
+
it_encodes 'VARCHAR from UTF-8 strings', :varchar, 'hello'.force_encoding(::Encoding::UTF_8), 'hello'
|
183
|
+
it_encodes 'positive VARINTs', :varint, 1231312312331283012830129382342342412123, "\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["
|
184
|
+
it_encodes 'negative VARINTs', :varint, -234234234234, "\xC9v\x8D:\x86"
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'with collection types' do
|
188
|
+
extend ExecuteRequestSpec::ItEncodes
|
189
|
+
|
190
|
+
it_encodes 'LIST<TIMESTAMP>', [:list, :timestamp], [Time.at(1358013521.123)], "\x00\x01" + "\x00\x08\x00\x00\x01</\xE9\xDC\xE3"
|
191
|
+
it_encodes 'LIST<BOOLEAN>', [:list, :boolean], [true, false, true, true], "\x00\x04" + "\x00\x01\x01" + "\x00\x01\x00" + "\x00\x01\x01" + "\x00\x01\x01"
|
192
|
+
it_encodes 'MAP<UUID,INT>', [:map, :uuid, :int], {Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6') => 45345, Uuid.new('a4a70900-24e1-11df-8924-001ff3591711') => 98765}, "\x00\x02" + "\x00\x10\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6" + "\x00\x04\x00\x00\xb1\x21" + "\x00\x10\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11" + "\x00\x04\x00\x01\x81\xcd"
|
193
|
+
it_encodes 'MAP<ASCII,BLOB>', [:map, :ascii, :blob], {'hello' => 'world', 'one' => "\x01", 'two' => "\x02"}, "\x00\x03" + "\x00\x05hello" + "\x00\x05world" + "\x00\x03one" + "\x00\x01\x01" + "\x00\x03two" + "\x00\x01\x02"
|
194
|
+
it_encodes 'SET<INT> from Sets', [:set, :int], Set.new([13, 3453, 456456, 123, 768678]), "\x00\x05" + "\x00\x04\x00\x00\x00\x0d" + "\x00\x04\x00\x00\x0d\x7d" + "\x00\x04\x00\x06\xf7\x08" + "\x00\x04\x00\x00\x00\x7b" + "\x00\x04\x00\x0b\xba\xa6"
|
195
|
+
it_encodes 'SET<INT> from arrays', [:set, :int], [13, 3453, 456456, 123, 768678], "\x00\x05" + "\x00\x04\x00\x00\x00\x0d" + "\x00\x04\x00\x00\x0d\x7d" + "\x00\x04\x00\x06\xf7\x08" + "\x00\x04\x00\x00\x00\x7b" + "\x00\x04\x00\x0b\xba\xa6"
|
196
|
+
it_encodes 'SET<VARCHAR> from Sets', [:set, :varchar], Set.new(['foo', 'bar', 'baz']), "\x00\x03" + "\x00\x03foo" + "\x00\x03bar" + "\x00\x03baz"
|
197
|
+
it_encodes 'SET<VARCHAR> from arrays', [:set, :varchar], ['foo', 'bar', 'baz'], "\x00\x03" + "\x00\x03foo" + "\x00\x03bar" + "\x00\x03baz"
|
198
|
+
end
|
199
|
+
|
200
|
+
context 'with user defined types' do
|
201
|
+
context 'with a flat UDT' do
|
202
|
+
let :type do
|
203
|
+
[:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]
|
204
|
+
end
|
205
|
+
|
206
|
+
let :value do
|
207
|
+
{'street' => '123 Some St.', 'city' => 'Frans Sanisco', 'zip' => 76543}
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'encodes a hash into bytes' do
|
211
|
+
bytes = encode_value(type, value)
|
212
|
+
bytes.should eql_bytes(
|
213
|
+
"\x00\x00\x00\f123 Some St." +
|
214
|
+
"\x00\x00\x00\rFrans Sanisco" +
|
215
|
+
"\x00\x00\x00\x04\x00\x01*\xFF"
|
216
|
+
)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'with a UDT as a MAP value' do
|
221
|
+
let :type do
|
222
|
+
[:map, :varchar, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]]
|
181
223
|
end
|
224
|
+
|
225
|
+
let :value do
|
226
|
+
{'secret_lair' => {'street' => '4 Some Other St.', 'city' => 'Gos Latos', 'zip' => 87654}}
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'encodes a hash into bytes' do
|
230
|
+
bytes = encode_value(type, value)
|
231
|
+
bytes.should eql_bytes(
|
232
|
+
"\x00\x01" +
|
233
|
+
"\x00\vsecret_lair" +
|
234
|
+
"\x00)" +
|
235
|
+
"\x00\x00\x00\x104 Some Other St." +
|
236
|
+
"\x00\x00\x00\tGos Latos" +
|
237
|
+
"\x00\x00\x00\x04\x00\x01Vf"
|
238
|
+
)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
context 'with nested UDTs' do
|
243
|
+
let :type do
|
244
|
+
[:set, [:udt, {'name' => :text, 'addresses' => [:list, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]]}]]
|
245
|
+
end
|
246
|
+
|
247
|
+
let :value do
|
248
|
+
Set.new([
|
249
|
+
{
|
250
|
+
'name' => 'Acme Corp',
|
251
|
+
'addresses' => [
|
252
|
+
{'street' => '1 St.', 'city' => '1 City', 'zip' => 11111},
|
253
|
+
{'street' => '2 St.', 'city' => '2 City', 'zip' => 22222}
|
254
|
+
]
|
255
|
+
},
|
256
|
+
{
|
257
|
+
'name' => 'Foo Inc.',
|
258
|
+
'addresses' => [
|
259
|
+
{'street' => '3 St.', 'city' => '3 City', 'zip' => 33333}
|
260
|
+
]
|
261
|
+
}
|
262
|
+
])
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'encodes a hash into bytes' do
|
266
|
+
bytes = encode_value(type, value)
|
267
|
+
bytes.should eql_bytes(
|
268
|
+
"\x00\x02" +
|
269
|
+
"\x00S" +
|
270
|
+
"\x00\x00\x00\tAcme Corp" +
|
271
|
+
"\x00\x00\x00B" +
|
272
|
+
"\x00\x00\x00\x02" +
|
273
|
+
"\x00\x00\x00\e" +
|
274
|
+
"\x00\x00\x00\x051 St." +
|
275
|
+
"\x00\x00\x00\x061 City" +
|
276
|
+
"\x00\x00\x00\x04\x00\x00+g" +
|
277
|
+
"\x00\x00\x00\e" +
|
278
|
+
"\x00\x00\x00\x052 St." +
|
279
|
+
"\x00\x00\x00\x062 City" +
|
280
|
+
"\x00\x00\x00\x04\x00\x00V\xCE" +
|
281
|
+
"\x003" +
|
282
|
+
"\x00\x00\x00\bFoo Inc." +
|
283
|
+
"\x00\x00\x00#" +
|
284
|
+
"\x00\x00\x00\x01" +
|
285
|
+
"\x00\x00\x00\e" +
|
286
|
+
"\x00\x00\x00\x053 St." +
|
287
|
+
"\x00\x00\x00\x063 City" +
|
288
|
+
"\x00\x00\x00\x04\x00\x00\x825"
|
289
|
+
)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'with custom types' do
|
295
|
+
let :type do
|
296
|
+
[:custom, 'com.example.CustomType']
|
297
|
+
end
|
298
|
+
|
299
|
+
let :value do
|
300
|
+
"\x01\x02\x03\x04\x05"
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'encodes a byte string into bytes' do
|
304
|
+
bytes = encode_value(type, value)
|
305
|
+
bytes.should eql_bytes("\x01\x02\x03\x04\x05")
|
182
306
|
end
|
183
307
|
end
|
184
308
|
end
|
@@ -73,6 +73,28 @@ module Cql
|
|
73
73
|
response.result_metadata.should be_nil
|
74
74
|
end
|
75
75
|
end
|
76
|
+
|
77
|
+
context 'with user defined types' do
|
78
|
+
let :buffer do
|
79
|
+
b = CqlByteBuffer.new
|
80
|
+
b << "\x00\x10\xC8\x90\r\x98\x06t\x97\x96\x94\xDA6\x13\xBB\x9D\xA5\xE1" # statement ID
|
81
|
+
b << "\x00\x00\x00\x00" # flags
|
82
|
+
b << "\x00\x00\x00\x00" # column count
|
83
|
+
b << "\x00\x00\x00\x01" # flags (global_tables_spec)
|
84
|
+
b << "\x00\x00\x00\x03" # column count
|
85
|
+
b << "\x00\x12user_defined_types\x00\x05users" # global_tables_spec
|
86
|
+
b << "\x00\x02id\x00\f" # col_spec (name + type)
|
87
|
+
b << "\x00\taddresses\x00!\x00\r\x00\x00\x01Morg.apache.cassandra.db.marshal.UserType(user_defined_types,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a69705f636f6465:org.apache.cassandra.db.marshal.Int32Type,70686f6e6573:org.apache.cassandra.db.marshal.SetType(org.apache.cassandra.db.marshal.UTF8Type))" # col_spec (name + type + extra type info)
|
88
|
+
b << "\x00\x04name\x00\r"
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'decodes the full type hierarchy' do
|
92
|
+
response = described_class.decode(2, buffer, buffer.length)
|
93
|
+
column_metadata = response.result_metadata[1]
|
94
|
+
type_description = column_metadata[3]
|
95
|
+
type_description.should == [:map, :varchar, [:udt, {'street' => :text, 'city' => :text, 'zip_code' => :int, 'phones' => [:set, :text]}]]
|
96
|
+
end
|
97
|
+
end
|
76
98
|
end
|
77
99
|
|
78
100
|
describe '#void?' do
|
@@ -292,6 +292,193 @@ module Cql
|
|
292
292
|
end
|
293
293
|
end
|
294
294
|
|
295
|
+
context 'with custom types' do
|
296
|
+
let :buffer do
|
297
|
+
b = CqlByteBuffer.new
|
298
|
+
b << "\x00\x00\x00\x01"
|
299
|
+
b << "\x00\x00\x00\x02"
|
300
|
+
b << "\x00\x12cql_rb_client_spec"
|
301
|
+
b << "\x00\x05users"
|
302
|
+
b << "\x00\x02id"
|
303
|
+
b << "\x00\r"
|
304
|
+
b << "\x00\x0Fprimary_address"
|
305
|
+
b << "\x00\x00\x00\x16com.example.CustomType"
|
306
|
+
b << "\x00\x00\x00\x01"
|
307
|
+
b << "\x00\x00\x00\x03sue"
|
308
|
+
b << "\x00\x00\x00\x05"
|
309
|
+
b << "\x01\x02\x03\x04\x05"
|
310
|
+
b
|
311
|
+
end
|
312
|
+
|
313
|
+
let :response do
|
314
|
+
described_class.decode(2, buffer, buffer.length)
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'decodes the type metadata' do
|
318
|
+
type_description = response.metadata[1]
|
319
|
+
type_description.should == [
|
320
|
+
'cql_rb_client_spec',
|
321
|
+
'users',
|
322
|
+
'primary_address',
|
323
|
+
[:custom, 'com.example.CustomType']
|
324
|
+
]
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'decodes the column value to a byte string' do
|
328
|
+
custom_value = response.rows[0]['primary_address']
|
329
|
+
custom_value.should == "\x01\x02\x03\x04\x05"
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
context 'with user defined types' do
|
334
|
+
let :buffer do
|
335
|
+
b = CqlByteBuffer.new
|
336
|
+
b << "\x00\x00\x00\x01"
|
337
|
+
b << "\x00\x00\x00\x02"
|
338
|
+
b << "\x00\x12cql_rb_client_spec"
|
339
|
+
b << "\x00\x05users"
|
340
|
+
b << "\x00\x02id"
|
341
|
+
b << "\x00\r"
|
342
|
+
b << "\x00\x0Fprimary_address"
|
343
|
+
b << "\x00\x00\x00\xE4org.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type)"
|
344
|
+
b << "\x00\x00\x00\x01"
|
345
|
+
b << "\x00\x00\x00\x03sue"
|
346
|
+
b << "\x00\x00\x00)"
|
347
|
+
b << "\x00\x00\x00\f123 Some St."
|
348
|
+
b << "\x00\x00\x00\rFrans Sanisco"
|
349
|
+
b << "\x00\x00\x00\x04\x00\x01*\xFF"
|
350
|
+
b
|
351
|
+
end
|
352
|
+
|
353
|
+
let :response do
|
354
|
+
described_class.decode(2, buffer, buffer.length)
|
355
|
+
end
|
356
|
+
|
357
|
+
it 'decodes the type metadata' do
|
358
|
+
type_description = response.metadata[1]
|
359
|
+
type_description.should == [
|
360
|
+
'cql_rb_client_spec',
|
361
|
+
'users',
|
362
|
+
'primary_address',
|
363
|
+
[:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]
|
364
|
+
]
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'decodes the column value to a hash' do
|
368
|
+
custom_value = response.rows[0]['primary_address']
|
369
|
+
custom_value.should eql(
|
370
|
+
'street' => '123 Some St.',
|
371
|
+
'city' => 'Frans Sanisco',
|
372
|
+
'zip' => 76543,
|
373
|
+
)
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
context 'with user defined types inside of collection types' do
|
378
|
+
let :buffer do
|
379
|
+
b = CqlByteBuffer.new
|
380
|
+
b << "\x00\x00\x00\x01"
|
381
|
+
b << "\x00\x00\x00\x01"
|
382
|
+
b << "\x00\x12cql_rb_client_spec"
|
383
|
+
b << "\x00\x05users"
|
384
|
+
b << "\x00\x13secondary_addresses"
|
385
|
+
b << "\x00!"
|
386
|
+
b << "\x00\r"
|
387
|
+
b << "\x00\x00\x00\xE4org.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type)"
|
388
|
+
b << "\x00\x00\x00\x01"
|
389
|
+
b << "\x00\x00\x00:"
|
390
|
+
b << "\x00\x01\x00\vsecret_lair"
|
391
|
+
b << "\x00)"
|
392
|
+
b << "\x00\x00\x00\x104 Some Other St."
|
393
|
+
b << "\x00\x00\x00\tGos Latos"
|
394
|
+
b << "\x00\x00\x00\x04\x00\x01Vf"
|
395
|
+
b
|
396
|
+
end
|
397
|
+
|
398
|
+
let :response do
|
399
|
+
described_class.decode(2, buffer, buffer.length)
|
400
|
+
end
|
401
|
+
|
402
|
+
it 'decodes the type metadata' do
|
403
|
+
type_description = response.metadata[0]
|
404
|
+
type_description.should == [
|
405
|
+
'cql_rb_client_spec',
|
406
|
+
'users',
|
407
|
+
'secondary_addresses',
|
408
|
+
[:map, :varchar, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]]
|
409
|
+
]
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'decodes the column value to a hash' do
|
413
|
+
custom_value = response.rows[0]['secondary_addresses']
|
414
|
+
custom_value.should eql(
|
415
|
+
'secret_lair' => {
|
416
|
+
'street' => '4 Some Other St.',
|
417
|
+
'city' => 'Gos Latos',
|
418
|
+
'zip' => 87654,
|
419
|
+
}
|
420
|
+
)
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
context 'with user defined types inside of other user defined types and collection types' do
|
425
|
+
let :buffer1 do
|
426
|
+
b = CqlByteBuffer.new
|
427
|
+
b << "\x00\x00\x00\x01"
|
428
|
+
b << "\x00\x00\x00\x01"
|
429
|
+
b << "\x00\x12cql_rb_client_spec"
|
430
|
+
b << "\x00\x05users"
|
431
|
+
b << "\x00\temployers"
|
432
|
+
b << "\x00\""
|
433
|
+
b << "\x00\x00\x01\x9Forg.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,636f6d70616e79,6e616d65:org.apache.cassandra.db.marshal.UTF8Type,616464726573736573:org.apache.cassandra.db.marshal.ListType(org.apache.cassandra.db.marshal.UserType(cql_rb_client_spec,61646472657373,737472656574:org.apache.cassandra.db.marshal.UTF8Type,63697479:org.apache.cassandra.db.marshal.UTF8Type,7a6970:org.apache.cassandra.db.marshal.Int32Type)))"
|
434
|
+
b << "\x00\x00\x00\x01"
|
435
|
+
b << "\x00\x00\x00\x8C"
|
436
|
+
b << "\x00\x02"
|
437
|
+
b << "\x00S"
|
438
|
+
b << "\x00\x00\x00\tAcme Corp"
|
439
|
+
b << "\x00\x00\x00B"
|
440
|
+
b << "\x00\x00\x00\x02"
|
441
|
+
b << "\x00\x00\x00\e"
|
442
|
+
b << "\x00\x00\x00\x051 St."
|
443
|
+
b << "\x00\x00\x00\x061 City"
|
444
|
+
b << "\x00\x00\x00\x04\x00\x00+g"
|
445
|
+
b << "\x00\x00\x00\e"
|
446
|
+
b << "\x00\x00\x00\x052 St."
|
447
|
+
b << "\x00\x00\x00\x062 City"
|
448
|
+
b << "\x00\x00\x00\x04\x00\x00V\xCE"
|
449
|
+
b << "\x003"
|
450
|
+
b << "\x00\x00\x00\bFoo Inc."
|
451
|
+
b << "\x00\x00\x00#"
|
452
|
+
b << "\x00\x00\x00\x01"
|
453
|
+
b << "\x00\x00\x00\e"
|
454
|
+
b << "\x00\x00\x00\x053 St."
|
455
|
+
b << "\x00\x00\x00\x063 City"
|
456
|
+
b << "\x00\x00\x00\x04\x00\x00\x825"
|
457
|
+
end
|
458
|
+
|
459
|
+
let :response do
|
460
|
+
described_class.decode(2, buffer1, buffer1.length)
|
461
|
+
end
|
462
|
+
|
463
|
+
it 'decodes the type metadata' do
|
464
|
+
type_description = response.metadata[0]
|
465
|
+
type_description.should == [
|
466
|
+
'cql_rb_client_spec',
|
467
|
+
'users',
|
468
|
+
'employers',
|
469
|
+
[:set, [:udt, {'name' => :text, 'addresses' => [:list, [:udt, {'street' => :text, 'city' => :text, 'zip' => :int}]]}]]
|
470
|
+
]
|
471
|
+
end
|
472
|
+
|
473
|
+
it 'decodes the column value to a hash' do
|
474
|
+
custom_value = response.rows[0]['employers']
|
475
|
+
custom_value.should eql(Set.new([
|
476
|
+
{'name' => 'Acme Corp', 'addresses' => [{'street' => '1 St.', 'city' => '1 City', 'zip' => 11111}, {'street' => '2 St.', 'city' => '2 City', 'zip' => 22222}]},
|
477
|
+
{'name' => 'Foo Inc.', 'addresses' => [{'street' => '3 St.', 'city' => '3 City', 'zip' => 33333}]}
|
478
|
+
]))
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
295
482
|
context 'with an unknown column type' do
|
296
483
|
it 'raises an error when encountering an unknown column type' do
|
297
484
|
buffer = CqlByteBuffer.new("\x00\x00\x00\x01\x00\x00\x00\x03\x00\ncql_rb_328\x00\x05users\x00\tuser_name\x00\xff\x00\x05email\x00\r\x00\bpassword\x00\r\x00\x00\x00\x00")
|