cql-rb 2.0.5 → 2.1.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '2.0.5'.freeze
4
+ VERSION = '2.1.0.pre0'.freeze
5
5
  end
@@ -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, 9, logger)
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, 9)
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, 5, {:data_center => 'dc0', :host_id => Uuid.new('00000000-0000-0000-0000-000000000000')}),
17
- FakeConnection.new('host1', 9042, 5, {:data_center => 'dc0', :host_id => Uuid.new('11111111-1111-1111-1111-111111111111')}),
18
- FakeConnection.new('host2', 9042, 5, {:data_center => 'dc0', :host_id => Uuid.new('22222222-2222-2222-2222-222222222222')}),
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, 42),
59
- FakeConnection.new('h1.example.com', 1234, 42),
60
- FakeConnection.new('h2.example.com', 1234, 42),
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, 5)
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 different data types' do
145
- specs = [
146
- [:ascii, 'test', "test"],
147
- [:bigint, 1012312312414123, "\x00\x03\x98\xB1S\xC8\x7F\xAB"],
148
- [:blob, "\xab\xcd", "\xab\xcd"],
149
- [:boolean, false, "\x00"],
150
- [:boolean, true, "\x01"],
151
- [:decimal, BigDecimal.new('1042342234234.123423435647768234'), "\x00\x00\x00\x12\r'\xFDI\xAD\x80f\x11g\xDCfV\xAA"],
152
- [:double, 10000.123123123, "@\xC3\x88\x0F\xC2\x7F\x9DU"],
153
- [:float, 12.13, "AB\x14{"],
154
- [:inet, IPAddr.new('8.8.8.8'), "\x08\x08\x08\x08"],
155
- [:inet, IPAddr.new('::1'), "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"],
156
- [:int, 12348098, "\x00\xBCj\xC2"],
157
- [:text, 'FOOBAR', 'FOOBAR'],
158
- [:timestamp, Time.at(1358013521.123), "\x00\x00\x01</\xE9\xDC\xE3"],
159
- [:timeuuid, Uuid.new('a4a70900-24e1-11df-8924-001ff3591711'), "\xA4\xA7\t\x00$\xE1\x11\xDF\x89$\x00\x1F\xF3Y\x17\x11"],
160
- [:uuid, Uuid.new('cfd66ccc-d857-4e90-b1e5-df98a3d40cd6'), "\xCF\xD6l\xCC\xD8WN\x90\xB1\xE5\xDF\x98\xA3\xD4\f\xD6"],
161
- [:varchar, 'hello', 'hello'],
162
- [:varint, 1231312312331283012830129382342342412123, "\x03\x9EV \x15\f\x03\x9DK\x18\xCDI\\$?\a["],
163
- [:varint, -234234234234, "\xC9v\x8D:\x86"],
164
- [[:list, :timestamp], [Time.at(1358013521.123)], "\x00\x01" + "\x00\x08\x00\x00\x01</\xE9\xDC\xE3"],
165
- [[:list, :boolean], [true, false, true, true], "\x00\x04" + "\x00\x01\x01" + "\x00\x01\x00" + "\x00\x01\x01" + "\x00\x01\x01"],
166
- [[: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"],
167
- [[: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"],
168
- [[: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"],
169
- [[:set, :varchar], Set.new(['foo', 'bar', 'baz']), "\x00\x03" + "\x00\x03foo" + "\x00\x03bar" + "\x00\x03baz"],
170
- [[: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"],
171
- [[:set, :varchar], ['foo', 'bar', 'baz'], "\x00\x03" + "\x00\x03foo" + "\x00\x03bar" + "\x00\x03baz"]
172
- ]
173
- specs.each do |type, value, expected_bytes|
174
- it "encodes #{type} values" do
175
- metadata = [['ks', 'tbl', 'id_column', type]]
176
- buffer = ExecuteRequest.new(id, metadata, [value], true, :one, nil, nil, nil, false).write(1, CqlByteBuffer.new)
177
- buffer.discard(2 + 16 + 2)
178
- length = buffer.read_int
179
- result_bytes = buffer.read(length)
180
- result_bytes.should eql_bytes(expected_bytes)
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")