cql-rb 1.0.0.pre5 → 1.0.0.pre6

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.
@@ -16,15 +16,13 @@ module Cql
16
16
  def write_string(buffer, str)
17
17
  str = str.to_s
18
18
  buffer << [str.bytesize].pack(Formats::SHORT_FORMAT)
19
- buffer << binary_cast(str)
20
- buffer.force_encoding(::Encoding::BINARY)
19
+ buffer << str
21
20
  buffer
22
21
  end
23
22
 
24
23
  def write_long_string(buffer, str)
25
24
  buffer << [str.bytesize].pack(Formats::INT_FORMAT)
26
- buffer << binary_cast(str)
27
- buffer.force_encoding(::Encoding::BINARY)
25
+ buffer << str
28
26
  buffer
29
27
  end
30
28
 
@@ -43,22 +41,20 @@ module Cql
43
41
  def write_bytes(buffer, bytes)
44
42
  if bytes
45
43
  write_int(buffer, bytes.bytesize)
46
- buffer << binary_cast(bytes)
44
+ buffer << bytes
47
45
  else
48
46
  write_int(buffer, -1)
49
47
  end
50
- buffer.force_encoding(::Encoding::BINARY)
51
48
  buffer
52
49
  end
53
50
 
54
51
  def write_short_bytes(buffer, bytes)
55
52
  if bytes
56
53
  write_short(buffer, bytes.bytesize)
57
- buffer << binary_cast(bytes)
54
+ buffer << bytes
58
55
  else
59
56
  write_short(buffer, -1)
60
57
  end
61
- buffer.force_encoding(::Encoding::BINARY)
62
58
  buffer
63
59
  end
64
60
 
@@ -109,13 +105,6 @@ module Cql
109
105
  def write_float(buffer, n)
110
106
  buffer << [n].pack(Formats::FLOAT_FORMAT)
111
107
  end
112
-
113
- private
114
-
115
- def binary_cast(str)
116
- return str if str.ascii_only?
117
- str.dup.force_encoding(::Encoding::BINARY)
118
- end
119
108
  end
120
109
  end
121
110
  end
@@ -10,9 +10,9 @@ module Cql
10
10
  end
11
11
 
12
12
  def write(io)
13
- buffer = [1, 0, @stream_id, @body.opcode, 0].pack(Formats::HEADER_FORMAT)
14
- buffer = @body.write(buffer)
15
- buffer[4, 4] = [buffer.length - 8].pack(Formats::INT_FORMAT)
13
+ buffer = @body.write(ByteBuffer.new)
14
+ io << [1, 0, @stream_id, @body.opcode].pack(Formats::HEADER_FORMAT)
15
+ io << [buffer.length].pack(Formats::INT_FORMAT)
16
16
  io << buffer
17
17
  end
18
18
  end
@@ -8,7 +8,7 @@ require 'set'
8
8
  module Cql
9
9
  module Protocol
10
10
  class ResponseFrame
11
- def initialize(buffer='')
11
+ def initialize(buffer=ByteBuffer.new)
12
12
  @headers = FrameHeaders.new(buffer)
13
13
  check_complete!
14
14
  end
@@ -87,7 +87,11 @@ module Cql
87
87
 
88
88
  def check_complete!
89
89
  if @buffer.length >= 8
90
- @protocol_version, @flags, @stream_id, @opcode, @length = @buffer.slice!(0, 8).unpack(Formats::HEADER_FORMAT)
90
+ @protocol_version = @buffer.read_byte(true)
91
+ @flags = @buffer.read_byte(true)
92
+ @stream_id = @buffer.read_byte(true)
93
+ @opcode = @buffer.read_byte(true)
94
+ @length = @buffer.read_int
91
95
  raise UnsupportedFrameTypeError, 'Request frames are not supported' if @protocol_version > 0
92
96
  @protocol_version &= 0x7f
93
97
  end
@@ -120,7 +124,7 @@ module Cql
120
124
  extra_length = @buffer.length - @length
121
125
  @response = @type.decode!(@buffer)
122
126
  if @buffer.length > extra_length
123
- @buffer.slice!(0, @buffer.length - extra_length)
127
+ @buffer.discard(@buffer.length - extra_length)
124
128
  end
125
129
  end
126
130
  end
@@ -303,33 +307,38 @@ module Cql
303
307
 
304
308
  private
305
309
 
310
+ COLUMN_TYPES = [
311
+ nil,
312
+ :ascii,
313
+ :bigint,
314
+ :blob,
315
+ :boolean,
316
+ :counter,
317
+ :decimal,
318
+ :double,
319
+ :float,
320
+ :int,
321
+ :text,
322
+ :timestamp,
323
+ :uuid,
324
+ :varchar,
325
+ :varint,
326
+ :timeuuid,
327
+ :inet,
328
+ ].freeze
329
+
306
330
  def self.read_column_type!(buffer)
307
331
  id, type = read_option!(buffer) do |id, b|
308
- case id
309
- when 0x01 then :ascii
310
- when 0x02 then :bigint
311
- when 0x03 then :blob
312
- when 0x04 then :boolean
313
- when 0x05 then :counter
314
- when 0x06 then :decimal
315
- when 0x07 then :double
316
- when 0x08 then :float
317
- when 0x09 then :int
318
- # when 0x0a then :text
319
- when 0x0b then :timestamp
320
- when 0x0c then :uuid
321
- when 0x0d then :varchar
322
- when 0x0e then :varint
323
- when 0x0f then :timeuuid
324
- when 0x10 then :inet
325
- when 0x20
332
+ if id > 0 && id <= 0x10
333
+ COLUMN_TYPES[id]
334
+ elsif id == 0x20
326
335
  sub_type = read_column_type!(buffer)
327
336
  [:list, sub_type]
328
- when 0x21
337
+ elsif id == 0x21
329
338
  key_type = read_column_type!(buffer)
330
339
  value_type = read_column_type!(buffer)
331
340
  [:map, key_type, value_type]
332
- when 0x22
341
+ elsif id == 0x22
333
342
  sub_type = read_column_type!(buffer)
334
343
  [:set, sub_type]
335
344
  else
@@ -360,75 +369,159 @@ module Cql
360
369
  end
361
370
  end
362
371
 
363
- def self.convert_type(bytes, type)
364
- return nil unless bytes
365
- case type
366
- when :ascii
367
- bytes.force_encoding(::Encoding::ASCII)
368
- when :bigint
369
- read_long!(bytes)
370
- when :blob
371
- bytes
372
- when :boolean
373
- bytes == Constants::TRUE_BYTE
374
- when :counter
375
- read_long!(bytes)
376
- when :decimal
377
- read_decimal!(bytes)
378
- when :double
379
- read_double!(bytes)
380
- when :float
381
- read_float!(bytes)
382
- when :int
383
- read_int!(bytes)
384
- when :timestamp
385
- timestamp = read_long!(bytes)
386
- Time.at(timestamp/1000.0)
387
- when :varchar, :text
388
- bytes.force_encoding(::Encoding::UTF_8)
389
- when :varint
390
- read_varint!(bytes)
391
- when :timeuuid, :uuid
392
- read_uuid!(bytes)
393
- when :inet
394
- IPAddr.new_ntoh(bytes)
395
- when Array
396
- case type.first
397
- when :list
398
- list = []
399
- size = read_short!(bytes)
400
- size.times do
401
- list << convert_type(read_short_bytes!(bytes), type.last)
402
- end
403
- list
404
- when :map
405
- map = {}
406
- size = read_short!(bytes)
407
- size.times do
408
- key = convert_type(read_short_bytes!(bytes), type[1])
409
- value = convert_type(read_short_bytes!(bytes), type[2])
410
- map[key] = value
411
- end
412
- map
413
- when :set
414
- set = Set.new
415
- size = read_short!(bytes)
416
- size.times do
417
- set << convert_type(read_short_bytes!(bytes), type.last)
372
+ class TypeConverter
373
+ include Decoding
374
+
375
+ def initialize
376
+ @conversions = conversions
377
+ end
378
+
379
+ def convert_type(buffer, type, size_bytes=4)
380
+ return nil if buffer.empty?
381
+ case type
382
+ when Array
383
+ buffer.discard(size_bytes)
384
+ case type.first
385
+ when :list
386
+ convert_list(buffer, @conversions[type[1]])
387
+ when :map
388
+ convert_map(buffer, @conversions[type[1]], @conversions[type[2]])
389
+ when :set
390
+ convert_set(buffer, @conversions[type[1]])
418
391
  end
419
- set
392
+ else
393
+ @conversions[type].call(buffer, size_bytes)
394
+ end
395
+ end
396
+
397
+ def conversions
398
+ {
399
+ :ascii => method(:convert_ascii),
400
+ :bigint => method(:convert_bigint),
401
+ :blob => method(:convert_blob),
402
+ :boolean => method(:convert_boolean),
403
+ :counter => method(:convert_bigint),
404
+ :decimal => method(:convert_decimal),
405
+ :double => method(:convert_double),
406
+ :float => method(:convert_float),
407
+ :int => method(:convert_int),
408
+ :timestamp => method(:convert_timestamp),
409
+ :varchar => method(:convert_varchar),
410
+ :text => method(:convert_varchar),
411
+ :varint => method(:convert_varint),
412
+ :timeuuid => method(:convert_uuid),
413
+ :uuid => method(:convert_uuid),
414
+ :inet => method(:convert_inet),
415
+ }
416
+ end
417
+
418
+ def convert_ascii(buffer, size_bytes)
419
+ bytes = size_bytes == 4 ? read_bytes!(buffer) : read_short_bytes!(buffer)
420
+ bytes ? bytes.force_encoding(::Encoding::ASCII) : nil
421
+ end
422
+
423
+ def convert_bigint(buffer, size_bytes)
424
+ buffer.discard(size_bytes)
425
+ read_long!(buffer)
426
+ end
427
+
428
+ def convert_blob(buffer, size_bytes)
429
+ bytes = size_bytes == 4 ? read_bytes!(buffer) : read_short_bytes!(buffer)
430
+ bytes ? bytes : nil
431
+ end
432
+
433
+ def convert_boolean(buffer, size_bytes)
434
+ buffer.discard(size_bytes)
435
+ buffer.read(1) == Constants::TRUE_BYTE
436
+ end
437
+
438
+ def convert_counter(buffer, size_bytes)
439
+ buffer.discard(size_bytes)
440
+ read_long!(buffer)
441
+ end
442
+
443
+ def convert_decimal(buffer, size_bytes)
444
+ read_decimal!(buffer, buffer.read_int)
445
+ end
446
+
447
+ def convert_double(buffer, size_bytes)
448
+ buffer.discard(size_bytes)
449
+ read_double!(buffer)
450
+ end
451
+
452
+ def convert_float(buffer, size_bytes)
453
+ buffer.discard(size_bytes)
454
+ read_float!(buffer)
455
+ end
456
+
457
+ def convert_int(buffer, size_bytes)
458
+ buffer.discard(size_bytes)
459
+ read_int!(buffer)
460
+ end
461
+
462
+ def convert_timestamp(buffer, size_bytes)
463
+ buffer.discard(size_bytes)
464
+ timestamp = read_long!(buffer)
465
+ Time.at(timestamp/1000.0)
466
+ end
467
+
468
+ def convert_varchar(buffer, size_bytes)
469
+ bytes = size_bytes == 4 ? read_bytes!(buffer) : read_short_bytes!(buffer)
470
+ bytes ? bytes.force_encoding(::Encoding::UTF_8) : nil
471
+ end
472
+
473
+ def convert_varint(buffer, size_bytes)
474
+ read_varint!(buffer, buffer.read_int)
475
+ end
476
+
477
+ def convert_uuid(buffer, size_bytes)
478
+ buffer.discard(size_bytes)
479
+ read_uuid!(buffer)
480
+ end
481
+
482
+ def convert_inet(buffer, size_bytes)
483
+ size = size_bytes == 4 ? buffer.read_int : buffer.read_short
484
+ IPAddr.new_ntoh(buffer.read(size))
485
+ end
486
+
487
+ def convert_list(buffer, value_converter)
488
+ list = []
489
+ size = buffer.read_short
490
+ size.times do
491
+ list << value_converter.call(buffer, 2)
492
+ end
493
+ list
494
+ end
495
+
496
+ def convert_map(buffer, key_converter, value_converter)
497
+ map = {}
498
+ size = buffer.read_short
499
+ size.times do
500
+ key = key_converter.call(buffer, 2)
501
+ value = value_converter.call(buffer, 2)
502
+ map[key] = value
503
+ end
504
+ map
505
+ end
506
+
507
+ def convert_set(buffer, value_converter)
508
+ set = Set.new
509
+ size = buffer.read_short
510
+ size.times do
511
+ set << value_converter.call(buffer, 2)
420
512
  end
513
+ set
421
514
  end
422
515
  end
423
516
 
424
517
  def self.read_rows!(buffer, column_specs)
518
+ type_converter = TypeConverter.new
425
519
  rows_count = read_int!(buffer)
426
520
  rows = []
427
521
  rows_count.times do |row_index|
428
522
  row = {}
429
523
  column_specs.each do |column_spec|
430
- column_value = read_bytes!(buffer)
431
- row[column_spec[2]] = convert_type(column_value, column_spec[3])
524
+ row[column_spec[2]] = type_converter.convert_type(buffer, column_spec[3])
432
525
  end
433
526
  rows << row
434
527
  end
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Cql
4
- VERSION = '1.0.0.pre5'.freeze
4
+ VERSION = '1.0.0.pre6'.freeze
5
5
  end
@@ -0,0 +1,299 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+
6
+ module Cql
7
+ describe ByteBuffer do
8
+ let :buffer do
9
+ described_class.new
10
+ end
11
+
12
+ describe '#initialize' do
13
+ it 'can be inititialized empty' do
14
+ described_class.new.should be_empty
15
+ end
16
+
17
+ it 'can be initialized with bytes' do
18
+ described_class.new('hello').length.should == 5
19
+ end
20
+ end
21
+
22
+ describe '#length/#size/#bytesize' do
23
+ it 'returns the number of bytes in the buffer' do
24
+ buffer << 'foo'
25
+ buffer.length.should == 3
26
+ end
27
+
28
+ it 'is zero initially' do
29
+ buffer.length.should == 0
30
+ end
31
+
32
+ it 'is aliased as #size' do
33
+ buffer << 'foo'
34
+ buffer.size.should == 3
35
+ end
36
+
37
+ it 'is aliased as #bytesize' do
38
+ buffer << 'foo'
39
+ buffer.bytesize.should == 3
40
+ end
41
+ end
42
+
43
+ describe '#empty?' do
44
+ it 'is true initially' do
45
+ buffer.should be_empty
46
+ end
47
+
48
+ it 'is false when there are bytes in the buffer' do
49
+ buffer << 'foo'
50
+ buffer.should_not be_empty
51
+ end
52
+ end
53
+
54
+ describe '#append/#<<' do
55
+ it 'adds bytes to the buffer' do
56
+ buffer.append('foo')
57
+ buffer.should_not be_empty
58
+ end
59
+
60
+ it 'can be used as <<' do
61
+ buffer << 'foo'
62
+ buffer.should_not be_empty
63
+ end
64
+
65
+ it 'returns itself' do
66
+ buffer.append('foo').should eql(buffer)
67
+ end
68
+
69
+ it 'stores its bytes as binary' do
70
+ buffer.append('hällö').length.should == 7
71
+ buffer.to_s.encoding.should == ::Encoding::BINARY
72
+ end
73
+
74
+ it 'handles appending with multibyte strings' do
75
+ buffer.append('hello')
76
+ buffer.append('würld')
77
+ buffer.to_s.should == 'hellowürld'.force_encoding(::Encoding::BINARY)
78
+ end
79
+
80
+ it 'handles appending with another byte buffer' do
81
+ buffer.append('hello ').append(ByteBuffer.new('world'))
82
+ buffer.to_s.should == 'hello world'
83
+ end
84
+ end
85
+
86
+ describe '#eql?' do
87
+ it 'is equal to another buffer with the same contents' do
88
+ b1 = described_class.new
89
+ b2 = described_class.new
90
+ b1.append('foo')
91
+ b2.append('foo')
92
+ b1.should eql(b2)
93
+ end
94
+
95
+ it 'is not equal to another buffer with other contents' do
96
+ b1 = described_class.new
97
+ b2 = described_class.new
98
+ b1.append('foo')
99
+ b2.append('bar')
100
+ b1.should_not eql(b2)
101
+ end
102
+
103
+ it 'is aliased as #==' do
104
+ b1 = described_class.new
105
+ b2 = described_class.new
106
+ b1.append('foo')
107
+ b2.append('foo')
108
+ b1.should == b2
109
+ end
110
+
111
+ it 'is equal to another buffer when both are empty' do
112
+ b1 = described_class.new
113
+ b2 = described_class.new
114
+ b1.should eql(b2)
115
+ end
116
+ end
117
+
118
+ describe '#hash' do
119
+ it 'has the same hash code as another buffer with the same contents' do
120
+ b1 = described_class.new
121
+ b2 = described_class.new
122
+ b1.append('foo')
123
+ b2.append('foo')
124
+ b1.hash.should == b2.hash
125
+ end
126
+
127
+ it 'is not equal to the hash code of another buffer with other contents' do
128
+ b1 = described_class.new
129
+ b2 = described_class.new
130
+ b1.append('foo')
131
+ b2.append('bar')
132
+ b1.hash.should_not == b2.hash
133
+ end
134
+
135
+ it 'is equal to the hash code of another buffer when both are empty' do
136
+ b1 = described_class.new
137
+ b2 = described_class.new
138
+ b1.hash.should == b2.hash
139
+ end
140
+ end
141
+
142
+ describe '#to_s' do
143
+ it 'returns the bytes' do
144
+ buffer.append('hello world').to_s.should == 'hello world'
145
+ end
146
+ end
147
+
148
+ describe '#to_str' do
149
+ it 'returns the bytes' do
150
+ buffer.append('hello world').to_str.should == 'hello world'
151
+ end
152
+ end
153
+
154
+ describe '#inspect' do
155
+ it 'returns the bytes wrapped in ByteBuffer(...)' do
156
+ buffer.append("\xca\xfe")
157
+ buffer.inspect.should == '#<Cql::ByteBuffer: "\xCA\xFE">'
158
+ end
159
+ end
160
+
161
+ describe '#discard' do
162
+ it 'discards the specified number of bytes from the front of the buffer' do
163
+ buffer.append('hello world')
164
+ buffer.discard(4)
165
+ buffer.should == ByteBuffer.new('o world')
166
+ end
167
+
168
+ it 'returns the byte buffer' do
169
+ buffer.append('hello world')
170
+ buffer.discard(4).should == ByteBuffer.new('o world')
171
+ end
172
+
173
+ it 'raises an error if the number of bytes in the buffer is fewer than the number to discard' do
174
+ expect { buffer.discard(1) }.to raise_error(RangeError)
175
+ buffer.append('hello')
176
+ expect { buffer.discard(7) }.to raise_error(RangeError)
177
+ end
178
+ end
179
+
180
+ describe '#read' do
181
+ it 'returns the specified number of bytes, as a string' do
182
+ buffer.append('hello')
183
+ buffer.read(4).should == 'hell'
184
+ end
185
+
186
+ it 'removes the bytes from the buffer' do
187
+ buffer.append('hello')
188
+ buffer.read(3)
189
+ buffer.should == ByteBuffer.new('lo')
190
+ buffer.read(2).should == 'lo'
191
+ end
192
+
193
+ it 'raises an error if there are not enough bytes' do
194
+ buffer.append('hello')
195
+ expect { buffer.read(23423543) }.to raise_error(RangeError)
196
+ expect { buffer.discard(5).read(1) }.to raise_error(RangeError)
197
+ end
198
+
199
+ it 'returns a string with binary encoding' do
200
+ buffer.append('hello')
201
+ buffer.read(4).encoding.should == ::Encoding::BINARY
202
+ buffer.append('∆')
203
+ buffer.read(2).encoding.should == ::Encoding::BINARY
204
+ end
205
+ end
206
+
207
+ describe '#read_int' do
208
+ it 'returns the first four bytes interpreted as an int' do
209
+ buffer.append("\xca\xfe\xba\xbe\x01")
210
+ buffer.read_int.should == 0xcafebabe
211
+ end
212
+
213
+ it 'removes the bytes from the buffer' do
214
+ buffer.append("\xca\xfe\xba\xbe\x01")
215
+ buffer.read_int
216
+ buffer.should == ByteBuffer.new("\x01")
217
+ end
218
+
219
+ it 'raises an error if there are not enough bytes' do
220
+ buffer.append("\xca\xfe\xba")
221
+ expect { buffer.read_int }.to raise_error(RangeError)
222
+ end
223
+ end
224
+
225
+ describe '#read_short' do
226
+ it 'returns the first two bytes interpreted as a short' do
227
+ buffer.append("\xca\xfe\x01")
228
+ buffer.read_short.should == 0xcafe
229
+ end
230
+
231
+ it 'removes the bytes from the buffer' do
232
+ buffer.append("\xca\xfe\x01")
233
+ buffer.read_short
234
+ buffer.should == ByteBuffer.new("\x01")
235
+ end
236
+
237
+ it 'raises an error if there are not enough bytes' do
238
+ buffer.append("\xca")
239
+ expect { buffer.read_short }.to raise_error(RangeError)
240
+ end
241
+ end
242
+
243
+ describe '#read_byte' do
244
+ it 'returns the first bytes interpreted as an int' do
245
+ buffer.append("\x10\x01")
246
+ buffer.read_byte.should == 0x10
247
+ buffer.read_byte.should == 0x01
248
+ end
249
+
250
+ it 'removes the byte from the buffer' do
251
+ buffer.append("\x10\x01")
252
+ buffer.read_byte
253
+ buffer.should == ByteBuffer.new("\x01")
254
+ end
255
+
256
+ it 'raises an error if there are no bytes' do
257
+ expect { buffer.read_byte }.to raise_error(RangeError)
258
+ end
259
+
260
+ it 'can interpret the byte as signed' do
261
+ buffer.append("\x81\x02")
262
+ buffer.read_byte(true).should == -127
263
+ buffer.read_byte(true).should == 2
264
+ end
265
+ end
266
+
267
+ describe '#dup' do
268
+ it 'returns a copy' do
269
+ buffer.append('hello world')
270
+ copy = buffer.dup
271
+ copy.should eql(buffer)
272
+ end
273
+
274
+ it 'returns a copy which can be modified without modifying the original' do
275
+ buffer.append('hello world')
276
+ copy = buffer.dup
277
+ copy.append('goodbye')
278
+ copy.should_not eql(buffer)
279
+ end
280
+ end
281
+
282
+ context 'when reading and appending' do
283
+ it 'handles heavy churn' do
284
+ 1000.times do
285
+ buffer.append('x' * 6)
286
+ buffer.read_byte
287
+ buffer.append('y')
288
+ buffer.read_int
289
+ buffer.read_short
290
+ buffer.append('z' * 4)
291
+ buffer.read_byte
292
+ buffer.append('z')
293
+ buffer.read_int
294
+ buffer.should be_empty
295
+ end
296
+ end
297
+ end
298
+ end
299
+ end