cql-rb 1.0.0.pre5 → 1.0.0.pre6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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