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.
@@ -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")