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.
- data/README.md +61 -46
- data/lib/cql.rb +1 -0
- data/lib/cql/byte_buffer.rb +138 -0
- data/lib/cql/future.rb +3 -0
- data/lib/cql/io/node_connection.rb +29 -26
- data/lib/cql/protocol.rb +1 -1
- data/lib/cql/protocol/decoding.rb +41 -33
- data/lib/cql/protocol/encoding.rb +4 -15
- data/lib/cql/protocol/request_frame.rb +3 -3
- data/lib/cql/protocol/response_frame.rb +174 -81
- data/lib/cql/version.rb +1 -1
- data/spec/cql/byte_buffer_spec.rb +299 -0
- data/spec/cql/io/io_reactor_spec.rb +13 -16
- data/spec/cql/protocol/decoding_spec.rb +128 -91
- data/spec/cql/protocol/encoding_spec.rb +8 -57
- data/spec/cql/protocol/request_frame_spec.rb +10 -5
- data/spec/cql/protocol/response_frame_spec.rb +31 -15
- data/spec/integration/client_spec.rb +4 -0
- data/spec/integration/protocol_spec.rb +1 -1
- data/spec/integration/regression_spec.rb +41 -14
- data/spec/spec_helper.rb +13 -6
- data/spec/support/bytes_helper.rb +1 -1
- data/spec/support/fake_server.rb +10 -2
- metadata +5 -2
data/README.md
CHANGED
@@ -18,72 +18,85 @@ The native transport protocol (sometimes called binary protocol, or CQL protocol
|
|
18
18
|
|
19
19
|
# Quick start
|
20
20
|
|
21
|
-
|
21
|
+
```ruby
|
22
|
+
require 'cql'
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
24
|
+
client = Cql::Client.connect(host: 'cassandra.example.com')
|
25
|
+
client.use('system')
|
26
|
+
rows = client.execute('SELECT keyspace_name, columnfamily_name FROM schema_columnfamilies')
|
27
|
+
rows.each do |row|
|
28
|
+
puts "The keyspace #{row['keyspace_name']} has a table called #{row['columnfamily_name']}""
|
29
|
+
end
|
30
|
+
```
|
29
31
|
|
30
32
|
when you're done you can call `#close` to disconnect from Cassandra. You can connect to multiple Cassandra nodes by passing multiple comma separated host names to the `:host` option.
|
31
33
|
|
32
34
|
## Changing keyspaces
|
33
35
|
|
34
|
-
|
36
|
+
```ruby
|
37
|
+
client.use('measurements')
|
38
|
+
```
|
35
39
|
|
36
40
|
or using CQL:
|
37
41
|
|
38
|
-
|
42
|
+
```ruby
|
43
|
+
client.execute('USE measurements')
|
44
|
+
```
|
39
45
|
|
40
46
|
## Running queries
|
41
47
|
|
42
48
|
You run CQL statements by passing them to `#execute`. Most statements don't have any result and the call will return nil.
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
client.execute("UPDATE events SET description = 'Oh, my' WHERE id = 13126")
|
50
|
+
```ruby
|
51
|
+
client.execute("INSERT INTO events (id, date, description) VALUES (23462, '2013-02-24T10:14:23+0000', 'Rang bell, ate food')")
|
47
52
|
|
53
|
+
client.execute("UPDATE events SET description = 'Oh, my' WHERE id = 13126")
|
54
|
+
```
|
48
55
|
|
49
56
|
If the CQL statement passed to `#execute` returns a result (e.g. it's a `SELECT` statement) the call returns an enumerable of rows:
|
50
57
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
58
|
+
```ruby
|
59
|
+
rows = client.execute('SELECT date, description FROM events')
|
60
|
+
rows.each do |row|
|
61
|
+
row.each do |key, value|
|
62
|
+
puts "#{key} = #{value}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
57
66
|
|
58
67
|
The enumerable also has an accessor called `metadata` which returns a description of the rows and columns:
|
59
68
|
|
60
|
-
|
61
|
-
|
69
|
+
```ruby
|
70
|
+
rows = client.execute('SELECT date, description FROM events'
|
71
|
+
rows.metadata['date'].type # => :date
|
72
|
+
```
|
62
73
|
|
63
74
|
## Creating keyspaces and tables
|
64
75
|
|
65
76
|
There is no special facility for creating keyspaces and tables, they are created by executing CQL:
|
66
77
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
78
|
+
```ruby
|
79
|
+
keyspace_definition = <<-KSDEF
|
80
|
+
CREATE KEYSPACE measurements
|
81
|
+
WITH replication = {
|
82
|
+
'class': 'SimpleStrategy',
|
83
|
+
'replication_factor': 3
|
84
|
+
}
|
85
|
+
KSDEF
|
86
|
+
|
87
|
+
table_definition = <<-TABLEDEF
|
88
|
+
CREATE TABLE events (
|
89
|
+
id INT,
|
90
|
+
date DATE,
|
91
|
+
comment VARCHAR,
|
92
|
+
PRIMARY KEY (id)
|
93
|
+
)
|
94
|
+
TABLEDEF
|
95
|
+
|
96
|
+
client.execute(keyspace_definition)
|
97
|
+
client.use(measurements)
|
98
|
+
client.execute(table_definition)
|
99
|
+
```
|
87
100
|
|
88
101
|
You can also `ALTER` keyspaces and tables.
|
89
102
|
|
@@ -91,8 +104,10 @@ You can also `ALTER` keyspaces and tables.
|
|
91
104
|
|
92
105
|
The driver supports prepared statements. Use `#prepare` to create a statement object, and then call `#execute` on that object to run a statement. You must supply values for all bound parameters when you call `#execute`.
|
93
106
|
|
94
|
-
|
95
|
-
|
107
|
+
```ruby
|
108
|
+
statement = client.prepare('SELECT date, description FROM events WHERE id = ?')
|
109
|
+
rows = statement.execute(1235)
|
110
|
+
```
|
96
111
|
|
97
112
|
A prepared statement can be run many times, but the CQL parsing will only be done once. Use prepared statements for queries you run over and over again.
|
98
113
|
|
@@ -104,7 +119,9 @@ At this time prepared statements are local to a single connection. Even if you c
|
|
104
119
|
|
105
120
|
The `#execute` method supports setting the desired consistency level for the statement:
|
106
121
|
|
107
|
-
|
122
|
+
```ruby
|
123
|
+
client.execute('SELECT * FROM peers', :local_quorum)
|
124
|
+
```
|
108
125
|
|
109
126
|
The possible values are:
|
110
127
|
|
@@ -129,11 +146,9 @@ Read more about CQL3 in the [CQL3 syntax documentation](https://github.com/apach
|
|
129
146
|
|
130
147
|
# Known bugs & limitations
|
131
148
|
|
132
|
-
*
|
149
|
+
* There are still edge cases around connection errors, and there is no automatic reconnection.
|
133
150
|
* JRuby 1.6.8 is not supported, although it should be. The only known issue is that connection failures aren't handled gracefully.
|
134
151
|
* No automatic peer discovery.
|
135
|
-
* You can't specify consistency level when executing prepared statements.
|
136
|
-
* Authentication is not supported.
|
137
152
|
* Compression is not supported.
|
138
153
|
* Large results are buffered in memory until the whole response has been loaded, the protocol makes it possible to start to deliver rows to the client code as soon as the metadata is loaded, but this is not supported yet.
|
139
154
|
* There is no cluster introspection utilities (like the `DESCRIBE` commands in `cqlsh`).
|
data/lib/cql.rb
CHANGED
@@ -0,0 +1,138 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cql
|
4
|
+
class ByteBuffer
|
5
|
+
def initialize(initial_bytes='')
|
6
|
+
@read_buffer = ''
|
7
|
+
@write_buffer = ''
|
8
|
+
@offset = 0
|
9
|
+
@length = 0
|
10
|
+
append(initial_bytes) unless initial_bytes.empty?
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :length
|
14
|
+
alias_method :size, :length
|
15
|
+
alias_method :bytesize, :length
|
16
|
+
|
17
|
+
def empty?
|
18
|
+
length == 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def append(bytes)
|
22
|
+
bytes = bytes.to_s
|
23
|
+
unless bytes.ascii_only?
|
24
|
+
bytes = bytes.dup.force_encoding(::Encoding::BINARY)
|
25
|
+
end
|
26
|
+
retag = @write_buffer.empty?
|
27
|
+
@write_buffer << bytes
|
28
|
+
@write_buffer.force_encoding(::Encoding::BINARY) if retag
|
29
|
+
@length += bytes.bytesize
|
30
|
+
self
|
31
|
+
end
|
32
|
+
alias_method :<<, :append
|
33
|
+
|
34
|
+
def discard(n)
|
35
|
+
raise RangeError, "#{n} bytes to discard but only #{@length} available" if @length < n
|
36
|
+
@offset += n
|
37
|
+
@length -= n
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def read(n)
|
42
|
+
raise RangeError, "#{n} bytes required but only #{@length} available" if @length < n
|
43
|
+
if @offset >= @read_buffer.bytesize
|
44
|
+
swap_buffers
|
45
|
+
end
|
46
|
+
if @offset + n > @read_buffer.bytesize
|
47
|
+
s = read(@read_buffer.bytesize - @offset)
|
48
|
+
s << read(n - s.bytesize)
|
49
|
+
s
|
50
|
+
else
|
51
|
+
s = @read_buffer[@offset, n]
|
52
|
+
@offset += n
|
53
|
+
@length -= n
|
54
|
+
s
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_int
|
59
|
+
raise RangeError, "4 bytes required to read an int, but only #{@length} available" if @length < 4
|
60
|
+
if @offset >= @read_buffer.bytesize
|
61
|
+
swap_buffers
|
62
|
+
end
|
63
|
+
if @read_buffer.bytesize >= @offset + 4
|
64
|
+
i0 = @read_buffer.getbyte(@offset + 0)
|
65
|
+
i1 = @read_buffer.getbyte(@offset + 1)
|
66
|
+
i2 = @read_buffer.getbyte(@offset + 2)
|
67
|
+
i3 = @read_buffer.getbyte(@offset + 3)
|
68
|
+
@offset += 4
|
69
|
+
@length -= 4
|
70
|
+
else
|
71
|
+
i0 = read_byte
|
72
|
+
i1 = read_byte
|
73
|
+
i2 = read_byte
|
74
|
+
i3 = read_byte
|
75
|
+
end
|
76
|
+
(i0 << 24) | (i1 << 16) | (i2 << 8) | i3
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_short
|
80
|
+
raise RangeError, "2 bytes required to read a short, but only #{@length} available" if @length < 2
|
81
|
+
if @offset >= @read_buffer.bytesize
|
82
|
+
swap_buffers
|
83
|
+
end
|
84
|
+
if @read_buffer.bytesize >= @offset + 2
|
85
|
+
i0 = @read_buffer.getbyte(@offset + 0)
|
86
|
+
i1 = @read_buffer.getbyte(@offset + 1)
|
87
|
+
@offset += 2
|
88
|
+
@length -= 2
|
89
|
+
else
|
90
|
+
i0 = read_byte
|
91
|
+
i1 = read_byte
|
92
|
+
end
|
93
|
+
(i0 << 8) | i1
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_byte(signed=false)
|
97
|
+
raise RangeError, "No bytes available to read byte" if empty?
|
98
|
+
if @offset >= @read_buffer.bytesize
|
99
|
+
swap_buffers
|
100
|
+
end
|
101
|
+
b = @read_buffer.getbyte(@offset)
|
102
|
+
b = (b & 0x7f) - (b & 0x80) if signed
|
103
|
+
@offset += 1
|
104
|
+
@length -= 1
|
105
|
+
b
|
106
|
+
end
|
107
|
+
|
108
|
+
def eql?(other)
|
109
|
+
self.to_str.eql?(other.to_str)
|
110
|
+
end
|
111
|
+
alias_method :==, :eql?
|
112
|
+
|
113
|
+
def hash
|
114
|
+
to_str.hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def dup
|
118
|
+
self.class.new(to_str)
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_str
|
122
|
+
(@read_buffer + @write_buffer)[@offset, @length]
|
123
|
+
end
|
124
|
+
alias_method :to_s, :to_str
|
125
|
+
|
126
|
+
def inspect
|
127
|
+
%(#<#{self.class.name}: #{to_str.inspect}>)
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def swap_buffers
|
133
|
+
@offset -= @read_buffer.bytesize
|
134
|
+
@read_buffer = @write_buffer
|
135
|
+
@write_buffer = ''
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/cql/future.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'socket'
|
4
|
-
require 'resolv-replace'
|
5
4
|
|
6
5
|
|
7
6
|
module Cql
|
@@ -13,8 +12,8 @@ module Cql
|
|
13
12
|
@connected_future = Future.new
|
14
13
|
@io = nil
|
15
14
|
@addrinfo = nil
|
16
|
-
@write_buffer =
|
17
|
-
@read_buffer =
|
15
|
+
@write_buffer = ByteBuffer.new
|
16
|
+
@read_buffer = ByteBuffer.new
|
18
17
|
@current_frame = Protocol::ResponseFrame.new(@read_buffer)
|
19
18
|
@response_tasks = [nil] * 128
|
20
19
|
@event_listeners = Hash.new { |h, k| h[k] = [] }
|
@@ -29,7 +28,7 @@ module Cql
|
|
29
28
|
@io = Socket.new(address_family, socket_type, 0)
|
30
29
|
@io.connect_nonblock(@sockaddr)
|
31
30
|
rescue Errno::EINPROGRESS
|
32
|
-
#
|
31
|
+
# NOTE not connected yet, this is expected
|
33
32
|
rescue SystemCallError, SocketError => e
|
34
33
|
fail_connection!(e)
|
35
34
|
end
|
@@ -72,22 +71,6 @@ module Cql
|
|
72
71
|
@io && (!@write_buffer.empty? || connecting?)
|
73
72
|
end
|
74
73
|
|
75
|
-
def perform_request(request, future)
|
76
|
-
stream_id = next_stream_id
|
77
|
-
Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
|
78
|
-
@response_tasks[stream_id] = future
|
79
|
-
rescue => e
|
80
|
-
case e
|
81
|
-
when CqlError
|
82
|
-
error = e
|
83
|
-
else
|
84
|
-
error = IoError.new(e.message)
|
85
|
-
error.set_backtrace(e.backtrace)
|
86
|
-
end
|
87
|
-
@response_tasks.delete(stream_id)
|
88
|
-
future.fail!(error)
|
89
|
-
end
|
90
|
-
|
91
74
|
def handle_read
|
92
75
|
new_bytes = @io.read_nonblock(2**16)
|
93
76
|
@current_frame << new_bytes
|
@@ -107,11 +90,30 @@ module Cql
|
|
107
90
|
force_close(e)
|
108
91
|
end
|
109
92
|
|
93
|
+
def perform_request(request, future)
|
94
|
+
stream_id = next_stream_id
|
95
|
+
Protocol::RequestFrame.new(request, stream_id).write(@write_buffer)
|
96
|
+
@response_tasks[stream_id] = future
|
97
|
+
rescue => e
|
98
|
+
case e
|
99
|
+
when CqlError
|
100
|
+
error = e
|
101
|
+
else
|
102
|
+
error = IoError.new(e.message)
|
103
|
+
error.set_backtrace(e.backtrace)
|
104
|
+
end
|
105
|
+
@response_tasks.delete(stream_id)
|
106
|
+
future.fail!(error)
|
107
|
+
end
|
108
|
+
|
110
109
|
def handle_write
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
110
|
+
if connecting?
|
111
|
+
handle_connecting
|
112
|
+
else
|
113
|
+
if !@write_buffer.empty?
|
114
|
+
bytes_written = @io.write_nonblock(@write_buffer)
|
115
|
+
@write_buffer.discard(bytes_written)
|
116
|
+
end
|
115
117
|
end
|
116
118
|
rescue => e
|
117
119
|
force_close(e)
|
@@ -124,8 +126,9 @@ module Cql
|
|
124
126
|
@io.connect_nonblock(@sockaddr)
|
125
127
|
succeed_connection!
|
126
128
|
end
|
129
|
+
rescue Errno::EALREADY, Errno::EINPROGRESS
|
130
|
+
# NOTE still not connected
|
127
131
|
rescue Errno::EISCONN
|
128
|
-
# ok
|
129
132
|
succeed_connection!
|
130
133
|
rescue SystemCallError, SocketError => e
|
131
134
|
fail_connection!(e)
|
@@ -136,7 +139,7 @@ module Cql
|
|
136
139
|
begin
|
137
140
|
@io.close
|
138
141
|
rescue SystemCallError
|
139
|
-
# nothing to do, it wasn't open
|
142
|
+
# NOTE nothing to do, it wasn't open
|
140
143
|
end
|
141
144
|
if connecting?
|
142
145
|
succeed_connection!
|
data/lib/cql/protocol.rb
CHANGED
@@ -6,14 +6,13 @@ module Cql
|
|
6
6
|
extend self
|
7
7
|
|
8
8
|
def read_byte!(buffer)
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
buffer.read_byte
|
10
|
+
rescue RangeError => e
|
11
|
+
raise DecodingError, e.message, e.backtrace
|
12
12
|
end
|
13
13
|
|
14
14
|
def read_varint!(buffer, length=buffer.length, signed=true)
|
15
|
-
|
16
|
-
bytes = buffer.slice!(0, length)
|
15
|
+
bytes = buffer.read(length)
|
17
16
|
n = 0
|
18
17
|
bytes.each_byte do |b|
|
19
18
|
n = (n << 8) | b
|
@@ -22,62 +21,72 @@ module Cql
|
|
22
21
|
n -= 2**(bytes.length * 8)
|
23
22
|
end
|
24
23
|
n
|
24
|
+
rescue RangeError => e
|
25
|
+
raise DecodingError, e.message, e.backtrace
|
25
26
|
end
|
26
27
|
|
27
28
|
def read_decimal!(buffer, length=buffer.length)
|
28
|
-
raise DecodingError, "Length #{length} specifed but only #{buffer.bytesize} bytes given" if buffer.bytesize < length
|
29
29
|
size = read_int!(buffer)
|
30
|
-
|
31
|
-
number_string = read_varint!(number_bytes).to_s
|
30
|
+
number_string = read_varint!(buffer, length - 4).to_s
|
32
31
|
fraction_string = number_string[0, number_string.length - size] << DECIMAL_POINT << number_string[number_string.length - size, number_string.length]
|
33
32
|
BigDecimal.new(fraction_string)
|
33
|
+
rescue RangeError => e
|
34
|
+
raise DecodingError, e.message, e.backtrace
|
34
35
|
end
|
35
36
|
|
36
37
|
def read_long!(buffer)
|
37
|
-
|
38
|
-
top, bottom = buffer.slice!(0, 8).unpack(Formats::TWO_INTS_FORMAT)
|
38
|
+
top, bottom = buffer.read(8).unpack(Formats::TWO_INTS_FORMAT)
|
39
39
|
(top << 32) | bottom
|
40
|
+
rescue RangeError => e
|
41
|
+
raise DecodingError, e.message, e.backtrace
|
40
42
|
end
|
41
43
|
|
42
44
|
def read_double!(buffer)
|
43
|
-
|
44
|
-
|
45
|
+
buffer.read(8).unpack(Formats::DOUBLE_FORMAT).first
|
46
|
+
rescue RangeError => e
|
47
|
+
raise DecodingError, "Not enough bytes available to decode a double: #{e.message}", e.backtrace
|
45
48
|
end
|
46
49
|
|
47
50
|
def read_float!(buffer)
|
48
|
-
|
49
|
-
|
51
|
+
buffer.read(4).unpack(Formats::FLOAT_FORMAT).first
|
52
|
+
rescue RangeError => e
|
53
|
+
raise DecodingError, "Not enough bytes available to decode a float: #{e.message}", e.backtrace
|
50
54
|
end
|
51
55
|
|
52
56
|
def read_int!(buffer)
|
53
|
-
|
54
|
-
|
57
|
+
buffer.read_int
|
58
|
+
rescue RangeError => e
|
59
|
+
raise DecodingError, "Not enough bytes available to decode an int: #{e.message}", e.backtrace
|
55
60
|
end
|
56
61
|
|
57
62
|
def read_short!(buffer)
|
58
|
-
|
59
|
-
|
63
|
+
buffer.read_short
|
64
|
+
rescue RangeError => e
|
65
|
+
raise DecodingError, "Not enough bytes available to decode a short: #{e.message}", e.backtrace
|
60
66
|
end
|
61
67
|
|
62
68
|
def read_string!(buffer)
|
63
69
|
length = read_short!(buffer)
|
64
|
-
|
65
|
-
string = buffer.slice!(0, length)
|
70
|
+
string = buffer.read(length)
|
66
71
|
string.force_encoding(::Encoding::UTF_8)
|
67
72
|
string
|
73
|
+
rescue RangeError => e
|
74
|
+
raise DecodingError, "Not enough bytes available to decode a string: #{e.message}", e.backtrace
|
68
75
|
end
|
69
76
|
|
70
77
|
def read_long_string!(buffer)
|
71
78
|
length = read_int!(buffer)
|
72
|
-
|
73
|
-
string = buffer.slice!(0, length)
|
79
|
+
string = buffer.read(length)
|
74
80
|
string.force_encoding(::Encoding::UTF_8)
|
75
81
|
string
|
82
|
+
rescue RangeError => e
|
83
|
+
raise DecodingError, "Not enough bytes available to decode a long string: #{e.message}", e.backtrace
|
76
84
|
end
|
77
85
|
|
78
86
|
def read_uuid!(buffer)
|
79
|
-
raise DecodingError, "UUID requires 16 bytes, but only #{buffer.bytesize} bytes given" if buffer.bytesize < 16
|
80
87
|
Uuid.new(read_varint!(buffer, 16, false))
|
88
|
+
rescue RangeError => e
|
89
|
+
raise DecodingError, "Not enough bytes available to decode a UUID: #{e.message}", e.backtrace
|
81
90
|
end
|
82
91
|
|
83
92
|
def read_string_list!(buffer)
|
@@ -90,19 +99,17 @@ module Cql
|
|
90
99
|
def read_bytes!(buffer)
|
91
100
|
size = read_int!(buffer)
|
92
101
|
return nil if size & 0x80000000 == 0x80000000
|
93
|
-
|
94
|
-
|
95
|
-
bytes.
|
96
|
-
bytes
|
102
|
+
buffer.read(size)
|
103
|
+
rescue RangeError => e
|
104
|
+
raise DecodingError, "Not enough bytes available to decode a bytes: #{e.message}", e.backtrace
|
97
105
|
end
|
98
106
|
|
99
107
|
def read_short_bytes!(buffer)
|
100
108
|
size = read_short!(buffer)
|
101
109
|
return nil if size & 0x8000 == 0x8000
|
102
|
-
|
103
|
-
|
104
|
-
bytes.
|
105
|
-
bytes
|
110
|
+
buffer.read(size)
|
111
|
+
rescue RangeError => e
|
112
|
+
raise DecodingError, "Not enough bytes available to decode a short bytes: #{e.message}", e.backtrace
|
106
113
|
end
|
107
114
|
|
108
115
|
def read_option!(buffer)
|
@@ -116,10 +123,11 @@ module Cql
|
|
116
123
|
|
117
124
|
def read_inet!(buffer)
|
118
125
|
size = read_byte!(buffer)
|
119
|
-
|
120
|
-
ip_addr = IPAddr.new_ntoh(buffer.slice!(0, size))
|
126
|
+
ip_addr = IPAddr.new_ntoh(buffer.read(size))
|
121
127
|
port = read_int!(buffer)
|
122
128
|
[ip_addr, port]
|
129
|
+
rescue RangeError => e
|
130
|
+
raise DecodingError, "Not enough bytes available to decode an INET: #{e.message}", e.backtrace
|
123
131
|
end
|
124
132
|
|
125
133
|
def read_consistency!(buffer)
|