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 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
- require 'cql'
21
+ ```ruby
22
+ require 'cql'
22
23
 
23
- client = Cql::Client.connect(host: 'cassandra.example.com')
24
- client.use('system')
25
- rows = client.execute('SELECT keyspace_name, columnfamily_name FROM schema_columnfamilies')
26
- rows.each do |row|
27
- puts "The keyspace #{row['keyspace_name']} has a table called #{row['columnfamily_name']}""
28
- end
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
- client.use('measurements')
36
+ ```ruby
37
+ client.use('measurements')
38
+ ```
35
39
 
36
40
  or using CQL:
37
41
 
38
- client.execute('USE measurements')
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
- client.execute("INSERT INTO events (id, date, description) VALUES (23462, '2013-02-24T10:14:23+0000', 'Rang bell, ate food')")
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
- rows = client.execute('SELECT date, description FROM events')
52
- rows.each do |row|
53
- row.each do |key, value|
54
- puts "#{key} = #{value}"
55
- end
56
- end
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
- rows = client.execute('SELECT date, description FROM events'
61
- rows.metadata['date'].type # => :date
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
- keyspace_definition = <<-KSDEF
68
- CREATE KEYSPACE measurements
69
- WITH replication = {
70
- 'class': 'SimpleStrategy',
71
- 'replication_factor': 3
72
- }
73
- KSDEF
74
-
75
- table_definition = <<- TABLEDEF
76
- CREATE TABLE events (
77
- id INT,
78
- date DATE,
79
- comment VARCHAR,
80
- PRIMARY KEY (id)
81
- )
82
- TABLEDEF
83
-
84
- client.execute(keyspace_definition)
85
- client.use(measurements)
86
- client.execute(table_definition)
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
- statement = client.prepare('SELECT date, description FROM events WHERE id = ?')
95
- rows = statement.execute(1235)
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
- client.execute('SELECT * FROM peers', :local_quorum)
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
- * If any connection raises an error the whole IO reactor shuts down.
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
@@ -5,6 +5,7 @@ module Cql
5
5
  end
6
6
 
7
7
  require 'cql/uuid'
8
+ require 'cql/byte_buffer'
8
9
  require 'cql/future'
9
10
  require 'cql/io'
10
11
  require 'cql/protocol'
@@ -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
@@ -1,5 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'thread'
4
+
5
+
3
6
  module Cql
4
7
  FutureError = Class.new(CqlError)
5
8
 
@@ -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
- # ok
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
- succeed_connection! if connecting?
112
- if !@write_buffer.empty?
113
- bytes_written = @io.write_nonblock(@write_buffer)
114
- @write_buffer.slice!(0, bytes_written)
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!
@@ -26,7 +26,7 @@ module Cql
26
26
 
27
27
  BYTES_FORMAT = 'C*'.freeze
28
28
  TWO_INTS_FORMAT = 'NN'.freeze
29
- HEADER_FORMAT = 'c4N'.freeze
29
+ HEADER_FORMAT = 'c4'.freeze
30
30
  end
31
31
 
32
32
  module Constants
@@ -6,14 +6,13 @@ module Cql
6
6
  extend self
7
7
 
8
8
  def read_byte!(buffer)
9
- raise DecodingError, 'No byte available to decode' if buffer.empty?
10
- b = buffer.slice!(0, 1)
11
- b.getbyte(0)
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
- raise DecodingError, "Length #{length} specifed but only #{buffer.bytesize} bytes given" if buffer.bytesize < length
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
- number_bytes = buffer.slice!(0, length - 4)
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
- raise DecodingError, "Need eight bytes to decode long, only #{buffer.bytesize} bytes given" if buffer.bytesize < 8
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
- raise DecodingError, "Need eight bytes to decode double, only #{buffer.bytesize} bytes given" if buffer.bytesize < 8
44
- buffer.slice!(0, 8).unpack(Formats::DOUBLE_FORMAT).first
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
- raise DecodingError, "Need four bytes to decode float, only #{buffer.bytesize} bytes given" if buffer.bytesize < 4
49
- buffer.slice!(0, 4).unpack(Formats::FLOAT_FORMAT).first
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
- raise DecodingError, "Need four bytes to decode an int, only #{buffer.bytesize} bytes given" if buffer.bytesize < 4
54
- buffer.slice!(0, 4).unpack(Formats::INT_FORMAT).first
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
- raise DecodingError, "Need two bytes to decode a short, only #{buffer.bytesize} bytes given" if buffer.bytesize < 2
59
- buffer.slice!(0, 2).unpack(Formats::SHORT_FORMAT).first
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
- raise DecodingError, "String length is #{length}, but only #{buffer.bytesize} bytes given" if buffer.bytesize < length
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
- raise DecodingError, "String length is #{length}, but only #{buffer.bytesize} bytes given" if buffer.bytesize < length
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
- raise DecodingError, "Byte array length is #{size}, but only #{buffer.bytesize} bytes given" if buffer.bytesize < size
94
- bytes = buffer.slice!(0, size)
95
- bytes.force_encoding(::Encoding::BINARY)
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
- raise DecodingError, "Byte array length is #{size}, but only #{buffer.bytesize} bytes given" if buffer.bytesize < size
103
- bytes = buffer.slice!(0, size)
104
- bytes.force_encoding(::Encoding::BINARY)
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
- raise DecodingError, "Inet requires #{size} bytes, but only #{buffer.bytesize} bytes given" if buffer.bytesize < size
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)