cql-rb 1.0.0.pre7 → 1.0.0.pre8
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.
- data/lib/cql/byte_buffer.rb +22 -0
- data/lib/cql/client.rb +79 -310
- data/lib/cql/client/asynchronous_client.rb +254 -0
- data/lib/cql/client/asynchronous_prepared_statement.rb +19 -0
- data/lib/cql/client/column_metadata.rb +22 -0
- data/lib/cql/client/query_result.rb +34 -0
- data/lib/cql/client/result_metadata.rb +31 -0
- data/lib/cql/client/synchronous_client.rb +47 -0
- data/lib/cql/client/synchronous_prepared_statement.rb +47 -0
- data/lib/cql/future.rb +7 -3
- data/lib/cql/io.rb +1 -0
- data/lib/cql/io/io_reactor.rb +9 -3
- data/lib/cql/io/node_connection.rb +2 -2
- data/lib/cql/protocol.rb +5 -4
- data/lib/cql/protocol/request.rb +23 -0
- data/lib/cql/protocol/requests/credentials_request.rb +1 -1
- data/lib/cql/protocol/requests/execute_request.rb +13 -84
- data/lib/cql/protocol/requests/options_request.rb +1 -1
- data/lib/cql/protocol/requests/prepare_request.rb +2 -1
- data/lib/cql/protocol/requests/query_request.rb +3 -1
- data/lib/cql/protocol/requests/register_request.rb +1 -1
- data/lib/cql/protocol/requests/startup_request.rb +1 -2
- data/lib/cql/protocol/{response_body.rb → response.rb} +1 -1
- data/lib/cql/protocol/responses/authenticate_response.rb +1 -1
- data/lib/cql/protocol/responses/error_response.rb +1 -1
- data/lib/cql/protocol/responses/ready_response.rb +1 -1
- data/lib/cql/protocol/responses/result_response.rb +1 -1
- data/lib/cql/protocol/responses/rows_result_response.rb +1 -1
- data/lib/cql/protocol/responses/supported_response.rb +1 -1
- data/lib/cql/protocol/type_converter.rb +226 -46
- data/lib/cql/version.rb +1 -1
- data/spec/cql/byte_buffer_spec.rb +38 -0
- data/spec/cql/client/asynchronous_client_spec.rb +472 -0
- data/spec/cql/client/client_shared.rb +27 -0
- data/spec/cql/client/synchronous_client_spec.rb +104 -0
- data/spec/cql/client/synchronous_prepared_statement_spec.rb +65 -0
- data/spec/cql/future_spec.rb +4 -0
- data/spec/cql/io/io_reactor_spec.rb +39 -20
- data/spec/cql/protocol/request_spec.rb +17 -0
- data/spec/cql/protocol/requests/credentials_request_spec.rb +82 -0
- data/spec/cql/protocol/requests/execute_request_spec.rb +174 -0
- data/spec/cql/protocol/requests/options_request_spec.rb +24 -0
- data/spec/cql/protocol/requests/prepare_request_spec.rb +70 -0
- data/spec/cql/protocol/requests/query_request_spec.rb +95 -0
- data/spec/cql/protocol/requests/register_request_spec.rb +24 -0
- data/spec/cql/protocol/requests/startup_request_spec.rb +29 -0
- data/spec/integration/client_spec.rb +26 -19
- data/spec/integration/protocol_spec.rb +2 -2
- data/spec/integration/regression_spec.rb +1 -1
- metadata +35 -9
- data/lib/cql/protocol/request_body.rb +0 -15
- data/lib/cql/protocol/request_frame.rb +0 -20
- data/spec/cql/client_spec.rb +0 -454
- data/spec/cql/protocol/request_frame_spec.rb +0 -456
data/lib/cql/byte_buffer.rb
CHANGED
@@ -105,6 +105,28 @@ module Cql
|
|
105
105
|
b
|
106
106
|
end
|
107
107
|
|
108
|
+
def update(location, bytes)
|
109
|
+
absolute_offset = @offset + location
|
110
|
+
bytes_length = bytes.bytesize
|
111
|
+
if absolute_offset >= @read_buffer.bytesize
|
112
|
+
@write_buffer[absolute_offset - @read_buffer.bytesize, bytes_length] = bytes
|
113
|
+
else
|
114
|
+
overflow = absolute_offset + bytes_length - @read_buffer.bytesize
|
115
|
+
read_buffer_portion = bytes_length - overflow
|
116
|
+
@read_buffer[absolute_offset, read_buffer_portion] = bytes[0, read_buffer_portion]
|
117
|
+
if overflow > 0
|
118
|
+
@write_buffer[0, overflow] = bytes[read_buffer_portion, bytes_length - 1]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def cheap_peek
|
124
|
+
if @offset >= @read_buffer.bytesize
|
125
|
+
swap_buffers
|
126
|
+
end
|
127
|
+
@read_buffer[@offset, @read_buffer.bytesize - @offset]
|
128
|
+
end
|
129
|
+
|
108
130
|
def eql?(other)
|
109
131
|
self.to_str.eql?(other.to_str)
|
110
132
|
end
|
data/lib/cql/client.rb
CHANGED
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module Cql
|
4
4
|
class QueryError < CqlError
|
5
|
-
attr_reader :code
|
5
|
+
attr_reader :code, :cql
|
6
6
|
|
7
|
-
def initialize(code, message)
|
7
|
+
def initialize(code, message, cql=nil)
|
8
8
|
super(message)
|
9
9
|
@code = code
|
10
|
+
@cql = cql
|
10
11
|
end
|
11
12
|
end
|
12
13
|
|
@@ -39,15 +40,13 @@ module Cql
|
|
39
40
|
#
|
40
41
|
# Client instances are threadsafe.
|
41
42
|
#
|
42
|
-
|
43
|
+
# See {Cql::Client::Client} for the full client API.
|
44
|
+
#
|
45
|
+
module Client
|
43
46
|
NotConnectedError = Class.new(ClientError)
|
44
47
|
InvalidKeyspaceNameError = Class.new(ClientError)
|
45
48
|
|
46
|
-
# Create a new client.
|
47
|
-
#
|
48
|
-
# Creating a client does not automatically connect to Cassandra, you need to
|
49
|
-
# call {#connect} to connect, or use {Client.connect}. `#connect` returns
|
50
|
-
# `self` so you can chain that call after `new`.
|
49
|
+
# Create a new client and connects to Cassandra.
|
51
50
|
#
|
52
51
|
# @param [Hash] options
|
53
52
|
# @option options [String] :host ('localhost') One or more (comma separated)
|
@@ -57,237 +56,83 @@ module Cql
|
|
57
56
|
# connection, in seconds
|
58
57
|
# @option options [String] :keyspace The keyspace to change to immediately
|
59
58
|
# after all connections have been established, this is optional.
|
60
|
-
|
61
|
-
connection_timeout = options[:connection_timeout]
|
62
|
-
@host = options[:host] || 'localhost'
|
63
|
-
@port = options[:port] || 9042
|
64
|
-
@io_reactor = options[:io_reactor] || Io::IoReactor.new(connection_timeout: connection_timeout)
|
65
|
-
@lock = Mutex.new
|
66
|
-
@started = false
|
67
|
-
@shut_down = false
|
68
|
-
@initial_keyspace = options[:keyspace]
|
69
|
-
@credentials = options[:credentials]
|
70
|
-
@connection_keyspaces = {}
|
71
|
-
end
|
72
|
-
|
73
|
-
def self.connect(options={})
|
74
|
-
new(options).connect
|
75
|
-
end
|
76
|
-
|
77
|
-
# Connect to all nodes.
|
78
|
-
#
|
79
|
-
# You must call this method before you call any of the other methods of a
|
80
|
-
# client. Calling it again will have no effect.
|
81
|
-
#
|
82
|
-
# If `:keyspace` was specified when the client was created the current
|
83
|
-
# keyspace will also be changed (otherwise the current keyspace will not
|
84
|
-
# be set).
|
85
|
-
#
|
86
|
-
# @return self
|
87
|
-
#
|
88
|
-
def connect
|
89
|
-
@lock.synchronize do
|
90
|
-
return if @started
|
91
|
-
@started = true
|
92
|
-
@io_reactor.start
|
93
|
-
hosts = @host.split(',')
|
94
|
-
connection_futures = hosts.map { |host| connect_to_host(host) }
|
95
|
-
@connection_ids = Future.combine(*connection_futures).get
|
96
|
-
end
|
97
|
-
use(@initial_keyspace) if @initial_keyspace
|
98
|
-
self
|
99
|
-
rescue => e
|
100
|
-
close
|
101
|
-
if e.is_a?(Cql::QueryError) && e.code == 0x100
|
102
|
-
raise AuthenticationError, e.message, e.backtrace
|
103
|
-
else
|
104
|
-
raise
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
# @deprecated Use {#connect} or {.connect}
|
109
|
-
def start!
|
110
|
-
$stderr.puts('Client#start! is deprecated, use Client#connect, or Client.connect')
|
111
|
-
connect
|
112
|
-
end
|
113
|
-
|
114
|
-
# Disconnect from all nodes.
|
115
|
-
#
|
116
|
-
# @return self
|
117
|
-
#
|
118
|
-
def close
|
119
|
-
@lock.synchronize do
|
120
|
-
return if @shut_down || !@started
|
121
|
-
@shut_down = true
|
122
|
-
@started = false
|
123
|
-
end
|
124
|
-
@io_reactor.stop.get
|
125
|
-
self
|
126
|
-
end
|
127
|
-
|
128
|
-
# @deprecated Use {#close}
|
129
|
-
def shutdown!
|
130
|
-
$stderr.puts('Client#shutdown! is deprecated, use Client#close')
|
131
|
-
close
|
132
|
-
end
|
133
|
-
|
134
|
-
# Returns whether or not the client is connected.
|
135
|
-
#
|
136
|
-
def connected?
|
137
|
-
@started
|
138
|
-
end
|
139
|
-
|
140
|
-
# Returns the name of the current keyspace, or `nil` if no keyspace has been
|
141
|
-
# set yet.
|
142
|
-
#
|
143
|
-
def keyspace
|
144
|
-
@lock.synchronize do
|
145
|
-
return @connection_ids.map { |id| @connection_keyspaces[id] }.first
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Changes keyspace by sending a `USE` statement to all connections.
|
150
|
-
#
|
151
|
-
# The the second parameter is meant for internal use only.
|
152
|
-
#
|
153
|
-
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
154
|
-
#
|
155
|
-
def use(keyspace, connection_ids=@connection_ids)
|
156
|
-
raise NotConnectedError unless connected?
|
157
|
-
if check_keyspace_name!(keyspace)
|
158
|
-
@lock.synchronize do
|
159
|
-
connection_ids = connection_ids.select { |id| @connection_keyspaces[id] != keyspace }
|
160
|
-
end
|
161
|
-
if connection_ids.any?
|
162
|
-
futures = connection_ids.map do |connection_id|
|
163
|
-
execute_request(Protocol::QueryRequest.new("USE #{keyspace}", :one), connection_id)
|
164
|
-
end
|
165
|
-
futures.compact!
|
166
|
-
Future.combine(*futures).get
|
167
|
-
end
|
168
|
-
nil
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Execute a CQL statement
|
173
|
-
#
|
174
|
-
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
175
|
-
# @raise [Cql::QueryError] raised when the CQL has syntax errors or for
|
176
|
-
# other situations when the server complains.
|
177
|
-
# @return [nil, Enumerable<Hash>] Most statements have no result and return
|
178
|
-
# `nil`, but `SELECT` statements return an `Enumerable` of rows
|
179
|
-
# (see {QueryResult}).
|
180
|
-
#
|
181
|
-
def execute(cql, consistency=DEFAULT_CONSISTENCY_LEVEL)
|
182
|
-
raise NotConnectedError unless connected?
|
183
|
-
result = execute_request(Protocol::QueryRequest.new(cql, consistency)).value
|
184
|
-
ensure_keyspace!
|
185
|
-
result
|
186
|
-
end
|
187
|
-
|
188
|
-
# @private
|
189
|
-
def execute_statement(connection_id, statement_id, metadata, values, consistency)
|
190
|
-
raise NotConnectedError unless connected?
|
191
|
-
execute_request(Protocol::ExecuteRequest.new(statement_id, metadata, values, consistency || DEFAULT_CONSISTENCY_LEVEL), connection_id).value
|
192
|
-
end
|
193
|
-
|
194
|
-
# Returns a prepared statement that can be run over and over again with
|
195
|
-
# different values.
|
196
|
-
#
|
197
|
-
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
198
|
-
# @return [Cql::PreparedStatement] an object encapsulating the prepared statement
|
59
|
+
# @return [Cql::Client::Client]
|
199
60
|
#
|
200
|
-
def
|
201
|
-
|
202
|
-
execute_request(Protocol::PrepareRequest.new(cql)).value
|
61
|
+
def self.connect(options={})
|
62
|
+
SynchronousClient.new(AsynchronousClient.new(options)).connect
|
203
63
|
end
|
204
64
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
65
|
+
class Client
|
66
|
+
# @!method connect
|
67
|
+
#
|
68
|
+
# Connect to all nodes.
|
69
|
+
#
|
70
|
+
# You must call this method before you call any of the other methods of a
|
71
|
+
# client. Calling it again will have no effect.
|
72
|
+
#
|
73
|
+
# If `:keyspace` was specified when the client was created the current
|
74
|
+
# keyspace will also be changed (otherwise the current keyspace will not
|
75
|
+
# be set).
|
76
|
+
#
|
77
|
+
# @return self
|
216
78
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
end
|
223
|
-
end
|
79
|
+
# @!method close
|
80
|
+
#
|
81
|
+
# Disconnect from all nodes.
|
82
|
+
#
|
83
|
+
# @return self
|
224
84
|
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
execute_request(credentials_request, connection_id).map { connection_id }
|
231
|
-
else
|
232
|
-
Future.failed(AuthenticationError.new('Server requested authentication, but no credentials given'))
|
233
|
-
end
|
234
|
-
else
|
235
|
-
Future.completed(connection_id)
|
236
|
-
end
|
237
|
-
end
|
85
|
+
# @!method connected?
|
86
|
+
#
|
87
|
+
# Returns whether or not the client is connected.
|
88
|
+
#
|
89
|
+
# @return [true, false]
|
238
90
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
91
|
+
# @!method keyspace
|
92
|
+
#
|
93
|
+
# Returns the name of the current keyspace, or `nil` if no keyspace has been
|
94
|
+
# set yet.
|
95
|
+
#
|
96
|
+
# @param [String] keyspace
|
97
|
+
# @return [nil]
|
244
98
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
@lock.synchronize do
|
255
|
-
@last_keyspace_change = @connection_keyspaces[connection_id] = response.keyspace
|
256
|
-
end
|
257
|
-
nil
|
258
|
-
when Protocol::AuthenticateResponse
|
259
|
-
AuthenticationRequired.new(response.authentication_class)
|
260
|
-
else
|
261
|
-
nil
|
262
|
-
end
|
263
|
-
end
|
99
|
+
# @!method use(keyspace)
|
100
|
+
#
|
101
|
+
# Changes keyspace by sending a `USE` statement to all connections.
|
102
|
+
#
|
103
|
+
# The the second parameter is meant for internal use only.
|
104
|
+
#
|
105
|
+
# @param [String] keyspace
|
106
|
+
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
107
|
+
# @return [nil]
|
264
108
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
109
|
+
# @!method execute(cql, consistency=:quorum)
|
110
|
+
#
|
111
|
+
# Execute a CQL statement
|
112
|
+
#
|
113
|
+
# @param [String] cql
|
114
|
+
# @param [Symbol] consistency
|
115
|
+
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
116
|
+
# @raise [Cql::QueryError] raised when the CQL has syntax errors or for
|
117
|
+
# other situations when the server complains.
|
118
|
+
# @return [nil, Cql::Client::QueryResult] Most statements have no result and return
|
119
|
+
# `nil`, but `SELECT` statements return an `Enumerable` of rows
|
120
|
+
# (see {Cql::Client::QueryResult}).
|
121
|
+
|
122
|
+
# @!method prepare(cql)
|
123
|
+
#
|
124
|
+
# Returns a prepared statement that can be run over and over again with
|
125
|
+
# different values.
|
126
|
+
#
|
127
|
+
# @param [String] cql
|
128
|
+
# @raise [Cql::NotConnectedError] raised when the client is not connected
|
129
|
+
# @return [Cql::Client::PreparedStatement] an object encapsulating the prepared statement
|
272
130
|
end
|
273
131
|
|
274
|
-
public
|
275
|
-
|
276
|
-
# The representation of a prepared statement.
|
277
|
-
#
|
278
|
-
# Prepared statements are parsed once and stored on the server, allowing
|
279
|
-
# you to execute them over and over again but only send values for the bound
|
280
|
-
# parameters.
|
281
|
-
#
|
282
132
|
class PreparedStatement
|
283
133
|
# @return [ResultMetadata]
|
284
134
|
attr_reader :metadata
|
285
135
|
|
286
|
-
def initialize(*args)
|
287
|
-
@client, @connection_id, @statement_id, @raw_metadata = args
|
288
|
-
@metadata = ResultMetadata.new(@raw_metadata)
|
289
|
-
end
|
290
|
-
|
291
136
|
# Execute the prepared statement with a list of values for the bound parameters.
|
292
137
|
#
|
293
138
|
# The number of arguments must equal the number of bound parameters.
|
@@ -298,92 +143,16 @@ module Cql
|
|
298
143
|
#
|
299
144
|
# @param args [Array] the values for the bound parameters, and optionally
|
300
145
|
# the desired consistency level, as a symbol (defaults to :quorum)
|
301
|
-
#
|
302
146
|
def execute(*args)
|
303
|
-
bound_args = args.shift(@raw_metadata.size)
|
304
|
-
consistency_level = args.shift
|
305
|
-
@client.execute_statement(@connection_id, @statement_id, @raw_metadata, bound_args, consistency_level)
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
class AuthenticationRequired
|
310
|
-
attr_reader :authentication_class
|
311
|
-
|
312
|
-
def initialize(authentication_class)
|
313
|
-
@authentication_class = authentication_class
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
class QueryResult
|
318
|
-
include Enumerable
|
319
|
-
|
320
|
-
# @return [ResultMetadata]
|
321
|
-
attr_reader :metadata
|
322
|
-
|
323
|
-
# @private
|
324
|
-
def initialize(metadata, rows)
|
325
|
-
@metadata = ResultMetadata.new(metadata)
|
326
|
-
@rows = rows
|
327
|
-
end
|
328
|
-
|
329
|
-
# Returns whether or not there are any rows in this result set
|
330
|
-
#
|
331
|
-
def empty?
|
332
|
-
@rows.empty?
|
333
|
-
end
|
334
|
-
|
335
|
-
# Iterates over each row in the result set.
|
336
|
-
#
|
337
|
-
# @yieldparam [Hash] row each row in the result set as a hash
|
338
|
-
# @return [Enumerable<Hash>]
|
339
|
-
#
|
340
|
-
def each(&block)
|
341
|
-
@rows.each(&block)
|
342
|
-
end
|
343
|
-
alias_method :each_row, :each
|
344
|
-
end
|
345
|
-
|
346
|
-
class ResultMetadata
|
347
|
-
include Enumerable
|
348
|
-
|
349
|
-
# @private
|
350
|
-
def initialize(metadata)
|
351
|
-
@metadata = metadata.each_with_object({}) { |m, h| h[m[2]] = ColumnMetadata.new(*m) }
|
352
|
-
end
|
353
|
-
|
354
|
-
# Returns the column metadata
|
355
|
-
#
|
356
|
-
# @return [ColumnMetadata] column_metadata the metadata for the column
|
357
|
-
#
|
358
|
-
def [](column_name)
|
359
|
-
@metadata[column_name]
|
360
|
-
end
|
361
|
-
|
362
|
-
# Iterates over the metadata for each column
|
363
|
-
#
|
364
|
-
# @yieldparam [ColumnMetadata] metadata the metadata for each column
|
365
|
-
# @return [Enumerable<ColumnMetadata>]
|
366
|
-
#
|
367
|
-
def each(&block)
|
368
|
-
@metadata.each_value(&block)
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
# Represents metadata about a column in a query result set or prepared
|
373
|
-
# statement. Apart from the keyspace, table and column names there's also
|
374
|
-
# the type as a symbol (e.g. `:varchar`, `:int`, `:date`).
|
375
|
-
class ColumnMetadata
|
376
|
-
attr_reader :keyspace, :table, :column_name, :type
|
377
|
-
|
378
|
-
# @private
|
379
|
-
def initialize(*args)
|
380
|
-
@keyspace, @table, @column_name, @type = args
|
381
|
-
end
|
382
|
-
|
383
|
-
# @private
|
384
|
-
def to_ary
|
385
|
-
[@keyspace, @table, @column_name, @type]
|
386
147
|
end
|
387
148
|
end
|
388
149
|
end
|
389
|
-
end
|
150
|
+
end
|
151
|
+
|
152
|
+
require 'cql/client/column_metadata'
|
153
|
+
require 'cql/client/result_metadata'
|
154
|
+
require 'cql/client/query_result'
|
155
|
+
require 'cql/client/asynchronous_prepared_statement'
|
156
|
+
require 'cql/client/synchronous_prepared_statement'
|
157
|
+
require 'cql/client/synchronous_client'
|
158
|
+
require 'cql/client/asynchronous_client'
|